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