1 //
2 // Copyright (C) 2021 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 "common/libs/confui/packet.h"
17
18 #include <algorithm>
19
20 namespace cuttlefish {
21 namespace confui {
22 namespace packet {
ReadRawData(SharedFD s)23 static std::optional<std::vector<std::uint8_t>> ReadRawData(SharedFD s) {
24 if (!s->IsOpen()) {
25 ConfUiLog(ERROR) << "file, socket, etc, is not open to read";
26 return std::nullopt;
27 }
28 packet::PayloadHeader p;
29 auto nread = ReadExactBinary(s, &p);
30
31 if (nread != sizeof(p)) {
32 ConfUiLog(ERROR) << nread << " and sizeof(p) = " << sizeof(p)
33 << " not matching";
34 return std::nullopt;
35 }
36 if (p.payload_length_ == 0) {
37 return {{}};
38 }
39
40 if (p.payload_length_ >= packet::kMaxPayloadLength) {
41 ConfUiLog(ERROR) << "Payload length " << p.payload_length_
42 << " must be less than " << packet::kMaxPayloadLength;
43 return std::nullopt;
44 }
45
46 std::unique_ptr<char[]> buf{new char[p.payload_length_ + 1]};
47 nread = ReadExact(s, buf.get(), p.payload_length_);
48 buf[p.payload_length_] = 0;
49 if (nread != p.payload_length_) {
50 ConfUiLog(ERROR) << "The length ReadRawData read does not match.";
51 return std::nullopt;
52 }
53 std::vector<std::uint8_t> result{buf.get(), buf.get() + nread};
54
55 return {result};
56 }
57
ParseRawData(const std::vector<std::uint8_t> & data_to_parse)58 static std::optional<ParsedPacket> ParseRawData(
59 const std::vector<std::uint8_t>& data_to_parse) {
60 /*
61 * data_to_parse has 0 in it, so it is not exactly "your (text) std::string."
62 * If we type-cast data_to_parse to std::string and use 3rd party std::string-
63 * processing libraries, the outcome might be incorrect. However, the header
64 * part has no '\0' in it, and is actually a sequence of letters, or a text.
65 * So, we use android::base::Split() to take the header
66 *
67 */
68 std::string as_string{data_to_parse.begin(), data_to_parse.end()};
69 auto tokens = android::base::Split(as_string, ":");
70 CHECK(tokens.size() >= 3)
71 << "Raw packet for confirmation UI must have at least"
72 << " three components.";
73 /**
74 * Here is how the raw data, i.e. tokens[2:] looks like
75 *
76 * n:l[0]:l[1]:l[2]:...:l[n-1]:data[0]data[1]data[2]...data[n]
77 *
78 * Thus it basically has the number of items, the lengths of each item,
79 * and the byte representation of each item. n and l[i] are separated by ':'
80 * Note that the byte representation may have ':' in it. This could mess
81 * up the parsing if we totally depending on ':' separation.
82 *
83 * However, it is safe to assume that there's no ':' inside n or
84 * the string for l[i]. So, we do anyway split the data_to_parse by ':',
85 * and take n and from l[0] through l[n-1] only.
86 */
87 std::string session_id = tokens[0];
88 std::string cmd_type = tokens[1];
89 if (!IsOnlyDigits(tokens[2])) {
90 ConfUiLog(ERROR) << "Token[2] of the ConfUi packet should be a number";
91 return std::nullopt;
92 }
93 const int n = std::stoi(tokens[2]);
94
95 if (n + 2 > tokens.size()) {
96 ConfUiLog(ERROR) << "The ConfUi packet is ill-formatted.";
97 return std::nullopt;
98 }
99 ConfUiPacketInfo data_to_return;
100 std::vector<int> lengths;
101 lengths.reserve(n);
102 for (int i = 1; i <= n; i++) {
103 if (!IsOnlyDigits(tokens[2 + i])) {
104 ConfUiLog(ERROR) << tokens[2 + i] << " should be a number but is not.";
105 return std::nullopt;
106 }
107 lengths.emplace_back(std::stoi(tokens[2 + i]));
108 }
109 // to find the first position of the non-header part
110 int pos = 0;
111 // 3 for three ":"s
112 pos += tokens[0].size() + tokens[1].size() + tokens[2].size() + 3;
113 for (int i = 1; i <= n; i++) {
114 pos += tokens[2 + i].size() + 1;
115 }
116 int expected_total_length = pos;
117 for (auto const len : lengths) {
118 expected_total_length += len;
119 }
120 if (expected_total_length != data_to_parse.size()) {
121 ConfUiLog(ERROR) << "expected length in ParseRawData is "
122 << expected_total_length << " while the actual length is "
123 << data_to_parse.size();
124 return std::nullopt;
125 }
126 for (const auto len : lengths) {
127 if (len == 0) {
128 // push null vector or whatever empty, appropriately-typed
129 // container
130 data_to_return.emplace_back(std::vector<std::uint8_t>{});
131 continue;
132 }
133 data_to_return.emplace_back(data_to_parse.begin() + pos,
134 data_to_parse.begin() + pos + len);
135 pos = pos + len;
136 }
137 ParsedPacket result{session_id, cmd_type, data_to_return};
138 return {result};
139 }
140
ReadPayload(SharedFD s)141 std::optional<ParsedPacket> ReadPayload(SharedFD s) {
142 auto raw_data = ReadRawData(s);
143 if (!raw_data) {
144 ConfUiLog(ERROR) << "raw data returned std::nullopt";
145 return std::nullopt;
146 }
147 auto parsed_result = ParseRawData(raw_data.value());
148 if (!parsed_result) {
149 ConfUiLog(ERROR) << "parsed result returns nullopt";
150 return std::nullopt;
151 }
152 return parsed_result;
153 }
154 } // end of namespace packet
155 } // end of namespace confui
156 } // end of namespace cuttlefish
157