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