From 1ce6943f4f6ea8e4b1b7a570d139e5629785643f Mon Sep 17 00:00:00 2001 From: Luigi Rizzo Date: Thu, 21 Dec 2006 16:50:26 +0000 Subject: Add a bit of documentation on this code, including pointers to relevant documents and comment on timing issues. Initial merge of the code in http://bugs.digium.com/view.php?id=8586 by Filippo Grassilli (Hyppo) to support the SMS Protocol 2. In this commit i have tried to minimize the diffs, so further code cleanup will come in subsequent commits. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@48736 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- apps/app_sms.c | 412 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 376 insertions(+), 36 deletions(-) (limited to 'apps/app_sms.c') diff --git a/apps/app_sms.c b/apps/app_sms.c index 0fb796109..c6cb93473 100644 --- a/apps/app_sms.c +++ b/apps/app_sms.c @@ -16,10 +16,24 @@ /*! \file * - * \brief SMS application - ETSI ES 201 912 protocol 1 implimentation + * \brief SMS application - ETSI ES 201 912 protocol 1 implementation + * + * \par Development notes + * \note The ETSI standards are available free of charge from ETSI at + * http://pda.etsi.org/pda/queryform.asp + * Among the relevant documents here we have: + * + * ES 201 912 SMS for PSTN/ISDN + * TS 123 040 Technical realization of SMS + * + * \note 2006-09-19: ETSI ES 201 912 protocol 2 used in Italy and Spain + * support added by Filippo Grassilli (Hyppo) + * (Hyppo) + * Not fully tested, under development + * * \ingroup applications * - * \author Adrian Kennard + * \author Adrian Kennard (for the original protocol 1 code) */ #include "asterisk.h" @@ -65,9 +79,10 @@ static char *app = "SMS"; static char *synopsis = "Communicates with SMS service centres and SMS capable analogue phones"; static char *descrip = - " SMS(name|[a][s]): SMS handles exchange of SMS data with a call to/from SMS capabale\n" + " SMS(name|[a][s][t]): SMS handles exchange of SMS data with a call to/from SMS capable\n" "phone or SMS PSTN service center. Can send and/or receive SMS messages.\n" "Works to ETSI ES 201 912 compatible with BT SMS PSTN service in UK\n" + "and Telecom Italia in Italy.\n" "Typical usage is to use to handle called from the SMS service centre CLI,\n" "or to set up a call using 'outgoing' or manager interface to connect\n" "service centre to SMS()\n" @@ -75,9 +90,13 @@ static char *descrip = "Arguments:\n" " a: answer, i.e. send initial FSK packet.\n" " s: act as service centre talking to a phone.\n" + " t: use protocol 2 (default used is protocol 1).\n" "Messages are processed as per text file message queues.\n" "smsq (a separate software) is a command to generate message\n" - "queues and send messages.\n"; + "queues and send messages.\n" + "NOTE: the protocol has tight delay bounds. Please use short frames\n" + "and disable/keep short the jitter buffer on the ATA to make sure that\n" + "respones (ACK etc.) are received in time.\n"; /* * 80 samples of a single period of the wave. At 8000 Hz, it means these @@ -105,6 +124,43 @@ static const output_t *wave_out = wave; /* outgoing samples */ #define __OUT_FMT AST_FORMAT_SLINEAR #endif +#define OSYNC_BITS 80 /* initial sync bits */ + +/*! + * The SMS spec ETSI ES 201 912 defines two protocols with different message types. + * Also note that the high bit is used to indicate whether the message + * is complete or not, but in two opposite ways: + * for Protocol 1, 0x80 means that the message is complete; + * for Protocol 2, 0x00 means that the message is complete; + */ +enum message_types { + DLL_SMS_MASK = 0x7f, /* mask for the valid bits */ + + /* Protocol 1 values */ + DLL1_SMS_DATA = 0x11, /* data packet */ + DLL1_SMS_ERROR = 0x12, + DLL1_SMS_EST = 0x13, /* start the connection */ + DLL1_SMS_REL = 0x14, /* end the connection */ + DLL1_SMS_ACK = 0x15, + DLL1_SMS_NACK = 0x16, + + DLL1_SMS_COMPLETE = 0x80, /* packet is complete */ + DLL1_SMS_MORE = 0x00, /* more data to follow */ + + /* Protocol 2 values */ + DLL2_SMS_EST = 0x7f, /* magic number. No message body */ + DLL2_SMS_INFO_MO = 0x10, + DLL2_SMS_INFO_MT = 0x11, + DLL2_SMS_INFO_STA = 0x12, + DLL2_SMS_NACK = 0x13, + DLL2_SMS_ACK0 = 0x14, /* ack even-numbered frame */ + DLL2_SMS_ACK1 = 0x15, /* ack odd-numbered frame */ + DLL2_SMS_ENQ = 0x16, + DLL2_SMS_REL = 0x17, /* end the connection */ + + DLL2_SMS_COMPLETE = 0x00, /* packet is complete */ + DLL2_SMS_MORE = 0x80, /* more data to follow */ +}; /* SMS 7 bit character mapping to UCS-2 */ static const unsigned short defaultalphabet[] = { @@ -186,6 +242,11 @@ typedef struct sms_s unsigned char ibith; /*!< history of last bits */ unsigned char ibitt; /*!< total of 1's in last 3 bytes */ /* more to go here */ + + int protocol; /*!< ETSI SMS protocol to use (passed at app call) */ + int oseizure; /*!< protocol 2: channel seizure bits to send */ + int framenumber; /*!< protocol 2: frame number (for sending ACK0 or ACK1) */ + unsigned char udtxt[SMSLEN]; /*!< user data (message), PLAIN text */ } sms_t; /* different types of encoding */ @@ -716,6 +777,7 @@ static void sms_readfile (sms_t * h, char *fn) *p++ = 0; if (!strcmp (line, "ud")) { /* parse message (UTF-8) */ unsigned char o = 0; + memcpy(h->udtxt,p,SMSLEN); /* for protocol 2 */ while (*p && o < SMSLEN) h->ud[o++] = utf8decode(pp); h->udl = o; @@ -1007,6 +1069,216 @@ static unsigned char sms_handleincoming (sms_t * h) #define NAME_MAX 1024 #endif +/*! + * Add data to a protocol 2 message. + * Use the length field (h->omsg[1]) as a pointer to the next free position. + */ +static void adddata_proto2 (sms_t *h, unsigned char msg, char *data, int size) +{ + int x = h->omsg[1]+2; /* Get current position */ + if (x==2) + x += 2; /* First: skip Payload length (set later) */ + h->omsg[x++] = msg; /* Message code */ + h->omsg[x++]=(unsigned char)size; /* Data size Low */ + h->omsg[x++]=0; /* Data size Hi */ + for (; size>0 ; size--) + h->omsg[x++] = *data++; + h->omsg[1] = x-2; /* Frame size */ + h->omsg[2] = x-4; /* Payload length (Lo) */ + h->omsg[3] = 0; /* Payload length (Hi) */ +} + +static void putdummydata_proto2 (sms_t *h) +{ + adddata_proto2 (h, 0x10, "\0", 1); /* Media Identifier > SMS */ + adddata_proto2 (h, 0x11, "\0\0\0\0\0\0", 6); /* Firmware version */ + adddata_proto2 (h, 0x12, "\2\0\4", 3); /* SMS provider ID */ + adddata_proto2 (h, 0x13, h->udtxt, h->udl); /* Body */ +} + +static void sms_compose2(sms_t *h, int more) +{ + struct tm *tm; + char stm[9]; + + h->omsg[0] = 0x00; /* set later... */ + h->omsg[1] = 0; + putdummydata_proto2 (h); + if (h->smsc) { /* deliver */ + h->omsg[0] = 0x11; /* SMS_DELIVERY */ + // Required: 10 11 12 13 14 15 17 (seems they must be ordered!) + tm=localtime(&h->scts); + sprintf (stm, "%02d%02d%02d%02d", tm->tm_mon+1,tm->tm_mday,tm->tm_hour,tm->tm_min); // Date mmddHHMM + adddata_proto2 (h, 0x14, stm, 8); /* Date */ + if(*h->oa==0) + strcpy(h->oa,"00000000"); + adddata_proto2 (h, 0x15, h->oa, strlen(h->oa)); /* Originator */ + adddata_proto2 (h, 0x17, "\1", 1); /* Calling Terminal ID */ + } else { /* submit */ + h->omsg[0] = 0x10; /* SMS_SUBMIT */ + // Required: 10 11 12 13 17 18 1B 1C (seems they must be ordered!) + adddata_proto2 (h, 0x17, "\1", 1); /* Calling Terminal ID */ + if(*h->da==0) + strcpy(h->da,"00000000"); + adddata_proto2 (h, 0x18, h->da, strlen(h->da)); /* Originator */ + adddata_proto2 (h, 0x1B, "\1", 1); /* Called Terminal ID */ + adddata_proto2 (h, 0x1C, "\0\0\0", 3); /* Notification */ + } +} + +static void putdummydata_proto2 (sms_t *h); + +#if 1 /* XXX debugging */ +static char *sms_hexdump (unsigned char buf[], int size) +{ + static char *s=NULL; + char *p; + int f; + + s=(char *)realloc(s,(size*3)+1); + for(p=s,f=0; fimsg[1]+2; + /* ast_verbose (VERBOSE_PREFIX_3 "SMS-P2 Frame: %s\n", sms_hexdump(h->imsg,sz)); */ + + /* Parse message body (called payload) */ + h->scts = time (0); + for(f=4; fimsg[f++]; + msgsz=h->imsg[f++]; + msgsz+=(h->imsg[f++]*256); + switch(msg) { + case 0x13: /* Body */ + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "SMS-P2 Body#%02X=[%.*s]\n",msg,msgsz,&h->imsg[f]); + if (msgsz >= sizeof(h->imsg)) + msgsz = sizeof(h->imsg)-1; + for (i=0; iud[i]=h->imsg[f+i]; + h->udl = msgsz; + break; + case 0x14: /* Date SCTS */ + h->scts = time (0); + tm = localtime (&h->scts); + tm->tm_mon = ( (h->imsg[f]*10) + h->imsg[f+1] ) - 1; + tm->tm_mday = ( (h->imsg[f+2]*10) + h->imsg[f+3] ); + tm->tm_hour = ( (h->imsg[f+4]*10) + h->imsg[f+5] ); + tm->tm_min = ( (h->imsg[f+6]*10) + h->imsg[f+7] ); + tm->tm_sec = 0; + h->scts = mktime (tm); + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "SMS-P2 Date#%02X=%02d/%02d %02d:%02d\n", msg, tm->tm_mday, tm->tm_mon+1, tm->tm_hour, tm->tm_min); + break; + case 0x15: /* Calling line (from SMSC) */ + if (msgsz>=20) + msgsz=20-1; + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "SMS-P2 Origin#%02X=[%.*s]\n",msg,msgsz,&h->imsg[f]); + ast_copy_string (h->oa, &h->imsg[f], msgsz+1); + break; + case 0x18: /* Destination (from TE/phone) */ + if (msgsz>=20) + msgsz=20-1; + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "SMS-P2 Destination#%02X=[%.*s]\n",msg,msgsz,&h->imsg[f]); + ast_copy_string (h->da, &h->imsg[f], msgsz+1); + break; + case 0x1C: /* Notify */ + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "SMS-P2 Notify#%02X=%s\n",msg,sms_hexdump(&h->imsg[f],3)); + break; + default: + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "SMS-P2 Par#%02X [%d]: %s\n",msg,msgsz,sms_hexdump(&h->imsg[f],msgsz)); + break; + } + f+=msgsz; /* Skip to next */ + } + h->rx = 1; /* received message */ + sms_writefile (h); /* write the file */ + return 0; /* no error */ +} + +#if 0 +static void smssend(sms_t *h, char *c) +{ + int f, x; + for(f=0; fomsg[f] = x; + } + sms_messagetx (h); +} +#endif + +static void sms_nextoutgoing (sms_t *h); + +static void sms_messagerx2(sms_t * h) +{ + int p = h->imsg[0] & DLL_SMS_MASK ; /* mask the high bit */ + int cause; + +#define DLL2_ACK(h) ((h->framenumber & 1) ? DLL2_SMS_ACK1: DLL2_SMS_ACK1) + switch (p) { + case DLL2_SMS_EST: /* Protocol 2: Connection ready (fake): send message */ + sms_nextoutgoing (h); + //smssend(h,"11 29 27 00 10 01 00 00 11 06 00 00 00 00 00 00 00 12 03 00 02 00 04 13 01 00 41 14 08 00 30 39 31 35 30 02 30 02 15 02 00 39 30 "); + break; + + case DLL2_SMS_INFO_MO: /* transport SMS_SUBMIT */ + case DLL2_SMS_INFO_MT: /* transport SMS_DELIVERY */ + cause = sms_handleincoming_proto2(h); + if (!cause) /* ACK */ + sms_log (h, 'Y'); + h->omsg[0] = DLL2_ACK(h); + h->omsg[1] = 0x06; /* msg len */ + h->omsg[2] = 0x04; /* payload len */ + h->omsg[3] = 0x00; /* payload len */ + h->omsg[4] = 0x1f; /* Response type */ + h->omsg[5] = 0x01; /* parameter len */ + h->omsg[6] = 0x00; /* parameter len */ + h->omsg[7] = cause; /* CONFIRM or error */ + sms_messagetx (h); + break; + + case DLL2_SMS_NACK: /* Protocol 2: SMS_NAK */ + h->omsg[0] = DLL2_SMS_REL; /* SMS_REL */ + h->omsg[1] = 0x00; /* msg len */ + sms_messagetx (h); + break; + + case DLL2_SMS_ACK0: + case DLL2_SMS_ACK1: + /* SMS_ACK also transport SMS_SUBMIT or SMS_DELIVERY */ + if( (h->omsg[0] & DLL_SMS_MASK) == DLL2_SMS_REL) { + /* a response to our Release, just hangup */ + h->hangup = 1; /* hangup */ + } else { + /* XXX depending on what we are.. */ + ast_log(LOG_NOTICE, "SMS_SUBMIT or SMS_DELIVERY"); + sms_nextoutgoing (h); + } + break; + + case DLL2_SMS_REL: /* Protocol 2: SMS_REL (hangup req) */ + h->omsg[0] = DLL2_ACK(h); + h->omsg[1] = 0; + sms_messagetx (h); + break; + } +} + /*! \brief compose a message for protocol 1 */ static void sms_compose1(sms_t *h, int more) { @@ -1072,20 +1344,31 @@ static void sms_nextoutgoing (sms_t * h) closedir (d); } if (*h->da || *h->oa) { /* message to send */ - sms_compose1(h, more); - } else { /* no message */ - h->omsg[0] = 0x94; /* SMS_REL */ - h->omsg[1] = 0; + if (h->protocol==2) + sms_compose2(h, more); + else + sms_compose1(h,more); + } else { /* no message */ + if (h->protocol==2) { + h->omsg[0] = 0x17; /* SMS_REL */ + h->omsg[1] = 0; + } else { + h->omsg[0] = 0x94; /* SMS_REL */ + h->omsg[1] = 0; + } } sms_messagetx (h); } -static void sms_debug (char *dir, unsigned char *msg) +#define DIR_RX 1 +#define DIR_TX 2 +static void sms_debug (int dir, sms_t *h) { - char txt[259 * 3 + 1], - *p = txt; /* always long enough */ - int n = msg[1] + 3, - q = 0; + char txt[259 * 3 + 1]; + char *p = txt; /* always long enough */ + unsigned char *msg = (dir == DIR_RX) ? h->imsg : h->omsg; + int n = (dir == DIR_RX) ? h->ibytep : msg[1] + 2; + int q = 0; while (q < n && q < 30) { sprintf (p, " %02X", msg[q++]); p += 3; @@ -1093,17 +1376,23 @@ static void sms_debug (char *dir, unsigned char *msg) if (q < n) sprintf (p, "..."); if (option_verbose > 2) - ast_verbose (VERBOSE_PREFIX_3 "SMS %s%s\n", dir, txt); + ast_verbose (VERBOSE_PREFIX_3 "SMS %s%s\n", dir == DIR_RX ? "RX" : "TX", txt); } + static void sms_messagerx(sms_t * h) { - sms_debug ("RX", h->imsg); - /* testing */ - switch (h->imsg[0]) { /* PROTOCOL version 1 */ + int cause; + + sms_debug (DIR_RX, h); + if (h->protocol == 2) { + sms_messagerx2(h); + return; + } + /* parse incoming message for Protocol 1 */ + switch (h->imsg[0]) { case 0x91: /* SMS_DATA */ - { - unsigned char cause = sms_handleincoming (h); + cause = sms_handleincoming (h); if (!cause) { sms_log (h, 'Y'); h->omsg[0] = 0x95; /* SMS_ACK */ @@ -1119,8 +1408,8 @@ static void sms_messagerx(sms_t * h) h->omsg[4] = 0; /* no parameters */ } sms_messagetx (h); - } - break; + break; + case 0x92: /* SMS_ERROR */ h->err = 1; sms_messagetx (h); /* send whatever we sent again */ @@ -1157,15 +1446,27 @@ static void sms_messagetx(sms_t * h) for (p = 0; p < len; p++) /* compute checksum */ c += h->omsg[p]; h->omsg[len] = 0 - c; /* actually, (256 - (c & 0fxx)) & 0xff) */ - sms_debug ("TX", h->omsg); - h->obyte = 1; + sms_debug(DIR_TX, h); + h->framenumber++; /* Proto 2 */ + h->obyte = 1; /* send mark ('1') at the beginning */ h->opause = 200; + /* Change the initial message delay. BT requires 300ms, + * but for others this might be way too much and the phone + * could time out. XXX make it configurable. + */ if (h->omsg[0] == 0x93) - h->opause = 2400; /* initial message delay 300ms (for BT) */ + h->opause = 200; /* XXX initial message delay 300ms (for BT) */ h->obytep = 0; h->obitp = 0; + if (h->protocol == 2) { + h->oseizure = 300; /* Proto 2: 300bits (or more ?) */ + h->obyte = 0; /* Seizure starts with space (0) */ + h->opause = 400; + } else { + h->oseizure = 0; /* Proto 1: No seizure */ + } /* Note - setting osync triggers the generator */ - h->osync = 80; /* 80 sync bits */ + h->osync = OSYNC_BITS; /* 80 sync bits */ h->obyten = len + 1; /* bytes to send (including checksum) */ } @@ -1199,15 +1500,22 @@ static int sms_generate (struct ast_channel *chan, void *data, int len, int samp if (h->opause) h->opause--; - else if (h->obyten || h->osync) { /* sending data */ + else if (h->obyten || h->osync) { /* sending data */ buf[i] = wave_out[h->ophase]; h->ophase += (h->obyte & 1) ? 13 : 21; /* compute next phase */ if (h->ophase >= 80) h->ophase -= 80; if ((h->ophasep += 12) >= 80) { /* time to send the next bit */ h->ophasep -= 80; - if (h->osync) { + if (h->oseizure > 0) { /* sending channel seizure (proto 2) */ + h->oseizure--; + h->obyte ^= 1; /* toggle low bit */ + } else if (h->osync) { + h->obyte = 1; /* send mark as sync bit */ h->osync--; /* sending sync bits */ + if (h->osync == 0 && h->protocol == 2 && h->omsg[0] == DLL2_SMS_EST) { + h->obytep = h->obyten = 0; /* we are done */ + } } else { h->obitp++; if (h->obitp == 1) @@ -1250,8 +1558,13 @@ static int sms_generate (struct ast_channel *chan, void *data, int len, int samp */ static void sms_process (sms_t * h, int samples, signed short *data) { +#if 1 + /* Do we really need to remain deaf while a packet is + * being transmitted ? + */ if (h->obyten || h->osync) return; /* sending */ +#endif while (samples--) { unsigned long long m0, m1; if (abs (*data) > h->imag) @@ -1301,27 +1614,43 @@ static void sms_process (sms_t * h, int samples, signed short *data) h->iphasep = 0; } if (bit && h->ibitc == 200) { /* sync, restart message */ + /* Protocol 2: empty connnection ready (I am master) */ + if(h->framenumber<0 && h->ibytec>=160 && !memcmp(h->imsg,"UUUUUUUUUUUUUUUUUUUU",20)) { + h->framenumber = 1; + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "SMS protocol 2 detected\n"); + h->protocol = 2; + h->imsg[0] = 0xff; /* special message (fake) */ + h->imsg[1] = h->imsg[2] = 0x00; + h->ierr = h->ibitn = h->ibytep = h->ibytec = 0; + sms_messagerx (h); + } h->ierr = h->ibitn = h->ibytep = h->ibytec = 0; } if (h->ibitn) { h->iphasep += 12; - if (h->iphasep >= 80) { /* next bit */ + if (h->iphasep >= 80) { /* next bit */ h->iphasep -= 80; - if (h->ibitn++ == 9) { /* end of byte */ - if (!bit) /* bad stop bit */ + if (h->ibitn++ == 9) { /* end of byte */ + if (!bit) { /* bad stop bit */ + ast_log(LOG_NOTICE, "bad stop bit"); h->ierr = 0xFF; /* unknown error */ - else { + } else { if (h->ibytep < sizeof (h->imsg)) { h->imsg[h->ibytep] = h->ibytev; h->ibytec += h->ibytev; h->ibytep++; - } else if (h->ibytep == sizeof (h->imsg)) + } else if (h->ibytep == sizeof (h->imsg)) { + ast_log(LOG_NOTICE, "msg too large"); h->ierr = 2; /* bad message length */ + } if (h->ibytep > 1 && h->ibytep == 3 + h->imsg[1] && !h->ierr) { if (!h->ibytec) sms_messagerx (h); - else + else { + ast_log(LOG_NOTICE, "bad checksum"); h->ierr = 1; /* bad checksum */ + } } } h->ibitn = 0; @@ -1332,11 +1661,12 @@ static void sms_process (sms_t * h, int samples, signed short *data) } } else { /* lost carrier */ if (h->idle++ == 80000) { /* nothing happening */ - ast_log (LOG_EVENT, "No data, hanging up\n"); + ast_log (LOG_NOTICE, "No data, hanging up\n"); h->hangup = 1; h->err = 1; } if (h->ierr) { /* error */ + ast_log (LOG_NOTICE, "Error %d, hanging up\n", h->ierr); /* Protocol 1 */ h->err = 1; h->omsg[0] = 0x92; /* error */ @@ -1398,6 +1728,7 @@ static int sms_exec (struct ast_channel *chan, void *data) for (p = (unsigned char *)h.queue; *p; p++) if (!isalnum (*p)) *p = '-'; /* make very safe for filenames */ +ast_log(LOG_NOTICE, "sms to queue %s\n", h.queue); while (*d && *d != '|') { switch (*d) { case 'a': /* we have to send the initial FSK sequence */ @@ -1406,6 +1737,9 @@ static int sms_exec (struct ast_channel *chan, void *data) case 's': /* we are acting as a service centre talking to a phone */ h.smsc = 1; break; + case 't': /* use protocol 2 ([t]wo)! couldn't use numbers *!* */ + h.protocol = 2; + break; /* the following apply if there is an arg3/4 and apply to the created message file */ case 'r': h.srr = 1; @@ -1462,9 +1796,15 @@ static int sms_exec (struct ast_channel *chan, void *data) } if (answer) { + h.framenumber = 1; /* Proto 2 */ /* set up SMS_EST initial message */ - h.omsg[0] = 0x93; - h.omsg[1] = 0; + if (h.protocol == 2) { + h.omsg[0] = DLL2_SMS_EST; + h.omsg[1] = 0; + } else { + h.omsg[0] = DLL1_SMS_EST | DLL1_SMS_COMPLETE; + h.omsg[1] = 0; + } sms_messagetx (&h); } } -- cgit v1.2.3