aboutsummaryrefslogtreecommitdiffstats
path: root/telephony/sms.c
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commit55f4e4a5ec657a017e3bf75299ad71fd1c968dd3 (patch)
tree550ce922ea0e125ac6a9738210ce2939bf2fe901 /telephony/sms.c
parent413f05aaf54fa08c0ae7e997327a4f4a473c0a8d (diff)
downloadexternal_qemu-55f4e4a5ec657a017e3bf75299ad71fd1c968dd3.zip
external_qemu-55f4e4a5ec657a017e3bf75299ad71fd1c968dd3.tar.gz
external_qemu-55f4e4a5ec657a017e3bf75299ad71fd1c968dd3.tar.bz2
Initial Contribution
Diffstat (limited to 'telephony/sms.c')
-rw-r--r--telephony/sms.c1655
1 files changed, 1655 insertions, 0 deletions
diff --git a/telephony/sms.c b/telephony/sms.c
new file mode 100644
index 0000000..7a394d4
--- /dev/null
+++ b/telephony/sms.c
@@ -0,0 +1,1655 @@
+/* Copyright (C) 2007-2008 The Android Open Source Project
+**
+** This software is licensed under the terms of the GNU General Public
+** License version 2, as published by the Free Software Foundation, and
+** may be copied, distributed, and modified under those terms.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+*/
+#include "sms.h"
+#include "gsm.h"
+#include <memory.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#define DEBUG 1
+
+#if 1
+# include "android_debug.h"
+# define D_ACTIVE VERBOSE_CHECK(modem)
+#else
+# define D_ACTIVE DEBUG
+#endif
+
+#if DEBUG
+# define D(...) VERBOSE_PRINT(modem,__VA_ARGS__)
+#else
+# define D(...) ((void)0)
+#endif
+
+/* maximum number of data bytes in a SMS data message */
+#define MAX_USER_DATA_BYTES 140
+
+/* maximum number of 7-bit septets in a SMS text message */
+#define MAX_USER_DATA_SEPTETS 160
+
+/* size of the user data header in bytes */
+#define USER_DATA_HEADER_SIZE 6
+
+/** MESSAGE TEXT
+ **/
+int
+sms_utf8_from_message_str( const char* str, int strlen, unsigned char* utf8, int utf8len )
+{
+ cbytes_t p = (cbytes_t)str;
+ cbytes_t end = p + strlen;
+ int count = 0;
+ int escaped = 0;
+
+ while (p < end)
+ {
+ int c = p[0];
+
+ /* read the value from the string */
+ p += 1;
+ if (c >= 128) {
+ if ((c & 0xe0) == 0xc0)
+ c &= 0x1f;
+ else if ((c & 0xf0) == 0xe0)
+ c &= 0x0f;
+ else
+ c &= 0x07;
+ p++;
+ while (p < end && (p[0] & 0xc0) == 0x80) {
+ c = (c << 6) | (p[0] & 0x3f);
+ p++;
+ }
+ }
+ if (escaped) {
+ switch (c) {
+ case '\\':
+ break;
+ case 'n': /* \n is line feed */
+ c = 10;
+ break;
+
+ case 'x': /* \xNN, where NN is a 2-digit hexadecimal value */
+ if (p+2 > end)
+ return -1;
+ c = gsm_hex2_to_byte( (const char*)p );
+ if (c < 0)
+ return -1;
+ p += 2;
+ break;
+
+ case 'u': /* \uNNNN where NNNN is a 4-digiti hexadecimal value */
+ if (p + 4 > end)
+ return -1;
+ c = gsm_hex4_to_short( (const char*)p );
+ if (c < 0)
+ return -1;
+ p += 4;
+ break;
+
+ default: /* invalid escape, return -1 */
+ return -1;
+ }
+ escaped = 0;
+ }
+ else if (c == '\\')
+ {
+ escaped = 1;
+ continue;
+ }
+
+ /* now, try to write it to the destination */
+ if (c < 128) {
+ if (count < utf8len)
+ utf8[count] = (byte_t) c;
+ count += 1;
+ }
+ else if (c < 0x800) {
+ if (count < utf8len)
+ utf8[count] = (byte_t)(0xc0 | ((c >> 6) & 0x1f));
+ if (count+1 < utf8len)
+ utf8[count+1] = (byte_t)(0x80 | (c & 0x3f));
+ count += 2;
+ }
+ else {
+ if (count < utf8len)
+ utf8[count] = (byte_t)(0xc0 | ((c >> 12) & 0xf));
+ if (count+1 < utf8len)
+ utf8[count+1] = (byte_t)(0x80 | ((c >> 6) & 0x3f));
+ if (count+2 < utf8len)
+ utf8[count+2] = (byte_t)(0x80 | (c & 0x3f));
+ count += 3;
+ }
+ }
+
+ if (escaped) /* bad final escape */
+ return -1;
+
+ return count;
+}
+
+/* to convert utf-8 to a message string, we only need to deal with control characters
+ * and that's it */
+int sms_utf8_to_message_str( const unsigned char* utf8, int utf8len, char* str, int strlen )
+{
+ cbytes_t p = utf8;
+ cbytes_t end = p + utf8len;
+ int count = 0;
+
+ while (p < end)
+ {
+ int c = p[0];
+ int escape = 0;
+
+ /* read the value from the string */
+ p += 1;
+ if (c >= 128) {
+ if ((c & 0xe0) == 0xc0)
+ c &= 0x1f;
+ else if ((c & 0xf0) == 0xe0)
+ c &= 0x0f;
+ else
+ c &= 0x07;
+ p++;
+ while (p < end && (p[0] & 0xc0) == 0x80) {
+ c = (c << 6) | (p[0] & 0x3f);
+ p++;
+ }
+ }
+
+ if (c < ' ') {
+ escape = 1;
+ if (c == '\n') {
+ c = 'n';
+ escape = 2;
+ }
+ }
+ else if (c == '\\')
+ escape = 2;
+
+ switch (escape) {
+ case 0:
+ if (c < 128) {
+ if (count < strlen)
+ str[count] = (char) c;
+ count += 1;
+ }
+ else if (c < 0x800) {
+ if (count < strlen)
+ str[count] = (byte_t)(0xc0 | ((c >> 6) & 0x1f));
+ if (count+1 < strlen)
+ str[count+1] = (byte_t)(0x80 | (c & 0x3f));
+ count += 2;
+ }
+ else {
+ if (count < strlen)
+ str[count] = (byte_t)(0xc0 | ((c >> 12) & 0xf));
+ if (count+1 < strlen)
+ str[count+1] = (byte_t)(0x80 | ((c >> 6) & 0x3f));
+ if (count+2 < strlen)
+ str[count+2] = (byte_t)(0x80 | (c & 0x3f));
+ count += 3;
+ }
+ break;
+
+ case 1:
+ if (count+3 < strlen) {
+ str[count+0] = '\\';
+ str[count+1] = 'x';
+ gsm_hex_from_byte(str + count + 2, c);
+ }
+ count += 4;
+ break;
+
+ default:
+ if (count+2 < strlen) {
+ str[count+0] = '\\';
+ str[count+1] = (char) c;
+ }
+ count += 2;
+ }
+ }
+ return count;
+}
+
+
+/** TIMESTAMPS
+ **/
+void
+sms_timestamp_now( SmsTimeStamp stamp )
+{
+ time_t now_time = time(NULL);
+ struct tm gm = *(gmtime(&now_time));
+ struct tm local = *(localtime(&now_time));
+ int tzdiff = 0;
+
+ stamp->data[0] = gsm_int_to_bcdi( local.tm_year % 100 );
+ stamp->data[1] = gsm_int_to_bcdi( local.tm_mon+1 );
+ stamp->data[2] = gsm_int_to_bcdi( local.tm_mday );
+ stamp->data[3] = gsm_int_to_bcdi( local.tm_hour );
+ stamp->data[4] = gsm_int_to_bcdi( local.tm_min );
+ stamp->data[5] = gsm_int_to_bcdi( local.tm_sec );
+
+ tzdiff = (local.tm_hour*4 + local.tm_min/15) - (gm.tm_hour*4 + gm.tm_min/15);
+ if (local.tm_yday > gm.tm_yday)
+ tzdiff += 24*4;
+ else if (local.tm_yday < gm.tm_yday)
+ tzdiff -= 24*4;
+
+ stamp->data[6] = gsm_int_to_bcdi( tzdiff >= 0 ? tzdiff : -tzdiff );
+ if (tzdiff < 0)
+ stamp->data[6] |= 0x08;
+}
+
+int
+sms_timestamp_to_tm( SmsTimeStamp stamp, struct tm* tm )
+{
+ int tzdiff;
+
+ tm->tm_year = gsm_int_from_bcdi( stamp->data[0] );
+ if (tm->tm_year < 50)
+ tm->tm_year += 100;
+ tm->tm_mon = gsm_int_from_bcdi( stamp->data[1] ) -1;
+ tm->tm_mday = gsm_int_from_bcdi( stamp->data[2] );
+ tm->tm_hour = gsm_int_from_bcdi( stamp->data[3] );
+ tm->tm_min = gsm_int_from_bcdi( stamp->data[4] );
+ tm->tm_sec = gsm_int_from_bcdi( stamp->data[5] );
+
+ tm->tm_isdst = -1;
+
+ tzdiff = gsm_int_from_bcdi( stamp->data[6] & 0xf7 );
+ if (stamp->data[6] & 0x8)
+ tzdiff = -tzdiff;
+
+ return tzdiff;
+}
+
+static void
+gsm_rope_add_timestamp( GsmRope rope, const SmsTimeStampRec* ts )
+{
+ gsm_rope_add( rope, ts->data, 7 );
+}
+
+
+/** SMS ADDRESSES
+ **/
+
+int
+sms_address_from_str( SmsAddress address, const char* src, int srclen )
+{
+ const char* end = src + srclen;
+ int shift = 0, len = 0;
+ bytes_t data = address->data;
+
+ address->len = 0;
+ address->toa = 0x81;
+
+ if (src >= end)
+ return -1;
+
+ if ( src[0] == '+' ) {
+ address->toa = 0x91;
+ if (++src == end)
+ goto Fail;
+ }
+
+ memset( address->data, 0, sizeof(address->data) );
+
+ shift = 0;
+
+ while (src < end) {
+ int c = *src++ - '0';
+
+ if ( (unsigned)c >= 10 ||
+ data >= address->data + sizeof(address->data) )
+ goto Fail;
+
+ data[0] |= c << shift;
+ len += 1;
+ shift += 4;
+ if (shift == 8) {
+ shift = 0;
+ data += 1;
+ }
+ }
+ if (shift != 0)
+ data[0] |= 0xf0;
+
+ address->len = len;
+ return 0;
+
+Fail:
+ return -1;
+}
+
+int
+sms_address_to_str( SmsAddress address, char* str, int strlen )
+{
+ static const char dialdigits[16] = "0123456789*#,N%";
+ int n, count = 0;
+
+ if (address->toa == 0x91) {
+ if (count < strlen)
+ str[count] = '+';
+ count++;
+ }
+ for (n = 0; n < address->len; n += 2)
+ {
+ int c = address->data[n/2];
+
+ if (count < strlen)
+ str[count] = dialdigits[c & 0xf];
+ count += 1;
+
+ if (n+1 > address->len)
+ break;
+
+ if (count < strlen)
+ str[count] = dialdigits[(c >> 4) & 0xf];
+ count += 1;
+ }
+ return count;
+}
+
+int
+sms_address_from_bytes( SmsAddress address, const unsigned char* buf, int buflen )
+{
+ int len = sizeof(address->data), num_digits;
+
+ if (buflen < 2)
+ return -1;
+
+ address->len = num_digits = buf[0];
+ address->toa = buf[1];
+
+ len = (num_digits+1)/2;
+ if ( len > sizeof(address->data) )
+ return -1;
+
+ memcpy( address->data, buf+2, len );
+ return 0;
+}
+
+int
+sms_address_to_bytes( SmsAddress address, unsigned char* buf, int bufsize )
+{
+ int len = (address->len + 1)/2 + 2;
+
+ if (buf == NULL)
+ bufsize = 0;
+
+ if (bufsize < 1) goto Exit;
+ buf[0] = address->len;
+
+ if (bufsize < 2) goto Exit;
+ buf[1] = address->toa;
+
+ buf += 2;
+ bufsize -= 2;
+ if (bufsize > len-2)
+ bufsize = len - 2;
+
+ memcpy( buf, address->data, bufsize );
+Exit:
+ return len;
+}
+
+int
+sms_address_from_hex ( SmsAddress address, const char* hex, int hexlen )
+{
+ const char* hexend = hex + hexlen;
+ int nn, len, num_digits;
+
+ if (hexlen < 4)
+ return -1;
+
+ address->len = num_digits = gsm_hex2_to_byte( hex );
+ address->toa = gsm_hex2_to_byte( hex+2 );
+ hex += 4;
+
+ len = (num_digits + 1)/2;
+ if (hex + len*2 > hexend)
+ return -1;
+
+ for ( nn = 0; nn < len; nn++ )
+ address->data[nn] = gsm_hex2_to_byte( hex + nn*2 );
+
+ return 0;
+}
+
+int
+sms_address_to_hex ( SmsAddress address, char* hex, int hexlen )
+{
+ int len = (address->len + 1)/2 + 2;
+ int nn;
+
+ if (hex == NULL)
+ hexlen = 0;
+
+ if (hexlen < 2) goto Exit;
+ gsm_hex_from_byte( hex, address->len );
+ if (hexlen < 4) goto Exit;
+ gsm_hex_from_byte( hex+2, address->toa );
+ hex += 4;
+ hexlen -= 4;
+ if ( hexlen > 2*(len - 2) )
+ hexlen = (len - 2)/2;
+
+ for ( nn = 0; nn < hexlen; nn += 2 )
+ gsm_hex_from_byte( hex+nn, address->data[nn/2] );
+
+Exit:
+ return len*2;
+}
+
+static void
+gsm_rope_add_address( GsmRope rope, const SmsAddressRec* addr )
+{
+ gsm_rope_add_c( rope, addr->len );
+ gsm_rope_add_c( rope, addr->toa );
+ gsm_rope_add( rope, addr->data, (addr->len+1)/2 );
+ if (addr->len & 1) {
+ if (!rope->error && rope->data != NULL)
+ rope->data[ rope->pos-1 ] |= 0xf0;
+ }
+}
+
+static int
+sms_address_eq( const SmsAddressRec* addr1, const SmsAddressRec* addr2 )
+{
+ if ( addr1->toa != addr2->toa ||
+ addr1->len != addr2->len )
+ return 0;
+
+ return ( !memcmp( addr1->data, addr2->data, addr1->len ) );
+}
+
+/** SMS PARSER
+ **/
+static int
+sms_get_byte( cbytes_t *pcur, cbytes_t end )
+{
+ cbytes_t cur = *pcur;
+ int result = -1;
+
+ if (cur < end) {
+ result = cur[0];
+ *pcur = cur + 1;
+ }
+ return result;
+}
+
+/* parse a service center address, returns -1 in case of error */
+static int
+sms_get_sc_address( cbytes_t *pcur,
+ cbytes_t end,
+ SmsAddress address )
+{
+ cbytes_t cur = *pcur;
+ int result = -1;
+
+ if (cur < end) {
+ int len = cur[0];
+ int dlen, adjust = 0;
+
+ cur += 1;
+
+ if (len == 0) { /* empty address */
+ address->len = 0;
+ address->toa = 0x00;
+ result = 0;
+ goto Exit;
+ }
+
+ if (cur + len > end) {
+ goto Exit;
+ }
+
+ address->toa = *cur++;
+ len -= 1;
+ result = 0;
+
+ for (dlen = 0; dlen < len; dlen+=1)
+ {
+ int c = cur[dlen];
+ int v;
+
+ adjust = 0;
+ if (dlen >= sizeof(address->data)) {
+ result = -1;
+ break;
+ }
+
+ v = (c & 0xf);
+ if (v >= 0xe)
+ break;
+
+ adjust = 1;
+ address->data[dlen] = (byte_t) c;
+
+ v = (c >> 4) & 0xf;
+ if (v >= 0xe) {
+ break;
+ }
+ }
+ address->len = 2*dlen + adjust;
+ }
+Exit:
+ if (!result)
+ *pcur = cur;
+
+ return result;
+}
+
+static int
+sms_skip_sc_address( cbytes_t *pcur,
+ cbytes_t end )
+{
+ cbytes_t cur = *pcur;
+ int result = -1;
+ int len;
+
+ if (cur >= end)
+ goto Exit;
+
+ len = cur[0];
+ cur += 1 + len;
+ if (cur > end)
+ goto Exit;
+
+ *pcur = cur;
+ result = 0;
+Exit:
+ return result;
+}
+
+/* parse a sender/receiver address, returns -1 in case of error */
+static int
+sms_get_address( cbytes_t *pcur,
+ cbytes_t end,
+ SmsAddress address )
+{
+ cbytes_t cur = *pcur;
+ int result = -1;
+ int len, dlen;
+
+ if (cur >= end)
+ goto Exit;
+
+ dlen = *cur++;
+
+ if (dlen == 0) {
+ address->len = 0;
+ address->toa = 0;
+ result = 0;
+ goto Exit;
+ }
+
+ if (cur + 1 + (dlen+1)/2 > end)
+ goto Exit;
+
+ address->len = dlen;
+ address->toa = *cur++;
+
+ len = (dlen + 1)/2;
+ if (len > sizeof(address->data))
+ goto Exit;
+
+ memcpy( address->data, cur, len );
+ cur += len;
+ result = 0;
+
+Exit:
+ if (!result)
+ *pcur = cur;
+
+ return result;
+}
+
+static int
+sms_skip_address( cbytes_t *pcur,
+ cbytes_t end )
+{
+ cbytes_t cur = *pcur;
+ int result = -1;
+ int dlen;
+
+ if (cur + 2 > end)
+ goto Exit;
+
+ dlen = cur[0];
+ cur += 2 + (dlen + 1)/2;
+ if (cur > end)
+ goto Exit;
+
+ result = 0;
+Exit:
+ return result;
+}
+
+/* parse a service center timestamp */
+static int
+sms_get_timestamp( cbytes_t *pcur,
+ cbytes_t end,
+ SmsTimeStamp ts )
+{
+ cbytes_t cur = *pcur;
+
+ if (cur + 7 > end)
+ return -1;
+
+ memcpy( ts->data, cur, 7 );
+ *pcur = cur + 7;
+ return 0;
+}
+
+static int
+sms_skip_timestamp( cbytes_t *pcur,
+ cbytes_t end )
+{
+ cbytes_t cur = *pcur;
+
+ if (cur + 7 > end)
+ return -1;
+
+ *pcur = cur + 7;
+ return 0;
+}
+
+
+static int
+sms_skip_validity_period( cbytes_t *pcur,
+ cbytes_t end,
+ int mtiByte )
+{
+ cbytes_t cur = *pcur;
+
+ switch ((mtiByte >> 3) & 3) {
+ case 1: /* relative format */
+ cur += 1;
+ break;
+
+ case 2: /* enhanced format */
+ case 3: /* absolute format */
+ cur += 7;
+ }
+ if (cur > end)
+ return -1;
+
+ *pcur = cur;
+ return 0;
+}
+
+/** SMS PDU
+ **/
+
+typedef struct SmsPDURec {
+ bytes_t base;
+ bytes_t end;
+ bytes_t tpdu;
+} SmsPDURec;
+
+void
+smspdu_free( SmsPDU pdu )
+{
+ if (pdu) {
+ free( pdu->base );
+ pdu->base = NULL;
+ pdu->end = NULL;
+ pdu->tpdu = NULL;
+ }
+}
+
+SmsPduType
+smspdu_get_type( SmsPDU pdu )
+{
+ cbytes_t data = pdu->tpdu;
+ cbytes_t end = pdu->end;
+ int mtiByte = sms_get_byte(&data, end);
+
+ switch (mtiByte & 3) {
+ case 0: return SMS_PDU_DELIVER;
+ case 1: return SMS_PDU_SUBMIT;
+ case 2: return SMS_PDU_STATUS_REPORT;
+ default: return SMS_PDU_INVALID;
+ }
+}
+
+int
+smspdu_get_sender_address( SmsPDU pdu, SmsAddress address )
+{
+ cbytes_t data = pdu->tpdu;
+ cbytes_t end = pdu->end;
+ int mtiByte = sms_get_byte(&data, end);
+
+ switch (mtiByte & 3) {
+ case 0: /* SMS_PDU_DELIVER; */
+ return sms_get_sc_address( &data, end, address );
+
+ default: return -1;
+ }
+}
+
+int
+smspdu_get_sc_timestamp( SmsPDU pdu, SmsTimeStamp ts )
+{
+ cbytes_t data = pdu->tpdu;
+ cbytes_t end = pdu->end;
+ int mtiByte = sms_get_byte( &data, end );
+
+ switch (mtiByte & 3) {
+ case 0: /* SMS_PDU_DELIVER */
+ {
+ SmsAddressRec address;
+
+ if ( sms_get_sc_address( &data, end, &address ) < 0 )
+ return -1;
+
+ data += 2; /* skip protocol identifer + coding scheme */
+
+ return sms_get_timestamp( &data, end, ts );
+ }
+
+ default: return -1;
+ }
+}
+
+int
+smspdu_get_receiver_address( SmsPDU pdu, SmsAddress address )
+{
+ cbytes_t data = pdu->tpdu;
+ cbytes_t end = pdu->end;
+ int mtiByte = sms_get_byte( &data, end );
+
+ switch (mtiByte & 3) {
+ case 1: /* SMS_PDU_SUBMIT */
+ {
+ data += 1; /* skip message reference */
+ return sms_get_address( &data, end, address );
+ }
+
+ default: return -1;
+ }
+}
+
+typedef enum {
+ SMS_CODING_SCHEME_UNKNOWN = 0,
+ SMS_CODING_SCHEME_GSM7,
+ SMS_CODING_SCHEME_UCS2
+
+} SmsCodingScheme;
+
+/* see TS 23.038 Section 5 for details */
+static SmsCodingScheme
+sms_get_coding_scheme( cbytes_t *pcur,
+ cbytes_t end )
+{
+ cbytes_t cur = *pcur;
+ int dataCoding;
+
+ if (cur >= end)
+ return SMS_CODING_SCHEME_UNKNOWN;
+
+ dataCoding = *cur++;
+ *pcur = cur;
+
+ switch (dataCoding >> 4) {
+ case 0x00:
+ case 0x02:
+ case 0x03:
+ return SMS_CODING_SCHEME_GSM7;
+
+ case 0x01:
+ if (dataCoding == 0x10) return SMS_CODING_SCHEME_GSM7;
+ if (dataCoding == 0x11) return SMS_CODING_SCHEME_UCS2;
+ break;
+
+ case 0x04: case 0x05: case 0x06: case 0x07:
+ if (dataCoding & 0x20) return SMS_CODING_SCHEME_UNKNOWN; /* compressed 7-bits */
+ if (((dataCoding >> 2) & 3) == 0) return SMS_CODING_SCHEME_GSM7;
+ if (((dataCoding >> 2) & 3) == 2) return SMS_CODING_SCHEME_UCS2;
+ break;
+
+ case 0xF:
+ if (!(dataCoding & 4)) return SMS_CODING_SCHEME_GSM7;
+ break;
+ }
+ return SMS_CODING_SCHEME_UNKNOWN;
+}
+
+
+/* see TS 23.040 section 9.2.3.24 for details */
+static int
+sms_get_text_utf8( cbytes_t *pcur,
+ cbytes_t end,
+ int hasUDH,
+ SmsCodingScheme coding,
+ GsmRope rope )
+{
+ cbytes_t cur = *pcur;
+ int result = -1;
+ int len;
+
+ if (cur >= end)
+ goto Exit;
+
+ len = *cur++;
+
+ /* skip user data header if any */
+ if ( hasUDH )
+ {
+ int hlen;
+
+ if (cur >= end)
+ goto Exit;
+
+ hlen = *cur++;
+ if (cur + hlen > end)
+ goto Exit;
+
+ cur += hlen;
+
+ if (coding == SMS_CODING_SCHEME_GSM7)
+ len -= 2*(hlen+1);
+ else
+ len -= hlen+1;
+
+ if (len < 0)
+ goto Exit;
+ }
+
+ /* switch the user data header if any */
+ if (coding == SMS_CODING_SCHEME_GSM7)
+ {
+ int count = utf8_from_gsm7( cur, 0, len, NULL );
+
+ if (rope != NULL)
+ {
+ bytes_t dst = gsm_rope_reserve( rope, count );
+ if (dst != NULL)
+ utf8_from_gsm7( cur, 0, len, dst );
+ }
+ cur += (len+1)/2;
+ }
+ else if (coding == SMS_CODING_SCHEME_UCS2)
+ {
+ int count = ucs2_to_utf8( cur, len/2, NULL );
+
+ if (rope != NULL)
+ {
+ bytes_t dst = gsm_rope_reserve( rope, count );
+ if (dst != NULL)
+ ucs2_to_utf8( cur, len/2, dst );
+ }
+ cur += len;
+ }
+ result = 0;
+
+Exit:
+ if (!result)
+ *pcur = cur;
+
+ return result;
+}
+
+/* get the message embedded in a SMS PDU as a utf8 byte array, returns the length of the message in bytes */
+/* or -1 in case of error */
+int
+smspdu_get_text_message( SmsPDU pdu, unsigned char* utf8, int utf8len )
+{
+ cbytes_t data = pdu->tpdu;
+ cbytes_t end = pdu->end;
+ int mtiByte = sms_get_byte( &data, end );
+
+ switch (mtiByte & 3) {
+ case 0: /* SMS_PDU_DELIVER */
+ {
+ SmsAddressRec address;
+ SmsTimeStampRec timestamp;
+ SmsCodingScheme coding;
+ GsmRopeRec rope[1];
+ int result;
+
+ if ( sms_get_sc_address( &data, end, &address ) < 0 )
+ goto Fail;
+
+ data += 1; /* skip protocol identifier */
+ coding = sms_get_coding_scheme( &data, end );
+ if (coding == SMS_CODING_SCHEME_UNKNOWN)
+ goto Fail;
+
+ if ( sms_get_timestamp( &data, end, &timestamp ) < 0 )
+ goto Fail;
+
+ if ( sms_get_text_utf8( &data, end, (mtiByte & 0x40), coding, rope ) < 0 )
+ goto Fail;
+
+ result = rope->pos;
+ if (utf8len > result)
+ utf8len = result;
+
+ if (utf8len > 0)
+ memcpy( utf8, rope->data, utf8len );
+
+ gsm_rope_done( rope );
+ return result;
+ }
+
+ case 1: /* SMS_PDU_SUBMIT */
+ {
+ SmsAddressRec address;
+ SmsCodingScheme coding;
+ GsmRopeRec rope[1];
+ int result;
+
+ data += 1; /* message reference */
+
+ if ( sms_get_address( &data, end, &address ) < 0 )
+ goto Fail;
+
+ data += 1; /* skip protocol identifier */
+ coding = sms_get_coding_scheme( &data, end );
+ if (coding == SMS_CODING_SCHEME_UNKNOWN)
+ goto Fail;
+
+ gsm_rope_init_alloc( rope, 0 );
+ if ( sms_get_text_utf8( &data, end, (mtiByte & 0x40), coding, rope ) < 0 ) {
+ gsm_rope_done( rope );
+ goto Fail;
+ }
+
+ result = rope->pos;
+ if (utf8len > result)
+ utf8len = result;
+
+ if (utf8len > 0)
+ memcpy( utf8, rope->data, utf8len );
+
+ gsm_rope_done( rope );
+ return result;
+ }
+ }
+Fail:
+ return -1;
+}
+
+static cbytes_t
+smspdu_get_user_data_ref( SmsPDU pdu )
+{
+ cbytes_t data = pdu->tpdu;
+ cbytes_t end = pdu->end;
+ int mtiByte = sms_get_byte( &data, end );
+ int len;
+
+ /* if there is no user-data-header, there is no message reference here */
+ if ((mtiByte & 0x40) == 0)
+ goto Fail;
+
+ switch (mtiByte & 3) {
+ case 0: /* SMS_PDU_DELIVER */
+ if ( sms_skip_address( &data, end ) < 0 )
+ goto Fail;
+
+ data += 2; /* skip protocol identifier + coding scheme */
+
+ if ( sms_skip_timestamp( &data, end ) < 0 )
+ goto Fail;
+
+ break;
+
+ case 1: /* SMS_PDU_SUBMIT */
+ data += 1; /* skip message reference */
+
+ if ( sms_skip_address( &data, end ) < 0 )
+ goto Fail;
+
+ data += 2; /* protocol identifier + oding schene */
+ if ( sms_skip_validity_period( &data, end, mtiByte ) < 0 )
+ goto Fail;
+
+ break;
+
+ default:
+ goto Fail;
+ }
+
+ /* skip user-data length */
+ if (data+1 >= end)
+ goto Fail;
+
+ len = data[1];
+ data += 2;
+
+ while (len >= 2 && data + 2 <= end) {
+ int htype = data[0];
+ int hlen = data[1];
+
+ if (htype == 00 && hlen == 3 && data + 5 <= end) {
+ return data + 2;
+ }
+
+ data += hlen;
+ len -= hlen - 2;
+ }
+Fail:
+ return NULL;
+}
+
+int
+smspdu_get_ref( SmsPDU pdu )
+{
+ cbytes_t user_ref = smspdu_get_user_data_ref( pdu );
+
+ if (user_ref != NULL)
+ {
+ return user_ref[0];
+ }
+ else
+ {
+ cbytes_t data = pdu->tpdu;
+ cbytes_t end = pdu->end;
+ int mtiByte = sms_get_byte( &data, end );
+
+ if ((mtiByte & 3) == 1) {
+ /* try to extract directly the reference for a SMS-SUBMIT */
+ if (data < end)
+ return data[0];
+ }
+ }
+ return -1;
+}
+
+int
+smspdu_get_max_index( SmsPDU pdu )
+{
+ cbytes_t user_ref = smspdu_get_user_data_ref( pdu );
+
+ if (user_ref != NULL) {
+ return user_ref[1];
+ } else {
+ return 1;
+ }
+}
+
+int
+smspdu_get_cur_index( SmsPDU pdu )
+{
+ cbytes_t user_ref = smspdu_get_user_data_ref( pdu );
+
+ if (user_ref != NULL) {
+ return user_ref[2] - 1;
+ } else {
+ return 0;
+ }
+}
+
+
+static void
+gsm_rope_add_sms_user_header( GsmRope rope,
+ int ref_number,
+ int pdu_count,
+ int pdu_index )
+{
+ gsm_rope_add_c( rope, 0x05 ); /* total header length == 5 bytes */
+ gsm_rope_add_c( rope, 0x00 ); /* element id: concatenated message reference number */
+ gsm_rope_add_c( rope, 0x03 ); /* element len: 3 bytes */
+ gsm_rope_add_c( rope, (byte_t)ref_number ); /* reference number */
+ gsm_rope_add_c( rope, (byte_t)pdu_count ); /* max pdu index */
+ gsm_rope_add_c( rope, (byte_t)pdu_index+1 ); /* current pdu index */
+}
+
+/* write a SMS-DELIVER PDU into a rope */
+static void
+gsm_rope_add_sms_deliver_pdu( GsmRope rope,
+ cbytes_t utf8,
+ int utf8len,
+ int use_gsm7,
+ const SmsAddressRec* sender_address,
+ const SmsTimeStampRec* timestamp,
+ int ref_num,
+ int pdu_count,
+ int pdu_index)
+{
+ int coding;
+ int mtiByte = 0x20; /* message type - SMS DELIVER */
+
+ if (pdu_count > 1)
+ mtiByte |= 0x40; /* user data header indicator */
+
+ gsm_rope_add_c( rope, 0 ); /* no SC Address */
+ gsm_rope_add_c( rope, mtiByte ); /* message type - SMS-DELIVER */
+ gsm_rope_add_address( rope, sender_address );
+ gsm_rope_add_c( rope, 0 ); /* protocol identifier */
+
+ /* data coding scheme - GSM 7 bits / no class - or - 16-bit UCS2 / class 1 */
+ coding = (use_gsm7 ? 0x00 : 0x09);
+
+ gsm_rope_add_c( rope, coding ); /* data coding scheme */
+ gsm_rope_add_timestamp( rope, timestamp ); /* service center timestamp */
+
+ if (use_gsm7) {
+ bytes_t dst;
+ int count = utf8_to_gsm7( utf8, utf8len, NULL, 0 );
+ int pad = 0;
+
+ assert( count <= MAX_USER_DATA_SEPTETS - USER_DATA_HEADER_SIZE );
+
+ if (pdu_count > 1)
+ {
+ int headerBits = 6*8; /* 6 is size of header in bytes */
+ int headerSeptets = headerBits / 7;
+ if (headerBits % 7 > 0)
+ headerSeptets += 1;
+
+ pad = headerSeptets*7 - headerBits;
+
+ gsm_rope_add_c( rope, count + headerSeptets );
+ gsm_rope_add_sms_user_header(rope, ref_num, pdu_count, pdu_index);
+ }
+ else
+ gsm_rope_add_c( rope, count );
+
+ count = (count*7+pad+7)/8; /* convert to byte count */
+
+ dst = gsm_rope_reserve( rope, count );
+ if (dst != NULL) {
+ utf8_to_gsm7( utf8, utf8len, dst, pad );
+ }
+ } else {
+ bytes_t dst;
+ int count = utf8_to_ucs2( utf8, utf8len, NULL );
+
+ assert( count*2 <= MAX_USER_DATA_BYTES - USER_DATA_HEADER_SIZE );
+
+ if (pdu_count > 1)
+ {
+ gsm_rope_add_c( rope, count*2 + 6 );
+ gsm_rope_add_sms_user_header( rope, ref_num, pdu_count, pdu_index );
+ }
+ else
+ gsm_rope_add_c( rope, count*2 );
+
+ gsm_rope_add_c( rope, count*2 );
+ dst = gsm_rope_reserve( rope, count*2 );
+ if (dst != NULL) {
+ utf8_to_ucs2( utf8, utf8len, dst );
+ }
+ }
+}
+
+
+static SmsPDU
+smspdu_create_deliver( cbytes_t utf8,
+ int utf8len,
+ int use_gsm7,
+ const SmsAddressRec* sender_address,
+ const SmsTimeStampRec* timestamp,
+ int ref_num,
+ int pdu_count,
+ int pdu_index )
+{
+ SmsPDU p;
+ GsmRopeRec rope[1];
+ int size;
+
+ p = calloc( sizeof(*p), 1 );
+ if (!p) goto Exit;
+
+ gsm_rope_init( rope );
+ gsm_rope_add_sms_deliver_pdu( rope, utf8, utf8len, use_gsm7,
+ sender_address, timestamp,
+ ref_num, pdu_count, pdu_index);
+ if (rope->error)
+ goto Fail;
+
+ gsm_rope_init_alloc( rope, rope->pos );
+
+ gsm_rope_add_sms_deliver_pdu( rope, utf8, utf8len, use_gsm7,
+ sender_address, timestamp,
+ ref_num, pdu_count, pdu_index );
+
+ p->base = gsm_rope_done_acquire( rope, &size );
+ if (p->base == NULL)
+ goto Fail;
+
+ p->end = p->base + size;
+ p->tpdu = p->base + 1;
+Exit:
+ return p;
+
+Fail:
+ free(p);
+ return NULL;
+}
+
+
+void
+smspdu_free_list( SmsPDU* pdus )
+{
+ if (pdus) {
+ int nn;
+ for (nn = 0; pdus[nn] != NULL; nn++)
+ smspdu_free( pdus[nn] );
+
+ free( pdus );
+ }
+}
+
+
+
+SmsPDU*
+smspdu_create_deliver_utf8( const unsigned char* utf8,
+ int utf8len,
+ const SmsAddressRec* sender_address,
+ const SmsTimeStampRec* timestamp )
+{
+ SmsTimeStampRec ts0;
+ int use_gsm7;
+ int count, block;
+ int num_pdus = 0;
+ int leftover = 0;
+ SmsPDU* list = NULL;
+
+ static unsigned char ref_num = 0;
+
+ if (timestamp == NULL) {
+ sms_timestamp_now( &ts0 );
+ timestamp = &ts0;
+ }
+
+ /* can we encode the message with the GSM 7-bit alphabet ? */
+ use_gsm7 = utf8_check_gsm7( utf8, utf8len );
+
+ /* count the number of SMS PDUs we'll need */
+ block = MAX_USER_DATA_SEPTETS - USER_DATA_HEADER_SIZE;
+
+ if (use_gsm7) {
+ count = utf8_to_gsm7( utf8, utf8len, NULL, 0 );
+ } else {
+ count = utf8_to_ucs2( utf8, utf8len, NULL );
+ block = MAX_USER_DATA_BYTES - USER_DATA_HEADER_SIZE;
+ }
+
+ num_pdus = count / block;
+ leftover = count - num_pdus*block;
+ if (leftover > 0)
+ num_pdus += 1;
+
+ list = calloc( sizeof(SmsPDU*), num_pdus + 1 );
+ if (list == NULL)
+ return NULL;
+
+ /* now create each SMS PDU */
+ {
+ cbytes_t src = utf8;
+ cbytes_t src_end = utf8 + utf8len;
+ int nn;
+
+ for (nn = 0; nn < num_pdus; nn++)
+ {
+ int skip = block;
+ cbytes_t src_next;
+
+ if (leftover > 0 && nn == num_pdus-1)
+ skip = leftover;
+
+ src_next = utf8_skip_gsm7( src, src_end, skip );
+
+ list[nn] = smspdu_create_deliver( src, src_next - src, use_gsm7, sender_address, timestamp,
+ ref_num, num_pdus, nn );
+ if (list[nn] == NULL)
+ goto Fail;
+
+ src = src_next;
+ }
+ }
+
+ ref_num++;
+ return list;
+
+Fail:
+ smspdu_free_list(list);
+ return NULL;
+}
+
+
+SmsPDU
+smspdu_create_from_hex( const char* hex, int hexlen )
+{
+ SmsPDU p;
+ cbytes_t data;
+
+ p = calloc( sizeof(*p), 1 );
+ if (!p) goto Exit;
+
+ p->base = malloc( (hexlen+1)/2 );
+ if (p->base == NULL) {
+ free(p);
+ p = NULL;
+ goto Exit;
+ }
+
+ if ( gsm_hex_to_bytes( (cbytes_t)hex, hexlen, p->base ) < 0 )
+ goto Fail;
+
+ p->end = p->base + (hexlen+1)/2;
+
+ data = p->base;
+ if ( sms_skip_sc_address( &data, p->end ) < 0 )
+ goto Fail;
+
+ p->tpdu = (bytes_t) data;
+
+Exit:
+ return p;
+
+Fail:
+ free(p->base);
+ free(p);
+ return NULL;
+}
+
+int
+smspdu_to_hex( SmsPDU pdu, char* hex, int hexlen )
+{
+ int result = (pdu->end - pdu->base)*2;
+ int nn;
+
+ if (hexlen > result)
+ hexlen = result;
+
+ for (nn = 0; nn*2 < hexlen; nn++) {
+ gsm_hex_from_byte( &hex[nn*2], pdu->base[nn] );
+ }
+ return result;
+}
+
+
+/** SMS SUBMIT RECEIVER
+ ** collects one or more SMS-SUBMIT PDUs to generate a single message to deliver
+ **/
+
+typedef struct SmsFragmentRec {
+ struct SmsFragmentRec* next;
+ SmsAddressRec from[1];
+ byte_t ref;
+ byte_t max;
+ byte_t count;
+ int index;
+ SmsPDU* pdus;
+
+} SmsFragmentRec, *SmsFragment;
+
+
+typedef struct SmsReceiverRec {
+ int last;
+ SmsFragment fragments;
+
+} SmsReceiverRec;
+
+
+static void
+sms_fragment_free( SmsFragment frag )
+{
+ int nn;
+
+ for (nn = 0; nn < frag->max; nn++) {
+ if (frag->pdus[nn] != NULL) {
+ smspdu_free( frag->pdus[nn] );
+ frag->pdus[nn] = NULL;
+ }
+ }
+ frag->pdus = NULL;
+ frag->count = 0;
+ frag->max = 0;
+ frag->index = 0;
+ free( frag );
+}
+
+static SmsFragment
+sms_fragment_alloc( SmsReceiver rec, const SmsAddressRec* from, int ref, int max )
+{
+ SmsFragment frag = calloc(sizeof(*frag) + max*sizeof(SmsPDU), 1 );
+
+ if (frag != NULL) {
+ frag->from[0] = from[0];
+ frag->ref = ref;
+ frag->max = max;
+ frag->pdus = (SmsPDU*)(frag + 1);
+ frag->index = ++rec->last;
+ }
+ return frag;
+}
+
+
+
+SmsReceiver sms_receiver_create( void )
+{
+ SmsReceiver rec = calloc(sizeof(*rec),1);
+ return rec;
+}
+
+void
+sms_receiver_destroy( SmsReceiver rec )
+{
+ while (rec->fragments) {
+ SmsFragment frag = rec->fragments;
+ rec->fragments = frag->next;
+ sms_fragment_free(frag);
+ }
+}
+
+static SmsFragment*
+sms_receiver_find_p( SmsReceiver rec, const SmsAddressRec* from, int ref )
+{
+ SmsFragment* pnode = &rec->fragments;
+ SmsFragment node;
+
+ for (;;) {
+ node = *pnode;
+ if (node == NULL)
+ break;
+ if (node->ref == ref && sms_address_eq( node->from, from ))
+ break;
+ pnode = &node->next;
+ }
+ return pnode;
+}
+
+static SmsFragment*
+sms_receiver_find_index_p( SmsReceiver rec, int index )
+{
+ SmsFragment* pnode = &rec->fragments;
+ SmsFragment node;
+
+ for (;;) {
+ node = *pnode;
+ if (node == NULL)
+ break;
+ if (node->index == index)
+ break;
+ pnode = &node->next;
+ }
+ return pnode;
+}
+
+int
+sms_receiver_add_submit_pdu( SmsReceiver rec, SmsPDU submit_pdu )
+{
+ SmsAddressRec from[1];
+ int ref, max, cur;
+ SmsFragment* pnode;
+ SmsFragment frag;
+
+ if ( smspdu_get_receiver_address( submit_pdu, from ) < 0 ) {
+ D( "%s: could not extract receiver address\n", __FUNCTION__ );
+ return -1;
+ }
+
+ ref = smspdu_get_ref( submit_pdu );
+ if (ref < 0) {
+ D( "%s: could not extract message reference from pdu\n", __FUNCTION__ );
+ return -1;
+ }
+ max = smspdu_get_max_index( submit_pdu );
+ if (max < 0) {
+ D( "%s: invalid max fragment value: %d should be >= 1\n",
+ __FUNCTION__, max );
+ return -1;
+ }
+ pnode = sms_receiver_find_p( rec, from, ref );
+ frag = *pnode;
+ if (frag == NULL) {
+ frag = sms_fragment_alloc( rec, from, ref, max );
+ if (frag == NULL) {
+ D("%s: not enough memory to allocate new fragment\n", __FUNCTION__ );
+ return -1;
+ }
+ if (D_ACTIVE) {
+ char tmp[32];
+ int len;
+
+ len = sms_address_to_str( from, tmp, sizeof(tmp) );
+ if (len < 0) {
+ strcpy( tmp, "<unknown>" );
+ len = strlen(tmp);
+ }
+ D("%s: created SMS index %d, from %.*s, ref %d, max %d\n", __FUNCTION__,
+ frag->index, len, tmp, frag->ref, frag->max);
+ }
+ *pnode = frag;
+ }
+
+ cur = smspdu_get_cur_index( submit_pdu );
+ if (cur < 0) {
+ D("%s: SMS fragment index is too small: %d should be >= 1\n", __FUNCTION__, cur+1 );
+ return -1;
+ }
+ if (cur >= max) {
+ D("%s: SMS fragment index is too large (%d >= %d)\n", __FUNCTION__, cur, max);
+ return -1;
+ }
+ if ( frag->pdus[cur] != NULL ) {
+ D("%s: receiving duplicate SMS fragment for %d/%d, ref=%d, discarding old one\n",
+ __FUNCTION__, cur+1, max, ref);
+ smspdu_free( frag->pdus[cur] );
+ frag->count -= 1;
+ }
+ frag->pdus[cur] = submit_pdu;
+ frag->count += 1;
+
+ if (frag->count >= frag->max) {
+ /* yes, we received all fragments for this SMS */
+ D( "%s: SMS index %d, received all %d fragments\n", __FUNCTION__, frag->index, frag->count );
+ return frag->index;
+ }
+ else {
+ /* still waiting for more */
+ D( "%s: SMS index %d, received %d/%d, waiting for %d more\n", __FUNCTION__,
+ frag->index, cur+1, max, frag->max - frag->count );
+ return 0;
+ }
+}
+
+
+int
+sms_receiver_get_text_message( SmsReceiver rec, int index, bytes_t utf8, int utf8len )
+{
+ SmsFragment* pnode = sms_receiver_find_index_p( rec, index );
+ SmsFragment frag = *pnode;
+ int nn, total;
+
+ if (frag == NULL) {
+ D( "%s: invalid SMS index %d\n", __FUNCTION__, index );
+ return -1;
+ }
+ if (frag->count != frag->max) {
+ D( "%s: SMS index %d still needs %d fragments\n", __FUNCTION__,
+ frag->index, frag->max - frag->count );
+ return -1;
+ }
+ /* get the size of all combined text */
+ total = 0;
+ for ( nn = 0; nn < frag->count; nn++ ) {
+ int partial;
+ if (utf8 && utf8len > 0) {
+ partial = smspdu_get_text_message( frag->pdus[nn], utf8, utf8len );
+ utf8 += partial;
+ utf8len -= partial;
+ } else {
+ partial = smspdu_get_text_message( frag->pdus[nn], NULL, 0 );
+ }
+ total += partial;
+ }
+ return total;
+}
+
+
+static void
+sms_receiver_remove( SmsReceiver rec, int index )
+{
+ SmsFragment* pnode = sms_receiver_find_index_p( rec, index );
+ SmsFragment frag = *pnode;
+ if (frag != NULL) {
+ *pnode = frag->next;
+ sms_fragment_free(frag);
+ }
+}
+
+
+SmsPDU*
+sms_receiver_create_deliver( SmsReceiver rec, int index, const SmsAddressRec* from )
+{
+ SmsPDU* result = NULL;
+ SmsFragment* pnode = sms_receiver_find_index_p( rec, index );
+ SmsFragment frag = *pnode;
+ SmsTimeStampRec now[1];
+ int nn, total;
+ bytes_t utf8;
+ int utf8len;
+
+ if (frag == NULL) {
+ D( "%s: invalid SMS index %d\n", __FUNCTION__, index );
+ return NULL;
+ }
+ if (frag->count != frag->max) {
+ D( "%s: SMS index %d still needs %d fragments\n", __FUNCTION__,
+ frag->index, frag->max - frag->count );
+ return NULL;
+ }
+
+ /* get the combined text message */
+ utf8len = sms_receiver_get_text_message( rec, index, NULL, 0 );
+ if (utf8len < 0)
+ goto Exit;
+
+ utf8 = malloc( utf8len + 1 );
+ if (utf8 == NULL) {
+ D( "%s: not enough memory to allocate %d bytes\n",
+ __FUNCTION__, utf8len+1 );
+ goto Exit;
+ }
+
+ total = 0;
+ for ( nn = 0; nn < frag->count; nn++ ) {
+ total += smspdu_get_text_message( frag->pdus[nn], utf8 + total, utf8len - total );
+ }
+
+ sms_timestamp_now( now );
+
+ result = smspdu_create_deliver_utf8( utf8, utf8len, from, now );
+
+ free(utf8);
+
+Exit:
+ sms_receiver_remove( rec, index );
+ return result;
+}
+