1 /* (c) 2002-2006 by Marcin Wiacek */
2
3 #include <ctype.h>
4 #include <string.h>
5 #include <time.h>
6
7 #include <gammu-unicode.h>
8 #include <gammu-debug.h>
9
10 #include "gsmmulti.h"
11 #include "../gsmring.h"
12 #include "../gsmlogo.h"
13 #include "../../misc/coding/coding.h"
14 #include "../../debug.h"
15 #include "gsmems.h"
16 #include "../gsmdata.h"
17 #include "../gsmnet.h"
18
19 /* ----------------- Splitting SMS into parts ------------------------------ */
20
GSM_MakeSMSIDFromTime(void)21 unsigned char GSM_MakeSMSIDFromTime(void)
22 {
23 GSM_DateTime Date;
24 unsigned char retval;
25
26 GSM_GetCurrentDateTime (&Date);
27 retval = Date.Second;
28 switch (Date.Minute/10) {
29 case 2: case 7: retval = retval + 60; break;
30 case 4: case 8: retval = retval + 120; break;
31 case 9: case 5: case 0: retval = retval + 180; break;
32 }
33 retval += Date.Minute/10;
34 return retval;
35 }
36
GSM_Find_Free_Used_SMS2(GSM_Debug_Info * di,GSM_Coding_Type Coding,GSM_SMSMessage * SMS,size_t * UsedText,size_t * FreeText,size_t * FreeBytes)37 void GSM_Find_Free_Used_SMS2(GSM_Debug_Info *di, GSM_Coding_Type Coding,GSM_SMSMessage *SMS, size_t *UsedText, size_t *FreeText, size_t *FreeBytes)
38 {
39 size_t UsedBytes = 0;
40
41 switch (Coding) {
42 case SMS_Coding_Default_No_Compression:
43 FindDefaultAlphabetLen(SMS->Text,&UsedBytes,UsedText,500);
44 UsedBytes = *UsedText * 7 / 8;
45 if (UsedBytes * 8 / 7 != *UsedText) UsedBytes++;
46 *FreeBytes = GSM_MAX_8BIT_SMS_LENGTH - SMS->UDH.Length - UsedBytes;
47 *FreeText = (GSM_MAX_8BIT_SMS_LENGTH - SMS->UDH.Length) * 8 / 7 - *UsedText;
48 break;
49 case SMS_Coding_Unicode_No_Compression:
50 *UsedText = UnicodeLength(SMS->Text);
51 UsedBytes = *UsedText * 2;
52 *FreeBytes = GSM_MAX_8BIT_SMS_LENGTH - SMS->UDH.Length - UsedBytes;
53 *FreeText = *FreeBytes / 2;
54 break;
55 case SMS_Coding_8bit:
56 *UsedText = UsedBytes = SMS->Length;
57 *FreeBytes = GSM_MAX_8BIT_SMS_LENGTH - SMS->UDH.Length - UsedBytes;
58 *FreeText = *FreeBytes;
59 break;
60 default:
61 break;
62 }
63 smfprintf(di, "UDH len %i, UsedBytes %ld, FreeText %ld, UsedText %ld, FreeBytes %ld\n",
64 SMS->UDH.Length,
65 (long)UsedBytes,
66 (long)*FreeText,
67 (long)*UsedText,
68 (long)*FreeBytes);
69 }
70
ReassembleCharacter(char * Buffer,size_t character_index)71 unsigned int ReassembleCharacter(char *Buffer, size_t character_index)
72 {
73 size_t offset = character_index * 2;
74
75 return (
76 (unsigned int) ((unsigned char) Buffer[offset] << 8)
77 + (unsigned char) Buffer[offset + 1]
78 );
79 }
80
AlignIfSurrogatePair(GSM_Debug_Info * di,size_t * Copy,char * Buffer,size_t BufferLen)81 int AlignIfSurrogatePair(GSM_Debug_Info *di,
82 size_t *Copy,
83 char *Buffer,
84 size_t BufferLen)
85 {
86 int rv = 0;
87 unsigned int n;
88
89 /* Precondition:
90 * Resulting copy must always be non-zero. */
91
92 if (*Copy <= 1) {
93 return rv;
94 }
95
96 /* Don't split a UTF-16 surrogate pair:
97 * If the final code unit to be copied is a lead surrogate, save
98 * it for the next message segment. This allows recipients to view
99 * the proper four-byte UTF-16 character even if they're unable to
100 * reassemble the message (e.g. if a telecom strips off the UDH). */
101
102 n = ReassembleCharacter(Buffer, *Copy - 1);
103
104 /* UTF-16 leading surrogate:
105 * First character in pair is always between U+D800 and U+DBFF */
106
107 if (n >= 0xd800 && n <= 0xdbff) {
108 *Copy -= 1;
109 ++rv;
110 }
111
112 return rv;
113 }
114
AlignIfCombinedCharacter(GSM_Debug_Info * di,size_t * Copy,char * Buffer,size_t BufferLen)115 int AlignIfCombinedCharacter(GSM_Debug_Info *di,
116 size_t *Copy,
117 char *Buffer,
118 size_t BufferLen)
119 {
120 int rv = 0;
121 unsigned int n;
122
123 /* Precondition:
124 * If we only have one character to copy, or if there isn't any
125 * code unit following our copy window, don't change anything. */
126
127 if (*Copy <= 1 || *Copy >= BufferLen) {
128 return rv;
129 }
130
131 /* Don't split up a combining sequence:
132 * Peek at the next message segment to see if it begins with
133 * a combining character (e.g. a discritical mark). If it does,
134 * push the final character of this message segment in to the
135 * next message segment. This ensures that the recipient can
136 * visually combine the sequence, even if reassembly fails. */
137
138 n = ReassembleCharacter(Buffer, *Copy);
139
140 /* Unicode combining characters:
141 * Combining Half Marks (U+FE20 - U+FE2F)
142 * Combining Diacritical Marks (U+300 - U+36F)
143 * Combining Diacritical Marks Extended (U+1AB0 - U+1AFF)
144 * Combining Diacritical Marks Supplement (U+1DC0 - U+1DFF)
145 * Combining Diacritical Marks for Symbols (U+20D0 - U+20FF) */
146
147 if ((n >= 0xfe20 && n <= 0xfe2f) ||
148 (n >= 0x300 && n <= 0x36f) || (n >= 0x1ab0 && n <= 0x1aff) ||
149 (n >= 0x1dc0 && n <= 0x1dff) || (n >= 0x20d0 && n <= 0x20ff)) {
150 *Copy -= 1;
151 ++rv;
152 }
153
154 return rv;
155 }
156
AlignIfCombinedSurrogate(GSM_Debug_Info * di,size_t * Copy,char * Buffer,size_t BufferLen)157 int AlignIfCombinedSurrogate(GSM_Debug_Info *di,
158 size_t *Copy,
159 char *Buffer,
160 size_t BufferLen)
161 {
162 int rv = 0;
163 unsigned int l1, l2, r1, r2;
164
165 /* Precondition:
166 * If we have two or fewer characters to copy, omitting two
167 * of them would cause us to send empty message segments. If
168 * there aren't at least two characters remaining *after* the
169 * copy boundary, then there can't possibly be space for a
170 * second surrogate pair there. In either case, send as-is. */
171
172 if (*Copy <= 2 || (*Copy + 2) >= BufferLen) {
173 return rv;
174 }
175
176 /* Fetch characters:
177 * We retrieve two UTF-16 characters directly preceeding the
178 * copy boundary, and two directly following the copy boundary. */
179
180 l1 = ReassembleCharacter(Buffer, *Copy - 2);
181 l2 = ReassembleCharacter(Buffer, *Copy - 1);
182 r1 = ReassembleCharacter(Buffer, *Copy);
183 r2 = ReassembleCharacter(Buffer, *Copy + 1);
184
185 /* Regional Indicator Symbol (U+1F1E6 - U+1F1FF)
186 * UTF-16 surrogate pairs: 0xd83c 0xdde6 - 0xd83c 0xddff */
187
188 if (l1 == 0xd83c && r1 == 0xd83c &&
189 l2 >= 0xdde6 && l2 <= 0xddff && r2 >= 0xdde6 && r2 <= 0xddff) {
190 *Copy -= 2;
191 rv += 2;
192 }
193
194 return rv;
195 }
196
AlignSegmentForContent(GSM_Debug_Info * di,size_t * Copy,char * Buffer,size_t BufferLen)197 int AlignSegmentForContent(GSM_Debug_Info *di,
198 size_t *Copy,
199 char *Buffer,
200 size_t BufferLen)
201 {
202 int rv = 0;
203
204 if (!(rv += AlignIfSurrogatePair(di, Copy, Buffer, BufferLen))) {
205 rv += AlignIfCombinedCharacter(di, Copy, Buffer, BufferLen);
206 }
207
208 rv += AlignIfCombinedSurrogate(di, Copy, Buffer, BufferLen);
209 return rv;
210 }
211
GSM_AddSMS_Text_UDH(GSM_Debug_Info * di,GSM_MultiSMSMessage * SMS,GSM_Coding_Type Coding,char * Buffer,size_t BufferLen,gboolean UDH,size_t * UsedText,size_t * CopiedText,size_t * CopiedSMSText)212 GSM_Error GSM_AddSMS_Text_UDH(GSM_Debug_Info *di,
213 GSM_MultiSMSMessage *SMS,
214 GSM_Coding_Type Coding,
215 char *Buffer,
216 size_t BufferLen,
217 gboolean UDH,
218 size_t *UsedText,
219 size_t *CopiedText,
220 size_t *CopiedSMSText)
221 {
222 size_t FreeText=0,FreeBytes=0,Copy,i,j;
223
224 smfprintf(di, "Checking used: ");
225 GSM_Find_Free_Used_SMS2(
226 di, Coding, &(SMS->SMS[SMS->Number]),
227 UsedText, &FreeText, &FreeBytes
228 );
229
230 if (UDH) {
231 smfprintf(di, "Adding UDH\n");
232 if (FreeBytes - BufferLen <= 0) {
233 smfprintf(di, "Going to the new SMS\n");
234 SMS->Number++;
235 GSM_Find_Free_Used_SMS2(
236 di, Coding, &(SMS->SMS[SMS->Number]),
237 UsedText, &FreeText, &FreeBytes
238 );
239 }
240 if (SMS->SMS[SMS->Number].UDH.Length == 0) {
241 SMS->SMS[SMS->Number].UDH.Length = 1;
242 SMS->SMS[SMS->Number].UDH.Text[0] = 0x00;
243 }
244 memcpy(SMS->SMS[SMS->Number].UDH.Text+SMS->SMS[SMS->Number].UDH.Length,Buffer,BufferLen);
245 SMS->SMS[SMS->Number].UDH.Length += BufferLen;
246 SMS->SMS[SMS->Number].UDH.Text[0] += BufferLen;
247 SMS->SMS[SMS->Number].UDH.Type = UDH_UserUDH;
248 smfprintf(di, "UDH added %ld\n", (long)BufferLen);
249 } else {
250 smfprintf(di, "Adding text\n");
251 if (FreeText == 0) {
252 smfprintf(di, "Going to the new SMS\n");
253 SMS->Number++;
254 GSM_Find_Free_Used_SMS2(
255 di, Coding, &(SMS->SMS[SMS->Number]),
256 UsedText, &FreeText, &FreeBytes
257 );
258 }
259
260 Copy = FreeText;
261 smfprintf(di, "Copy %ld (max %ld)\n", (long)Copy, (long)BufferLen);
262 if (BufferLen < Copy) {
263 Copy = BufferLen;
264 }
265
266 switch (Coding) {
267 case SMS_Coding_Default_No_Compression:
268 FindDefaultAlphabetLen(Buffer,&i,&j,FreeText);
269 smfprintf(di, "Defalt text, length %ld %ld\n", (long)i, (long)j);
270 SMS->SMS[SMS->Number].Text[UnicodeLength(SMS->SMS[SMS->Number].Text)*2+i*2] = 0;
271 SMS->SMS[SMS->Number].Text[UnicodeLength(SMS->SMS[SMS->Number].Text)*2+i*2+1] = 0;
272 memcpy(SMS->SMS[SMS->Number].Text+UnicodeLength(SMS->SMS[SMS->Number].Text)*2,Buffer,i*2);
273 *CopiedText = i;
274 *CopiedSMSText = j;
275 SMS->SMS[SMS->Number].Length += i;
276 break;
277 case SMS_Coding_Unicode_No_Compression:
278 AlignSegmentForContent(di, &Copy, Buffer, BufferLen);
279 SMS->SMS[SMS->Number].Text[UnicodeLength(SMS->SMS[SMS->Number].Text)*2+Copy*2] = 0;
280 SMS->SMS[SMS->Number].Text[UnicodeLength(SMS->SMS[SMS->Number].Text)*2+Copy*2+1] = 0;
281 memcpy(SMS->SMS[SMS->Number].Text+UnicodeLength(SMS->SMS[SMS->Number].Text)*2,Buffer,Copy*2);
282 *CopiedText = *CopiedSMSText = Copy;
283 SMS->SMS[SMS->Number].Length += Copy;
284 break;
285 case SMS_Coding_8bit:
286 memcpy(SMS->SMS[SMS->Number].Text+SMS->SMS[SMS->Number].Length,Buffer,Copy);
287 SMS->SMS[SMS->Number].Length += Copy;
288 *CopiedText = *CopiedSMSText = Copy;
289 break;
290 default:
291 break;
292 }
293 smfprintf(di, "Text added\n");
294 }
295
296 smfprintf(di, "Checking at the end: ");
297 GSM_Find_Free_Used_SMS2(
298 di, Coding, &(SMS->SMS[SMS->Number]),
299 UsedText, &FreeText, &FreeBytes
300 );
301
302 return ERR_NONE;
303 }
304
GSM_MakeMultiPartSMS(GSM_Debug_Info * di,GSM_MultiSMSMessage * SMS,unsigned char * MessageBuffer,size_t MessageLength,GSM_UDH UDHType,GSM_Coding_Type Coding,int Class,unsigned char ReplaceMessage)305 void GSM_MakeMultiPartSMS(GSM_Debug_Info *di, GSM_MultiSMSMessage *SMS,
306 unsigned char *MessageBuffer,
307 size_t MessageLength,
308 GSM_UDH UDHType,
309 GSM_Coding_Type Coding,
310 int Class,
311 unsigned char ReplaceMessage)
312 {
313 size_t Len,UsedText = 0,CopiedText = 0,CopiedSMSText = 0;
314 int j;
315 unsigned char UDHID;
316 GSM_DateTime Date;
317
318 Len = 0;
319 while(1) {
320 if (SMS->Number >= GSM_MAX_MULTI_SMS) {
321 break;
322 }
323 GSM_SetDefaultSMSData(&SMS->SMS[SMS->Number]);
324 SMS->SMS[SMS->Number].Class = Class;
325 SMS->SMS[SMS->Number].Coding = Coding;
326
327 SMS->SMS[SMS->Number].UDH.Type = UDHType;
328 GSM_EncodeUDHHeader(di, &SMS->SMS[SMS->Number].UDH);
329
330 if (Coding == SMS_Coding_8bit) {
331 GSM_AddSMS_Text_UDH(di, SMS,Coding,MessageBuffer+Len,MessageLength - Len,FALSE,&UsedText,&CopiedText,&CopiedSMSText);
332 } else {
333 GSM_AddSMS_Text_UDH(di, SMS,Coding,MessageBuffer+Len*2,MessageLength - Len,FALSE,&UsedText,&CopiedText,&CopiedSMSText);
334 }
335 Len += CopiedText;
336 smfprintf(di, "%ld %ld\n", (long)Len, (long)MessageLength);
337 SMS->Number++;
338 if (Len == MessageLength) break;
339 }
340
341 UDHID = GSM_MakeSMSIDFromTime();
342 GSM_GetCurrentDateTime (&Date);
343 for (j=0;j<SMS->Number;j++) {
344 SMS->SMS[j].UDH.Type = UDHType;
345 SMS->SMS[j].UDH.ID8bit = UDHID;
346 SMS->SMS[j].UDH.ID16bit = UDHID + 256 * Date.Hour;
347 SMS->SMS[j].UDH.PartNumber = j+1;
348 SMS->SMS[j].UDH.AllParts = SMS->Number;
349 GSM_EncodeUDHHeader(di, &SMS->SMS[j].UDH);
350 }
351 if (SMS->Number == 1) SMS->SMS[0].ReplaceMessage = ReplaceMessage;
352 }
353
354 /* Calculates number of SMS and number of left chars in SMS */
GSM_SMSCounter(GSM_Debug_Info * di,unsigned char * MessageBuffer,GSM_UDH UDHType,GSM_Coding_Type Coding,int * SMSNum,size_t * CharsLeft)355 void GSM_SMSCounter(GSM_Debug_Info *di,
356 unsigned char *MessageBuffer,
357 GSM_UDH UDHType,
358 GSM_Coding_Type Coding,
359 int *SMSNum,
360 size_t *CharsLeft)
361 {
362 size_t UsedText=0,FreeBytes=0;
363 GSM_MultiSMSMessage MultiSMS;
364
365 MultiSMS.Number = 0;
366 GSM_MakeMultiPartSMS(di, &MultiSMS,MessageBuffer,UnicodeLength(MessageBuffer),UDHType,Coding,-1,FALSE);
367 GSM_Find_Free_Used_SMS2(
368 di, Coding, &(MultiSMS.SMS[MultiSMS.Number-1]),
369 &UsedText, CharsLeft, &FreeBytes
370 );
371 *SMSNum = MultiSMS.Number;
372 }
373
374 /* Nokia Smart Messaging 3.0 */
GSM_EncodeSMS30MultiPartSMS(GSM_MultiPartSMSInfo * Info,char * Buffer,size_t * Length)375 static void GSM_EncodeSMS30MultiPartSMS(GSM_MultiPartSMSInfo *Info,
376 char *Buffer, size_t *Length)
377 {
378 size_t len;
379
380 /*SM version. Here 3.0*/
381 Buffer[(*Length)++] = 0x30;
382
383 if (Info->Entries[0].ID == SMS_NokiaProfileLong) {
384 if (Info->Entries[0].Buffer != NULL) {
385 if (Info->Entries[0].Buffer[0]!=0x00 || Info->Entries[0].Buffer[1]!=0x00) {
386 Buffer[(*Length)++] = SM30_PROFILENAME;
387 Buffer[(*Length)++] = 0x00;
388 Buffer[(*Length)++] = 2*UnicodeLength(Info->Entries[0].Buffer);
389 CopyUnicodeString(Buffer+(*Length),Info->Entries[0].Buffer);
390 *Length = *Length + 2*UnicodeLength(Info->Entries[0].Buffer);
391 }
392 }
393 if (Info->Entries[0].Ringtone != NULL) {
394 Buffer[(*Length)++] = SM30_RINGTONE;
395 /* Length for this part later will be changed */
396 Buffer[(*Length)++] = 0x01;
397 Buffer[(*Length)++] = 0x00;
398 /* Smart Messaging 3.0 says: 16*9=144 bytes,
399 * but on 3310 4.02 it was possible to save about 196 chars
400 * (without cutting) */
401 len = 196;
402 Info->Entries[0].RingtoneNotes=GSM_EncodeNokiaRTTLRingtone(Info->Entries[0].Ringtone,Buffer+(*Length),&len);
403 Buffer[(*Length)-2] = len / 256;
404 Buffer[(*Length)-1] = len % 256;
405 *Length = *Length + len;
406 }
407 }
408 if (Info->Entries[0].Bitmap != NULL) {
409 if (Info->Entries[0].ID == SMS_NokiaPictureImageLong) {
410 Buffer[(*Length)++] = SM30_OTA;
411 } else {
412 Buffer[(*Length)++] = SM30_SCREENSAVER;
413 }
414 Buffer[(*Length)++] = 0x01;
415 Buffer[(*Length)++] = 0x00;
416 NOKIA_CopyBitmap(GSM_NokiaPictureImage, &Info->Entries[0].Bitmap->Bitmap[0], Buffer, Length);
417 if (Info->Entries[0].Bitmap->Bitmap[0].Text[0]!=0 || Info->Entries[0].Bitmap->Bitmap[0].Text[1]!=0) {
418 if (Info->UnicodeCoding) {
419 Buffer[(*Length)++] = SM30_UNICODETEXT;
420 /* Length for text part */
421 Buffer[(*Length)++] = 0x00;
422 Buffer[(*Length)++] = UnicodeLength(Info->Entries[0].Bitmap->Bitmap[0].Text)*2;
423 memcpy(Buffer+(*Length),Info->Entries[0].Bitmap->Bitmap[0].Text,UnicodeLength(Info->Entries[0].Bitmap->Bitmap[0].Text)*2);
424 *Length = *Length + UnicodeLength(Info->Entries[0].Bitmap->Bitmap[0].Text)*2;
425 } else {
426 /*ID for ISO-8859-1 text*/
427 Buffer[(*Length)++] = SM30_ISOTEXT;
428 Buffer[(*Length)++] = 0x00;
429 Buffer[(*Length)++] = UnicodeLength(Info->Entries[0].Bitmap->Bitmap[0].Text);
430 memcpy(Buffer+(*Length),DecodeUnicodeString(Info->Entries[0].Bitmap->Bitmap[0].Text),UnicodeLength(Info->Entries[0].Bitmap->Bitmap[0].Text));
431 *Length = *Length +UnicodeLength(Info->Entries[0].Bitmap->Bitmap[0].Text);
432 }
433 }
434 }
435 }
436
437 /* Alcatel docs from www.alcatel.com/wap/ahead */
GSM_EncodeAlcatelMultiPartSMS(GSM_Debug_Info * di,GSM_MultiSMSMessage * SMS,unsigned char * Data,size_t Len,unsigned char * Name,size_t Type)438 GSM_Error GSM_EncodeAlcatelMultiPartSMS(GSM_Debug_Info *di, GSM_MultiSMSMessage *SMS,
439 unsigned char *Data,
440 size_t Len,
441 unsigned char *Name,
442 size_t Type)
443 {
444 unsigned char buff[100],UDHID;
445 size_t i, p;
446 GSM_UDHHeader MyUDH;
447
448 for (i=0;i<GSM_MAX_MULTI_SMS;i++) {
449 GSM_SetDefaultSMSData(&SMS->SMS[i]);
450 SMS->SMS[i].UDH.Type = UDH_UserUDH;
451 SMS->SMS[i].UDH.Text[1] = 0x80; /* Alcatel */
452 p = UnicodeLength(Name);
453 EncodeDefault(buff, Name, &p, TRUE, NULL);
454 SMS->SMS[i].UDH.Text[2] = GSM_PackSevenBitsToEight(0, buff, SMS->SMS[i].UDH.Text+3, p) + 4;
455 SMS->SMS[i].UDH.Text[3] = GSM_PackSevenBitsToEight(0, buff, SMS->SMS[i].UDH.Text+3, p);
456 SMS->SMS[i].UDH.Text[4] = Type;
457 SMS->SMS[i].UDH.Text[5] = Len / 256;
458 SMS->SMS[i].UDH.Text[6] = Len % 256;
459 SMS->SMS[i].UDH.Text[0] = 6 + SMS->SMS[i].UDH.Text[3];
460 SMS->SMS[i].UDH.Length = SMS->SMS[i].UDH.Text[0] + 1;
461
462 if (Len > (size_t)(140 - SMS->SMS[i].UDH.Length)) {
463 MyUDH.Type = UDH_ConcatenatedMessages;
464 GSM_EncodeUDHHeader(di, &MyUDH);
465
466 memcpy(SMS->SMS[i].UDH.Text+SMS->SMS[i].UDH.Length,MyUDH.Text+1,MyUDH.Length-1);
467 SMS->SMS[i].UDH.Text[0] += MyUDH.Length-1;
468 SMS->SMS[i].UDH.Length += MyUDH.Length-1;
469 }
470
471 SMS->SMS[i].Coding = SMS_Coding_8bit;
472 SMS->SMS[i].Class = 1;
473 }
474
475 p = 0;
476 while (p != Len) {
477 i = 140-SMS->SMS[SMS->Number].UDH.Length;
478 if (Len - p < i) i = Len - p;
479 memcpy(SMS->SMS[SMS->Number].Text,Data+p,i);
480 p += i;
481 SMS->SMS[SMS->Number].Length = i;
482 SMS->Number++;
483
484 }
485
486 /* Linked sms UDH */
487 if (SMS->Number != 1) {
488 UDHID = GSM_MakeSMSIDFromTime();
489 for (i = 0; i < (size_t)SMS->Number; i++) {
490 SMS->SMS[i].UDH.Text[SMS->SMS[i].UDH.Length-3] = UDHID;
491 SMS->SMS[i].UDH.Text[SMS->SMS[i].UDH.Length-2] = SMS->Number;
492 SMS->SMS[i].UDH.Text[SMS->SMS[i].UDH.Length-1] = i+1;
493 }
494 }
495
496 return ERR_NONE;
497 }
498
499 /* Alcatel docs from www.alcatel.com/wap/ahead and other */
GSM_EncodeMultiPartSMS(GSM_Debug_Info * di,GSM_MultiPartSMSInfo * Info,GSM_MultiSMSMessage * SMS)500 GSM_Error GSM_EncodeMultiPartSMS(GSM_Debug_Info *di,
501 GSM_MultiPartSMSInfo *Info,
502 GSM_MultiSMSMessage *SMS)
503 {
504 unsigned char *Buffer;
505 unsigned char *Buffer2;
506 size_t buffer_size = GSM_MAX_SMS_LENGTH * 2 * GSM_MAX_MULTI_SMS;
507 int i, Class = -1, j;
508 size_t p;
509 size_t Length = 0, smslen;
510 GSM_Error error;
511 GSM_Coding_Type Coding = SMS_Coding_8bit;
512 GSM_UDH UDH = UDH_NoUDH;
513 GSM_UDHHeader UDHHeader;
514 gboolean EMS = FALSE;
515 int textnum = 0;
516
517 if (Info->EntriesNum == 0) return ERR_EMPTY;
518
519 Buffer = malloc(buffer_size);
520 if (Buffer == NULL) {
521 return ERR_MOREMEMORY;
522 }
523 Buffer2 = malloc(buffer_size);
524 if (Buffer2 == NULL) {
525 free(Buffer);
526 return ERR_MOREMEMORY;
527 }
528
529 SMS->Number = 0;
530
531 if (Info->Entries[0].ID == SMS_AlcatelSMSTemplateName) {
532 Buffer[Length++] = 0x00; /* number of elements */
533 for (i=1;i<Info->EntriesNum;i++) {
534 switch (Info->Entries[i].ID) {
535 case SMS_EMSSound10:
536 case SMS_EMSSound12:
537 case SMS_EMSSonyEricssonSound:
538 case SMS_EMSSound10Long:
539 case SMS_EMSSound12Long:
540 case SMS_EMSSonyEricssonSoundLong:
541 case SMS_EMSVariableBitmap:
542 case SMS_EMSAnimation:
543 case SMS_EMSVariableBitmapLong:
544 break;
545 case SMS_EMSPredefinedSound:
546 Buffer[0]++;
547 Buffer[Length++] = 0x01; /* type of data */
548 Buffer[Length++] = 1 % 256; /* len */
549 Buffer[Length++] = 1 / 256; /* len */
550 Buffer[Length++] = Info->Entries[i].Number;
551 break;
552 case SMS_EMSPredefinedAnimation:
553 Buffer[0]++;
554 Buffer[Length++] = 0x02; /* type of data */
555 Buffer[Length++] = 1 % 256; /* len */
556 Buffer[Length++] = 1 / 256; /* len */
557 Buffer[Length++] = Info->Entries[i].Number;
558 break;
559 case SMS_ConcatenatedTextLong:
560 Buffer[0]++;
561 p = UnicodeLength(Info->Entries[i].Buffer);
562 EncodeDefault(Buffer2, Info->Entries[i].Buffer, &p, TRUE, NULL);
563 Buffer[Length++] = 0x00; /* type of data */
564 Length = Length + 2;
565 smslen = GSM_PackSevenBitsToEight(0, Buffer2, Buffer+Length, p);
566 Buffer[Length-2] = smslen % 256; /* len */
567 Buffer[Length-1] = smslen / 256; /* len */
568 Length = Length + smslen;
569 break;
570 default:
571 error = ERR_UNKNOWN;
572 goto out;
573 }
574 }
575 Buffer[0] = Buffer[0] * 2;
576 error = GSM_EncodeAlcatelMultiPartSMS(di, SMS,Buffer,Length,Info->Entries[0].Buffer,ALCATELTDD_SMSTEMPLATE);
577 goto out;
578 }
579
580 for (i=0;i<Info->EntriesNum;i++) {
581 switch (Info->Entries[i].ID) {
582 case SMS_EMSPredefinedAnimation:
583 case SMS_EMSPredefinedSound:
584 case SMS_EMSSound10:
585 case SMS_EMSSound12:
586 case SMS_EMSSonyEricssonSound:
587 case SMS_EMSSound10Long:
588 case SMS_EMSSound12Long:
589 case SMS_EMSSonyEricssonSoundLong:
590 case SMS_EMSFixedBitmap:
591 case SMS_EMSVariableBitmap:
592 case SMS_EMSAnimation:
593 case SMS_EMSVariableBitmapLong:
594 EMS = TRUE;
595 break;
596 case SMS_ConcatenatedTextLong:
597 case SMS_ConcatenatedTextLong16bit:
598
599 /* This covers situation, when somebody will call function
600 * with two or more SMS_Concatenated.... entries only.
601 * It will be still only linked sms, but functions below
602 * will pack only first entry according to own limits.
603 * We redirect to EMS functions, because they are more generic
604 * here and will handle it correctly and produce linked sms
605 * from all entries
606 */
607 textnum ++;
608 if (textnum > 1) EMS = TRUE;
609
610 if (Info->Entries[i].Left || Info->Entries[i].Right ||
611 Info->Entries[i].Center || Info->Entries[i].Large ||
612 Info->Entries[i].Small || Info->Entries[i].Bold ||
613 Info->Entries[i].Italic || Info->Entries[i].Underlined ||
614 Info->Entries[i].Strikethrough) {
615 EMS = TRUE;
616 }
617 default:
618 break;
619 }
620 if (EMS) break;
621 }
622 if (EMS) {
623 error=GSM_EncodeEMSMultiPartSMS(di, Info,SMS,UDH_NoUDH);
624 if (error != ERR_NONE) {
625 goto out;
626 }
627 if (SMS->Number != 1) {
628 SMS->Number = 0;
629 for (i=0;i<Info->EntriesNum;i++) {
630 if (Info->Entries[i].ID == SMS_ConcatenatedTextLong16bit) {
631 error = GSM_EncodeEMSMultiPartSMS(di, Info,SMS,UDH_ConcatenatedMessages);
632 goto out;
633 }
634 }
635 error = GSM_EncodeEMSMultiPartSMS(di, Info,SMS,UDH_ConcatenatedMessages16bit);
636 }
637 goto out;
638 }
639
640 if (Info->EntriesNum != 1) {
641 error = ERR_UNKNOWN;
642 goto out;
643 }
644
645 switch (Info->Entries[0].ID) {
646 case SMS_AlcatelMonoBitmapLong:
647 Buffer[0] = Info->Entries[0].Bitmap->Bitmap[0].BitmapWidth;
648 Buffer[1] = Info->Entries[0].Bitmap->Bitmap[0].BitmapHeight;
649 PHONE_EncodeBitmap(GSM_AlcatelBMMIPicture, Buffer+2, &Info->Entries[0].Bitmap->Bitmap[0]);
650 Length = PHONE_GetBitmapSize(GSM_AlcatelBMMIPicture,Info->Entries[0].Bitmap->Bitmap[0].BitmapWidth,Info->Entries[0].Bitmap->Bitmap[0].BitmapHeight)+2;
651 error = GSM_EncodeAlcatelMultiPartSMS(di, SMS,Buffer,Length,Info->Entries[0].Bitmap->Bitmap[0].Text,ALCATELTDD_PICTURE);
652 goto out;
653 case SMS_AlcatelMonoAnimationLong:
654 /* Number of sequence words */
655 Buffer[0] = (Info->Entries[0].Bitmap->Number+1) % 256;
656 Buffer[1] = (Info->Entries[0].Bitmap->Number+1) / 256;
657 /* Picture display time 1 second (1 = 100ms) */
658 Buffer[2] = 10 % 256;
659 Buffer[3] = 10 / 256 + 0xF0;
660
661 Length = 4;
662 j = 0;
663
664 /* Offsets to bitmaps */
665 for (i=0;i<Info->Entries[0].Bitmap->Number;i++) {
666 Buffer[Length++] = (4+j+Info->Entries[0].Bitmap->Number*2) % 256;
667 Buffer[Length++] = (4+j+Info->Entries[0].Bitmap->Number*2) / 256;
668 j += PHONE_GetBitmapSize(GSM_AlcatelBMMIPicture,Info->Entries[0].Bitmap->Bitmap[i].BitmapWidth,Info->Entries[0].Bitmap->Bitmap[i].BitmapHeight)+2;
669 }
670
671 /* Bitmaps */
672 for (i=0;i<Info->Entries[0].Bitmap->Number;i++) {
673 Buffer[Length++] = Info->Entries[0].Bitmap->Bitmap[i].BitmapWidth;
674 Buffer[Length++] = Info->Entries[0].Bitmap->Bitmap[i].BitmapHeight;
675 PHONE_EncodeBitmap(GSM_AlcatelBMMIPicture, Buffer+Length, &Info->Entries[0].Bitmap->Bitmap[i]);
676 Length += PHONE_GetBitmapSize(GSM_AlcatelBMMIPicture,Info->Entries[0].Bitmap->Bitmap[i].BitmapWidth,Info->Entries[0].Bitmap->Bitmap[i].BitmapHeight);
677 }
678 error = GSM_EncodeAlcatelMultiPartSMS(di, SMS,Buffer,Length,Info->Entries[0].Bitmap->Bitmap[0].Text,ALCATELTDD_ANIMATION);
679 goto out;
680 case SMS_MMSIndicatorLong:
681 Class = 1;
682 UDH = UDH_MMSIndicatorLong;
683 GSM_EncodeMMSIndicatorSMSText(Buffer,&Length,Info->Entries[0].MMSIndicator);
684 break;
685 case SMS_WAPIndicatorLong:
686 Class = 1;
687 UDH = UDH_MMSIndicatorLong;
688 GSM_EncodeWAPIndicatorSMSText(Buffer,&Length,Info->Entries[0].MMSIndicator->Title,Info->Entries[0].MMSIndicator->Address);
689 break;
690 case SMS_NokiaRingtoneLong:
691 case SMS_NokiaRingtone:
692 UDH = UDH_NokiaRingtone;
693 Class = 1;
694 /* 7 = length of UDH_NokiaRingtone UDH header */
695 Length = GSM_MAX_8BIT_SMS_LENGTH-7;
696 Info->Entries[0].RingtoneNotes = GSM_EncodeNokiaRTTLRingtone(Info->Entries[0].Ringtone,Buffer,&Length);
697 if (Info->Entries[0].ID == SMS_NokiaRingtone) break;
698 if (Info->Entries[0].RingtoneNotes != Info->Entries[0].Ringtone->NoteTone.NrCommands) {
699 UDH = UDH_NokiaRingtoneLong;
700 Length = (GSM_MAX_8BIT_SMS_LENGTH-12)*3;
701 Info->Entries[0].RingtoneNotes = GSM_EncodeNokiaRTTLRingtone(Info->Entries[0].Ringtone,Buffer,&Length);
702 }
703 break;
704 case SMS_NokiaOperatorLogoLong:
705 if (Info->Entries[0].Bitmap->Bitmap[0].BitmapWidth > 72 || Info->Entries[0].Bitmap->Bitmap[0].BitmapHeight > 14) {
706 UDH = UDH_NokiaOperatorLogoLong;
707 Class = 1;
708 NOKIA_EncodeNetworkCode(Buffer, Info->Entries[0].Bitmap->Bitmap[0].NetworkCode);
709 Length = Length + 3;
710 NOKIA_CopyBitmap(GSM_Nokia7110OperatorLogo, &Info->Entries[0].Bitmap->Bitmap[0], Buffer, &Length);
711 break;
712 }
713 FALLTHROUGH
714 case SMS_NokiaOperatorLogo:
715 UDH = UDH_NokiaOperatorLogo;
716 Class = 1;
717 NOKIA_EncodeNetworkCode(Buffer, Info->Entries[0].Bitmap->Bitmap[0].NetworkCode);
718 Length = Length + 3;
719 NOKIA_CopyBitmap(GSM_NokiaOperatorLogo, &Info->Entries[0].Bitmap->Bitmap[0], Buffer, &Length);
720 break;
721 case SMS_NokiaCallerLogo:
722 UDH = UDH_NokiaCallerLogo;
723 Class = 1;
724 NOKIA_CopyBitmap(GSM_NokiaCallerLogo, &Info->Entries[0].Bitmap->Bitmap[0], Buffer, &Length);
725 break;
726 case SMS_NokiaProfileLong:
727 case SMS_NokiaPictureImageLong:
728 case SMS_NokiaScreenSaverLong:
729 Class = 1;
730 UDH = UDH_NokiaProfileLong;
731 GSM_EncodeSMS30MultiPartSMS(Info,Buffer,&Length);
732 break;
733 case SMS_NokiaWAPBookmarkLong:
734 Class = 1;
735 NOKIA_EncodeWAPBookmarkSMSText(Buffer,&Length,Info->Entries[0].Bookmark);
736 /* 7 = length of UDH_NokiaWAP UDH header */
737 if (Length>(GSM_MAX_8BIT_SMS_LENGTH-7)) {
738 UDH=UDH_NokiaWAPLong;
739 } else {
740 UDH=UDH_NokiaWAP;
741 }
742 break;
743 case SMS_NokiaWAPSettingsLong:
744 Class = 1;
745 UDH = UDH_NokiaWAPLong;
746 NOKIA_EncodeWAPMMSSettingsSMSText(Buffer,&Length,Info->Entries[0].Settings,FALSE);
747 break;
748 case SMS_NokiaMMSSettingsLong:
749 Class = 1;
750 UDH = UDH_NokiaWAPLong;
751 NOKIA_EncodeWAPMMSSettingsSMSText(Buffer,&Length,Info->Entries[0].Settings,TRUE);
752 break;
753 case SMS_NokiaVCARD10Long:
754 Coding = SMS_Coding_Default_No_Compression;
755 UDH = UDH_NokiaPhonebookLong;
756 error = GSM_EncodeVCARD(di, Buffer, buffer_size, &Length, Info->Entries[0].Phonebook, TRUE, Nokia_VCard10);
757 if (error != ERR_NONE) {
758 goto out;
759 }
760 memcpy(Buffer2,Buffer,Length);
761 EncodeUnicode(Buffer,Buffer2,Length);
762 break;
763 case SMS_NokiaVCARD21Long:
764 error = GSM_EncodeVCARD(di, Buffer, buffer_size, &Length, Info->Entries[0].Phonebook, TRUE, Nokia_VCard21);
765 if (error != ERR_NONE) {
766 goto out;
767 }
768 Coding = SMS_Coding_Default_No_Compression;
769 UDH = UDH_NokiaPhonebookLong;
770 memcpy(Buffer2,Buffer,Length);
771 EncodeUnicode(Buffer,Buffer2,Length);
772 break;
773 case SMS_VCARD10Long:
774 error = GSM_EncodeVCARD(di, Buffer, buffer_size, &Length,Info->Entries[0].Phonebook,TRUE,Nokia_VCard10);
775 if (error != ERR_NONE) {
776 goto out;
777 }
778 if (Length>GSM_MAX_SMS_CHARS_LENGTH) UDH = UDH_ConcatenatedMessages;
779 Coding = SMS_Coding_Default_No_Compression;
780 memcpy(Buffer2,Buffer,Length);
781 EncodeUnicode(Buffer,Buffer2,Length);
782 break;
783 case SMS_VCARD21Long:
784 error = GSM_EncodeVCARD(di, Buffer, buffer_size, &Length,Info->Entries[0].Phonebook,TRUE,Nokia_VCard21);
785 if (error != ERR_NONE) {
786 goto out;
787 }
788 if (Length>GSM_MAX_SMS_CHARS_LENGTH) UDH = UDH_ConcatenatedMessages;
789 Coding = SMS_Coding_Default_No_Compression;
790 memcpy(Buffer2,Buffer,Length);
791 EncodeUnicode(Buffer,Buffer2,Length);
792 break;
793 case SMS_NokiaVCALENDAR10Long:
794 error=GSM_EncodeVCALENDAR(Buffer, buffer_size,&Length,Info->Entries[0].Calendar,TRUE,Nokia_VCalendar);
795 if (error != ERR_NONE) {
796 goto out;
797 }
798 Coding = SMS_Coding_Default_No_Compression;
799 /* Is 1 SMS ? 8 = length of ..SCKE4 */
800 if (Length <= GSM_MAX_SMS_CHARS_LENGTH - 8) {
801 sprintf(Buffer,"//SCKE4 ");
802 Length = 8;
803 } else {
804 UDH = UDH_NokiaCalendarLong;
805 }
806 memcpy(Buffer2,Buffer,Length);
807 EncodeUnicode(Buffer,Buffer2,Length);
808 break;
809 case SMS_NokiaVTODOLong:
810 error=GSM_EncodeVTODO(Buffer, buffer_size,&Length,Info->Entries[0].ToDo,TRUE,Nokia_VToDo);
811 if (error != ERR_NONE) {
812 goto out;
813 }
814 UDH = UDH_NokiaCalendarLong;
815 Coding = SMS_Coding_Default_No_Compression;
816 memcpy(Buffer2,Buffer,Length);
817 EncodeUnicode(Buffer,Buffer2,Length);
818 break;
819 case SMS_DisableVoice:
820 case SMS_DisableFax:
821 case SMS_DisableEmail:
822 case SMS_EnableVoice:
823 case SMS_EnableFax:
824 case SMS_EnableEmail:
825 case SMS_VoidSMS:
826 case SMS_Text:
827 Class = Info->Class;
828 switch (Info->Entries[0].ID) {
829 case SMS_DisableVoice : UDH = UDH_DisableVoice; break;
830 case SMS_DisableFax : UDH = UDH_DisableFax; break;
831 case SMS_DisableEmail : UDH = UDH_DisableEmail; break;
832 case SMS_EnableVoice : UDH = UDH_EnableVoice; break;
833 case SMS_EnableFax : UDH = UDH_EnableFax; break;
834 case SMS_EnableEmail : UDH = UDH_EnableEmail; break;
835 case SMS_VoidSMS : UDH = UDH_VoidSMS; break;
836 case SMS_Text : UDH = UDH_NoUDH; break;
837 default : break;
838 }
839 UDHHeader.Type = UDH;
840 GSM_EncodeUDHHeader(di, &UDHHeader);
841 memcpy(Buffer,Info->Entries[0].Buffer,UnicodeLength(Info->Entries[0].Buffer)*2+2);
842 if (Info->UnicodeCoding) {
843 Coding = SMS_Coding_Unicode_No_Compression;
844 Length = UnicodeLength(Info->Entries[0].Buffer);
845 if (Length > (size_t)(140 - UDHHeader.Length) / 2) {
846 Length = (140 - UDHHeader.Length) / 2;
847 }
848 } else {
849 Coding = SMS_Coding_Default_No_Compression;
850 FindDefaultAlphabetLen(Info->Entries[0].Buffer,&Length,&smslen,(GSM_MAX_8BIT_SMS_LENGTH-UDHHeader.Length)*8/7);
851 }
852 break;
853 case SMS_USSD:
854 Class = Info->Class;
855 break;
856 case SMS_ConcatenatedAutoTextLong:
857 case SMS_ConcatenatedAutoTextLong16bit:
858 smslen = UnicodeLength(Info->Entries[0].Buffer);
859 memcpy(Buffer,Info->Entries[0].Buffer,smslen*2);
860 EncodeDefault(Buffer2, Buffer, &smslen, TRUE, NULL);
861 DecodeDefault(Buffer, Buffer2, smslen, TRUE, NULL);
862 #ifdef DEBUG
863 if (di->dl == DL_TEXTALL || di->dl == DL_TEXTALLDATE) {
864 smfprintf(di, "Info->Entries[0].Buffer:\n");
865 DumpMessage(di, Info->Entries[0].Buffer, UnicodeLength(Info->Entries[0].Buffer)*2);
866 smfprintf(di, "Buffer:\n");
867 DumpMessage(di, Buffer, UnicodeLength(Buffer)*2);
868 }
869 #endif
870 Info->UnicodeCoding = FALSE;
871 for (smslen = 0; smslen < UnicodeLength(Info->Entries[0].Buffer) * 2; smslen++) {
872 if (Info->Entries[0].Buffer[smslen] != Buffer[smslen]) {
873 Info->UnicodeCoding = TRUE;
874 smfprintf(di, "Setting to Unicode %ld\n", (long)smslen);
875 break;
876 }
877 }
878 /* No break here - we go to the SMS_ConcatenatedTextLong */
879 FALLTHROUGH
880 case SMS_ConcatenatedTextLong:
881 case SMS_ConcatenatedTextLong16bit:
882 Class = Info->Class;
883 if (Info->Entries[0].Buffer == NULL) {
884 Buffer[0] = 0;
885 Buffer[1] = 0;
886 } else {
887 memcpy(Buffer,Info->Entries[0].Buffer,UnicodeLength(Info->Entries[0].Buffer)*2+2);
888 }
889 UDH = UDH_NoUDH;
890 if (Info->UnicodeCoding) {
891 Coding = SMS_Coding_Unicode_No_Compression;
892 Length = UnicodeLength(Buffer);
893 if (Info->Entries[0].ID == SMS_ConcatenatedTextLong16bit ||
894 Info->Entries[0].ID == SMS_ConcatenatedAutoTextLong16bit) {
895 if (Length>70) UDH=UDH_ConcatenatedMessages16bit;
896 } else {
897 if (Length>70) UDH=UDH_ConcatenatedMessages;
898 }
899 } else {
900 Coding = SMS_Coding_Default_No_Compression;
901 FindDefaultAlphabetLen(Buffer,&Length,&smslen,5000);
902 if (Info->Entries[0].ID == SMS_ConcatenatedTextLong16bit ||
903 Info->Entries[0].ID == SMS_ConcatenatedAutoTextLong16bit) {
904 if (smslen>GSM_MAX_SMS_CHARS_LENGTH) UDH=UDH_ConcatenatedMessages16bit;
905 } else {
906 if (smslen>GSM_MAX_SMS_CHARS_LENGTH) UDH=UDH_ConcatenatedMessages;
907 }
908 }
909 default:
910 break;
911 }
912 GSM_MakeMultiPartSMS(di, SMS,Buffer,Length,UDH,Coding,Class,Info->ReplaceMessage);
913 error = ERR_NONE;
914 out:
915 free(Buffer);
916 free(Buffer2);
917 return error;
918 }
919
GSM_ClearMultiPartSMSInfo(GSM_MultiPartSMSInfo * Info)920 void GSM_ClearMultiPartSMSInfo(GSM_MultiPartSMSInfo *Info)
921 {
922 int i;
923
924 for (i=0;i<GSM_MAX_MULTI_SMS;i++) {
925 Info->Entries[i].Number = 0;
926 Info->Entries[i].Ringtone = NULL;
927 Info->Entries[i].Bitmap = NULL;
928 Info->Entries[i].Bookmark = NULL;
929 Info->Entries[i].File = NULL;
930 Info->Entries[i].Settings = NULL;
931 Info->Entries[i].MMSIndicator = NULL;
932 Info->Entries[i].Phonebook = NULL;
933 Info->Entries[i].Calendar = NULL;
934 Info->Entries[i].ToDo = NULL;
935 Info->Entries[i].Protected = FALSE;
936
937 Info->Entries[i].Buffer = NULL;
938 Info->Entries[i].Left = FALSE;
939 Info->Entries[i].Right = FALSE;
940 Info->Entries[i].Center = FALSE;
941 Info->Entries[i].Large = FALSE;
942 Info->Entries[i].Small = FALSE;
943 Info->Entries[i].Bold = FALSE;
944 Info->Entries[i].Italic = FALSE;
945 Info->Entries[i].Underlined = FALSE;
946 Info->Entries[i].Strikethrough = FALSE;
947
948 Info->Entries[i].RingtoneNotes = 0;
949 }
950 Info->Unknown = FALSE;
951 Info->EntriesNum = 0;
952 Info->Class = -1;
953 Info->ReplaceMessage = 0;
954 Info->UnicodeCoding = FALSE;
955 }
956
GSM_FreeMultiPartSMSInfo(GSM_MultiPartSMSInfo * Info)957 void GSM_FreeMultiPartSMSInfo(GSM_MultiPartSMSInfo *Info)
958 {
959 int i;
960
961 for (i=0;i<GSM_MAX_MULTI_SMS;i++) {
962 free(Info->Entries[i].Ringtone);
963 Info->Entries[i].Ringtone = NULL;
964 free(Info->Entries[i].Bitmap);
965 Info->Entries[i].Bitmap = NULL;
966 free(Info->Entries[i].Bookmark);
967 Info->Entries[i].Bookmark = NULL;
968 free(Info->Entries[i].Settings);
969 Info->Entries[i].Settings = NULL;
970 free(Info->Entries[i].MMSIndicator);
971 Info->Entries[i].MMSIndicator = NULL;
972 free(Info->Entries[i].Phonebook);
973 Info->Entries[i].Phonebook = NULL;
974 free(Info->Entries[i].Calendar);
975 Info->Entries[i].Calendar = NULL;
976 free(Info->Entries[i].ToDo);
977 Info->Entries[i].ToDo = NULL;
978 free(Info->Entries[i].Buffer);
979 Info->Entries[i].Buffer = NULL;
980 }
981 }
982
983 /**
984 * Decodes long MMS notification SMS.
985 */
GSM_DecodeMMSIndication(GSM_Debug_Info * di,GSM_MultiPartSMSInfo * Info,GSM_MultiSMSMessage * SMS)986 gboolean GSM_DecodeMMSIndication(GSM_Debug_Info *di,
987 GSM_MultiPartSMSInfo *Info,
988 GSM_MultiSMSMessage *SMS)
989 {
990 int i, Length = 0, j;
991 unsigned char Buffer[GSM_MAX_SMS_LENGTH*2*GSM_MAX_MULTI_SMS];
992
993 /* Concatenate data */
994 for (i = 0; i < SMS->Number; i++) {
995 if (SMS->SMS[i].UDH.Type == UDH_MMSIndicatorLong) {
996 if (SMS->SMS[i].UDH.Text[11] != i+1 ||
997 SMS->SMS[i].UDH.Text[10] != SMS->Number) {
998 return FALSE;
999 }
1000 } else if (SMS->SMS[i].UDH.Type != UDH_UserUDH) {
1001 return FALSE;
1002 }
1003 memcpy(Buffer + Length, SMS->SMS[i].Text, SMS->SMS[i].Length);
1004 Length = Length + SMS->SMS[i].Length;
1005 }
1006
1007 dbgprintf(di, "MMS data of length %d:\n", Length);
1008 DumpMessage(di, Buffer, Length);
1009 Info->Entries[0].MMSIndicator = (GSM_MMSIndicator *)malloc(sizeof(GSM_MMSIndicator));
1010 if (Info->Entries[0].MMSIndicator == NULL) {
1011 return FALSE;
1012 }
1013 Info->EntriesNum = 1;
1014 Info->Entries[0].ID = SMS_MMSIndicatorLong;
1015 Info->Entries[0].MMSIndicator->Class = GSM_MMS_None;
1016 Info->Entries[0].MMSIndicator->MessageSize = 0;
1017 Info->Entries[0].MMSIndicator->Title[0] = 0;
1018 Info->Entries[0].MMSIndicator->Sender[0] = 0;
1019 Info->Entries[0].MMSIndicator->Address[0] = 0;
1020
1021 /* First byte is the WSP transaction ID */
1022 /* Second byte is PUSH */
1023 if (Buffer[1] != 0x06) {
1024 dbgprintf(di, "Unsupported WSP PDU type: 0x%02x\n", Buffer[1]);
1025 return FALSE;
1026 }
1027 /*
1028 * WSP Push PDU follows:
1029 *
1030 * Buffer[2] is length of content type and headers
1031 * Buffer[3] is start of content type
1032 *
1033 * Process payload after headers per
1034 * Multimedia Messaging Service Encapsulation Protocol
1035 */
1036 for (i = 3 + Buffer[2]; i < Length; i++) {
1037 switch(Buffer[i]) {
1038 case 0x8c:
1039 /* X-Mms-Message-Type (Transaction type) */
1040 i++;
1041 /* We support only m-notification-ind (130) */
1042 if (Buffer[i] != 0x82) {
1043 dbgprintf(di, "Unsupported transaction type: 0x%02x\n", Buffer[i]);
1044 return FALSE;
1045 }
1046 break;
1047 case 0x98:
1048 /* X-Mms-Transaction-Id (Message ID) */
1049 dbgprintf(di, "Transaction ID: %s\n", Buffer + i + 1);
1050 while (Buffer[i] != 0 && i < Length) i++;
1051 break;
1052 case 0x8d:
1053 /* X-Mms-MMS-Version (MMS version) */
1054 i++;
1055 if (Buffer[i] < 0x90 || Buffer[i] > 0x92) {
1056 dbgprintf(di, "Unsupported MMS version: 0x%02x\n", Buffer[i]);
1057 return FALSE;
1058 }
1059 break;
1060 case 0x89:
1061 /* From (Sender) */
1062 i++;
1063 /* Length */
1064 if (Buffer[i] == 0) continue;
1065 /* Address-present-token */
1066 if (Buffer[i + 1] == 0x80) {
1067 if (Buffer[i + 2] < 32) {
1068 /* String with length + encoding, we just ignore it for now */
1069 strcpy(Info->Entries[0].MMSIndicator->Sender, Buffer + i + 4);
1070 } else {
1071 strcpy(Info->Entries[0].MMSIndicator->Sender, Buffer + i + 2);
1072 }
1073 }
1074 i += Buffer[i];
1075 break;
1076 case 0x96:
1077 /* Subject (Title) */
1078 if (Buffer[i + 1] == 0x0a && Buffer[i + 2] == 0xea) {
1079 /* UTF-8 */
1080 strcpy(Info->Entries[0].MMSIndicator->Title, Buffer + i + 3);
1081 i += strlen(Info->Entries[0].MMSIndicator->Title) + 3;
1082 } else {
1083 strcpy(Info->Entries[0].MMSIndicator->Title, Buffer + i + 1);
1084 i += strlen(Info->Entries[0].MMSIndicator->Title) + 1;
1085 }
1086 break;
1087 case 0x8a:
1088 /* X-Mms-Message-Class (Class) */
1089 i++;
1090 switch (Buffer[i]) {
1091 case 0x80:
1092 Info->Entries[0].MMSIndicator->Class = GSM_MMS_Personal;
1093 break;
1094 case 0x81:
1095 Info->Entries[0].MMSIndicator->Class = GSM_MMS_Advertisement;
1096 break;
1097 case 0x82:
1098 Info->Entries[0].MMSIndicator->Class = GSM_MMS_Info;
1099 break;
1100 case 0x83:
1101 Info->Entries[0].MMSIndicator->Class = GSM_MMS_Auto;
1102 break;
1103 default:
1104 dbgprintf(di, "Unsupported MMS class: 0x%02x\n", Buffer[i]);
1105 break;
1106 }
1107 break;
1108 case 0x8e:
1109 /* X-Mms-Message-Size (Message size) */
1110 i++;
1111 for (j = i + 1; j < i + 1 + Buffer[i]; j++) {
1112 Info->Entries[0].MMSIndicator->MessageSize = (Info->Entries[0].MMSIndicator->MessageSize << 8) + Buffer[j];
1113 }
1114 i += Buffer[i];
1115 break;
1116 case 0x83:
1117 /* X-Mms-Content-Location (URL) */
1118 strcpy(Info->Entries[0].MMSIndicator->Address, Buffer + i + 1);
1119 i += strlen(Info->Entries[0].MMSIndicator->Address) + 1;
1120 break;
1121
1122 /* Ignored variable length fields */
1123 case 0x87: /* X-Mms-Delivery-Time */
1124 case 0x88: /* X-Mms-Expiry */
1125 case 0x9d: /* X-Mms-Reply-Charging-Deadline */
1126 case 0xa0: /* X-Mms-Previously-Sent-By */
1127 case 0xa1: /* X-Mms-Previously-Sent-Date */
1128 case 0xa4: /* X-Mms-MM-Flags */
1129 case 0xaa: /* X-Mms-Mbox-Totals */
1130 case 0xac: /* X-Mms-Mbox-Quotas */
1131 case 0xb2: /* X-Mms-Element-Descriptor */
1132 i++;
1133 i += Buffer[i];
1134 break;
1135
1136 /* Ignored long integer types */
1137 case 0x85: /* Date */
1138 case 0x9f: /* X-Mms-Reply-Charging-Size */
1139 i++;
1140 i += Buffer[i];
1141 break;
1142
1143 /* Ignored integer types */
1144 case 0xad: /* X-Mms-Message-Count */
1145 case 0xaf: /* X-Mms-Start */
1146 case 0xb3: /* X-Mms-Limit */
1147 i++;
1148 break;
1149
1150 /* Ignored octet types */
1151 case 0x86: /* X-Mms-Delivery-Report */
1152 case 0x8f: /* X-Mms-Priority */
1153 case 0x90: /* X-Mms-Read-Report */
1154 case 0x91: /* X-Mms-Report-Allowed */
1155 case 0x92: /* X-Mms-Response-Status */
1156 case 0x94: /* X-Mms-Sender-Visibility */
1157 case 0x95: /* X-Mms-Status */
1158 case 0x99: /* X-Mms-Retrieve-Status */
1159 case 0x9b: /* X-Mms-Read-Status */
1160 case 0x9c: /* X-Mms-Reply-Charging */
1161 case 0xa2: /* X-Mms-Store */
1162 case 0xa3: /* X-Mms-MM-State */
1163 case 0xa5: /* X-Mms-Store-Status */
1164 case 0xa7: /* X-Mms-Stored */
1165 case 0xa8: /* X-Mms-Attributes */
1166 case 0xa9: /* X-Mms-Totals */
1167 case 0xab: /* X-Mms-Quotas */
1168 case 0xb1: /* X-Mms-Distribution-Indicator */
1169 case 0xb4: /* X-Mms-Recommended-Retrieval-Mode */
1170 case 0xba: /* X-Mms-Content-Class */
1171 case 0xbb: /* X-Mms-DRM-Content */
1172 case 0xbc: /* X-Mms-Adaptation-Allowed */
1173 case 0xbf: /* X-Mms-Cancel-Status */
1174 i++;
1175 break;
1176
1177 /* Ignored encoded string types */
1178 case 0x81: /* Bcc */
1179 case 0x82: /* Cc */
1180 case 0x84: /* Content-Type */
1181 case 0x97: /* To */
1182 case 0x93: /* X-Mms-Response-Text */
1183 case 0x9a: /* X-Mms-Retrieve-Text */
1184 case 0xa6: /* X-Mms-Store-Status-Text */
1185 case 0xb5: /* X-Mms-Recommended-Retrieval-Mode-Text */
1186 case 0xb6: /* X-Mms-Status-Text */
1187 while (Buffer[i] != 0 && i < Length) {
1188 i++;
1189 }
1190 break;
1191
1192 /* Ignored string types */
1193 case 0x8b: /* Message-ID */
1194 case 0x9e: /* X-Mms-Reply-Charging-ID */
1195 case 0xb7: /* X-Mms-Applic-ID */
1196 case 0xb8: /* X-Mms-Reply-Applic-ID */
1197 case 0xb9: /* X-Mms-Aux-Applic-Info */
1198 case 0xbd: /* X-Mms-Replace-ID */
1199 case 0xbe: /* X-Mms-Cancel-ID */
1200 i++;
1201 i += Buffer[i];
1202 break;
1203 default:
1204 dbgprintf(di, "Unknown MMS tag: 0x%02x\n", Buffer[i]);
1205 break;
1206 }
1207 }
1208
1209 return TRUE;
1210 }
1211
1212 /**
1213 * Decodes long Nokia profile SMS.
1214 */
GSM_DecodeNokiaProfile(GSM_Debug_Info * di,GSM_MultiPartSMSInfo * Info,GSM_MultiSMSMessage * SMS)1215 gboolean GSM_DecodeNokiaProfile(GSM_Debug_Info *di,
1216 GSM_MultiPartSMSInfo *Info,
1217 GSM_MultiSMSMessage *SMS)
1218 {
1219 int i, Length = 0;
1220 char Buffer[GSM_MAX_SMS_LENGTH*2*GSM_MAX_MULTI_SMS];
1221
1222 for (i=0;i<SMS->Number;i++) {
1223 if (SMS->SMS[i].UDH.Type != UDH_NokiaProfileLong ||
1224 SMS->SMS[i].UDH.Text[11] != i+1 ||
1225 SMS->SMS[i].UDH.Text[10] != SMS->Number) {
1226 return FALSE;
1227 }
1228 memcpy(Buffer+Length,SMS->SMS[i].Text,SMS->SMS[i].Length);
1229 Length = Length + SMS->SMS[i].Length;
1230 }
1231 Info->EntriesNum = 1;
1232 Info->Entries[0].ID = SMS_NokiaPictureImageLong;
1233 Info->Entries[0].Bitmap = (GSM_MultiBitmap *)malloc(sizeof(GSM_MultiBitmap));
1234 if (Info->Entries[0].Bitmap == NULL) return FALSE;
1235 Info->Entries[0].Bitmap->Number = 0;
1236 Info->Entries[0].Bitmap->Bitmap[0].Text[0] = 0;
1237 Info->Entries[0].Bitmap->Bitmap[0].Text[1] = 0;
1238 i=1;
1239 while (i < Length) {
1240 switch (Buffer[i]) {
1241 case SM30_ISOTEXT:
1242 smfprintf(di, "ISO 8859-2 text\n");
1243 break;
1244 case SM30_UNICODETEXT:
1245 smfprintf(di, "Unicode text\n");
1246 break;
1247 case SM30_OTA:
1248 smfprintf(di, "OTA bitmap as Picture Image\n");
1249 PHONE_DecodeBitmap(GSM_NokiaPictureImage, Buffer + i + 7, &Info->Entries[0].Bitmap->Bitmap[Info->Entries[0].Bitmap->Number]);
1250 Info->Entries[0].Bitmap->Number += 1;
1251 #ifdef DEBUG
1252 if (di->dl == DL_TEXTALL || di->dl == DL_TEXTALLDATE) {
1253 GSM_PrintBitmap(di->df, &Info->Entries[0].Bitmap->Bitmap[0]);
1254 }
1255 #endif
1256 break;
1257 case SM30_RINGTONE:
1258 smfprintf(di, "RTTL ringtone\n");
1259 Info->Unknown = TRUE;
1260 break;
1261 case SM30_PROFILENAME:
1262 smfprintf(di, "Profile Name\n");
1263 Info->Entries[0].ID = SMS_NokiaProfileLong;
1264 Info->Unknown = TRUE;
1265 break;
1266 case SM30_SCREENSAVER:
1267 smfprintf(di, "OTA bitmap as Screen Saver\n");
1268 PHONE_DecodeBitmap(GSM_NokiaPictureImage, Buffer + i + 7, &Info->Entries[0].Bitmap->Bitmap[Info->Entries[0].Bitmap->Number]);
1269 Info->Entries[0].Bitmap->Number += 1;
1270 #ifdef DEBUG
1271 if (di->dl == DL_TEXTALL || di->dl == DL_TEXTALLDATE) {
1272 GSM_PrintBitmap(di->df, &Info->Entries[0].Bitmap->Bitmap[0]);
1273 }
1274 #endif
1275 Info->Entries[0].ID = SMS_NokiaScreenSaverLong;
1276 break;
1277 }
1278 i = i + Buffer[i+1]*256 + Buffer[i+2] + 3;
1279 smfprintf(di, "Profile: pos=%i length=%i\n", i, Length);
1280 }
1281 i=1;
1282 while (i < Length) {
1283 switch (Buffer[i]) {
1284 case SM30_ISOTEXT:
1285 smfprintf(di, "ISO 8859-2 text\n");
1286 EncodeUnicode (Info->Entries[0].Bitmap->Bitmap[0].Text, Buffer+i+3, Buffer[i+2]);
1287 smfprintf(di, "ISO Text \"%s\"\n",DecodeUnicodeString(Info->Entries[0].Bitmap->Bitmap[0].Text));
1288 break;
1289 case SM30_UNICODETEXT:
1290 smfprintf(di, "Unicode text\n");
1291 memcpy(Info->Entries[0].Bitmap->Bitmap[0].Text,Buffer+i+3,Buffer[i+1]*256+Buffer[i+2]);
1292 Info->Entries[0].Bitmap->Bitmap[0].Text[Buffer[i+1]*256 + Buffer[i+2]] = 0;
1293 Info->Entries[0].Bitmap->Bitmap[0].Text[Buffer[i+1]*256 + Buffer[i+2]+ 1] = 0;
1294 smfprintf(di, "Unicode Text \"%s\"\n",DecodeUnicodeString(Info->Entries[0].Bitmap->Bitmap[0].Text));
1295 break;
1296 case SM30_OTA:
1297 smfprintf(di, "OTA bitmap as Picture Image\n");
1298 break;
1299 case SM30_RINGTONE:
1300 smfprintf(di, "RTTL ringtone\n");
1301 break;
1302 case SM30_PROFILENAME:
1303 smfprintf(di, "Profile Name\n");
1304 break;
1305 case SM30_SCREENSAVER:
1306 smfprintf(di, "OTA bitmap as Screen Saver\n");
1307 break;
1308 }
1309 i = i + Buffer[i+1]*256 + Buffer[i+2] + 3;
1310 smfprintf(di, "Profile: pos=%i length=%i\n", i, Length);
1311 }
1312 return TRUE;
1313 }
1314
1315 /**
1316 * Decodes long linked text SMS.
1317 */
GSM_DecodeLinkedText(GSM_Debug_Info * di,GSM_MultiPartSMSInfo * Info,GSM_MultiSMSMessage * SMS)1318 gboolean GSM_DecodeLinkedText(GSM_Debug_Info *di,
1319 GSM_MultiPartSMSInfo *Info,
1320 GSM_MultiSMSMessage *SMS)
1321 {
1322 int i, Length = 0;
1323
1324 Info->EntriesNum = 1;
1325 Info->Entries[0].ID = SMS_ConcatenatedTextLong;
1326 if (SMS->SMS[0].UDH.Type == UDH_ConcatenatedMessages16bit) {
1327 Info->Entries[0].ID = SMS_ConcatenatedTextLong16bit;
1328 }
1329
1330 for (i=0;i<SMS->Number;i++) {
1331 switch (SMS->SMS[i].Coding) {
1332 case SMS_Coding_8bit:
1333 Info->Entries[0].Buffer = (unsigned char *)realloc(Info->Entries[0].Buffer, Length + SMS->SMS[i].Length + 2);
1334 if (Info->Entries[0].Buffer == NULL) return FALSE;
1335
1336 memcpy(Info->Entries[0].Buffer + Length, SMS->SMS[i].Text, SMS->SMS[i].Length);
1337 Length=Length+SMS->SMS[i].Length;
1338 break;
1339 case SMS_Coding_Unicode_No_Compression:
1340 if (Info->Entries[0].ID == SMS_ConcatenatedTextLong) {
1341 Info->Entries[0].ID = SMS_ConcatenatedAutoTextLong;
1342 }
1343 if (Info->Entries[0].ID == SMS_ConcatenatedTextLong16bit) {
1344 Info->Entries[0].ID = SMS_ConcatenatedAutoTextLong16bit;
1345 }
1346 FALLTHROUGH
1347 case SMS_Coding_Default_No_Compression:
1348 Info->Entries[0].Buffer = (unsigned char *)realloc(Info->Entries[0].Buffer, Length + UnicodeLength(SMS->SMS[i].Text)*2 + 2);
1349 if (Info->Entries[0].Buffer == NULL) return FALSE;
1350
1351 memcpy(Info->Entries[0].Buffer+Length,SMS->SMS[i].Text,UnicodeLength(SMS->SMS[i].Text)*2);
1352 Length=Length+UnicodeLength(SMS->SMS[i].Text)*2;
1353 break;
1354 default:
1355 break;
1356 }
1357 }
1358 Info->Entries[0].Buffer[Length] = 0;
1359 Info->Entries[0].Buffer[Length+1] = 0;
1360 return TRUE;
1361 }
1362
1363 /* ----------------- Joining SMS from parts -------------------------------- */
1364
GSM_DecodeMultiPartSMS(GSM_Debug_Info * di,GSM_MultiPartSMSInfo * Info,GSM_MultiSMSMessage * SMS,gboolean ems)1365 gboolean GSM_DecodeMultiPartSMS(GSM_Debug_Info *di,
1366 GSM_MultiPartSMSInfo *Info,
1367 GSM_MultiSMSMessage *SMS,
1368 gboolean ems)
1369 {
1370 int i;
1371 unsigned int j;
1372 gboolean emsexist = FALSE, result;
1373 GSM_SiemensOTASMSInfo SiemensInfo;
1374
1375 GSM_ClearMultiPartSMSInfo(Info);
1376 if (ems) {
1377 emsexist = TRUE;
1378 for (i=0;i<SMS->Number;i++) {
1379 if (SMS->SMS[i].UDH.Type != UDH_ConcatenatedMessages &&
1380 SMS->SMS[i].UDH.Type != UDH_ConcatenatedMessages16bit &&
1381 SMS->SMS[i].UDH.Type != UDH_UserUDH) {
1382 emsexist = FALSE;
1383 break;
1384 }
1385 }
1386 }
1387
1388 /* EMS decoding */
1389 if (emsexist) {
1390 return GSM_DecodeEMSMultiPartSMS(di, Info,SMS);
1391 }
1392
1393 /* Siemens OTA */
1394 if (GSM_DecodeSiemensOTASMS(di, &SiemensInfo,&SMS->SMS[0])) {
1395 Info->Entries[0].File = (GSM_File *)malloc(sizeof(GSM_File));
1396 if (Info->Entries[0].File == NULL) return FALSE;
1397 Info->Entries[0].File->Buffer = NULL;
1398 Info->Entries[0].File->Used = 0;
1399 for (i=0;i<SMS->Number;i++) {
1400 if (GSM_DecodeSiemensOTASMS(di, &SiemensInfo,&SMS->SMS[i])) {
1401 j = SiemensInfo.AllDataLen - Info->Entries[0].File->Used;
1402 if (j>SiemensInfo.DataLen) j = SiemensInfo.DataLen;
1403 Info->Entries[0].File->Buffer = (unsigned char *)realloc(Info->Entries[0].File->Buffer,j+Info->Entries[0].File->Used);
1404 memcpy(Info->Entries[0].File->Buffer+Info->Entries[0].File->Used,SiemensInfo.Data,j);
1405 Info->Entries[0].File->Used += j;
1406 }
1407 }
1408 if (SiemensInfo.AllDataLen == Info->Entries[0].File->Used) {
1409 Info->Entries[0].ID = SMS_SiemensFile;
1410 Info->EntriesNum = 1;
1411 EncodeUnicode(Info->Entries[0].File->Name,SiemensInfo.DataName,strlen(SiemensInfo.DataName));
1412 return TRUE;
1413 }
1414 free(Info->Entries[0].File->Buffer);
1415 Info->Entries[0].File->Buffer=NULL;
1416 }
1417
1418 /* Smart Messaging decoding */
1419 if (SMS->SMS[0].UDH.Type == UDH_NokiaRingtone && SMS->Number == 1) {
1420 Info->Entries[0].Ringtone = (GSM_Ringtone *)malloc(sizeof(GSM_Ringtone));
1421 if (Info->Entries[0].Ringtone == NULL) return FALSE;
1422 if (GSM_DecodeNokiaRTTLRingtone(Info->Entries[0].Ringtone, SMS->SMS[0].Text, SMS->SMS[0].Length)==ERR_NONE) {
1423 Info->Entries[0].ID = SMS_NokiaRingtone;
1424 Info->EntriesNum = 1;
1425 return TRUE;
1426 }
1427 }
1428 if (SMS->SMS[0].UDH.Type == UDH_NokiaCallerLogo && SMS->Number == 1) {
1429 Info->Entries[0].Bitmap = (GSM_MultiBitmap *)malloc(sizeof(GSM_MultiBitmap));
1430 if (Info->Entries[0].Bitmap == NULL) return FALSE;
1431 Info->Entries[0].Bitmap->Number = 1;
1432 PHONE_DecodeBitmap(GSM_NokiaCallerLogo, SMS->SMS[0].Text+4, &Info->Entries[0].Bitmap->Bitmap[0]);
1433 #ifdef DEBUG
1434 if (di->dl == DL_TEXTALL || di->dl == DL_TEXTALLDATE)
1435 GSM_PrintBitmap(di->df,&Info->Entries[0].Bitmap->Bitmap[0]);
1436 #endif
1437 Info->Entries[0].ID = SMS_NokiaCallerLogo;
1438 Info->EntriesNum = 1;
1439 return TRUE;
1440 }
1441 if (SMS->SMS[0].UDH.Type == UDH_NokiaOperatorLogo && SMS->Number == 1) {
1442 Info->Entries[0].Bitmap = (GSM_MultiBitmap *)malloc(sizeof(GSM_MultiBitmap));
1443 if (Info->Entries[0].Bitmap == NULL) return FALSE;
1444 Info->Entries[0].Bitmap->Number = 1;
1445 PHONE_DecodeBitmap(GSM_NokiaOperatorLogo, SMS->SMS[0].Text+7, &Info->Entries[0].Bitmap->Bitmap[0]);
1446 NOKIA_DecodeNetworkCode(SMS->SMS[0].Text, Info->Entries[0].Bitmap->Bitmap[0].NetworkCode);
1447 #ifdef DEBUG
1448 if (di->dl == DL_TEXTALL || di->dl == DL_TEXTALLDATE)
1449 GSM_PrintBitmap(di->df,&Info->Entries[0].Bitmap->Bitmap[0]);
1450 #endif
1451 Info->Entries[0].ID = SMS_NokiaOperatorLogo;
1452 Info->EntriesNum = 1;
1453 return TRUE;
1454 }
1455 if (SMS->SMS[0].UDH.Type == UDH_NokiaProfileLong) {
1456 return GSM_DecodeNokiaProfile(di, Info, SMS);
1457 }
1458
1459 /* Linked sms */
1460 if (SMS->SMS[0].UDH.Type == UDH_ConcatenatedMessages ||
1461 SMS->SMS[0].UDH.Type == UDH_ConcatenatedMessages16bit) {
1462 return GSM_DecodeLinkedText(di, Info, SMS);
1463 }
1464 /* Nokia vCard/vCalendar */
1465 if (SMS->SMS[0].UDH.Type == UDH_NokiaCalendarLong ||
1466 SMS->SMS[0].UDH.Type == UDH_NokiaPhonebookLong) {
1467 result = GSM_DecodeLinkedText(di, Info, SMS);
1468 if (result) {
1469 if (SMS->SMS[0].UDH.Type == UDH_NokiaPhonebookLong) {
1470 Info->Entries[0].ID = SMS_NokiaVCARD10Long;
1471 } else {
1472 Info->Entries[0].ID = SMS_NokiaVCALENDAR10Long;
1473 }
1474 }
1475 return result;
1476 }
1477 /* MMS indication */
1478 if (SMS->SMS[0].UDH.Type == UDH_MMSIndicatorLong) {
1479 return GSM_DecodeMMSIndication(di, Info, SMS);
1480 }
1481
1482 return FALSE;
1483 }
1484
GSM_LinkSMS(GSM_Debug_Info * di,GSM_MultiSMSMessage ** InputMessages,GSM_MultiSMSMessage ** OutputMessages,gboolean ems)1485 GSM_Error GSM_LinkSMS(GSM_Debug_Info *di, GSM_MultiSMSMessage **InputMessages, GSM_MultiSMSMessage **OutputMessages, gboolean ems)
1486 {
1487 gboolean *InputMessagesSorted, copyit,OtherNumbers[GSM_SMS_OTHER_NUMBERS+1],wrong=FALSE;
1488 int i,OutputMessagesNum,z,w,m,p;
1489 int j;
1490 GSM_SiemensOTASMSInfo SiemensOTA,SiemensOTA2;
1491
1492 i = 0;
1493 while (InputMessages[i] != NULL) i++;
1494
1495 OutputMessagesNum = 0;
1496 OutputMessages[0] = NULL;
1497
1498 if (i == 0) {
1499 return ERR_NONE;
1500 }
1501
1502 InputMessagesSorted = calloc(i, sizeof(gboolean));
1503 if (InputMessagesSorted == NULL) return ERR_MOREMEMORY;
1504
1505 if (ems) {
1506 for (i = 0; InputMessages[i] != NULL; i++) {
1507 if (InputMessages[i]->SMS[0].UDH.Type == UDH_UserUDH) {
1508 w=1;
1509 while (w < InputMessages[i]->SMS[0].UDH.Length) {
1510 switch(InputMessages[i]->SMS[0].UDH.Text[w]) {
1511 case 0x00:
1512 smfprintf(di, "Adding ID to user UDH - linked SMS with 8 bit ID\n");
1513 InputMessages[i]->SMS[0].UDH.ID8bit = InputMessages[i]->SMS[0].UDH.Text[w+2];
1514 InputMessages[i]->SMS[0].UDH.ID16bit = -1;
1515 InputMessages[i]->SMS[0].UDH.AllParts = InputMessages[i]->SMS[0].UDH.Text[w+3];
1516 InputMessages[i]->SMS[0].UDH.PartNumber = InputMessages[i]->SMS[0].UDH.Text[w+4];
1517 break;
1518 case 0x08:
1519 smfprintf(di, "Adding ID to user UDH - linked SMS with 16 bit ID\n");
1520 InputMessages[i]->SMS[0].UDH.ID8bit = -1;
1521 InputMessages[i]->SMS[0].UDH.ID16bit = InputMessages[i]->SMS[0].UDH.Text[w+2]*256+InputMessages[i]->SMS[0].UDH.Text[w+3];
1522 InputMessages[i]->SMS[0].UDH.AllParts = InputMessages[i]->SMS[0].UDH.Text[w+4];
1523 InputMessages[i]->SMS[0].UDH.PartNumber = InputMessages[i]->SMS[0].UDH.Text[w+5];
1524 break;
1525 default:
1526 smfprintf(di, "Block %02x\n",InputMessages[i]->SMS[0].UDH.Text[w]);
1527 }
1528 smfprintf(di, "id8: %i, id16: %i, part: %i, parts count: %i\n",
1529 InputMessages[i]->SMS[0].UDH.ID8bit,
1530 InputMessages[i]->SMS[0].UDH.ID16bit,
1531 InputMessages[i]->SMS[0].UDH.PartNumber,
1532 InputMessages[i]->SMS[0].UDH.AllParts);
1533 w=w+InputMessages[i]->SMS[0].UDH.Text[w+1]+2;
1534 }
1535 }
1536 }
1537 }
1538
1539 i=0;
1540 while (InputMessages[i]!=NULL) {
1541 /* If this one SMS was sorted earlier, do not touch */
1542 if (InputMessagesSorted[i]) {
1543 i++;
1544 continue;
1545 }
1546 /* We have 1'st part of SIEMENS sms. It's single.
1547 * We will try to find other parts
1548 */
1549 if (GSM_DecodeSiemensOTASMS(di, &SiemensOTA,&InputMessages[i]->SMS[0]) &&
1550 SiemensOTA.PacketNum == 1) {
1551 OutputMessages[OutputMessagesNum] = (GSM_MultiSMSMessage *)malloc(sizeof(GSM_MultiSMSMessage));
1552 if (OutputMessages[OutputMessagesNum] == NULL) {
1553 free(InputMessagesSorted);
1554 InputMessagesSorted=NULL;
1555 return ERR_MOREMEMORY;
1556 }
1557 OutputMessages[OutputMessagesNum+1] = NULL;
1558
1559 memcpy(&OutputMessages[OutputMessagesNum]->SMS[0],&InputMessages[i]->SMS[0],sizeof(GSM_SMSMessage));
1560 OutputMessages[OutputMessagesNum]->Number = 1;
1561 InputMessagesSorted[i] = TRUE;
1562 j = 1;
1563 /* We're searching for other parts in sequence */
1564 while (j!=(int)SiemensOTA.PacketsNum) {
1565
1566 if(j >= GSM_MAX_MULTI_SMS) {
1567 smfprintf(di,
1568 "WARNING: Hard coded message parts limit of %d has been reached,"
1569 "skipping remaining parts.\n", GSM_MAX_MULTI_SMS);
1570 break;
1571 }
1572
1573 z=0;
1574 while(InputMessages[z]!=NULL) {
1575 /* This was sorted earlier or is not single */
1576 if (InputMessagesSorted[z] || InputMessages[z]->Number != 1) {
1577 z++;
1578 continue;
1579 }
1580 if (!GSM_DecodeSiemensOTASMS(di, &SiemensOTA2,&InputMessages[z]->SMS[0])) {
1581 z++;
1582 continue;
1583 }
1584 if (SiemensOTA2.SequenceID != SiemensOTA.SequenceID ||
1585 (int)SiemensOTA2.PacketNum != j+1 ||
1586 SiemensOTA2.PacketsNum != SiemensOTA.PacketsNum ||
1587 strcmp(SiemensOTA2.DataType,SiemensOTA.DataType) ||
1588 strcmp(SiemensOTA2.DataName,SiemensOTA.DataName)) {
1589 z++;
1590 continue;
1591 }
1592 /* For SMS_Deliver compare also SMSC and Sender numbers */
1593 if (InputMessages[z]->SMS[0].PDU == SMS_Deliver &&
1594 strcmp(DecodeUnicodeString(InputMessages[z]->SMS[0].SMSC.Number),DecodeUnicodeString(InputMessages[i]->SMS[0].SMSC.Number))) {
1595 z++;
1596 continue;
1597 }
1598 if (InputMessages[z]->SMS[0].PDU == SMS_Deliver &&
1599 InputMessages[z]->SMS[0].OtherNumbersNum!=InputMessages[i]->SMS[0].OtherNumbersNum) {
1600 z++;
1601 continue;
1602 }
1603 if (InputMessages[z]->SMS[0].PDU == SMS_Deliver) {
1604 for (m=0;m<GSM_SMS_OTHER_NUMBERS+1;m++) {
1605 OtherNumbers[m]=FALSE;
1606 }
1607 for (m=0;m<InputMessages[z]->SMS[0].OtherNumbersNum+1;m++) {
1608 wrong=TRUE;
1609 for (p=0;p<InputMessages[i]->SMS[0].OtherNumbersNum+1;p++) {
1610 if (OtherNumbers[p]) continue;
1611 if (m==0 && p==0 && !strcmp(DecodeUnicodeString(InputMessages[z]->SMS[0].Number),DecodeUnicodeString(InputMessages[i]->SMS[0].Number))) {
1612 OtherNumbers[0]=TRUE;
1613 wrong=FALSE;
1614 break;
1615 }
1616 if (m==0 && p!=0 && !strcmp(DecodeUnicodeString(InputMessages[z]->SMS[0].Number),DecodeUnicodeString(InputMessages[i]->SMS[0].OtherNumbers[p-1]))) {
1617 OtherNumbers[p]=TRUE;
1618 wrong=FALSE;
1619 break;
1620 }
1621 if (m!=0 && p==0 && !strcmp(DecodeUnicodeString(InputMessages[z]->SMS[0].OtherNumbers[m-1]),DecodeUnicodeString(InputMessages[i]->SMS[0].Number))) {
1622 OtherNumbers[0]=TRUE;
1623 wrong=FALSE;
1624 break;
1625 }
1626 if (m!=0 && p!=0 && !strcmp(DecodeUnicodeString(InputMessages[z]->SMS[0].OtherNumbers[m-1]),DecodeUnicodeString(InputMessages[i]->SMS[0].OtherNumbers[p-1]))) {
1627 OtherNumbers[p]=TRUE;
1628 wrong=FALSE;
1629 break;
1630 }
1631 }
1632 if (wrong) break;
1633 }
1634 if (wrong) {
1635 z++;
1636 continue;
1637 }
1638 }
1639 /* DCT4 Outbox: SMS Deliver. Empty number and SMSC. We compare dates */
1640 if (InputMessages[z]->SMS[0].PDU == SMS_Deliver &&
1641 UnicodeLength(InputMessages[z]->SMS[0].SMSC.Number)==0 &&
1642 UnicodeLength(InputMessages[z]->SMS[0].Number)==0 &&
1643 (InputMessages[z]->SMS[0].DateTime.Day != InputMessages[i]->SMS[0].DateTime.Day ||
1644 InputMessages[z]->SMS[0].DateTime.Month != InputMessages[i]->SMS[0].DateTime.Month ||
1645 InputMessages[z]->SMS[0].DateTime.Year != InputMessages[i]->SMS[0].DateTime.Year ||
1646 InputMessages[z]->SMS[0].DateTime.Hour != InputMessages[i]->SMS[0].DateTime.Hour ||
1647 InputMessages[z]->SMS[0].DateTime.Minute != InputMessages[i]->SMS[0].DateTime.Minute ||
1648 InputMessages[z]->SMS[0].DateTime.Second != InputMessages[i]->SMS[0].DateTime.Second)) {
1649 z++;
1650 continue;
1651 }
1652 smfprintf(di, "Found Siemens SMS %i\n",j);
1653 /* We found correct sms. Copy it */
1654 memcpy(&OutputMessages[OutputMessagesNum]->SMS[j],&InputMessages[z]->SMS[0],sizeof(GSM_SMSMessage));
1655 OutputMessages[OutputMessagesNum]->Number++;
1656 InputMessagesSorted[z]=TRUE;
1657 break;
1658 }
1659 /* Incomplete sequence */
1660 if (OutputMessages[OutputMessagesNum]->Number==j) {
1661 smfprintf(di, "Incomplete sequence\n");
1662 break;
1663 }
1664 j++;
1665 }
1666 OutputMessagesNum++;
1667 i = 0;
1668 continue;
1669 }
1670 /* We have some next Siemens sms from sequence */
1671 if (GSM_DecodeSiemensOTASMS(di, &SiemensOTA,&InputMessages[i]->SMS[0]) &&
1672 SiemensOTA.PacketNum > 1) {
1673 j = 0;
1674 while (InputMessages[j]!=NULL) {
1675 if (InputMessagesSorted[j]) {
1676 j++;
1677 continue;
1678 }
1679 /* We have some not unassigned first sms from sequence.
1680 * We can't touch other sms from sequences
1681 */
1682 if (GSM_DecodeSiemensOTASMS(di, &SiemensOTA,&InputMessages[j]->SMS[0]) &&
1683 SiemensOTA.PacketNum == 1) {
1684 break;
1685 }
1686 j++;
1687 }
1688 if (InputMessages[j]==NULL) {
1689 OutputMessages[OutputMessagesNum] = (GSM_MultiSMSMessage *)malloc(sizeof(GSM_MultiSMSMessage));
1690 if (OutputMessages[OutputMessagesNum] == NULL) {
1691 free(InputMessagesSorted);
1692 InputMessagesSorted=NULL;
1693 return ERR_MOREMEMORY;
1694 }
1695 OutputMessages[OutputMessagesNum+1] = NULL;
1696
1697 memcpy(OutputMessages[OutputMessagesNum],InputMessages[i],sizeof(GSM_MultiSMSMessage));
1698 InputMessagesSorted[i]=TRUE;
1699 OutputMessagesNum++;
1700 i = 0;
1701 continue;
1702 } else i++;
1703 }
1704 copyit = FALSE;
1705 /* If we have:
1706 * - linked sms returned by phone driver
1707 * - sms without linking
1708 * we copy it to OutputMessages
1709 */
1710 if (InputMessages[i]->Number != 1 ||
1711 InputMessages[i]->SMS[0].UDH.Type == UDH_NoUDH ||
1712 InputMessages[i]->SMS[0].UDH.PartNumber == -1) {
1713 copyit = TRUE;
1714 }
1715 /* If we have unknown UDH, we copy it to OutputMessages */
1716 if (InputMessages[i]->SMS[0].UDH.Type == UDH_UserUDH) {
1717 if (!ems) copyit = TRUE;
1718 if (ems && InputMessages[i]->SMS[0].UDH.PartNumber == -1) copyit = TRUE;
1719 }
1720 if (copyit) {
1721 OutputMessages[OutputMessagesNum] = (GSM_MultiSMSMessage *)malloc(sizeof(GSM_MultiSMSMessage));
1722 if (OutputMessages[OutputMessagesNum] == NULL) {
1723 free(InputMessagesSorted);
1724 InputMessagesSorted=NULL;
1725 return ERR_MOREMEMORY;
1726 }
1727 OutputMessages[OutputMessagesNum+1] = NULL;
1728
1729 memcpy(OutputMessages[OutputMessagesNum],InputMessages[i],sizeof(GSM_MultiSMSMessage));
1730 InputMessagesSorted[i]=TRUE;
1731 OutputMessagesNum++;
1732 i = 0;
1733 continue;
1734 }
1735 /* We have 1'st part of linked sms. It's single.
1736 * We will try to find other parts
1737 */
1738 if (InputMessages[i]->SMS[0].UDH.PartNumber == 1) {
1739 OutputMessages[OutputMessagesNum] = (GSM_MultiSMSMessage *)malloc(sizeof(GSM_MultiSMSMessage));
1740 if (OutputMessages[OutputMessagesNum] == NULL) {
1741 free(InputMessagesSorted);
1742 InputMessagesSorted=NULL;
1743 return ERR_MOREMEMORY;
1744 }
1745 OutputMessages[OutputMessagesNum+1] = NULL;
1746
1747 memcpy(&OutputMessages[OutputMessagesNum]->SMS[0],&InputMessages[i]->SMS[0],sizeof(GSM_SMSMessage));
1748 OutputMessages[OutputMessagesNum]->Number = 1;
1749 InputMessagesSorted[i] = TRUE;
1750 j = 1;
1751 /* We're searching for other parts in sequence */
1752 while (j != InputMessages[i]->SMS[0].UDH.AllParts) {
1753
1754 if(j >= GSM_MAX_MULTI_SMS) {
1755 smfprintf(di,
1756 "WARNING: Hard coded message parts limit of %d has been reached,"
1757 "skipping remaining parts.\n", GSM_MAX_MULTI_SMS);
1758 break;
1759 }
1760
1761 z=0;
1762 while(InputMessages[z]!=NULL) {
1763 /* This was sorted earlier or is not single */
1764 if (InputMessagesSorted[z] || InputMessages[z]->Number != 1) {
1765 z++;
1766 continue;
1767 }
1768 if (ems && InputMessages[i]->SMS[0].UDH.Type != UDH_ConcatenatedMessages &&
1769 InputMessages[i]->SMS[0].UDH.Type != UDH_ConcatenatedMessages16bit &&
1770 InputMessages[i]->SMS[0].UDH.Type != UDH_UserUDH &&
1771 InputMessages[z]->SMS[0].UDH.Type != UDH_ConcatenatedMessages &&
1772 InputMessages[z]->SMS[0].UDH.Type != UDH_ConcatenatedMessages16bit &&
1773 InputMessages[z]->SMS[0].UDH.Type != UDH_UserUDH) {
1774 if (InputMessages[z]->SMS[0].UDH.Type != InputMessages[i]->SMS[0].UDH.Type) {
1775 z++;
1776 continue;
1777 }
1778 }
1779 if (!ems && InputMessages[z]->SMS[0].UDH.Type != InputMessages[i]->SMS[0].UDH.Type) {
1780 z++;
1781 continue;
1782 }
1783 smfprintf(di, "compare %i %i %i %i %i",
1784 j+1,
1785 InputMessages[i]->SMS[0].UDH.ID8bit,
1786 InputMessages[i]->SMS[0].UDH.ID16bit,
1787 InputMessages[i]->SMS[0].UDH.PartNumber,
1788 InputMessages[i]->SMS[0].UDH.AllParts);
1789 smfprintf(di, " %i %i %i %i\n",
1790 InputMessages[z]->SMS[0].UDH.ID8bit,
1791 InputMessages[z]->SMS[0].UDH.ID16bit,
1792 InputMessages[z]->SMS[0].UDH.PartNumber,
1793 InputMessages[z]->SMS[0].UDH.AllParts);
1794 if (InputMessages[z]->SMS[0].UDH.ID8bit != InputMessages[i]->SMS[0].UDH.ID8bit ||
1795 InputMessages[z]->SMS[0].UDH.ID16bit != InputMessages[i]->SMS[0].UDH.ID16bit ||
1796 InputMessages[z]->SMS[0].UDH.AllParts != InputMessages[i]->SMS[0].UDH.AllParts ||
1797 (InputMessages[z]->SMS[0].UDH.PartNumber) != j + 1) {
1798 z++;
1799 continue;
1800 }
1801 /* For SMS_Deliver compare also SMSC and Sender numbers */
1802 if (InputMessages[z]->SMS[0].PDU == SMS_Deliver &&
1803 strcmp(DecodeUnicodeString(InputMessages[z]->SMS[0].SMSC.Number),DecodeUnicodeString(InputMessages[i]->SMS[0].SMSC.Number))) {
1804 z++;
1805 continue;
1806 }
1807 if (InputMessages[z]->SMS[0].PDU == SMS_Deliver &&
1808 InputMessages[z]->SMS[0].OtherNumbersNum!=InputMessages[i]->SMS[0].OtherNumbersNum) {
1809 z++;
1810 continue;
1811 }
1812 if (InputMessages[z]->SMS[0].PDU == SMS_Deliver) {
1813 for (m=0;m<GSM_SMS_OTHER_NUMBERS+1;m++) {
1814 OtherNumbers[m]=FALSE;
1815 }
1816 for (m=0;m<InputMessages[z]->SMS[0].OtherNumbersNum+1;m++) {
1817 wrong=TRUE;
1818 for (p=0;p<InputMessages[i]->SMS[0].OtherNumbersNum+1;p++) {
1819 if (OtherNumbers[p]) continue;
1820 if (m==0 && p==0 && !strcmp(DecodeUnicodeString(InputMessages[z]->SMS[0].Number),DecodeUnicodeString(InputMessages[i]->SMS[0].Number))) {
1821 OtherNumbers[0]=TRUE;
1822 wrong=FALSE;
1823 break;
1824 }
1825 if (m==0 && p!=0 && !strcmp(DecodeUnicodeString(InputMessages[z]->SMS[0].Number),DecodeUnicodeString(InputMessages[i]->SMS[0].OtherNumbers[p-1]))) {
1826 OtherNumbers[p]=TRUE;
1827 wrong=FALSE;
1828 break;
1829 }
1830 if (m!=0 && p==0 && !strcmp(DecodeUnicodeString(InputMessages[z]->SMS[0].OtherNumbers[m-1]),DecodeUnicodeString(InputMessages[i]->SMS[0].Number))) {
1831 OtherNumbers[0]=TRUE;
1832 wrong=FALSE;
1833 break;
1834 }
1835 if (m!=0 && p!=0 && !strcmp(DecodeUnicodeString(InputMessages[z]->SMS[0].OtherNumbers[m-1]),DecodeUnicodeString(InputMessages[i]->SMS[0].OtherNumbers[p-1]))) {
1836 OtherNumbers[p]=TRUE;
1837 wrong=FALSE;
1838 break;
1839 }
1840 }
1841 if (wrong) break;
1842 }
1843 if (wrong) {
1844 z++;
1845 continue;
1846 }
1847 }
1848 /* DCT4 Outbox: SMS Deliver. Empty number and SMSC. We compare dates */
1849 if (InputMessages[z]->SMS[0].PDU == SMS_Deliver &&
1850 UnicodeLength(InputMessages[z]->SMS[0].SMSC.Number)==0 &&
1851 UnicodeLength(InputMessages[z]->SMS[0].Number)==0 &&
1852 (InputMessages[z]->SMS[0].DateTime.Day != InputMessages[i]->SMS[0].DateTime.Day ||
1853 InputMessages[z]->SMS[0].DateTime.Month != InputMessages[i]->SMS[0].DateTime.Month ||
1854 InputMessages[z]->SMS[0].DateTime.Year != InputMessages[i]->SMS[0].DateTime.Year ||
1855 InputMessages[z]->SMS[0].DateTime.Hour != InputMessages[i]->SMS[0].DateTime.Hour ||
1856 InputMessages[z]->SMS[0].DateTime.Minute != InputMessages[i]->SMS[0].DateTime.Minute ||
1857 InputMessages[z]->SMS[0].DateTime.Second != InputMessages[i]->SMS[0].DateTime.Second)) {
1858 z++;
1859 continue;
1860 }
1861 /* We found correct sms. Copy it */
1862 memcpy(&OutputMessages[OutputMessagesNum]->SMS[j],&InputMessages[z]->SMS[0],sizeof(GSM_SMSMessage));
1863 OutputMessages[OutputMessagesNum]->Number++;
1864 InputMessagesSorted[z]=TRUE;
1865 break;
1866 }
1867 /* Incomplete sequence */
1868 if (OutputMessages[OutputMessagesNum]->Number==j) {
1869 smfprintf(di, "Incomplete sequence\n");
1870 break;
1871 }
1872 j++;
1873 }
1874 OutputMessagesNum++;
1875 i = 0;
1876 continue;
1877 }
1878 /* We have some next linked sms from sequence */
1879 if (InputMessages[i]->SMS[0].UDH.PartNumber > 1) {
1880 j = 0;
1881 while (InputMessages[j]!=NULL) {
1882 if (InputMessagesSorted[j]) {
1883 j++;
1884 continue;
1885 }
1886 /* We have some not unassigned first sms from sequence.
1887 * We can't touch other sms from sequences
1888 */
1889 if (InputMessages[j]->SMS[0].UDH.PartNumber == 1) break;
1890 j++;
1891 }
1892 if (InputMessages[j]==NULL) {
1893 OutputMessages[OutputMessagesNum] = (GSM_MultiSMSMessage *)malloc(sizeof(GSM_MultiSMSMessage));
1894 if (OutputMessages[OutputMessagesNum] == NULL) {
1895 free(InputMessagesSorted);
1896 InputMessagesSorted=NULL;
1897 return ERR_MOREMEMORY;
1898 }
1899 OutputMessages[OutputMessagesNum+1] = NULL;
1900
1901 memcpy(OutputMessages[OutputMessagesNum],InputMessages[i],sizeof(GSM_MultiSMSMessage));
1902 InputMessagesSorted[i]=TRUE;
1903 OutputMessagesNum++;
1904 i = 0;
1905 continue;
1906 } else i++;
1907 }
1908 }
1909 free(InputMessagesSorted);
1910 InputMessagesSorted=NULL;
1911 return ERR_NONE;
1912 }
1913
1914 /* How should editor hadle tabs in this file? Add editor commands here.
1915 * vim: noexpandtab sw=8 ts=8 sts=8:
1916 */
1917