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: server:
ip: "127.0.0.1" ip: "127.0.0.1"
port: 8085 port: 9095
test_data: test_data:
intrinsic_params: [1.1, 0.0, 0.0, 0.0, 1.1, 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.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] extrinsic_params: [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
cloud_point: cloud_point:
- [0.1, 0.2, 0.3] - [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 #pragma once
#include "jsonrpccxx/iclientconnector.hpp"
#include <asio.hpp>
#include <cstddef>
#include <glog/logging.h> #include <glog/logging.h>
#include <jsonrpccxx/client.hpp>
#include <nlohmann/json.hpp>
#include <stdexcept>
#include <string> #include <string>
#include <vector> #include <vector>
#include <nlohmann/json.hpp>
#include <asio.hpp>
#include <stdexcept>
namespace cloud_point_rpc { namespace cloud_point_rpc {
class RpcClient { class TCPConnector : public jsonrpccxx::IClientConnector {
public: public:
RpcClient() : socket_(io_context_) {} TCPConnector(const std::string &ip, size_t port) noexcept(false)
: io_context_(), socket_(io_context_) {
void connect(const std::string& ip, int port) {
try { try {
LOG(INFO) << "Client connecting to " << ip << ":" << port; LOG(INFO) << "Client connecting to " << ip << ":" << port;
asio::ip::tcp::endpoint endpoint(asio::ip::make_address(ip), port); asio::ip::tcp::endpoint endpoint(asio::ip::make_address(ip), port);
socket_.connect(endpoint); socket_.connect(endpoint);
LOG(INFO) << "Client connected"; LOG(INFO) << "Client connected";
} catch (const std::exception& e) { } catch (const std::exception &e) {
throw std::runtime_error(std::string("Connection Failed: ") + e.what()); 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() { [[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() { [[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() { [[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()) { ~RpcClient() = default;
using json = nlohmann::json;
// Create Request private:
json request = { int id{0};
{"jsonrpc", "2.0"},
{"method", method},
{"params", params},
{"id", ++id_counter_}
};
std::string request_str = request.dump();
// 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;
}; };
} // namespace cloud_point_rpc } // namespace cloud_point_rpc

View File

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

View File

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

View File

@ -1,28 +1,29 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <vector>
#include "cloud_point_rpc/rpc_server.hpp" #include "cloud_point_rpc/rpc_server.hpp"
#include "cloud_point_rpc/service.hpp" #include "cloud_point_rpc/service.hpp"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <string>
#include <vector>
using json = nlohmann::json; using json = nlohmann::json;
using namespace cloud_point_rpc; using namespace cloud_point_rpc;
class RpcServerTest : public ::testing::Test { class RpcServerTest : public ::testing::Test {
protected: protected:
RpcServer server; RpcServer server;
Service service; Service service;
void SetUp() override { void SetUp() override {
server.register_method("get-intrinsic-params", [&](const json&) { server.register_method("get-intrinsic-params", [&](const json &) {
return service.get_intrinsic_params(); return service.get_intrinsic_params();
}); });
} }
}; };
TEST_F(RpcServerTest, GetIntrinsicParamsReturnsMatrix) { 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); std::string response_str = server.process(request);
json response = json::parse(response_str); json response = json::parse(response_str);
@ -40,7 +41,8 @@ TEST_F(RpcServerTest, GetIntrinsicParamsReturnsMatrix) {
} }
TEST_F(RpcServerTest, MethodNotFoundReturnsError) { 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); std::string response_str = server.process(request);
json response = json::parse(response_str); json response = json::parse(response_str);