blob: 943819d889c871992b1adbfddf44486188e93673 [file] [log] [blame]
#include "host/commands/cvd_send_sms/pdu_format_builder.h"
#include <algorithm>
#include <codecvt>
#include <cstddef>
#include <iomanip>
#include <iostream>
#include <map>
#include <regex>
#include <sstream>
#include <vector>
#include "android-base/logging.h"
#include "unicode/uchriter.h"
#include "unicode/unistr.h"
#include "unicode/ustring.h"
namespace cuttlefish {
namespace {
// 3GPP TS 23.038 V9.1.1 section 6.2.1 - GSM 7 bit Default Alphabet
// https://www.etsi.org/deliver/etsi_ts/123000_123099/123038/09.01.01_60/ts_123038v090101p.pdf
// clang-format off
const std::vector<std::string> kGSM7BitDefaultAlphabet = {
"@", "£", "$", "¥", "è", "é", "ù", "ì", "ò", "Ç", "\n", "Ø", "ø", "\r", "Å", "å",
"Δ", "_", "Φ", "Γ", "Λ", "Ω", "Π", "Ψ", "Σ", "Θ", "Ξ", u8"\uffff" /*ESC*/, "Æ", "æ", "ß", "É",
" ", "!", "\"", "#", "¤", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
"¡", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
"P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Ä", "Ö", "Ñ", "Ü", "§",
"¿", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ä", "ö", "ñ", "ü", "à",
};
// clang-format on
// Encodes using the GSM 7bit encoding as defined in 3GPP TS 23.038
// https://www.etsi.org/deliver/etsi_ts/123000_123099/123038/09.01.01_60/ts_123038v090101p.pdf
static std::string Gsm7bitEncode(const std::string& input) {
icu::UnicodeString unicode_str(input.c_str());
icu::UCharCharacterIterator iter(unicode_str.getTerminatedBuffer(),
unicode_str.length());
size_t octects_size = unicode_str.length() - (unicode_str.length() / 8);
std::byte octects[octects_size];
std::byte* octects_index = octects;
int bits_to_write_in_prev_octect = 0;
for (; iter.hasNext(); iter.next()) {
UChar uchar = iter.current();
char dest[5];
UErrorCode uerror_code;
u_strToUTF8(dest, 5, NULL, &uchar, 1, &uerror_code);
if (U_FAILURE(uerror_code)) {
LOG(ERROR) << "u_strToUTF8 failed with error: "
<< u_errorName(uerror_code) << ", with string: " << input;
return "";
}
std::string character(dest);
auto found_it = std::find(kGSM7BitDefaultAlphabet.begin(),
kGSM7BitDefaultAlphabet.end(), character);
if (found_it == kGSM7BitDefaultAlphabet.end()) {
LOG(ERROR) << "Character: " << character
<< " does not exist in GSM 7 bit Default Alphabet";
return "";
}
std::byte code =
(std::byte)std::distance(kGSM7BitDefaultAlphabet.begin(), found_it);
if (iter.hasPrevious()) {
std::byte prev_octect_value = *(octects_index - 1);
// Writes the corresponding lowest part in the previous octect.
*(octects_index - 1) =
code << (8 - bits_to_write_in_prev_octect) | prev_octect_value;
}
if (bits_to_write_in_prev_octect < 7) {
// Writes the remaining highest part in the current octect.
*octects_index = code >> bits_to_write_in_prev_octect;
bits_to_write_in_prev_octect++;
octects_index++;
} else { // bits_to_write_in_prev_octect == 7
// The 7 bits of the current character were fully packed into the
// previous octect.
bits_to_write_in_prev_octect = 0;
}
}
std::stringstream result;
for (int i = 0; i < octects_size; i++) {
result << std::setfill('0') << std::setw(2) << std::hex
<< std::to_integer<int>(octects[i]);
}
return result.str();
}
// Validates whether the passed phone number conforms to the E.164 specs,
// https://www.itu.int/rec/T-REC-E.164
static bool IsValidE164PhoneNumber(const std::string& number) {
const static std::regex e164_regex("^\\+?[1-9]\\d{1,14}$");
return std::regex_match(number, e164_regex);
}
// Encodes numeric values by using the Semi-Octect representation.
static std::string SemiOctectsEncode(const std::string& input) {
bool length_is_odd = input.length() % 2 == 1;
int end = length_is_odd ? input.length() - 1 : input.length();
std::stringstream ss;
for (int i = 0; i < end; i += 2) {
ss << input[i + 1];
ss << input[i];
}
if (length_is_odd) {
ss << "f";
ss << input[input.length() - 1];
}
return ss.str();
}
// Converts to hexadecimal representation filling with a leading 0 if
// necessary.
static std::string DecimalToHexString(int number) {
std::stringstream ss;
ss << std::setfill('0') << std::setw(2) << std::hex << number;
return ss.str();
}
} // namespace
void PDUFormatBuilder::SetUserData(const std::string& user_data) {
user_data_ = user_data;
}
void PDUFormatBuilder::SetSenderNumber(const std::string& sender_number) {
sender_number_ = sender_number;
}
std::string PDUFormatBuilder::Build() {
if (user_data_.empty()) {
LOG(ERROR) << "Empty user data.";
return "";
}
if (sender_number_.empty()) {
LOG(ERROR) << "Empty sender phone number.";
return "";
}
if (!IsValidE164PhoneNumber(sender_number_)) {
LOG(ERROR) << "Sender phone number"
<< " \"" << sender_number_ << "\" "
<< "does not conform with the E.164 format";
return "";
}
std::string sender_number_without_plus =
sender_number_[0] == '+' ? sender_number_.substr(1) : sender_number_;
int ulength = icu::UnicodeString(user_data_.c_str()).length();
if (ulength > 160) {
LOG(ERROR) << "Invalid user data as it has more than 160 characters: "
<< user_data_;
return "";
}
std::string encoded = Gsm7bitEncode(user_data_);
if (encoded.empty()) {
return "";
}
std::stringstream ss;
ss << "000100" << DecimalToHexString(sender_number_without_plus.length())
<< "91" // 91 indicates international phone number format.
<< SemiOctectsEncode(sender_number_without_plus)
<< "00" // TP-PID. Protocol identifier
<< "00" // TP-DCS. Data coding scheme. The GSM 7bit default alphabet.
<< DecimalToHexString(ulength) << encoded;
return ss.str();
}
} // namespace cuttlefish