1 // Copyright 2022 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // Frontend client
16 #include "frontend/frontend_client.h"
17 
18 #include <google/protobuf/util/json_util.h>
19 #include <grpcpp/support/status.h>
20 
21 #include <chrono>
22 #include <cstdint>
23 #include <memory>
24 #include <string>
25 
26 #include "google/protobuf/empty.pb.h"
27 #include "grpcpp/create_channel.h"
28 #include "grpcpp/security/credentials.h"
29 #include "grpcpp/support/status_code_enum.h"
30 #include "netsim-cli/src/ffi.rs.h"
31 #include "netsim/frontend.grpc.pb.h"
32 #include "netsim/frontend.pb.h"
33 #include "netsim/model.pb.h"
34 #include "util/log.h"
35 #include "util/os_utils.h"
36 
37 namespace netsim {
38 namespace frontend {
39 namespace {
40 const std::chrono::duration kConnectionDeadline = std::chrono::seconds(1);
41 
NewFrontendStub(std::string server)42 std::unique_ptr<frontend::FrontendService::Stub> NewFrontendStub(
43     std::string server) {
44   if (server == "") {
45     return {};
46   }
47   std::shared_ptr<grpc::Channel> channel =
48       grpc::CreateChannel(server, grpc::InsecureChannelCredentials());
49 
50   auto deadline = std::chrono::system_clock::now() + kConnectionDeadline;
51   if (!channel->WaitForConnected(deadline)) {
52     BtsLogWarn("Frontend gRPC channel not connected");
53     return nullptr;
54   }
55 
56   return frontend::FrontendService::NewStub(channel);
57 }
58 
59 // A synchronous client for the netsim frontend service.
60 class FrontendClientImpl : public FrontendClient {
61  public:
FrontendClientImpl(std::unique_ptr<frontend::FrontendService::Stub> stub)62   FrontendClientImpl(std::unique_ptr<frontend::FrontendService::Stub> stub)
63       : stub_(std::move(stub)) {}
64 
make_result(const grpc::Status & status,const google::protobuf::Message & message) const65   std::unique_ptr<ClientResult> make_result(
66       const grpc::Status &status,
67       const google::protobuf::Message &message) const {
68     std::vector<unsigned char> message_vec(message.ByteSizeLong());
69     message.SerializeToArray(message_vec.data(), message_vec.size());
70     if (!status.ok()) {
71       return std::make_unique<ClientResult>(false, status.error_message(),
72                                             message_vec);
73     }
74     return std::make_unique<ClientResult>(true, "", message_vec);
75   }
76 
77   // Gets the version of the network simulator service.
GetVersion() const78   std::unique_ptr<ClientResult> GetVersion() const override {
79     frontend::VersionResponse response;
80     grpc::ClientContext context_;
81     auto status = stub_->GetVersion(&context_, {}, &response);
82     return make_result(status, response);
83   }
84 
85   // Gets the list of device information
ListDevice() const86   std::unique_ptr<ClientResult> ListDevice() const override {
87     frontend::ListDeviceResponse response;
88     grpc::ClientContext context_;
89     auto status = stub_->ListDevice(&context_, {}, &response);
90     return make_result(status, response);
91   }
92 
Reset() const93   std::unique_ptr<ClientResult> Reset() const override {
94     grpc::ClientContext context_;
95     google::protobuf::Empty response;
96     auto status = stub_->Reset(&context_, {}, &response);
97     return make_result(status, response);
98   }
99 
CreateDevice(rust::Vec<::rust::u8> const & request_byte_vec) const100   std::unique_ptr<ClientResult> CreateDevice(
101       rust::Vec<::rust::u8> const &request_byte_vec) const {
102     frontend::CreateDeviceResponse response;
103     grpc::ClientContext context_;
104     frontend::CreateDeviceRequest request;
105     if (!request.ParseFromArray(request_byte_vec.data(),
106                                 request_byte_vec.size())) {
107       return make_result(
108           grpc::Status(
109               grpc::StatusCode::INVALID_ARGUMENT,
110               "Error parsing CreateDevice request protobuf. request size:" +
111                   std::to_string(request_byte_vec.size())),
112           response);
113     }
114     auto status = stub_->CreateDevice(&context_, request, &response);
115     return make_result(status, response);
116   }
117 
118   // Patchs the information of the device
PatchDevice(rust::Vec<::rust::u8> const & request_byte_vec) const119   std::unique_ptr<ClientResult> PatchDevice(
120       rust::Vec<::rust::u8> const &request_byte_vec) const override {
121     google::protobuf::Empty response;
122     grpc::ClientContext context_;
123     frontend::PatchDeviceRequest request;
124     if (!request.ParseFromArray(request_byte_vec.data(),
125                                 request_byte_vec.size())) {
126       return make_result(
127           grpc::Status(
128               grpc::StatusCode::INVALID_ARGUMENT,
129               "Error parsing PatchDevice request protobuf. request size:" +
130                   std::to_string(request_byte_vec.size())),
131           response);
132     };
133     auto status = stub_->PatchDevice(&context_, request, &response);
134     return make_result(status, response);
135   }
136 
DeleteChip(rust::Vec<::rust::u8> const & request_byte_vec) const137   std::unique_ptr<ClientResult> DeleteChip(
138       rust::Vec<::rust::u8> const &request_byte_vec) const {
139     google::protobuf::Empty response;
140     grpc::ClientContext context_;
141     frontend::DeleteChipRequest request;
142     if (!request.ParseFromArray(request_byte_vec.data(),
143                                 request_byte_vec.size())) {
144       return make_result(
145           grpc::Status(
146               grpc::StatusCode::INVALID_ARGUMENT,
147               "Error parsing DeleteChip request protobuf. request size:" +
148                   std::to_string(request_byte_vec.size())),
149           response);
150     }
151     auto status = stub_->DeleteChip(&context_, request, &response);
152     return make_result(status, response);
153   }
154 
155   // Get the list of Capture information
ListCapture() const156   std::unique_ptr<ClientResult> ListCapture() const override {
157     frontend::ListCaptureResponse response;
158     grpc::ClientContext context_;
159     auto status = stub_->ListCapture(&context_, {}, &response);
160     return make_result(status, response);
161   }
162 
163   // Patch the Capture
PatchCapture(rust::Vec<::rust::u8> const & request_byte_vec) const164   std::unique_ptr<ClientResult> PatchCapture(
165       rust::Vec<::rust::u8> const &request_byte_vec) const override {
166     google::protobuf::Empty response;
167     grpc::ClientContext context_;
168     frontend::PatchCaptureRequest request;
169     if (!request.ParseFromArray(request_byte_vec.data(),
170                                 request_byte_vec.size())) {
171       return make_result(
172           grpc::Status(
173               grpc::StatusCode::INVALID_ARGUMENT,
174               "Error parsing PatchCapture request protobuf. request size:" +
175                   std::to_string(request_byte_vec.size())),
176           response);
177     };
178     auto status = stub_->PatchCapture(&context_, request, &response);
179     return make_result(status, response);
180   }
181 
182   // Download capture file by using ClientResponseReader to handle streaming
183   // grpc
GetCapture(rust::Vec<::rust::u8> const & request_byte_vec,ClientResponseReader const & client_reader) const184   std::unique_ptr<ClientResult> GetCapture(
185       rust::Vec<::rust::u8> const &request_byte_vec,
186       ClientResponseReader const &client_reader) const override {
187     grpc::ClientContext context_;
188     frontend::GetCaptureRequest request;
189     if (!request.ParseFromArray(request_byte_vec.data(),
190                                 request_byte_vec.size())) {
191       return make_result(
192           grpc::Status(
193               grpc::StatusCode::INVALID_ARGUMENT,
194               "Error parsing GetCapture request protobuf. request size:" +
195                   std::to_string(request_byte_vec.size())),
196           google::protobuf::Empty());
197     };
198     auto reader = stub_->GetCapture(&context_, request);
199     frontend::GetCaptureResponse chunk;
200     // Read every available chunks from grpc reader
201     while (reader->Read(&chunk)) {
202       // Using a mutable protobuf here so the move iterator can move
203       // the capture stream without copying.
204       auto mut_stream = chunk.mutable_capture_stream();
205       auto bytes =
206           std::vector<uint8_t>(std::make_move_iterator(mut_stream->begin()),
207                                std::make_move_iterator(mut_stream->end()));
208       client_reader.handle_chunk(
209           rust::Slice<const uint8_t>{bytes.data(), bytes.size()});
210     }
211     auto status = reader->Finish();
212     return make_result(status, google::protobuf::Empty());
213   }
214 
215   // Helper function to redirect to the correct Grpc call
SendGrpc(frontend::GrpcMethod const & grpc_method,rust::Vec<::rust::u8> const & request_byte_vec) const216   std::unique_ptr<ClientResult> SendGrpc(
217       frontend::GrpcMethod const &grpc_method,
218       rust::Vec<::rust::u8> const &request_byte_vec) const override {
219     switch (grpc_method) {
220       case frontend::GrpcMethod::GetVersion:
221         return GetVersion();
222       case frontend::GrpcMethod::CreateDevice:
223         return CreateDevice(request_byte_vec);
224       case frontend::GrpcMethod::DeleteChip:
225         return DeleteChip(request_byte_vec);
226       case frontend::GrpcMethod::PatchDevice:
227         return PatchDevice(request_byte_vec);
228       case frontend::GrpcMethod::ListDevice:
229         return ListDevice();
230       case frontend::GrpcMethod::Reset:
231         return Reset();
232       case frontend::GrpcMethod::ListCapture:
233         return ListCapture();
234       case frontend::GrpcMethod::PatchCapture:
235         return PatchCapture(request_byte_vec);
236       default:
237         return make_result(grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
238                                         "Unknown GrpcMethod found."),
239                            google::protobuf::Empty());
240     }
241   }
242 
243  private:
244   std::unique_ptr<frontend::FrontendService::Stub> stub_;
245 
CheckStatus(const grpc::Status & status,const std::string & message)246   static bool CheckStatus(const grpc::Status &status,
247                           const std::string &message) {
248     if (status.ok()) return true;
249     if (status.error_code() == grpc::StatusCode::UNAVAILABLE)
250       BtsLogError(
251           "netsim frontend service is unavailable, "
252           "please restart.");
253     else
254       BtsLogError("request to frontend service failed (%d) - %s",
255                   status.error_code(), status.error_message().c_str());
256     return false;
257   }
258 };
259 
260 }  // namespace
261 
NewFrontendClient(const std::string & server)262 std::unique_ptr<FrontendClient> NewFrontendClient(const std::string &server) {
263   auto stub = NewFrontendStub(server);
264   return (stub == nullptr
265               ? nullptr
266               : std::make_unique<FrontendClientImpl>(std::move(stub)));
267 }
268 
269 }  // namespace frontend
270 }  // namespace netsim