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).
20 #include "gsm-common.h"
21 #include "gsm-encoding.h"
22 #include "gsm-bitmaps.h"
24 SMSMessage_PhoneLayout layout;
25 static SMSMessage_Layout llayout;
32 /* User data headers */
33 static struct udh_data headers[] = {
35 { 0x05, "\x00\x03\x01\x00\x00" }, /* Concatenated messages */
36 { 0x06, "\x05\x04\x15\x82\x00\x00" }, /* Operator logos */
37 { 0x06, "\x05\x04\x15\x83\x00\x00" }, /* Caller logos */
38 { 0x06, "\x05\x04\x15\x81\x00\x00" }, /* Ringtones */
39 { 0x04, "\x03\x01\x00\x00" }, /* Voice Messages */
40 { 0x04, "\x03\x01\x01\x00" }, /* Fax Messages */
41 { 0x04, "\x03\x01\x02\x00" }, /* Email Messages */
42 { 0x06, "\x05\x04\x23\xf4\x00\x00" }, /* Business Card */
51 /* This function implements packing of numbers (SMS Center number and
52 destination number) for SMS sending function. */
53 static int SemiOctetPack(char *Number, unsigned char *Output, SMS_NumberType type)
55 unsigned char *IN = Number; /* Pointer to the input number */
56 unsigned char *OUT = Output; /* Pointer to the output */
57 int count = 0; /* This variable is used to notify us about count of already
60 /* The first byte in the Semi-octet representation of the address field is
61 the Type-of-Address. This field is described in the official GSM
62 specification 03.40 version 6.1.0, section 9.1.2.5, page 33. We support
63 only international and unknown number. */
66 if (type == SMS_International) IN++; /* Skip '+' */
67 if ((type == SMS_Unknown) && (*IN == '+')) IN++; /* Optional '+' in Unknown number type */
69 /* The next field is the number. It is in semi-octet representation - see
70 GSM scpecification 03.40 version 6.1.0, section 9.1.2.3, page 31. */
73 *OUT = *OUT | ((*IN - '0') << 4);
81 /* We should also fill in the most significant bits of the last byte with
82 0x0f (1111 binary) if the number is represented with odd number of
89 return (2 * (OUT - Output - 1) - (count % 2));
92 char *GetBCDNumber(u8 *Number)
94 static char Buffer[20] = "";
95 int length = Number[0]; /* This is the length of BCD coded number */
98 memset(Buffer, 0, 20);
100 case SMS_Alphanumeric:
101 Unpack7BitCharacters(0, length, length, Number+2, Buffer);
104 case SMS_International:
105 sprintf(Buffer, "+");
110 case SMS_Abbreviated:
112 for (count = 0; count < length - 1; count++) {
113 Digit = Number[count+2] & 0x0f;
114 if (Digit < 10) sprintf(Buffer, "%s%d", Buffer, Digit);
115 Digit = Number[count+2] >> 4;
116 if (Digit < 10) sprintf(Buffer, "%s%d", Buffer, Digit);
123 static char *PrintDateTime(u8 *Number)
125 static char Buffer[23] = "";
127 memset(Buffer, 0, 23);
128 if (Number[0] < 70) sprintf(Buffer, "20");
129 else sprintf(Buffer, "19");
130 sprintf(Buffer, "%s%d%d-", Buffer, Number[0] & 0x0f, Number[0] >> 4);
131 sprintf(Buffer, "%s%d%d-", Buffer, Number[1] & 0x0f, Number[1] >> 4);
132 sprintf(Buffer, "%s%d%d ", Buffer, Number[2] & 0x0f, Number[2] >> 4);
133 sprintf(Buffer, "%s%d%d:", Buffer, Number[3] & 0x0f, Number[3] >> 4);
134 sprintf(Buffer, "%s%d%d:", Buffer, Number[4] & 0x0f, Number[4] >> 4);
135 sprintf(Buffer, "%s%d%d", Buffer, Number[5] & 0x0f, Number[5] >> 4);
136 if (Number[6] & 0x08)
137 sprintf(Buffer, "%s-", Buffer);
139 sprintf(Buffer, "%s+", Buffer);
140 sprintf(Buffer, "%s%02d00", Buffer, (10 * (Number[6] & 0x07) + (Number[6] >> 4)) / 4);
145 SMS_DateTime *UnpackDateTime(u8 *Number, SMS_DateTime *dt)
147 dt->Year = 10 * (Number[0] & 0x0f) + (Number[0] >> 4);
148 if (dt->Year < 70) dt->Year += 2000;
149 else dt->Year += 1900;
150 dt->Month = 10 * (Number[1] & 0x0f) + (Number[1] >> 4);
151 dt->Day = 10 * (Number[2] & 0x0f) + (Number[2] >> 4);
152 dt->Hour = 10 * (Number[3] & 0x0f) + (Number[3] >> 4);
153 dt->Minute = 10 * (Number[4] & 0x0f) + (Number[4] >> 4);
154 dt->Second = 10 * (Number[5] & 0x0f) + (Number[5] >> 4);
155 dt->Timezone = (10 * (Number[6] & 0x07) + (Number[6] >> 4)) / 4;
156 if (Number[6] & 0x08) dt->Timezone = -dt->Timezone;
165 static GSM_Error EncodeData(GSM_SMSMessage *SMS, char *dcs, char *message)
168 unsigned short length = strlen(SMS->MessageText);
170 switch (SMS->DCS.Type) {
171 case SMS_GeneralDataCoding:
172 switch (SMS->DCS.u.General.Class) {
173 case 1: dcs[0] |= 0xf0; break;
174 case 2: dcs[0] |= 0xf1; break;
175 case 3: dcs[0] |= 0xf2; break;
176 case 4: dcs[0] |= 0xf3; break;
179 if (SMS->DCS.u.General.Compressed) {
180 /* Compression not supported yet */
181 /* dcs[0] |= 0x20; */
183 al = SMS->DCS.u.General.Alphabet;
185 case SMS_MessageWaiting:
186 al = SMS->DCS.u.MessageWaiting.Alphabet;
187 if (SMS->DCS.u.MessageWaiting.Discard) dcs[0] |= 0xc0;
188 else if (SMS->DCS.u.MessageWaiting.Alphabet == SMS_UCS2) dcs[0] |= 0xe0;
191 if (SMS->DCS.u.MessageWaiting.Active) dcs[0] |= 0x08;
192 dcs[0] |= (SMS->DCS.u.MessageWaiting.Type & 0x03);
196 return GE_SMSWRONGFORMAT;
199 case SMS_DefaultAlphabet:
200 Pack7BitCharacters((7 - (SMS->UDH_Length % 7)) % 7, SMS->MessageText, message);
201 SMS->Length = 8 * SMS->UDH_Length + (7 - (SMS->UDH_Length % 7)) % 7 + length;
205 memcpy(message, SMS->MessageText + 1, SMS->MessageText[0]);
206 SMS->Length = SMS->UDH_Length + SMS->MessageText[0];
210 EncodeUnicode(message, SMS->MessageText, length);
211 SMS->Length = length;
214 return GE_SMSWRONGFORMAT;
219 /* This function encodes the UserDataHeader as described in:
220 - GSM 03.40 version 6.1.0 Release 1997, section 9.2.3.24
221 - Smart Messaging Specification, Revision 1.0.0, September 15, 1997
223 static GSM_Error EncodeUDH(SMS_UDHInfo UDHi, char *UDH)
231 case SMS_VoiceMessage:
233 case SMS_EmailMessage:
234 UDH[pos+4] = UDHi.u.SpecialSMSMessageIndication.MessageCount;
235 if (UDHi.u.SpecialSMSMessageIndication.Store) UDH[pos+3] |= 0x80;
236 case SMS_ConcatenatedMessages:
238 case SMS_CallerIDLogo:
240 UDH[0] += headers[UDHi.Type].length;
241 memcpy(UDH+pos+1, headers[UDHi.Type].header, headers[UDHi.Type].length);
244 dprintf("Not supported User Data Header type\n");
250 static GSM_Error EncodeSMSSubmitHeader(GSM_SMSMessage *SMS, char *frame)
252 GSM_Error error = GE_NONE;
254 /* Standard Header: */
255 memcpy(frame + llayout.UserDataHeader, "\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);
258 if (llayout.ReplyViaSameSMSC > -1) {
259 if (SMS->ReplyViaSameSMSC) frame[llayout.ReplyViaSameSMSC] |= 0x80;
262 /* User Data Header Indicator */
263 if (llayout.UserDataHeader > -1) {
264 if (SMS->UDH_No) frame[llayout.UserDataHeader] |= 0x40;
267 /* Status (Delivery) Report Request */
268 if (llayout.Report > -1) {
269 if (SMS->Report) frame[llayout.Report] |= 0x20;
272 /* Validity Period Format: mask - 0x00, 0x10, 0x08, 0x18 */
273 if (llayout.Validity > -1) {
274 frame[llayout.Validity] |= ((SMS->Validity.VPF & 0x03) << 3);
277 /* Reject Duplicates */
278 if (llayout.RejectDuplicates > -1) {
279 if (SMS->RejectDuplicates) frame[llayout.RejectDuplicates] |= 0x04;
282 /* Message Type is already set */
284 /* Message Reference */
285 /* Can we set this? */
287 /* Protocol Identifier */
288 /* FIXME: allow to change this in better way.
289 currently only 0x5f == `Return Call Message' is used */
290 if (llayout.PID > -1) {
291 if (SMS->PID) frame[llayout.PID] = SMS->PID;
294 /* Data Coding Scheme */
295 if (llayout.DataCodingScheme > -1) {
296 switch (SMS->DCS.Type) {
297 case SMS_GeneralDataCoding:
298 if (SMS->DCS.u.General.Compressed) frame[llayout.DataCodingScheme] |= 0x20;
299 if (SMS->DCS.u.General.Class) frame[llayout.DataCodingScheme] |= (0x10 | (SMS->DCS.u.General.Class - 1));
300 frame[llayout.DataCodingScheme] |= ((SMS->DCS.u.General.Alphabet & 0x03) << 2);
302 case SMS_MessageWaiting:
303 if (SMS->DCS.u.MessageWaiting.Discard) frame[llayout.DataCodingScheme] |= 0xc0;
304 else if (SMS->DCS.u.MessageWaiting.Alphabet == SMS_UCS2) frame[llayout.DataCodingScheme] |= 0xe0;
305 else frame[llayout.DataCodingScheme] |= 0xd0;
306 if (SMS->DCS.u.MessageWaiting.Active) frame[llayout.DataCodingScheme] |= 0x80;
307 frame[llayout.DataCodingScheme] |= (SMS->DCS.u.MessageWaiting.Type & 0x03);
310 dprintf("Wrong Data Coding Scheme (DCS) format\n");
311 return GE_SMSWRONGFORMAT;
315 /* Destination Address */
316 if (llayout.RemoteNumber > -1) {
317 frame[llayout.RemoteNumber] = SemiOctetPack(SMS->RemoteNumber.number, frame + llayout.RemoteNumber + 1, SMS->RemoteNumber.type);
320 /* Validity Period */
321 switch (SMS->Validity.VPF) {
322 case SMS_EnhancedFormat:
324 case SMS_RelativeFormat:
326 case SMS_AbsoluteFormat:
335 static GSM_Error EncodeSMSDeliverHeader()
340 static GSM_Error EncodeSMSHeader(GSM_SMSMessage *SMS, char *frame)
341 /* We can create either SMS DELIVER (for saving in Inbox) or SMS SUBMIT
342 (for sending or saving in Outbox) message */
346 case SMS_Submit: /* we send SMS or save it in Outbox */
347 return EncodeSMSSubmitHeader(SMS, frame);
348 case SMS_Deliver: /* we save SMS in Inbox */
349 return EncodeSMSDeliverHeader(SMS, frame);
350 default: /* we don't create other formats of SMS */
351 return GE_SMSWRONGFORMAT;
355 /* This function encodes SMS as described in:
356 - GSM 03.40 version 6.1.0 Release 1997, section 9
358 int EncodePDUSMS(GSM_SMSMessage *SMS, char *message)
360 GSM_Error error = GE_NONE;
365 llayout = layout.Submit;
366 dprintf("Sending SMS to %s via message center %s\n", SMS->RemoteNumber.number, SMS->MessageCenter.Number);
369 llayout = layout.Deliver;
370 dprintf("Saving SMS to Inbox\n");
373 llayout = layout.Picture;
374 dprintf("Sending Picture Message\n");
376 case SMS_Delivery_Report:
378 dprintf("Not supported message type: %d\n", SMS->Type);
379 return GE_NOTSUPPORTED;
383 if (llayout.MessageCenter > -1) {
384 dprintf("%d %s\n", SMS->MessageCenter.Type, SMS->MessageCenter.Number);
385 message[llayout.MessageCenter] = SemiOctetPack(SMS->MessageCenter.Number, message + llayout.MessageCenter + 1, SMS->MessageCenter.Type);
386 if (message[llayout.MessageCenter] % 2) message[llayout.MessageCenter]++;
387 message[llayout.MessageCenter] = message[llayout.MessageCenter] / 2 + 1;
391 error = EncodeSMSHeader(SMS, message);
392 if (error != GE_NONE) return error;
394 /* User Data Header - if present */
395 // for (i = 0; i < SMS->UDH_No; i++) {
396 // error = EncodeUDH(SMS->UDH[i], message + 24);
397 // if (error != GE_NONE) return error;
402 EncodeData(SMS, message + llayout.DataCodingScheme, message + llayout.UserData + SMS->UDH_Length);
403 message[llayout.Length] = SMS->Length;
404 return SMS->Length + llayout.UserData - 1;
407 /* This function does simple SMS encoding - no PDU coding */
408 GSM_Error EncodeTextSMS()
417 static GSM_Error SMSStatus(unsigned char status, GSM_SMSMessage *SMS)
420 strcpy(SMS->MessageText, _("Delivered"));
423 dprintf("SM received by the SME");
426 dprintf("SM forwarded by the SC to the SME but the SC is unable to confirm delivery");
429 dprintf("SM replaced by the SC");
432 SMS->Length = strlen(_("Delivered"));
433 } else if (status & 0x40) {
435 strcpy(SMS->MessageText, _("Failed"));
437 /* more detailed reason only for debug */
440 dprintf("Temporary error, SC is not making any more transfer attempts\n");
444 dprintf("Congestion");
450 dprintf("No response from SME");
453 dprintf("Service rejected");
456 dprintf("Quality of service not aviable");
459 dprintf("Error in SME");
462 dprintf("Reserved/Specific to SC: %x", status);
466 dprintf("Permanent error, SC is not making any more transfer attempts\n");
469 dprintf("Remote procedure error");
472 dprintf("Incompatibile destination");
475 dprintf("Connection rejected by SME");
478 dprintf("Not obtainable");
481 dprintf("Quality of service not aviable");
484 dprintf("No internetworking available");
487 dprintf("SM Validity Period Expired");
490 dprintf("SM deleted by originating SME");
493 dprintf("SM Deleted by SC Administration");
496 dprintf("SM does not exist");
499 dprintf("Reserved/Specific to SC: %x", status);
503 SMS->Length = strlen(_("Failed"));
504 } else if (status & 0x20) {
505 strcpy(SMS->MessageText, _("Pending"));
507 /* more detailed reason only for debug */
508 dprintf("Temporary error, SC still trying to transfer SM\n");
511 dprintf("Congestion");
517 dprintf("No response from SME");
520 dprintf("Service rejected");
523 dprintf("Quality of service not aviable");
526 dprintf("Error in SME");
529 dprintf("Reserved/Specific to SC: %x", status);
532 SMS->Length = strlen(_("Pending"));
534 strcpy(SMS->MessageText, _("Unknown"));
536 /* more detailed reason only for debug */
537 dprintf("Reserved/Specific to SC: %x", status);
538 SMS->Length = strlen(_("Unknown"));
544 static GSM_Error DecodeData(char *message, char *output, int length, int size, int udhlen, SMS_DataCodingScheme dcs)
547 if ((dcs.Type & 0x08) == 0x08) {
548 dprintf("Unicode message\n");
549 length = (length - udhlen)/2;
550 DecodeUnicode(output, message, length);
553 if ((dcs.Type & 0xf4) == 0xf4) {
554 dprintf("8bit message\n");
555 memcpy(output, message, length);
558 dprintf("Default Alphabet\n");
559 length = length - (udhlen * 8 + ((7-(udhlen%7))%7)) / 7;
560 Unpack7BitCharacters((7-udhlen)%7, size, length, message, output);
561 DecodeAscii(output, output, length);
564 dprintf("%s\n", output);
568 /* This function decodes UDH as described in:
569 - GSM 03.40 version 6.1.0 Release 1997, section 9.2.3.24
570 - Smart Messaging Specification, Revision 1.0.0, September 15, 1997
572 static GSM_Error DecodeUDH(char *message, GSM_SMSMessage *SMS)
574 unsigned char length, pos, nr;
576 SMS->UDH_Length = length = message[0] + 1;
580 unsigned char udh_length;
582 udh_length = message[pos+1];
583 switch (message[pos]) {
584 case 0x00: // Concatenated short messages
585 dprintf("Concatenated messages\n");
586 SMS->UDH[nr].Type = SMS_ConcatenatedMessages;
587 SMS->UDH[nr].u.ConcatenatedShortMessage.ReferenceNumber = message[pos + 2];
588 SMS->UDH[nr].u.ConcatenatedShortMessage.MaximumNumber = message[pos + 3];
589 SMS->UDH[nr].u.ConcatenatedShortMessage.CurrentNumber = message[pos + 4];
591 case 0x01: // Special SMS Message Indication
592 switch (message[pos + 2] & 0x03) {
594 dprintf("Voice Message\n");
595 SMS->UDH[nr].Type = SMS_VoiceMessage;
598 dprintf("Fax Message\n");
599 SMS->UDH[nr].Type = SMS_FaxMessage;
602 dprintf("Email Message\n");
603 SMS->UDH[nr].Type = SMS_EmailMessage;
606 dprintf("Unknown\n");
607 SMS->UDH[nr].Type = SMS_UnknownUDH;
610 SMS->UDH[nr].u.SpecialSMSMessageIndication.Store = (message[pos + 2] & 0x80) >> 7;
611 SMS->UDH[nr].u.SpecialSMSMessageIndication.MessageCount = message[pos + 3];
613 case 0x04: // Application port addression scheme, 8 bit address
615 case 0x05: // Application port addression scheme, 16 bit address
616 switch (((0x00ff & message[pos + 2]) << 8) | (0x00ff & message[pos + 3])) {
618 dprintf("Ringtone\n");
619 SMS->UDH[nr].Type = SMS_Ringtone;
622 dprintf("Operator Logo\n");
623 SMS->UDH[nr].Type = SMS_OpLogo;
626 dprintf("Caller Icon\n");
627 SMS->UDH[nr].Type = SMS_CallerIDLogo;
630 dprintf("Business Card\n");
631 SMS->UDH[nr].Type = SMS_BusinessCard;
634 dprintf("Unknown\n");
635 SMS->UDH[nr].Type = SMS_UnknownUDH;
639 case 0x06: // SMSC Control Parameters
641 case 0x07: // UDH Source Indicator
644 dprintf("Not supported UDH\n");
647 length -= (udh_length + 2);
648 pos += (udh_length + 2);
656 static GSM_Error DecodeSMSHeader(unsigned char *message, GSM_SMSMessage *SMS)
658 /* Short Message Type */
659 SMS->Type = message[layout.Type];
662 llayout = layout.Deliver;
663 dprintf("Mobile Terminated message:\n");
665 case SMS_Delivery_Report:
666 llayout = layout.DeliveryReport;
667 dprintf("Delivery Report:\n");
670 llayout = layout.Submit;
671 dprintf("Mobile Originated message:\n");
674 llayout = layout.Picture;
675 dprintf("Picture Message:\n");
678 dprintf("Not supported message type: %d\n", SMS->Type);
679 return GE_NOTSUPPORTED;
682 if (!llayout.IsSupported) return GE_NOTSUPPORTED;
685 if (llayout.Time > -1) {
686 UnpackDateTime(message + llayout.Time, &(SMS->SMSCTime));
687 dprintf("\tDelivery date: %s\n", PrintDateTime(message + llayout.Time));
690 /* Short Message location in memory */
691 if (llayout.Number > -1) {
692 SMS->Number = message[llayout.Number];
693 dprintf("\tLocation: %d\n", SMS->Number);
696 /* Short Message Center */
697 if (llayout.MessageCenter > -1) {
698 strcpy(SMS->MessageCenter.Number, GetBCDNumber(message + llayout.MessageCenter));
699 dprintf("\tSMS center number: %s\n", SMS->MessageCenter.Number);
700 SMS->ReplyViaSameSMSC = false;
701 if (SMS->RemoteNumber.number[0] == 0 && (message[llayout.ReplyViaSameSMSC] & 0x80)) {
702 SMS->ReplyViaSameSMSC = true;
707 if (llayout.RemoteNumber > -1) {
708 message[llayout.RemoteNumber] = ((message[llayout.RemoteNumber])+1)/2+1;
709 strcpy(SMS->RemoteNumber.number, GetBCDNumber(message + llayout.RemoteNumber));
710 dprintf("\tRemote number (recipient or sender): %s\n", SMS->RemoteNumber.number);
714 if (llayout.SMSCTime > -1) {
715 UnpackDateTime(message + llayout.SMSCTime, &(SMS->Time));
716 dprintf("\tDate: %s\n", PrintDateTime(message + llayout.SMSCTime));
720 if (llayout.Length > -1)
721 SMS->Length = message[llayout.Length];
723 /* Data Coding Scheme */
724 if (llayout.DataCodingScheme > -1)
725 SMS->DCS.Type = message[llayout.DataCodingScheme];
727 /* User Data Header */
728 if (llayout.UserDataHeader > -1)
729 if (message[llayout.UserDataHeader] & 0x40) { /* UDH header available */
730 dprintf("UDH found\n");
731 DecodeUDH(message + llayout.UserData, SMS);
737 /* This function decodes SMS as described in:
738 - GSM 03.40 version 6.1.0 Release 1997, section 9
740 GSM_Error DecodePDUSMS(unsigned char *message, GSM_SMSMessage *SMS, int MessageLength)
746 error = DecodeSMSHeader(message, SMS);
747 if (error != GE_NONE) return error;
749 case SMS_Delivery_Report:
750 if (llayout.UserData > -1) SMSStatus(message[llayout.UserData], SMS);
753 GSM_ReadSMSBitmap(SMS_Picture, message + llayout.UserData, NULL, &bitmap);
754 GSM_PrintBitmap(&bitmap);
755 size = MessageLength - llayout.UserData - 4 - bitmap.size;
756 SMS->Length = message[llayout.UserData + 4 + bitmap.size];
757 DecodeData(message + llayout.UserData + 5 + bitmap.size,
758 (unsigned char *)&(SMS->MessageText),
759 SMS->Length, size, 0, SMS->DCS);
760 SMS->MessageText[SMS->Length] = 0;
763 size = MessageLength -
764 llayout.UserData + 1 - /* Header Length */
765 SMS->UDH_Length; /* UDH Length */
766 DecodeData(message + llayout.UserData + SMS->UDH_Length,
767 (unsigned char *)&(SMS->MessageText),
768 SMS->Length, size, SMS->UDH_Length, SMS->DCS);
770 SMS->MessageText[SMS->Length] = 0;
777 /* This function does simple SMS decoding - no PDU coding */
778 GSM_Error DecodeTextSMS(unsigned char *message, GSM_SMSMessage *SMS)