PDU decoding support
[ussd-widget] / ussd-common / src / usr / lib / python2.5 / gsmdecode.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 ## This program is free software; you can redistribute it and/or modify
4 ## it under the terms of the GNU General Public License as published
5 ## by the Free Software Foundation; version 2 and higer.
6 ##
7 ## Martin Grimme (martin.grimme # gmail.com) 2010
8 ## Guseynov Alexey (kibergus # gmail.com) 2010
9
10 LANG_DE = 0x0
11 LANG_EN = 0x1
12 LANG_IT = 0x2
13 LANG_FR = 0x3
14 LANG_ES = 0x4
15 LANG_NL = 0x5
16 LANG_SE = 0x6
17 LANG_DA = 0x7
18 LANG_PO = 0x8
19 LANG_FI = 0x9
20 LANG_NO = 0xa
21 LANG_GR = 0xb
22 LANG_TR = 0xc
23 LANG_UNSPECIFIED = 0xf
24
25
26 GSM_DEFAULT_ALPHABET = [
27     u"@",
28     u"\u00a3",
29     u"$",
30     u"\u00a5",
31     u"\u00e8",
32     u"\u00e9",
33     u"\u00f9",
34     u"\u00ec",
35     u"\u00f2",
36     u"\u00c7",
37     u"\n",
38     u"\u00d8",
39     u"\u00f8",
40     u"\r",
41     u"\u00c5",
42     u"\u00e5",
43     
44     u"\u0394",
45     u"_",
46     u"\u03a6",
47     u"\u0393",
48     u"\u039b",
49     u"\u03a9",
50     u"\u03a0",
51     u"\u03a8",
52     u"\u03a3",
53     u"\u0398",
54     u"\u039e",
55     u" ",
56     u"\u00c6",
57     u"\u00e6",
58     u"\u00df",
59     u"\u00c9",
60     
61     u" ",
62     u"!",
63     u"\"",
64     u"#",
65     u"\u00a4",
66     u"%",
67     u"&",
68     u"'",
69     u"(",
70     u")",
71     u"*",
72     u"+",
73     u",",
74     u"-",
75     u".",
76     u"/",
77     
78     u"0",
79     u"1",
80     u"2",
81     u"3",
82     u"4",
83     u"5",
84     u"6",
85     u"7",
86     u"8",
87     u"9",
88     u":",
89     u";",
90     u"<",
91     u"=",
92     u">",
93     u"?",
94     
95     u"\u00a1",
96     u"A",
97     u"B",
98     u"C",
99     u"D",
100     u"E",
101     u"F",
102     u"G",
103     u"H",
104     u"I",
105     u"J",
106     u"K",
107     u"L",
108     u"M",
109     u"N",
110     u"O",
111     
112     u"P",
113     u"Q",
114     u"R",
115     u"S",
116     u"T",
117     u"U",
118     u"V",
119     u"W",
120     u"X",
121     u"Y",
122     u"Z",
123     u"\u00c4",
124     u"\u00d6",
125     u"\u00d1",
126     u"\u00dc",
127     u"\u00a7",
128
129     u"\u00bf",
130     u"a",
131     u"b",
132     u"c",
133     u"d",
134     u"e",
135     u"f",
136     u"g",
137     u"h",
138     u"i",
139     u"j",
140     u"k",
141     u"l",
142     u"m",
143     u"n",
144     u"o",
145
146     u"p",
147     u"q",
148     u"r",
149     u"s",
150     u"t",
151     u"u",
152     u"v",
153     u"w",
154     u"x",
155     u"y",
156     u"z",
157     u"\u00e4",
158     u"\u00f6",
159     u"\u00f1",
160     u"\u00fc",
161     u"\u00e0"
162 ]
163
164
165 def decode(s, n):
166     """
167     Decodes the given string using the given cell broadcast data coding scheme.
168     
169     @param s: string to decode
170     @param n: GSM cell broadcast data coding scheme
171     @return: UTF-8 string
172     """
173
174     # separate into nibbles
175     hbits = (n & 0xf0) >> 4
176     lbits = (n & 0x0f)
177     
178     if (hbits == 0x0):
179         # language
180         return _decode_language(s, lbits)
181
182     elif (0x1 <= hbits <= 0x3):
183         # reserved language
184         return s
185         
186     elif (0x4 <= hbits <= 0x7):
187         # general data coding indication
188         return _decode_general_data_coding(s, hbits, lbits)
189         
190     elif (0x8 <= hbits <= 0xe):
191         # reserved coding group
192         return s
193         
194     elif (hbits == 0xf):
195         # data coding / message handling
196         return s
197
198
199 def _decode_language(s, lang):
200
201     return _decode_default_alphabet(s)
202
203
204 def _decode_default_alphabet(s):
205     
206     # ought to be all in the 7 bit GSM character map
207     # modem is in 8 bit mode, so it makes 7 bit unpacking itself
208     chars = [ GSM_DEFAULT_ALPHABET[ord(c)] for c in s ]
209     u_str = "".join(chars)
210     return u_str.encode("utf-8")
211
212
213 def _decode_hex(s):
214
215     return s.decode("hex")
216
217
218 def _decode_usc2(s):
219
220     return s.decode("hex").decode("utf-16-be").encode("utf-8")
221
222
223 def _decode_general_data_coding(s, h, l):
224
225     is_compressed = (h & 0x2)
226     
227     alphabet = (l & 0xc) >> 2
228
229     if (alphabet == 0x0):
230         # default alphabet
231         return _decode_default_alphabet(s)
232         
233     elif (alphabet == 0x1):
234         # 8 bit
235         # actually, encoding is user-defined, but let's assume hex'd ASCII
236         # for now
237         return _decode_hex(s)
238         
239     elif (alphabet == 0x2):
240         # USC2 (16 bit, BE)
241         return _decode_usc2(s)
242     elif (alphabet == 0x3):
243         # reserved
244         return s
245
246 def decode_number(number):
247         dnumber = ""
248         for i in number:
249                 if i & 0xf < 10:
250                         dnumber += str(int((i & 0xf)))
251                 if (i & 0xf0) >> 4 < 10:
252                         dnumber += str(int((i & 0xf0) >> 4))
253         return dnumber
254
255 def decode_timestamp(timestamp):
256         res = {}
257         res['year'] =  str(timestamp[0] & 0xf) + str((timestamp[0] & 0xf0) >> 4)
258         res['month'] = str(timestamp[1] & 0xf) + str((timestamp[1] & 0xf0) >> 4)
259         res['day'] = str(timestamp[2] & 0xf) + str((timestamp[2] & 0xf0) >> 4)
260         res['hour'] = str(timestamp[3] & 0xf) + str((timestamp[3] & 0xf0) >> 4)
261         res['minute'] = str(timestamp[4] & 0xf) + str((timestamp[4] & 0xf0) >> 4)
262         res['second'] = str(timestamp[5] & 0xf) + str((timestamp[5] & 0xf0) >> 4)
263         res['timezone'] = str(timestamp[6] & 0xf) + str((timestamp[6] & 0xf0) >> 4)
264         return res
265
266 def decode_pdu (pdumsg):
267         pdu = {}
268         pdu['type'] = int(pdumsg[0])
269         if pdu['type'] & 0x3 == 0x0:
270                 pdu['address_len'] = int(pdumsg[1])
271                 pdu['type_of_address'] = int(pdumsg[2])
272                 base = 3+(pdu['address_len']+1)/2
273                 pdu['sender'] = decode_number(pdumsg[3:base])
274                 pdu['pid'] = int(pdumsg[base])
275                 pdu['dcs'] = int(pdumsg[base+1])
276                 pdu['timestamp'] = decode_timestamp (pdumsg[base+2:base+9]);
277                 pdu['udl'] = int(pdumsg[base+9])
278                 pdu['user_data'] = pdumsg[base+10:len(pdumsg)]
279
280                 alphabet = (pdu['dcs'] & 0xc) >> 2
281                 if alphabet == 0x0:
282                         # This is 7-bit data. Taking of only pdu['udl'] bytes is important because we can't distinguish 0 at the end from @ if only one bit is used in last bite
283                         pdu['user_data'] = _decode_default_alphabet(deoctify(pdu['user_data']))[0:pdu['udl']]
284                 elif alphabet == 0x1:
285                         # actually, encoding is user-defined, but let's assume ASCII
286                         pdu['user_data'] = ''.join([chr(i) for i in pdu['user_data']])
287                 elif (alphabet == 0x2):
288                         # USC2 (16 bit, BE)
289                         pdu['user_data'] = (''.join([chr(i) for i in pdu['user_data'][6:len(pdu['user_data'])]])).decode("utf-16-be").encode("utf-8")
290                 elif (alphabet == 0x3):
291                         # reserved
292                         pdu['user_data'] = ''.join([chr(i) for i in pdu['user_data']])
293
294                 if pdu['type'] & 0x4 == 0:
295                         pdu['part'] = True
296                 else:
297                         pdu['part'] = False
298         else:
299                 # TODO support other types of messages
300                 # This is not incoming message
301                 # pdu['type'] & 0x3 == 2 means delivery report
302                 return None
303
304         return pdu
305