From d7ab83a50f57b4f491337b38b54c4faa7a6407fe Mon Sep 17 00:00:00 2001 From: Lei Yu Date: Tue, 18 Jan 2011 14:54:19 -0800 Subject: cdma-sms: Add CDMA SMS Support --- src/cdma-smsutil.c | 735 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 735 insertions(+) create mode 100644 src/cdma-smsutil.c (limited to 'src/cdma-smsutil.c') diff --git a/src/cdma-smsutil.c b/src/cdma-smsutil.c new file mode 100644 index 00000000..283e1ed2 --- /dev/null +++ b/src/cdma-smsutil.c @@ -0,0 +1,735 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2010-2011 Nokia Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include + +#include "cdma-smsutil.h" + +#define uninitialized_var(x) x = x + +enum cdma_sms_rec_flag { + CDMA_SMS_REC_FLAG_MANDATORY = 1, +}; + +typedef gboolean (*rec_handler)(const guint8 *, guint8, void *); + +struct simple_iter { + guint8 max; + const guint8 *pdu; + guint8 pos; + guint8 id; + guint8 len; + const guint8 *data; +}; + +static void simple_iter_init(struct simple_iter *iter, + const guint8 *pdu, guint8 len) +{ + iter->pdu = pdu; + iter->max = len; + iter->pos = 0; + iter->id = 0; + iter->len = 0; + iter->data = NULL; +} + +static gboolean simple_iter_next(struct simple_iter *iter) +{ + const guint8 *pdu = iter->pdu + iter->pos; + const guint8 *end = iter->pdu + iter->max; + guint8 id; + guint8 len; + + if (pdu == end) + return FALSE; + + id = *pdu; + pdu++; + + if (pdu == end) + return FALSE; + + len = *pdu++; + + if (pdu + len > end) + return FALSE; + + iter->id = id; + iter->len = len; + iter->data = pdu; + + iter->pos = pdu + len - iter->pdu; + + return TRUE; +} + +static guint8 simple_iter_get_id(struct simple_iter *iter) +{ + return iter->id; +} + +static guint8 simple_iter_get_length(struct simple_iter *iter) +{ + return iter->len; +} + +static const guint8 *simple_iter_get_data(struct simple_iter *iter) +{ + return iter->data; +} + +static inline void set_bitmap(guint32 *bitmap, guint8 pos) +{ + *bitmap = *bitmap | (1 << pos); +} + +/* Unpacks the byte stream. The field has to be <= 8 bits. */ +static guint8 bit_field_unpack(const guint8 *buf, guint16 offset, guint8 nbit) +{ + guint8 bit_pos; + guint8 val = 0; + const guint8 *pdu; + + pdu = buf + (offset >> 3); + bit_pos = 8 - (offset & 0x7); + + /* Field to be extracted is within current byte */ + if (nbit <= bit_pos) + return (*pdu >> (bit_pos - nbit)) & ((1 << nbit) - 1); + + /* Field to be extracted crossing two bytes */ + val = *pdu & ((1 << bit_pos) - 1); + nbit -= bit_pos; + pdu++; + + return (val << nbit) | (*pdu >> (8 - nbit)); +} + +/* Convert CDMA DTMF digits into a string */ +static gboolean dtmf_to_ascii(char *buf, const guint8 *addr, + guint8 num_fields) +{ + /* + * Mapping from binary DTMF code to the digit it represents. + * As defined in Table 2.7.1.3.2.4-4 of 3GPP2 C.S0005-E v2.0. + * Note, 0 is NOT a valid value and not mapped to + * any valid DTMF digit. + */ + static const char dtmf_digits[13] = {0, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '*', '#'}; + guint8 index; + guint8 value; + + for (index = 0; index < num_fields; index++) { + if (addr[index] == 0 || addr[index] > 12) + return FALSE; /* Invalid digit in address field */ + + value = addr[index]; + buf[index] = dtmf_digits[value]; + } + + buf[index] = 0; /* Make it NULL terminated string */ + + return TRUE; +} + +const char *cdma_sms_address_to_string(const struct cdma_sms_address *addr) +{ + static char buf[CDMA_SMS_MAX_ADDR_FIELDS + 1]; + + /* TODO: Only support CDMA_SMS_DIGIT_MODE_4BIT_DTMF currently */ + switch (addr->digit_mode) { + case CDMA_SMS_DIGIT_MODE_4BIT_DTMF: + if (dtmf_to_ascii(buf, addr->address, + addr->num_fields) == TRUE) + return buf; + else + return NULL; + case CDMA_SMS_DIGIT_MODE_8BIT_ASCII: + return NULL; + } + + return NULL; +} + +/* Decode Teleservice ID */ +static gboolean cdma_sms_decode_teleservice(const guint8 *buf, guint8 len, + void *data) +{ + enum cdma_sms_teleservice_id *id = data; + + *id = bit_field_unpack(buf, 0, 8) << 8 | + bit_field_unpack(buf, 8, 8); + + switch (*id) { + case CDMA_SMS_TELESERVICE_ID_CMT91: + case CDMA_SMS_TELESERVICE_ID_WPT: + case CDMA_SMS_TELESERVICE_ID_WMT: + case CDMA_SMS_TELESERVICE_ID_VMN: + case CDMA_SMS_TELESERVICE_ID_WAP: + case CDMA_SMS_TELESERVICE_ID_WEMT: + case CDMA_SMS_TELESERVICE_ID_SCPT: + case CDMA_SMS_TELESERVICE_ID_CATPT: + return TRUE; + } + + return FALSE; /* Invalid teleservice type */ +} + +/* Decode Address parameter record */ +static gboolean cdma_sms_decode_addr(const guint8 *buf, guint8 len, + void *data) +{ + struct cdma_sms_address *addr = data; + guint16 bit_offset = 0; + guint8 chari_len; + guint16 total_num_bits = len * 8; + guint8 index; + + addr->digit_mode = bit_field_unpack(buf, bit_offset, 1); + bit_offset += 1; + + addr->number_mode = bit_field_unpack(buf, bit_offset, 1); + bit_offset += 1; + + if (addr->digit_mode == CDMA_SMS_DIGIT_MODE_8BIT_ASCII) { + if (addr->number_mode == CDMA_SMS_NUM_MODE_DIGIT) + addr->digi_num_type = + bit_field_unpack(buf, bit_offset, 3); + else + addr->data_nw_num_type = + bit_field_unpack(buf, bit_offset, 3); + + bit_offset += 3; + + if (addr->number_mode == CDMA_SMS_NUM_MODE_DIGIT) { + if (bit_offset + 4 > total_num_bits) + return FALSE; + + addr->number_plan = + bit_field_unpack(buf, bit_offset, 4); + bit_offset += 4; + } + } + + if (bit_offset + 8 > total_num_bits) + return FALSE; + + addr->num_fields = bit_field_unpack(buf, bit_offset, 8); + bit_offset += 8; + + if (addr->digit_mode == CDMA_SMS_DIGIT_MODE_4BIT_DTMF) + chari_len = 4; + else + chari_len = 8; + + if ((bit_offset + chari_len * addr->num_fields) > total_num_bits) + return FALSE; + + for (index = 0; index < addr->num_fields; index++) { + addr->address[index] = bit_field_unpack(buf, + bit_offset, + chari_len); + bit_offset += chari_len; + } + + return TRUE; +} + +static char *decode_text_7bit_ascii(const struct cdma_sms_ud *ud) +{ + char *buf; + + buf = g_new(char, ud->num_fields + 1); + if (buf == NULL) + return NULL; + + memcpy(buf, ud->chari, ud->num_fields); + buf[ud->num_fields] = 0; /* Make it NULL terminated string */ + + return buf; +} + +char *cdma_sms_decode_text(const struct cdma_sms_ud *ud) +{ + switch (ud->msg_encoding) { + case CDMA_SMS_MSG_ENCODING_OCTET: + case CDMA_SMS_MSG_ENCODING_EXTENDED_PROTOCOL_MSG: + return NULL; /* TODO */ + case CDMA_SMS_MSG_ENCODING_7BIT_ASCII: + return decode_text_7bit_ascii(ud); + case CDMA_SMS_MSG_ENCODING_IA5: + case CDMA_SMS_MSG_ENCODING_UNICODE: + case CDMA_SMS_MSG_ENCODING_SHIFT_JIS: + case CDMA_SMS_MSG_ENCODING_KOREAN: + case CDMA_SMS_MSG_ENCODING_LATIN_HEBREW: + case CDMA_SMS_MSG_ENCODING_LATIN: + case CDMA_SMS_MSG_ENCODING_GSM_7BIT: + case CDMA_SMS_MSG_ENCODING_GSM_DATA_CODING: + return NULL; /* TODO */ + } + + return NULL; +} + +/* Decode User Data */ +static gboolean cdma_sms_decode_ud(const guint8 *buf, guint8 len, void *data) +{ + guint16 bit_offset = 0; + guint8 chari_len = 0; + guint16 total_num_bits = len * 8; + guint8 index; + enum cdma_sms_msg_encoding msg_encoding; + struct cdma_sms_ud *ud = data; + + if (total_num_bits < 13) + return FALSE; + + msg_encoding = bit_field_unpack(buf, bit_offset, 5); + ud->msg_encoding = msg_encoding; + bit_offset += 5; + + if (msg_encoding == CDMA_SMS_MSG_ENCODING_EXTENDED_PROTOCOL_MSG || + msg_encoding == CDMA_SMS_MSG_ENCODING_GSM_DATA_CODING) { + /* + * Skip message type field for now. + * TODO: Add support for message type field. + */ + bit_offset += 8; + } + + if (bit_offset + 8 > total_num_bits) + return FALSE; + + ud->num_fields = bit_field_unpack(buf, bit_offset, 8); + bit_offset += 8; + + switch (msg_encoding) { + case CDMA_SMS_MSG_ENCODING_OCTET: + chari_len = 8; + break; + case CDMA_SMS_MSG_ENCODING_EXTENDED_PROTOCOL_MSG: + return FALSE; /* TODO */ + case CDMA_SMS_MSG_ENCODING_7BIT_ASCII: + case CDMA_SMS_MSG_ENCODING_IA5: + chari_len = 7; + break; + case CDMA_SMS_MSG_ENCODING_UNICODE: + case CDMA_SMS_MSG_ENCODING_SHIFT_JIS: + case CDMA_SMS_MSG_ENCODING_KOREAN: + return FALSE; /* TODO */ + case CDMA_SMS_MSG_ENCODING_LATIN_HEBREW: + case CDMA_SMS_MSG_ENCODING_LATIN: + chari_len = 8; + break; + case CDMA_SMS_MSG_ENCODING_GSM_7BIT: + chari_len = 7; + break; + case CDMA_SMS_MSG_ENCODING_GSM_DATA_CODING: + return FALSE; /* TODO */ + } + + /* TODO: Add support for all other encoding types */ + if (chari_len == 0) + return FALSE; + + if (bit_offset + chari_len * ud->num_fields > total_num_bits) + return FALSE; + + for (index = 0; index < ud->num_fields; index++) { + ud->chari[index] = bit_field_unpack(buf, + bit_offset, + chari_len); + bit_offset += chari_len; + } + + return TRUE; +} + +/* Decode Message Identifier */ +static gboolean cdma_sms_decode_message_id(const guint8 *buf, guint8 len, + void *data) +{ + struct cdma_sms_identifier *id = data; + + if (len != 3) + return FALSE; + + id->msg_type = bit_field_unpack(buf, 0, 4); + + if (id->msg_type <= 0 || + id->msg_type > CDMA_SMS_MSG_TYPE_SUBMIT_REPORT) + return FALSE; /* Invalid message type */ + + id->msg_id = (bit_field_unpack(buf, 4, 8) << 8) | + bit_field_unpack(buf, 12, 8); + + id->header_ind = bit_field_unpack(buf, 20, 1); + + return TRUE; +} + +static gboolean find_and_decode(struct simple_iter *iter, guint8 rec_id, + rec_handler handler, void *data) +{ + guint8 id; + guint8 len; + const guint8 *buf; + + while (simple_iter_next(iter) == TRUE) { + id = simple_iter_get_id(iter); + if (id != rec_id) + continue; + + len = simple_iter_get_length(iter); + buf = simple_iter_get_data(iter); + + return handler(buf, len, data); + } + + return FALSE; +} + +static rec_handler subparam_handler_for_id(enum cdma_sms_subparam_id id) +{ + switch (id) { + case CDMA_SMS_SUBPARAM_ID_MESSAGE_ID: + return cdma_sms_decode_message_id; + case CDMA_SMS_SUBPARAM_ID_USER_DATA: + return cdma_sms_decode_ud; + case CDMA_SMS_SUBPARAM_ID_USER_RESPONSE_CODE: + case CDMA_SMS_SUBPARAM_ID_MC_TIME_STAMP: + case CDMA_SMS_SUBPARAM_ID_VALIDITY_PERIOD_ABSOLUTE: + case CDMA_SMS_SUBPARAM_ID_VALIDITY_PERIOD_RELATIVE: + case CDMA_SMS_SUBPARAM_ID_DEFERRED_DELIVERY_TIME_ABSOLUTE: + case CDMA_SMS_SUBPARAM_ID_DEFERRED_DELIVERY_TIME_RELATIVE: + case CDMA_SMS_SUBPARAM_ID_PRIORITY_INDICATOR: + case CDMA_SMS_SUBPARAM_ID_PRIVACY_INDICATOR: + case CDMA_SMS_SUBPARAM_ID_REPLY_OPTION: + case CDMA_SMS_SUBPARAM_ID_NUMBER_OF_MESSAGES: + case CDMA_SMS_SUBPARAM_ID_ALERT_ON_MESSAGE_DELIVERY: + case CDMA_SMS_SUBPARAM_ID_LANGUAGE_INDICATOR: + case CDMA_SMS_SUBPARAM_ID_CALL_BACK_NUMBER: + case CDMA_SMS_SUBPARAM_ID_MESSAGE_DISPLAY_MODE: + case CDMA_SMS_SUBPARAM_ID_MULTIPLE_ENCODING_USER_DATA: + case CDMA_SMS_SUBPARAM_ID_MESSAGE_DEPOSIT_INDEX: + case CDMA_SMS_SUBPARAM_ID_SERVICE_CATEGORY_PROGRAM_DATA: + case CDMA_SMS_SUBPARAM_ID_SERVICE_CATEGORY_PROGRAM_RESULT: + case CDMA_SMS_SUBPARAM_ID_MESSAGE_STATUS: + case CDMA_SMS_SUBPARAM_ID_TP_FAILURE_CAUSE: + case CDMA_SMS_SUBPARAM_ID_ENHANCED_VMN: + case CDMA_SMS_SUBPARAM_ID_ENHANCED_VMN_ACK: + return NULL; /* TODO */ + } + + return NULL; +} + +struct subparam_handler_entry { + enum cdma_sms_subparam_id id; + int flags; + gboolean found; + void *data; +}; + +static gboolean decode_subparams(struct simple_iter *iter, guint32 *bitmap, + void *data, ...) +{ + GSList *entries = NULL; + GSList *l; + va_list args; + gboolean decode_result = TRUE; + + va_start(args, data); + + while (data != NULL) { + struct subparam_handler_entry *entry; + + entry = g_new0(struct subparam_handler_entry, 1); + + entry->data = data; + entry->id = va_arg(args, enum cdma_sms_subparam_id); + entry->flags = va_arg(args, int); + + data = va_arg(args, void *); + entries = g_slist_prepend(entries, entry); + } + + va_end(args); + + entries = g_slist_reverse(entries); + + l = entries; + while (simple_iter_next(iter) == TRUE) { + rec_handler handler; + struct subparam_handler_entry *entry; + guint8 subparam_len; + const guint8 *subparam_buf; + GSList *l2; + + for (l2 = l; l2; l2 = l2->next) { + entry = l2->data; + + if (simple_iter_get_id(iter) == entry->id) + break; + } + + /* Ignore unexpected subparameter record */ + if (l2 == NULL) + continue; + + entry->found = TRUE; + + subparam_len = simple_iter_get_length(iter); + subparam_buf = simple_iter_get_data(iter); + + handler = subparam_handler_for_id(entry->id); + + decode_result = handler(subparam_buf, + subparam_len, + entry->data); + if (decode_result == FALSE) + break; /* Stop if decoding failed */ + + set_bitmap(bitmap, entry->id); + } + + for (; l; l = l->next) { + struct subparam_handler_entry *entry = l->data; + + if ((entry->flags & CDMA_SMS_REC_FLAG_MANDATORY) && + (entry->found == FALSE)) { + decode_result = FALSE; + break; + } + } + + g_slist_foreach(entries, (GFunc) g_free, NULL); + g_slist_free(entries); + + return decode_result; +} + +/* Decode WMT */ +static gboolean cdma_sms_decode_wmt(struct simple_iter *iter, + struct cdma_sms_bearer_data *bd) +{ + switch (bd->id.msg_type) { + case CDMA_SMS_MSG_TYPE_RESERVED: + return FALSE; /* Invalid */ + case CDMA_SMS_MSG_TYPE_DELIVER: + /* + * WMT DELIVER, table 4.3.4-1 of C.S0015-B v2.0 + * TODO: Not all optional subparameters supported. + */ + return decode_subparams(iter, + &bd->subparam_bitmap, + &bd->wmt_deliver.ud, + CDMA_SMS_SUBPARAM_ID_USER_DATA, + 0, + NULL); + break; + case CDMA_SMS_MSG_TYPE_SUBMIT: + case CDMA_SMS_MSG_TYPE_CANCEL: + return FALSE; /* Invalid for MT WMT */ + case CDMA_SMS_MSG_TYPE_DELIVER_ACK: + case CDMA_SMS_MSG_TYPE_USER_ACK: + case CDMA_SMS_MSG_TYPE_READ_ACK: + return FALSE; /* TODO: Not supported yet */ + case CDMA_SMS_MSG_TYPE_DELIVER_REPORT: + case CDMA_SMS_MSG_TYPE_SUBMIT_REPORT: + return FALSE; /* Invalid for MT WMT */ + } + + return FALSE; +} + +static gboolean p2p_decode_bearer_data(const guint8 *buf, guint8 len, + enum cdma_sms_teleservice_id tele_id, + struct cdma_sms_bearer_data *bd) +{ + struct simple_iter iter; + + simple_iter_init(&iter, buf, len); + + /* Message Identifier is mandatory, * Section 4 of C.S0015-B v2.0 */ + if (find_and_decode(&iter, + CDMA_SMS_SUBPARAM_ID_MESSAGE_ID, + cdma_sms_decode_message_id, + &bd->id) != TRUE) + return FALSE; + + set_bitmap(&bd->subparam_bitmap, CDMA_SMS_SUBPARAM_ID_MESSAGE_ID); + + simple_iter_init(&iter, buf, len); + + switch (tele_id) { + case CDMA_SMS_TELESERVICE_ID_CMT91: + case CDMA_SMS_TELESERVICE_ID_WPT: + return FALSE; /* TODO */ + case CDMA_SMS_TELESERVICE_ID_WMT: + return cdma_sms_decode_wmt(&iter, bd); + case CDMA_SMS_TELESERVICE_ID_VMN: + case CDMA_SMS_TELESERVICE_ID_WAP: + case CDMA_SMS_TELESERVICE_ID_WEMT: + case CDMA_SMS_TELESERVICE_ID_SCPT: + case CDMA_SMS_TELESERVICE_ID_CATPT: + return FALSE; /* TODO */ + } + + return FALSE; +} + +/* Decode Bearer Data */ +static gboolean cdma_sms_decode_bearer_data(const guint8 *buf, guint8 len, + void *data) +{ + struct cdma_sms *msg = data; + + switch (msg->type) { + case CDMA_SMS_TP_MSG_TYPE_P2P: + return p2p_decode_bearer_data(buf, len, + msg->p2p_msg.teleservice_id, + &msg->p2p_msg.bd); + case CDMA_SMS_TP_MSG_TYPE_BCAST: + return FALSE; /* TODO */ + case CDMA_SMS_TP_MSG_TYPE_ACK: + return FALSE; /* Invalid */ + } + + return FALSE; +} + +static rec_handler param_handler_for_id(enum cdma_sms_param_id id, + struct cdma_sms *incoming, + void **data) +{ + if (incoming->type != CDMA_SMS_TP_MSG_TYPE_P2P) + return NULL; /* TODO: Other types not supported yet */ + + switch (id) { + case CDMA_SMS_PARAM_ID_TELESERVICE_IDENTIFIER: + *data = &incoming->p2p_msg.teleservice_id; + return cdma_sms_decode_teleservice; + case CDMA_SMS_PARAM_ID_SERVICE_CATEGORY: + return NULL; /* TODO */ + case CDMA_SMS_PARAM_ID_ORIGINATING_ADDRESS: + *data = &incoming->p2p_msg.oaddr; + return cdma_sms_decode_addr; + case CDMA_SMS_PARAM_ID_ORIGINATING_SUBADDRESS: + case CDMA_SMS_PARAM_ID_DESTINATION_ADDRESS: + case CDMA_SMS_PARAM_ID_DESTINATION_SUBADDRESS: + case CDMA_SMS_PARAM_ID_BEARER_REPLY_OPTION: + case CDMA_SMS_PARAM_ID_CAUSE_CODE: + return NULL; /* TODO */ + case CDMA_SMS_PARAM_ID_BEARER_DATA: + *data = incoming; + return cdma_sms_decode_bearer_data; + } + + return NULL; +} + +static gboolean cdma_sms_p2p_decode(const guint8 *pdu, guint8 len, + struct cdma_sms *incoming) +{ + struct simple_iter iter; + + simple_iter_init(&iter, pdu, len); + + /* + * Teleservice Identifier is mandatory, + * Table 3.4.2.1-1 of C.S0015-B v2.0 + */ + if (find_and_decode(&iter, + CDMA_SMS_PARAM_ID_TELESERVICE_IDENTIFIER, + cdma_sms_decode_teleservice, + &incoming->p2p_msg.teleservice_id) != TRUE) + return FALSE; + + set_bitmap(&incoming->p2p_msg.param_bitmap, + CDMA_SMS_PARAM_ID_TELESERVICE_IDENTIFIER); + + simple_iter_init(&iter, pdu, len); + + while (simple_iter_next(&iter) == TRUE) { + rec_handler handler; + enum cdma_sms_param_id rec_id; + guint8 rec_len; + const guint8 *rec_buf; + void *dataobj; + + rec_id = simple_iter_get_id(&iter); + if (rec_id == CDMA_SMS_PARAM_ID_TELESERVICE_IDENTIFIER) + continue; + + rec_len = simple_iter_get_length(&iter); + rec_buf = simple_iter_get_data(&iter); + + handler = param_handler_for_id(rec_id, incoming, &dataobj); + if (handler != NULL) { + if (handler(rec_buf, rec_len, dataobj) == FALSE) + return FALSE; + + set_bitmap(&incoming->p2p_msg.param_bitmap, rec_id); + } + } + + /* + * Originating Address is mandatory field, + * Table 3.4.2.1-1 of C.S0015-B v2.0 + */ + if ((incoming->p2p_msg.param_bitmap & + (1 << CDMA_SMS_PARAM_ID_ORIGINATING_ADDRESS)) == 0) + return FALSE; + + return TRUE; +} + +gboolean cdma_sms_decode(const guint8 *pdu, guint8 len, + struct cdma_sms *incoming) +{ + incoming->type = bit_field_unpack(pdu, 0, 8); + pdu += 1; + len -= 1; + + switch (incoming->type) { + case CDMA_SMS_TP_MSG_TYPE_P2P: + return cdma_sms_p2p_decode(pdu, len, incoming); + case CDMA_SMS_TP_MSG_TYPE_BCAST: + case CDMA_SMS_TP_MSG_TYPE_ACK: + /* TODO: Not supported yet */ + return FALSE; + } + + return FALSE; +} -- cgit v1.2.3