diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:32 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:32 -0800 |
commit | 8b23a6c7e1aee255004dd19098d4c2462b61b849 (patch) | |
tree | 7a4d682ba51f0ff0364c5ca2509f515bdaf96de9 /telephony/sms.c | |
parent | f721e3ac031f892af46f255a47d7f54a91317b30 (diff) | |
download | external_qemu-8b23a6c7e1aee255004dd19098d4c2462b61b849.zip external_qemu-8b23a6c7e1aee255004dd19098d4c2462b61b849.tar.gz external_qemu-8b23a6c7e1aee255004dd19098d4c2462b61b849.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'telephony/sms.c')
-rw-r--r-- | telephony/sms.c | 1655 |
1 files changed, 1655 insertions, 0 deletions
diff --git a/telephony/sms.c b/telephony/sms.c new file mode 100644 index 0000000..448eab4 --- /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/utils/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, ×tamp ) < 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; +} + |