summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@lanedo.com>2013-08-19 18:27:19 +0200
committerAleksander Morgado <aleksander@lanedo.com>2013-10-25 19:47:19 +0200
commit2eb5e0d578adbb6c630d88f8d55a4eae0c887cdd (patch)
treee5f7ce23d4503059f00a99e6f718e132d3c81d4a
parentcadbc0412d5dee16cd484b52b14df4f19ae47cf6 (diff)
downloadModemManager-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.c370
-rw-r--r--src/mm-sms-part-cdma.h4
-rw-r--r--src/tests/test-sms-part-cdma.c99
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 ();
}