| /* 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]; |
| if (str[count]) |
| 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; |
| } |
| |