• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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