| // |
| // 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 |