This commit is contained in:
Artur Mukhamadiev 2026-02-07 15:43:12 +03:00
parent 4ddaea91a7
commit 19d21ec47d
5 changed files with 186 additions and 153 deletions

View File

@ -1,11 +1,8 @@
server:
ip: "127.0.0.1"
port: 8085
port: 9095
test_data:
intrinsic_params: [1.1, 0.0, 0.0, 0.0, 1.1, 0.0, 0.0, 0.0, 1.0]
extrinsic_params: [1.0, 0.0, 0.0, 0.5, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
intrinsic_params: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
extrinsic_params: [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
cloud_point:
- [0.1, 0.2, 0.3]
- [1.1, 1.2, 1.3]
- [5.5, 6.6, 7.7]

View File

@ -1,82 +1,88 @@
#pragma once
#include "jsonrpccxx/iclientconnector.hpp"
#include <asio.hpp>
#include <cstddef>
#include <glog/logging.h>
#include <jsonrpccxx/client.hpp>
#include <nlohmann/json.hpp>
#include <stdexcept>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include <asio.hpp>
#include <stdexcept>
namespace cloud_point_rpc {
class RpcClient {
public:
RpcClient() : socket_(io_context_) {}
void connect(const std::string& ip, int port) {
class TCPConnector : public jsonrpccxx::IClientConnector {
public:
TCPConnector(const std::string &ip, size_t port) noexcept(false)
: io_context_(), socket_(io_context_) {
try {
LOG(INFO) << "Client connecting to " << ip << ":" << port;
asio::ip::tcp::endpoint endpoint(asio::ip::make_address(ip), port);
socket_.connect(endpoint);
LOG(INFO) << "Client connected";
} catch (const std::exception& e) {
} catch (const std::exception &e) {
throw std::runtime_error(std::string("Connection Failed: ") + e.what());
}
}
std::string Send(const std::string &request) override {
// Send
LOG(INFO) << "Client sending: " << request;
asio::write(socket_, asio::buffer(request));
return read();
}
protected:
std::string read() {
std::string result;
std::array<char, 8> header;
LOG(INFO) << "trying to read";
size_t len = asio::read(socket_, asio::buffer(header));
if (len != sizeof(uint64_t)) {
LOG(ERROR) << "failed to read header";
return result;
}
uint64_t packet_size = reinterpret_cast<uint64_t>(header.data());
std::vector<char> payload(packet_size);
len = asio::read(socket_, asio::buffer(payload));
LOG(INFO) << std::format("read len={}", len);
result = payload.data();
return result;
}
private:
asio::io_context io_context_;
asio::ip::tcp::socket socket_;
};
class RpcClient : public jsonrpccxx::JsonRpcClient {
public:
RpcClient(TCPConnector &connector)
: jsonrpccxx::JsonRpcClient(connector, jsonrpccxx::version::v2) {}
[[nodiscard]] std::vector<double> get_intrinsic_params() {
return call("get-intrinsic-params")["result"].get<std::vector<double>>();
return this->CallMethod<std::vector<double>>(id++, "get-intrinsic-params");
}
[[nodiscard]] std::vector<double> get_extrinsic_params() {
return call("get-extrinsic-params")["result"].get<std::vector<double>>();
return this->CallMethod<std::vector<double>>(id++, "get-extrinsic-params");
}
[[nodiscard]] std::vector<std::vector<double>> get_cloud_point() {
return call("get-cloud-point")["result"].get<std::vector<std::vector<double>>>();
return this->CallMethod<std::vector<std::vector<double>>>(
id++, "get-cloud-point");
}
[[nodiscard]] nlohmann::json call(const std::string& method, const nlohmann::json& params = nlohmann::json::object()) {
using json = nlohmann::json;
// Create Request
json request = {
{"jsonrpc", "2.0"},
{"method", method},
{"params", params},
{"id", ++id_counter_}
};
std::string request_str = request.dump();
~RpcClient() = default;
// Send
LOG(INFO) << "Client sending: " << request_str;
asio::write(socket_, asio::buffer(request_str));
// Read Response
LOG(INFO) << "Client reading response...";
std::vector<char> buffer(65536);
asio::error_code ec;
size_t len = socket_.read_some(asio::buffer(buffer), ec);
if (ec) throw std::system_error(ec);
LOG(INFO) << "Client read " << len << " bytes";
json response = json::parse(std::string(buffer.data(), len));
if (response.contains("error")) {
throw std::runtime_error(response["error"]["message"].get<std::string>());
}
return response;
}
~RpcClient() {
// Socket closes automatically
}
private:
asio::io_context io_context_;
asio::ip::tcp::socket socket_;
int id_counter_ = 0;
private:
int id{0};
};
} // namespace cloud_point_rpc

View File

@ -2,57 +2,89 @@
#include "cloud_point_rpc/rpc_client.hpp"
#include <glog/logging.h>
#include <iomanip>
#include <string>
namespace cloud_point_rpc {
void print_menu(std::ostream& output) {
output << "\n=== Cloud Point RPC CLI ===" << std::endl;
output << "1. get-intrinsic-params" << std::endl;
output << "2. get-extrinsic-params" << std::endl;
output << "3. get-cloud-point" << std::endl;
output << "0. Exit" << std::endl;
output << "Select an option: ";
void print_menu(std::ostream &output) {
output << "\n=== Cloud Point RPC CLI ===" << std::endl;
output << "1. get-intrinsic-params" << std::endl;
output << "2. get-extrinsic-params" << std::endl;
output << "3. get-cloud-point" << std::endl;
output << "0. Exit" << std::endl;
output << "Select an option: ";
}
int run_cli(std::istream& input, std::ostream& output, const std::string& ip, int port) {
try {
RpcClient client;
client.connect(ip, port);
output << "Connected to " << ip << ":" << port << std::endl;
template <typename T> std::string vector_to_string(const std::vector<T> &v) {
std::string result;
for (auto &el : v) {
result += std::to_string(el) + " ";
}
return result;
}
std::string choice;
while (true) {
print_menu(output);
if (!(input >> choice)) break;
template <typename T>
std::string vector_to_string(const std::vector<std::vector<T>> &v) {
std::string result;
for (auto &el : v) {
result += vector_to_string(el) + "\n";
}
return result;
}
if (choice == "0") break;
int run_cli(std::istream &input, std::ostream &output, const std::string &ip,
int port) {
try {
TCPConnector connector(ip, port);
RpcClient client(connector);
std::string method;
if (choice == "1") {
method = "get-intrinsic-params";
} else if (choice == "2") {
method = "get-extrinsic-params";
} else if (choice == "3") {
method = "get-cloud-point";
} else {
output << "Invalid option: " << choice << std::endl;
continue;
}
output << "Connected to " << ip << ":" << port << std::endl;
try {
auto response = client.call(method);
output << "\nResponse:\n" << response.dump(4) << std::endl;
} catch (const std::exception& e) {
output << "\nRPC Error: " << e.what() << std::endl;
}
std::string choice;
while (true) {
print_menu(output);
if (!(input >> choice))
break;
if (choice == "0")
break;
std::string method;
if (choice == "1") {
method = "get-intrinsic-params";
} else if (choice == "2") {
method = "get-extrinsic-params";
} else if (choice == "3") {
method = "get-cloud-point";
} else {
output << "Invalid option: " << choice << std::endl;
continue;
}
try {
if (method == "get-intrinsic-params") {
auto response = client.get_intrinsic_params();
LOG(INFO) << vector_to_string(response);
}
} catch (const std::exception& e) {
LOG(ERROR) << "CLI Error: " << e.what();
output << "Error: " << e.what() << std::endl;
return 1;
if (method == "get-extrinsic-params") {
auto response = client.get_extrinsic_params();
LOG(INFO) << vector_to_string(response);
}
if (method == "get-cloud-point") {
auto response = client.get_cloud_point();
LOG(INFO) << vector_to_string(response);
}
} catch (const std::exception &e) {
output << "\nRPC Error: " << e.what() << std::endl;
}
}
return 0;
} catch (const std::exception &e) {
LOG(ERROR) << "CLI Error: " << e.what();
output << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
} // namespace cloud_point_rpc

View File

@ -1,58 +1,57 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <thread>
#include <chrono>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <thread>
#include "cloud_point_rpc/config.hpp"
#include "cloud_point_rpc/rpc_client.hpp"
#include "cloud_point_rpc/rpc_server.hpp"
#include "cloud_point_rpc/service.hpp"
#include "cloud_point_rpc/tcp_server.hpp"
#include "cloud_point_rpc/rpc_client.hpp"
#include <fstream>
using namespace cloud_point_rpc;
class IntegrationTest : public ::testing::Test {
protected:
protected:
void SetUp() override {
// Create a temporary config file for testing
std::ofstream config_file("config.yaml");
config_file << "server:\n"
<< " ip: \"127.0.0.1\"\n"
<< " port: 9095\n"
<< "test_data:\n"
<< " intrinsic_params: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]\n"
<< " extrinsic_params: [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]\n"
<< " cloud_point:\n"
<< " - [0.1, 0.2, 0.3]\n";
config_file
<< "server:\n"
<< " ip: \"127.0.0.1\"\n"
<< " port: 9095\n"
<< "test_data:\n"
<< " intrinsic_params: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]\n"
<< " extrinsic_params: [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]\n"
<< " cloud_point:\n"
<< " - [0.1, 0.2, 0.3]\n";
config_file.close();
// Setup Mock Server
try {
config_ = ConfigLoader::load("config.yaml");
config_ = ConfigLoader::load("config.yaml");
} catch (...) {
// If config fails, we can't proceed, but we should avoid crashing in TearDown
throw;
// If config fails, we can't proceed, but we should avoid crashing in
// TearDown
throw;
}
service_ = std::make_unique<Service>(config_.test_data);
rpc_server_ = std::make_unique<RpcServer>();
rpc_server_->register_method("get-intrinsic-params", [&](const nlohmann::json&) {
return service_->get_intrinsic_params();
});
rpc_server_->register_method("get-intrinsic-params",
[&](const nlohmann::json &) {
return service_->get_intrinsic_params();
});
// Start Server Thread
tcp_server_ = std::make_unique<TcpServer>(config_.server.ip, config_.server.port,
[&](const std::string& req) {
return rpc_server_->process(req);
}
);
server_thread_ = std::thread([&]() {
tcp_server_->start();
});
tcp_server_ = std::make_unique<TcpServer>(
config_.server.ip, config_.server.port,
[&](const std::string &req) { return rpc_server_->process(req); });
server_thread_ = std::thread([&]() { tcp_server_->start(); });
// Give server time to start
std::this_thread::sleep_for(std::chrono::milliseconds(200));
@ -60,7 +59,7 @@ class IntegrationTest : public ::testing::Test {
void TearDown() override {
if (tcp_server_) {
tcp_server_->stop();
tcp_server_->stop();
}
if (server_thread_.joinable()) {
server_thread_.join();
@ -77,16 +76,15 @@ class IntegrationTest : public ::testing::Test {
};
TEST_F(IntegrationTest, ClientCanConnectAndRetrieveValues) {
RpcClient client;
// Act: Connect
ASSERT_NO_THROW(client.connect(config_.server.ip, config_.server.port));
TCPConnector connector(config_.server.ip, config_.server.port);
RpcClient client(connector);
// Act: Call Method
auto params = client.get_intrinsic_params();
std::vector<double> params;
EXPECT_NO_THROW(params = client.get_intrinsic_params());
// Assert: Values match config
const auto& expected = config_.test_data.intrinsic_params;
const auto &expected = config_.test_data.intrinsic_params;
ASSERT_EQ(params.size(), expected.size());
for (size_t i = 0; i < params.size(); ++i) {
EXPECT_DOUBLE_EQ(params[i], expected[i]);
@ -94,7 +92,5 @@ TEST_F(IntegrationTest, ClientCanConnectAndRetrieveValues) {
}
TEST_F(IntegrationTest, ClientHandlesConnectionError) {
RpcClient client;
// Try connecting to a closed port
EXPECT_THROW(client.connect("127.0.0.1", 9999), std::runtime_error);
EXPECT_THROW(TCPConnector connector("127.0.0.1", 9999), std::runtime_error);
}

View File

@ -1,36 +1,37 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <vector>
#include "cloud_point_rpc/rpc_server.hpp"
#include "cloud_point_rpc/service.hpp"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
using json = nlohmann::json;
using namespace cloud_point_rpc;
class RpcServerTest : public ::testing::Test {
protected:
protected:
RpcServer server;
Service service;
void SetUp() override {
server.register_method("get-intrinsic-params", [&](const json&) {
server.register_method("get-intrinsic-params", [&](const json &) {
return service.get_intrinsic_params();
});
}
};
TEST_F(RpcServerTest, GetIntrinsicParamsReturnsMatrix) {
std::string request = R"({"jsonrpc": "2.0", "method": "get-intrinsic-params", "id": 1})";
std::string request =
R"({"jsonrpc": "2.0", "method": "get-intrinsic-params", "id": 1})";
std::string response_str = server.process(request);
json response = json::parse(response_str);
ASSERT_EQ(response["jsonrpc"], "2.0");
ASSERT_EQ(response["id"], 1);
ASSERT_TRUE(response.contains("result"));
auto result = response["result"].get<std::vector<double>>();
EXPECT_EQ(result.size(), 9);
// Verify Identity Matrix
@ -40,11 +41,12 @@ TEST_F(RpcServerTest, GetIntrinsicParamsReturnsMatrix) {
}
TEST_F(RpcServerTest, MethodNotFoundReturnsError) {
std::string request = R"({"jsonrpc": "2.0", "method": "unknown-method", "id": 99})";
std::string request =
R"({"jsonrpc": "2.0", "method": "unknown-method", "id": 99})";
std::string response_str = server.process(request);
json response = json::parse(response_str);
ASSERT_TRUE(response.contains("error"));
EXPECT_EQ(response["error"]["code"], -32601);
EXPECT_EQ(response["error"]["message"], "Method not found");
@ -53,9 +55,9 @@ TEST_F(RpcServerTest, MethodNotFoundReturnsError) {
TEST_F(RpcServerTest, InvalidJsonReturnsParseError) {
std::string request = R"({"jsonrpc": "2.0", "method": "broken-json...)";
std::string response_str = server.process(request);
json response = json::parse(response_str);
ASSERT_TRUE(response.contains("error"));
EXPECT_EQ(response["error"]["code"], -32700);
}