Skip to content

Commit

Permalink
- Change command parsing to read the actual size of the data sent
Browse files Browse the repository at this point in the history
 - Rename user when the data sent is a piece of text
 - Get origin address and port from config file
 - Use a buffered socket
  • Loading branch information
jbvsmo committed Apr 18, 2014
1 parent 50d6351 commit 2d6bc55
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 46 deletions.
120 changes: 120 additions & 0 deletions buffer.py
Original file line number Diff line number Diff line change
@@ -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
75 changes: 43 additions & 32 deletions controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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()

Expand All @@ -185,4 +196,4 @@ def send_udp(self, cmd):
self.udp.sendto(cmd, (self.ADDR, self.PORT))


controller = Controller()
controller_instance = Controller()
49 changes: 36 additions & 13 deletions server.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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):
Expand Down
5 changes: 4 additions & 1 deletion user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

0 comments on commit 2d6bc55

Please sign in to comment.