diff options
author | Aleksander Morgado <aleksander@lanedo.com> | 2013-08-19 18:27:19 +0200 |
---|---|---|
committer | Aleksander Morgado <aleksander@lanedo.com> | 2013-10-25 19:47:19 +0200 |
commit | 2eb5e0d578adbb6c630d88f8d55a4eae0c887cdd (patch) | |
tree | e5f7ce23d4503059f00a99e6f718e132d3c81d4a | |
parent | cadbc0412d5dee16cd484b52b14df4f19ae47cf6 (diff) | |
download | ModemManager-2eb5e0d578adbb6c630d88f8d55a4eae0c887cdd.tar.gz |
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.
-rw-r--r-- | src/mm-sms-part-cdma.c | 370 | ||||
-rw-r--r-- | src/mm-sms-part-cdma.h | 4 | ||||
-rw-r--r-- | src/tests/test-sms-part-cdma.c | 99 |
3 files changed, 473 insertions, 0 deletions
diff --git a/src/mm-sms-part-cdma.c b/src/mm-sms-part-cdma.c index 26def6898..5a913427a 100644 --- a/src/mm-sms-part-cdma.c +++ b/src/mm-sms-part-cdma.c @@ -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; +} diff --git a/src/mm-sms-part-cdma.h b/src/mm-sms-part-cdma.h index bb3253a6d..14c2c1df7 100644 --- a/src/mm-sms-part-cdma.h +++ b/src/mm-sms-part-cdma.h @@ -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 */ diff --git a/src/tests/test-sms-part-cdma.c b/src/tests/test-sms-part-cdma.c index cad20323f..f95b7ac81 100644 --- a/src/tests/test-sms-part-cdma.c +++ b/src/tests/test-sms-part-cdma.c @@ -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 (); } |