Currently very limited: * Only WMT teleservice. * Only DMTF-encoded numbers. * Only either raw binary data or ASCII-7 text.
1483 lines
52 KiB
C
1483 lines
52 KiB
C
/* -*- 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: %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 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);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
#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 */
|
|
write_bits (&pdu[byte_offset], bit_offset, 1, DIGIT_MODE_DTMF);
|
|
OFFSETS_UPDATE (1);
|
|
|
|
/* Number mode: DIGIT always */
|
|
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;
|
|
}
|
|
write_bits (&pdu[byte_offset], bit_offset, 8, n_digits);
|
|
OFFSETS_UPDATE (8);
|
|
|
|
/* Actual DTMF encoded 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 */
|
|
|
|
/* 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 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 i;
|
|
|
|
#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;
|
|
|
|
/* Message encoding*/
|
|
write_bits (&pdu[byte_offset], bit_offset, 5, data ? ENCODING_OCTET : ENCODING_ASCII_7BIT);
|
|
OFFSETS_UPDATE (5);
|
|
|
|
/* Number of fields */
|
|
num_fields = data ? data->len : strlen (text);
|
|
if (num_fields > 256) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Data too long (max 256 fields, %u given)",
|
|
num_fields);
|
|
return FALSE;
|
|
}
|
|
write_bits (&pdu[byte_offset], bit_offset, 8, num_fields);
|
|
OFFSETS_UPDATE (8);
|
|
|
|
/* Actual text or data */
|
|
if (data) {
|
|
for (i = 0; i < num_fields; i++) {
|
|
write_bits (&pdu[byte_offset], bit_offset, 8, data->data[i]);
|
|
OFFSETS_UPDATE (8);
|
|
}
|
|
} else {
|
|
for (i = 0; i < num_fields; i++) {
|
|
/* ASCII-7 characters given in gchar should have the most
|
|
* significant bit set to 0 */
|
|
if (text[i] & 0x80) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Unsupported character in text: '%u'. Cannot convert to ASCII-7",
|
|
text[i]);
|
|
return FALSE;
|
|
}
|
|
write_bits (&pdu[byte_offset], bit_offset, 7, text[i]);
|
|
OFFSETS_UPDATE (7);
|
|
}
|
|
}
|
|
|
|
#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;
|
|
|
|
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;
|
|
}
|