blob: 3aa6b45544a3710e6780b9305917dc59e079cb29 [file] [log] [blame]
/* -*- 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-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, &parameter->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, &parameter->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 (&parameter->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 (&parameter->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 (&parameter->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 (&parameter->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 (&parameter->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 (&parameter->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 (&parameter->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 (&parameter->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 (&parameter->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 (&parameter->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 (&parameter->parameter_value[0], 0, 6);
mm_dbg (" sequence: %u", sequence);
error_class = read_bits (&parameter->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 *)&parameter->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;
}