sms-part-cdma: new CDMA SMS creator
Currently very limited: * Only WMT teleservice. * Only DMTF-encoded numbers. * Only either raw binary data or ASCII-7 text.
This commit is contained in:
@@ -1110,3 +1110,373 @@ mm_sms_part_cdma_new_from_binary_pdu (guint index,
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -30,4 +30,8 @@ MMSmsPart *mm_sms_part_cdma_new_from_binary_pdu (guint index,
|
||||
gsize pdu_len,
|
||||
GError **error);
|
||||
|
||||
guint8 *mm_sms_part_cdma_get_submit_pdu (MMSmsPart *part,
|
||||
guint *out_pdulen,
|
||||
GError **error);
|
||||
|
||||
#endif /* MM_SMS_PART_CDMA_H */
|
||||
|
||||
@@ -211,6 +211,103 @@ test_invalid_address_length (void)
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_created_by_us (void)
|
||||
{
|
||||
static const guint8 pdu[] = {
|
||||
/* message type */
|
||||
0x00,
|
||||
/* teleservice id */
|
||||
0x00, 0x02,
|
||||
0x10, 0x02,
|
||||
/* destination address */
|
||||
0x04, 0x07,
|
||||
0x02, 0x8C, 0xE9, 0x5D, 0xCC, 0x65, 0x80,
|
||||
/* bearer data */
|
||||
0x08, 0x0D,
|
||||
0x00, 0x03, 0x20, 0x00, 0x00, /* message id */
|
||||
0x01, 0x06, 0x10, 0x24, 0x18, 0x30, 0x60, 0x80 /* user_data */
|
||||
};
|
||||
|
||||
common_test_part_from_pdu (
|
||||
pdu, sizeof (pdu),
|
||||
MM_SMS_CDMA_TELESERVICE_ID_WMT,
|
||||
MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN,
|
||||
"3305773196",
|
||||
0,
|
||||
"AAAA");
|
||||
}
|
||||
|
||||
/********************* PDU CREATOR TESTS *********************/
|
||||
|
||||
static void
|
||||
common_test_create_pdu (MMSmsCdmaTeleserviceId teleservice_id,
|
||||
const gchar *number,
|
||||
const gchar *text,
|
||||
const guint8 *data,
|
||||
gsize data_size,
|
||||
const guint8 *expected,
|
||||
gsize expected_size)
|
||||
{
|
||||
MMSmsPart *part;
|
||||
guint8 *pdu;
|
||||
guint len = 0;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (number != NULL);
|
||||
|
||||
part = mm_sms_part_new (0, MM_SMS_PDU_TYPE_CDMA_SUBMIT);
|
||||
mm_sms_part_set_cdma_teleservice_id (part, teleservice_id);
|
||||
mm_sms_part_set_number (part, number);
|
||||
if (text)
|
||||
mm_sms_part_set_text (part, text);
|
||||
else {
|
||||
GByteArray *data_bytearray;
|
||||
|
||||
data_bytearray = g_byte_array_sized_new (data_size);
|
||||
g_byte_array_append (data_bytearray, data, data_size);
|
||||
mm_sms_part_take_data (part, data_bytearray);
|
||||
}
|
||||
|
||||
pdu = mm_sms_part_cdma_get_submit_pdu (part, &len, &error);
|
||||
|
||||
trace_pdu (pdu, len);
|
||||
|
||||
g_assert_no_error (error);
|
||||
g_assert (pdu != NULL);
|
||||
g_assert_cmpuint (len, ==, expected_size);
|
||||
g_assert_cmpint (memcmp (pdu, expected, len), ==, 0);
|
||||
|
||||
g_free (pdu);
|
||||
}
|
||||
|
||||
static void
|
||||
test_create_pdu_text (void)
|
||||
{
|
||||
static const char *number = "3305773196";
|
||||
static const char *text = "AAAA";
|
||||
static const guint8 expected[] = {
|
||||
/* message type */
|
||||
0x00,
|
||||
/* teleservice id */
|
||||
0x00, 0x02,
|
||||
0x10, 0x02,
|
||||
/* destination address */
|
||||
0x04, 0x07,
|
||||
0x02, 0x8C, 0xE9, 0x5D, 0xCC, 0x65, 0x80,
|
||||
/* bearer data */
|
||||
0x08, 0x0D,
|
||||
0x00, 0x03, 0x20, 0x00, 0x00, /* message id */
|
||||
0x01, 0x06, 0x10, 0x24, 0x18, 0x30, 0x60, 0x80 /* user_data */
|
||||
};
|
||||
|
||||
common_test_create_pdu (MM_SMS_CDMA_TELESERVICE_ID_WMT,
|
||||
number,
|
||||
text,
|
||||
NULL, 0,
|
||||
expected, sizeof (expected));
|
||||
}
|
||||
|
||||
/************************************************************/
|
||||
|
||||
void
|
||||
@@ -243,6 +340,8 @@ int main (int argc, char **argv)
|
||||
g_test_add_func ("/MM/SMS/CDMA/PDU-Parser/pdu1", test_pdu1);
|
||||
g_test_add_func ("/MM/SMS/CDMA/PDU-Parser/invalid-parameter-length", test_invalid_parameter_length);
|
||||
g_test_add_func ("/MM/SMS/CDMA/PDU-Parser/invalid-address-length", test_invalid_address_length);
|
||||
g_test_add_func ("/MM/SMS/CDMA/PDU-Parser/created-by-us", test_created_by_us);
|
||||
g_test_add_func ("/MM/SMS/CDMA/PDU-Creator/Text", test_create_pdu_text);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user