Example #1
0
 def test_unpack_message(self):
     ''' Former docstring tests from Connection.unpack_message().'''
     c = Connection()
     c.rep = Representation({0x4101: 'Sth'}, c.proto.base_rep)
     msg = [HLO.number, BRA.number, 0x4101, KET.number]
     unpacked = c.unpack_message(pack('!HHHH', *msg))
     self.failUnlessEqual(repr(unpacked),
                          "Message([HLO, [Token('Sth', 0x4101)]])")
Example #2
0
 def read_representation(self, data):
     ''' Creates a representation dictionary from the RM.
         This dictionary maps names to numbers and vice-versa.
     '''#'''
     if data:
         rep = {}
         fieldlen = 6
         for i in xrange(0, len(data), fieldlen):
             num, name = unpack('!H3sx', data[i:i+fieldlen])
             rep[num] = name
         self.rep = Representation(rep, self.proto.base_rep)
     else: self.rep = self.proto.default_rep
Example #3
0
 def test_rep_equals_dict(self):
     first = Representation({0x4A00: "ONE"}, protocol.base_rep)
     second = {"ONE": 0x4A00}
     self.failUnlessEqual(first, second)
Example #4
0
 def test_simple_reps_equal(self):
     first = Representation({0x4A00: "ONE"}, protocol.base_rep)
     second = Representation({0x4A00: "ONE"}, protocol.base_rep)
     self.failUnlessEqual(first, second)
Example #5
0
 def test_empty_reps_equal(self):
     first = Representation({}, protocol.base_rep)
     second = Representation({}, protocol.base_rep)
     self.failUnlessEqual(first, second)
Example #6
0
 def test_unpack_message(self):
     rep = Representation({0x4101: 'Sth'}, protocol.base_rep)
     msg = [HLO.number, BRA.number, 0x4101, KET.number]
     unpacked = rep.unpack(pack('!HHHH', *msg))
     self.failUnlessEqual(repr(unpacked),
         "Message([HLO, [Token('Sth', 0x4101)]])")
Example #7
0
 def test_passed_rep(self):
     rep = Representation({0x4A00: "ONE"}, protocol.base_rep)
     variant = Variant("testing", rep=rep)
     self.failUnlessEqual(variant.rep, rep)
Example #8
0
class DaideProtocol(VerboseObject, StatefulProtocol, TimeoutMixin):
    r'''Base methods for the DAIDE Client-Server Protocol.'''
    
    __options__ = (
        ('echo_final', bool, False, 'send unnecessary final messages',
            'Whether to send FM after receiving EM or FM.',
            'The extra FM may be useful to terminate input loops,',
            'particularly when using threads, but the protocol prohibits it.'),
        ('null_rm', bool, False, 'send empty representation messages',
            'Whether to send an empty RM for the standard map.',
            'The standard says yes, but that may be changed soon.'),
    )
    
    def connectionMade(self):
        self.configure(protocol)
        self.final_sent = False
        self.closed = False
    def close(self, notified=False):
        self.log.debug("Closing")
        self.closed = True
        self.setTimeout(None)
        if not self.final_sent:
            if self.options.echo_final or not notified:
                self.send_dcsp(self.FM, "")
            self.final_sent = True
        self.transport.loseConnection()
    
    def getInitialState(self):
        self.log.debug("Initializing State")
        return (self.read_header, 4)
    def dataReceived(self, data):
        #self.log.debug("Processing %r", data)
        StatefulProtocol.dataReceived(self, data)
    
    def configure(self, proto):
        self.send_final = True
        self.proto = proto
        self.rep = None
        
        # IM, DM, FM, etc.
        self.handlers = {}
        for name, value in proto.message_types.iteritems():
            abbr = name[0] + 'M'
            setattr(self, abbr, value)
            self.handlers[value] = getattr(self, "read_" + abbr)
    
    def write(self, message):
        self.send_dcsp(self.DM, message.pack())
    def send_dcsp(self, msg_type, data):
        r'''Sends a DCSP message to the client.
            msg_type must be an integer, one of the defined message types.
            data must be a packed binary string.
        '''#'''
        #self.log.debug("Sending %s: %r", msg_type, data)
        msg = pack('!BxH', msg_type, len(data)) + data
        self.transport.write(msg)
    
    def read_header(self, data):
        msg_type, msg_len = unpack('!BxH', data)
        #self.log.debug("Header %d/%d: %r", msg_type, msg_len, data)
        error = None
        if self.first:
            first, err = self.first
            if msg_type not in (first, self.FM, self.EM):
                error = err
            elif msg_type == self.IM and msg_len == 0x0400:
                error = self.proto.EndianError
            self.first = None
        
        if error is None:
            try:
                self.__handler = self.handlers[msg_type]
            except KeyError:
                error = self.proto.MessageTypeError
        
        if error is None:
            self.setTimeout(30)
            state = (self.read_body, msg_len)
        else:
            self.send_error(error)
            state = (self.read_closed, 4)
        
        return state
    def read_body(self, data):
        # Handles timeouts and state transitions for the handlers
        #self.log.debug("%s(%r)", self.__handler.__name__, data)
        self.setTimeout(None)
        self.__handler(data)
        if self.closed:
            state = (self.read_closed, 4)
        else:
            state = (self.read_header, 4)
        return state
    def read_closed(self, data):
        return None
    
    def read_IM(self, data):
        r'''Verifies the Initial Message from the client.'''
        if len(data) == 4:
            (version, magic) = unpack('!HH', data)
            if magic == self.proto.magic:
                if version == self.proto.version:
                    # Success!
                    address = self.transport.getPeer().host
                    self.service = Service(self, address,
                        self.factory.server, self.factory.game)
                else: self.send_error(self.proto.VersionError)
            elif unpack('<HH', data)[1] == self.proto.magic:
                self.send_error(self.proto.EndianError)
            else: self.send_error(self.proto.MagicError)
        else: self.send_error(self.proto.LengthError)
        
        self.handlers[self.IM] = self.duplicate_IM
    def duplicate_IM(self, data):
        self.send_error(self.proto.DuplicateIMError)
    
    def send_RM(self, representation):
        ''' Sends the representation message to the client.
            This implementation can be configured to always sends a full RM,
            or to rely on the default for the Standard map.
        '''#'''
        if representation == self.rep:
            # No need to send again.
            return
        
        self.rep = representation
        if self.options.null_rm and representation == self.proto.default_rep:
            data = ''
        else:
            data = str.join('', (pack('!H3sx', token.number, name)
                        for name, token in representation.items()))
        self.send_dcsp(self.RM, data)
    def read_RM(self, data):
        # This might check for UnexpectedRM, but that interferes with SEL.
        if len(data) % 6:
            self.send_error(self.proto.LengthError)
        else:
            self.read_representation(data)
    def read_representation(self, data):
        ''' Creates a representation dictionary from the RM.
            This dictionary maps names to numbers and vice-versa.
        '''#'''
        if data:
            rep = {}
            fieldlen = 6
            for i in xrange(0, len(data), fieldlen):
                num, name = unpack('!H3sx', data[i:i+fieldlen])
                rep[num] = name
            self.rep = Representation(rep, self.proto.base_rep)
        else: self.rep = self.proto.default_rep
    
    def read_DM(self, data):
        if len(data) % 2 or not data:
            self.send_error(self.proto.LengthError)
        elif not self.rep:
            self.send_error(self.proto.EarlyDMError)
        else:
            msg = self.unpack_message(data)
            if msg:
                self.handle_message(msg)
            else:
                self.send_error(self.proto.IllegalToken)
    def unpack_message(self, data):
        r'''Produces a Message from a string of token numbers.
            Uses values in the representation, if available.
        '''#'''
        try:
            msg = self.rep.unpack(data)
        except ValueError:
            # Someone foolishly chose to disconnect over an unknown token.
            msg = None
        else:
            # Tokens in the "Reserved for AI use" category
            # must never be sent over the wire.
            if any('Reserved' in token.category_name() for token in msg):
                msg = None
        return msg
    def handle_message(self, msg):
        raise NotImplementedError
    
    def send_error(self, code):
        self.log_error("Foreign", code)
        self.send_dcsp(self.EM, pack('!H', code))
        self.close(True)
    def read_EM(self, data):
        code = unpack('!H', data)[0]
        self.log_error("Local", code)
        self.close(True)
    def log_error(self, faulty, code):
        text = self.proto.error_strings.get(code, "Unknown")
        self.log.error("%s error 0x%02X (%s)", faulty, code, text)
    def timeoutConnection(self):
        self.send_error(self.proto.LengthError)
    
    def read_FM(self, data):
        self.close(True)