1 # -*- coding: utf-8 -*-
3 # This library is free software, distributed under the terms of
4 # the GNU Lesser General Public License Version 2.
5 # See the COPYING.LESSER file included in this archive
7 # The docstrings in this module contain epytext markup; API documentation
8 # may be created by processing this file with epydoc: http://epydoc.sf.net
10 Library for WAP transport, original by Francois Aucamp, modified by Nick Leppänen Larsson
13 @author: Francois Aucamp <faucamp@csir.co.za>
14 @author: Nick Leppänen Larsson <frals@frals.se>
20 from iterator import PreviewIterator
23 """ This class implements a very limited subset of the WTP layer """
24 pduTypes = {0x00: None, # Not Used
29 0x05: 'Segmented Invoke',
30 0x06: 'Segmented Result',
33 abortTypes = {0x00: 'PROVIDER',
36 abortReasons = {0x00: 'UNKNOWN',
39 0x03: 'NOTIMPLEMENTEDCL2',
40 0x04: 'NOTIMPLEMENTEDSAR',
41 0x05: 'NOTIMPLEMENTEDUACK',
42 0x06: 'WTPVERSIONONE',
43 0x07: 'CAPTEMPEXCEEDED',
45 0x09: 'MESSAGETOOLARGE',
46 0x10: 'NOTIMPLEMENTEDESAR'}
48 def __init__(self, gatewayHost, gatewayPort=9201):
49 self.udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
51 # Currently "active" WTP transactions (their IDs)
52 self.activeTransactions = []
53 self.gatewayHost = gatewayHost
54 self.gatewayPort = gatewayPort
56 def invoke(self, wspPDU):
57 """ Invoke (send) a request via WTP, and get the response.
59 This method automatically assigns a new unique transaction ID to the
62 @return: an iterator over the bytes read from the response
63 @rtype: mms.iterator.previewIterator
66 print '>> WTP: Invoke, transaction ID: %d' % self.tidCounter
67 pdu = self.encodeInvokePDU(self.tidCounter) + wspPDU
69 print '>> WTP: Sent PDU'
70 self.activeTransactions.append(self.tidCounter)
71 return self._parseResponse(self._receiveData())
73 def ack(self, transactionID):
74 print '>> WTP: Ack, transaction ID: %d' % transactionID
75 self._sendPDU(self.encodeAckPDU(transactionID))
77 def _sendPDU(self, pdu):
78 """ Transmits a PDU through the socket
80 @param pdu: The PDU to send (a sequence of bytes)
86 self.udpSocket.sendto(data, (self.gatewayHost, self.gatewayPort))
88 def _receiveData(self):
89 """ Read data from the UDP socket
91 @return: The data read from the socket
97 print '>> WTP: Receiving data'
99 buff = self.udpSocket.recv(1024)
106 def _parseResponse(self, responseData):
107 """ Interpret data read from the socket (at the WTP layer level)
109 @param responseData: A buffer containing data to interpret
110 @type responseData: str
112 byteArray = array.array('B')
114 for char in responseData:
115 byteArray.append(ord(char))
116 byteIter = PreviewIterator(byteArray)
117 pduType, transactionID = self._decodePDU(byteIter)
118 if pduType == 'Result':
119 self.ack(transactionID)
124 def encodeInvokePDU(tid):
125 """ Builds a WTP Invoke PDU
127 @param tid: The transaction ID for this PDU
130 @return: the WTP invoke PDU as a sequence of bytes
133 The WTP Invoke PDU structure is defined in WAP-224, section 8.3.1::
134 Bit| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
135 Octet | | | | | | | |
136 1 |CON| PDU Type = Invoke |GTR|TDR|RID
139 4 |Version |TIDnew| U/P | RES |RES| TCL
141 ...where bit 0 is the most significant bit.
143 Invoke PDU type = 0x01 = 0 0 0 1
144 GTR is 0 and TDR is 1 (check: maybe make both 1: segmentation not supported)
145 RID is set to 0 (not retransmitted)
146 TCL is 0x02 == 1 0 (transaction class 2)
147 Version is 0x00 (according to WAP-224, section 8.3.1)
148 Thus, for our Invoke, this is::
149 Bit| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
150 Octet | | | | | | | |
151 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0
154 4 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0
156 #TODO: check GTR and TDR values (probably should rather be 11, for segmentation not supported)
157 pdu = [0x0a] # 0000 1010
158 pdu.extend(WTP._encodeTID(tid))
159 pdu.append(0x12) # 0001 0010
163 def encodeAckPDU(tid):
164 """ Builds a WTP Ack PDU (acknowledge)
166 @param tid: The transaction ID for this PDU
169 @return: the WTP invoke PDU as a sequence of bytes
172 The WTP PDU structure is defined in WAP-224, section 8
173 The ACK PDU structure is described in WAP-224, section 8.3.3::
174 Bit| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
175 Octet | | | | | | | |
176 1 |CON|PDU Type = Acknowledgement|Tve/Tok|RES|RID
180 ...where bit 0 is the most significant bit.
182 Thus, for our ACK, this is::
183 Bit| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
184 Octet | | | | | | | |
185 1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0
186 | PDU type = 0x03 = 0011 |
190 pdu = [0x18] # binary: 00011000
191 pdu.extend(WTP._encodeTID(tid))
194 def _decodePDU(self, byteIter):
195 """ Reads and decodes a WTP PDU from the sequence of bytes starting at
196 the byte pointed to by C{dataIter.next()}.
198 @param byteIter: an iterator over a sequence of bytes
199 @type byteIteror: mms.iterator.PreviewIterator
201 @note: If the PDU type is correctly determined, byteIter will be
202 modified in order to read past the amount of bytes required
205 @return: The PDU type, and the transaction ID, in the format:
206 (str:<pdu_type>, int:<transaction_id>)
209 byte = byteIter.preview()
210 byteIter.resetPreview()
212 pduType = (byte >> 3) & 0x0f
213 pduValue = (None, None)
214 if pduType not in WTP.pduTypes:
215 #TODO: maybe raise some error or something
216 print 'Error - unknown WTP PDU type: %s' % hex(pduType)
218 print '<< WTP: %s' % WTP.pduTypes[pduType],
220 exec 'pduValue = self._decode%sPDU(byteIter)' % WTP.pduTypes[pduType]
222 print 'A fatal error occurred, probably due to an unimplemented feature.\n'
224 # after this follows the WSP pdu(s)....
227 def _decodeResultPDU(self, byteIter):
228 """ Decodes a WTP Result PDU
230 @param byteIter: an iterator over a sequence of bytes
231 @type byteIteror: mms.iterator.PreviewIterator
233 The WTP Result PDU structure is defined in WAP-224, section 8.3.2::
234 Bit| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
235 Octet | | | | | | | |
236 1 |CON| PDU Type = Result |Tve/Tok|RES|RID
240 The WTP Result PDU Type is 0x02, according to WAP-224, table 11
245 bytes.append(byteIter.next())
246 pduType = (bytes[0] >> 3) & 0x0f
247 # Get the transaction ID
248 transactionID = WTP._decodeTID(bytes[1:])
249 print 'transaction ID: %d' % transactionID
250 if transactionID in self.activeTransactions:
251 self.activeTransactions.remove(transactionID)
252 return (WTP.pduTypes[pduType], transactionID)
254 def _decodeAckPDU(self, byteIter):
255 """ Decodes a WTP Result PDU
257 @param byteIter: an iterator over a sequence of bytes
258 @type byteIteror: mms.iterator.PreviewIterator
260 The ACK PDU structure is described in WAP-224, section 8.3.3::
261 Bit| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
262 Octet | | | | | | | |
263 1 |CON|PDU Type = Acknowledgement|Tve/Tok|RES|RID
267 The WTP Result PDU Type is 0x03, according to WAP-224, table 11
272 bytes.append(byteIter.next())
273 pduType = (bytes[0] >> 3) & 0x0f
274 # Get the transaction ID
275 transactionID = WTP._decodeTID(bytes[1:])
276 print 'transaction ID: %d' % transactionID
277 if transactionID not in self.activeTransactions:
278 self.activeTransactions.append(transactionID)
279 return (WTP.pduTypes[pduType], transactionID)
281 def _decodeAbortPDU(self, byteIter):
282 """ Decodes a WTP Abort PDU
284 @param byteIter: an iterator over a sequence of bytes
285 @type byteIteror: mms.iterator.PreviewIterator
287 The WTP Result PDU structure is defined in WAP-224, section 8.3.2::
288 Bit| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
289 Octet | | | | | | | |
290 1 |CON| PDU Type = Result | Abort type
295 The WTP Abort PDU Type is 0x04, according to WAP-224, table 11
300 bytes.append(byteIter.next())
301 pduType = (bytes[0] >> 3) & 0x0f
302 abortType = bytes[0] & 0x07
303 abortReason = bytes[3]
304 if abortType in self.abortTypes:
305 abortType = self.abortTypes[abortType]
307 abortType = str(abortType)
308 if abortReason in self.abortReasons:
309 abortReason = self.abortReasons[abortReason]
311 abortReason = str(abortReason)
312 # Get the transaction ID
313 transactionID = WTP._decodeTID(bytes[1:3])
314 print 'transaction ID: %d' % transactionID
315 if transactionID in self.activeTransactions:
316 self.activeTransactions.remove(transactionID)
317 print 'WTP: Abort, type: %s, reason: %s' % (abortType, abortReason)
318 return (WTP.pduTypes[pduType], transactionID)
321 def _encodeTID(transactionID):
322 """ Encodes the specified transaction ID into the format used in
323 WTP PDUs (makes sure it spans 2 bytes)
325 From WAP-224, section 7.8.1: The TID is 16-bits but the high order bit
326 is used to indicate the direction. This means that the TID space is
327 2**15. The TID is an unsigned integer.
329 @param transactionID: The transaction ID to encode
330 @type transactionID: int
332 @return: The encoded transaction ID as a sequence of bytes
335 if transactionID > 0x7FFF:
336 raise ValueError, 'Transaction ID too large (must fit into 15 bits): %d' % transactionID
338 encodedTID = [transactionID & 0xFF]
339 encodedTID.insert(0, transactionID >> 8)
343 def _decodeTID(bytes):
344 """ Decodes the transaction ID contained in <bytes>
346 From WAP-224, section 7.8.1: The TID is 16-bits but the high order bit
347 is used to indicate the direction. This means that the TID space is
348 2**15. The TID is an unsigned integer.
350 @param bytes: The byte sequence containing the transaction ID
353 @return: The decoded transaction ID