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,19 +1,21 @@
#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);
@ -24,59 +26,63 @@ class RpcClient {
} }
} }
[[nodiscard]] std::vector<double> get_intrinsic_params() { std::string Send(const std::string &request) override {
return call("get-intrinsic-params")["result"].get<std::vector<double>>();
}
[[nodiscard]] std::vector<double> get_extrinsic_params() {
return call("get-extrinsic-params")["result"].get<std::vector<double>>();
}
[[nodiscard]] std::vector<std::vector<double>> get_cloud_point() {
return call("get-cloud-point")["result"].get<std::vector<std::vector<double>>>();
}
[[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();
// Send // Send
LOG(INFO) << "Client sending: " << request_str; LOG(INFO) << "Client sending: " << request;
asio::write(socket_, asio::buffer(request_str)); asio::write(socket_, asio::buffer(request));
return read();
// 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; 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;
} }
~RpcClient() { uint64_t packet_size = reinterpret_cast<uint64_t>(header.data());
// Socket closes automatically
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: private:
asio::io_context io_context_; asio::io_context io_context_;
asio::ip::tcp::socket socket_; asio::ip::tcp::socket socket_;
int id_counter_ = 0; };
class RpcClient : public jsonrpccxx::JsonRpcClient {
public:
RpcClient(TCPConnector &connector)
: jsonrpccxx::JsonRpcClient(connector, jsonrpccxx::version::v2) {}
[[nodiscard]] std::vector<double> get_intrinsic_params() {
return this->CallMethod<std::vector<double>>(id++, "get-intrinsic-params");
}
[[nodiscard]] std::vector<double> get_extrinsic_params() {
return this->CallMethod<std::vector<double>>(id++, "get-extrinsic-params");
}
[[nodiscard]] std::vector<std::vector<double>> get_cloud_point() {
return this->CallMethod<std::vector<std::vector<double>>>(
id++, "get-cloud-point");
}
~RpcClient() = default;
private:
int id{0};
}; };
} // namespace cloud_point_rpc } // namespace cloud_point_rpc

View File

@ -2,6 +2,7 @@
#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 {
@ -14,19 +15,39 @@ void print_menu(std::ostream& output) {
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) {
std::string result;
for (auto &el : v) {
result += std::to_string(el) + " ";
}
return result;
}
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;
}
int run_cli(std::istream &input, std::ostream &output, const std::string &ip,
int port) {
try { try {
RpcClient client; TCPConnector connector(ip, port);
client.connect(ip, port); RpcClient client(connector);
output << "Connected to " << ip << ":" << port << std::endl; output << "Connected to " << ip << ":" << port << std::endl;
std::string choice; std::string choice;
while (true) { while (true) {
print_menu(output); print_menu(output);
if (!(input >> choice)) break; if (!(input >> choice))
break;
if (choice == "0") break; if (choice == "0")
break;
std::string method; std::string method;
if (choice == "1") { if (choice == "1") {
@ -41,8 +62,19 @@ int run_cli(std::istream& input, std::ostream& output, const std::string& ip, in
} }
try { try {
auto response = client.call(method);
output << "\nResponse:\n" << response.dump(4) << std::endl; if (method == "get-intrinsic-params") {
auto response = client.get_intrinsic_params();
LOG(INFO) << vector_to_string(response);
}
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) { } catch (const std::exception &e) {
output << "\nRPC Error: " << e.what() << std::endl; output << "\nRPC Error: " << e.what() << std::endl;
} }

View File

@ -1,13 +1,13 @@
#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>
@ -18,7 +18,8 @@ class IntegrationTest : public ::testing::Test {
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
<< "server:\n"
<< " ip: \"127.0.0.1\"\n" << " ip: \"127.0.0.1\"\n"
<< " port: 9095\n" << " port: 9095\n"
<< "test_data:\n" << "test_data:\n"
@ -32,27 +33,25 @@ class IntegrationTest : public ::testing::Test {
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
// TearDown
throw; 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",
[&](const nlohmann::json &) {
return service_->get_intrinsic_params(); 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));
@ -77,13 +76,12 @@ 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;
@ -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,10 +1,10 @@
#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;
@ -22,7 +22,8 @@ class RpcServerTest : public ::testing::Test {
}; };
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);