blob: 4adcd2e8d7575b634099bd5599d81ea3d9794f54 [file] [log] [blame]
/*
* Copyright 2023 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.
*/
#include <iostream>
#include <memory>
#include <string>
#include <android-base/hex.h>
#include <gflags/gflags.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
#include "casimir_control.grpc.pb.h"
#include "common/libs/utils/result.h"
#include "host/commands/casimir_control_server/casimir_controller.h"
#include "host/commands/casimir_control_server/hex.h"
using casimircontrolserver::CasimirControlService;
using casimircontrolserver::PowerLevel;
using casimircontrolserver::RadioState;
using casimircontrolserver::SendApduReply;
using casimircontrolserver::SendApduRequest;
using casimircontrolserver::SendBroadcastRequest;
using casimircontrolserver::SendBroadcastResponse;
using casimircontrolserver::SenderId;
using casimircontrolserver::TransceiveConfiguration;
using casimircontrolserver::Void;
using cuttlefish::CasimirController;
using google::protobuf::Empty;
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using grpc::StatusCode;
using std::string;
using std::vector;
DEFINE_string(grpc_uds_path, "", "grpc_uds_path");
DEFINE_int32(casimir_rf_port, -1, "RF port to control Casimir");
DEFINE_string(casimir_rf_path, "", "RF unix server path to control Casimir");
namespace cuttlefish {
namespace {
Result<CasimirController> ConnectToCasimir() {
if (FLAGS_casimir_rf_port >= 0) {
return CF_EXPECT(
CasimirController::ConnectToTcpPort(FLAGS_casimir_rf_port));
} else if (!FLAGS_casimir_rf_path.empty()) {
return CF_EXPECT(
CasimirController::ConnectToUnixSocket(FLAGS_casimir_rf_path));
} else {
return CF_ERR("`--casimir_rf_port` or `--casimir_rf_path` must be set");
}
}
Status ResultToStatus(Result<void> res) {
if (res.ok()) {
return Status::OK;
} else {
LOG(ERROR) << "RPC failed: " << res.error().FormatForEnv();
return Status(StatusCode::INTERNAL,
res.error().FormatForEnv(/* color = */ false));
}
}
class CasimirControlServiceImpl final : public CasimirControlService::Service {
private:
Status SetPowerLevel(ServerContext* context, const PowerLevel* power_level,
Void*) override {
return ResultToStatus(SetPowerLevelResult(power_level));
}
Result<void> SetPowerLevelResult(const PowerLevel* power_level) {
if (!device_) {
return {};
}
CF_EXPECT(device_->SetPowerLevel(power_level->power_level()),
"Failed to set power level");
return {};
}
Status Close(ServerContext* context, const Void*, Void* senderId) override {
device_ = std::nullopt;
return Status::OK;
}
Status Init(ServerContext*, const Void*, Void*) override {
return ResultToStatus(Init());
}
Result<void> Init() {
if (device_.has_value()) {
return {};
}
// Step 1: Initialize connection with casimir
device_ = CF_EXPECT(ConnectToCasimir());
return {};
}
Result<void> Mute() {
if (!device_.has_value()) {
return {};
}
if (is_radio_on_) {
CF_EXPECT(device_->Mute(), "Failed to mute radio");
is_radio_on_ = false;
}
return {};
}
Result<void> Unmute() {
if (!is_radio_on_) {
CF_EXPECT(device_->Unmute(), "Failed to unmute radio");
is_radio_on_ = true;
}
return {};
}
Status SetRadioState(ServerContext* context, const RadioState* radio_state,
Void*) override {
return ResultToStatus(SetRadioStateResult(radio_state));
}
Result<void> SetRadioStateResult(const RadioState* radio_state) {
if (radio_state->radio_on()) {
CF_EXPECT(Init());
CF_EXPECT(Unmute());
return {};
} else {
if (!device_.has_value()) {
return {};
}
CF_EXPECT(Mute());
return {};
}
}
Result<void> PollAResult(SenderId* sender_id) {
// Step 1: Initialize connection with casimir
if (!device_.has_value()) {
device_ = CF_EXPECT(ConnectToCasimir(), "Failed to connect with casimir");
CF_EXPECT(Unmute(), "failed to unmute the device");
}
// Step 2: Poll
/* Casimir control server seems to be dropping integer values of zero.
This works around that issue by translating the 0-based sender IDs to
be 1-based.*/
sender_id->set_sender_id(
CF_EXPECT(device_->Poll(),
"Failed to poll and select NFC-A and ISO-DEP") +
1);
return {};
}
Status PollA(ServerContext*, const Void*, SenderId* sender_id) override {
return ResultToStatus(PollAResult(sender_id));
}
Result<void> SendApduResult(const SendApduRequest* request,
SendApduReply* response) {
// Step 0: Parse input
std::vector<std::vector<uint8_t>> apdu_bytes;
for (const std::string& apdu_hex_string : request->apdu_hex_strings()) {
apdu_bytes.emplace_back(
CF_EXPECT(HexToBytes(apdu_hex_string),
"Failed to parse input. Must only contain [0-9a-fA-F]"));
}
// Step 1: Initialize connection with casimir
CF_EXPECT(Init());
int16_t id;
if (request->has_sender_id()) {
/* Casimir control server seems to be dropping integer values of zero.
This works around that issue by translating the 0-based sender IDs to
be 1-based.*/
id = request->sender_id() - 1;
} else {
// Step 2: Poll
SenderId sender_id;
CF_EXPECT(PollAResult(&sender_id));
id = sender_id.sender_id() - 1;
}
// Step 3: Send APDU bytes
response->clear_response_hex_strings();
for (int i = 0; i < apdu_bytes.size(); i++) {
std::vector<uint8_t> bytes =
CF_EXPECT(device_->SendApdu(id, std::move(apdu_bytes[i])),
"Failed to send APDU bytes");
std::string resp = android::base::HexString(
reinterpret_cast<void*>(bytes.data()), bytes.size());
response->add_response_hex_strings(std::move(resp));
}
// Returns OK although returned bytes is valids if ends with [0x90, 0x00].
return {};
}
Status SendApdu(ServerContext*, const SendApduRequest* request,
SendApduReply* response) override {
return ResultToStatus(SendApduResult(request, response));
}
Result<void> SendBroadcastResult(const SendBroadcastRequest* request,
SendBroadcastResponse* response) {
// Default configuration values
TransceiveConfiguration requestConfig;
// Type A
requestConfig.set_type("A");
// CRC present
requestConfig.set_crc(true);
// 8 bits in last byte
requestConfig.set_bits(8);
// 106kbps
requestConfig.set_bitrate(106);
// No timeout, timeout immediately
requestConfig.clear_timeout();
// 100% output power
requestConfig.set_power(100);
// Overwrite defaults with provided configuration, if present
if (request->has_configuration()) {
auto config = request->configuration();
if (config.has_type()) {
requestConfig.set_type(config.type());
}
if (config.has_crc()) {
requestConfig.set_crc(config.crc());
}
if (config.has_bits()) {
requestConfig.set_bits(config.bits());
}
if (config.has_bitrate()) {
requestConfig.set_bitrate(config.bitrate());
}
if (config.has_timeout()) {
requestConfig.set_timeout(config.timeout());
}
if (config.has_power()) {
requestConfig.set_power(config.power());
}
}
if (!device_.has_value()) {
device_ = CF_EXPECT(ConnectToCasimir(), "Failed to connect with casimir");
CF_EXPECT(Unmute(), "failed to unmute the device");
}
std::vector<uint8_t> requestData =
CF_EXPECT(HexToBytes(request->data()),
"Failed to parse input. Must only contain [0-9a-fA-F]");
CF_EXPECT(device_->SendBroadcast(
requestData, requestConfig.type(), requestConfig.crc(),
requestConfig.bits(), requestConfig.bitrate(),
requestConfig.timeout(), requestConfig.power()),
"Failed to send broadcast data");
return {}; // Success
}
Status SendBroadcast(ServerContext*, const SendBroadcastRequest* request,
SendBroadcastResponse* response) override {
return ResultToStatus(SendBroadcastResult(request, response));
}
std::optional<CasimirController> device_;
bool is_radio_on_ = false;
};
void RunServer(int argc, char** argv) {
::gflags::ParseCommandLineFlags(&argc, &argv, true);
std::string server_address("unix:" + FLAGS_grpc_uds_path);
CasimirControlServiceImpl service;
grpc::EnableDefaultHealthCheckService(true);
grpc::reflection::InitProtoReflectionServerBuilderPlugin();
ServerBuilder builder;
// Listen on the given address without any authentication mechanism.
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
// Register "service" as the instance through which we'll communicate with
// clients. In this case it corresponds to an *synchronous* service.
builder.RegisterService(&service);
// Finally assemble the server.
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
// Wait for the server to shutdown. Note that some other thread must be
// responsible for shutting down the server for this call to ever return.
server->Wait();
}
} // namespace
} // namespace cuttlefish
int main(int argc, char** argv) {
cuttlefish::RunServer(argc, argv);
return 0;
}