Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/linux/rfattenuator/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ target_sources(${PROJECT_NAME}-rfattenuator-cli-linux
target_link_libraries(${PROJECT_NAME}-rfattenuator-cli-linux
PRIVATE
${PROJECT_NAME}-rfattenuator-linux
${PROJECT_NAME}-protocol
)

set_target_properties(${PROJECT_NAME}-rfattenuator-cli-linux
Expand Down
294 changes: 277 additions & 17 deletions src/linux/rfattenuator/cli/Main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,172 @@
#include <functional>
#include <ios>
#include <iostream>
#include <limits>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <tuple>

#include <google/protobuf/empty.pb.h>
#include <grpcpp/client_context.h>
#include <grpcpp/create_channel.h>
#include <grpcpp/security/credentials.h>
#include <microsoft/net/remote/protocol/NetRemoteRfAttenuator.grpc.pb.h>
#include <microsoft/net/remote/protocol/NetRemoteRfAttenuatorService.grpc.pb.h>
#include <microsoft/net/remote/service/RfAttenuator.hxx>
#include <microsoft/net/remote/service/RfAttenuatorFactory.hxx>
// #include "RfAttenuatorSoftwareSimulated.hxx"

namespace detail
{
struct RfAttenuatorNetRemoteException : public RfAttenuatorException
{
explicit RfAttenuatorNetRemoteException(std::string message) :
Message(std::move(message))
{
}

const char*
what() const noexcept override
{
return std::data(Message);
}

std::string Message;
};

struct RfAttenuatorNetRemoteController : public IRfAttenuatorController
{
explicit RfAttenuatorNetRemoteController(std::string address) :
m_address(std::move(address)),
m_channel(grpc::CreateChannel(m_address, grpc::InsecureChannelCredentials())),
m_client(Microsoft::Net::Remote::Service::NetRemoteRfAttenuator::NewStub(m_channel))
{
if (m_channel == nullptr || m_client == nullptr) {
throw RfAttenuatorNetRemoteException(std::format("failed to create netremote client for '{}'", m_address));
}
}

void
Reset() override
{
const google::protobuf::Empty request{ };
Microsoft::Net::Remote::RfAttenuator::ResetResult response{ };
grpc::ClientContext clientContext{ };

auto status = m_client->Reset(&clientContext, request, &response);
ThrowIfRpcFailed(status, "Reset");
ThrowIfOperationFailed(response.status(), "Reset");
}

RfAttenuatorProperties
GetProperties() override
{
const google::protobuf::Empty request{ };
Microsoft::Net::Remote::RfAttenuator::GetPropertiesResult response{ };
grpc::ClientContext clientContext{ };

auto status = m_client->GetProperties(&clientContext, request, &response);
ThrowIfRpcFailed(status, "GetProperties");
ThrowIfOperationFailed(response.status(), "GetProperties");

std::vector<uint32_t> channels{ };
channels.reserve(static_cast<size_t>(response.channels_size()));
for (const auto channel : response.channels()) {
channels.emplace_back(channel);
}

return RfAttenuatorProperties{
.Channels = std::move(channels),
.AttenuationRangeDbmMin = response.attenuationrangedbmmin(),
.AttenuationRangeDbmMax = response.attenuationrangedbmmax(),
.AttenuationStepDbmMin = response.attenuationstepdbmmin(),
.AttenuationStepDbmMax = response.attenuationstepdbmmax(),
.AttenuationAccuracyDbmMin = response.attenuationaccuracydbmmin(),
.AttenuationAccuracyDbmMax = response.attenuationaccuracydbmmax(),
.FrequencyBandwidthMHzMin = response.frequencybandwidthmhzmin(),
.FrequencyBandwidthMHzMax = response.frequencybandwidthmhzmax(),
.SupportsSweep = response.supportssweep(),
.Identification = response.identification(),
};
}

double
GetAttenuationForChannel(uint32_t channel) override
{
Microsoft::Net::Remote::RfAttenuator::GetAttenuationRequest request{ };
request.set_channel(channel);

Microsoft::Net::Remote::RfAttenuator::GetAttenuationResult response{ };
grpc::ClientContext clientContext{ };

auto status = m_client->GetAttenuationForChannel(&clientContext, request, &response);
ThrowIfRpcFailed(status, "GetAttenuationForChannel");
ThrowIfOperationFailed(response.status(), "GetAttenuationForChannel");

return response.attenuationdbm();
}

bool
SetAttenuationForChannel(uint32_t channel, double attenuation) override
{
Microsoft::Net::Remote::RfAttenuator::SetAttenuationRequest request{ };
request.set_channel(channel);
request.set_attenuationdbm(attenuation);

Microsoft::Net::Remote::RfAttenuator::SetAttenuationResult response{ };
grpc::ClientContext clientContext{ };

auto status = m_client->SetAttenuationForChannel(&clientContext, request, &response);
ThrowIfRpcFailed(status, "SetAttenuationForChannel");
ThrowIfOperationFailed(response.status(), "SetAttenuationForChannel");

return true;
}

static void
ThrowIfRpcFailed(const grpc::Status& status, std::string_view operation)
{
if (!status.ok()) {
throw RfAttenuatorNetRemoteException(
std::format("{} RPC failed ({}: {})", operation, static_cast<int>(status.error_code()), status.error_message()));
}
}

static void
ThrowIfOperationFailed(const Microsoft::Net::Remote::RfAttenuator::RfAttenuatorOperationStatus& status, std::string_view operation)
{
if (status.code() != Microsoft::Net::Remote::RfAttenuator::RfAttenuatorOperationStatusCode::RfAttenuatorOperationStatusCodeSucceeded) {
throw RfAttenuatorNetRemoteException(std::format(
"{} operation failed (code={}, message='{}')",
operation,
static_cast<int>(status.code()),
status.has_message() ? status.message() : ""));
}
}

private:
std::string m_address{ };
std::shared_ptr<grpc::Channel> m_channel{ };
std::unique_ptr<Microsoft::Net::Remote::Service::NetRemoteRfAttenuator::Stub> m_client{ };
};

bool
TryParsePort(const std::string& portString, uint16_t& port)
{
std::istringstream ss{ portString };
uint32_t parsedPort{ 0 };
ss >> parsedPort;

if (!ss || !ss.eof() || parsedPort > std::numeric_limits<uint16_t>::max()) {
return false;
}

port = static_cast<uint16_t>(parsedPort);
return true;
}

void
DisplayAttenuatorProperties(const RfAttenuatorProperties& properties, std::ostream& out)
{
Expand Down Expand Up @@ -160,19 +314,23 @@ ShowUsage(const char* programPath)
const auto programName = std::filesystem::path(programPath).filename().string();

// clang-format off
std::cout << programName << " <attenuator type> [attenuator-type-args] [validation-mode]" << std::endl
std::cout << programName << " <attenuator type> [attenuator-type-args] [command]" << std::endl
<< std::endl
<< " Attenuator Types: " << std::endl
<< " 'software'" << std::endl
<< " 'socket'" << std::endl
<< " 'netremote'" << std::endl
<< std::endl
<< " Attenuator Type Arguments:" << std::endl
<< " 'software': none" << std::endl
<< " 'socket': AFW83 <ip address> <port>" << std::endl
<< " 'netremote': <ip address> <port>" << std::endl
<< std::endl
<< " Validation Modes: " << std::endl
<< " 'basic' (default)" << std::endl
<< " 'extended'" << std::endl
<< " Commands (optional): " << std::endl
<< " 'get': get <channel> - Get attenuation for a channel" << std::endl
<< " 'set': set <channel> <attenuation> - Set attenuation for a channel" << std::endl
<< " 'basic': Run basic validation (default if no command specified)" << std::endl
<< " 'extended': Run extended validation" << std::endl
<< std::endl;
// clang-format on
}
Expand All @@ -184,8 +342,11 @@ main(int argc, char* argv[])
static const auto MinExpectedArguments = 1;
static const auto MinExpectedArgumentsSoftware = 1;
static const auto MinExpectedArgumentsSocket = 4;
static const auto ValidationModeArgBasic = "basic";
static const auto ValidationModeArgExtended = "extended";
static const auto MinExpectedArgumentsNetRemote = 3;
static const auto CommandArgBasic = "basic";
static const auto CommandArgExtended = "extended";
static const auto CommandArgGet = "get";
static const auto CommandArgSet = "set";

if (argc < (MinExpectedArguments + 1)) {
std::cerr << std::format("error: {} arguments are expected, {} specified", MinExpectedArguments, argc - 1) << std::endl;
Expand All @@ -197,7 +358,6 @@ main(int argc, char* argv[])

int argIndex{ 1 };
int numArgsExpected{ 0 };
std::string validationMode{ ValidationModeArgBasic };
std::string attenuatorType{ argv[argIndex++] };

if (attenuatorType == "software") {
Expand All @@ -213,10 +373,6 @@ main(int argc, char* argv[])
return -1;
}

if (argc > numArgsExpected + 1) {
validationMode = argv[argIndex++];
}

attenuator = RfAttenuatorFactory::CreateSimulatedSoftwareAttenuator();
} else if (attenuatorType == "socket") {
numArgsExpected = MinExpectedArgumentsSocket;
Expand All @@ -235,13 +391,14 @@ main(int argc, char* argv[])
const std::string attenuatorName{ argv[argIndex++] };
const std::string ipAddress{ argv[argIndex++] };
const std::string portString{ argv[argIndex++] };
if (argc > numArgsExpected + 1) {
validationMode = argv[argIndex++];
}

std::istringstream ss{ portString };
uint16_t port{ 0 };
ss >> port;
if (!detail::TryParsePort(portString, port)) {
std::cerr << std::format("invalid socket attenuator port '{}'", portString) << std::endl;
detail::ShowUsage(argv[0]);
return -1;
}

if (attenuatorName == "AFW83") {
attenuator = RfAttenuatorFactory::CreateSocketAfw83Attenuator(std::move(ipAddress), port);
}
Expand All @@ -250,6 +407,39 @@ main(int argc, char* argv[])
detail::ShowUsage(argv[0]);
return -1;
}
} else if (attenuatorType == "netremote") {
numArgsExpected = MinExpectedArgumentsNetRemote;

if (argc < numArgsExpected + 1) {
std::cerr << std::format(
"error: {} arguments are expected for netremote attenuators, {} specified",
numArgsExpected,
argc - 1)
<< std::endl;
detail::ShowUsage(argv[0]);
return -1;
}

const std::string ipAddress{ argv[argIndex++] };
const std::string portString{ argv[argIndex++] };

uint16_t port{ 0 };
if (!detail::TryParsePort(portString, port)) {
std::cerr << std::format("invalid netremote server port '{}'", portString) << std::endl;
detail::ShowUsage(argv[0]);
return -1;
}

const auto address = std::format("{}:{}", ipAddress, port);
std::cout << std::format("Creating netremote attenuator client @ {} ... ", address);

try {
attenuator = std::make_unique<detail::RfAttenuatorNetRemoteController>(address);
std::cout << "succeeded" << std::endl;
} catch (const std::exception& e) {
std::cout << std::format("failed ({})", e.what()) << std::endl;
return -1;
}
} else {
std::cerr << "attenuator type not specified, exiting" << std::endl;
detail::ShowUsage(argv[0]);
Expand All @@ -261,7 +451,77 @@ main(int argc, char* argv[])
return -1;
}

const std::function<bool(IRfAttenuatorController*)> validateSanity = (validationMode == ValidationModeArgExtended)
const std::string command{ (argc > argIndex) ? argv[argIndex++] : CommandArgBasic };

if (command == CommandArgGet) {
// get <channel>
if (argc != argIndex + 1) {
std::cerr << "error: 'get' command requires exactly one argument: <channel>" << std::endl;
detail::ShowUsage(argv[0]);
return -1;
}

uint32_t channel{ 0 };
try {
channel = static_cast<uint32_t>(std::stoul(argv[argIndex]));
} catch (const std::exception& e) {
std::cerr << std::format("error: invalid channel '{}': {}", argv[argIndex], e.what()) << std::endl;
return -1;
}

try {
double attenuation = attenuator->GetAttenuationForChannel(channel);
std::cout << std::format("Attenuation for channel {}: {} dBm", channel, attenuation) << std::endl;
return 0;
} catch (const std::exception& e) {
std::cerr << std::format("error: failed to get attenuation for channel {}: {}", channel, e.what()) << std::endl;
return -1;
}
}

if (command == CommandArgSet) {
// set <channel> <attenuation>
if (argc != argIndex + 2) {
std::cerr << "error: 'set' command requires exactly two arguments: <channel> <attenuation>" << std::endl;
detail::ShowUsage(argv[0]);
return -1;
}

uint32_t channel{ 0 };
double attenuation{ 0.0 };

try {
channel = static_cast<uint32_t>(std::stoul(argv[argIndex]));
attenuation = std::stod(argv[argIndex + 1]);
} catch (const std::exception& e) {
std::cerr << std::format("error: invalid arguments: {}", e.what()) << std::endl;
return -1;
}

try {
std::cout << std::format("Setting attenuation for channel {} to {} dBm: ", channel, attenuation);
bool succeeded = attenuator->SetAttenuationForChannel(channel, attenuation);
std::cout << ((succeeded) ? "succeeded" : "failed") << std::endl;
return succeeded ? 0 : -1;
} catch (const std::exception& e) {
std::cerr << std::format("error: failed to set attenuation: {}", e.what()) << std::endl;
return -1;
}
}

if (command != CommandArgBasic && command != CommandArgExtended) {
std::cerr << std::format("error: unknown command '{}'", command) << std::endl;
detail::ShowUsage(argv[0]);
return -1;
}

if (argc != argIndex) {
std::cerr << std::format("error: unexpected extra arguments provided for command '{}'", command) << std::endl;
detail::ShowUsage(argv[0]);
return -1;
}

const std::function<bool(IRfAttenuatorController*)> validateSanity = (command == CommandArgExtended)
? detail::ValidateAttenuatorSanityExtended
: detail::ValidateAttenuatorSanityBasic;

Expand Down
Loading