1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
#
# PropertyListPacketProtocol.py
# ooliteConsoleServer
#
# Created by Jens Ayton on 2007-11-29.
# Copyright (c) 2007 Jens Ayton. All rights reserved.
#
from twisted.internet.protocol import Protocol
from plistlib import readPlist, writePlist
from cStringIO import StringIO
# These are part of plistlib.py on Mac OS X, but not in the files easily
# available on the web.
def readPlistFromString(data):
"""Read a plist data from a string. Return the root object.
"""
return readPlist(StringIO(data))
def writePlistToString(rootObject):
"""Return 'rootObject' as a plist-formatted string.
"""
f = StringIO()
writePlist(rootObject, f)
return f.getvalue()
class PropertyListPacketProtocol(Protocol):
"""
Class handling a property list packet stream.
Oolite's debug console is based on property lists. Each property list is a
self-contained entity, or packet. Since TCP is stream-oriented, it is
necessary to have a packet framing protocol on top of it.
The framing protocol used for the debug console is just about the simplest
possible: each frame has a header, which consists of four eight-bit bytes.
These form a 32-bit network-endian integer, specifying the length of the
packet data. This is followed by packet data. The packet data is an XML
property list.
This class is a Twisted protocol implementing the packet framing and XML
property list decoding (using plistlib to handle the details of that). It
is implemented as an implicit state machine, with two states: receiving
header (identified by a __sizeCount less than 4) and receiving data. When
a full data packet is received, it is decoded as a plist and dispatched
to a subclass's plistPacketReceived() method.
"""
__buffer = ""
__received = ""
__expect = 0
__sizeCount = 0
def dataReceived(self, data):
"""
Receive data from the network. This is called by Twisted.
This method handles the decoding of incoming packets and dispatches
them to be handled by the subclass implementation.
"""
# Append data to incoming buffer
self.__received += data
# Loop over buffer
while len(self.__received) > 0:
if self.__sizeCount < 4:
# Receiving header (size)
# Decode as big-endian 32-bit integer
self.__expect = (self.__expect << 8) + ord(self.__received[0])
self.__received = self.__received[1:]
self.__sizeCount += 1
else:
# Receiving data
if len(self.__received) < self.__expect:
# This is not the end of the data
self.__buffer += self.__received
self.__expect -= len(self.__received)
self.__received = ""
else:
# End of packet reached
self.__buffer += self.__received[:self.__expect]
self.__received = self.__received[self.__expect:]
try:
self.__dispatchPacket()
finally:
# Expect new packet
self.__reset()
def sendPlistPacket(self, packet):
"""
Send a packet (property list). Called by subclass or client objects.
This encodes an XML plist, adds the header and sends it over the
network connection.
"""
data = None
try:
if packet:
data = writePlistToString(packet)
except:
data = None
if data:
length = len(data)
self.transport.write(chr((length >> 24) & 0xFF))
self.transport.write(chr((length >> 16) & 0xFF))
self.transport.write(chr((length >> 8) & 0xFF))
self.transport.write(chr(length & 0xFF))
self.transport.write(data)
else:
self.badPListSend(packet)
def __dispatchPacket(self):
# Decode plist and send to subclass method
plist = None
try:
plist = readPlistFromString(self.__buffer)
except:
plist = None
if plist: self.plistPacketReceived(plist)
else: self.badPacketReceived(self.__buffer)
def __reset(self):
# Reset to waiting-for-beginning-of-packet state.
self.__expect = 0
self.__sizeCount = 0
self.__buffer = ""
def plistPacketReceived(self, plist):
# Doing something useful with the plist is a subclass responsibilitiy.
pass
def badPacketReceived(self, data):
# Called for bad (non-plist) packets; subclasses may override.
pass
def badPListSend(self, plist):
# Called for invalid (non-plist) objects sent to sendPListPacket(); subclasses may override.
pass
|