initial commit
[fmms] / src / mms / WSP.py
1 # -*- coding: utf-8 -*-
2 #\r
3 # This library is free software, distributed under the terms of\r
4 # the GNU Lesser General Public License Version 2.\r
5 # See the COPYING.LESSER file included in this archive\r
6 #\r
7 # The docstrings in this module contain epytext markup; API documentation\r
8 # may be created by processing this file with epydoc: http://epydoc.sf.net\r
9 """\r
10 Library for WAP transport, original by Francois Aucamp, modified by Nick Leppänen Larsson\r
11 for use in Maemo5/Fremantle on the Nokia N900.\r
12 \r
13 @author: Francois Aucamp <faucamp@csir.co.za>\r
14 @author: Nick Leppänen Larsson <frals@frals.se>\r
15 @license: GNU LGPL\r
16 """\r
17 from WTP import WTP\r
18 import sys\r
19 import array\r
20 import socket, time\r
21 \r
22 from wsp_pdu import Decoder, Encoder, WSPEncodingAssignments\r
23 from iterator import PreviewIterator\r
24 \r
25 class WSP:\r
26     """ This class implements a very limited subset of the WSP layer.\r
27     \r
28     It uses python-mms's WSP PDU encoding module for almost all encodings,\r
29     and essentially just glues it together into a limited WSP layer. """\r
30     def __init__(self, wapGatewayHost, wapGatewayPort=9201):\r
31         self.serverSessionID = -1\r
32         self.capabilities = {'ClientSDUSize': 261120,\r
33                              'ServerSDUSize': 261120}\r
34         self.headers = [('User-Agent', 'Nokia N900'),\r
35                         ('Accept', 'text/plain'),\r
36                         ('Accept', 'application/vnd.wap.mms-message')]\r
37         self.wtp = WTP(wapGatewayHost, wapGatewayPort)\r
38 \r
39     def connect(self):\r
40         """ Sends a WSP Connect message to the gateway, including any\r
41         configured capabilities. It also updates the WSP object to reflect\r
42         the status of the WSP connection """\r
43         print '>> WSP: Connect'\r
44         response = self.wtp.invoke(self.encodeConnectPDU())\r
45         self._decodePDU(response)\r
46         \r
47 \r
48     def disconnect(self):\r
49         """ Sends a WSP Connect message to the gateway, including any\r
50         configured capabilities. It also updates the WSP object to reflect\r
51         the status of the WSP connection """\r
52         print '>> WSP: Disconnect'\r
53         self.wtp.invoke(self.encodeDisconnectPDU(self.serverSessionID))\r
54         self.serverSessionID = -1\r
55         \r
56     def post(self, uri, contentType, data):\r
57         """ Performs a WSP POST """\r
58         if type(data) == array.array:\r
59             data = data.tolist()\r
60         print '>> WSP: Post'\r
61         pdu = self.encodePostPDU(uri, contentType) + data\r
62         response = self.wtp.invoke(pdu)\r
63         self._decodePDU(response)\r
64         \r
65     def get(self, uri):\r
66         """ Performs a WSP GET """\r
67         response = self.wtp.invoke(self.encodeGetPDU(uri))\r
68         self._decodePDU(response)\r
69 \r
70     def encodeConnectPDU(self):\r
71         """ Sends a WSP connect request (S-Connect.req, i.e. Connect PDU) to\r
72         the WAP gateway \r
73         \r
74         This PDU is described in WAP-230, section 8.2.2, and is sent to\r
75         initiate the creation of a WSP session. Its field structure::\r
76          \r
77             Field Name       Type               Description\r
78             ===============  =================  =================\r
79             Version          uint8              WSP protocol version\r
80             CapabilitiesLen  uintvar            Length of the Capabilities field\r
81             HeadersLen       uintvar            Length of the Headers field\r
82             Capabilities     <CapabilitiesLen>\r
83                               octets            S-Connect.req::Requested Capabilities\r
84             Headers          <HeadersLen>\r
85                               octets            S-Connect.req::Client Headers\r
86         """\r
87         pdu = []\r
88         pdu.append(0x01) # Type: "Connect"\r
89         # Version field - we are using version 1.0\r
90         pdu.extend(Encoder.encodeVersionValue('1.0'))\r
91         # Add capabilities\r
92         capabilities = []\r
93         for capability in self.capabilities:\r
94             # Unimplemented/broken capabilities are not added\r
95             try:\r
96                 exec 'capabilities.extend(WSP._encodeCapabilty%s(self.capabilities[capability]))' % capability\r
97             except:\r
98                 pass\r
99         # Add and encode headers\r
100         headers = array.array('B')\r
101         for hdr, hdrValue in self.headers:\r
102             headers.extend(Encoder.encodeHeader(hdr, hdrValue))\r
103         # Add capabilities and headers to PDU (including their lengths)\r
104         pdu.extend(Encoder.encodeUintvar(len(capabilities)))\r
105         pdu.extend(Encoder.encodeUintvar(len(headers)))\r
106         pdu.extend(capabilities)\r
107         pdu.extend(headers)\r
108         return pdu\r
109     \r
110     @staticmethod\r
111     def encodePostPDU(uri, contentType):\r
112         """ Builds a WSP POST PDU\r
113         \r
114         @note: This method does not add the <Data> part at the end of the PDU;\r
115                this should be appended manually to the result of this method.\r
116         \r
117         The WSP Post PDU is defined in WAP-230, section 8.2.3.2::\r
118                                      Table 10. Post Fields\r
119          Name        Type                       Source\r
120          ==========  ========================   ========================================\r
121          UriLen      uintvar                    Length of the URI field\r
122          HeadersLen  uintvar                    Length of the ContentType and Headers fields\r
123                                                 combined\r
124          Uri         UriLen octets              S-MethodInvoke.req::Request URI or\r
125                                                 S-Unit-MethodInvoke.req::Request URI\r
126          ContentType multiple octets            S-MethodInvoke.req::Request Headers or\r
127                                                 S-Unit-MethodInvoke.req::Request Headers\r
128          Headers     (HeadersLen - length of    S-MethodInvoke.req::Request Headers or\r
129                      ContentType) octets        S-Unit-MethodInvoke.req::Request Headers\r
130          Data        multiple octets            S-MethodInvoke.req::Request Body or\r
131                                                 S-Unit-MethodInvoke.req::Request Body\r
132 \r
133         """\r
134         #TODO: remove this, or make it dynamic or something:\r
135         headers = [('Accept', 'application/vnd.wap.mms-message')]\r
136         pdu = [0x60] # Type: "Post"\r
137         # UriLen:\r
138         pdu.extend(Encoder.encodeUintvar(len(uri)))\r
139         # HeadersLen:\r
140         encodedContentType = Encoder.encodeContentTypeValue(contentType, {})\r
141         encodedHeaders = []\r
142         for hdr, hdrValue in headers:\r
143             encodedHeaders.extend(Encoder.encodeHeader(hdr, hdrValue))\r
144         headersLen = len(encodedContentType) + len(encodedHeaders)\r
145         pdu.extend(Encoder.encodeUintvar(headersLen))\r
146         # URI - this should NOT be null-terminated (according to WAP-230 section 8.2.3.2)\r
147         for char in uri:\r
148             pdu.append(ord(char))\r
149         # Content-Type:\r
150         pdu.extend(encodedContentType)\r
151         # Headers:\r
152         pdu.extend(encodedHeaders)\r
153         return pdu\r
154     \r
155     @staticmethod\r
156     def encodeGetPDU(uri):\r
157         """ Builds a WSP GET PDU \r
158         \r
159         The WSP Get PDU is defined in WAP-230, section 8.2.3.1::\r
160          Name    Type          Source\r
161          ======  ============  =======================\r
162          URILen  uintvar       Length of the URI field\r
163          URI     URILen octets S-MethodInvoke.req::Request URI or\r
164                                S-Unit-MethodInvoke.req::Request URI\r
165          Headers multiple      S-MethodInvoke.req::Request Headers or\r
166                  octets        S-Unit-MethodInvoke.req::Request Headers\r
167         """\r
168         pdu = self\r
169         # UriLen:\r
170         pdu.extend(Encoder.encodeUintvar(len(uri)))\r
171         # URI - this should NOT be null-terminated (according to WAP-230 section 8.2.3.1)\r
172         for char in uri:\r
173             pdu.append(ord(char))\r
174         headers = []\r
175         #TODO: not sure if these should go here...\r
176         for hdr, hdrValue in pdu.headers:\r
177             headers.extend(Encoder.encodeHeader(hdr, hdrValue))\r
178         pdu.extend(headers)\r
179         return pdu\r
180     \r
181     @staticmethod\r
182     def encodeDisconnectPDU(serverSessionID):\r
183         """ Builds a WSP Disconnect PDU\r
184         \r
185         The Disconnect PDU is sent to terminate a session. It structure is\r
186         defined in WAP-230, section 8.2.2.4::\r
187          Name             Type     Source\r
188          ===============  =======  ===================\r
189          ServerSessionId  uintvar  Session_ID variable\r
190         """\r
191         pdu = [0x05] # Type: "Disconnect"\r
192         pdu.extend(Encoder.encodeUintvar(serverSessionID))\r
193         return pdu\r
194     \r
195     def _decodePDU(self, byteIter):\r
196         """ Reads and decodes a WSP PDU from the sequence of bytes starting at\r
197         the byte pointed to by C{dataIter.next()}.\r
198         \r
199         @param byteIter: an iterator over a sequence of bytes\r
200         @type byteIteror: mms.iterator.PreviewIterator\r
201         \r
202         @note: If the PDU type is correctly determined, byteIter will be\r
203                modified in order to read past the amount of bytes required\r
204                by the PDU type.\r
205         """\r
206         pduType = Decoder.decodeUint8(byteIter)\r
207         if pduType not in WSPEncodingAssignments.wspPDUTypes:\r
208             #TODO: maybe raise some error or something\r
209             print 'Error - unknown WSP PDU type: %s' % hex(pduType)\r
210             raise TypeError\r
211         pduType = WSPEncodingAssignments.wspPDUTypes[pduType]\r
212         print '<< WSP: %s' % pduType\r
213         pduValue = None\r
214         try:\r
215             exec 'pduValue = self._decode%sPDU(byteIter)' % pduType\r
216         except:\r
217             print 'A fatal error occurred, probably due to an unimplemented feature.\n'\r
218             raise\r
219         return pduValue\r
220     \r
221     def _decodeConnectReplyPDU(self, byteIter):\r
222         """ The WSP ConnectReply PDU is sent in response to a S-Connect.req\r
223         PDU. It is defined in WAP-230, section 8.2.2.2.\r
224         \r
225         All WSP PDU headers start with a type (uint8) byte (we do not\r
226         implement connectionless WSP, thus we don't prepend TIDs to the WSP\r
227         header). The WSP PDU types are specified in WAP-230, table 34.\r
228         \r
229         ConnectReply PDU Fields::\r
230          Name            Type               Source\r
231          =============== =================  =====================================\r
232          ServerSessionId Uintvar            Session_ID variable\r
233          CapabilitiesLen Uintvar            Length of Capabilities field\r
234          HeadersLen      Uintvar            Length of the Headers field\r
235          Capabilities    <CapabilitiesLen>  S-Connect.res::Negotiated Capabilities\r
236                           octets\r
237          Headers         <HeadersLen>       S-Connect.res::Server Headers\r
238                           octets\r
239 \r
240         @param byteIters: an iterator over the sequence of bytes containing\r
241                           the ConnectReply PDU\r
242         @type bytes: mms.iterator.PreviewIterator\r
243         """\r
244         self.serverSessionID = Decoder.decodeUintvar(byteIter)\r
245         capabilitiesLen = Decoder.decodeUintvar(byteIter)\r
246         headersLen = Decoder.decodeUintvar(byteIter)\r
247         # Stub to decode capabilities (currently we ignore these)\r
248         cFieldBytes = []\r
249         for i in range(capabilitiesLen):\r
250             cFieldBytes.append(byteIter.next())\r
251         cIter = PreviewIterator(cFieldBytes)\r
252         # Stub to decode headers (currently we ignore these)\r
253         hdrFieldBytes = []\r
254         for i in range(headersLen):\r
255             hdrFieldBytes.append(byteIter.next())\r
256         hdrIter = PreviewIterator(hdrFieldBytes)\r
257     \r
258     \r
259     def _decodeReplyPDU(self, byteIter):\r
260         """ The WSP Reply PDU is the generic response PDU used to return\r
261         information from the server in response to a request. It is defined in\r
262         WAP-230, section 8.2.3.3.\r
263         \r
264         All WSP PDU headers start with a type (uint8) byte (we do not\r
265         implement connectionless WSP, thus we don't prepend TIDs to the WSP\r
266         header). The WSP PDU types are specified in WAP-230, table 34.\r
267         \r
268         Reply PDU Fields::\r
269          Name            Type\r
270          =============== =================\r
271          Status          Uint8\r
272          HeadersLen      Uintvar\r
273          ContentType     multiple octects\r
274          Headers         <HeadersLen> - len(ContentType) octets\r
275          Data            multiple octects\r
276 \r
277         @param byteIters: an iterator over the sequence of bytes containing\r
278                           the ConnectReply PDU\r
279         @type bytes: mms.iterator.PreviewIterator\r
280         """\r
281         status = Decoder.decodeUint8(byteIter)\r
282         headersLen = Decoder.decodeUintvar(byteIter)\r
283         \r
284         # Stub to decode headers (currently we ignore these)\r
285         hdrFieldBytes = []\r
286         for i in range(headersLen):\r
287             hdrFieldBytes.append(byteIter.next())\r
288         hdrIter = PreviewIterator(hdrFieldBytes)\r
289         contentType, parameters = Decoder.decodeContentTypeValue(hdrIter)\r
290         while True:\r
291             try:\r
292                 hdr, value = Decoder.decodeHeader(hdrIter)\r
293             except StopIteration:\r
294                 break\r
295         # Read the data\r
296         data = []\r
297         while True:\r
298             try:\r
299                 data.append(byteIter.next())\r
300             except StopIteration:\r
301                 break\r
302     \r
303     @staticmethod\r
304     def _encodeCapabiltyClientSDUSize(size):\r
305         """ Encodes the Client-SDU-Size capability (Client Service Data Unit);\r
306         described in WAP-230, section 8.3.2.1\r
307         \r
308         This defines the maximum size (in octets) of WTP Service Data Units\r
309         \r
310         @param size: The requested SDU size to negotiate (in octets)\r
311         @type size: int\r
312         """\r
313         identifier = Encoder.encodeShortInteger(0x00)\r
314         parameters = Encoder.encodeUintvar(size)\r
315         length = Encoder.encodeUintvar(len(identifier) + len(parameters))\r
316         capability = length\r
317         capability.extend(identifier)\r
318         capability.extend(parameters)\r
319         return capability\r
320      \r
321     @staticmethod\r
322     def _encodeCapabilityServerSDUSize(size):\r
323         """ Encodes the Client-SDU-Size capability (Server Service Data Unit);\r
324         described in WAP-230, section 8.3.2.1.\r
325         \r
326         This defines the maximum size (in octets) of WTP Service Data Units\r
327         \r
328         @param size: The requested SDU size to negotiate (in octets)\r
329         @type size: int\r
330         """\r
331         identifier = Encoder.encodeShortInteger(0x01)\r
332         parameters = Encoder.encodeUintvar(size)\r
333         length = Encoder.encodeUintvar(len(identifier) + len(parameters))\r
334         capability = length\r
335         capability.extend(identifier)\r
336         capability.extend(parameters)\r
337         return capability\r