1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "host/commands/cvd_send_sms/pdu_format_builder.h"
17
18 #include <algorithm>
19 #include <codecvt>
20 #include <cstddef>
21 #include <iomanip>
22 #include <iostream>
23 #include <map>
24 #include <regex>
25 #include <sstream>
26 #include <vector>
27
28 #include "android-base/logging.h"
29 #include "unicode/uchriter.h"
30 #include "unicode/unistr.h"
31 #include "unicode/ustring.h"
32
33 namespace cuttlefish {
34
35 namespace {
36 // 3GPP TS 23.038 V9.1.1 section 6.2.1 - GSM 7 bit Default Alphabet
37 // https://www.etsi.org/deliver/etsi_ts/123000_123099/123038/09.01.01_60/ts_123038v090101p.pdf
38 // clang-format off
39 const std::vector<std::string> kGSM7BitDefaultAlphabet = {
40 "@", "£", "$", "¥", "è", "é", "ù", "ì", "ò", "Ç", "\n", "Ø", "ø", "\r", "Å", "å",
41 "Δ", "_", "Φ", "Γ", "Λ", "Ω", "Π", "Ψ", "Σ", "Θ", "Ξ", "\uffff" /*ESC*/, "Æ", "æ", "ß", "É",
42 " ", "!", "\"", "#", "¤", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
43 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
44 "¡", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
45 "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Ä", "Ö", "Ñ", "Ü", "§",
46 "¿", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
47 "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ä", "ö", "ñ", "ü", "à",
48 };
49 // clang-format on
50
51 // Encodes using the GSM 7bit encoding as defined in 3GPP TS 23.038
52 // https://www.etsi.org/deliver/etsi_ts/123000_123099/123038/09.01.01_60/ts_123038v090101p.pdf
Gsm7bitEncode(const std::string & input)53 static std::string Gsm7bitEncode(const std::string& input) {
54 icu::UnicodeString unicode_str(input.c_str());
55 icu::UCharCharacterIterator iter(unicode_str.getTerminatedBuffer(),
56 unicode_str.length());
57 size_t octects_size = unicode_str.length() - (unicode_str.length() / 8);
58 std::byte octets[octects_size];
59 std::byte* octects_index = octets;
60 int bits_to_write_in_prev_octect = 0;
61 for (; iter.hasNext(); iter.next()) {
62 UChar uchar = iter.current();
63 char dest[5];
64 UErrorCode uerror_code;
65 u_strToUTF8(dest, 5, NULL, &uchar, 1, &uerror_code);
66 if (U_FAILURE(uerror_code)) {
67 LOG(ERROR) << "u_strToUTF8 failed with error: "
68 << u_errorName(uerror_code) << ", with string: " << input;
69 return "";
70 }
71 std::string character(dest);
72 auto found_it = std::find(kGSM7BitDefaultAlphabet.begin(),
73 kGSM7BitDefaultAlphabet.end(), character);
74 if (found_it == kGSM7BitDefaultAlphabet.end()) {
75 LOG(ERROR) << "Character: " << character
76 << " does not exist in GSM 7 bit Default Alphabet";
77 return "";
78 }
79 std::byte code =
80 (std::byte)std::distance(kGSM7BitDefaultAlphabet.begin(), found_it);
81 if (iter.hasPrevious()) {
82 std::byte prev_octect_value = *(octects_index - 1);
83 // Writes the corresponding lowest part in the previous octet.
84 *(octects_index - 1) =
85 code << (8 - bits_to_write_in_prev_octect) | prev_octect_value;
86 }
87 if (bits_to_write_in_prev_octect < 7) {
88 // Writes the remaining highest part in the current octet.
89 *octects_index = code >> bits_to_write_in_prev_octect;
90 bits_to_write_in_prev_octect++;
91 octects_index++;
92 } else { // bits_to_write_in_prev_octect == 7
93 // The 7 bits of the current character were fully packed into the
94 // previous octet.
95 bits_to_write_in_prev_octect = 0;
96 }
97 }
98 std::stringstream result;
99 for (int i = 0; i < octects_size; i++) {
100 result << std::setfill('0') << std::setw(2) << std::hex
101 << std::to_integer<int>(octets[i]);
102 }
103 return result.str();
104 }
105
106 // Validates whether the passed phone number conforms to the E.164 specs,
107 // https://www.itu.int/rec/T-REC-E.164
IsValidE164PhoneNumber(const std::string & number)108 static bool IsValidE164PhoneNumber(const std::string& number) {
109 const static std::regex e164_regex("^\\+?[1-9]\\d{1,14}$");
110 return std::regex_match(number, e164_regex);
111 }
112
113 // Encodes numeric values by using the Semi-Octect representation.
SemiOctectsEncode(const std::string & input)114 static std::string SemiOctectsEncode(const std::string& input) {
115 bool length_is_odd = input.length() % 2 == 1;
116 int end = length_is_odd ? input.length() - 1 : input.length();
117 std::stringstream ss;
118 for (int i = 0; i < end; i += 2) {
119 ss << input[i + 1];
120 ss << input[i];
121 }
122 if (length_is_odd) {
123 ss << "f";
124 ss << input[input.length() - 1];
125 }
126 return ss.str();
127 }
128
129 // Converts to hexadecimal representation filling with a leading 0 if
130 // necessary.
DecimalToHexString(int number)131 static std::string DecimalToHexString(int number) {
132 std::stringstream ss;
133 ss << std::setfill('0') << std::setw(2) << std::hex << number;
134 return ss.str();
135 }
136 } // namespace
137
SetUserData(const std::string & user_data)138 void PDUFormatBuilder::SetUserData(const std::string& user_data) {
139 user_data_ = user_data;
140 }
141
SetSenderNumber(const std::string & sender_number)142 void PDUFormatBuilder::SetSenderNumber(const std::string& sender_number) {
143 sender_number_ = sender_number;
144 }
145
Build()146 std::string PDUFormatBuilder::Build() {
147 if (user_data_.empty()) {
148 LOG(ERROR) << "Empty user data.";
149 return "";
150 }
151 if (sender_number_.empty()) {
152 LOG(ERROR) << "Empty sender phone number.";
153 return "";
154 }
155 if (!IsValidE164PhoneNumber(sender_number_)) {
156 LOG(ERROR) << "Sender phone number"
157 << " \"" << sender_number_ << "\" "
158 << "does not conform with the E.164 format";
159 return "";
160 }
161 std::string sender_number_without_plus =
162 sender_number_[0] == '+' ? sender_number_.substr(1) : sender_number_;
163 int ulength = icu::UnicodeString(user_data_.c_str()).length();
164 if (ulength > 160) {
165 LOG(ERROR) << "Invalid user data as it has more than 160 characters: "
166 << user_data_;
167 return "";
168 }
169 std::string encoded = Gsm7bitEncode(user_data_);
170 if (encoded.empty()) {
171 return "";
172 }
173 std::stringstream ss;
174 ss << "000100" << DecimalToHexString(sender_number_without_plus.length())
175 << "91" // 91 indicates international phone number format.
176 << SemiOctectsEncode(sender_number_without_plus)
177 << "00" // TP-PID. Protocol identifier
178 << "00" // TP-DCS. Data coding scheme. The GSM 7bit default alphabet.
179 << DecimalToHexString(ulength) << encoded;
180 return ss.str();
181 }
182 } // namespace cuttlefish
183