OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "sms_message.h" |
| 6 |
| 7 #include <glog/logging.h> |
| 8 |
| 9 #include "utilities.h" |
| 10 |
| 11 static const uint8_t INTL_E164_NUMBER_FORMAT = 0x91; |
| 12 static const uint8_t SMSC_TIMESTAMP_LEN = 7; |
| 13 static const size_t MIN_PDU_LEN = 7 + SMSC_TIMESTAMP_LEN; |
| 14 |
| 15 static char NibbleToChar(uint8_t nibble) { |
| 16 switch (nibble) { |
| 17 case 0: return '0'; |
| 18 case 1: return '1'; |
| 19 case 2: return '2'; |
| 20 case 3: return '3'; |
| 21 case 4: return '4'; |
| 22 case 5: return '5'; |
| 23 case 6: return '6'; |
| 24 case 7: return '7'; |
| 25 case 8: return '8'; |
| 26 case 9: return '9'; |
| 27 case 10: return '*'; |
| 28 case 11: return '#'; |
| 29 case 12: return 'a'; |
| 30 case 13: return 'b'; |
| 31 case 14: return 'c'; |
| 32 case 0xff: return '\0'; // padding nibble |
| 33 } |
| 34 return '\0'; |
| 35 } |
| 36 |
| 37 // Convert an array of octets into a BCD string. Each octet consists |
| 38 // of two nibbles which are converted to hex characters. Those hex |
| 39 // characters are the digits of the BCD string. The lower nibble is |
| 40 // the more significant digit. |
| 41 static std::string SemiOctetsToBcdString(const uint8_t* octets, |
| 42 int num_octets) { |
| 43 std::string bcd; |
| 44 |
| 45 for (int i = 0; i < num_octets; ++i) { |
| 46 char first = NibbleToChar(octets[i] & 0xf); |
| 47 char second = NibbleToChar((octets[i] >> 4) & 0xf); |
| 48 |
| 49 if (first != '\0') |
| 50 bcd += first; |
| 51 if (second != '\0') |
| 52 bcd += second; |
| 53 } |
| 54 return bcd; |
| 55 } |
| 56 |
| 57 SmsMessage* SmsMessage::CreateMessage(const uint8_t* pdu, size_t pdu_len) { |
| 58 utilities::DumpHex(pdu, pdu_len); |
| 59 |
| 60 // Make sure the PDU is of a valid size |
| 61 if (pdu_len < MIN_PDU_LEN) { |
| 62 LOG(INFO) << "PDU too short: " << pdu_len << " vs. " << MIN_PDU_LEN; |
| 63 return NULL; |
| 64 } |
| 65 |
| 66 // Format of message: |
| 67 // |
| 68 // 1 octet - length of SMSC information in octets, including type field |
| 69 // 1 octet - type of address of SMSC (value 0x91 is international E.164) |
| 70 // variable - SMSC address |
| 71 // 1 octet - first octet of SMS-DELIVER (value = 0x04) |
| 72 // 1 octet - length of sender address in decimal digits (semi-octets) |
| 73 // 1 octet - type of sender address (value 0x91 is international E.164) |
| 74 // variable - sender address |
| 75 // 1 octet - protocol identifier (value = 0) |
| 76 // 1 octet - data coding scheme (value = 0) |
| 77 // 7 octets - SMSC timestamp |
| 78 // 1 octet - user data length in septets |
| 79 // variable - user data (body of message) |
| 80 |
| 81 // Do a bunch of validity tests first so we can bail out early |
| 82 // if we're not able to handle the PDU. We first check the validity |
| 83 // of all length fields and make sure the PDU length is consistent |
| 84 // with those values. |
| 85 |
| 86 uint8_t smsc_addr_num_octets = pdu[0]; |
| 87 uint8_t variable_length_items = smsc_addr_num_octets; |
| 88 |
| 89 if (pdu_len < variable_length_items + MIN_PDU_LEN) { |
| 90 LOG(INFO) << "PDU too short: " << pdu_len << " vs. " |
| 91 << variable_length_items + MIN_PDU_LEN; |
| 92 return NULL; |
| 93 } |
| 94 |
| 95 // where in the PDU the actual SMS protocol message begins |
| 96 uint8_t msg_start_offset = 1 + smsc_addr_num_octets; |
| 97 uint8_t sender_addr_num_digits = pdu[msg_start_offset + 1]; |
| 98 // round the sender address length up to an even number of semi-octets, |
| 99 // and thus an integral number of octets |
| 100 uint8_t sender_addr_num_octets = (sender_addr_num_digits + 1) >> 1; |
| 101 variable_length_items += sender_addr_num_octets; |
| 102 if (pdu_len < variable_length_items + MIN_PDU_LEN) { |
| 103 LOG(INFO) << "PDU too short: " << pdu_len << " vs. " |
| 104 << variable_length_items + MIN_PDU_LEN; |
| 105 return NULL; |
| 106 } |
| 107 |
| 108 uint8_t tp_pid_offset = msg_start_offset + 3 + sender_addr_num_octets; |
| 109 uint8_t user_data_offset = tp_pid_offset + 2 + SMSC_TIMESTAMP_LEN; |
| 110 uint8_t user_data_num_septets = pdu[user_data_offset]; |
| 111 variable_length_items += (7 * (user_data_num_septets + 1 )) / 8; |
| 112 |
| 113 if (pdu_len < variable_length_items + MIN_PDU_LEN) { |
| 114 LOG(INFO) << "PDU too short: " << pdu_len << " vs. " |
| 115 << variable_length_items + MIN_PDU_LEN; |
| 116 return NULL; |
| 117 } |
| 118 |
| 119 // wow do some validity checks on the values of several fields in the PDU |
| 120 |
| 121 // smsc number format must be international, E.164 |
| 122 if (pdu[1] != INTL_E164_NUMBER_FORMAT) { |
| 123 LOG(INFO) << "Invalid SMSC address format: " << std::hex << (int)pdu[1] |
| 124 << " vs. " << std::hex << INTL_E164_NUMBER_FORMAT; |
| 125 return NULL; |
| 126 } |
| 127 // we only handle SMS-DELIVER messages, with more-messages-to-send false |
| 128 if ((pdu[msg_start_offset] & 0x07) != 0x04) { |
| 129 LOG(INFO) << "Unhandled message type: " << std::hex |
| 130 << (int)pdu[msg_start_offset] << " vs. 0x04"; |
| 131 return NULL; |
| 132 } |
| 133 // we only handle the basic protocol identifier |
| 134 if (pdu[tp_pid_offset] != 0) { |
| 135 LOG(INFO) << "Unhandled protocol identifier: " << std::hex |
| 136 << (int)pdu[tp_pid_offset] << " vs. 0x00"; |
| 137 return NULL; |
| 138 } |
| 139 // for data coding scheme, we only handle the default alphabet, i.e. GSM7 |
| 140 if (pdu[tp_pid_offset+1] != 0) { |
| 141 LOG(INFO) << "Unhandled data coding scheme: " << std::hex |
| 142 << (int)pdu[tp_pid_offset+1] << " vs. 0x00"; |
| 143 return NULL; |
| 144 } |
| 145 |
| 146 std::string smsc_addr = SemiOctetsToBcdString(&pdu[2], |
| 147 smsc_addr_num_octets-1); |
| 148 std::string sender_addr = SemiOctetsToBcdString(&pdu[msg_start_offset+3], |
| 149 sender_addr_num_octets); |
| 150 std::string sc_timestamp = SemiOctetsToBcdString(&pdu[tp_pid_offset+2], |
| 151 SMSC_TIMESTAMP_LEN-1); |
| 152 std::string msg_text = utilities::Gsm7ToUtf8String(&pdu[user_data_offset]); |
| 153 |
| 154 // The last two semi-octets of the timestamp indicate an offset from |
| 155 // GMT, and are handled differently than the first 12 semi-octets. |
| 156 uint8_t toff_octet = pdu[tp_pid_offset+1+SMSC_TIMESTAMP_LEN]; |
| 157 sc_timestamp += (toff_octet & 0x8) ? '-' : '+'; |
| 158 uint8_t offset_in_hours = |
| 159 ((toff_octet & 0x7) << 4 | (toff_octet & 0xf0) >> 4) / 4; |
| 160 sc_timestamp += (char)((offset_in_hours / 10) + '0'); |
| 161 sc_timestamp += (char)((offset_in_hours % 10) + '0'); |
| 162 |
| 163 return new SmsMessage(smsc_addr, sender_addr, sc_timestamp, msg_text); |
| 164 } |
OLD | NEW |