"""Bmp: the base marshal protocol

by Phillip Lenhardt <philen@monkey.org>

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','<string>','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)

