blob: e3f0dd7104fff8ac3dd92de57ae14e882472bef8 [file] [log] [blame]
// Copyright (c) 2012 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 "cromo/sms_message.h"
#include <base/logging.h>
#include <base/stl_util.h>
#include "cromo/utilities.h"
namespace cromo {
static const uint8_t kMsgTypeMask = 0x03;
static const uint8_t kMsgTypeDeliver = 0x00;
// udhi is "User Data Header Indicator"
static const uint8_t kTpUdhi = 0x40;
static const uint8_t kTypeOfAddrNumMask = 0x70;
static const uint8_t kTypeOfAddrNumIntl = 0x10;
static const uint8_t kTypeOfAddrNumAlpha = 0x50;
// SMS user data header information element IDs
static const uint8_t kConcatenatedSms8bit = 0x00;
static const uint8_t kConcatenatedSms16bit = 0x08;
static const uint8_t kSmscTimestampLen = 7;
static const size_t kMinPduLen = 7 + kSmscTimestampLen;
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 15: 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) {
const char first = NibbleToChar(octets[i] & 0xf);
const char second = NibbleToChar((octets[i] >> 4) & 0xf);
if (first != '\0')
bcd += first;
if (second != '\0')
bcd += second;
}
return bcd;
}
static std::string DecodeAddress(const uint8_t* octets,
uint8_t type,
int num_octets) {
std::string addr;
if ((type & kTypeOfAddrNumMask) != kTypeOfAddrNumAlpha) {
addr = SemiOctetsToBcdString(octets, num_octets);
if ((type & kTypeOfAddrNumMask) == kTypeOfAddrNumIntl)
addr = "+" + addr;
} else {
const size_t datalen = (num_octets * 8) / 7;
addr = utilities::Gsm7ToUtf8String(octets, num_octets, datalen, 0);
}
return addr;
}
// Helper class to make it easy to extract successive bytes and byte
// ranges from a binary buffer.
class Bytes {
public:
Bytes(const uint8_t* pdu, int len) : pdu_(pdu), len_(len), offset_(0) {}
// Return the number of bytes remaining to be consumed.
int BytesLeft() const {
return len_ - offset_;
}
// Return the next byte, or 0 if the buffer has been consumed.
// Advances the internal pointer by one.
uint8_t NextByte() {
if (BytesLeft() >= 1)
return pdu_[offset_++];
else
return 0;
}
// Return a pointer to the next N bytes, or nullptr if there aren't that many.
// Advances the internal pointer by N if successful.
const uint8_t* NextBytes(int n) {
if (BytesLeft() >= n) {
const uint8_t* bytes = pdu_ + offset_;
offset_ += n;
return bytes;
} else {
return nullptr;
}
}
private:
// Buffer of bytes.
const uint8_t* pdu_;
// Length of the buffer.
const int len_;
// Current position in the buffer.
int offset_;
};
// 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
// 1 octet - data coding scheme
// 7 octets - SMSC timestamp
// 1 octet - user data length (in septets for GSM7, else octets)
// variable (0 or more octets) user data header
// variable - user data (body of message)
static bool parse_smsc_address(Bytes* bytes, std::string* smsc_address) {
if (bytes->BytesLeft() < 2) {
LOG(ERROR) << "PDU truncated in SMSC address header";
return false;
}
const int smsc_addr_num_octets = bytes->NextByte() - 1;
const int smsc_addr_type = bytes->NextByte();
if (bytes->BytesLeft() < smsc_addr_num_octets) {
LOG(ERROR) << "PDU truncated in SMSC address";
return false;
}
*smsc_address = DecodeAddress(bytes->NextBytes(smsc_addr_num_octets),
smsc_addr_type,
smsc_addr_num_octets);
return true;
}
static bool parse_sender_address(Bytes* bytes, std::string* sender_address) {
if (bytes->BytesLeft() < 2) {
LOG(ERROR) << "PDU truncated in sender address header";
return false;
}
const int sender_addr_num_digits = bytes->NextByte();
// round the sender address length up to an even number of semi-octets,
// and thus an integral number of octets
const int sender_addr_num_octets = (sender_addr_num_digits + 1) >> 1;
const int sender_addr_type = bytes->NextByte();
if (bytes->BytesLeft() < sender_addr_num_octets) {
LOG(ERROR) << "PDU truncated in sender address";
return false;
}
*sender_address = DecodeAddress(bytes->NextBytes(sender_addr_num_octets),
sender_addr_type,
sender_addr_num_octets);
return true;
}
static bool parse_timestamp(Bytes* bytes, std::string* timestamp) {
if (bytes->BytesLeft() < kSmscTimestampLen) {
LOG(ERROR) << "PDU truncated in timestamp";
return false;
}
*timestamp = SemiOctetsToBcdString(bytes->NextBytes(kSmscTimestampLen - 1),
kSmscTimestampLen - 1);
// The last two semi-octets of the timestamp indicate an offset from
// GMT, where bit 3 of the first semi-octet is interpreted as a sign bit
const int tzoff_octet = bytes->NextByte();
const char sign = (tzoff_octet & 0x8) ? '-' : '+';
*timestamp += sign;
const int quarter_hours = (tzoff_octet & 0x7) * 10 +
((tzoff_octet >> 4) & 0xf);
*timestamp += (quarter_hours / 40) + '0';
*timestamp += (quarter_hours / 4) % 10 + '0';
return true;
}
static bool parse_user_data_header(Bytes* bytes,
int flags,
int user_data_len,
int* user_data_header_len,
int* part_reference,
int* part_sequence,
int* part_count) {
*part_reference = 0;
*part_sequence = 1;
*part_count = 1;
*user_data_header_len = 0;
if ((flags & kTpUdhi) == 0)
return true;
*user_data_header_len = bytes->NextByte() + 1; // Include the length itself
if (*user_data_header_len == 0)
return true;
if (bytes->BytesLeft() < *user_data_header_len) {
LOG(ERROR) << "PDU truncated in user data header";
return false;
}
// The user data is made up of a number of information elements,
// which are each composed of an ID octet, a length octet, and the data.
// The length octet is the length of the data, not of the entire element.
for (int ie_offset = 1;
ie_offset < *user_data_header_len; ) {
int ie_id = bytes->NextByte();
int ie_len = bytes->NextByte();
if (ie_id == kConcatenatedSms8bit && ie_len == 3) {
*part_reference = bytes->NextByte();
*part_count = bytes->NextByte();
*part_sequence = bytes->NextByte();
} else if (ie_id == kConcatenatedSms16bit && ie_len == 4) {
*part_reference = bytes->NextByte() << 8;
*part_reference |= bytes->NextByte();
*part_count = bytes->NextByte();
*part_sequence = bytes->NextByte();
} else {
// Unknown information elements are simply skipped.
bytes->NextBytes(ie_len);
}
ie_offset += ie_len + 2;
}
return true;
}
static bool parse_text(Bytes* bytes,
int user_data_len,
int user_data_header_len,
int data_coding_scheme,
std::string* text) {
enum {kDcsUnknown, kDcsGsm7, kDcsUcs2, kDcs8bit} scheme = kDcsUnknown;
switch ((data_coding_scheme >> 4) & 0xf) {
// General data coding group
case 0: case 1:
case 2: case 3:
switch (data_coding_scheme & 0x0c) {
case 0x8:
scheme = kDcsUcs2;
break;
case 0:
case 0xc: // reserved - spec says to treat it as default alphabet
scheme = kDcsGsm7;
break;
case 0x4:
scheme = kDcs8bit;
break;
}
break;
// Message waiting group (default alphabet)
case 0xc:
case 0xd:
scheme = kDcsGsm7;
break;
// Message waiting group (UCS2 alphabet)
case 0xe:
scheme = kDcsUcs2;
break;
// Data coding/message class group
case 0xf:
switch (data_coding_scheme & 0x04) {
case 0:
scheme = kDcsGsm7;
break;
case 0x4:
scheme = kDcs8bit;
break;
}
break;
// Reserved coding group values - spec says to treat it as default alphabet
default:
scheme = kDcsGsm7;
break;
}
if (scheme == kDcsUnknown) {
LOG(WARNING) << "Unhandled data coding scheme: " << std::hex
<< data_coding_scheme;
return false;
}
if (scheme == kDcsGsm7) {
int bit_offset = 0;
if (user_data_header_len != 0) {
const int len_adjust = (user_data_header_len * 8 + 6) / 7;
user_data_len -= len_adjust;
bit_offset = len_adjust * 7 - user_data_header_len * 8;
}
const int user_data_octets = (user_data_len * 7 + bit_offset + 7) / 8;
if (bytes->BytesLeft() < user_data_octets) {
LOG(ERROR) << "PDU truncated in message text - needed "
<< user_data_octets << " bytes, had "
<< bytes->BytesLeft();
return false;
}
*text = utilities::Gsm7ToUtf8String(bytes->NextBytes(user_data_octets),
user_data_octets,
user_data_len,
bit_offset);
} else if (scheme == kDcsUcs2) {
user_data_len -= user_data_header_len;
if (bytes->BytesLeft() < user_data_len) {
LOG(ERROR) << "PDU truncated in message text - needed "
<< user_data_len << " bytes, had "
<< bytes->BytesLeft();
return false;
}
*text = utilities::Ucs2ToUtf8String(bytes->NextBytes(user_data_len),
user_data_len);
} else { // 8-bit data: just copy it as-is
user_data_len -= user_data_header_len;
if (bytes->BytesLeft() < user_data_len) {
LOG(ERROR) << "PDU truncated in message text - needed "
<< user_data_len << " bytes, had "
<< bytes->BytesLeft();
return false;
}
text->assign(reinterpret_cast<const char*>(bytes->NextBytes(user_data_len)),
user_data_len);
}
return true;
}
SmsMessageFragment* SmsMessageFragment::CreateFragment(const uint8_t* pdu,
size_t pdu_len,
int index) {
if (false) { // Change to true for debug logging of PDU data
std::string hexpdu;
static const char kHexDigits[]="0123456789abcdef";
for (unsigned int i = 0 ; i < pdu_len ; i++) {
hexpdu += kHexDigits[pdu[i] >> 4];
hexpdu += kHexDigits[pdu[i] & 0xf];
}
LOG(INFO) << "PDU: " << hexpdu;
}
if (pdu_len < kMinPduLen) {
LOG(ERROR) << "PDU too short - needed at least " << kMinPduLen
<< " bytes, had " << pdu_len;
return nullptr;
}
Bytes bytes(pdu, pdu_len);
std::string smsc_address;
if (!parse_smsc_address(&bytes, &smsc_address))
return nullptr;
const int flags = bytes.NextByte();
// we only handle SMS-DELIVER messages
if ((flags & kMsgTypeMask) != kMsgTypeDeliver) {
LOG(WARNING) << "Unhandled message type: have "
<< std::hex << static_cast<int>(flags & kMsgTypeMask)
<< " need " << std::hex << static_cast<int>(kMsgTypeDeliver);
return nullptr;
}
std::string sender_address;
if (!parse_sender_address(&bytes, &sender_address))
return nullptr;
bytes.NextByte(); // skip over the protocol byte
const int data_coding_scheme = bytes.NextByte();
std::string sc_timestamp;
if (!parse_timestamp(&bytes, &sc_timestamp))
return nullptr;
const int user_data_len = bytes.NextByte();
int user_data_header_len;
int part_reference, part_sequence, part_count;
if (!parse_user_data_header(&bytes, flags, user_data_len,
&user_data_header_len,
&part_reference, &part_sequence, &part_count))
return nullptr;
std::string message_text;
if (!parse_text(&bytes, user_data_len, user_data_header_len,
data_coding_scheme, &message_text))
return nullptr;
return new SmsMessageFragment(smsc_address,
sender_address,
sc_timestamp,
message_text,
part_reference, part_sequence, part_count,
index);
}
SmsMessage::SmsMessage(SmsMessageFragment* base)
: base_(base),
num_remaining_parts_(base->part_count() - 1),
fragments_(base->part_count()) {
fragments_[base->part_sequence() - 1] = base;
LOG(INFO) << "Created new message with base ref " << base->part_reference();
}
void SmsMessage::AddFragment(SmsMessageFragment* sms) {
if (sms->part_reference() != base_->part_reference()) {
LOG(WARNING) << "Attempt to add SMS part with reference "
<< sms->part_reference()
<< " to multipart SMS with reference "
<< base_->part_reference();
return;
}
int sequence = sms->part_sequence();
if (sequence > base_->part_count()) {
LOG(WARNING) << "SMS part out of range: "
<< sequence << " vs. " << base_->part_count();
return;
}
if (fragments_[sequence - 1]) {
LOG(WARNING) << "Part " << sequence << " already exists in message";
return;
}
num_remaining_parts_--;
fragments_[sequence - 1] = sms;
}
bool SmsMessage::IsComplete() const {
return num_remaining_parts_ == 0;
}
const std::string& SmsMessage::GetMessageText() {
// This could be more clever about not recomputing if the message is complete.
composite_text_.clear();
for (int i = 0; i < base_->part_count(); i++) {
if (fragments_[i])
composite_text_ += fragments_[i]->text();
}
return composite_text_;
}
std::vector<int>* SmsMessage::MessageIndexList() const {
std::vector<int>* ret = new std::vector<int>();
for (std::vector<SmsMessageFragment*>::const_iterator it = fragments_.begin();
it != fragments_.end();
++it) {
if (*it)
ret->push_back((*it)->index());
}
return ret;
}
SmsMessage::~SmsMessage() {
STLDeleteElements(&fragments_);
}
} // namespace cromo