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 command line interface.
16 #include "frontend/frontend_client.h"
17
18 #include <google/protobuf/util/json_util.h>
19 #include <grpcpp/support/status.h>
20 #include <stdlib.h>
21
22 #include <chrono>
23 #include <cstdint>
24 #include <iomanip>
25 #include <iostream>
26 #include <iterator>
27 #include <memory>
28 #include <optional>
29 #include <sstream>
30 #include <string>
31 #include <string_view>
32
33 #include "frontend-client-cxx/src/lib.rs.h"
34 #include "frontend.grpc.pb.h"
35 #include "frontend.pb.h"
36 #include "google/protobuf/empty.pb.h"
37 #include "grpcpp/create_channel.h"
38 #include "grpcpp/security/credentials.h"
39 #include "grpcpp/support/status_code_enum.h"
40 #include "model.pb.h"
41 #include "util/ini_file.h"
42 #include "util/os_utils.h"
43 #include "util/string_utils.h"
44
45 namespace netsim {
46 namespace frontend {
47 namespace {
48 const std::chrono::duration kConnectionDeadline = std::chrono::seconds(1);
49
NewFrontendStub()50 std::unique_ptr<frontend::FrontendService::Stub> NewFrontendStub() {
51 auto port = netsim::osutils::GetServerAddress();
52 if (!port.has_value()) {
53 return {};
54 }
55 auto server = "localhost:" + port.value();
56 std::shared_ptr<grpc::Channel> channel =
57 grpc::CreateChannel(server, grpc::InsecureChannelCredentials());
58
59 auto deadline = std::chrono::system_clock::now() + kConnectionDeadline;
60 if (!channel->WaitForConnected(deadline)) {
61 return nullptr;
62 }
63
64 return frontend::FrontendService::NewStub(channel);
65 }
66
67 // A synchronous client for the netsim frontend service.
68 class FrontendClientImpl : public FrontendClient {
69 public:
FrontendClientImpl(std::unique_ptr<frontend::FrontendService::Stub> stub)70 FrontendClientImpl(std::unique_ptr<frontend::FrontendService::Stub> stub)
71 : stub_(std::move(stub)) {}
72
make_result(const grpc::Status & status,const google::protobuf::Message & message) const73 std::unique_ptr<ClientResult> make_result(
74 const grpc::Status &status,
75 const google::protobuf::Message &message) const {
76 std::vector<unsigned char> message_vec(message.ByteSizeLong());
77 message.SerializeToArray(message_vec.data(), message_vec.size());
78 if (!status.ok()) {
79 return std::make_unique<ClientResult>(false, status.error_message(),
80 message_vec);
81 }
82 return std::make_unique<ClientResult>(true, "", message_vec);
83 }
84
85 // Gets the version of the network simulator service.
GetVersion() const86 std::unique_ptr<ClientResult> GetVersion() const override {
87 frontend::VersionResponse response;
88 grpc::ClientContext context_;
89 auto status = stub_->GetVersion(&context_, {}, &response);
90 return make_result(status, response);
91 }
92
93 // Gets the list of device information
GetDevices() const94 std::unique_ptr<ClientResult> GetDevices() const override {
95 frontend::GetDevicesResponse response;
96 grpc::ClientContext context_;
97 auto status = stub_->GetDevices(&context_, {}, &response);
98 return make_result(status, response);
99 }
100
Reset() const101 std::unique_ptr<ClientResult> Reset() const override {
102 grpc::ClientContext context_;
103 google::protobuf::Empty response;
104 auto status = stub_->Reset(&context_, {}, &response);
105 return make_result(status, response);
106 }
107
108 // Patchs the information of the device
PatchDevice(rust::Vec<::rust::u8> const & request_byte_vec) const109 std::unique_ptr<ClientResult> PatchDevice(
110 rust::Vec<::rust::u8> const &request_byte_vec) const override {
111 google::protobuf::Empty response;
112 grpc::ClientContext context_;
113 frontend::PatchDeviceRequest request;
114 if (!request.ParseFromArray(request_byte_vec.data(),
115 request_byte_vec.size())) {
116 return make_result(
117 grpc::Status(
118 grpc::StatusCode::INVALID_ARGUMENT,
119 "Error parsing PatchDevice request protobuf. request size:" +
120 std::to_string(request_byte_vec.size())),
121 response);
122 };
123 auto status = stub_->PatchDevice(&context_, request, &response);
124 return make_result(status, response);
125 }
126
127 // Get the list of Capture information
ListCapture() const128 std::unique_ptr<ClientResult> ListCapture() const override {
129 frontend::ListCaptureResponse response;
130 grpc::ClientContext context_;
131 auto status = stub_->ListCapture(&context_, {}, &response);
132 return make_result(status, response);
133 }
134
135 // Patch the Capture
PatchCapture(rust::Vec<::rust::u8> const & request_byte_vec) const136 std::unique_ptr<ClientResult> PatchCapture(
137 rust::Vec<::rust::u8> const &request_byte_vec) const override {
138 google::protobuf::Empty response;
139 grpc::ClientContext context_;
140 frontend::PatchCaptureRequest request;
141 if (!request.ParseFromArray(request_byte_vec.data(),
142 request_byte_vec.size())) {
143 return make_result(
144 grpc::Status(
145 grpc::StatusCode::INVALID_ARGUMENT,
146 "Error parsing PatchCapture request protobuf. request size:" +
147 std::to_string(request_byte_vec.size())),
148 response);
149 };
150 auto status = stub_->PatchCapture(&context_, request, &response);
151 return make_result(status, response);
152 }
153
154 // Download capture file by using ClientResponseReader to handle streaming
155 // grpc
GetCapture(rust::Vec<::rust::u8> const & request_byte_vec,ClientResponseReader const & client_reader) const156 std::unique_ptr<ClientResult> GetCapture(
157 rust::Vec<::rust::u8> const &request_byte_vec,
158 ClientResponseReader const &client_reader) const override {
159 grpc::ClientContext context_;
160 frontend::GetCaptureRequest request;
161 if (!request.ParseFromArray(request_byte_vec.data(),
162 request_byte_vec.size())) {
163 return make_result(
164 grpc::Status(
165 grpc::StatusCode::INVALID_ARGUMENT,
166 "Error parsing GetCapture request protobuf. request size:" +
167 std::to_string(request_byte_vec.size())),
168 google::protobuf::Empty());
169 };
170 auto reader = stub_->GetCapture(&context_, request);
171 frontend::GetCaptureResponse chunk;
172 // Read every available chunks from grpc reader
173 while (reader->Read(&chunk)) {
174 // Using a mutable protobuf here so the move iterator can move
175 // the capture stream without copying.
176 auto mut_stream = chunk.mutable_capture_stream();
177 auto bytes =
178 std::vector<uint8_t>(std::make_move_iterator(mut_stream->begin()),
179 std::make_move_iterator(mut_stream->end()));
180 client_reader.handle_chunk(
181 rust::Slice<const uint8_t>{bytes.data(), bytes.size()});
182 }
183 auto status = reader->Finish();
184 return make_result(status, google::protobuf::Empty());
185 }
186
187 // Helper function to redirect to the correct Grpc call
SendGrpc(frontend::GrpcMethod const & grpc_method,rust::Vec<::rust::u8> const & request_byte_vec) const188 std::unique_ptr<ClientResult> SendGrpc(
189 frontend::GrpcMethod const &grpc_method,
190 rust::Vec<::rust::u8> const &request_byte_vec) const override {
191 switch (grpc_method) {
192 case frontend::GrpcMethod::GetVersion:
193 return GetVersion();
194 case frontend::GrpcMethod::PatchDevice:
195 return PatchDevice(request_byte_vec);
196 case frontend::GrpcMethod::GetDevices:
197 return GetDevices();
198 case frontend::GrpcMethod::Reset:
199 return Reset();
200 case frontend::GrpcMethod::ListCapture:
201 return ListCapture();
202 case frontend::GrpcMethod::PatchCapture:
203 return PatchCapture(request_byte_vec);
204 default:
205 return make_result(grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
206 "Unknown GrpcMethod found."),
207 google::protobuf::Empty());
208 }
209 }
210
211 private:
212 std::unique_ptr<frontend::FrontendService::Stub> stub_;
213
CheckStatus(const grpc::Status & status,const std::string & message)214 static bool CheckStatus(const grpc::Status &status,
215 const std::string &message) {
216 if (status.ok()) return true;
217 if (status.error_code() == grpc::StatusCode::UNAVAILABLE)
218 std::cerr << "error: netsim frontend service is unavailable, "
219 "please restart."
220 << std::endl;
221 else
222 std::cerr << "error: request to service failed (" << status.error_code()
223 << ") - " << status.error_message() << std::endl;
224 return false;
225 }
226 };
227
228 } // namespace
229
NewFrontendClient()230 std::unique_ptr<FrontendClient> NewFrontendClient() {
231 auto stub = NewFrontendStub();
232 return (stub == nullptr
233 ? nullptr
234 : std::make_unique<FrontendClientImpl>(std::move(stub)));
235 }
236
237 } // namespace frontend
238 } // namespace netsim
239