blob: 4a89324e7e542ea781ff119318125c846e4499c0 [file] [log] [blame]
// Copyright 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Frontend command line interface.
#include "frontend/frontend_client.h"
#include <google/protobuf/util/json_util.h>
#include <grpcpp/support/status.h>
#include <stdlib.h>
#include <chrono>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include "frontend-client-cxx/src/lib.rs.h"
#include "frontend.grpc.pb.h"
#include "frontend.pb.h"
#include "google/protobuf/empty.pb.h"
#include "grpcpp/create_channel.h"
#include "grpcpp/security/credentials.h"
#include "grpcpp/support/status_code_enum.h"
#include "model.pb.h"
#include "util/ini_file.h"
#include "util/os_utils.h"
#include "util/string_utils.h"
namespace netsim {
namespace frontend {
namespace {
const std::chrono::duration kConnectionDeadline = std::chrono::seconds(1);
std::unique_ptr<frontend::FrontendService::Stub> NewFrontendStub() {
auto port = netsim::osutils::GetServerAddress();
if (!port.has_value()) {
return {};
}
auto server = "localhost:" + port.value();
std::shared_ptr<grpc::Channel> channel =
grpc::CreateChannel(server, grpc::InsecureChannelCredentials());
auto deadline = std::chrono::system_clock::now() + kConnectionDeadline;
if (!channel->WaitForConnected(deadline)) {
return nullptr;
}
return frontend::FrontendService::NewStub(channel);
}
// A synchronous client for the netsim frontend service.
class FrontendClientImpl : public FrontendClient {
public:
FrontendClientImpl(std::unique_ptr<frontend::FrontendService::Stub> stub)
: stub_(std::move(stub)) {}
std::unique_ptr<ClientResult> make_result(
const grpc::Status &status,
const google::protobuf::Message &message) const {
std::vector<unsigned char> message_vec(message.ByteSizeLong());
message.SerializeToArray(message_vec.data(), message_vec.size());
if (!status.ok()) {
return std::make_unique<ClientResult>(false, status.error_message(),
message_vec);
}
return std::make_unique<ClientResult>(true, "", message_vec);
}
// Gets the version of the network simulator service.
std::unique_ptr<ClientResult> GetVersion() const override {
frontend::VersionResponse response;
grpc::ClientContext context_;
auto status = stub_->GetVersion(&context_, {}, &response);
return make_result(status, response);
}
// Gets the list of device information
std::unique_ptr<ClientResult> GetDevices() const override {
frontend::GetDevicesResponse response;
grpc::ClientContext context_;
auto status = stub_->GetDevices(&context_, {}, &response);
return make_result(status, response);
}
std::unique_ptr<ClientResult> Reset() const override {
grpc::ClientContext context_;
google::protobuf::Empty response;
auto status = stub_->Reset(&context_, {}, &response);
return make_result(status, response);
}
// Patchs the information of the device
std::unique_ptr<ClientResult> PatchDevice(
rust::Vec<::rust::u8> const &request_byte_vec) const override {
google::protobuf::Empty response;
grpc::ClientContext context_;
frontend::PatchDeviceRequest request;
if (!request.ParseFromArray(request_byte_vec.data(),
request_byte_vec.size())) {
return make_result(
grpc::Status(
grpc::StatusCode::INVALID_ARGUMENT,
"Error parsing PatchDevice request protobuf. request size:" +
std::to_string(request_byte_vec.size())),
response);
};
auto status = stub_->PatchDevice(&context_, request, &response);
return make_result(status, response);
}
// Get the list of Capture information
std::unique_ptr<ClientResult> ListCapture() const override {
frontend::ListCaptureResponse response;
grpc::ClientContext context_;
auto status = stub_->ListCapture(&context_, {}, &response);
return make_result(status, response);
}
// Patch the Capture
std::unique_ptr<ClientResult> PatchCapture(
rust::Vec<::rust::u8> const &request_byte_vec) const override {
google::protobuf::Empty response;
grpc::ClientContext context_;
frontend::PatchCaptureRequest request;
if (!request.ParseFromArray(request_byte_vec.data(),
request_byte_vec.size())) {
return make_result(
grpc::Status(
grpc::StatusCode::INVALID_ARGUMENT,
"Error parsing PatchCapture request protobuf. request size:" +
std::to_string(request_byte_vec.size())),
response);
};
auto status = stub_->PatchCapture(&context_, request, &response);
return make_result(status, response);
}
// Download capture file by using ClientResponseReader to handle streaming
// grpc
std::unique_ptr<ClientResult> GetCapture(
rust::Vec<::rust::u8> const &request_byte_vec,
ClientResponseReader const &client_reader) const override {
grpc::ClientContext context_;
frontend::GetCaptureRequest request;
if (!request.ParseFromArray(request_byte_vec.data(),
request_byte_vec.size())) {
return make_result(
grpc::Status(
grpc::StatusCode::INVALID_ARGUMENT,
"Error parsing GetCapture request protobuf. request size:" +
std::to_string(request_byte_vec.size())),
google::protobuf::Empty());
};
auto reader = stub_->GetCapture(&context_, request);
frontend::GetCaptureResponse chunk;
// Read every available chunks from grpc reader
while (reader->Read(&chunk)) {
// Using a mutable protobuf here so the move iterator can move
// the capture stream without copying.
auto mut_stream = chunk.mutable_capture_stream();
auto bytes =
std::vector<uint8_t>(std::make_move_iterator(mut_stream->begin()),
std::make_move_iterator(mut_stream->end()));
client_reader.handle_chunk(
rust::Slice<const uint8_t>{bytes.data(), bytes.size()});
}
auto status = reader->Finish();
return make_result(status, google::protobuf::Empty());
}
// Helper function to redirect to the correct Grpc call
std::unique_ptr<ClientResult> SendGrpc(
frontend::GrpcMethod const &grpc_method,
rust::Vec<::rust::u8> const &request_byte_vec) const override {
switch (grpc_method) {
case frontend::GrpcMethod::GetVersion:
return GetVersion();
case frontend::GrpcMethod::PatchDevice:
return PatchDevice(request_byte_vec);
case frontend::GrpcMethod::GetDevices:
return GetDevices();
case frontend::GrpcMethod::Reset:
return Reset();
case frontend::GrpcMethod::ListCapture:
return ListCapture();
case frontend::GrpcMethod::PatchCapture:
return PatchCapture(request_byte_vec);
default:
return make_result(grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
"Unknown GrpcMethod found."),
google::protobuf::Empty());
}
}
private:
std::unique_ptr<frontend::FrontendService::Stub> stub_;
static bool CheckStatus(const grpc::Status &status,
const std::string &message) {
if (status.ok()) return true;
if (status.error_code() == grpc::StatusCode::UNAVAILABLE)
std::cerr << "error: netsim frontend service is unavailable, "
"please restart."
<< std::endl;
else
std::cerr << "error: request to service failed (" << status.error_code()
<< ") - " << status.error_message() << std::endl;
return false;
}
};
} // namespace
std::unique_ptr<FrontendClient> NewFrontendClient() {
auto stub = NewFrontendStub();
return (stub == nullptr
? nullptr
: std::make_unique<FrontendClientImpl>(std::move(stub)));
}
} // namespace frontend
} // namespace netsim