File: PropertyListPacketProtocol.py

package info (click to toggle)
oolite 1.77.1-3
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 41,264 kB
  • ctags: 5,362
  • sloc: objc: 132,090; ansic: 10,457; python: 2,225; sh: 1,325; makefile: 332; perl: 259; xml: 125; php: 5
file content (147 lines) | stat: -rw-r--r-- 4,250 bytes parent folder | download
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