Commits

Szymon Wróblewski committed 937e6ec

added structtype, expanded Peer class

Comments (0)

Files changed (4)

                 # no empty slot for new peer
                 return  # TODO: raise exception
             else:
-                peer = Peer(self, address, channel_count, data)
+                peer = Peer(self, address, i + 1, channel_count, data)
                 self.peers.append(peer)
                 return peer
         # reuse slot
-        self.peers[i] = peer = Peer(self, address, channel_count, data)
+        self.peers[i] = peer = Peer(self, address, i, channel_count, data)
         return peer
+import random
 import protocol
 
 DEFAULT_ROUND_TRIP_TIME = 500
     ZOMBIE) = range(10)
 
 
+class Channel(object):
+    def __init__(self):
+        self.outgoing_reliable_sequence_number = 0
+        self.outgoing_unreliable_sequence_number = 0
+        self.used_reliable_windows = 0
+        self.reliable_windows = []
+        self.incoming_reliable_sequence_number = 0
+        self.incoming_unreliable_sequence_number = 0
+        self.incoming_reliable_commands = []
+        self.incoming_unreliable_commands = []
+
+
 class Peer(object):
     def __init__(self, host, address, peer_id, channel_count, data=None):
         self.host = host
         self.address = address
         self.data = data
         self.state = State.DISCONNECTED
-        self.channels =
-        self.channel_count = channel_count
+        self.channels = []
+        self.channel_count = 0
         self.incoming_bandwidth = 0
         self.outgoing_bandwidth = 0
         self.incoming_bandwidth_throttle_epoch = 0
         self.round_trip_time_variance = 0
         self.mtu = host.mtu
         self.window_size = protocol.MAXIMUM_WINDOW_SIZE
-        self.reliable_data_in_transit
-        self.outgoing_reliable_sequence_number
+        self.reliable_data_in_transit = 0
+        self.outgoing_reliable_sequence_number = 0
         self.acknowledgements = []
         self.sent_reliable_commands = []
         self.sent_unreliable_commands = []
         self.outgoing_reliable_commands = []
         self.outgoing_unreliable_commands = []
         self.dispatched_commands = []
-        self.needs_dispatch
+        self.needs_dispatch = 0
         self.incoming_unsequenced_group = 0
         self.outgoing_unsequenced_group = 0
         self.unsequenced_window = []  # probably bytearray or memoryview
         self.event_data = 0
+
+    def connect(self, address):
+        host = self.host
+        self.state = State.CONNECTING
+        self.address = address
+        self.connect_id = random.getrandbits(32)
+        if host.outgoing_bandwidth == 0:
+            window_size = protocol.MAXIMUM_WINDOW_SIZE
+        else:
+            window_size = host.outgoing_bandwidth / WINDOW_SIZE_SCALE\
+                          * protocol.MAXIMUM_WINDOW_SIZE
+        self.window_size = max(min(window_size, protocol.MAXIMUM_WINDOW_SIZE),
+                               protocol.MINIMUM_WINDOW_SIZE)
+        protocol.CommandHeader(
+            protocol.Command.CONNECT | protocol.Flag.COMMAND_ACKNOWLEDGE,
+            0xFF,
+            0
+        )
+

ppenet/protocol.py

-from collections import namedtuple
 import socket
-from struct import Struct
+from structtype import structtype
 
 
 MINIMUM_MTU = 576
 
 
 class Flag:
-    COMMAND_FLAG_ACKNOWLEDGE = 1 << 7
-    COMMAND_FLAG_UNSEQUENCED = 1 << 6
+    COMMAND_ACKNOWLEDGE = 1 << 7
+    COMMAND_UNSEQUENCED = 1 << 6
 
-    HEADER_FLAG_COMPRESSED = 1 << 14
-    HEADER_FLAG_SENT_TIME = 1 << 15
-    HEADER_FLAG_MASK = HEADER_FLAG_COMPRESSED | HEADER_FLAG_SENT_TIME
+    HEADER_COMPRESSED = 1 << 14
+    HEADER_SENT_TIME = 1 << 15
+    HEADER_MASK = HEADER_COMPRESSED | HEADER_SENT_TIME
     HEADER_SESSION_MASK = 3 << 12
     HEADER_SESSION_SHIFT = 12
 
 
-Header = namedtuple('Header', (
+Header = structtype('Header', (
     'peer_id',
     'sent_time',
-))
-HeaderStruct = Struct('!HH')
+), '!HH', default=0)
 
-CommandHeader = namedtuple('CommandHeader', (
+CommandHeader = structtype('CommandHeader', (
     'command',
     'channel_id',
     'reliable_sequence_number',
-))
-CommandHeaderStruct = Struct('!BBH')
+), '!BBH', default=0)
 
-Acknowledge = namedtuple('Acknowledge', (
+Acknowledge = structtype('Acknowledge', (
     'header',
     'received_reliable_sequence_number',
     'received_sent_time'
-))
-AcknowledgeStruct = Struct('!%dsHH' % CommandHeaderStruct.size)
+), '!%dsHH' % int(CommandHeader.size), default=0)
 
-Connect = namedtuple('Connect', (
+Connect = structtype('Connect', (
     'header',
     'outgoing_peer_id',
     'incoming_session_id',
     'packet_throttle_deceleration',
     'connect_id',
     'data',
-))
-ConnectStruct = Struct('!%dsHBB10I' % CommandHeaderStruct.size)
+), '!%dsHBB10I' % CommandHeader.size, default=0)
 
-VerifyConnect = namedtuple('VerifyConnect', (
+VerifyConnect = structtype('VerifyConnect', (
     'header',
     'outgoing_peer_id',
     'incoming_session_id',
     'packet_throttle_acceleration',
     'packet_throttle_deceleration',
     'connect_id',
-))
-VerifyConnectStruct = Struct('!%dsHBB9I' % CommandHeaderStruct.size)
+), '!%dsHBB9I' % CommandHeader.size, default=0)
 
-BandwidthLimit = namedtuple('BandwidthLimit', (
+BandwidthLimit = structtype('BandwidthLimit', (
     'header',
     'incoming_bandwidth',
     'outgoing_bandwidth',
-))
-BandwidthLimitStruct = Struct('!%dsII' % CommandHeaderStruct.size)
+), '!%dsII' % CommandHeader.size, default=0)
 
-ThrottleConfigure = namedtuple('ThrottleConfigure', (
+ThrottleConfigure = structtype('ThrottleConfigure', (
     'header',
     'packet_throttle_interval',
     'packet_throttle_acceleration',
     'packet_throttle_deceleration',
-))
-ThrottleConfigureStruct = Struct('!%dsIII' % CommandHeaderStruct.size)
+), '!%dsIII' % CommandHeader.size, default=0)
 
-Disconnect = namedtuple('Disconnect', (
+Disconnect = structtype('Disconnect', (
     'header',
     'data',
-))
-DisconnectStruct = Struct('!%dsI' % CommandHeaderStruct.size)
+), '!%dsI' % CommandHeader.size, default=0)
 
-Ping = namedtuple('Ping', (
+Ping = structtype('Ping', (
     'header',
-))
-PingStruct = Struct('!%ds' % CommandHeaderStruct.size)
+), '!%ds' % CommandHeader.size, default=0)
 
-SendReliable = namedtuple('SendReliable', (
+SendReliable = structtype('SendReliable', (
     'header',
     'data_length',
-))
-SendReliableStruct = Struct('!%dsH' % CommandHeaderStruct.size)
+), '!%dsH' % CommandHeader.size, default=0)
 
-SendUnreliable = namedtuple('SendUnreliable', (
+SendUnreliable = structtype('SendUnreliable', (
     'header',
     'unreliable_sequence_number',
     'data_length',
-))
-SendUnreliableStruct = Struct('!%dsHH' % CommandHeaderStruct.size)
+), '!%dsHH' % CommandHeader.size, default=0)
 
-SendUnsequenced = namedtuple('SendUnsequenced', (
+SendUnsequenced = structtype('SendUnsequenced', (
     'header',
     'unsequenced_group',
     'data_length',
-))
-SendUnsequencedStruct = Struct('!%dsHH' % CommandHeaderStruct.size)
+), '!%dsHH' % CommandHeader.size, default=0)
 
-SendFragment = namedtuple('SendFragment', (
+SendFragment = structtype('SendFragment', (
     'header',
     'start_sequence_number',
     'data_length',
     'fragment_number',
     'total_length',
     'fragment_offset',
-))
-SendFragmentStruct = Struct('!%dsHH4I' % CommandHeaderStruct.size)
+), '!%dsHH4I' % CommandHeader.size, default=0)

ppenet/structtype.py

+# based on recordtype recipe code
+# http://code.activestate.com/recipes/576555/
+
+__all__ = ['structtype']
+
+import sys
+import struct
+from textwrap import dedent
+from keyword import iskeyword
+
+
+def structtype(typename, field_names, format, verbose=False, **default_kwds):
+    '''Returns a new class with named fields.
+
+    @keyword field_defaults: A mapping from (a subset of) field names to
+        default values.
+    @keyword default: If provided, the default value for all fields without an
+        explicit default in `field_defaults`.
+
+    >>> Point = structtype('Point', 'x y', 'BB', default=0)
+    >>> Point.__doc__           # docstring for the new class
+    'Point(x, y)'
+    >>> Point()                 # instantiate with defaults
+    Point(x=0, y=0)
+    >>> p = Point(11, y=22)     # instantiate with positional args or keywords
+    >>> p[0] + p.y              # accessible by name and index
+    33
+    >>> p.x = 100; p[1] =200    # modifiable by name and index
+    >>> p
+    Point(x=100, y=200)
+    >>> x, y = p               # unpack
+    >>> x, y
+    (100, 200)
+    >>> d = p.todict()         # convert to a dictionary
+    >>> d['x']
+    100
+    >>> Point(**d) == p        # convert from a dictionary
+    True
+    '''
+    # Parse and validate the field names.  Validation serves two purposes,
+    # generating informative error messages and preventing template
+    # injection attacks.
+    if isinstance(field_names, basestring):
+        # names separated by whitespace and/or commas
+        field_names = field_names.replace(',', ' ').split()
+    field_names = tuple(map(str, field_names))
+    if not field_names:
+        raise ValueError('Records must have at least one field')
+    for name in (typename,) + field_names:
+        if not min(c.isalnum() or c == '_' for c in name):
+            raise ValueError('Type names and field names can only contain '
+                             'alphanumeric characters and underscores: %r' % name)
+        if iskeyword(name):
+            raise ValueError('Type names and field names cannot be a keyword: %r'
+                             % name)
+        if name[0].isdigit():
+            raise ValueError('Type names and field names cannot start with a '
+                             'number: %r' % name)
+    seen_names = set()
+    for name in field_names:
+        if name.startswith('_'):
+            raise ValueError('Field names cannot start with an underscore: %r'
+                             % name)
+        if name in seen_names:
+            raise ValueError('Encountered duplicate field name: %r' % name)
+        seen_names.add(name)
+    # determine the func_defaults of __init__
+    field_defaults = default_kwds.pop('field_defaults', {})
+    if 'default' in default_kwds:
+        default = default_kwds.pop('default')
+        init_defaults = tuple(field_defaults.get(f, default) for f in field_names)
+    elif not field_defaults:
+        init_defaults = None
+    else:
+        default_fields = field_names[-len(field_defaults):]
+        if set(default_fields) != set(field_defaults):
+            raise ValueError('Missing default parameter values')
+        init_defaults = tuple(field_defaults[f] for f in default_fields)
+    if default_kwds:
+        raise ValueError('Invalid keyword arguments: %s' % default_kwds)
+    # Create and fill-in the class template
+    numfields = len(field_names)
+    argtxt = ', '.join(field_names)
+    reprtxt = ', '.join('%s=%%r' % f for f in field_names)
+    dicttxt = ', '.join('%r: self.%s' % (f, f) for f in field_names)
+    tupletxt = repr(tuple('self.%s' % f for f in field_names)).replace("'", '')
+    inittxt = '; '.join('self.%s=%s' % (f, f) for f in field_names)
+    itertxt = '; '.join('yield self.%s' % f for f in field_names)
+    eqtxt = ' and '.join('self.%s==other.%s' % (f, f) for f in field_names)
+    template = dedent('''
+        class %(typename)s(object):
+            '%(typename)s(%(argtxt)s)'
+
+            __slots__  = %(field_names)r
+            __struct = struct.Struct('%(format)s')
+            format = __struct.format
+            size = __struct.size
+
+            def __init__(self, %(argtxt)s):
+                %(inittxt)s
+
+            def __len__(self):
+                return %(numfields)d
+
+            def __iter__(self):
+                %(itertxt)s
+
+            def __getitem__(self, index):
+                return getattr(self, self.__slots__[index])
+
+            def __setitem__(self, index, value):
+                return setattr(self, self.__slots__[index], value)
+
+            def todict(self):
+                'Return a new dict which maps field names to their values'
+                return {%(dicttxt)s}
+
+            def __repr__(self):
+                return '%(typename)s(%(reprtxt)s)' %% %(tupletxt)s
+
+            def __eq__(self, other):
+                return isinstance(other, self.__class__) and %(eqtxt)s
+
+            def __ne__(self, other):
+                return not self==other
+
+            def __getstate__(self):
+                return %(tupletxt)s
+
+            def __setstate__(self, state):
+                %(tupletxt)s = state
+
+            def pack(self):
+                return self.__struct.pack(*%(tupletxt)s)
+
+            def pack_into(self, buffer, offset=0):
+                return self.__struct.pack_into(buffer, offset, *%(tupletxt)s)
+
+            def unpack(self, string):
+                %(tupletxt)s =self.__struct.unpack(string)
+
+            def unpack_from(self, buffer, offset=0):
+                %(tupletxt)s = self.__struct.unpack_from(buffer, offset)
+    ''') % locals()
+    # Execute the template string in a temporary namespace
+    namespace = {'struct': struct}
+    try:
+        exec template in namespace
+        if verbose:
+            print template
+    except SyntaxError, e:
+        raise SyntaxError(e.message + ':\n' + template)
+    cls = namespace[typename]
+    cls.__init__.im_func.func_defaults = init_defaults
+    # For pickling to work, the __module__ variable needs to be set to the
+    # frame where the named tuple is created.  Bypass this step in environments
+    # where sys._getframe is not defined (Jython for example).
+    if hasattr(sys, '_getframe') and sys.platform != 'cli':
+        cls.__module__ = sys._getframe(1).f_globals['__name__']
+    return cls
+
+
+if __name__ == '__main__':
+    import doctest
+    TestResults = structtype('TestResults', 'failed, attempted')
+    print TestResults(*doctest.testmod())