"""Bmp: the base marshal protocol by Phillip Lenhardt Bmp message format is 4 bytes encoding body length followed by that many bytes of data. # The protocol state machine has the following stream states: Wait = 0 # The stream is between messages. Header = 1 # The stream is inside the header of a message. Body = 3 # The stream is inside the body of a message. Control = 4 # The stream is inside a control message. """ import types import struct import marshal import string class BMPError(Exception): pass def encode_length(length): minlength = 0 maxlength = 2147483647 if type(length) == types.IntType and minlength <= length <= maxlength: hi,lo = divmod(length,65536) hihi,hilo = divmod(hi,256) lohi,lolo = divmod(lo,256) header = struct.pack('!4B',hihi,hilo,lohi,lolo) if '\377' in header: header = string.replace(header,'\377','\377\377') return header return None def decode_header(header): if type(header) == types.StringType and len(header) == 4: hihi,hilo,lohi,lolo = struct.unpack('!4B',header) return hihi * 16777216 + hilo * 65536 + lohi * 256 + lolo return None def encode_object(object): try: body = marshal.dumps(object) if '\377' in body: body = string.replace(body,'\377','\377\377') return body except: raise BMPError, 'unencodable object' def decode_body(body): try: return marshal.loads(body) except: raise BMPError, 'malformed message body' def encode_message(object): """Returns a string on successful encoding and None on failure.""" body = encode_object(object) if body != None: header = encode_length(len(body)) if header != None: return header + body return None Wait = 0 Header = 1 Body = 2 Control = 3 Headerlen = 4 class bmpStateMachine: """Subclass for useful functionality. Override initialize, handle_message and handle_control. Call handle_bytes with bytes from a stream of properly formatted bmp messages. """ def __init__(self): self.state = Wait self.savestate = Wait self.header = '' self.body = '' self.bodylen = 0 self.initialize() def handle_bytes(self,bytes): for byte in bytes: if self.state == Control: if byte != '\377': self.state = self.savestate self.handle_control(byte) continue if byte == '\377': self.savestate = self.state self.state = Control elif self.state == Wait: self.header = self.header + byte self.state = Header elif self.state == Header: self.header = self.header + byte if len(self.header) == Headerlen: self.bodylen = decode_header(self.header) self.header = '' self.state = Body elif self.state == Body: self.body = self.body + byte if len(self.body) == self.bodylen: message = decode_body(self.body) self.body = '' self.state = Wait self.handle_message(message) # Overridable methods. def initialize(self): """Override in subclass to get useful subclass initialization""" pass def handle_message(self,message): """Override in subclass to get useful message handling.""" print 'Got message object',`message` def handle_control(self,control): """Override in subclass to get useful control handling.""" print 'Got control byte',control if __name__ == '__main__': class testBmpStateMachine(bmpStateMachine): def test_object(self,object): self.object = object self.handle_bytes(encode_message(object)) def test_control(self,byte): self.byte = byte self.handle_bytes('\377' + byte) def handle_message(self,message): if self.object == message: print 'Test of',type(self.object).__name__,'type succeeded.' else: print 'Test of',type(self.object).__name__,'type failed.' def handle_control(self,control): if self.byte == control: print 'Test of control byte',`self.byte`,'succeeded.' else: print 'Test of control byte',`self.byte`,'failed.' b = testBmpStateMachine() b.test_object(None) b.test_object(1) b.test_object(1L) b.test_object(1.0) b.test_object('abc') b.test_object((0,1,2)) b.test_object([0,1,2]) b.test_object({1:1}) b.test_object(compile('1 + 1','','eval')) b.test_control('\000') b.test_control('\001') b.test_control('\376') print 'Testing embedded control in messages' message = encode_message(None) message = message[0] + '\377\000' + message[1:] b.byte = '\000' b.object = None b.handle_bytes(message) message = encode_message('abcdefgh') message = message[0] + '\377\000' + message[1:] b.byte = '\000' b.object = 'abcdefgh' b.handle_bytes(message)