/* * * oFono - Open Source Telephony * * Copyright (C) 2010-2011 Nokia Corporation and/or its subsidiary(-ies). * * 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 *uninitialized_var(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; }