blob: 5cc425e59018bf4c11e9265ff7611f2e20fcc0a7 [file] [log] [blame]
//
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "host/commands/modem_simulator/pdu_parser.h"
#include <algorithm>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <string>
#include <thread>
namespace cuttlefish {
static const std::string kWithoutServiceCenterAddress = "00";
static const std::string kStatusReportIndicator = "06";
static const std::string kSRIAndMMSIndicator = "24"; /* SRI is 1 && MMS is 1*/
static const std::string kUDHIAndSRIAndMMSIndicator = "64"; /* UDHI is 1 && SRI is 1 && MMS is 1*/
PDUParser::PDUParser(std::string &pdu) {
is_valid_pdu_ = DecodePDU(pdu);
}
bool PDUParser::IsValidPDU() {
return is_valid_pdu_;
}
/**
* PDU format:
* SCA PDU-Type MR OA PID DCS VP UDL UD
* bytes: 1-12 1 1 2-12 1 1 0 1 0-140
* eg. 00 21 00 0B 91 5155255155F4 00 00 0C AB58AD56ABC962B55A8D06
*/
// 00 01 00 05 81 0180F6 00 00 0D 61B2996C0691CD6433190402
bool PDUParser::DecodePDU(std::string& pdu) {
// At least: SCA(1) + PDU-Type(1) + MR(1) + OA(2) + PID(1) + DSC(1) + UDL(1)
auto pdu_total_length = pdu.size();
if (pdu_total_length < 8) {
return false;
}
std::string_view pdu_view = pdu;
size_t pos = 0;
/* 1. SMSC Address Length: 1 byte */
std::string temp = pdu.substr(0, 2);
pos += 2;
if (temp != kWithoutServiceCenterAddress) {
auto smsc_length = Hex2ToByte(temp);
pos += smsc_length * 2; // Skip SMSC Address
}
/* 2. PDU-Type: 1 byte */
pdu_type_ = pdu_view.substr(std::min(pos, pdu_total_length), 2);
pos += 2;
/* 3. MR: 1 byte */
message_reference_ = pdu_view.substr(std::min(pos, pdu_total_length), 2);
pos += 2;
/* 4. Originator Address Length: 1 byte */
temp = pdu_view.substr(std::min(pos, pdu_total_length), 2);
auto oa_length = Hex2ToByte(temp);
if (oa_length & 0x01) oa_length += 1;
/* 5. Originator Address including OA length */
originator_address_ = pdu_view.substr(std::min(pos, pdu_total_length), (oa_length + 4));
pos += (oa_length + 4);
/* 6. Protocol ID: 1 byte */
protocol_id_ = pdu_view.substr(std::min(pos, pdu_total_length), 2);
pos += 2;
/* 7. Data Code Scheme: 1 byte */
data_code_scheme_ = pdu_view.substr(std::min(pos, pdu_total_length), 2);
pos += 2;
/* 8. User Data Length: 1 byte */
temp = pdu_view.substr(std::min(pos, pdu_total_length), 2);
auto ud_length = Hex2ToByte(temp);
/* 9. User Data including UDL */
user_data_ = pdu_view.substr(std::min(pos, pdu_total_length));
if (data_code_scheme_ == "00") { // GSM_7BIT
pos += ud_length * 2 + 2;
int offset = ud_length / 8;
pos -= offset * 2;
} else if (data_code_scheme_ == "08") { // GSM_UCS2
pos += ud_length;
} else {
pos += ud_length * 2 + 2;
}
if (pos == pdu_total_length) {
return true;
}
return false;
}
/**
* The PDU-Type of receiver
* BIT 7 6 5 4 3 2 1 0
* Param RP UDHI SRI - - MMS MTI MTI
* When SRR bit is 1, it represents that SMS status report should be reported.
*/
std::string PDUParser::CreatePDU() {
if (!is_valid_pdu_) return "";
// Ignore SMSC address, default to be '00'
std::string pdu = kWithoutServiceCenterAddress;
int pdu_type = Hex2ToByte(pdu_type_);
if (pdu_type & 0x40) {
pdu += kUDHIAndSRIAndMMSIndicator;
} else {
pdu += kSRIAndMMSIndicator;
}
pdu += originator_address_ + protocol_id_ + data_code_scheme_;
pdu += GetCurrentTimeStamp();
pdu += user_data_;
return pdu;
}
/**
* the PDU-Type of sender
* BIT 7 6 5 4 3 2 1 0
* Param RP UDHI SRR VPF VPF RD MTI MTI
* When SRR bit is 1, it represents that SMS status report should be reported.
*/
bool PDUParser::IsNeededStatuReport() {
if (!is_valid_pdu_) return false;
int pdu_type = Hex2ToByte(pdu_type_);
if (pdu_type & 0x20) {
return true;
}
return false;
}
std::string PDUParser::CreateStatuReport(int message_reference) {
if (!is_valid_pdu_) return "";
std::string pdu = kWithoutServiceCenterAddress;
pdu += kStatusReportIndicator;
std::stringstream ss;
ss << std::setfill('0') << std::setw(2) << std::hex << message_reference;
pdu += ss.str();
pdu += originator_address_;
pdu += GetCurrentTimeStamp();
std::this_thread::sleep_for(std::chrono::seconds(1));
pdu += GetCurrentTimeStamp();
pdu += "00"; /* "00" means that SMS have been sent successfully */
return pdu;
}
std::string PDUParser::CreateRemotePDU(std::string& host_port) {
if (host_port.size() != 4 || !is_valid_pdu_) {
return "";
}
std::string pdu = kWithoutServiceCenterAddress + pdu_type_ + message_reference_;
// Remove the remote port
std::string number = GetPhoneNumberFromAddress();
auto new_phone_number = number.substr(0, number.size() - 4);;
new_phone_number.append(host_port);
if (new_phone_number.size() & 1) {
new_phone_number.append("F");
}
// Add OA length and type
pdu += originator_address_.substr(0,
originator_address_.size() - new_phone_number .size());
pdu += BCDToString(new_phone_number); // Add local host port
pdu += protocol_id_;
pdu += data_code_scheme_;
pdu += user_data_;
return pdu;
}
std::string PDUParser::GetPhoneNumberFromAddress() {
if (!is_valid_pdu_) return "";
// Skip OA length and type
std::string address;
if (originator_address_.size() == 18) {
address = originator_address_.substr(6);
} else {
address = originator_address_.substr(4);
}
return BCDToString(address);
}
int PDUParser::HexCharToInt(char c) {
if (c >= '0' && c <= '9') return (c - '0');
if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
return -1; // Invalid hex char
}
int PDUParser::Hex2ToByte(const std::string& hex) {
int hi = HexCharToInt(hex[0]);
int lo = HexCharToInt(hex[1]);
if (hi < 0 || lo < 0) {
return -1;
}
return ( (hi << 4) | lo );
}
std::string PDUParser::IntToHexString(int value) {
int hi = value / 10;
int lo = value % 10;
return std::to_string(lo) + std::to_string(hi);
}
std::string PDUParser::IntToHexStringTimeZoneDiff(int tzdiff_hour) {
// https://en.wikipedia.org/wiki/GSM_03.40
int delta = 0;
if (tzdiff_hour < 0) {
tzdiff_hour = -tzdiff_hour;
delta = 8;
}
const int tzdiff_quarter_hour = 4 * tzdiff_hour;
const int hi = tzdiff_quarter_hour / 10 + delta;
const int lo = tzdiff_quarter_hour % 10;
std::stringstream ss;
ss << std::hex << lo;
ss << std::hex << hi;
return ss.str();
}
std::string PDUParser::BCDToString(std::string& data) {
std::string dst;
if (data.empty()) {
return "";
}
int length = data.size();
if (length & 0x01) { /* Must be even */
return "";
}
for (int i = 0; i < length; i += 2) {
dst += data[i + 1];
dst += data[i];
}
if (dst[length -1] == 'F') {
dst.replace(length -1, length, "\0");
}
return dst;
}
std::string PDUParser::GetCurrentTimeStamp() {
std::string time_stamp;
auto now = std::time(0);
auto local_time = *std::localtime(&now);
auto gm_time = *std::gmtime(&now);
auto t_local_time = std::mktime(&local_time);
auto t_gm_time = std::mktime(&gm_time);
auto tzdiff = (int)std::difftime(t_local_time, t_gm_time) / (60 * 60);
time_stamp += IntToHexString(local_time.tm_year % 100);
time_stamp += IntToHexString(local_time.tm_mon + 1);
time_stamp += IntToHexString(local_time.tm_mday);
time_stamp += IntToHexString(local_time.tm_hour);
time_stamp += IntToHexString(local_time.tm_min);
time_stamp += IntToHexString(local_time.tm_sec);
time_stamp += IntToHexStringTimeZoneDiff(tzdiff);
return time_stamp;
}
} // namespace cuttlefish