1 # -*- coding: utf-8 -*-
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
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
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
13 @author: Francois Aucamp <faucamp@csir.co.za>
\r
14 @author: Nick Leppänen Larsson <frals@frals.se>
\r
22 from wsp_pdu import Decoder, Encoder, WSPEncodingAssignments
\r
23 from iterator import PreviewIterator
\r
26 """ This class implements a very limited subset of the WSP layer.
\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
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
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
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
66 """ Performs a WSP GET """
\r
67 response = self.wtp.invoke(self.encodeGetPDU(uri))
\r
68 self._decodePDU(response)
\r
70 def encodeConnectPDU(self):
\r
71 """ Sends a WSP connect request (S-Connect.req, i.e. Connect PDU) to
\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
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
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
93 for capability in self.capabilities:
\r
94 # Unimplemented/broken capabilities are not added
\r
96 exec 'capabilities.extend(WSP._encodeCapabilty%s(self.capabilities[capability]))' % capability
\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
111 def encodePostPDU(uri, contentType):
\r
112 """ Builds a WSP POST PDU
\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
117 The WSP Post PDU is defined in WAP-230, section 8.2.3.2::
\r
118 Table 10. Post Fields
\r
120 ========== ======================== ========================================
\r
121 UriLen uintvar Length of the URI field
\r
122 HeadersLen uintvar Length of the ContentType and Headers fields
\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
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
138 pdu.extend(Encoder.encodeUintvar(len(uri)))
\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
148 pdu.append(ord(char))
\r
150 pdu.extend(encodedContentType)
\r
152 pdu.extend(encodedHeaders)
\r
156 def encodeGetPDU(uri):
\r
157 """ Builds a WSP GET PDU
\r
159 The WSP Get PDU is defined in WAP-230, section 8.2.3.1::
\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
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
173 pdu.append(ord(char))
\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
182 def encodeDisconnectPDU(serverSessionID):
\r
183 """ Builds a WSP Disconnect PDU
\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
188 =============== ======= ===================
\r
189 ServerSessionId uintvar Session_ID variable
\r
191 pdu = [0x05] # Type: "Disconnect"
\r
192 pdu.extend(Encoder.encodeUintvar(serverSessionID))
\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
199 @param byteIter: an iterator over a sequence of bytes
\r
200 @type byteIteror: mms.iterator.PreviewIterator
\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
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
211 pduType = WSPEncodingAssignments.wspPDUTypes[pduType]
\r
212 print '<< WSP: %s' % pduType
\r
215 exec 'pduValue = self._decode%sPDU(byteIter)' % pduType
\r
217 print 'A fatal error occurred, probably due to an unimplemented feature.\n'
\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
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
229 ConnectReply PDU Fields::
\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
237 Headers <HeadersLen> S-Connect.res::Server Headers
\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
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
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
254 for i in range(headersLen):
\r
255 hdrFieldBytes.append(byteIter.next())
\r
256 hdrIter = PreviewIterator(hdrFieldBytes)
\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
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
270 =============== =================
\r
273 ContentType multiple octects
\r
274 Headers <HeadersLen> - len(ContentType) octets
\r
275 Data multiple octects
\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
281 status = Decoder.decodeUint8(byteIter)
\r
282 headersLen = Decoder.decodeUintvar(byteIter)
\r
284 # Stub to decode headers (currently we ignore these)
\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
292 hdr, value = Decoder.decodeHeader(hdrIter)
\r
293 except StopIteration:
\r
299 data.append(byteIter.next())
\r
300 except StopIteration:
\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
308 This defines the maximum size (in octets) of WTP Service Data Units
\r
310 @param size: The requested SDU size to negotiate (in octets)
\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
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
326 This defines the maximum size (in octets) of WTP Service Data Units
\r
328 @param size: The requested SDU size to negotiate (in octets)
\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