From 2d6bc55b170d0c120b22a84eaf91c0b090d0f958 Mon Sep 17 00:00:00 2001 From: Bernardo Vianna Date: Thu, 17 Apr 2014 22:36:46 -0300 Subject: [PATCH] - Change command parsing to read the actual size of the data sent - Rename user when the data sent is a piece of text - Get origin address and port from config file - Use a buffered socket --- buffer.py | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++ controller.py | 75 +++++++++++++++++-------------- server.py | 49 +++++++++++++++------ user.py | 5 ++- 4 files changed, 203 insertions(+), 46 deletions(-) create mode 100644 buffer.py diff --git a/buffer.py b/buffer.py new file mode 100644 index 0000000..43e3b8c --- /dev/null +++ b/buffer.py @@ -0,0 +1,120 @@ +""" + Socket Buffer + Usage: + + buff = Buffer(socket_object) + buff.read(10) + +""" + + +class SocketClosedError(Exception): + pass + + +class _BaseBuffer(object): + + def __init__(self, size): + self._data = bytearray(size) + self._pos = 0 + self._max = 0 + self._require_resize = False + self._inconsistent_state = False + + def __len__(self): + return self._max - self._pos + + def _extract(self, amount): + pos = self._pos + + if amount: + self._pos += amount + else: + self._pos = self._max + + return self._data[pos:self._pos] + + def set_size(self, value): + self._require_resize = value + + def set(self, data, clear_data=False, fill_later=False): + """ Set new content for buffer. + + The buffer must be empty for data to be added unless + `clear_data` is set. + + If the buffer is being set directly, call this function + first with the `fill_later` argument set. This is required + for the buffer to be resized if needed and previous content + to be checked. + """ + if len(self) and not clear_data: + raise BufferError('Rewriting non empty buffer.') + + if self._require_resize: + self._data = bytearray(self._require_resize) + self._require_resize = False + + if fill_later: + self._inconsistent_state = True + else: + self._data[:] = data + self._pos = 0 + self._max = len(data) + + def set_fill(self, size): + """ To be used with the `fill_later` argument of `set` method + After the buffer is filled, this function will set the + filled size and set the state to consistent. + """ + self._pos = 0 + self._max = size + self._inconsistent_state = False + + def get(self, amount): + if self._inconsistent_state: + raise BufferError('Buffer is inconsistent.') + + read_size = amount if amount <= len(self) else 0 + return self._extract(read_size) + + def view(self): + return self._data + + +class Buffer(object): + + def __init__(self, socket, read_size=4096): + self.socket = socket + self._read_size = read_size + self.buffer = _BaseBuffer(read_size) + + @property + def read_size(self): + return self._read_size + + @read_size.setter + def read_size(self, value): + self._read_size = value + self.buffer.set_size(value) + + def read(self, amount, raise_error=None): + data = bytearray() + while True: + new_data = self.buffer.get(amount) + data.extend(new_data) + amount -= len(new_data) + if not amount: + break + + self.buffer.set(None, fill_later=True) + try: + read = self.socket.recv_into(self.buffer.view(), self.read_size) + if not read: + if raise_error: + raise SocketClosedError('Missing {:d} bytes'.format(amount)) + break + finally: + self.buffer.set_fill(read) + + return data diff --git a/controller.py b/controller.py index 35f706d..0e9267c 100644 --- a/controller.py +++ b/controller.py @@ -7,12 +7,18 @@ import config -_base_button = b'\x00\x03' -_base_press = b'a' -_base_release = b'b' +class CommandType(object): + button_press = b'a' + button_release = b'b' + text = b'c' + +# Byte sent to denote a new command +command_header = b'\x00' + + +# Button pressed on Max Remote original_map = { - # Button pressed b'65': 'a', b'66': 'b', b'10': 'start', @@ -23,15 +29,18 @@ b'40': 'down', } +# Button to be sent to Max Remote Server +# The choice is A, B, C, ... because it is +# less harmful than the original with control characters button_hold = { - 'a': b'65', - 'b': b'66', - 'start': b'67', - 'select': b'68', - 'left': b'69', - 'up': b'70', - 'right': b'71', - 'down': b'72', + 'a': b'65', # A + 'b': b'66', # B + 'start': b'67', # C + 'select': b'68', # D + 'left': b'69', # E + 'up': b'70', # F + 'right': b'71', # G + 'down': b'72', # H } cmds = list(button_hold) @@ -44,22 +53,25 @@ def find_command(val): - if not val.startswith(_base_button): - return None - - val = val[len(_base_button):] st = val[:1] - val = original_map.get(val[1:3]) + val = val[1:] + if st == CommandType.text: + return val.decode('utf-8'), st + val = original_map.get(bytes(val)) if val is None: return None return val, st -class Controller(object): - ADDR = '127.0.0.1' - PORT = 8589 +def build_command(st, val): + data = st + val + return command_header + bytes([len(data)]) + data + +class Controller(object): + ADDR = config.parser.gete('origin', 'addr') + PORT = config.parser.gete('origin', 'port') cmd_callback = lambda *args: None grab_callback = lambda *args: None @@ -131,38 +143,37 @@ def run_queue(self): while True: self.modes[config.selected]() - def send_command(self, cmd, correct_cmd=False, user=''): + def send_command(self, cmd, user=''): if not config.enabled: return - if not correct_cmd: - cmd = find_command(cmd) - if cmd is None: - return - cmd, st = cmd if config.hold_click: - if st != _base_press: + if st != CommandType.button_press: return # Do not send at command end - #self.send_click(cmd) with self.cmds_lock: self.commands.put((cmd, user)) else: + # TODO: This should be disabled at least on + # Democracy/Raffle modes. It may break the game + # with multiple people holding commands at the same time self.send_click(cmd, st) def send_click(self, cmd, st=None): - sst = _base_press if st is None else st + sst = CommandType.button_press if st is None else st if config.delay: time.sleep(config.delay) - self.send_message(_base_button + sst + button_hold[cmd]) + self.send_message(build_command(sst, button_hold[cmd])) if st is not None: return time.sleep(config.click_duration) - self.send_message(_base_button + _base_release + button_hold[cmd]) + self.send_message(build_command(CommandType.button_release, button_hold[cmd])) def send_message(self, msg): + """ Low level function to handle connections and send atual data to TCP socket. + """ if self.conn is None: self.connect() @@ -185,4 +196,4 @@ def send_udp(self, cmd): self.udp.sendto(cmd, (self.ADDR, self.PORT)) -controller = Controller() +controller_instance = Controller() \ No newline at end of file diff --git a/server.py b/server.py index d7af9e2..09672b4 100644 --- a/server.py +++ b/server.py @@ -1,8 +1,9 @@ import socketserver import threading import config -from controller import controller -from user import User, PersonUser +import buffer +import controller +from user import PersonUser class OriginServer(object): @@ -24,21 +25,43 @@ class UDPServer( class Handler(socketserver.BaseRequestHandler): + user = None + buffer = None + def handle(self): + self.buffer = buffer.Buffer(self.request) ip = self.client_address[0] - print('TCP Connection:', ip) - user = PersonUser.get(ip, ip) - print(user) - print(User.users) + self.user = PersonUser.get(ip, ip) + print('TCP Connection:', self.user) while True: - data = self.request.recv(64) - if not data: - break - print('Data:', repr(data), list(data)) - controller.send_command(data, user=user.name) - #print('Sent to origin') - #self.request.sendall(data, self.client_address) + try: + content = self.read_command() + except buffer.SocketClosedError: + return + self.handle_content(content) + + def read_command(self): + startbyte = None + while startbyte != controller.command_header: + startbyte = self.buffer.read(1, True) + + size = self.buffer.read(1, True) + return self.buffer.read(size[0], True) + + def handle_content(self, content): + print('Data @ {0.name}, {0.ip}:'.format(self.user), bytes(content), list(content)) + + content = controller.find_command(content) + if content is None: + return + + cmd, st = content + if st == controller.CommandType.text: + self.user.name = cmd # TODO change user name on interface too? + return + + controller.controller_instance.send_command(content, user=self.user.name) class UDPHandler(Handler): diff --git a/user.py b/user.py index f32a45c..3f6ada0 100644 --- a/user.py +++ b/user.py @@ -73,5 +73,8 @@ def run(self): time.sleep(t) cmd = random.choice(controller.cmds) print('NAME:', self.name, '| SLEPT:', t, '| SENT:', cmd) - controller.controller.send_command((cmd, b'a'), correct_cmd=True, user=self.name) + controller.controller_instance.send_command( + (cmd, controller.CommandType.button_press), + correct_cmd=True, user=self.name + )