Compare commits
1 Commits
master
...
feature/js
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
886e3f1879 |
7
.clang-format
Normal file
7
.clang-format
Normal file
@ -0,0 +1,7 @@
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: true
|
||||
AcrossComments: false
|
||||
71
README.md
71
README.md
@ -6,6 +6,77 @@ Communication JSON RPC protocol and implementation with Unity Scene.
|
||||
|
||||
See [API.md](API.md) for detailed request/response formats.
|
||||
|
||||
## Comm model
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
box ClientProcess #LightBlue
|
||||
Participant Caller
|
||||
Participant CloudPointClient
|
||||
Participant TCPClient
|
||||
end box
|
||||
box UnityProcess #LightGreen
|
||||
Participant TCPServer
|
||||
Participant CloudPointServer
|
||||
Participant UnityWorld
|
||||
end box
|
||||
UnityWorld -> CloudPointServer : init thread
|
||||
activate CloudPointServer
|
||||
CloudPointServer -> TCPServer : await for connection
|
||||
activate TCPServer
|
||||
->CloudPointClient : init thread
|
||||
activate CloudPointClient
|
||||
CloudPointClient -> TCPClient : createConnection
|
||||
TCPClient -> TCPServer : establish connection
|
||||
TCPServer -> CloudPointServer : established
|
||||
deactivate TCPServer
|
||||
CloudPointServer -> TCPServer : await for calls
|
||||
|
||||
TCPServer -> TCPServer : await for packet
|
||||
Caller -> CloudPointClient : I want something
|
||||
activate CloudPointClient
|
||||
CloudPointClient -> CloudPointClient : CallMethod<Something>
|
||||
CloudPointClient -> TCPClient : send(message)
|
||||
activate TCPClient
|
||||
TCPClient -> TCPServer : packet send
|
||||
TCPServer -> TCPServer : await for packet
|
||||
activate TCPServer
|
||||
TCPServer -> TCPServer : read packet
|
||||
TCPServer -> TCPClient : packet read
|
||||
TCPClient -> CloudPointClient : done
|
||||
deactivate TCPClient
|
||||
CloudPointClient -> TCPClient : await for response
|
||||
activate TCPClient
|
||||
TCPClient -> TCPClient : await for packet
|
||||
TCPServer -> CloudPointServer : callMethod
|
||||
activate CloudPointServer
|
||||
CloudPointServer -> UnityWorld : addToStaticQueue
|
||||
|
||||
UnityWorld -> UnityWorld : read from queue
|
||||
activate UnityWorld
|
||||
UnityWorld -> UnityWorld : callMethod
|
||||
UnityWorld -> CloudPointServer: set task return value
|
||||
deactivate UnityWorld
|
||||
CloudPointServer -> TCPServer : return task
|
||||
deactivate CloudPointServer
|
||||
TCPServer -> TCPClient : send response
|
||||
TCPClient -> TCPServer : response read
|
||||
TCPClient -> CloudPointClient : response received
|
||||
TCPServer -> CloudPointServer : done
|
||||
deactivate TCPServer
|
||||
CloudPointClient -> Caller : here what you wanted
|
||||
deactivate CloudPointClient
|
||||
|
||||
Caller -> CloudPointClient : destruct
|
||||
CloudPointClient -> TCPClient : finish waiting
|
||||
deactivate TCPClient
|
||||
deactivate CloudPointClient
|
||||
UnityWorld -> CloudPointServer : destruct
|
||||
deactivate CloudPointServer
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
The project uses **Meson** build system and **C++20**.
|
||||
|
||||
11
config.yaml
11
config.yaml
@ -1,11 +0,0 @@
|
||||
server:
|
||||
ip: "127.0.0.1"
|
||||
port: 8085
|
||||
|
||||
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]
|
||||
cloud_point:
|
||||
- [0.1, 0.2, 0.3]
|
||||
- [1.1, 1.2, 1.3]
|
||||
- [5.5, 6.6, 7.7]
|
||||
@ -7,13 +7,14 @@ namespace cloud_point_rpc {
|
||||
|
||||
/**
|
||||
* @brief Runs the CLI client.
|
||||
*
|
||||
*
|
||||
* @param input Input stream (usually std::cin)
|
||||
* @param output Output stream (usually std::cout)
|
||||
* @param ip Server IP
|
||||
* @param port Server Port
|
||||
* @return int exit code
|
||||
*/
|
||||
int run_cli(std::istream& input, std::ostream& output, const std::string& ip, int port);
|
||||
int run_cli(std::istream &input, std::ostream &output, const std::string &ip,
|
||||
int port);
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
|
||||
@ -1,66 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <glog/logging.h>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <yaml-cpp/yaml.h>
|
||||
#include <glog/logging.h>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
|
||||
struct ServerConfig {
|
||||
std::string ip;
|
||||
int port;
|
||||
std::string ip;
|
||||
int port;
|
||||
};
|
||||
|
||||
struct TestData {
|
||||
std::vector<double> intrinsic_params;
|
||||
std::vector<double> extrinsic_params;
|
||||
std::vector<std::vector<double>> cloud_point;
|
||||
std::vector<double> intrinsic_params;
|
||||
std::vector<double> extrinsic_params;
|
||||
std::vector<std::vector<double>> cloud_point;
|
||||
};
|
||||
|
||||
struct Config {
|
||||
ServerConfig server;
|
||||
TestData test_data;
|
||||
ServerConfig server;
|
||||
TestData test_data;
|
||||
};
|
||||
|
||||
class ConfigLoader {
|
||||
public:
|
||||
static Config load(const std::string& path) {
|
||||
try {
|
||||
YAML::Node config = YAML::LoadFile(path);
|
||||
Config c;
|
||||
|
||||
// Server
|
||||
if (config["server"]) {
|
||||
c.server.ip = config["server"]["ip"].as<std::string>("127.0.0.1");
|
||||
c.server.port = config["server"]["port"].as<int>(8080);
|
||||
} else {
|
||||
c.server.ip = "127.0.0.1";
|
||||
c.server.port = 8080;
|
||||
LOG(WARNING) << "No 'server' section, using defaults.";
|
||||
}
|
||||
public:
|
||||
static Config load(const std::string &path) {
|
||||
try {
|
||||
YAML::Node config = YAML::LoadFile(path);
|
||||
Config c;
|
||||
|
||||
// Test Data
|
||||
if (config["test_data"]) {
|
||||
c.test_data.intrinsic_params = config["test_data"]["intrinsic_params"].as<std::vector<double>>();
|
||||
c.test_data.extrinsic_params = config["test_data"]["extrinsic_params"].as<std::vector<double>>();
|
||||
|
||||
// Parse cloud_point (list of lists)
|
||||
auto cp_node = config["test_data"]["cloud_point"];
|
||||
for (const auto& point_node : cp_node) {
|
||||
c.test_data.cloud_point.push_back(point_node.as<std::vector<double>>());
|
||||
// Server
|
||||
if (config["server"]) {
|
||||
c.server.ip =
|
||||
config["server"]["ip"].as<std::string>("127.0.0.1");
|
||||
c.server.port = config["server"]["port"].as<int>(8080);
|
||||
} else {
|
||||
c.server.ip = "127.0.0.1";
|
||||
c.server.port = 8080;
|
||||
LOG(WARNING) << "No 'server' section, using defaults.";
|
||||
}
|
||||
|
||||
// Test Data
|
||||
if (config["test_data"]) {
|
||||
c.test_data.intrinsic_params =
|
||||
config["test_data"]["intrinsic_params"]
|
||||
.as<std::vector<double>>();
|
||||
c.test_data.extrinsic_params =
|
||||
config["test_data"]["extrinsic_params"]
|
||||
.as<std::vector<double>>();
|
||||
|
||||
// Parse cloud_point (list of lists)
|
||||
auto cp_node = config["test_data"]["cloud_point"];
|
||||
for (const auto &point_node : cp_node) {
|
||||
c.test_data.cloud_point.push_back(
|
||||
point_node.as<std::vector<double>>());
|
||||
}
|
||||
} else {
|
||||
LOG(WARNING) << "No 'test_data' section, using empty/defaults.";
|
||||
}
|
||||
|
||||
return c;
|
||||
} catch (const YAML::Exception &e) {
|
||||
LOG(ERROR) << "Failed to load config: " << e.what();
|
||||
throw std::runtime_error("Config load failed");
|
||||
}
|
||||
} else {
|
||||
LOG(WARNING) << "No 'test_data' section, using empty/defaults.";
|
||||
}
|
||||
|
||||
return c;
|
||||
} catch (const YAML::Exception& e) {
|
||||
LOG(ERROR) << "Failed to load config: " << e.what();
|
||||
throw std::runtime_error("Config load failed");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
|
||||
@ -1,82 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <glog/logging.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "tcp_connector.hpp"
|
||||
#include <asio.hpp>
|
||||
#include <stdexcept>
|
||||
#include <glog/logging.h>
|
||||
#include <jsonrpccxx/client.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
|
||||
class RpcClient {
|
||||
public:
|
||||
RpcClient() : socket_(io_context_) {}
|
||||
class RpcClient : public jsonrpccxx::JsonRpcClient {
|
||||
public:
|
||||
RpcClient(TCPConnector &connector)
|
||||
: jsonrpccxx::JsonRpcClient(connector, jsonrpccxx::version::v2) {}
|
||||
|
||||
void connect(const std::string& ip, int port) {
|
||||
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) {
|
||||
throw std::runtime_error(std::string("Connection Failed: ") + e.what());
|
||||
[[nodiscard]] std::vector<double> get_intrinsic_params() {
|
||||
return call<std::vector<double>>("get-intrinsic-params");
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<double> get_intrinsic_params() {
|
||||
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
|
||||
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>());
|
||||
[[nodiscard]] std::vector<double> get_extrinsic_params() {
|
||||
return call<std::vector<double>>("get-extrinsic-params");
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
~RpcClient() {
|
||||
// Socket closes automatically
|
||||
}
|
||||
[[nodiscard]] std::vector<std::vector<double>> get_cloud_point() {
|
||||
return call<std::vector<std::vector<double>>>("get-cloud-point");
|
||||
}
|
||||
|
||||
private:
|
||||
asio::io_context io_context_;
|
||||
asio::ip::tcp::socket socket_;
|
||||
int id_counter_ = 0;
|
||||
template <typename ReturnType>
|
||||
[[nodiscard]] ReturnType call(std::string_view name) {
|
||||
return this->CallMethod<ReturnType>(id++, name.data());
|
||||
}
|
||||
|
||||
~RpcClient() = default;
|
||||
|
||||
private:
|
||||
int id{0};
|
||||
};
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
|
||||
@ -9,13 +9,13 @@
|
||||
namespace cloud_point_rpc {
|
||||
|
||||
class RpcServer {
|
||||
public:
|
||||
using Handler = std::function<nlohmann::json(const nlohmann::json&)>;
|
||||
public:
|
||||
using Handler = std::function<nlohmann::json(const nlohmann::json &)>;
|
||||
|
||||
void register_method(const std::string& name, Handler handler);
|
||||
[[nodiscard]] std::string process(const std::string& request_str);
|
||||
void register_method(const std::string &name, Handler handler);
|
||||
[[nodiscard]] std::string process(const std::string &request_str);
|
||||
|
||||
private:
|
||||
private:
|
||||
std::map<std::string, Handler> handlers_;
|
||||
};
|
||||
|
||||
|
||||
37
include/cloud_point_rpc/serialize.hpp
Normal file
37
include/cloud_point_rpc/serialize.hpp
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
|
||||
template <typename T>
|
||||
concept NumericType = requires(T param) {
|
||||
requires std::is_integral_v<T> || std::is_floating_point_v<T>;
|
||||
requires !std::is_same_v<bool, T>;
|
||||
requires std::is_arithmetic_v<decltype(param + 1)>;
|
||||
requires !std::is_pointer_v<T>;
|
||||
};
|
||||
|
||||
template <NumericType T> std::vector<uint8_t> serialize(const T &data) {
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(sizeof(data));
|
||||
|
||||
std::memcpy(buffer.data(), &data, sizeof(data));
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
inline void inplace_size_embedding(std::string &str) {
|
||||
auto size = str.size();
|
||||
auto tmp = serialize(size);
|
||||
str.insert(str.begin(), tmp.begin(), tmp.end());
|
||||
}
|
||||
|
||||
template <NumericType T> T deserialize(const std::vector<uint8_t> &buffer) {
|
||||
return *reinterpret_cast<const T *>(buffer.data());
|
||||
}
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
@ -1,20 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "cloud_point_rpc/config.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
|
||||
class Service {
|
||||
public:
|
||||
explicit Service(const TestData& data = {});
|
||||
public:
|
||||
explicit Service(const TestData &data = {});
|
||||
|
||||
[[nodiscard]] std::vector<double> get_intrinsic_params() const;
|
||||
[[nodiscard]] std::vector<double> get_extrinsic_params() const;
|
||||
[[nodiscard]] std::vector<std::vector<double>> get_cloud_point() const;
|
||||
[[nodiscard]] std::vector<double> get_intrinsic_params() const;
|
||||
[[nodiscard]] std::vector<double> get_extrinsic_params() const;
|
||||
[[nodiscard]] std::vector<std::vector<double>> get_cloud_point() const;
|
||||
|
||||
private:
|
||||
TestData data_;
|
||||
private:
|
||||
TestData data_;
|
||||
};
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
|
||||
80
include/cloud_point_rpc/tcp_connector.hpp
Normal file
80
include/cloud_point_rpc/tcp_connector.hpp
Normal file
@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
#include "cloud_point_rpc/serialize.hpp"
|
||||
#include "jsonrpccxx/iclientconnector.hpp"
|
||||
#include <asio.hpp>
|
||||
#include <glog/logging.h>
|
||||
#include <string>
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
/**
|
||||
* TCPConnector main purpose is to implement jsonrpccxx::IClientConnector Send
|
||||
* method As an internal implementation, TCPConnector adds to the beginning of
|
||||
* the json "expected size" of a packet in case when the packet was for some
|
||||
* reason on any level fractured.
|
||||
*/
|
||||
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) {
|
||||
throw std::runtime_error(std::string("Connection Failed: ") +
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::string Send(const std::string &request) override {
|
||||
// Send
|
||||
LOG(INFO) << "Client sending: " << request
|
||||
<< " Size: " << request.size();
|
||||
auto remove_const = request;
|
||||
inplace_size_embedding(remove_const);
|
||||
asio::write(socket_, asio::buffer(remove_const));
|
||||
return read();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string read() {
|
||||
std::string result;
|
||||
std::array<char, 8> header;
|
||||
LOG(INFO) << "trying to read";
|
||||
try {
|
||||
size_t len =
|
||||
asio::read(socket_, asio::buffer(header, header.size()));
|
||||
if (len != sizeof(uint64_t)) {
|
||||
LOG(ERROR) << "failed to read header";
|
||||
return result;
|
||||
}
|
||||
std::vector<uint8_t> v(header.begin(), header.begin() + 8);
|
||||
uint64_t packet_size = deserialize<uint64_t>(v);
|
||||
LOG(INFO) << "Received packet of size: " << packet_size;
|
||||
|
||||
std::vector<char> payload(packet_size);
|
||||
len = asio::read(socket_, asio::buffer(payload));
|
||||
|
||||
// if failed to fetch in one pack, try to read until size
|
||||
while (len < packet_size) {
|
||||
len += asio::read(socket_, asio::buffer(payload.data() + len,
|
||||
payload.size() - len));
|
||||
}
|
||||
|
||||
LOG(INFO) << std::format("Was able to read len={}", len);
|
||||
result = std::string(payload.begin(), payload.end());
|
||||
LOG(INFO) << "Payload: \n" << result;
|
||||
} catch (const std::exception &e) {
|
||||
LOG(WARNING) << "Connector handling error: " << e.what();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
asio::io_context io_context_;
|
||||
asio::ip::tcp::socket socket_;
|
||||
};
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
@ -1,129 +1,147 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "cloud_point_rpc/serialize.hpp"
|
||||
#include <asio.hpp>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <glog/logging.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <asio.hpp>
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
|
||||
class TcpServer {
|
||||
public:
|
||||
using RequestProcessor = std::function<std::string(const std::string&)>;
|
||||
public:
|
||||
using RequestProcessor = std::function<std::string(const std::string &)>;
|
||||
|
||||
TcpServer(const std::string& ip, int port, RequestProcessor processor)
|
||||
: ip_(ip), port_(port), processor_(std::move(processor)),
|
||||
acceptor_(io_context_), running_(false) {}
|
||||
TcpServer(const std::string &ip, int port, RequestProcessor processor)
|
||||
: ip_(ip), port_(port), processor_(std::move(processor)),
|
||||
acceptor_(io_context_), running_(false) {}
|
||||
|
||||
~TcpServer() { stop(); }
|
||||
~TcpServer() { stop(); }
|
||||
|
||||
void start() {
|
||||
try {
|
||||
asio::ip::tcp::endpoint endpoint(asio::ip::make_address(ip_), port_);
|
||||
acceptor_.open(endpoint.protocol());
|
||||
acceptor_.set_option(asio::ip::tcp::acceptor::reuse_address(true));
|
||||
acceptor_.bind(endpoint);
|
||||
acceptor_.listen();
|
||||
void start() {
|
||||
try {
|
||||
asio::ip::tcp::endpoint endpoint(asio::ip::make_address(ip_),
|
||||
port_);
|
||||
acceptor_.open(endpoint.protocol());
|
||||
acceptor_.set_option(asio::ip::tcp::acceptor::reuse_address(true));
|
||||
acceptor_.bind(endpoint);
|
||||
acceptor_.listen();
|
||||
|
||||
running_ = true;
|
||||
LOG(INFO) << "Server listening on " << ip_ << ":" << port_;
|
||||
running_ = true;
|
||||
LOG(INFO) << "Server listening on " << ip_ << ":" << port_;
|
||||
|
||||
accept_thread_ = std::jthread([this]() {
|
||||
LOG(INFO) << "Accept thread started";
|
||||
while (running_) {
|
||||
try {
|
||||
auto socket = std::make_shared<asio::ip::tcp::socket>(io_context_);
|
||||
acceptor_.accept(*socket);
|
||||
|
||||
LOG(INFO) << "New connection from " << socket->remote_endpoint().address().to_string();
|
||||
accept_thread_ = std::jthread([this]() {
|
||||
LOG(INFO) << "Accept thread started";
|
||||
while (running_) {
|
||||
try {
|
||||
auto socket = std::make_shared<asio::ip::tcp::socket>(
|
||||
io_context_);
|
||||
acceptor_.accept(*socket);
|
||||
|
||||
std::jthread([this, socket]() {
|
||||
handle_client(socket);
|
||||
}).detach();
|
||||
} catch (const std::system_error& e) {
|
||||
LOG(INFO) << "Accept exception: " << e.what();
|
||||
if (running_) {
|
||||
LOG(WARNING) << "Accept failed: " << e.what();
|
||||
}
|
||||
}
|
||||
LOG(INFO)
|
||||
<< "New connection from "
|
||||
<< socket->remote_endpoint().address().to_string();
|
||||
|
||||
std::jthread([this, socket]() {
|
||||
handle_client(socket);
|
||||
}).detach();
|
||||
} catch (const std::system_error &e) {
|
||||
LOG(INFO) << "Accept exception: " << e.what();
|
||||
if (running_) {
|
||||
LOG(WARNING) << "Accept failed: " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG(INFO) << "Accept thread exiting";
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
LOG(ERROR) << "Server start failed: " << e.what();
|
||||
throw;
|
||||
}
|
||||
LOG(INFO) << "Accept thread exiting";
|
||||
});
|
||||
} catch (const std::exception& e) {
|
||||
LOG(ERROR) << "Server start failed: " << e.what();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!running_) return;
|
||||
LOG(INFO) << "Stopping server...";
|
||||
running_ = false;
|
||||
// Closing acceptor unblocks accept() call usually, but sometimes we need to prod it
|
||||
asio::error_code ec;
|
||||
acceptor_.close(ec);
|
||||
|
||||
// Ensure accept unblocks by connecting a dummy socket
|
||||
try {
|
||||
asio::ip::tcp::endpoint endpoint(asio::ip::make_address(ip_), port_);
|
||||
asio::ip::tcp::socket dummy_sock(io_context_);
|
||||
asio::error_code connect_ec;
|
||||
dummy_sock.connect(endpoint, connect_ec);
|
||||
} catch (...) {
|
||||
// Ignore
|
||||
void stop() {
|
||||
if (!running_)
|
||||
return;
|
||||
LOG(INFO) << "Stopping server...";
|
||||
running_ = false;
|
||||
// Closing acceptor unblocks accept() call usually, but sometimes we
|
||||
// need to prod it
|
||||
asio::error_code ec;
|
||||
std::ignore = acceptor_.close(ec);
|
||||
if (ec.value()) {
|
||||
LOG(ERROR) << std::format(
|
||||
"acceptor closed with a value returned = {}", ec.value());
|
||||
}
|
||||
// Ensure accept unblocks by connecting a dummy socket
|
||||
try {
|
||||
asio::ip::tcp::endpoint endpoint(asio::ip::make_address(ip_),
|
||||
port_);
|
||||
asio::ip::tcp::socket dummy_sock(io_context_);
|
||||
asio::error_code connect_ec;
|
||||
std::ignore = dummy_sock.connect(endpoint, connect_ec);
|
||||
} catch (...) {
|
||||
// Ignore
|
||||
}
|
||||
LOG(INFO) << "Acceptor closed";
|
||||
}
|
||||
LOG(INFO) << "Acceptor closed";
|
||||
}
|
||||
|
||||
void join() {
|
||||
if (accept_thread_.joinable()) {
|
||||
LOG(INFO) << "Joining accept thread...";
|
||||
accept_thread_.join();
|
||||
LOG(INFO) << "Accept thread joined";
|
||||
void join() {
|
||||
if (accept_thread_.joinable()) {
|
||||
LOG(INFO) << "Joining accept thread...";
|
||||
accept_thread_.join();
|
||||
LOG(INFO) << "Accept thread joined";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void handle_client(std::shared_ptr<asio::ip::tcp::socket> socket) {
|
||||
try {
|
||||
asio::streambuf buffer;
|
||||
// Read until newline or EOF/error
|
||||
// Note: This matches the client implementation which should send a newline
|
||||
// However, previous implementation read 4096 bytes raw.
|
||||
// Let's emulate "read some" to match previous simple behavior, or use read_until if we enforce framing.
|
||||
// Given this is a prototype, let's read once.
|
||||
|
||||
LOG(INFO) << "Server reading from client...";
|
||||
char data[4096];
|
||||
size_t length = socket->read_some(asio::buffer(data, 4096)); // Error will throw
|
||||
LOG(INFO) << "Server read " << length << " bytes";
|
||||
|
||||
if (length > 0) {
|
||||
std::string request(data, length);
|
||||
LOG(INFO) << "Server processing request: " << request;
|
||||
std::string response = processor_(request);
|
||||
response += "\n";
|
||||
LOG(INFO) << "Server sending response: " << response;
|
||||
asio::write(*socket, asio::buffer(response));
|
||||
LOG(INFO) << "Server sent response";
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG(WARNING) << "Client handling error: " << e.what();
|
||||
private:
|
||||
void handle_client(std::shared_ptr<asio::ip::tcp::socket> socket) {
|
||||
try {
|
||||
LOG(INFO) << "Server reading from client...";
|
||||
|
||||
std::array<char, 8> header;
|
||||
size_t header_length = asio::read(*socket, asio::buffer(header));
|
||||
if (header_length != 8) {
|
||||
LOG(WARNING) << "Invalid header length: " << header_length;
|
||||
return;
|
||||
}
|
||||
uint64_t packet_size = *reinterpret_cast<uint64_t *>(header.data());
|
||||
LOG(INFO) << "Expected packet size: " << packet_size;
|
||||
|
||||
std::vector<char> payload(packet_size);
|
||||
size_t payload_length = asio::read(*socket, asio::buffer(payload));
|
||||
LOG(INFO) << "Read payload length: " << payload_length;
|
||||
if (payload_length != packet_size) {
|
||||
LOG(WARNING) << "Payload length mismatch: expected "
|
||||
<< packet_size << ", got " << payload_length;
|
||||
return;
|
||||
}
|
||||
if (payload_length > 0) {
|
||||
std::string request(payload.data(), payload_length);
|
||||
LOG(INFO) << "Server processing request: " << request;
|
||||
std::string response = processor_(request);
|
||||
response += "\n";
|
||||
LOG(INFO) << "Server sending response: " << response;
|
||||
inplace_size_embedding(response);
|
||||
asio::write(*socket, asio::buffer(response));
|
||||
LOG(INFO) << "Server sent response";
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
LOG(WARNING) << "Client handling error: " << e.what();
|
||||
}
|
||||
}
|
||||
// Socket closes when shared_ptr dies
|
||||
}
|
||||
|
||||
std::string ip_;
|
||||
int port_;
|
||||
RequestProcessor processor_;
|
||||
|
||||
asio::io_context io_context_;
|
||||
asio::ip::tcp::acceptor acceptor_;
|
||||
|
||||
std::atomic<bool> running_;
|
||||
std::jthread accept_thread_;
|
||||
std::string ip_;
|
||||
int port_;
|
||||
RequestProcessor processor_;
|
||||
|
||||
asio::io_context io_context_;
|
||||
asio::ip::tcp::acceptor acceptor_;
|
||||
|
||||
std::atomic<bool> running_;
|
||||
std::jthread accept_thread_;
|
||||
};
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
|
||||
55
src/cli.cpp
55
src/cli.cpp
@ -1,11 +1,11 @@
|
||||
#include "cloud_point_rpc/cli.hpp"
|
||||
#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) {
|
||||
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;
|
||||
@ -14,19 +14,39 @@ void print_menu(std::ostream& output) {
|
||||
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 {
|
||||
RpcClient client;
|
||||
client.connect(ip, port);
|
||||
|
||||
TCPConnector connector(ip, port);
|
||||
RpcClient client(connector);
|
||||
|
||||
output << "Connected to " << ip << ":" << port << std::endl;
|
||||
|
||||
std::string choice;
|
||||
while (true) {
|
||||
print_menu(output);
|
||||
if (!(input >> choice)) break;
|
||||
if (!(input >> choice))
|
||||
break;
|
||||
|
||||
if (choice == "0") break;
|
||||
if (choice == "0")
|
||||
break;
|
||||
|
||||
std::string method;
|
||||
if (choice == "1") {
|
||||
@ -41,13 +61,24 @@ int run_cli(std::istream& input, std::ostream& output, const std::string& ip, in
|
||||
}
|
||||
|
||||
try {
|
||||
auto response = client.call(method);
|
||||
output << "\nResponse:\n" << response.dump(4) << std::endl;
|
||||
} catch (const std::exception& e) {
|
||||
|
||||
if (method == "get-intrinsic-params") {
|
||||
auto response = client.get_intrinsic_params();
|
||||
output << vector_to_string(response);
|
||||
}
|
||||
if (method == "get-extrinsic-params") {
|
||||
auto response = client.get_extrinsic_params();
|
||||
output << vector_to_string(response);
|
||||
}
|
||||
if (method == "get-cloud-point") {
|
||||
auto response = client.get_cloud_point();
|
||||
output << vector_to_string(response);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
output << "\nRPC Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
} catch (const std::exception &e) {
|
||||
LOG(ERROR) << "CLI Error: " << e.what();
|
||||
output << "Error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
|
||||
16
src/main.cpp
16
src/main.cpp
@ -1,12 +1,12 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include "cloud_point_rpc/cli.hpp"
|
||||
#include "cloud_point_rpc/config.hpp"
|
||||
#include <fstream>
|
||||
#include <glog/logging.h>
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int main(int argc, char *argv[]) {
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
FLAGS_logtostderr = 1;
|
||||
FLAGS_logtostderr = 1;
|
||||
|
||||
std::string config_path = "config.yaml";
|
||||
if (argc > 1) {
|
||||
@ -17,15 +17,17 @@ int main(int argc, char* argv[]) {
|
||||
std::ifstream f(config_path.c_str());
|
||||
if (!f.good()) {
|
||||
std::cerr << "Config file not found: " << config_path << std::endl;
|
||||
std::cerr << "Please create config.yaml or provide path as argument." << std::endl;
|
||||
std::cerr << "Please create config.yaml or provide path as argument."
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
f.close();
|
||||
|
||||
try {
|
||||
auto config = cloud_point_rpc::ConfigLoader::load(config_path);
|
||||
return cloud_point_rpc::run_cli(std::cin, std::cout, config.server.ip, config.server.port);
|
||||
} catch (const std::exception& e) {
|
||||
return cloud_point_rpc::run_cli(std::cin, std::cout, config.server.ip,
|
||||
config.server.port);
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "Failed to start CLI: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -1,61 +1,59 @@
|
||||
#include "cloud_point_rpc/rpc_server.hpp"
|
||||
#include <iostream>
|
||||
|
||||
#include <glog/logging.h>
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
|
||||
namespace {
|
||||
json create_error(int code, const std::string& message, const json& id = nullptr) {
|
||||
return {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"error", {{"code", code}, {"message", message}}},
|
||||
{"id", id}
|
||||
};
|
||||
json create_error(int code, const std::string &message,
|
||||
const json &id = nullptr) {
|
||||
return {{"jsonrpc", "2.0"},
|
||||
{"error", {{"code", code}, {"message", message}}},
|
||||
{"id", id}};
|
||||
}
|
||||
|
||||
json create_success(const json& result, const json& id) {
|
||||
return {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"result", result},
|
||||
{"id", id}
|
||||
};
|
||||
json create_success(const json &result, const json &id) {
|
||||
return {{"jsonrpc", "2.0"}, {"result", result}, {"id", id}};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void RpcServer::register_method(const std::string& name, Handler handler) {
|
||||
handlers_[name] = std::move(handler);
|
||||
void RpcServer::register_method(const std::string &name, Handler handler) {
|
||||
handlers_[name] = std::move(handler);
|
||||
}
|
||||
|
||||
std::string RpcServer::process(const std::string& request_str) {
|
||||
json request;
|
||||
try {
|
||||
request = json::parse(request_str);
|
||||
} catch (const json::parse_error&) {
|
||||
return create_error(-32700, "Parse error").dump();
|
||||
}
|
||||
std::string RpcServer::process(const std::string &request_str) {
|
||||
json request;
|
||||
try {
|
||||
request = json::parse(request_str);
|
||||
} catch (const json::parse_error &) {
|
||||
LOG(ERROR) << "json parse error" << __func__;
|
||||
return create_error(-32700, "Parse error").dump();
|
||||
}
|
||||
|
||||
// Batch requests are not supported in this minimal version, assume single object
|
||||
if (!request.contains("jsonrpc") || request["jsonrpc"] != "2.0" ||
|
||||
!request.contains("method") || !request.contains("id")) {
|
||||
return create_error(-32600, "Invalid Request", request.value("id", json(nullptr))).dump();
|
||||
}
|
||||
// Batch requests are not supported in this minimal version, assume single
|
||||
// object
|
||||
if (!request.contains("jsonrpc") || request["jsonrpc"] != "2.0" ||
|
||||
!request.contains("method") || !request.contains("id")) {
|
||||
return create_error(-32600, "Invalid Request",
|
||||
request.value("id", json(nullptr)))
|
||||
.dump();
|
||||
}
|
||||
|
||||
std::string method = request["method"];
|
||||
json id = request["id"];
|
||||
json params = request.value("params", json::object());
|
||||
std::string method = request["method"];
|
||||
json id = request["id"];
|
||||
json params = request.value("params", json::object());
|
||||
|
||||
auto it = handlers_.find(method);
|
||||
if (it == handlers_.end()) {
|
||||
return create_error(-32601, "Method not found", id).dump();
|
||||
}
|
||||
auto it = handlers_.find(method);
|
||||
if (it == handlers_.end()) {
|
||||
return create_error(-32601, "Method not found", id).dump();
|
||||
}
|
||||
|
||||
try {
|
||||
json result = it->second(params);
|
||||
return create_success(result, id).dump();
|
||||
} catch (const std::exception& e) {
|
||||
return create_error(-32000, e.what(), id).dump(); // Server error
|
||||
}
|
||||
try {
|
||||
json result = it->second(params);
|
||||
return create_success(result, id).dump();
|
||||
} catch (const std::exception &e) {
|
||||
return create_error(-32000, e.what(), id).dump(); // Server error
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
|
||||
@ -1,58 +1,57 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <glog/logging.h>
|
||||
#include "cloud_point_rpc/config.hpp"
|
||||
#include "cloud_point_rpc/rpc_server.hpp"
|
||||
#include "cloud_point_rpc/service.hpp"
|
||||
#include "cloud_point_rpc/config.hpp"
|
||||
#include "cloud_point_rpc/tcp_server.hpp"
|
||||
#include <glog/logging.h>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
google::InstallFailureSignalHandler();
|
||||
FLAGS_alsologtostderr = 1;
|
||||
int main(int argc, char *argv[]) {
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
google::InstallFailureSignalHandler();
|
||||
FLAGS_alsologtostderr = 1;
|
||||
|
||||
std::string config_path = "config.yaml";
|
||||
if (argc > 1) {
|
||||
config_path = argv[1];
|
||||
}
|
||||
std::string config_path = "config.yaml";
|
||||
if (argc > 1) {
|
||||
config_path = argv[1];
|
||||
}
|
||||
|
||||
LOG(INFO) << "Starting Cloud Point RPC Server (Test Mock)...";
|
||||
LOG(INFO) << "Starting Cloud Point RPC Server (Test Mock)...";
|
||||
|
||||
try {
|
||||
auto config = cloud_point_rpc::ConfigLoader::load(config_path);
|
||||
LOG(INFO) << "Loaded config from " << config_path;
|
||||
try {
|
||||
auto config = cloud_point_rpc::ConfigLoader::load(config_path);
|
||||
LOG(INFO) << "Loaded config from " << config_path;
|
||||
|
||||
// Inject test data into service
|
||||
cloud_point_rpc::Service service(config.test_data);
|
||||
cloud_point_rpc::RpcServer rpc_server;
|
||||
// Inject test data into service
|
||||
cloud_point_rpc::Service service(config.test_data);
|
||||
cloud_point_rpc::RpcServer rpc_server;
|
||||
|
||||
rpc_server.register_method("get-intrinsic-params", [&](const json&) {
|
||||
return service.get_intrinsic_params();
|
||||
});
|
||||
rpc_server.register_method("get-intrinsic-params", [&](const json &) {
|
||||
return service.get_intrinsic_params();
|
||||
});
|
||||
|
||||
rpc_server.register_method("get-extrinsic-params", [&](const json&) {
|
||||
return service.get_extrinsic_params();
|
||||
});
|
||||
rpc_server.register_method("get-extrinsic-params", [&](const json &) {
|
||||
return service.get_extrinsic_params();
|
||||
});
|
||||
|
||||
rpc_server.register_method("get-cloud-point", [&](const json&) {
|
||||
return service.get_cloud_point();
|
||||
});
|
||||
rpc_server.register_method("get-cloud-point", [&](const json &) {
|
||||
return service.get_cloud_point();
|
||||
});
|
||||
|
||||
cloud_point_rpc::TcpServer server(config.server.ip, config.server.port,
|
||||
[&](const std::string& request) {
|
||||
return rpc_server.process(request);
|
||||
}
|
||||
);
|
||||
cloud_point_rpc::TcpServer server(config.server.ip, config.server.port,
|
||||
[&](const std::string &request) {
|
||||
return rpc_server.process(
|
||||
request);
|
||||
});
|
||||
|
||||
server.start();
|
||||
server.join();
|
||||
server.start();
|
||||
server.join();
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
LOG(ERROR) << "Fatal error: " << e.what();
|
||||
return 1;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
LOG(ERROR) << "Fatal error: " << e.what();
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2,37 +2,29 @@
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
|
||||
Service::Service(const TestData& data) : data_(data) {}
|
||||
Service::Service(const TestData &data) : data_(data) {}
|
||||
|
||||
std::vector<double> Service::get_intrinsic_params() const {
|
||||
if (data_.intrinsic_params.empty()) {
|
||||
// Fallback if no config loaded
|
||||
return {1.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 1.0};
|
||||
}
|
||||
return data_.intrinsic_params;
|
||||
if (data_.intrinsic_params.empty()) {
|
||||
// Fallback if no config loaded
|
||||
return {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
|
||||
}
|
||||
return data_.intrinsic_params;
|
||||
}
|
||||
|
||||
std::vector<double> Service::get_extrinsic_params() const {
|
||||
if (data_.extrinsic_params.empty()) {
|
||||
return {1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0};
|
||||
}
|
||||
return data_.extrinsic_params;
|
||||
if (data_.extrinsic_params.empty()) {
|
||||
return {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0};
|
||||
}
|
||||
return data_.extrinsic_params;
|
||||
}
|
||||
|
||||
std::vector<std::vector<double>> Service::get_cloud_point() const {
|
||||
if (data_.cloud_point.empty()) {
|
||||
return {
|
||||
{0.1, 0.2, 0.3},
|
||||
{1.1, 1.2, 1.3},
|
||||
{2.1, 2.2, 2.3}
|
||||
};
|
||||
}
|
||||
return data_.cloud_point;
|
||||
if (data_.cloud_point.empty()) {
|
||||
return {{0.1, 0.2, 0.3}, {1.1, 1.2, 1.3}, {2.1, 2.2, 2.3}};
|
||||
}
|
||||
return data_.cloud_point;
|
||||
}
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
test_sources = files(
|
||||
'test_rpc.cpp',
|
||||
'test_integration.cpp',
|
||||
'test_cli.cpp'
|
||||
'test_cli.cpp',
|
||||
'test_tcp.cpp'
|
||||
)
|
||||
|
||||
test_exe = executable('unit_tests',
|
||||
|
||||
@ -1,39 +1,36 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <iostream>
|
||||
#include <future>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include "cloud_point_rpc/tcp_server.hpp"
|
||||
#include "cloud_point_rpc/rpc_server.hpp"
|
||||
#include "cloud_point_rpc/cli.hpp"
|
||||
#include "cloud_point_rpc/rpc_server.hpp"
|
||||
#include "cloud_point_rpc/tcp_server.hpp"
|
||||
|
||||
using namespace cloud_point_rpc;
|
||||
|
||||
class CliTest : public ::testing::Test {
|
||||
protected:
|
||||
protected:
|
||||
void SetUp() override {
|
||||
server_ip = "127.0.0.1";
|
||||
server_ip = "127.0.0.1";
|
||||
server_port = 9096;
|
||||
|
||||
rpc_server = std::make_unique<RpcServer>();
|
||||
rpc_server->register_method("hello", [](const nlohmann::json& params) {
|
||||
return "world";
|
||||
});
|
||||
rpc_server = std::make_unique<RpcServer>();
|
||||
rpc_server->register_method(
|
||||
"hello", [](const nlohmann::json &) { return "world"; });
|
||||
|
||||
tcp_server = std::make_unique<TcpServer>(server_ip, server_port, [this](const std::string& req) {
|
||||
return rpc_server->process(req);
|
||||
});
|
||||
tcp_server = std::make_unique<TcpServer>(
|
||||
server_ip, server_port, [this](const std::string &req) {
|
||||
return rpc_server->process(req);
|
||||
});
|
||||
|
||||
tcp_server->start();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
tcp_server->stop();
|
||||
}
|
||||
void TearDown() override { tcp_server->stop(); }
|
||||
|
||||
std::string server_ip;
|
||||
int server_port;
|
||||
@ -48,9 +45,10 @@ TEST_F(CliTest, SendsInputToServerAndReceivesResponse) {
|
||||
// Select option 1 (get-intrinsic-params) then 0 (exit)
|
||||
// First we need to make sure the rpc_server has the method registered.
|
||||
// Our SetUp registers "hello", let's register "get-intrinsic-params" too.
|
||||
rpc_server->register_method("get-intrinsic-params", [](const nlohmann::json&) {
|
||||
return std::vector<double>{1.0, 2.0, 3.0};
|
||||
});
|
||||
rpc_server->register_method(
|
||||
"get-intrinsic-params", [](const nlohmann::json &) {
|
||||
return std::vector<double>{14589.0, 22489.0, 3123124.555};
|
||||
});
|
||||
|
||||
input << "1" << std::endl;
|
||||
input << "0" << std::endl;
|
||||
@ -60,8 +58,7 @@ TEST_F(CliTest, SendsInputToServerAndReceivesResponse) {
|
||||
EXPECT_EQ(result, 0);
|
||||
std::string response = output.str();
|
||||
// Use more flexible check because of pretty printing
|
||||
EXPECT_THAT(response, ::testing::HasSubstr("\"result\": ["));
|
||||
EXPECT_THAT(response, ::testing::HasSubstr("1.0"));
|
||||
EXPECT_THAT(response, ::testing::HasSubstr("2.0"));
|
||||
EXPECT_THAT(response, ::testing::HasSubstr("3.0"));
|
||||
EXPECT_THAT(response, ::testing::HasSubstr("14589.0"));
|
||||
EXPECT_THAT(response, ::testing::HasSubstr("22489.0"));
|
||||
EXPECT_THAT(response, ::testing::HasSubstr("3123124.555"));
|
||||
}
|
||||
|
||||
@ -1,100 +1,97 @@
|
||||
#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:
|
||||
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.close();
|
||||
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.close();
|
||||
|
||||
// Setup Mock Server
|
||||
try {
|
||||
config_ = ConfigLoader::load("config.yaml");
|
||||
} catch (...) {
|
||||
// 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();
|
||||
});
|
||||
|
||||
// Start Server Thread
|
||||
tcp_server_ = std::make_unique<TcpServer>(config_.server.ip, config_.server.port,
|
||||
[&](const std::string& req) {
|
||||
return rpc_server_->process(req);
|
||||
// Setup Mock Server
|
||||
try {
|
||||
config_ = ConfigLoader::load("config.yaml");
|
||||
} catch (...) {
|
||||
// If config fails, we can't proceed, but we should avoid crashing
|
||||
// in TearDown
|
||||
throw;
|
||||
}
|
||||
);
|
||||
|
||||
server_thread_ = std::thread([&]() {
|
||||
tcp_server_->start();
|
||||
});
|
||||
|
||||
// Give server time to start
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
}
|
||||
service_ = std::make_unique<Service>(config_.test_data);
|
||||
rpc_server_ = std::make_unique<RpcServer>();
|
||||
|
||||
void TearDown() override {
|
||||
if (tcp_server_) {
|
||||
tcp_server_->stop();
|
||||
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(); });
|
||||
|
||||
// Give server time to start
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
}
|
||||
if (server_thread_.joinable()) {
|
||||
server_thread_.join();
|
||||
}
|
||||
// Clean up
|
||||
std::remove("config.yaml");
|
||||
}
|
||||
|
||||
Config config_;
|
||||
std::unique_ptr<Service> service_;
|
||||
std::unique_ptr<RpcServer> rpc_server_;
|
||||
std::unique_ptr<TcpServer> tcp_server_;
|
||||
std::thread server_thread_;
|
||||
void TearDown() override {
|
||||
if (tcp_server_) {
|
||||
tcp_server_->stop();
|
||||
}
|
||||
if (server_thread_.joinable()) {
|
||||
server_thread_.join();
|
||||
}
|
||||
// Clean up
|
||||
std::remove("config.yaml");
|
||||
}
|
||||
|
||||
Config config_;
|
||||
std::unique_ptr<Service> service_;
|
||||
std::unique_ptr<RpcServer> rpc_server_;
|
||||
std::unique_ptr<TcpServer> tcp_server_;
|
||||
std::thread server_thread_;
|
||||
};
|
||||
|
||||
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();
|
||||
// Act: Call Method
|
||||
std::vector<double> params;
|
||||
EXPECT_NO_THROW(params = client.get_intrinsic_params());
|
||||
|
||||
// Assert: Values match config
|
||||
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]);
|
||||
}
|
||||
// Assert: Values match config
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -1,61 +1,63 @@
|
||||
#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:
|
||||
RpcServer server;
|
||||
Service service;
|
||||
protected:
|
||||
RpcServer server;
|
||||
Service service;
|
||||
|
||||
void SetUp() override {
|
||||
server.register_method("get-intrinsic-params", [&](const json&) {
|
||||
return service.get_intrinsic_params();
|
||||
});
|
||||
}
|
||||
void SetUp() override {
|
||||
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 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
|
||||
EXPECT_EQ(result[0], 1.0);
|
||||
EXPECT_EQ(result[4], 1.0);
|
||||
EXPECT_EQ(result[8], 1.0);
|
||||
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
|
||||
EXPECT_EQ(result[0], 1.0);
|
||||
EXPECT_EQ(result[4], 1.0);
|
||||
EXPECT_EQ(result[8], 1.0);
|
||||
}
|
||||
|
||||
TEST_F(RpcServerTest, MethodNotFoundReturnsError) {
|
||||
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");
|
||||
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");
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
58
tests/test_tcp.cpp
Normal file
58
tests/test_tcp.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include "cloud_point_rpc/serialize.hpp"
|
||||
#include "cloud_point_rpc/tcp_connector.hpp"
|
||||
#include "cloud_point_rpc/tcp_server.hpp"
|
||||
#include <asio.hpp>
|
||||
#include <cstdint>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
|
||||
class TcpTest : public ::testing::Test {
|
||||
public:
|
||||
void ExpectedResponse(const std::string &expected_response) {
|
||||
expected_ = expected_response;
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetUp() override {
|
||||
server_ = std::make_unique<cloud_point_rpc::TcpServer>(
|
||||
"127.0.0.1", 12345, [this](const std::string &request) {
|
||||
EXPECT_EQ(request, expected_);
|
||||
std::string msg = "Echo: " + request;
|
||||
auto v = cloud_point_rpc::serialize(msg.length());
|
||||
std::string res(v.begin(), v.end());
|
||||
res += msg;
|
||||
return res;
|
||||
});
|
||||
server_->start();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
server_->stop();
|
||||
server_.reset();
|
||||
}
|
||||
|
||||
std::string expected_;
|
||||
std::unique_ptr<cloud_point_rpc::TcpServer> server_;
|
||||
};
|
||||
|
||||
TEST(SerializeTest, Base) {
|
||||
uint64_t value{123};
|
||||
auto res = cloud_point_rpc::serialize(value);
|
||||
EXPECT_EQ(value, cloud_point_rpc::deserialize<uint64_t>(res));
|
||||
}
|
||||
|
||||
TEST_F(TcpTest, EchoTest) {
|
||||
constexpr std::string_view msg = "Hello, TCP Server!";
|
||||
ExpectedResponse(msg.data());
|
||||
cloud_point_rpc::TCPConnector connector("127.0.0.1", 12345);
|
||||
auto res = connector.Send(msg.data());
|
||||
}
|
||||
|
||||
TEST_F(TcpTest, HugeBuffer) {
|
||||
static constexpr uint64_t w = 1920, h = 1080, c = 3;
|
||||
std::string data(w * h * c, 77);
|
||||
ExpectedResponse(data);
|
||||
cloud_point_rpc::TCPConnector connector("127.0.0.1", 12345);
|
||||
auto res = connector.Send(data);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user