/* 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; }