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