diff options
-rw-r--r-- | src/mm-sms-part.c | 532 |
1 files changed, 361 insertions, 171 deletions
diff --git a/src/mm-sms-part.c b/src/mm-sms-part.c index 541810c6..1964fdad 100644 --- a/src/mm-sms-part.c +++ b/src/mm-sms-part.c @@ -32,7 +32,7 @@ #define SMS_TP_MTI_MASK 0x03 #define SMS_TP_MTI_SMS_DELIVER 0x00 -#define SMS_TP_MTI_SMS_SUBMIT_REPORT 0x01 +#define SMS_TP_MTI_SMS_SUBMIT 0x01 #define SMS_TP_MTI_SMS_STATUS_REPORT 0x02 #define SMS_NUMBER_TYPE_MASK 0x70 @@ -259,6 +259,63 @@ sms_decode_text (const guint8 *text, int len, MMSmsEncoding encoding, int bit_of return utf8; } +static guint +relative_to_validity (guint8 relative) +{ + if (relative <= 143) + return (relative + 1) * 5; + + if (relative <= 167) + return 720 + (relative - 143) * 30; + + return (relative - 166) * 1440; +} + +static guint8 +validity_to_relative (guint validity) +{ + if (validity == 0) + return 167; /* 24 hours */ + + if (validity <= 720) { + /* 5 minute units up to 12 hours */ + if (validity % 5) + validity += 5; + return (validity / 5) - 1; + } + + if (validity > 720 && validity <= 1440) { + /* 12 hours + 30 minute units up to 1 day */ + if (validity % 30) + validity += 30; /* round up to next 30 minutes */ + validity = MIN (validity, 1440); + return 143 + ((validity - 720) / 30); + } + + if (validity > 1440 && validity <= 43200) { + /* 2 days up to 1 month */ + if (validity % 1440) + validity += 1440; /* round up to next day */ + validity = MIN (validity, 43200); + return 167 + ((validity - 1440) / 1440); + } + + /* 43200 = 30 days in minutes + * 10080 = 7 days in minutes + * 635040 = 63 weeks in minutes + * 40320 = 4 weeks in minutes + */ + if (validity > 43200 && validity <= 635040) { + /* 5 weeks up to 63 weeks */ + if (validity % 10080) + validity += 10080; /* round up to next week */ + validity = MIN (validity, 635040); + return 196 + ((validity - 40320) / 10080); + } + + return 255; /* 63 weeks */ +} + struct _MMSmsPart { guint index; MMSmsPduType pdu_type; @@ -432,102 +489,283 @@ mm_sms_part_new_from_binary_pdu (guint index, GError **error) { MMSmsPart *sms_part; - guint smsc_addr_num_octets, variable_length_items, msg_start_offset, - sender_addr_num_digits, sender_addr_num_octets, - tp_pid_offset, tp_dcs_offset, user_data_offset, user_data_len, - user_data_len_offset, bit_offset; - MMSmsEncoding user_data_encoding; GByteArray *raw; + guint8 pdu_type; + guint offset; + guint smsc_addr_size_bytes; + guint tp_addr_size_digits; + guint tp_addr_size_bytes; + guint8 validity_format = 0; + gboolean has_udh = FALSE; + /* The following offsets are OPTIONAL, as STATUS REPORTs may not have + * them; we use '0' to indicate their absence */ + guint tp_pid_offset = 0; + guint tp_dcs_offset = 0; + guint tp_user_data_len_offset = 0; + MMSmsEncoding user_data_encoding = MM_SMS_ENCODING_UNKNOWN; + + /* Create the new MMSmsPart */ + sms_part = mm_sms_part_new (index, MM_SMS_PDU_TYPE_UNKNOWN); - /* SMSC, in address format, precedes the TPDU */ - smsc_addr_num_octets = pdu[0]; - variable_length_items = smsc_addr_num_octets; - if (pdu_len < variable_length_items + SMS_MIN_PDU_LEN) { + if (index != SMS_PART_INVALID_INDEX) + mm_dbg ("Parsing PDU (%u)...", index); + else + mm_dbg ("Parsing 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, \ + "PDU too short, %s: %zd < %u", \ + check_descr_str, \ + pdu_len, \ + required_size); \ + mm_sms_part_free (sms_part); \ + return NULL; \ + } + + offset = 0; + + /* ---------------------------------------------------------------------- */ + /* SMSC, in address format, precedes the TPDU + * First byte represents the number of BYTES for the address value */ + PDU_SIZE_CHECK (1, "cannot read SMSC address length"); + smsc_addr_size_bytes = pdu[offset++]; + if (smsc_addr_size_bytes > 0) { + PDU_SIZE_CHECK (offset + smsc_addr_size_bytes, "cannot read SMSC address"); + /* SMSC may not be given in DELIVER PDUs */ + mm_sms_part_take_smsc (sms_part, + sms_decode_address (&pdu[1], 2 * (smsc_addr_size_bytes - 1))); + mm_dbg (" SMSC address parsed: '%s'", mm_sms_part_get_smsc (sms_part)); + offset += smsc_addr_size_bytes; + } else + mm_dbg (" No SMSC address given"); + + + /* ---------------------------------------------------------------------- */ + /* TP-MTI (1 byte) */ + PDU_SIZE_CHECK (offset + 1, "cannot read TP-MTI"); + + pdu_type = (pdu[offset] & SMS_TP_MTI_MASK); + switch (pdu_type) { + case SMS_TP_MTI_SMS_DELIVER: + mm_dbg (" Deliver type PDU detected"); + mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_DELIVER); + break; + case SMS_TP_MTI_SMS_SUBMIT: + mm_dbg (" Submit type PDU detected"); + mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_SUBMIT); + break; + case SMS_TP_MTI_SMS_STATUS_REPORT: + mm_dbg (" Status report type PDU detected"); + mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_STATUS_REPORT); + break; + default: + mm_sms_part_free (sms_part); g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, - "PDU too short (1): %zd < %d", - pdu_len, - variable_length_items + SMS_MIN_PDU_LEN); + "Unhandled message type: 0x%02x", + pdu_type); return NULL; } - /* where in the PDU the actual SMS protocol message begins */ - msg_start_offset = 1 + smsc_addr_num_octets; - sender_addr_num_digits = pdu[msg_start_offset + 1]; + /* Delivery report was requested? */ + if (pdu[offset] & 0x20) + mm_sms_part_set_delivery_report_request (sms_part, TRUE); + + /* PDU with validity? (only in SUBMIT PDUs) */ + if (pdu_type == SMS_TP_MTI_SMS_SUBMIT) + validity_format = pdu[offset] & 0x18; + + /* PDU with user data header? */ + if (pdu[offset] & 0x40) + has_udh = TRUE; + + offset++; + + /* ---------------------------------------------------------------------- */ + /* TP-MR (1 byte, in STATUS_REPORT and SUBMIT PDUs */ + if (pdu_type == SMS_TP_MTI_SMS_STATUS_REPORT || + pdu_type == SMS_TP_MTI_SMS_SUBMIT) { + PDU_SIZE_CHECK (offset + 1, "cannot read message reference"); + + mm_dbg (" message reference: %u", (guint)pdu[offset]); + offset++; + } + - /* - * round the sender address length up to an even number of - * semi-octets, and thus an integral number of octets + /* ---------------------------------------------------------------------- */ + /* TP-DA or TP-OA or TP-RA + * First byte represents the number of DIGITS in the number. + * Round the sender address length up to an even number of + * semi-octets, and thus an integral number of octets. */ - sender_addr_num_octets = (sender_addr_num_digits + 1) >> 1; - variable_length_items += sender_addr_num_octets; - if (pdu_len < variable_length_items + SMS_MIN_PDU_LEN) { - g_set_error (error, - MM_CORE_ERROR, - MM_CORE_ERROR_FAILED, - "PDU too short (2): %zd < %d", - pdu_len, - variable_length_items + SMS_MIN_PDU_LEN); - return NULL; + PDU_SIZE_CHECK (offset + 1, "cannot read number of digits in number"); + tp_addr_size_digits = pdu[offset++]; + tp_addr_size_bytes = (tp_addr_size_digits + 1) >> 1; + + PDU_SIZE_CHECK (offset + tp_addr_size_bytes, "cannot read number"); + mm_sms_part_take_number (sms_part, + sms_decode_address (&pdu[offset], + tp_addr_size_digits)); + mm_dbg (" Number parsed: '%s'", mm_sms_part_get_number (sms_part)); + offset += (1 + tp_addr_size_bytes); /* +1 due to the Type of Address byte */ + + /* ---------------------------------------------------------------------- */ + /* Get timestamps and indexes for TP-PID, TP-DCS and TP-UDL/TP-UD */ + + if (pdu_type == SMS_TP_MTI_SMS_DELIVER) { + PDU_SIZE_CHECK (offset + 9, + "cannot read PID/DCS/Timestamp"); /* 1+1+7=9 */ + + /* ------ TP-PID (1 byte) ------ */ + tp_pid_offset = offset++; + + /* ------ TP-DCS (1 byte) ------ */ + tp_dcs_offset = offset++; + + /* ------ Timestamp (7 bytes) ------ */ + mm_sms_part_take_timestamp (sms_part, + sms_decode_timestamp (&pdu[offset])); + offset += 7; + + tp_user_data_len_offset = offset; + } else if (pdu_type == SMS_TP_MTI_SMS_SUBMIT) { + PDU_SIZE_CHECK (offset + 2 + !!validity_format, + "cannot read PID/DCS/Validity"); /* 1+1=2 */ + + /* ------ TP-PID (1 byte) ------ */ + tp_pid_offset = offset++; + + /* ------ TP-DCS (1 byte) ------ */ + tp_dcs_offset = offset++; + + /* ----------- TP-Validity-Period (1 byte) ----------- */ + if (validity_format) { + switch (validity_format) { + case 0x10: + mm_dbg (" validity available, format relative"); + mm_sms_part_set_validity (sms_part, + relative_to_validity (pdu[offset])); + break; + case 0x08: + /* TODO: support enhanced format; GSM 03.40 */ + mm_dbg (" validity available, format enhanced (not implemented)"); + break; + case 0x18: + /* TODO: support absolute format; GSM 03.40 */ + mm_dbg (" validity available, format absolute (not implemented)"); + break; + default: + /* Cannot happen as we AND with the 0x18 mask */ + g_assert_not_reached(); + } + offset++; + } + + tp_user_data_len_offset = offset; } + else if (pdu_type == SMS_TP_MTI_SMS_STATUS_REPORT) { + /* We have 2 timestamps in status report PDUs: + * first, the timestamp for when the PDU was received in the SMSC + * second, the timestamp for when the PDU was forwarded by the SMSC + */ + PDU_SIZE_CHECK (offset + 15, "cannot read Timestamps/TP-STATUS"); /* 7+7+1=15 */ - tp_pid_offset = msg_start_offset + 3 + sender_addr_num_octets; - tp_dcs_offset = tp_pid_offset + 1; + /* ------ Timestamp (7 bytes) ------ */ + mm_sms_part_take_timestamp (sms_part, + sms_decode_timestamp (&pdu[offset])); + offset += 7; - user_data_len_offset = tp_dcs_offset + 1 + SMS_TIMESTAMP_LEN; - user_data_offset = user_data_len_offset + 1; - user_data_len = pdu[user_data_len_offset]; - user_data_encoding = sms_encoding_type(pdu[tp_dcs_offset]); + /* ------ Discharge Timestamp (7 bytes) ------ */ + mm_sms_part_take_timestamp (sms_part, + sms_decode_timestamp (&pdu[offset])); + offset += 7; - if (user_data_encoding == MM_SMS_ENCODING_GSM7) - variable_length_items += (7 * (user_data_len + 1 )) / 8; - else - variable_length_items += user_data_len; + /* ----- TP-STATUS (1 byte) ------ */ + mm_dbg (" status: %u", (guint)pdu[offset]); + offset++; - if (pdu_len < variable_length_items + SMS_MIN_PDU_LEN) { - g_set_error (error, - MM_CORE_ERROR, - MM_CORE_ERROR_FAILED, - "PDU too short (3): %zd < %d", - pdu_len, - variable_length_items + SMS_MIN_PDU_LEN); - return NULL; + /* ------ TP-PI (1 byte) OPTIONAL ------ */ + if (offset < pdu_len) { + guint next_optional_field_offset = offset + 1; + + /* TP-PID? */ + if (pdu[offset] & 0x01) + tp_pid_offset = next_optional_field_offset++; + + /* TP-DCS? */ + if (pdu[offset] & 0x02) + tp_dcs_offset = next_optional_field_offset++; + + /* TP-UserData? */ + if (pdu[offset] & 0x04) + tp_user_data_len_offset = next_optional_field_offset; + } + } else + g_assert_not_reached (); + + if (tp_pid_offset > 0) { + PDU_SIZE_CHECK (tp_pid_offset + 1, "cannot read TP-PID"); + mm_dbg (" PID: %u", (guint)pdu[tp_pid_offset]); } - /* Only handle SMS-DELIVER */ - if ((pdu[msg_start_offset] & SMS_TP_MTI_MASK) != SMS_TP_MTI_SMS_DELIVER) { - g_set_error (error, - MM_CORE_ERROR, - MM_CORE_ERROR_FAILED, - "Unhandled message type: 0x%02x", - pdu[msg_start_offset]); - return NULL; + /* Grab user data encoding */ + if (tp_dcs_offset > 0) { + PDU_SIZE_CHECK (tp_dcs_offset + 1, "cannot read TP-DCS"); + user_data_encoding = sms_encoding_type(pdu[tp_dcs_offset]); + switch (user_data_encoding) { + case MM_SMS_ENCODING_GSM7: + mm_dbg (" user data encoding is GSM7"); + break; + case MM_SMS_ENCODING_UCS2: + mm_dbg (" user data encoding is UCS2"); + break; + case MM_SMS_ENCODING_8BIT: + mm_dbg (" user data encoding is 8bit"); + break; + default: + mm_dbg (" user data encoding is unknown"); + break; + } + mm_sms_part_set_encoding (sms_part, user_data_encoding); } - /* Create the new MMSmsPart */ - sms_part = mm_sms_part_new (index, MM_SMS_PDU_TYPE_SUBMIT); - mm_sms_part_take_smsc (sms_part, - sms_decode_address (&pdu[1], 2 * (pdu[0] - 1))); - mm_sms_part_take_number (sms_part, - sms_decode_address (&pdu[msg_start_offset + 2], - pdu[msg_start_offset + 1])); - mm_sms_part_take_timestamp (sms_part, - sms_decode_timestamp (&pdu[tp_dcs_offset + 1])); + if (tp_user_data_len_offset > 0) { + guint tp_user_data_size_elements; + guint tp_user_data_size_bytes; + guint tp_user_data_offset; + guint bit_offset; + + PDU_SIZE_CHECK (tp_user_data_len_offset + 1, "cannot read TP-UDL"); + tp_user_data_size_elements = pdu[tp_user_data_len_offset]; + mm_dbg (" user data length: %u elements", tp_user_data_size_elements); + + if (user_data_encoding == MM_SMS_ENCODING_GSM7) + tp_user_data_size_bytes = (7 * (tp_user_data_size_elements + 1 )) / 8; + else + tp_user_data_size_bytes = tp_user_data_size_elements; + mm_dbg (" user data length: %u bytes", tp_user_data_size_bytes); - bit_offset = 0; - if (pdu[msg_start_offset] & SMS_TP_UDHI) { - int udhl, end, offset; - udhl = pdu[user_data_offset] + 1; - end = user_data_offset + udhl; + tp_user_data_offset = tp_user_data_len_offset + 1; + PDU_SIZE_CHECK (tp_user_data_offset + tp_user_data_size_bytes, "cannot read TP-UD"); - for (offset = user_data_offset + 1; offset < end;) { - guint8 ie_id, ie_len; + bit_offset = 0; + if (has_udh) { + guint udhl, end; - ie_id = pdu[offset++]; - ie_len = pdu[offset++]; + udhl = pdu[tp_user_data_offset] + 1; + end = tp_user_data_offset + udhl; - switch (ie_id) { + for (offset = tp_user_data_offset + 1; offset < end;) { + guint8 ie_id, ie_len; + + ie_id = pdu[offset++]; + ie_len = pdu[offset++]; + + switch (ie_id) { case 0x00: /* * Ignore the IE if one of the following is true: @@ -538,12 +776,9 @@ mm_sms_part_new_from_binary_pdu (guint index, pdu[offset + 2] > pdu[offset + 1]) break; - mm_sms_part_set_concat_reference (sms_part, - pdu[offset]); - mm_sms_part_set_concat_max (sms_part, - pdu[offset + 1]); - mm_sms_part_set_concat_sequence (sms_part, - pdu[offset + 2]); + mm_sms_part_set_concat_reference (sms_part, pdu[offset]); + mm_sms_part_set_concat_max (sms_part, pdu[offset + 1]); + mm_sms_part_set_concat_sequence (sms_part, pdu[offset + 2]); break; case 0x08: /* Concatenated short message, 16-bit reference */ @@ -551,62 +786,62 @@ mm_sms_part_new_from_binary_pdu (guint index, pdu[offset + 3] > pdu[offset + 2]) break; - mm_sms_part_set_concat_reference (sms_part, - (pdu[offset] << 8) | pdu[offset + 1]); - mm_sms_part_set_concat_max (sms_part, - pdu[offset + 2]); - mm_sms_part_set_concat_sequence (sms_part, - pdu[offset + 3]); + mm_sms_part_set_concat_reference (sms_part, (pdu[offset] << 8) | pdu[offset + 1]); + mm_sms_part_set_concat_max (sms_part,pdu[offset + 2]); + mm_sms_part_set_concat_sequence (sms_part, pdu[offset + 3]); break; + } + + offset += ie_len; } - offset += ie_len; + /* + * Move past the user data headers to prevent it from being + * decoded into garbage text. + */ + tp_user_data_offset += udhl; + tp_user_data_size_bytes -= udhl; + if (user_data_encoding == MM_SMS_ENCODING_GSM7) { + /* + * Find the number of bits we need to add to the length of the + * user data to get a multiple of 7 (the padding). + */ + bit_offset = (7 - udhl % 7) % 7; + tp_user_data_size_elements -= (udhl * 8 + bit_offset) / 7; + } else + tp_user_data_size_elements -= udhl; } - /* - * Move past the user data headers to prevent it from being - * decoded into garbage text. - */ - user_data_offset += udhl; - if (user_data_encoding == MM_SMS_ENCODING_GSM7) { - /* - * Find the number of bits we need to add to the length of the - * user data to get a multiple of 7 (the padding). + if ( user_data_encoding == MM_SMS_ENCODING_8BIT + || user_data_encoding == MM_SMS_ENCODING_UNKNOWN) { + /* 8-bit encoding is usually binary data, and we have no idea what + * actual encoding the data is in so we can't convert it. */ - bit_offset = (7 - udhl % 7) % 7; - user_data_len -= (udhl * 8 + bit_offset) / 7; - } else - user_data_len -= udhl; - } + mm_dbg ("Skipping SMS part text: 8-bit or Unknown encoding"); + mm_sms_part_set_text (sms_part, ""); + } else { + /* Otherwise if it's 7-bit or UCS2 we can decode it */ + mm_dbg ("Decoding text with '%u' elements", tp_user_data_size_elements); + mm_sms_part_take_text (sms_part, + sms_decode_text (&pdu[tp_user_data_offset], + tp_user_data_size_elements, + user_data_encoding, + bit_offset)); + g_warn_if_fail (sms_part->text != NULL); + } - /* Keep user data encoding */ - mm_sms_part_set_encoding (sms_part, user_data_encoding); + /* Add the raw PDU data */ + raw = g_byte_array_sized_new (tp_user_data_size_bytes); + g_byte_array_append (raw, &pdu[tp_user_data_offset], tp_user_data_size_bytes); + mm_sms_part_take_data (sms_part, raw); + mm_sms_part_set_data_coding_scheme (sms_part, + pdu[tp_dcs_offset] & 0xFF); - if ( user_data_encoding == MM_SMS_ENCODING_8BIT - || user_data_encoding == MM_SMS_ENCODING_UNKNOWN) { - /* 8-bit encoding is usually binary data, and we have no idea what - * actual encoding the data is in so we can't convert it. - */ - mm_dbg ("Skipping SMS part text: 8-bit or Unknown encoding"); - mm_sms_part_set_text (sms_part, ""); - } else { - /* Otherwise if it's 7-bit or UCS2 we can decode it */ - mm_sms_part_take_text (sms_part, - sms_decode_text (&pdu[user_data_offset], user_data_len, - user_data_encoding, bit_offset)); - g_warn_if_fail (sms_part->text != NULL); + if (pdu[tp_dcs_offset] & SMS_DCS_CLASS_VALID) + mm_sms_part_set_class (sms_part, + pdu[tp_dcs_offset] & SMS_DCS_CLASS_MASK); } - /* Add the raw PDU data */ - raw = g_byte_array_sized_new (user_data_len); - g_byte_array_append (raw, &pdu[user_data_offset], user_data_len); - mm_sms_part_take_data (sms_part, raw); - mm_sms_part_set_data_coding_scheme (sms_part, pdu[tp_dcs_offset] & 0xFF); - - if (pdu[tp_dcs_offset] & SMS_DCS_CLASS_VALID) - mm_sms_part_set_class (sms_part, - pdu[tp_dcs_offset] & SMS_DCS_CLASS_MASK); - return sms_part; } @@ -651,51 +886,6 @@ mm_sms_part_encode_address (const gchar *address, return len ? len + 2 : 0; /* addr length + size byte + number type/plan */ } -static guint8 -validity_to_relative (guint validity) -{ - if (validity == 0) - return 167; /* 24 hours */ - - if (validity <= 720) { - /* 5 minute units up to 12 hours */ - if (validity % 5) - validity += 5; - return (validity / 5) - 1; - } - - if (validity > 720 && validity <= 1440) { - /* 12 hours + 30 minute units up to 1 day */ - if (validity % 30) - validity += 30; /* round up to next 30 minutes */ - validity = MIN (validity, 1440); - return 143 + ((validity - 720) / 30); - } - - if (validity > 1440 && validity <= 43200) { - /* 2 days up to 1 month */ - if (validity % 1440) - validity += 1440; /* round up to next day */ - validity = MIN (validity, 43200); - return 167 + ((validity - 1440) / 1440); - } - - /* 43200 = 30 days in minutes - * 10080 = 7 days in minutes - * 635040 = 63 weeks in minutes - * 40320 = 4 weeks in minutes - */ - if (validity > 43200 && validity <= 635040) { - /* 5 weeks up to 63 weeks */ - if (validity % 10080) - validity += 10080; /* round up to next week */ - validity = MIN (validity, 635040); - return 196 + ((validity - 40320) / 10080); - } - - return 255; /* 63 weeks */ -} - /** * mm_sms_part_get_submit_pdu: * |