1 /*
2 * Copyright (C) 2023 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
17 #include <filesystem>
18 #include <functional>
19 #include <memory>
20 #include <sstream>
21 #include <string>
22 #include <unordered_map>
23 #include <vector>
24
25 #include <android-base/strings.h>
26 #include <grpcpp/security/credentials.h>
27 #include <json/json.h>
28 #include <test/cpp/util/cli_credentials.h>
29 #include <test/cpp/util/grpc_tool.h>
30 #include <test/cpp/util/test_config.h>
31
32 #include "common/libs/utils/contains.h"
33 #include "common/libs/utils/result.h"
34
35 using android::base::EndsWith;
36 using android::base::Split;
37 using android::base::Trim;
38 using grpc::InsecureChannelCredentials;
39
40 namespace cuttlefish {
41 namespace {
42
43 constexpr char kDefaultOptionL[] = "-l=false";
44 constexpr char kDefaultOptionJsonInput[] = "--json_input=true";
45 constexpr char kDefaultOptionJsonOutput[] = "--json_output=true";
46 constexpr char kServiceServerReflection[] =
47 "grpc.reflection.v1alpha.ServerReflection";
48 constexpr char kServiceHealth[] = "grpc.health.v1.Health";
49 constexpr char kServiceControlEnvProxy[] = "ControlEnvProxyService";
50 constexpr char kServiceControlEnvProxyFull[] =
51 "controlenvproxyserver.ControlEnvProxyService";
52
PrintStream(std::stringstream * ss,const std::string & output)53 bool PrintStream(std::stringstream* ss, const std::string& output) {
54 (*ss) << output;
55 return true;
56 }
57
58 class InsecureCliCredentials final : public grpc::testing::CliCredentials {
59 public:
GetChannelCredentials() const60 std::shared_ptr<grpc::ChannelCredentials> GetChannelCredentials()
61 const override {
62 return InsecureChannelCredentials();
63 }
GetCredentialUsage() const64 std::string GetCredentialUsage() const override { return ""; }
65 };
66
CombineArgumentsAndOptions(const std::vector<std::string> & arguments,const std::vector<std::string> & options)67 std::vector<char*> CombineArgumentsAndOptions(
68 const std::vector<std::string>& arguments,
69 const std::vector<std::string>& options) {
70 std::vector<char*> char_vec;
71 // Add 3 for default options
72 char_vec.reserve(arguments.size() + options.size() + 3);
73 for (const auto& arg : arguments) {
74 char_vec.push_back(const_cast<char*>(arg.c_str()));
75 }
76 // Grpc keeps the option value as global flag, so we should pass default
77 // option value. Default option value could be overwritten by the options
78 // given from parameter.
79 char_vec.push_back(const_cast<char*>(kDefaultOptionL));
80 char_vec.push_back(const_cast<char*>(kDefaultOptionJsonInput));
81 char_vec.push_back(const_cast<char*>(kDefaultOptionJsonOutput));
82 for (const auto& opt : options) {
83 char_vec.push_back(const_cast<char*>(opt.c_str()));
84 }
85 return char_vec;
86 }
87
RunGrpcCommand(const std::vector<std::string> & arguments,const std::vector<std::string> & options,std::stringstream & output_stream)88 Result<void> RunGrpcCommand(const std::vector<std::string>& arguments,
89 const std::vector<std::string>& options,
90 std::stringstream& output_stream) {
91 auto combined_arguments = CombineArgumentsAndOptions(arguments, options);
92 int grpc_cli_argc = combined_arguments.size();
93 char** grpc_cli_argv = combined_arguments.data();
94
95 grpc::testing::InitTest(&grpc_cli_argc, &grpc_cli_argv, true);
96 CF_EXPECT(
97 grpc::testing::GrpcToolMainLib(
98 grpc_cli_argc, (const char**)grpc_cli_argv, InsecureCliCredentials(),
99 std::bind(PrintStream, &output_stream, std::placeholders::_1)) == 0,
100 "gRPC command failed");
101 return {};
102 }
103
RunGrpcCommand(const std::vector<std::string> & arguments,const std::vector<std::string> & options)104 Result<std::string> RunGrpcCommand(const std::vector<std::string>& arguments,
105 const std::vector<std::string>& options) {
106 std::stringstream output_stream;
107 CF_EXPECT(RunGrpcCommand(arguments, options, output_stream));
108 return output_stream.str();
109 }
110
RunGrpcCommand(const std::vector<std::string> & arguments)111 Result<std::string> RunGrpcCommand(const std::vector<std::string>& arguments) {
112 std::vector<std::string> options;
113 return RunGrpcCommand(arguments, options);
114 }
115
GetServiceList(const std::string & server_address)116 Result<std::vector<std::string>> GetServiceList(
117 const std::string& server_address) {
118 std::vector<std::string> service_list;
119 std::stringstream output_stream;
120
121 std::vector<std::string> arguments{"grpc_cli", "ls", server_address};
122 std::vector<std::string> options;
123 CF_EXPECT(RunGrpcCommand(arguments, options, output_stream));
124
125 std::string service_name;
126 while (std::getline(output_stream, service_name)) {
127 if (service_name.compare(kServiceServerReflection) == 0 ||
128 service_name.compare(kServiceHealth) == 0) {
129 continue;
130 }
131 service_list.emplace_back(service_name);
132 }
133
134 return service_list;
135 }
136
GetServerAddress(const std::vector<std::string> & server_address_list,const std::string & service_name)137 Result<std::string> GetServerAddress(
138 const std::vector<std::string>& server_address_list,
139 const std::string& service_name) {
140 std::vector<std::string> candidates;
141 for (const auto& server_address : server_address_list) {
142 auto service_names = CF_EXPECT(GetServiceList(server_address));
143 for (auto& full_service_name : service_names) {
144 if (EndsWith(full_service_name, service_name)) {
145 candidates.emplace_back(server_address);
146 break;
147 }
148 }
149 }
150
151 CF_EXPECT(candidates.size() > 0, service_name + " is not found.");
152 CF_EXPECT(candidates.size() < 2, service_name + " is ambiguous.");
153
154 return candidates[0];
155 }
156
GetFullServiceName(const std::string & server_address,const std::string & service_name)157 Result<std::string> GetFullServiceName(const std::string& server_address,
158 const std::string& service_name) {
159 std::vector<std::string> candidates;
160 auto service_names = CF_EXPECT(GetServiceList(server_address));
161 for (auto& full_service_name : service_names) {
162 if (EndsWith(full_service_name, service_name)) {
163 candidates.emplace_back(full_service_name);
164 }
165 }
166
167 CF_EXPECT(candidates.size() > 0, service_name + " is not found.");
168 CF_EXPECT(candidates.size() < 2, service_name + " is ambiguous.");
169
170 return candidates[0];
171 }
172
GetFullMethodName(const std::string & server_address,const std::string & service_name,const std::string & method_name)173 Result<std::string> GetFullMethodName(const std::string& server_address,
174 const std::string& service_name,
175 const std::string& method_name) {
176 const auto& full_service_name =
177 CF_EXPECT(GetFullServiceName(server_address, service_name));
178 return full_service_name + "/" + method_name;
179 }
180
HandleLsCmd(const std::vector<std::string> & server_address_list,const std::vector<std::string> & args)181 Result<std::string> HandleLsCmd(
182 const std::vector<std::string>& server_address_list,
183 const std::vector<std::string>& args) {
184 switch (args.size()) {
185 case 0: {
186 // ls subcommand with no arguments
187 std::string command_output;
188 for (const auto& server_address : server_address_list) {
189 std::vector<std::string> grpc_arguments{"grpc_cli", "ls",
190 server_address};
191 command_output += CF_EXPECT(RunGrpcCommand(grpc_arguments));
192 }
193
194 Json::Value json;
195 json["services"] = Json::Value(Json::arrayValue);
196 for (auto& full_service_name : Split(Trim(command_output), "\n")) {
197 if (full_service_name.compare(kServiceServerReflection) == 0 ||
198 full_service_name.compare(kServiceHealth) == 0 ||
199 full_service_name.compare(kServiceControlEnvProxyFull) == 0) {
200 continue;
201 }
202 json["services"].append(Split(full_service_name, ".").back());
203 }
204 Json::StreamWriterBuilder builder;
205 return Json::writeString(builder, json) + "\n";
206 }
207 case 1: {
208 // ls subcommand with 1 argument; service_name
209 const auto& service_name = args[0];
210 CF_EXPECT(service_name.compare(kServiceControlEnvProxy) != 0,
211 "Prohibited service name");
212 const auto& server_address =
213 CF_EXPECT(GetServerAddress(server_address_list, service_name));
214 const auto& full_service_name =
215 CF_EXPECT(GetFullServiceName(server_address, service_name));
216 std::vector<std::string> grpc_arguments{"grpc_cli", "ls", server_address,
217 full_service_name};
218 std::string command_output = CF_EXPECT(RunGrpcCommand(grpc_arguments));
219
220 Json::Value json;
221 json["methods"] = Json::Value(Json::arrayValue);
222 for (auto& method_name : Split(Trim(command_output), "\n")) {
223 json["methods"].append(method_name);
224 }
225 Json::StreamWriterBuilder builder;
226 return Json::writeString(builder, json) + "\n";
227 }
228 case 2: {
229 // ls subcommand with 2 arguments; service_name and method_name
230 const auto& service_name = args[0];
231 CF_EXPECT(service_name.compare(kServiceControlEnvProxy) != 0,
232 "Prohibited service name");
233 const auto& server_address =
234 CF_EXPECT(GetServerAddress(server_address_list, service_name));
235 const auto& method_name = args[1];
236 const auto& full_method_name = CF_EXPECT(
237 GetFullMethodName(server_address, service_name, method_name));
238 std::vector<std::string> grpc_arguments{"grpc_cli", "ls", server_address,
239 full_method_name};
240 std::vector<std::string> options{"-l"};
241 std::string command_output =
242 CF_EXPECT(RunGrpcCommand(grpc_arguments, options));
243
244 // Example command_output:
245 // rpc SetTxpower(wmediumdserver.SetTxpowerRequest) returns
246 // (google.protobuf.Empty) {}
247 std::vector<std::string> parsed_output =
248 Split(Trim(command_output), "()");
249 CF_EXPECT(parsed_output.size() == 5, "Unexpected parsing result");
250 Json::Value json;
251 json["request_type"] = parsed_output[1];
252 json["response_type"] = parsed_output[3];
253 Json::StreamWriterBuilder builder;
254 return Json::writeString(builder, json) + "\n";
255 }
256 default:
257 return CF_ERR("too many arguments");
258 }
259 }
260
HandleTypeCmd(const std::vector<std::string> & server_address_list,const std::vector<std::string> & args)261 Result<std::string> HandleTypeCmd(
262 const std::vector<std::string>& server_address_list,
263 const std::vector<std::string>& args) {
264 CF_EXPECT(args.size() > 1,
265 "need to specify the service name and the type_name");
266 CF_EXPECT(args.size() < 3, "too many arguments");
267
268 std::vector<std::string> grpc_arguments{"grpc_cli", "type"};
269 const auto& service_name = args[0];
270 CF_EXPECT(service_name.compare(kServiceControlEnvProxy) != 0,
271 "Prohibited service name");
272 const auto& type_name = args[1];
273
274 const auto& server_address =
275 CF_EXPECT(GetServerAddress(server_address_list, service_name));
276 grpc_arguments.push_back(server_address);
277 grpc_arguments.push_back(type_name);
278
279 return RunGrpcCommand(grpc_arguments);
280 }
281
HandleCallCmd(const std::vector<std::string> & server_address_list,const std::vector<std::string> & args)282 Result<std::string> HandleCallCmd(
283 const std::vector<std::string>& server_address_list,
284 const std::vector<std::string>& args) {
285 CF_EXPECT(args.size() > 2,
286 "need to specify the service name, the method name, and the "
287 "json-formatted proto");
288 CF_EXPECT(args.size() < 4, "too many arguments");
289
290 std::vector<std::string> grpc_arguments{"grpc_cli", "call"};
291 // TODO(b/265384449): support calling streaming method.
292 const auto& service_name = args[0];
293 const auto& method_name = args[1];
294 const auto& json_format_proto = args[2];
295
296 const auto& server_address =
297 CF_EXPECT(GetServerAddress(server_address_list, service_name));
298 grpc_arguments.push_back(server_address);
299 const auto& full_method_name =
300 CF_EXPECT(GetFullMethodName(server_address, service_name, method_name));
301 grpc_arguments.push_back(full_method_name);
302 grpc_arguments.push_back(json_format_proto);
303
304 return RunGrpcCommand(grpc_arguments);
305 }
306
307 } // namespace
308
HandleCmds(const std::string & grpc_socket_path,const std::string & cmd,const std::vector<std::string> & args)309 Result<std::string> HandleCmds(const std::string& grpc_socket_path,
310 const std::string& cmd,
311 const std::vector<std::string>& args) {
312 std::vector<std::string> server_address_list;
313 for (const auto& entry :
314 std::filesystem::directory_iterator(grpc_socket_path)) {
315 LOG(DEBUG) << "loading " << entry.path();
316 server_address_list.emplace_back("unix:" + entry.path().string());
317 }
318
319 auto command_map =
320 std::unordered_map<std::string, std::function<Result<std::string>(
321 const std::vector<std::string>&,
322 const std::vector<std::string>&)>>{{
323 {"call", HandleCallCmd},
324 {"ls", HandleLsCmd},
325 {"type", HandleTypeCmd},
326 }};
327 CF_EXPECT(Contains(command_map, cmd), cmd << " isn't supported");
328
329 auto command_output = CF_EXPECT(command_map[cmd](server_address_list, args));
330 return command_output;
331 }
332
333 } // namespace cuttlefish
334