diff options
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/mm-sms-part-cdma.c | 1070 | ||||
-rw-r--r-- | src/mm-sms-part-cdma.h | 33 |
3 files changed, 1106 insertions, 1 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 1ed6df91..d3afed22 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -32,7 +32,9 @@ libmodem_helpers_la_SOURCES = \ mm-sms-part.h \ mm-sms-part.c \ mm-sms-part-3gpp.h \ - mm-sms-part-3gpp.c + mm-sms-part-3gpp.c \ + mm-sms-part-cdma.h \ + mm-sms-part-cdma.c # Additional QMI support in libmodem-helpers if WITH_QMI diff --git a/src/mm-sms-part-cdma.c b/src/mm-sms-part-cdma.c new file mode 100644 index 00000000..b567e38e --- /dev/null +++ b/src/mm-sms-part-cdma.c @@ -0,0 +1,1070 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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: + * + * Copyright (C) 2013 Google, Inc. + */ + +#include <ctype.h> +#include <string.h> + +#include <glib.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-sms-part-cdma.h" +#include "mm-log.h" + +/* + * Documentation that you may want to have around: + * + * 3GPP2 C.S0015-B: Short Message Service (SMS) for Wideband Spread Spectrum + * Systems. + * + * 3GPP2 C.R1001-G: Administration of Parameter Value Assignments for CDMA2000 + * Spread Spectrum Standards. + * + * 3GPP2 X.S0004-550-E: Mobile Application Part (MAP). + * + * 3GPP2 C.S0005-E: Upper Layer (Layer 3) Signaling Standard for CDMA2000 + * Spread Spectrum Systems. + * + * 3GPP2 N.S0005-O: Cellular Radiotelecommunications Intersystem Operations. + */ + +/* 3GPP2 C.S0015-B, section 3.4, table 3.4-1 */ +typedef enum { + MESSAGE_TYPE_POINT_TO_POINT = 0, + MESSAGE_TYPE_BROADCAST = 1, + MESSAGE_TYPE_ACKNOWLEDGE = 2 +} MessageType; + +/* 3GPP2 C.S0015-B, section 3.4.3, table 3.4.3-1 */ +typedef enum { + PARAMETER_ID_TELESERVICE_ID = 0, + PARAMETER_ID_SERVICE_CATEGORY = 1, + PARAMETER_ID_ORIGINATING_ADDRESS = 2, + PARAMETER_ID_ORIGINATING_SUBADDRESS = 3, + PARAMETER_ID_DESTINATION_ADDRESS = 4, + PARAMETER_ID_DESTINATION_SUBADDRESS = 5, + PARAMETER_ID_BEARER_REPLY_OPTION = 6, + PARAMETER_ID_CAUSE_CODES = 7, + PARAMETER_ID_BEARER_DATA = 8 +} ParameterId; + +/* 3GPP2 C.S0015-B, section 3.4.3.3 */ +typedef enum { + DIGIT_MODE_DTMF = 0, + DIGIT_MODE_ASCII = 1 +} DigitMode; + +/* 3GPP2 C.S0015-B, section 3.4.3.3 */ +typedef enum { + NUMBER_MODE_DIGIT = 0, + NUMBER_MODE_DATA_NETWORK_ADDRESS = 1 +} NumberMode; + +/* 3GPP2 C.S0005-E, section 2.7.1.3.2.4, table 2.7.1.3.2.4-2 */ +typedef enum { + NUMBER_TYPE_UNKNOWN = 0, + NUMBER_TYPE_INTERNATIONAL = 1, + NUMBER_TYPE_NATIONAL = 2, + NUMBER_TYPE_NETWORK_SPECIFIC = 3, + NUMBER_TYPE_SUBSCRIBER = 4, + /* 5 reserved */ + NUMBER_TYPE_ABBREVIATED = 6, + /* 7 reserved */ +} NumberType; + +/* 3GPP2 C.S0015-B, section 3.4.3.3, table 3.4.3.3-1 */ +typedef enum { + DATA_NETWORK_ADDRESS_TYPE_UNKNOWN = 0, + DATA_NETWORK_ADDRESS_TYPE_INTERNET_PROTOCOL = 1, + DATA_NETWORK_ADDRESS_TYPE_INTERNET_EMAIL_ADDRESS = 2 +} DataNetworkAddressType; + +/* 3GPP2 C.S0005-E, section 2.7.1.3.2.4, table 2.7.1.3.2.4-3 */ +typedef enum { + NUMBERING_PLAN_UNKNOWN = 0, + NUMBERING_PLAN_ISDN = 1, + NUMBERING_PLAN_DATA = 3, + NUMBERING_PLAN_TELEX = 4, + NUMBERING_PLAN_PRIVATE = 9, + /* 15 reserved */ +} NumberingPlan; + +/* 3GPP2 C.S0015-B, section 3.4.3.6 */ +typedef enum { + ERROR_CLASS_NO_ERROR = 0, + /* 1 reserved */ + ERROR_CLASS_TEMPORARY = 2, + ERROR_CLASS_PERMANENT = 3 +} ErrorClass; + +/* 3GPP2 N.S0005-O, section 6.5.2.125*/ +typedef enum { + CAUSE_CODE_NETWORK_PROBLEM_ADDRESS_VACANT = 0, + CAUSE_CODE_NETWORK_PROBLEM_ADDRESS_TRANSLATION_FAILURE = 1, + CAUSE_CODE_NETWORK_PROBLEM_NETWORK_RESOURCE_OUTAGE = 2, + CAUSE_CODE_NETWORK_PROBLEM_NETWORK_FAILURE = 3, + CAUSE_CODE_NETWORK_PROBLEM_INVALID_TELESERVICE_ID = 4, + CAUSE_CODE_NETWORK_PROBLEM_OTHER = 5, + /* 6 to 31 reserved, treat as CAUSE_CODE_NETWORK_PROBLEM_OTHER */ + CAUSE_CODE_TERMINAL_PROBLEM_NO_PAGE_RESPONSE = 32, + CAUSE_CODE_TERMINAL_PROBLEM_DESTINATION_BUSY = 33, + CAUSE_CODE_TERMINAL_PROBLEM_NO_ACKNOWLEDGMENT = 34, + CAUSE_CODE_TERMINAL_PROBLEM_DESTINATION_RESOURCE_SHORTAGE = 35, + CAUSE_CODE_TERMINAL_PROBLEM_SMS_DELIVERY_POSTPONED = 36, + CAUSE_CODE_TERMINAL_PROBLEM_DESTINATION_OUT_OF_SERVICE = 37, + CAUSE_CODE_TERMINAL_PROBLEM_DESTINATION_NO_LONGER_AT_THIS_ADDRESS = 38, + CAUSE_CODE_TERMINAL_PROBLEM_OTHER = 39, + /* 40 to 47 reserved, treat as CAUSE_CODE_TERMINAL_PROBLEM_OTHER */ + /* 48 to 63 reserved, treat as CAUSE_CODE_TERMINAL_PROBLEM_SMS_DELIVERY_POSTPONED */ + CAUSE_CODE_RADIO_INTERFACE_PROBLEM_RESOURCE_SHORTAGE = 64, + CAUSE_CODE_RADIO_INTERFACE_PROBLEM_INCOMPATIBILITY = 65, + CAUSE_CODE_RADIO_INTERFACE_PROBLEM_OTHER = 66, + /* 67 to 95 reserved, treat as CAUSE_CODE_RADIO_INTERFACE_PROBLEM_OTHER */ + CAUSE_CODE_GENERAL_PROBLEM_ENCODING = 96, + CAUSE_CODE_GENERAL_PROBLEM_SMS_ORIGINATION_DENIED = 97, + CAUSE_CODE_GENERAL_PROBLEM_SMS_TERMINATION_DENIED = 98, + CAUSE_CODE_GENERAL_PROBLEM_SUPPLEMENTARY_SERVICE_NOT_SUPPORTED = 99, + CAUSE_CODE_GENERAL_PROBLEM_SMS_NOT_SUPPORTED = 100, + /* 101 reserved */ + CAUSE_CODE_GENERAL_PROBLEM_MISSING_EXPECTED_PARAMETER = 102, + CAUSE_CODE_GENERAL_PROBLEM_MISSING_MANDATORY_PARAMETER = 103, + CAUSE_CODE_GENERAL_PROBLEM_UNRECOGNIZED_PARAMETER_VALUE = 104, + CAUSE_CODE_GENERAL_PROBLEM_UNEXPECTED_PARAMETER_VALUE = 105, + CAUSE_CODE_GENERAL_PROBLEM_USER_DATA_SIZE_ERROR = 106, + CAUSE_CODE_GENERAL_PROBLEM_OTHER = 107, + /* 108 to 223 reserved, treat as CAUSE_CODE_GENERAL_PROBLEM_OTHER */ + /* 224 to 255 reserved for TIA/EIA-41 extension, otherwise treat as CAUSE_CODE_GENERAL_PROBLEM_OTHER */ +} CauseCode; + +/* 3GPP2 C.S0015-B, section 4.5, table 4.5-1 */ +typedef enum { + SUBPARAMETER_ID_MESSAGE_ID = 0, + SUBPARAMETER_ID_USER_DATA = 1, + SUBPARAMETER_ID_USER_RESPONSE_CODE = 2, + SUBPARAMETER_ID_MESSAGE_CENTER_TIME_STAMP = 3, + SUBPARAMETER_ID_VALIDITY_PERIOD_ABSOLUTE = 4, + SUBPARAMETER_ID_VALIDITY_PERIOD_RELATIVE = 5, + SUBPARAMETER_ID_DEFERRED_DELIVERY_TIME_ABSOLUTE = 6, + SUBPARAMETER_ID_DEFERRED_DELIVERY_TIME_RELATIVE = 7, + SUBPARAMETER_ID_PRIORITY_INDICATOR = 8, + SUBPARAMETER_ID_PRIVACY_INDICATOR = 9, + SUBPARAMETER_ID_REPLY_OPTION = 10, + SUBPARAMETER_ID_NUMBER_OF_MESSAGES = 11, + SUBPARAMETER_ID_ALERT_ON_MESSAGE_DELIVERY = 12, + SUBPARAMETER_ID_LANGUAGE_INDICATOR = 13, + SUBPARAMETER_ID_CALL_BACK_NUMBER = 14, + SUBPARAMETER_ID_MESSAGE_DISPLAY_MODE = 15, + SUBPARAMETER_ID_MULTIPLE_ENCODING_USER_DATA = 16, + SUBPARAMETER_ID_MESSAGE_DEPOSIT_INDEX = 17, + SUBPARAMETER_ID_SERVICE_CATEGORY_PROGRAM_DATA = 18, + SUBPARAMETER_ID_SERVICE_CATEGORY_PROGRAM_RESULT = 19, + SUBPARAMETER_ID_MESSAGE_STATUS = 20, + SUBPARAMETER_ID_TP_FAILURE_CAUSE = 21, + SUBPARAMETER_ID_ENHANCED_VMN = 22, + SUBPARAMETER_ID_ENHANCED_VMN_ACK = 23, +} SubparameterId; + +/* 3GPP2 C.S0015-B, section 4.5.1, table 4.5.1-1 */ +typedef enum { + TELESERVICE_MESSAGE_TYPE_UNKNOWN = 0, + TELESERVICE_MESSAGE_TYPE_DELIVER = 1, + TELESERVICE_MESSAGE_TYPE_SUBMIT = 2, + TELESERVICE_MESSAGE_TYPE_CANCELLATION = 3, + TELESERVICE_MESSAGE_TYPE_DELIVERY_ACKNOWLEDGEMENT = 4, + TELESERVICE_MESSAGE_TYPE_USER_ACKNOWLEDGEMENT = 5, + TELESERVICE_MESSAGE_TYPE_READ_ACKNOWLEDGEMENT = 6, +} TeleserviceMessageType; + +/* C.R1001-G, section 9.1, table 9.1-1 */ +typedef enum { + ENCODING_OCTET = 0, + ENCODING_EXTENDED_PROTOCOL_MESSAGE = 1, + ENCODING_ASCII_7BIT = 2, + ENCODING_IA5 = 3, + ENCODING_UNICODE = 4, + ENCODING_SHIFT_JIS = 5, + ENCODING_KOREAN = 6, + ENCODING_LATIN_HEBREW = 7, + ENCODING_LATIN = 8, + ENCODING_GSM_7BIT = 9, + ENCODING_GSM_DCS = 10, +} Encoding; + +/*****************************************************************************/ +/* Read bits; o_bits < 8; n_bits <= 8 + * + * Byte 0 Byte 1 + * [7|6|5|4|3|2|1|0] [7|6|5|4|3|2|1|0] + * + * o_bits+n_bits <= 16 + * + */ +static guint8 +read_bits (const guint8 *bytes, + guint8 o_bits, + guint8 n_bits) +{ + guint8 bits_in_first; + guint8 bits_in_second; + + g_assert (o_bits < 8); + g_assert (n_bits <= 8); + g_assert (o_bits + n_bits <= 16); + + /* Read only from the first byte */ + if (o_bits + n_bits <= 8) + return (bytes[0] >> (8 - o_bits - n_bits)) & ((1 << n_bits) - 1); + + /* Read (8 - o_bits) from the first byte and (n_bits - (8 - o_bits)) from the second byte */ + bits_in_first = 8 - o_bits; + bits_in_second = n_bits - bits_in_first; + return (read_bits (&bytes[0], o_bits, bits_in_first) << bits_in_second) | read_bits (&bytes[1], 0, bits_in_second); +} + +/*****************************************************************************/ +/* Cause code to delivery state */ + +static MMSmsDeliveryState +cause_code_to_delivery_state (guint8 error_class, + guint8 cause_code) +{ + guint delivery_state = 0; + + switch (error_class) { + case ERROR_CLASS_NO_ERROR: + return MM_SMS_DELIVERY_STATE_COMPLETED_RECEIVED; + case ERROR_CLASS_TEMPORARY: + delivery_state += 0x300; + case ERROR_CLASS_PERMANENT: + delivery_state += 0x200; + default: + return MM_SMS_DELIVERY_STATE_UNKNOWN; + } + + /* Fixes for unknown cause codes */ + + if (cause_code >= 6 && cause_code <= 31) + /* 6 to 31 reserved, treat as CAUSE_CODE_NETWORK_PROBLEM_OTHER */ + delivery_state += CAUSE_CODE_NETWORK_PROBLEM_OTHER; + else if (cause_code >= 40 && cause_code <= 47) + /* 40 to 47 reserved, treat as CAUSE_CODE_TERMINAL_PROBLEM_OTHER */ + delivery_state += CAUSE_CODE_TERMINAL_PROBLEM_OTHER; + else if (cause_code >= 48 && cause_code <= 63) + /* 48 to 63 reserved, treat as CAUSE_CODE_TERMINAL_PROBLEM_SMS_DELIVERY_POSTPONED */ + delivery_state += CAUSE_CODE_TERMINAL_PROBLEM_SMS_DELIVERY_POSTPONED; + else if (cause_code >= 67 && cause_code <= 95) + /* 67 to 95 reserved, treat as CAUSE_CODE_RADIO_INTERFACE_PROBLEM_OTHER */ + delivery_state += CAUSE_CODE_RADIO_INTERFACE_PROBLEM_OTHER; + else if (cause_code == 101) + /* 101 reserved */ + delivery_state += CAUSE_CODE_GENERAL_PROBLEM_OTHER; + else if (cause_code >= 108 && cause_code <= 255) + /* 108 to 223 reserved, treat as CAUSE_CODE_GENERAL_PROBLEM_OTHER + * 224 to 255 reserved for TIA/EIA-41 extension, otherwise treat as CAUSE_CODE_GENERAL_PROBLEM_OTHER */ + delivery_state += CAUSE_CODE_GENERAL_PROBLEM_OTHER; + else + /* direct relationship */ + delivery_state += cause_code; + + return (MMSmsDeliveryState) delivery_state; +} + +/*****************************************************************************/ + +MMSmsPart * +mm_sms_part_cdma_new_from_pdu (guint index, + const gchar *hexpdu, + GError **error) +{ + gsize pdu_len; + guint8 *pdu; + MMSmsPart *part; + + /* Convert PDU from hex to binary */ + pdu = (guint8 *) mm_utils_hexstr2bin (hexpdu, &pdu_len); + if (!pdu) { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't convert CDMA PDU from hex to binary"); + return NULL; + } + + part = mm_sms_part_cdma_new_from_binary_pdu (index, pdu, pdu_len, error); + g_free (pdu); + + return part; +} + +struct Parameter { + guint8 parameter_id; + guint8 parameter_len; + guint8 parameter_value[]; +} __attribute__((packed)); + +static void +read_teleservice_id (MMSmsPart *sms_part, + const struct Parameter *parameter) +{ + guint16 teleservice_id; + + g_assert (parameter->parameter_id == PARAMETER_ID_TELESERVICE_ID); + + if (parameter->parameter_len != 2) { + mm_dbg (" invalid teleservice ID length found (%u != 2): ignoring", + parameter->parameter_len); + return; + } + + memcpy (&teleservice_id, ¶meter->parameter_value[0], 2); + teleservice_id = GUINT16_FROM_BE (teleservice_id); + + switch (teleservice_id){ + case MM_SMS_CDMA_TELESERVICE_ID_CMT91: + case MM_SMS_CDMA_TELESERVICE_ID_WPT: + case MM_SMS_CDMA_TELESERVICE_ID_WMT: + case MM_SMS_CDMA_TELESERVICE_ID_VMN: + case MM_SMS_CDMA_TELESERVICE_ID_WAP: + case MM_SMS_CDMA_TELESERVICE_ID_WEMT: + case MM_SMS_CDMA_TELESERVICE_ID_SCPT: + case MM_SMS_CDMA_TELESERVICE_ID_CATPT: + break; + default: + mm_dbg (" invalid teleservice ID found (%u): ignoring", teleservice_id); + return; + } + + mm_dbg (" teleservice ID read: %s (%u)", + mm_sms_cdma_teleservice_id_get_string (teleservice_id), + teleservice_id); + + mm_sms_part_set_cdma_teleservice_id (sms_part, + (MMSmsCdmaTeleserviceId)teleservice_id); +} + +static void +read_service_category (MMSmsPart *sms_part, + const struct Parameter *parameter) +{ + guint16 service_category; + + g_assert (parameter->parameter_id == PARAMETER_ID_SERVICE_CATEGORY); + + if (parameter->parameter_len != 2) { + mm_dbg (" invalid service category length found (%u != 2): ignoring", + parameter->parameter_len); + return; + } + + memcpy (&service_category, ¶meter->parameter_value[0], 2); + service_category = GUINT16_FROM_BE (service_category); + + switch (service_category) { + case MM_SMS_CDMA_SERVICE_CATEGORY_EMERGENCY_BROADCAST: + case MM_SMS_CDMA_SERVICE_CATEGORY_ADMINISTRATIVE: + case MM_SMS_CDMA_SERVICE_CATEGORY_MAINTENANCE: + case MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_LOCAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_REGIONAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_NATIONAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_INTERNATIONAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_LOCAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_REGIONAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_NATIONAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_INTERNATIONAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_LOCAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_REGIONAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_NATIONAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_INTERNATIONAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_LOCAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_REGIONAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_NATIONAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_INTERNATIONAL: + case MM_SMS_CDMA_SERVICE_CATEGORY_LOCAL_WEATHER: + case MM_SMS_CDMA_SERVICE_CATEGORY_TRAFFIC_REPORT: + case MM_SMS_CDMA_SERVICE_CATEGORY_FLIGHT_SCHEDULES: + case MM_SMS_CDMA_SERVICE_CATEGORY_RESTAURANTS: + case MM_SMS_CDMA_SERVICE_CATEGORY_LODGINGS: + case MM_SMS_CDMA_SERVICE_CATEGORY_RETAIL_DIRECTORY: + case MM_SMS_CDMA_SERVICE_CATEGORY_ADVERTISEMENTS: + case MM_SMS_CDMA_SERVICE_CATEGORY_STOCK_QUOTES: + case MM_SMS_CDMA_SERVICE_CATEGORY_EMPLOYMENT: + case MM_SMS_CDMA_SERVICE_CATEGORY_HOSPITALS: + case MM_SMS_CDMA_SERVICE_CATEGORY_TECHNOLOGY_NEWS: + case MM_SMS_CDMA_SERVICE_CATEGORY_MULTICATEGORY: + case MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_PRESIDENTIAL_ALERT: + case MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_EXTREME_THREAT: + case MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_SEVERE_THREAT: + case MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY: + case MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_TEST: + break; + default: + mm_dbg (" invalid service category found (%u): ignoring", service_category); + return; + } + + mm_dbg (" service category read: %s (%u)", + mm_sms_cdma_service_category_get_string (service_category), + service_category); + + mm_sms_part_set_cdma_service_category (sms_part, + (MMSmsCdmaServiceCategory)service_category); +} + +static guint8 +dtmf_to_ascii (guint8 dtmf) +{ + static const gchar dtmf_to_ascii_digits[13] = { + '\0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '*', '#' }; + + if (dtmf > 0 && dtmf < 13) + return dtmf_to_ascii_digits[dtmf]; + + mm_dbg (" invalid dtmf digit: %u", dtmf); + return '\0'; +} + +static void +read_address (MMSmsPart *sms_part, + const struct Parameter *parameter) +{ + guint8 digit_mode; + guint8 number_mode; + guint8 number_type; + guint8 numbering_plan; + guint8 num_fields; + guint byte_offset = 0; + guint bit_offset = 0; + guint i; + gchar *number = NULL; + +#define OFFSETS_UPDATE(n_bits) do { \ + bit_offset += n_bits; \ + if (bit_offset >= 8) { \ + bit_offset-=8; \ + byte_offset++; \ + } \ + } while (0) + +#define PARAMETER_SIZE_CHECK(required_size) \ + if (parameter->parameter_len < required_size) { \ + mm_dbg (" cannot read address, need at least %u bytes (got %u)", \ + required_size, \ + parameter->parameter_len); \ + return; \ + } + + /* Readability of digit mode and number mode (first 2 bits, i.e. first byte) */ + PARAMETER_SIZE_CHECK (1); + + /* Digit mode */ + digit_mode = read_bits (¶meter->parameter_value[byte_offset], bit_offset, 1); + OFFSETS_UPDATE (1); + g_assert (digit_mode <= 1); + switch (digit_mode) { + case DIGIT_MODE_DTMF: + mm_dbg (" digit mode: dtmf"); + break; + case DIGIT_MODE_ASCII: + mm_dbg (" digit mode: ascii"); + break; + default: + g_assert_not_reached (); + } + + /* Number mode */ + number_mode = read_bits (¶meter->parameter_value[byte_offset], bit_offset, 1); + OFFSETS_UPDATE (1); + switch (number_mode) { + case NUMBER_MODE_DIGIT: + mm_dbg (" number mode: digit"); + break; + case NUMBER_MODE_DATA_NETWORK_ADDRESS: + mm_dbg (" number mode: data network address"); + break; + default: + g_assert_not_reached (); + } + + /* Number type */ + if (digit_mode == DIGIT_MODE_ASCII) { + /* No need for readability check, still in first byte always */ + number_type = read_bits (¶meter->parameter_value[byte_offset], bit_offset, 3); + OFFSETS_UPDATE (3); + switch (number_type) { + case NUMBER_TYPE_UNKNOWN: + mm_dbg (" number type: unknown"); + break; + case NUMBER_TYPE_INTERNATIONAL: + mm_dbg (" number type: international"); + break; + case NUMBER_TYPE_NATIONAL: + mm_dbg (" number type: national"); + break; + case NUMBER_TYPE_NETWORK_SPECIFIC: + mm_dbg (" number type: specific"); + break; + case NUMBER_TYPE_SUBSCRIBER: + mm_dbg (" number type: subscriber"); + break; + case NUMBER_TYPE_ABBREVIATED: + mm_dbg (" number type: abbreviated"); + break; + default: + mm_dbg (" number type unknown (%u)", number_type); + break; + } + } else + number_type = 0xFF; + + /* Numbering plan */ + if (digit_mode == DIGIT_MODE_ASCII && number_mode == NUMBER_MODE_DIGIT) { + /* Readability of numbering plan; may go to second byte */ + PARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + 4) / 8)); + numbering_plan = read_bits (¶meter->parameter_value[byte_offset], bit_offset, 4); + OFFSETS_UPDATE (4); + switch (numbering_plan) { + case NUMBERING_PLAN_UNKNOWN: + mm_dbg (" numbering plan: unknown"); + break; + case NUMBERING_PLAN_ISDN: + mm_dbg (" numbering plan: isdn"); + break; + case NUMBERING_PLAN_DATA: + mm_dbg (" numbering plan: data"); + break; + case NUMBERING_PLAN_TELEX: + mm_dbg (" numbering plan: telex"); + break; + case NUMBERING_PLAN_PRIVATE: + mm_dbg (" numbering plan: private"); + break; + default: + mm_dbg (" numbering plan unknown (%u)", numbering_plan); + break; + } + } else + numbering_plan = 0xFF; + + /* Readability of num_fields; will go to third byte (((bit_offset + 8) / 8) == 1) */ + PARAMETER_SIZE_CHECK (byte_offset + 2); + num_fields = read_bits (¶meter->parameter_value[byte_offset], bit_offset, 8); + OFFSETS_UPDATE (8); + mm_dbg (" num fields: %u", num_fields); + + /* Address string */ + + if (digit_mode == DIGIT_MODE_DTMF) { + /* DTMF */ + PARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 4)) / 8)); + number = g_malloc (num_fields + 1); + for (i = 0; i < num_fields; i++) { + number[i] = dtmf_to_ascii (read_bits (¶meter->parameter_value[byte_offset], bit_offset, 4)); + OFFSETS_UPDATE (4); + } + number[i] = '\0'; + } else if (number_mode == NUMBER_MODE_DIGIT) { + /* ASCII + * TODO: should we expose numbering plan and number type? */ + PARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 8)) / 8)); + number = g_malloc (num_fields + 1); + for (i = 0; i < num_fields; i++) { + number[i] = read_bits (¶meter->parameter_value[byte_offset], bit_offset, 8); + OFFSETS_UPDATE (8); + } + number[i] = '\0'; + } else if (number_type == DATA_NETWORK_ADDRESS_TYPE_INTERNET_EMAIL_ADDRESS) { + /* Internet e-mail address (ASCII) */ + PARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 8)) / 8)); + number = g_malloc (num_fields + 1); + for (i = 0; i < num_fields; i++) { + number[i] = read_bits (¶meter->parameter_value[byte_offset], bit_offset, 8); + OFFSETS_UPDATE (8); + } + number[i] = '\0'; + } else if (number_type == DATA_NETWORK_ADDRESS_TYPE_INTERNET_PROTOCOL) { + GString *str; + + /* Binary data network address (most significant first) + * For now, just print the hex string (e.g. FF:01...) */ + PARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 8)) / 8)); + str = g_string_sized_new (num_fields * 2); + for (i = 0; i < num_fields; i++) { + g_string_append_printf (str, "%.2X", read_bits (¶meter->parameter_value[byte_offset], bit_offset, 8)); + OFFSETS_UPDATE (8); + } + number = g_string_free (str, FALSE); + } else + mm_dbg (" data network address number type unknown (%u)", number_type); + + mm_dbg (" address read: %s", number); + + mm_sms_part_set_number (sms_part, number); + g_free (number); + +#undef OFFSETS_UPDATE +#undef PARAMETER_SIZE_CHECK +} + +static void +read_bearer_reply_option (MMSmsPart *sms_part, + const struct Parameter *parameter) +{ + guint8 sequence; + + g_assert (parameter->parameter_id == PARAMETER_ID_BEARER_REPLY_OPTION); + + if (parameter->parameter_len != 1) { + mm_dbg (" invalid bearer reply option length found (%u != 1): ignoring", + parameter->parameter_len); + return; + } + + sequence = read_bits (¶meter->parameter_value[0], 0, 6); + mm_dbg (" sequence read: %u", sequence); + + mm_sms_part_set_message_reference (sms_part, sequence); +} + +static void +read_cause_codes (MMSmsPart *sms_part, + const struct Parameter *parameter) +{ + guint8 sequence; + guint8 error_class; + guint8 cause_code; + MMSmsDeliveryState delivery_state; + + g_assert (parameter->parameter_id == PARAMETER_ID_BEARER_REPLY_OPTION); + + if (parameter->parameter_len != 1 && parameter->parameter_len != 2) { + mm_dbg (" invalid cause codes length found (%u): ignoring", + parameter->parameter_len); + return; + } + + sequence = read_bits (¶meter->parameter_value[0], 0, 6); + mm_dbg (" sequence read: %u", sequence); + + error_class = read_bits (¶meter->parameter_value[0], 6, 2); + mm_dbg (" error class read: %u", error_class); + + if (error_class != ERROR_CLASS_NO_ERROR) { + if (parameter->parameter_len != 2) { + mm_dbg (" invalid cause codes length found (%u != 2): ignoring", + parameter->parameter_len); + return; + } + cause_code = parameter->parameter_value[1]; + mm_dbg (" cause code read: %u", cause_code); + } else + cause_code = 0; + + delivery_state = cause_code_to_delivery_state (error_class, cause_code); + mm_dbg (" delivery state built: %s", mm_sms_delivery_state_get_string (delivery_state)); + + mm_sms_part_set_message_reference (sms_part, sequence); + mm_sms_part_set_delivery_state (sms_part, delivery_state); +} + +static void +read_bearer_data_message_identifier (MMSmsPart *sms_part, + const struct Parameter *subparameter) +{ + guint8 message_type; + guint16 message_id; + guint8 header_ind; + + g_assert (subparameter->parameter_id == SUBPARAMETER_ID_MESSAGE_ID); + + if (subparameter->parameter_len != 3) { + mm_dbg (" invalid message identifier length found (%u): ignoring", + subparameter->parameter_len); + return; + } + + message_type = read_bits (&subparameter->parameter_value[0], 0, 4); + switch (message_type) { + case TELESERVICE_MESSAGE_TYPE_UNKNOWN: + mm_dbg (" message type: unknown"); + break; + case TELESERVICE_MESSAGE_TYPE_DELIVER: + mm_dbg (" message type: deliver"); + mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_CDMA_DELIVER); + break; + case TELESERVICE_MESSAGE_TYPE_SUBMIT: + mm_dbg (" message type: submit"); + mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_CDMA_SUBMIT); + break; + case TELESERVICE_MESSAGE_TYPE_CANCELLATION: + mm_dbg (" message type: cancellation"); + mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_CDMA_CANCELLATION); + break; + case TELESERVICE_MESSAGE_TYPE_DELIVERY_ACKNOWLEDGEMENT: + mm_dbg (" message type: delivery acknowledgement"); + mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_CDMA_DELIVERY_ACKNOWLEDGEMENT); + break; + case TELESERVICE_MESSAGE_TYPE_USER_ACKNOWLEDGEMENT: + mm_dbg (" message type: user acknowledgement"); + mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_CDMA_USER_ACKNOWLEDGEMENT); + break; + case TELESERVICE_MESSAGE_TYPE_READ_ACKNOWLEDGEMENT: + mm_dbg (" message type: read acknowledgement"); + mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_CDMA_READ_ACKNOWLEDGEMENT); + break; + default: + mm_dbg (" message type unknown (%u)", message_type); + break; + } + + message_id = ((read_bits (&subparameter->parameter_value[0], 4, 8) << 8) | + (read_bits (&subparameter->parameter_value[1], 4, 8))); + message_id = GUINT16_FROM_BE (message_id); + mm_dbg (" message id: %u", (guint) message_id); + + header_ind = read_bits (&subparameter->parameter_value[2], 4, 1); + mm_dbg (" header indicator read: %u", header_ind); +} + +static void +read_bearer_data_user_data (MMSmsPart *sms_part, + const struct Parameter *subparameter) +{ + guint8 message_encoding; + guint8 message_type; + guint8 num_fields; + guint byte_offset = 0; + guint bit_offset = 0; + +#define OFFSETS_UPDATE(n_bits) do { \ + bit_offset += n_bits; \ + if (bit_offset >= 8) { \ + bit_offset-=8; \ + byte_offset++; \ + } \ + } while (0) + +#define SUBPARAMETER_SIZE_CHECK(required_size) \ + if (subparameter->parameter_len < required_size) { \ + mm_dbg (" cannot read address, need at least %u bytes (got %u)", \ + required_size, \ + subparameter->parameter_len); \ + return; \ + } + + g_assert (subparameter->parameter_id == SUBPARAMETER_ID_USER_DATA); + + /* Message encoding */ + SUBPARAMETER_SIZE_CHECK (1); + message_encoding = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 5); + OFFSETS_UPDATE (5); + switch (message_encoding) { + case ENCODING_OCTET: + mm_dbg (" message encoding: octet, unspecified"); + break; + case ENCODING_EXTENDED_PROTOCOL_MESSAGE: + mm_dbg (" message encoding: extended protocol message"); + break; + case ENCODING_ASCII_7BIT: + mm_dbg (" message encoding: 7-bit ASCII"); + break; + case ENCODING_IA5: + mm_dbg (" message encoding: IA5"); + break; + case ENCODING_UNICODE: + mm_dbg (" message encoding: unicode"); + break; + case ENCODING_SHIFT_JIS: + mm_dbg (" message encoding: shift-j is"); + break; + case ENCODING_KOREAN: + mm_dbg (" message encoding: korean"); + break; + case ENCODING_LATIN_HEBREW: + mm_dbg (" message encoding: latin/hebrew"); + break; + case ENCODING_LATIN: + mm_dbg (" message encoding: latin"); + break; + case ENCODING_GSM_7BIT: + mm_dbg (" message encoding: 7-bit GSM"); + break; + case ENCODING_GSM_DCS: + mm_dbg (" message encoding: GSM data coding scheme"); + break; + default: + mm_dbg (" message encoding unknown (%u)", message_encoding); + break; + } + + /* Message type, only if extended protocol message */ + if (message_encoding == ENCODING_EXTENDED_PROTOCOL_MESSAGE) { + SUBPARAMETER_SIZE_CHECK (2); + message_type = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 8); + OFFSETS_UPDATE (8); + mm_dbg (" message type: %u", message_type); + } + + /* Number of fields */ + SUBPARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + 8) / 8)); + num_fields = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 8); + OFFSETS_UPDATE (8); + mm_dbg (" num fields: %u", num_fields); + +#undef OFFSETS_UPDATE +#undef SUBPARAMETER_SIZE_CHECK +} + +static void +read_bearer_data (MMSmsPart *sms_part, + const struct Parameter *parameter) +{ + guint offset; + +#define PARAMETER_SIZE_CHECK(required_size) \ + if (parameter->parameter_len < required_size) { \ + mm_dbg (" cannot read bearer data, need at least %u bytes (got %u)", \ + required_size, \ + parameter->parameter_len); \ + return; \ + } + + offset = 0; + while (offset < parameter->parameter_len) { + const struct Parameter *subparameter; + + PARAMETER_SIZE_CHECK (offset + 2); + subparameter = (const struct Parameter *)¶meter->parameter_value[offset]; + offset += 2; + + PARAMETER_SIZE_CHECK (offset + subparameter->parameter_len); + offset += subparameter->parameter_len; + + switch (subparameter->parameter_id) { + case SUBPARAMETER_ID_MESSAGE_ID: + mm_dbg (" reading message ID..."); + read_bearer_data_message_identifier (sms_part, subparameter); + break; + case SUBPARAMETER_ID_USER_DATA: + mm_dbg (" reading user data..."); + read_bearer_data_user_data (sms_part, subparameter); + break; + case SUBPARAMETER_ID_USER_RESPONSE_CODE: + mm_dbg (" skipping user response code..."); + break; + case SUBPARAMETER_ID_MESSAGE_CENTER_TIME_STAMP: + mm_dbg (" skipping message center timestamp..."); + break; + case SUBPARAMETER_ID_VALIDITY_PERIOD_ABSOLUTE: + mm_dbg (" skipping absolute validity period..."); + break; + case SUBPARAMETER_ID_VALIDITY_PERIOD_RELATIVE: + mm_dbg (" skipping relative validity period..."); + break; + case SUBPARAMETER_ID_DEFERRED_DELIVERY_TIME_ABSOLUTE: + mm_dbg (" skipping absolute deferred delivery time..."); + break; + case SUBPARAMETER_ID_DEFERRED_DELIVERY_TIME_RELATIVE: + mm_dbg (" skipping relative deferred delivery time..."); + break; + case SUBPARAMETER_ID_PRIORITY_INDICATOR: + mm_dbg (" skipping priority indicator..."); + break; + case SUBPARAMETER_ID_PRIVACY_INDICATOR: + mm_dbg (" skipping privacy indicator..."); + break; + case SUBPARAMETER_ID_REPLY_OPTION: + mm_dbg (" skipping reply option..."); + break; + case SUBPARAMETER_ID_NUMBER_OF_MESSAGES: + mm_dbg (" skipping number of messages..."); + break; + case SUBPARAMETER_ID_ALERT_ON_MESSAGE_DELIVERY: + mm_dbg (" skipping alert on message delivery..."); + break; + case SUBPARAMETER_ID_LANGUAGE_INDICATOR: + mm_dbg (" skipping language indicator..."); + break; + case SUBPARAMETER_ID_CALL_BACK_NUMBER: + mm_dbg (" skipping call back number..."); + break; + case SUBPARAMETER_ID_MESSAGE_DISPLAY_MODE: + mm_dbg (" skipping message display mode..."); + break; + case SUBPARAMETER_ID_MULTIPLE_ENCODING_USER_DATA: + mm_dbg (" skipping multiple encoding user data..."); + break; + case SUBPARAMETER_ID_MESSAGE_DEPOSIT_INDEX: + mm_dbg (" skipping message deposit index..."); + break; + case SUBPARAMETER_ID_SERVICE_CATEGORY_PROGRAM_DATA: + mm_dbg (" skipping service category program data..."); + break; + case SUBPARAMETER_ID_SERVICE_CATEGORY_PROGRAM_RESULT: + mm_dbg (" skipping service category program result..."); + break; + case SUBPARAMETER_ID_MESSAGE_STATUS: + mm_dbg (" skipping message status..."); + break; + case SUBPARAMETER_ID_TP_FAILURE_CAUSE: + mm_dbg (" skipping TP failure case..."); + break; + case SUBPARAMETER_ID_ENHANCED_VMN: + mm_dbg (" skipping enhanced vmn..."); + break; + case SUBPARAMETER_ID_ENHANCED_VMN_ACK: + mm_dbg (" skipping enhanced vmn ack..."); + break; + default: + mm_dbg (" unknown subparameter found: '%u' (ignoring)", + subparameter->parameter_id); + break; + } + } + +#undef PARAMETER_SIZE_CHECK +} + +MMSmsPart * +mm_sms_part_cdma_new_from_binary_pdu (guint index, + const guint8 *pdu, + gsize pdu_len, + GError **error) +{ + MMSmsPart *sms_part; + guint offset; + guint message_type; + + /* Create the new MMSmsPart */ + sms_part = mm_sms_part_new (index, MM_SMS_PDU_TYPE_UNKNOWN); + + if (index != SMS_PART_INVALID_INDEX) + mm_dbg ("Parsing CDMA PDU (%u)...", index); + else + mm_dbg ("Parsing CDMA PDU..."); + +#define PDU_SIZE_CHECK(required_size, check_descr_str) \ + if (pdu_len < required_size) { \ + g_set_error (error, \ + MM_CORE_ERROR, \ + MM_CORE_ERROR_FAILED, \ + "CDMA PDU too short, %s: %" G_GSIZE_FORMAT " < %u", \ + check_descr_str, \ + pdu_len, \ + required_size); \ + mm_sms_part_free (sms_part); \ + return NULL; \ + } + + offset = 0; + + /* First byte: SMS message type */ + PDU_SIZE_CHECK (offset + 1, "cannot read SMS message type"); + message_type = pdu[offset++]; + switch (message_type) { + case MESSAGE_TYPE_POINT_TO_POINT: + case MESSAGE_TYPE_BROADCAST: + case MESSAGE_TYPE_ACKNOWLEDGE: + break; + default: + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Invalid SMS message type (%u)", + message_type); + mm_sms_part_free (sms_part); + return NULL; + } + + /* Now walk parameters one by one */ + while (offset < pdu_len) { + const struct Parameter *parameter; + + PDU_SIZE_CHECK (offset + 2, "cannot read parameter header"); + parameter = (const struct Parameter *)&pdu[offset]; + offset += 2; + + PDU_SIZE_CHECK (offset + parameter->parameter_len, "cannot read parameter value"); + offset += parameter->parameter_len; + + switch (parameter->parameter_id) { + case PARAMETER_ID_TELESERVICE_ID: + mm_dbg (" reading teleservice ID..."); + read_teleservice_id (sms_part, parameter); + break; + case PARAMETER_ID_SERVICE_CATEGORY: + mm_dbg (" reading service category..."); + read_service_category (sms_part, parameter); + break; + case PARAMETER_ID_ORIGINATING_ADDRESS: + mm_dbg (" reading originating address..."); + if (mm_sms_part_get_number (sms_part)) + mm_dbg (" cannot read originating address; an address field was already read"); + else + read_address (sms_part, parameter); + break; + case PARAMETER_ID_ORIGINATING_SUBADDRESS: + mm_dbg (" skipping originating subaddress..."); + break; + case PARAMETER_ID_DESTINATION_ADDRESS: + mm_dbg (" reading destination address..."); + if (mm_sms_part_get_number (sms_part)) + mm_dbg (" cannot read destination address; an address field was already read"); + else + read_address (sms_part, parameter); + break; + case PARAMETER_ID_DESTINATION_SUBADDRESS: + mm_dbg (" skipping destination subaddress..."); + break; + case PARAMETER_ID_BEARER_REPLY_OPTION: + mm_dbg (" reading bearer reply option..."); + read_bearer_reply_option (sms_part, parameter); + break; + case PARAMETER_ID_CAUSE_CODES: + mm_dbg (" reading cause codes..."); + read_cause_codes (sms_part, parameter); + break; + case PARAMETER_ID_BEARER_DATA: + mm_dbg (" reading bearer data..."); + read_bearer_data (sms_part, parameter); + break; + default: + mm_dbg (" unknown parameter found: '%u' (ignoring)", + parameter->parameter_id); + break; + } + } + + /* Check mandatory parameters */ + switch (message_type) { + case MESSAGE_TYPE_POINT_TO_POINT: + if (mm_sms_part_get_cdma_teleservice_id (sms_part) == MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN) + mm_dbg (" mandatory parameter missing: teleservice ID not found or invalid in point-to-point message"); + break; + case MESSAGE_TYPE_BROADCAST: + if (mm_sms_part_get_cdma_service_category (sms_part) == MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN) + mm_dbg (" mandatory parameter missing: service category not found or invalid in broadcast message"); + break; + case MESSAGE_TYPE_ACKNOWLEDGE: + if (mm_sms_part_get_message_reference (sms_part) == 0) + mm_dbg (" mandatory parameter missing: cause codes not found or invalid in acknowledge message"); + break; + } + +#undef PDU_SIZE_CHECK + + return sms_part; +} diff --git a/src/mm-sms-part-cdma.h b/src/mm-sms-part-cdma.h new file mode 100644 index 00000000..bb3253a6 --- /dev/null +++ b/src/mm-sms-part-cdma.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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: + * + * Copyright (C) 2013 Google, Inc. + */ + +#ifndef MM_SMS_PART_CDMA_H +#define MM_SMS_PART_CDMA_H + +#include <glib.h> +#include <ModemManager-enums.h> + +#include "mm-sms-part.h" + +MMSmsPart *mm_sms_part_cdma_new_from_pdu (guint index, + const gchar *hexpdu, + GError **error); + +MMSmsPart *mm_sms_part_cdma_new_from_binary_pdu (guint index, + const guint8 *pdu, + gsize pdu_len, + GError **error); + +#endif /* MM_SMS_PART_CDMA_H */ |