Index: sms_message.cc |
diff --git a/sms_message.cc b/sms_message.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5f4001bcb08500819efb6cada55ceae95971dfb1 |
--- /dev/null |
+++ b/sms_message.cc |
@@ -0,0 +1,164 @@ |
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "sms_message.h" |
+ |
+#include <glog/logging.h> |
+ |
+#include "utilities.h" |
+ |
+static const uint8_t INTL_E164_NUMBER_FORMAT = 0x91; |
+static const uint8_t SMSC_TIMESTAMP_LEN = 7; |
+static const size_t MIN_PDU_LEN = 7 + SMSC_TIMESTAMP_LEN; |
+ |
+static char NibbleToChar(uint8_t nibble) { |
+ switch (nibble) { |
+ case 0: return '0'; |
+ case 1: return '1'; |
+ case 2: return '2'; |
+ case 3: return '3'; |
+ case 4: return '4'; |
+ case 5: return '5'; |
+ case 6: return '6'; |
+ case 7: return '7'; |
+ case 8: return '8'; |
+ case 9: return '9'; |
+ case 10: return '*'; |
+ case 11: return '#'; |
+ case 12: return 'a'; |
+ case 13: return 'b'; |
+ case 14: return 'c'; |
+ case 0xff: return '\0'; // padding nibble |
+ } |
+ return '\0'; |
+} |
+ |
+// Convert an array of octets into a BCD string. Each octet consists |
+// of two nibbles which are converted to hex characters. Those hex |
+// characters are the digits of the BCD string. The lower nibble is |
+// the more significant digit. |
+static std::string SemiOctetsToBcdString(const uint8_t* octets, |
+ int num_octets) { |
+ std::string bcd; |
+ |
+ for (int i = 0; i < num_octets; ++i) { |
+ char first = NibbleToChar(octets[i] & 0xf); |
+ char second = NibbleToChar((octets[i] >> 4) & 0xf); |
+ |
+ if (first != '\0') |
+ bcd += first; |
+ if (second != '\0') |
+ bcd += second; |
+ } |
+ return bcd; |
+} |
+ |
+SmsMessage* SmsMessage::CreateMessage(const uint8_t* pdu, size_t pdu_len) { |
+ utilities::DumpHex(pdu, pdu_len); |
+ |
+ // Make sure the PDU is of a valid size |
+ if (pdu_len < MIN_PDU_LEN) { |
+ LOG(INFO) << "PDU too short: " << pdu_len << " vs. " << MIN_PDU_LEN; |
+ return NULL; |
+ } |
+ |
+ // Format of message: |
+ // |
+ // 1 octet - length of SMSC information in octets, including type field |
+ // 1 octet - type of address of SMSC (value 0x91 is international E.164) |
+ // variable - SMSC address |
+ // 1 octet - first octet of SMS-DELIVER (value = 0x04) |
+ // 1 octet - length of sender address in decimal digits (semi-octets) |
+ // 1 octet - type of sender address (value 0x91 is international E.164) |
+ // variable - sender address |
+ // 1 octet - protocol identifier (value = 0) |
+ // 1 octet - data coding scheme (value = 0) |
+ // 7 octets - SMSC timestamp |
+ // 1 octet - user data length in septets |
+ // variable - user data (body of message) |
+ |
+ // Do a bunch of validity tests first so we can bail out early |
+ // if we're not able to handle the PDU. We first check the validity |
+ // of all length fields and make sure the PDU length is consistent |
+ // with those values. |
+ |
+ uint8_t smsc_addr_num_octets = pdu[0]; |
+ uint8_t variable_length_items = smsc_addr_num_octets; |
+ |
+ if (pdu_len < variable_length_items + MIN_PDU_LEN) { |
+ LOG(INFO) << "PDU too short: " << pdu_len << " vs. " |
+ << variable_length_items + MIN_PDU_LEN; |
+ return NULL; |
+ } |
+ |
+ // where in the PDU the actual SMS protocol message begins |
+ uint8_t msg_start_offset = 1 + smsc_addr_num_octets; |
+ uint8_t sender_addr_num_digits = pdu[msg_start_offset + 1]; |
+ // round the sender address length up to an even number of semi-octets, |
+ // and thus an integral number of octets |
+ uint8_t sender_addr_num_octets = (sender_addr_num_digits + 1) >> 1; |
+ variable_length_items += sender_addr_num_octets; |
+ if (pdu_len < variable_length_items + MIN_PDU_LEN) { |
+ LOG(INFO) << "PDU too short: " << pdu_len << " vs. " |
+ << variable_length_items + MIN_PDU_LEN; |
+ return NULL; |
+ } |
+ |
+ uint8_t tp_pid_offset = msg_start_offset + 3 + sender_addr_num_octets; |
+ uint8_t user_data_offset = tp_pid_offset + 2 + SMSC_TIMESTAMP_LEN; |
+ uint8_t user_data_num_septets = pdu[user_data_offset]; |
+ variable_length_items += (7 * (user_data_num_septets + 1 )) / 8; |
+ |
+ if (pdu_len < variable_length_items + MIN_PDU_LEN) { |
+ LOG(INFO) << "PDU too short: " << pdu_len << " vs. " |
+ << variable_length_items + MIN_PDU_LEN; |
+ return NULL; |
+ } |
+ |
+ // wow do some validity checks on the values of several fields in the PDU |
+ |
+ // smsc number format must be international, E.164 |
+ if (pdu[1] != INTL_E164_NUMBER_FORMAT) { |
+ LOG(INFO) << "Invalid SMSC address format: " << std::hex << (int)pdu[1] |
+ << " vs. " << std::hex << INTL_E164_NUMBER_FORMAT; |
+ return NULL; |
+ } |
+ // we only handle SMS-DELIVER messages, with more-messages-to-send false |
+ if ((pdu[msg_start_offset] & 0x07) != 0x04) { |
+ LOG(INFO) << "Unhandled message type: " << std::hex |
+ << (int)pdu[msg_start_offset] << " vs. 0x04"; |
+ return NULL; |
+ } |
+ // we only handle the basic protocol identifier |
+ if (pdu[tp_pid_offset] != 0) { |
+ LOG(INFO) << "Unhandled protocol identifier: " << std::hex |
+ << (int)pdu[tp_pid_offset] << " vs. 0x00"; |
+ return NULL; |
+ } |
+ // for data coding scheme, we only handle the default alphabet, i.e. GSM7 |
+ if (pdu[tp_pid_offset+1] != 0) { |
+ LOG(INFO) << "Unhandled data coding scheme: " << std::hex |
+ << (int)pdu[tp_pid_offset+1] << " vs. 0x00"; |
+ return NULL; |
+ } |
+ |
+ std::string smsc_addr = SemiOctetsToBcdString(&pdu[2], |
+ smsc_addr_num_octets-1); |
+ std::string sender_addr = SemiOctetsToBcdString(&pdu[msg_start_offset+3], |
+ sender_addr_num_octets); |
+ std::string sc_timestamp = SemiOctetsToBcdString(&pdu[tp_pid_offset+2], |
+ SMSC_TIMESTAMP_LEN-1); |
+ std::string msg_text = utilities::Gsm7ToUtf8String(&pdu[user_data_offset]); |
+ |
+ // The last two semi-octets of the timestamp indicate an offset from |
+ // GMT, and are handled differently than the first 12 semi-octets. |
+ uint8_t toff_octet = pdu[tp_pid_offset+1+SMSC_TIMESTAMP_LEN]; |
+ sc_timestamp += (toff_octet & 0x8) ? '-' : '+'; |
+ uint8_t offset_in_hours = |
+ ((toff_octet & 0x7) << 4 | (toff_octet & 0xf0) >> 4) / 4; |
+ sc_timestamp += (char)((offset_in_hours / 10) + '0'); |
+ sc_timestamp += (char)((offset_in_hours % 10) + '0'); |
+ |
+ return new SmsMessage(smsc_addr, sender_addr, sc_timestamp, msg_text); |
+} |