7 A Linux/Unix toolset and driver for Nokia mobile phones.
9 Copyright (C) 2001 Pawe³ Kot <pkot@linuxnews.pl>
11 Released under the terms of the GNU GPL, see file COPYING for more details.
13 Library for parsing and creating Short Messages (SMS).
16 Revision 1.1.1.1.12.1 2001/11/27 23:34:48 short
17 Update: orig2001_11_27_05_17 -> orig2001_11_27_22_58
19 Revision 1.1.1.1.8.1 2001/11/27 23:06:09 short
20 Update: orig2001_11_27_05_17 -> orig2001_11_27_22_58
22 Revision 1.1.1.1.2.1 2001/11/27 22:48:37 short
23 Update: orig2001_11_27_05_17 -> orig2001_11_27_22_58
25 Revision 1.1.1.2 2001/11/27 22:01:13 short
26 :pserver:cvs@pserver.samba.org:/cvsroot - gnokii - Tue Nov 27 22:58 CET 2001
28 Revision 1.14 2001/11/27 12:19:00 pkot
29 Cleanup, indentation, ANSI complaint preprocesor symbols (Jan Kratochvil, me)
31 Revision 1.13 2001/11/23 22:07:44 machek
32 Fix SMS receiving to work, again. Unfortunately, it is not possible to
33 reuse much of gsm-sms.c...
35 Revision 1.12 2001/11/22 17:56:53 pkot
36 smslib update. sms sending
38 Revision 1.11 2001/11/20 16:22:22 pkot
39 First attempt to read Picture Messages. They should appear when you enable DEBUG. Nokia seems to break own standards. :/ (Markus Plail)
41 Revision 1.10 2001/11/19 13:09:40 pkot
42 Begin work on sms sending
44 Revision 1.9 2001/11/18 00:54:32 pkot
45 Bugfixes. I18n of the user responses. UDH support in libsms. Business Card UDH Type
47 Revision 1.8 2001/11/17 20:19:29 pkot
48 smslib cleanups, fixes and debugging
50 Revision 1.7 2001/11/15 12:15:04 pkot
51 smslib updates. begin work on sms in 6100 series
53 Revision 1.6 2001/11/14 18:21:19 pkot
54 Fixing some problems with UDH and Unicode, but still doesn't work yet :-(
56 Revision 1.5 2001/11/14 14:26:18 pkot
57 Changed offset of DCS field to the right value in 6210/7110
59 Revision 1.4 2001/11/14 11:26:18 pkot
60 Getting SMS in 6210/7110 does finally work in some cases :)
62 Revision 1.3 2001/11/13 16:12:20 pkot
63 Preparing libsms to get to work. 6210/7110 SMS and SMS Folder updates
65 Revision 1.2 2001/11/09 14:25:04 pkot
68 Revision 1.1 2001/11/08 16:23:21 pkot
69 New version of libsms. Not functional yet, but it reasonably stable API.
71 Revision 1.1 2001/07/09 23:06:26 pkot
72 Moved sms.* files from my hard disk to CVS
79 #include "gsm-common.h"
80 #include "gsm-encoding.h"
81 #include "gsm-bitmaps.h"
88 /* Number of header specific octets in SMS header (before data) */
89 static unsigned short DataOffset[] = {
91 3, /* SMS Deliver Report */
93 3, /* SMS Submit Report */
95 3 /* SMS Status Report */
98 /* User data headers */
99 static struct udh_data headers[] = {
101 { 0x05, "\x00\x03\x01\x00\x00" }, /* Concatenated messages */
102 { 0x06, "\x05\x04\x15\x82\x00\x00" }, /* Operator logos */
103 { 0x06, "\x05\x04\x15\x83\x00\x00" }, /* Caller logos */
104 { 0x06, "\x05\x04\x15\x81\x00\x00" }, /* Ringtones */
105 { 0x04, "\x03\x01\x00\x00" }, /* Voice Messages */
106 { 0x04, "\x03\x01\x01\x00" }, /* Fax Messages */
107 { 0x04, "\x03\x01\x02\x00" }, /* Email Messages */
108 { 0x06, "\x05\x04\x23\xf4\x00\x00" }, /* Business Card */
117 /* This function implements packing of numbers (SMS Center number and
118 destination number) for SMS sending function. */
119 static int SemiOctetPack(char *Number, unsigned char *Output, SMS_NumberType type)
121 unsigned char *IN = Number; /* Pointer to the input number */
122 unsigned char *OUT = Output; /* Pointer to the output */
123 int count = 0; /* This variable is used to notify us about count of already
126 /* The first byte in the Semi-octet representation of the address field is
127 the Type-of-Address. This field is described in the official GSM
128 specification 03.40 version 6.1.0, section 9.1.2.5, page 33. We support
129 only international and unknown number. */
132 if (type == SMS_International) IN++; /* Skip '+' */
133 if ((type == SMS_Unknown) && (*IN == '+')) IN++; /* Optional '+' in Unknown number type */
135 /* The next field is the number. It is in semi-octet representation - see
136 GSM scpecification 03.40 version 6.1.0, section 9.1.2.3, page 31. */
139 *OUT = *OUT | ((*IN - '0') << 4);
147 /* We should also fill in the most significant bits of the last byte with
148 0x0f (1111 binary) if the number is represented with odd number of
155 return (2 * (OUT - Output - 1) - (count % 2));
158 char *GetBCDNumber(u8 *Number)
160 static char Buffer[20] = "";
161 int length = Number[0]; /* This is the length of BCD coded number */
164 memset(Buffer, 0, 20);
166 case SMS_Alphanumeric:
167 Unpack7BitCharacters(0, length, length, Number+2, Buffer);
170 case SMS_International:
171 sprintf(Buffer, "+");
176 case SMS_Abbreviated:
178 for (count = 0; count < length - 1; count++) {
179 Digit = Number[count+2] & 0x0f;
180 if (Digit < 10) sprintf(Buffer, "%s%d", Buffer, Digit);
181 Digit = Number[count+2] >> 4;
182 if (Digit < 10) sprintf(Buffer, "%s%d", Buffer, Digit);
189 static char *PrintDateTime(u8 *Number)
191 static char Buffer[23] = "";
193 memset(Buffer, 0, 23);
194 if (Number[0] < 70) sprintf(Buffer, "20");
195 else sprintf(Buffer, "19");
196 sprintf(Buffer, "%s%d%d-", Buffer, Number[0] & 0x0f, Number[0] >> 4);
197 sprintf(Buffer, "%s%d%d-", Buffer, Number[1] & 0x0f, Number[1] >> 4);
198 sprintf(Buffer, "%s%d%d ", Buffer, Number[2] & 0x0f, Number[2] >> 4);
199 sprintf(Buffer, "%s%d%d:", Buffer, Number[3] & 0x0f, Number[3] >> 4);
200 sprintf(Buffer, "%s%d%d:", Buffer, Number[4] & 0x0f, Number[4] >> 4);
201 sprintf(Buffer, "%s%d%d", Buffer, Number[5] & 0x0f, Number[5] >> 4);
202 if (Number[6] & 0x08)
203 sprintf(Buffer, "%s-", Buffer);
205 sprintf(Buffer, "%s+", Buffer);
206 sprintf(Buffer, "%s%02d00", Buffer, (10 * (Number[6] & 0x07) + (Number[6] >> 4)) / 4);
211 SMS_DateTime *UnpackDateTime(u8 *Number, SMS_DateTime *dt)
213 dt->Year = 10 * (Number[0] & 0x0f) + (Number[0] >> 4);
214 if (dt->Year < 70) dt->Year += 2000;
215 else dt->Year += 1900;
216 dt->Month = 10 * (Number[1] & 0x0f) + (Number[1] >> 4);
217 dt->Day = 10 * (Number[2] & 0x0f) + (Number[2] >> 4);
218 dt->Hour = 10 * (Number[3] & 0x0f) + (Number[3] >> 4);
219 dt->Minute = 10 * (Number[4] & 0x0f) + (Number[4] >> 4);
220 dt->Second = 10 * (Number[5] & 0x0f) + (Number[5] >> 4);
221 dt->Timezone = (10 * (Number[6] & 0x07) + (Number[6] >> 4)) / 4;
222 if (Number[6] & 0x08) dt->Timezone = -dt->Timezone;
231 static GSM_Error EncodeData(GSM_SMSMessage *SMS, char *dcs, char *message)
234 unsigned short length = strlen(SMS->MessageText);
236 switch (SMS->DCS.Type) {
237 case SMS_GeneralDataCoding:
238 switch (SMS->DCS.u.General.Class) {
239 case 1: dcs[0] |= 0xf0; break;
240 case 2: dcs[0] |= 0xf1; break;
241 case 3: dcs[0] |= 0xf2; break;
242 case 4: dcs[0] |= 0xf3; break;
245 if (SMS->DCS.u.General.Compressed) {
246 /* Compression not supported yet */
247 /* dcs[0] |= 0x20; */
249 al = SMS->DCS.u.General.Alphabet;
251 case SMS_MessageWaiting:
252 al = SMS->DCS.u.MessageWaiting.Alphabet;
253 if (SMS->DCS.u.MessageWaiting.Discard) dcs[0] |= 0xc0;
254 else if (SMS->DCS.u.MessageWaiting.Alphabet == SMS_UCS2) dcs[0] |= 0xe0;
257 if (SMS->DCS.u.MessageWaiting.Active) dcs[0] |= 0x08;
258 dcs[0] |= (SMS->DCS.u.MessageWaiting.Type & 0x03);
262 return GE_SMSWRONGFORMAT;
265 case SMS_DefaultAlphabet:
266 Pack7BitCharacters((7 - (SMS->UDH_Length % 7)) % 7, SMS->MessageText, message);
267 SMS->Length = 8 * SMS->UDH_Length + (7 - (SMS->UDH_Length % 7)) % 7 + length;
271 memcpy(message, SMS->MessageText + 1, SMS->MessageText[0]);
272 SMS->Length = SMS->UDH_Length + SMS->MessageText[0];
276 EncodeUnicode(message, SMS->MessageText, length);
277 SMS->Length = length;
280 return GE_SMSWRONGFORMAT;
285 /* This function encodes the UserDataHeader as described in:
286 - GSM 03.40 version 6.1.0 Release 1997, section 9.2.3.24
287 - Smart Messaging Specification, Revision 1.0.0, September 15, 1997
289 static GSM_Error EncodeUDH(SMS_UDHInfo UDHi, char *UDH)
297 case SMS_VoiceMessage:
299 case SMS_EmailMessage:
300 UDH[pos+4] = UDHi.u.SpecialSMSMessageIndication.MessageCount;
301 if (UDHi.u.SpecialSMSMessageIndication.Store) UDH[pos+3] |= 0x80;
302 case SMS_ConcatenatedMessages:
304 case SMS_CallerIDLogo:
306 UDH[0] += headers[UDHi.Type].length;
307 memcpy(UDH+pos+1, headers[UDHi.Type].header, headers[UDHi.Type].length);
310 dprintf("Not supported User Data Header type\n");
316 static GSM_Error EncodeSMSSubmitHeader(GSM_SMSMessage *SMS, char *frame)
318 GSM_Error error = GE_NONE;
320 /* Standard Header: */
321 memcpy(frame, "\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa9\x00\x00\x00\x00\x00\x00", 24);
324 if (SMS->ReplyViaSameSMSC) frame[0] |= 0x80;
326 /* User Data Header Indicator */
327 if (SMS->UDH_No) frame[0] |= 0x40;
329 /* Status (Delivery) Report Request */
330 if (SMS->ReportStatus) frame[0] |= 0x20;
332 /* Validity Period Format: mask - 0x00, 0x10, 0x08, 0x18 */
333 frame[0] |= ((SMS->Validity.VPF & 0x03) << 3);
335 /* Reject Duplicates */
336 if (SMS->RejectDuplicates) frame[0] |= 0x04;
338 /* Message Type is already set */
340 /* Message Reference */
341 /* Can we set this? */
343 /* Protocol Identifier */
344 /* FIXME: allow to change this in better way.
345 currently only 0x5f == `Return Call Message' is used */
346 if (SMS->PID) frame[3] = SMS->PID;
348 /* Data Coding Scheme */
349 switch (SMS->DCS.Type) {
350 case SMS_GeneralDataCoding:
351 if (SMS->DCS.u.General.Compressed) frame[4] |= 0x20;
352 if (SMS->DCS.u.General.Class) frame[4] |= (0x10 | (SMS->DCS.u.General.Class - 1));
353 frame[4] |= ((SMS->DCS.u.General.Alphabet & 0x03) << 2);
355 case SMS_MessageWaiting:
356 if (SMS->DCS.u.MessageWaiting.Discard) frame[4] |= 0xc0;
357 else if (SMS->DCS.u.MessageWaiting.Alphabet == SMS_UCS2) frame[4] |= 0xe0;
358 else frame[4] |= 0xd0;
359 if (SMS->DCS.u.MessageWaiting.Active) frame[4] |= 0x80;
360 frame[4] |= (SMS->DCS.u.MessageWaiting.Type & 0x03);
363 dprintf("Wrong Data Coding Scheme (DCS) format\n");
364 return GE_SMSWRONGFORMAT;
367 /* Destination Address */
368 frame[5] = SemiOctetPack(SMS->RemoteNumber.number, frame + 6, SMS->RemoteNumber.type);
370 /* Validity Period */
371 switch (SMS->Validity.VPF) {
372 case SMS_EnhancedFormat:
374 case SMS_RelativeFormat:
376 case SMS_AbsoluteFormat:
385 static GSM_Error EncodeSMSDeliverHeader()
390 static GSM_Error EncodeSMSHeader(GSM_SMSMessage *SMS, char *frame)
391 /* We can create either SMS DELIVER (for saving in Inbox) or SMS SUBMIT
392 (for sending or saving in Outbox) message */
395 frame[12] |= (SMS->Type >> 1);
397 case SMS_Submit: /* we send SMS or save it in Outbox */
398 return EncodeSMSSubmitHeader(SMS, frame);
399 case SMS_Deliver: /* we save SMS in Inbox */
400 return EncodeSMSDeliverHeader(SMS, frame);
401 default: /* we don't create other formats of SMS */
402 return GE_SMSWRONGFORMAT;
406 /* This function encodes SMS as described in:
407 - GSM 03.40 version 6.1.0 Release 1997, section 9
409 int EncodePDUSMS(GSM_SMSMessage *SMS, char *message)
411 GSM_Error error = GE_NONE;
414 dprintf("Sending SMS to %s via message center %s\n", SMS->RemoteNumber.number, SMS->MessageCenter.Number);
417 dprintf("%d %s\n", SMS->MessageCenter.Type, SMS->MessageCenter.Number);
418 message[0] = SemiOctetPack(SMS->MessageCenter.Number, message + 1, SMS->MessageCenter.Type);
419 if (message[0] % 2) message[0]++;
420 message[0] = message[0] / 2 + 1;
423 error = EncodeSMSHeader(SMS, message + 12);
424 if (error != GE_NONE) return error;
426 /* User Data Header - if present */
427 // for (i = 0; i < SMS->UDH_No; i++) {
428 // error = EncodeUDH(SMS->UDH[i], message + 24);
429 // if (error != GE_NONE) return error;
434 EncodeData(SMS, message + 14, message + 36 + SMS->UDH_Length);
435 message[16] = SMS->Length;
436 return SMS->Length + 35;
439 /* This function does simple SMS encoding - no PDU coding */
440 GSM_Error EncodeTextSMS()
449 static GSM_Error SMSStatus(unsigned char status, GSM_SMSMessage *SMS)
452 strcpy(SMS->MessageText, _("Delivered"));
455 dprintf("SM received by the SME");
458 dprintf("SM forwarded by the SC to the SME but the SC is unable to confirm delivery");
461 dprintf("SM replaced by the SC");
464 SMS->Length = strlen(_("Delivered"));
465 } else if (status & 0x40) {
467 strcpy(SMS->MessageText, _("Failed"));
469 /* more detailed reason only for debug */
472 dprintf("Temporary error, SC is not making any more transfer attempts\n");
476 dprintf("Congestion");
482 dprintf("No response from SME");
485 dprintf("Service rejected");
488 dprintf("Quality of service not aviable");
491 dprintf("Error in SME");
494 dprintf("Reserved/Specific to SC: %x", status);
498 dprintf("Permanent error, SC is not making any more transfer attempts\n");
501 dprintf("Remote procedure error");
504 dprintf("Incompatibile destination");
507 dprintf("Connection rejected by SME");
510 dprintf("Not obtainable");
513 dprintf("Quality of service not aviable");
516 dprintf("No internetworking available");
519 dprintf("SM Validity Period Expired");
522 dprintf("SM deleted by originating SME");
525 dprintf("SM Deleted by SC Administration");
528 dprintf("SM does not exist");
531 dprintf("Reserved/Specific to SC: %x", status);
535 SMS->Length = strlen(_("Failed"));
536 } else if (status & 0x20) {
537 strcpy(SMS->MessageText, _("Pending"));
539 /* more detailed reason only for debug */
540 dprintf("Temporary error, SC still trying to transfer SM\n");
543 dprintf("Congestion");
549 dprintf("No response from SME");
552 dprintf("Service rejected");
555 dprintf("Quality of service not aviable");
558 dprintf("Error in SME");
561 dprintf("Reserved/Specific to SC: %x", status);
564 SMS->Length = strlen(_("Pending"));
566 strcpy(SMS->MessageText, _("Unknown"));
568 /* more detailed reason only for debug */
569 dprintf("Reserved/Specific to SC: %x", status);
570 SMS->Length = strlen(_("Unknown"));
576 static GSM_Error DecodeData(char *message, char *output, int length, int size, int udhlen, SMS_DataCodingScheme dcs)
579 if ((dcs.Type & 0x08) == 0x08) {
580 dprintf("Unicode message\n");
581 length = (length - udhlen)/2;
582 DecodeUnicode(output, message, length);
585 if ((dcs.Type & 0xf4) == 0xf4) {
586 dprintf("8bit message\n");
587 memcpy(output, message, length);
590 dprintf("Default Alphabet\n");
591 length = length - (udhlen * 8 + ((7-(udhlen%7))%7)) / 7;
592 Unpack7BitCharacters((7-udhlen)%7, size, length, message, output);
593 DecodeAscii(output, output, length);
596 dprintf("%s\n", output);
600 /* This function decodes UDH as described in:
601 - GSM 03.40 version 6.1.0 Release 1997, section 9.2.3.24
602 - Smart Messaging Specification, Revision 1.0.0, September 15, 1997
604 static GSM_Error DecodeUDH(char *message, GSM_SMSMessage *SMS)
606 unsigned char length, pos, nr;
608 SMS->UDH_Length = length = message[0] + 1;
612 unsigned char udh_length;
614 udh_length = message[pos+1];
615 switch (message[pos]) {
616 case 0x00: // Concatenated short messages
617 dprintf("Concatenated messages\n");
618 SMS->UDH[nr].Type = SMS_ConcatenatedMessages;
619 SMS->UDH[nr].u.ConcatenatedShortMessage.ReferenceNumber = message[pos + 2];
620 SMS->UDH[nr].u.ConcatenatedShortMessage.MaximumNumber = message[pos + 3];
621 SMS->UDH[nr].u.ConcatenatedShortMessage.CurrentNumber = message[pos + 4];
623 case 0x01: // Special SMS Message Indication
624 switch (message[pos + 2] & 0x03) {
626 dprintf("Voice Message\n");
627 SMS->UDH[nr].Type = SMS_VoiceMessage;
630 dprintf("Fax Message\n");
631 SMS->UDH[nr].Type = SMS_FaxMessage;
634 dprintf("Email Message\n");
635 SMS->UDH[nr].Type = SMS_EmailMessage;
638 dprintf("Unknown\n");
639 SMS->UDH[nr].Type = SMS_UnknownUDH;
642 SMS->UDH[nr].u.SpecialSMSMessageIndication.Store = (message[pos + 2] & 0x80) >> 7;
643 SMS->UDH[nr].u.SpecialSMSMessageIndication.MessageCount = message[pos + 3];
645 case 0x04: // Application port addression scheme, 8 bit address
647 case 0x05: // Application port addression scheme, 16 bit address
648 switch (((0x00ff & message[pos + 2]) << 8) | (0x00ff & message[pos + 3])) {
650 dprintf("Ringtone\n");
651 SMS->UDH[nr].Type = SMS_Ringtone;
654 dprintf("Operator Logo\n");
655 SMS->UDH[nr].Type = SMS_OpLogo;
658 dprintf("Caller Icon\n");
659 SMS->UDH[nr].Type = SMS_CallerIDLogo;
662 dprintf("Business Card\n");
663 SMS->UDH[nr].Type = SMS_BusinessCard;
666 dprintf("Unknown\n");
667 SMS->UDH[nr].Type = SMS_UnknownUDH;
671 case 0x06: // SMSC Control Parameters
673 case 0x07: // UDH Source Indicator
678 length -= (udh_length + 2);
679 pos += (udh_length + 2);
687 static GSM_Error DecodeSMSHeader(unsigned char *message, GSM_SMSMessage *SMS)
689 /* Short Message Type */
690 switch (SMS->Type = message[2]) {
692 dprintf("Mobile Terminated message:\n");
694 case SMS_Delivery_Report:
695 dprintf("Delivery Report:\n");
696 UnpackDateTime(message + 34 + DataOffset[SMS->Type], &(SMS->SMSCTime));
697 dprintf("\tDelivery date: %s\n", PrintDateTime(message + 34 + DataOffset[SMS->Type]));
700 dprintf("Mobile Originated message:\n");
703 dprintf("Not supported message type:\n");
707 /* Short Message location in memory */
708 SMS->Number = message[1];
709 dprintf("\tLocation: %d\n", SMS->Number);
711 /* Short Message Center */
712 strcpy(SMS->MessageCenter.Number, GetBCDNumber(message + 3));
713 dprintf("\tSMS center number: %s\n", SMS->MessageCenter.Number);
714 SMS->ReplyViaSameSMSC = false;
715 if (SMS->RemoteNumber.number[0] == 0 && (message[6] & 0x80)) {
716 SMS->ReplyViaSameSMSC = true;
720 message[15+DataOffset[SMS->Type]] = ((message[15+DataOffset[SMS->Type]])+1)/2+1;
721 dprintf("\tRemote number (recipient or sender): %s\n", GetBCDNumber(message + 15 + DataOffset[SMS->Type]));
722 strcpy(SMS->RemoteNumber.number, GetBCDNumber(message + 15 + DataOffset[SMS->Type]));
724 UnpackDateTime(message + 27 + DataOffset[SMS->Type], &(SMS->Time));
725 dprintf("\tDate: %s\n", PrintDateTime(message + 27 + DataOffset[SMS->Type]));
728 SMS->Length = message[14+DataOffset[SMS->Type]];
730 /* Data Coding Scheme */
731 if (SMS->Type != SMS_Delivery_Report && SMS->Type != SMS_Picture)
732 SMS->DCS.Type = message[13 + DataOffset[SMS->Type]];
736 /* User Data Header */
737 if (message[15] & 0x40) { /* UDH header available */
738 dprintf("UDH found\n");
739 DecodeUDH(message + 34 + DataOffset[SMS->Type], SMS);
740 } else { /* No UDH */
748 /* This function decodes SMS as described in:
749 - GSM 03.40 version 6.1.0 Release 1997, section 9
751 GSM_Error DecodePDUSMS(unsigned char *message, GSM_SMSMessage *SMS, int MessageLength)
756 DecodeSMSHeader(message, SMS);
758 case SMS_Delivery_Report:
759 SMSStatus(message[17], SMS);
762 dprintf("Picture!!!\n");
763 GSM_ReadSMSBitmap(SMS_Picture, message + 41, NULL, &bitmap);
764 GSM_PrintBitmap(&bitmap);
765 size = MessageLength - 45 - bitmap.size;
766 SMS->Length = message[45 + bitmap.size];
767 printf("%d %d %d\n", SMS->Length, bitmap.size, size);
768 DecodeData(message + 46 + bitmap.size,
769 (unsigned char *)&(SMS->MessageText),
770 SMS->Length, size, 0, SMS->DCS);
771 SMS->MessageText[SMS->Length] = 0;
774 size = MessageLength -
775 34 - /* Header Length */
776 DataOffset[SMS->Type] - /* offset */
777 SMS->UDH_Length - /* UDH Length */
779 DecodeData(message + 34 + DataOffset[SMS->Type] + SMS->UDH_Length,
780 (unsigned char *)&(SMS->MessageText),
781 SMS->Length, size, SMS->UDH_Length, SMS->DCS);
783 SMS->MessageText[SMS->Length] = 0;
790 /* This function does simple SMS decoding - no PDU coding */
791 GSM_Error DecodeTextSMS(unsigned char *message, GSM_SMSMessage *SMS)