/* -*- 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 #include #include #include #define _LIBMM_INSIDE_MM #include #include "mm-charsets.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; static const gchar * encoding_to_string (Encoding encoding) { static const gchar *encoding_str[] = { "octet", "extend protocol message", "7-bit ASCII", "IA5", "unicode", "shift-j is", "korean", "latin/hebrew", "latin", "7-bit GSM", "GSM data coding scheme" }; if (encoding >= ENCODING_OCTET && encoding <= ENCODING_GSM_DCS) return encoding_str[encoding]; return "unknown"; } /*****************************************************************************/ /* 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: %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: %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: %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: %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: %u", sequence); error_class = read_bits (¶meter->parameter_value[0], 6, 2); mm_dbg (" error class: %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: %u", cause_code); } else cause_code = 0; delivery_state = cause_code_to_delivery_state (error_class, cause_code); mm_dbg (" delivery state: %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: %u", header_ind); } static void read_bearer_data_user_data (MMSmsPart *sms_part, const struct Parameter *subparameter) { guint8 message_encoding; guint8 message_type = 0; 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 user data, 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); mm_dbg (" message encoding: %s", encoding_to_string (message_encoding)); /* 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); /* Now, process actual text or data */ switch (message_encoding) { case ENCODING_OCTET: { GByteArray *data; guint i; SUBPARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 8)) / 8)); data = g_byte_array_sized_new (num_fields); g_byte_array_set_size (data, num_fields); for (i = 0; i < num_fields; i++) { data->data[i] = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 8); OFFSETS_UPDATE (8); } mm_dbg (" data: (%u bytes)", num_fields); mm_sms_part_take_data (sms_part, data); break; } case ENCODING_ASCII_7BIT: { gchar *text; guint i; SUBPARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 7)) / 8)); text = g_malloc (num_fields + 1); for (i = 0; i < num_fields; i++) { text[i] = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 7); OFFSETS_UPDATE (7); } text[i] = '\0'; mm_dbg (" text: '%s'", text); mm_sms_part_take_text (sms_part, text); break; } case ENCODING_LATIN: { gchar *latin; gchar *text; guint i; SUBPARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 8)) / 8)); latin = g_malloc (num_fields + 1); for (i = 0; i < num_fields; i++) { latin[i] = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 8); OFFSETS_UPDATE (8); } latin[i] = '\0'; text = g_convert (latin, -1, "UTF-8", "ISO−8859−1", NULL, NULL, NULL); if (!text) { mm_dbg (" text/data: ignored (latin to UTF-8 conversion error)"); } else { mm_dbg (" text: '%s'", text); mm_sms_part_take_text (sms_part, text); } g_free (latin); break; } case ENCODING_UNICODE: { gchar *utf16; gchar *text; guint i; guint num_bytes; /* 2 bytes per field! */ num_bytes = num_fields * 2; SUBPARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_bytes * 8)) / 8)); utf16 = g_malloc (num_bytes); for (i = 0; i < num_bytes; i++) { utf16[i] = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 8); OFFSETS_UPDATE (8); } text = g_convert (utf16, num_bytes, "UTF-8", "UCS-2BE", NULL, NULL, NULL); if (!text) { mm_dbg (" text/data: ignored (UTF-16 to UTF-8 conversion error)"); } else { mm_dbg (" text: '%s'", text); mm_sms_part_take_text (sms_part, text); } g_free (utf16); break; } default: mm_dbg (" text/data: ignored (unsupported encoding)"); } #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; } /*****************************************************************************/ /* Write 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 * * NOTE! The bits being set should be 0 initially. */ static void write_bits (guint8 *bytes, guint8 o_bits, guint8 n_bits, guint8 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); /* Write only in the first byte */ if (o_bits + n_bits <= 8) { bytes[0] |= (bits & ((1 << n_bits) - 1)) << (8 - o_bits - n_bits); return; } /* Write (8 - o_bits) in the first byte and (n_bits - (8 - o_bits)) in the second byte */ bits_in_first = 8 - o_bits; bits_in_second = n_bits - bits_in_first; write_bits (&bytes[0], o_bits, bits_in_first, (bits >> bits_in_second)); write_bits (&bytes[1], 0, bits_in_second, bits); } /*****************************************************************************/ static guint8 dtmf_from_ascii (guint8 ascii) { if (ascii >= '1' && ascii <= '9') return ascii - '0'; if (ascii == '0') return 10; if (ascii == '*') return 11; if (ascii == '#') return 12; mm_dbg (" invalid ascii digit in dtmf conversion: %c", ascii); return 0; } static gboolean write_teleservice_id (MMSmsPart *part, guint8 *pdu, guint *absolute_offset, GError **error) { guint16 aux16; mm_dbg (" writing teleservice ID..."); if (mm_sms_part_get_cdma_teleservice_id (part) != MM_SMS_CDMA_TELESERVICE_ID_WMT) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Teleservice '%s' not supported", mm_sms_cdma_teleservice_id_get_string ( mm_sms_part_get_cdma_teleservice_id (part))); return FALSE; } mm_dbg (" teleservice ID: %s (%u)", mm_sms_cdma_teleservice_id_get_string (MM_SMS_CDMA_TELESERVICE_ID_WMT), MM_SMS_CDMA_TELESERVICE_ID_WMT); /* Teleservice ID: WMT always */ pdu[0] = PARAMETER_ID_TELESERVICE_ID; pdu[1] = 2; /* parameter_len, always 2 */ aux16 = GUINT16_TO_BE (MM_SMS_CDMA_TELESERVICE_ID_WMT); memcpy (&pdu[2], &aux16, 2); *absolute_offset += 4; return TRUE; } static gboolean write_destination_address (MMSmsPart *part, guint8 *pdu, guint *absolute_offset, GError **error) { const gchar *number; guint bit_offset; guint byte_offset; guint n_digits; guint i; mm_dbg (" writing destination address..."); #define OFFSETS_UPDATE(n_bits) do { \ bit_offset += n_bits; \ if (bit_offset >= 8) { \ bit_offset-=8; \ byte_offset++; \ } \ } while (0) number = mm_sms_part_get_number (part); n_digits = strlen (number); pdu[0] = PARAMETER_ID_DESTINATION_ADDRESS; /* Write parameter length at the end */ byte_offset = 2; bit_offset = 0; /* Digit mode: DTMF always */ mm_dbg (" digit mode: dtmf"); write_bits (&pdu[byte_offset], bit_offset, 1, DIGIT_MODE_DTMF); OFFSETS_UPDATE (1); /* Number mode: DIGIT always */ mm_dbg (" number mode: digit"); write_bits (&pdu[byte_offset], bit_offset, 1, NUMBER_MODE_DIGIT); OFFSETS_UPDATE (1); /* Number type and numbering plan only needed in ASCII digit mode, so skip */ /* Number of fields */ if (n_digits > 256) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Number too long (max 256 digits, %u given)", n_digits); return FALSE; } mm_dbg (" num fields: %u", n_digits); write_bits (&pdu[byte_offset], bit_offset, 8, n_digits); OFFSETS_UPDATE (8); /* Actual DTMF encoded number */ mm_dbg (" address: %s", number); for (i = 0; i < n_digits; i++) { guint8 dtmf; dtmf = dtmf_from_ascii (number[i]); if (!dtmf) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Unsupported character in number: '%c'. Cannot convert to DTMF", number[i]); return FALSE; } write_bits (&pdu[byte_offset], bit_offset, 4, dtmf); OFFSETS_UPDATE (4); } #undef OFFSETS_UPDATE /* Write parameter length (remove header length to offset) */ byte_offset += !!bit_offset - 2; if (byte_offset > 256) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Number too long (max 256 bytes, %u given)", byte_offset); return FALSE; } pdu[1] = byte_offset; *absolute_offset += (2 + pdu[1]); return TRUE; } static gboolean write_bearer_data_message_identifier (MMSmsPart *part, guint8 *pdu, guint *parameter_offset, GError **error) { pdu[0] = SUBPARAMETER_ID_MESSAGE_ID; pdu[1] = 3; /* subparameter_len, always 3 */ mm_dbg (" writing message identifier: submit"); /* Message type */ write_bits (&pdu[2], 0, 4, TELESERVICE_MESSAGE_TYPE_SUBMIT); /* Skip adding a message id; assume it's filled in by device */ /* And no need for a header ind value, always false */ *parameter_offset += 5; return TRUE; } static void decide_best_encoding (const gchar *text, GByteArray **out, guint *num_fields, guint *num_bits_per_field, Encoding *encoding) { guint latin_unsupported = 0; guint ascii_unsupported = 0; guint i; guint len; len = strlen (text); /* Check if we can do ASCII-7 */ for (i = 0; i < len; i++) { if (text[i] & 0x80) { ascii_unsupported++; break; } } /* If ASCII-7 already supported, done we are */ if (!ascii_unsupported) { *out = g_byte_array_sized_new (len); g_byte_array_append (*out, (const guint8 *)text, len); *num_fields = len; *num_bits_per_field = 7; *encoding = ENCODING_ASCII_7BIT; return; } /* Check if we can do Latin encoding */ mm_charset_get_encoded_len (text, MM_MODEM_CHARSET_8859_1, &latin_unsupported); if (!latin_unsupported) { *out = g_byte_array_sized_new (len); mm_modem_charset_byte_array_append (*out, text, FALSE, MM_MODEM_CHARSET_8859_1); *num_fields = (*out)->len; *num_bits_per_field = 8; *encoding = ENCODING_LATIN; return; } /* If no Latin and no ASCII, default to UTF-16 */ *out = g_byte_array_sized_new (len * 2); mm_modem_charset_byte_array_append (*out, text, FALSE, MM_MODEM_CHARSET_UCS2); *num_fields = (*out)->len / 2; *num_bits_per_field = 16; *encoding = ENCODING_UNICODE; } static gboolean write_bearer_data_user_data (MMSmsPart *part, guint8 *pdu, guint *parameter_offset, GError **error) { const gchar *text; const GByteArray *data; guint bit_offset = 0; guint byte_offset = 0; guint num_fields; guint num_bits_per_field; guint i; Encoding encoding; GByteArray *converted = NULL; const GByteArray *aux; guint num_bits_per_iter; mm_dbg (" writing user data..."); #define OFFSETS_UPDATE(n_bits) do { \ bit_offset += n_bits; \ if (bit_offset >= 8) { \ bit_offset-=8; \ byte_offset++; \ } \ } while (0) text = mm_sms_part_get_text (part); data = mm_sms_part_get_data (part); g_assert (text || data); g_assert (!(!text && !data)); pdu[0] = SUBPARAMETER_ID_USER_DATA; /* Write parameter length at the end */ byte_offset = 2; bit_offset = 0; /* Text or Data */ if (text) { decide_best_encoding (text, &converted, &num_fields, &num_bits_per_field, &encoding); aux = (const GByteArray *)converted; } else { aux = data; num_fields = data->len; num_bits_per_field = 8; encoding = ENCODING_OCTET; } /* Message encoding*/ mm_dbg (" message encoding: %s", encoding_to_string (encoding)); write_bits (&pdu[byte_offset], bit_offset, 5, encoding); OFFSETS_UPDATE (5); /* Number of fields */ if (num_fields > 256) { if (converted) g_byte_array_unref (converted); g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Data too long (max 256 fields, %u given)", num_fields); return FALSE; } mm_dbg (" num fields: %u", num_fields); write_bits (&pdu[byte_offset], bit_offset, 8, num_fields); OFFSETS_UPDATE (8); /* For ASCII-7, write 7 bits in each iteration; for the remaining ones * go byte per byte */ if (text) mm_dbg (" text: '%s'", text); else mm_dbg (" data: (%u bytes)", num_fields); num_bits_per_iter = num_bits_per_field < 8 ? num_bits_per_field : 8; for (i = 0; i < aux->len; i++) { write_bits (&pdu[byte_offset], bit_offset, num_bits_per_iter, aux->data[i]); OFFSETS_UPDATE (num_bits_per_iter); } if (converted) g_byte_array_unref (converted); #undef OFFSETS_UPDATE /* Write subparameter length (remove header length to offset) */ byte_offset += !!bit_offset - 2; if (byte_offset > 256) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Data or Text too long (max 256 bytes, %u given)", byte_offset); return FALSE; } pdu[1] = byte_offset; *parameter_offset += (2 + pdu[1]); return TRUE; } static gboolean write_bearer_data (MMSmsPart *part, guint8 *pdu, guint *absolute_offset, GError **error) { GError *inner_error = NULL; guint offset = 0; mm_dbg (" writing bearer data..."); pdu[0] = PARAMETER_ID_BEARER_DATA; /* Write parameter length at the end */ offset = 2; if (!write_bearer_data_message_identifier (part, &pdu[offset], &offset, &inner_error)) mm_dbg ("Error writing message identifier: %s", inner_error->message); else if (!write_bearer_data_user_data (part, &pdu[offset], &offset, &inner_error)) mm_dbg ("Error writing user data: %s", inner_error->message); if (inner_error) { g_propagate_error (error, inner_error); g_prefix_error (error, "Error writing bearer data: "); return FALSE; } /* Write parameter length (remove header length to offset) */ offset -= 2; if (offset > 256) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Bearer data too long (max 256 bytes, %u given)", offset); return FALSE; } pdu[1] = offset; *absolute_offset += (2 + pdu[1]); return TRUE; } guint8 * mm_sms_part_cdma_get_submit_pdu (MMSmsPart *part, guint *out_pdulen, GError **error) { GError *inner_error = NULL; guint offset = 0; guint8 *pdu; g_return_val_if_fail (mm_sms_part_get_number (part) != NULL, NULL); g_return_val_if_fail (mm_sms_part_get_text (part) != NULL || mm_sms_part_get_data (part) != NULL, NULL); if (mm_sms_part_get_pdu_type (part) != MM_SMS_PDU_TYPE_CDMA_SUBMIT) { g_set_error (error, MM_MESSAGE_ERROR, MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER, "Invalid PDU type to generate a 'submit' PDU: '%s'", mm_sms_pdu_type_get_string (mm_sms_part_get_pdu_type (part))); return NULL; } mm_dbg ("Creating PDU for part..."); /* Current max size estimations: * Message type: 1 byte * Teleservice ID: 5 bytes * Destination address: 2 + 256 bytes * Bearer data: 2 + 256 bytes */ pdu = g_malloc0 (1024); /* First byte: SMS message type */ pdu[offset++] = MESSAGE_TYPE_POINT_TO_POINT; if (!write_teleservice_id (part, &pdu[offset], &offset, &inner_error)) mm_dbg ("Error writing Teleservice ID: %s", inner_error->message); else if (!write_destination_address (part, &pdu[offset], &offset, &inner_error)) mm_dbg ("Error writing destination address: %s", inner_error->message); else if (!write_bearer_data (part, &pdu[offset], &offset, &inner_error)) mm_dbg ("Error writing bearer data: %s", inner_error->message); if (inner_error) { g_propagate_error (error, inner_error); g_prefix_error (error, "Cannot create CDMA SMS part: "); g_free (pdu); return NULL; } *out_pdulen = offset; return pdu; }