initial commit
[fmms] / src / mms / WTP.py
1 # -*- coding: utf-8 -*-
2 #
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
6 #
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
9 """
10 Library for WAP transport, original by Francois Aucamp, modified by Nick Leppänen Larsson
11 for standalone use.
12
13 @author: Francois Aucamp <faucamp@csir.co.za>
14 @author: Nick Leppänen Larsson <frals@frals.se>
15 @license: GNU LGPL
16 """
17 import sys
18 import array
19 import socket, time
20 from iterator import PreviewIterator
21
22 class WTP:
23     """ This class implements a very limited subset of the WTP layer """
24     pduTypes = {0x00: None, # Not Used
25                 0x01: 'Invoke',
26                 0x02: 'Result',
27                 0x03: 'Ack',
28                 0x04: 'Abort',
29                 0x05: 'Segmented Invoke',
30                 0x06: 'Segmented Result',
31                 0x07: 'Negative Ack'}
32     
33     abortTypes = {0x00: 'PROVIDER',
34                   0x01: 'USER'}
35     
36     abortReasons = {0x00: 'UNKNOWN',
37                     0x01: 'PROTOERR',
38                     0x02: 'INVALIDTID',
39                     0x03: 'NOTIMPLEMENTEDCL2',
40                     0x04: 'NOTIMPLEMENTEDSAR',
41                     0x05: 'NOTIMPLEMENTEDUACK',
42                     0x06: 'WTPVERSIONONE',
43                     0x07: 'CAPTEMPEXCEEDED',
44                     0x08: 'NORESPONSE',
45                     0x09: 'MESSAGETOOLARGE',
46                     0x10: 'NOTIMPLEMENTEDESAR'}
47     
48     def __init__(self, gatewayHost, gatewayPort=9201):
49         self.udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
50         self.tidCounter = 0
51         # Currently "active" WTP transactions (their IDs)
52         self.activeTransactions = []
53         self.gatewayHost = gatewayHost
54         self.gatewayPort = gatewayPort
55     
56     def invoke(self, wspPDU):
57         """ Invoke (send) a request via WTP, and get the response.
58         
59         This method automatically assigns a new unique transaction ID to the 
60         transmitted PDU.
61         
62         @return: an iterator over the bytes read from the response
63         @rtype: mms.iterator.previewIterator
64         """
65         self.tidCounter += 1
66         print '>> WTP: Invoke, transaction ID: %d' % self.tidCounter
67         pdu = self.encodeInvokePDU(self.tidCounter) + wspPDU
68         self._sendPDU(pdu)
69         print '>> WTP: Sent PDU'
70         self.activeTransactions.append(self.tidCounter)    
71         return self._parseResponse(self._receiveData())
72     
73     def ack(self, transactionID):
74         print '>> WTP: Ack, transaction ID: %d' % transactionID
75         self._sendPDU(self.encodeAckPDU(transactionID))
76         
77     def _sendPDU(self, pdu):
78         """ Transmits a PDU through the socket
79         
80         @param pdu: The PDU to send (a sequence of bytes)
81         @type pdu: list
82         """
83         data = ''
84         for char in pdu:
85             data += chr(char)
86         self.udpSocket.sendto(data, (self.gatewayHost, self.gatewayPort))
87     
88     def _receiveData(self):
89         """ Read data from the UDP socket
90         
91         @return: The data read from the socket
92         @rtype: str
93         """
94         #done = False
95         done = True
96         response = ''
97         print '>> WTP: Receiving data'
98         while not done:
99             buff = self.udpSocket.recv(1024)
100             print buff
101             response += buff
102             if len(buff) < 1024:
103                 done = True
104         return response
105
106     def _parseResponse(self, responseData):
107         """ Interpret data read from the socket (at the WTP layer level) 
108         
109         @param responseData: A buffer containing data to interpret
110         @type responseData: str
111         """
112         byteArray = array.array('B')
113         print responseData
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)
120         return byteIter
121         
122     
123     @staticmethod
124     def encodeInvokePDU(tid):
125         """ Builds a WTP Invoke PDU
126         
127         @param tid: The transaction ID for this PDU
128         @type tid: int
129         
130         @return: the WTP invoke PDU as a sequence of bytes
131         @rtype: list
132         
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
137          2      |                TID
138          3      |
139          4      |Version  |TIDnew| U/P  |  RES |RES|  TCL
140          
141          ...where bit 0 is the most significant bit.
142         
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
152          2      |   TID
153          3      |   TID
154          4      | 0 |  0  |  0   |  1   |  0   | 0 | 1 | 0 
155         """
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
160         return pdu
161     
162     @staticmethod
163     def encodeAckPDU(tid):
164         """ Builds a WTP Ack PDU (acknowledge)
165         
166         @param tid: The transaction ID for this PDU
167         @type tid: int
168         
169         @return: the WTP invoke PDU as a sequence of bytes
170         @rtype: list
171         
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
177          2                       TID
178          3
179
180          ...where bit 0 is the most significant bit.
181         
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  |
187          2         TID
188          3         TID
189         """
190         pdu = [0x18] # binary: 00011000
191         pdu.extend(WTP._encodeTID(tid))
192         return pdu
193     
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()}.
197         
198         @param byteIter: an iterator over a sequence of bytes
199         @type byteIteror: mms.iterator.PreviewIterator
200         
201         @note: If the PDU type is correctly determined, byteIter will be
202                modified in order to read past the amount of bytes required
203                by the PDU type.
204         
205         @return: The PDU type, and the transaction ID, in the format:
206                  (str:<pdu_type>, int:<transaction_id>)
207         @rtype: tuple
208         """
209         byte = byteIter.preview()
210         byteIter.resetPreview()
211         # Get the PDU type
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)
217         else:
218             print '<< WTP: %s' % WTP.pduTypes[pduType],
219             try:
220                 exec 'pduValue = self._decode%sPDU(byteIter)' % WTP.pduTypes[pduType]
221             except:
222                 print 'A fatal error occurred, probably due to an unimplemented feature.\n'
223                 raise
224         # after this follows the WSP pdu(s)....
225         return pduValue
226     
227     def _decodeResultPDU(self, byteIter):
228         """ Decodes a WTP Result PDU
229         
230         @param byteIter: an iterator over a sequence of bytes
231         @type byteIteror: mms.iterator.PreviewIterator
232         
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
237          2                       TID
238          3
239         
240         The WTP Result PDU Type is 0x02, according to WAP-224, table 11
241         """
242         # Read in 3 bytes
243         bytes = []
244         for i in range(3):
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)
253     
254     def _decodeAckPDU(self, byteIter):
255         """ Decodes a WTP Result PDU
256         
257         @param byteIter: an iterator over a sequence of bytes
258         @type byteIteror: mms.iterator.PreviewIterator
259         
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
264          2                       TID
265          3
266         
267         The WTP Result PDU Type is 0x03, according to WAP-224, table 11
268         """
269         # Read in 3 bytes
270         bytes = []
271         for i in range(3):
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)
280     
281     def _decodeAbortPDU(self, byteIter):
282         """ Decodes a WTP Abort PDU
283         
284         @param byteIter: an iterator over a sequence of bytes
285         @type byteIteror: mms.iterator.PreviewIterator
286         
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
291          2                       TID
292          3
293          4                  Abort reason
294         
295         The WTP Abort PDU Type is 0x04, according to WAP-224, table 11
296         """
297         # Read in 4 bytes
298         bytes = []
299         for i in range(4):
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]
306         else:
307             abortType = str(abortType)
308         if abortReason in self.abortReasons:
309             abortReason = self.abortReasons[abortReason]
310         else:
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)
319     
320     @staticmethod
321     def _encodeTID(transactionID):
322         """ Encodes the specified transaction ID into the format used in
323         WTP PDUs (makes sure it spans 2 bytes)
324         
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.
328         
329         @param transactionID: The transaction ID to encode
330         @type transactionID: int
331         
332         @return: The encoded transaction ID as a sequence of bytes
333         @rtype: list
334         """
335         if transactionID > 0x7FFF:
336             raise ValueError, 'Transaction ID too large (must fit into 15 bits): %d' % transactionID
337         else:
338             encodedTID = [transactionID & 0xFF]
339             encodedTID.insert(0, transactionID >> 8)
340             return encodedTID
341
342     @staticmethod
343     def _decodeTID(bytes):
344         """ Decodes the transaction ID contained in <bytes>
345         
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.
349
350         @param bytes: The byte sequence containing the transaction ID
351         @type bytes: list
352         
353         @return: The decoded transaction ID
354         @rtype: int
355         """
356         tid = bytes[0] << 8
357         tid |= bytes[1]
358         # make unsigned
359         tid &= 0x7f
360         return tid