Compare commits
12 Commits
feature/js
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 603646c230 | |||
| e0295f21b5 | |||
| 46cf56196e | |||
| 69466a1771 | |||
| 7a2d6d0994 | |||
| f94a23b723 | |||
| e0ac93c657 | |||
|
|
b28c1b8e7f | ||
|
|
1ab01134c5 | ||
|
|
d26ab35339 | ||
| 8ee06fe6a0 | |||
| ece26e7b1f |
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
|
||||
34
.gitea/workflows/test.yaml
Normal file
34
.gitea/workflows/test.yaml
Normal file
@ -0,0 +1,34 @@
|
||||
name: Verification
|
||||
run-name: ${{ gitea.actor }} runs verification of the project
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
|
||||
jobs:
|
||||
Is-Buildable:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Ensure That Build system available
|
||||
run: |
|
||||
apt update
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y cmake make ninja-build gcc
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
python3 -m venv $HOME/py/
|
||||
source $HOME/py/bin/activate
|
||||
echo PATH=$PATH >> $GITEA_ENV
|
||||
pip install meson
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
- name: Build project
|
||||
run: |
|
||||
cd ${{ gitea.workspace }}
|
||||
meson setup build
|
||||
meson compile -C build
|
||||
- name: Unit Test Results
|
||||
run: |
|
||||
meson test -C build
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -5,4 +5,6 @@ subprojects/glog/
|
||||
subprojects/googletest-*
|
||||
subprojects/nlohmann_json/
|
||||
subprojects/packagecache/
|
||||
subprojects/yaml-cpp-0.8.0
|
||||
subprojects/yaml-cpp-0.8.0
|
||||
subprojects/base64-0.5.2/
|
||||
.venv/
|
||||
|
||||
45
README.md
45
README.md
@ -2,6 +2,11 @@
|
||||
|
||||
Communication JSON RPC protocol and implementation with Unity Scene.
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] Server implementation with C-API for Unity
|
||||
- [ ] Client correct implementation with OpenCV
|
||||
|
||||
## API Documentation
|
||||
|
||||
See [API.md](API.md) for detailed request/response formats.
|
||||
@ -11,38 +16,70 @@ See [API.md](API.md) for detailed request/response formats.
|
||||
The project uses **Meson** build system and **C++20**.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Meson, Ninja
|
||||
- GCC/Clang (C++20 support)
|
||||
- Git (for subprojects)
|
||||
|
||||
### Build & Run
|
||||
|
||||
```bash
|
||||
git submodule init
|
||||
git submodule update
|
||||
meson setup build
|
||||
meson compile -C build
|
||||
./build/src/cloud_point_rpc_server config.yaml
|
||||
```
|
||||
|
||||
#### Build on windows
|
||||
|
||||
It's assumed that you have `GCC` and `make`/`ninja` installed on your system (and available in `PATH`)
|
||||
|
||||
```powershell
|
||||
## FIRST OF ALL!
|
||||
git submodule init
|
||||
git submodule update
|
||||
# Next python:
|
||||
python3 -m venv .\venv
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
pip install meson cmake
|
||||
meson setup -Ddefault_library=static build
|
||||
meson compile -C build
|
||||
# To correctly get dlls:
|
||||
meson devenv -C build
|
||||
## .\build\tests\unit_tests < for dummy test
|
||||
## .\build\src\.. < produced execs and libs
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
meson test -C build -v
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
You can build and run the cli using Docker.
|
||||
You can build and run the cli using `Docker`.
|
||||
|
||||
### 1. Build Image
|
||||
|
||||
```bash
|
||||
docker build -t cloud-point-rpc .
|
||||
```
|
||||
|
||||
### 2. Run Container
|
||||
The cli will try to connect to a **running server** on ip and port defined in config.yml file. (defined in `config.yaml` inside the image).
|
||||
For simplicity, it's better to use a host network, so you will not have any headache with accessability.
|
||||
|
||||
> *Server is not configured to run through container, if you need, contact me*
|
||||
The cli will try to connect to a **running server** on ip and port defined in config.yml file. (defined in `config.yaml` inside the image).
|
||||
For simplicity, it's better to use a host network, so you will not have any headache with accessability.
|
||||
|
||||
> _Server is not configured to run through container, if you need, contact me_
|
||||
|
||||
You also can mount your own `config.yaml` to override the default settings:
|
||||
|
||||
```bash
|
||||
docker run --network=host -it -v $(pwd)/my_config.yaml:/app/config.yaml cloud-point-rpc
|
||||
```
|
||||
|
||||
## Communication model
|
||||
|
||||

|
||||
|
||||
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]
|
||||
BIN
docs/cm.png
Normal file
BIN
docs/cm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
66
docs/communication_model.pu
Normal file
66
docs/communication_model.pu
Normal file
@ -0,0 +1,66 @@
|
||||
@startuml comm
|
||||
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
|
||||
@ -2,18 +2,19 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
#include "export.h"
|
||||
namespace score {
|
||||
|
||||
/**
|
||||
* @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 CRPC_EXPORT 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 {
|
||||
namespace score {
|
||||
|
||||
struct ServerConfig {
|
||||
std::string ip;
|
||||
int port;
|
||||
std::string ip;
|
||||
int port{0};
|
||||
};
|
||||
|
||||
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,39 @@
|
||||
#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 score {
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
class RpcClient : public jsonrpccxx::JsonRpcClient {
|
||||
public:
|
||||
RpcClient(TCPConnector &connector)
|
||||
: jsonrpccxx::JsonRpcClient(connector, jsonrpccxx::version::v2) {}
|
||||
|
||||
class RpcClient {
|
||||
public:
|
||||
RpcClient() : socket_(io_context_) {}
|
||||
|
||||
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
|
||||
|
||||
27
include/cloud_point_rpc/rpc_coder.h
Normal file
27
include/cloud_point_rpc/rpc_coder.h
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// Created by vptyp on 11.03.2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace score {
|
||||
|
||||
class IRPCCoder {
|
||||
public:
|
||||
virtual ~IRPCCoder() = default;
|
||||
virtual std::vector<char> decode(const std::string& encoded) = 0;
|
||||
virtual std::string encode(const std::vector<char>& data) = 0;
|
||||
};
|
||||
|
||||
class Base64RPCCoder final : public IRPCCoder {
|
||||
public:
|
||||
Base64RPCCoder();
|
||||
~Base64RPCCoder() override;
|
||||
|
||||
std::vector<char> decode(const std::string& encoded) override;
|
||||
std::string encode(const std::vector<char>& data) override;
|
||||
};
|
||||
|
||||
}
|
||||
@ -5,17 +5,30 @@
|
||||
#include <map>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include "export.h"
|
||||
extern "C" {
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
struct rpc_string {
|
||||
rpc_string(const char* data, uint64_t size) : s(data,size) {}
|
||||
rpc_string() = default;
|
||||
|
||||
class RpcServer {
|
||||
public:
|
||||
using Handler = std::function<nlohmann::json(const nlohmann::json&)>;
|
||||
std::string s;
|
||||
};
|
||||
}
|
||||
|
||||
void register_method(const std::string& name, Handler handler);
|
||||
[[nodiscard]] std::string process(const std::string& request_str);
|
||||
namespace score {
|
||||
|
||||
private:
|
||||
class CRPC_EXPORT RpcServer {
|
||||
public:
|
||||
using Handler = std::function<nlohmann::json(const nlohmann::json &)>;
|
||||
using callback_t = rpc_string*(*)(rpc_string*);
|
||||
|
||||
void register_method(const std::string &name, Handler handler);
|
||||
void register_method(const std::string &name, callback_t handler);
|
||||
///@param request_str json rpc 2.0 formatted string
|
||||
[[nodiscard]] std::string process(const std::string &request_str);
|
||||
|
||||
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 score {
|
||||
|
||||
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>
|
||||
#include "export.h"
|
||||
namespace score {
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
class CRPC_EXPORT Service {
|
||||
public:
|
||||
explicit Service(const TestData &data = {});
|
||||
|
||||
class Service {
|
||||
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
|
||||
|
||||
46
include/cloud_point_rpc/tcp_connector.hpp
Normal file
46
include/cloud_point_rpc/tcp_connector.hpp
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include "cloud_point_rpc/serialize.hpp"
|
||||
#include "jsonrpccxx/iclientconnector.hpp"
|
||||
#include <asio.hpp>
|
||||
#include <cloud_point_rpc/tcp_read.hpp>
|
||||
#include <glog/logging.h>
|
||||
#include <string>
|
||||
#include "export.h"
|
||||
namespace score {
|
||||
/**
|
||||
* 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 CRPC_EXPORT 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 tcp_read(socket_, "TCPConnector] ");
|
||||
}
|
||||
|
||||
private:
|
||||
asio::io_context io_context_;
|
||||
asio::ip::tcp::socket socket_;
|
||||
};
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
43
include/cloud_point_rpc/tcp_read.hpp
Normal file
43
include/cloud_point_rpc/tcp_read.hpp
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <asio.hpp>
|
||||
#include <cloud_point_rpc/serialize.hpp>
|
||||
#include <glog/logging.h>
|
||||
namespace score {
|
||||
|
||||
static inline std::string tcp_read(asio::ip::tcp::socket &socket,
|
||||
std::string_view prefix) {
|
||||
std::string result;
|
||||
std::array<char, 8> header;
|
||||
LOG(INFO) << prefix.data() << "trying to read";
|
||||
try {
|
||||
size_t len = asio::read(socket, asio::buffer(header, header.size()));
|
||||
if (len != sizeof(uint64_t)) {
|
||||
LOG(ERROR) << prefix.data() << "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) << prefix.data()
|
||||
<< "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) << prefix.data()
|
||||
<< std::format("Was able to read len={}", len);
|
||||
result = std::string(payload.begin(), payload.end());
|
||||
DLOG(INFO) << prefix.data() << "Payload: \n" << result;
|
||||
} catch (const std::exception &e) {
|
||||
LOG(WARNING) << prefix.data() << "handling error: " << e.what();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
@ -1,129 +1,147 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <asio.hpp>
|
||||
#include <atomic>
|
||||
#include <cloud_point_rpc/tcp_read.hpp>
|
||||
#include <functional>
|
||||
#include <glog/logging.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <asio.hpp>
|
||||
#include "export.h"
|
||||
#include <list>
|
||||
#include <ranges>
|
||||
namespace score {
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
class CRPC_EXPORT TcpServer {
|
||||
public:
|
||||
using RequestProcessor = std::function<std::string(const std::string &)>;
|
||||
|
||||
class TcpServer {
|
||||
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(); }
|
||||
|
||||
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_;
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
~TcpServer() {
|
||||
stop();
|
||||
for (auto &thread : client_threads | std::views::keys) {
|
||||
thread.join();
|
||||
}
|
||||
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 start() {
|
||||
using namespace std::chrono_literals;
|
||||
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_;
|
||||
|
||||
accept_thread_ = std::jthread([this]() {
|
||||
LOG(INFO) << "Accept thread started";
|
||||
while (running_) {
|
||||
std::ranges::remove_if(client_threads.begin(), client_threads.end(), [](auto& client_info) {
|
||||
bool result = false;
|
||||
if (client_info.second.wait_for(0ms) == std::future_status::ready) {
|
||||
client_info.first.join();
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
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();
|
||||
auto done = std::make_shared<std::promise<bool>>();
|
||||
client_threads.push_back(std::make_pair(std::jthread([this, socket, done]() {
|
||||
handle_client(socket);
|
||||
done->set_value(true);
|
||||
}),done->get_future()));
|
||||
} 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) << "Acceptor closed";
|
||||
}
|
||||
|
||||
void join() {
|
||||
if (accept_thread_.joinable()) {
|
||||
LOG(INFO) << "Joining accept thread...";
|
||||
accept_thread_.join();
|
||||
LOG(INFO) << "Accept thread joined";
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
void join() {
|
||||
if (accept_thread_.joinable()) {
|
||||
LOG(INFO) << "Joining accept thread...";
|
||||
accept_thread_.join();
|
||||
LOG(INFO) << "Accept thread joined";
|
||||
}
|
||||
}
|
||||
// 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_;
|
||||
private:
|
||||
void handle_client(std::shared_ptr<asio::ip::tcp::socket> socket) {
|
||||
LOG(INFO) << "Server reading from client...";
|
||||
try {
|
||||
auto payload = tcp_read(*socket, "TCPServer] ");
|
||||
size_t payload_length = payload.size();
|
||||
if (payload_length > 0) {
|
||||
std::string response = processor_(payload);
|
||||
response += "\n";
|
||||
DLOG(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();
|
||||
}
|
||||
}
|
||||
|
||||
std::string ip_;
|
||||
int port_;
|
||||
RequestProcessor processor_;
|
||||
|
||||
asio::io_context io_context_;
|
||||
asio::ip::tcp::acceptor acceptor_;
|
||||
|
||||
std::atomic<bool> running_;
|
||||
std::list<std::pair<std::jthread, std::future<bool>>> client_threads;
|
||||
std::jthread accept_thread_;
|
||||
};
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
|
||||
14
include/export.h
Normal file
14
include/export.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#if defined(_WIN32)
|
||||
#ifdef CRPC_SERVER_API_EXPORT
|
||||
#define CRPC_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define CRPC_EXPORT __declspec(dllimport)
|
||||
#endif
|
||||
#elif defined(__GNUC__) // GCC, Clang, etc.
|
||||
// Linux, macOS, etc.
|
||||
#define CRPC_EXPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
#define CRPC_EXPORT
|
||||
#pragma warning Unknown dynamic link import/export semantics.
|
||||
#endif
|
||||
38
include/server_api.h
Normal file
38
include/server_api.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef CRPC_SERVER_API
|
||||
#define CRPC_SERVER_API
|
||||
|
||||
#include <cstdint>
|
||||
#include "export.h"
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif //cpp
|
||||
/**
|
||||
* @brief basically just std::string wrapper:
|
||||
* struct rpc_string {
|
||||
* std::string s;
|
||||
* };
|
||||
* has internal gc and would be automatically deallocated on deinit call,
|
||||
* but it is better to call destroy manually, to prevent exceeding memory usage
|
||||
*/
|
||||
struct CRPC_EXPORT rpc_string;
|
||||
|
||||
CRPC_EXPORT const char* crpc_str_get_data(const rpc_string*);
|
||||
CRPC_EXPORT uint64_t crpc_str_get_size(const rpc_string*);
|
||||
|
||||
CRPC_EXPORT rpc_string* crpc_str_create(const char* data, uint64_t size);
|
||||
CRPC_EXPORT void crpc_str_destroy(rpc_string*);
|
||||
|
||||
typedef rpc_string*(*callback_t)(rpc_string*);
|
||||
|
||||
CRPC_EXPORT void crpc_init(const char* config_path);
|
||||
CRPC_EXPORT void crpc_deinit();
|
||||
|
||||
CRPC_EXPORT void crpc_add_method(callback_t cb, rpc_string* name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif //cpp
|
||||
|
||||
#endif //CRPC_SERVER_API
|
||||
50
include/test_api.h
Normal file
50
include/test_api.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef CRPC_TEST_API
|
||||
#define CRPC_TEST_API
|
||||
|
||||
#include <cstdint>
|
||||
#include "export.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif //cpp
|
||||
|
||||
struct CRPC_EXPORT rpc_string;
|
||||
|
||||
/// @brief callback function intendent to handle json and retrieve string data
|
||||
typedef rpc_string*(*callback_t)(rpc_string*);
|
||||
|
||||
/// @brief call it to initialize library thread;
|
||||
/// by default would call available methods one by one
|
||||
CRPC_EXPORT void crpc_test_init();
|
||||
|
||||
/// @brief please, call it, when you're done working with the lib
|
||||
/// @note blocking operation
|
||||
CRPC_EXPORT void crpc_test_deinit();
|
||||
|
||||
/// @brief crpc_test_add_method will add to the available set/map
|
||||
/// @note blocking operation
|
||||
CRPC_EXPORT void crpc_test_add_method(callback_t cb, rpc_string* name);
|
||||
|
||||
/// @brief crpc_test_remove_method would
|
||||
/// @param name pointer to name string; memory management not transferred
|
||||
CRPC_EXPORT int crpc_test_remove_method(rpc_string* name);
|
||||
|
||||
/// @brief crpc_schedule_call would add to the execution queue
|
||||
/// may be called one after current
|
||||
/// @note blocking operation
|
||||
CRPC_EXPORT void crpc_test_schedule_call(rpc_string* name);
|
||||
|
||||
/// @brief crpc_test_change_duration will change duration between regular calls
|
||||
/// @note blocking operation
|
||||
CRPC_EXPORT void crpc_test_change_duration(uint64_t duration_ms);
|
||||
|
||||
/// @note blocking operation
|
||||
CRPC_EXPORT uint64_t crpc_test_duration();
|
||||
|
||||
/// @brief crpc_test_auto_call change internal state of auto call
|
||||
/// @param state 0 - turn off auto call on sleep; 1 - turn on auto call on sleep
|
||||
CRPC_EXPORT void crpc_test_auto_call(uint32_t state);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif //cpp
|
||||
|
||||
#endif //CRPC_SERVER_API
|
||||
31
meson.build
31
meson.build
@ -6,11 +6,23 @@ project('cloud_point_rpc', 'cpp',
|
||||
json_dep = dependency('nlohmann_json', fallback : ['nlohmann_json', 'nlohmann_json_dep'])
|
||||
thread_dep = dependency('threads')
|
||||
asio_dep = dependency('asio', fallback : ['asio', 'asio_dep'])
|
||||
|
||||
base64_dep = dependency('base64', fallback: ['aklomp-base64', 'base64'])
|
||||
# GLog via CMake fallback
|
||||
cmake = import('cmake')
|
||||
glog_opt = cmake.subproject_options()
|
||||
glog_opt.add_cmake_defines({'WITH_GFLAGS': 'OFF', 'WITH_GTEST': 'OFF'})
|
||||
glog_opt.add_cmake_defines({
|
||||
'WITH_GFLAGS': 'OFF',
|
||||
'WITH_GTEST': 'OFF',
|
||||
})
|
||||
|
||||
libtype = get_option('default_library')
|
||||
if libtype == 'static'
|
||||
message('Will share static state with glog')
|
||||
glog_opt.add_cmake_defines({
|
||||
'BUILD_SHARED_LIBS': 'OFF',
|
||||
})
|
||||
endif
|
||||
|
||||
glog_proj = cmake.subproject('glog', options: glog_opt)
|
||||
glog_dep = glog_proj.dependency('glog')
|
||||
|
||||
@ -26,3 +38,18 @@ inc = include_directories(['include', 'rpc/include'])
|
||||
|
||||
subdir('src')
|
||||
subdir('tests')
|
||||
|
||||
if host_machine.system() == 'windows' and libtype == 'shared'
|
||||
message('We are on Windows, so to prevent our ass from pain')
|
||||
devenv = environment()
|
||||
message('By the way, I hate windows :O')
|
||||
prefixed = meson.global_build_root() / 'subprojects'
|
||||
message('Prefixed dir is: ', prefixed)
|
||||
devenv.append('PATH', prefixed / 'glog')
|
||||
devenv.append('PATH', prefixed / 'googletest-1.17.0' / 'googletest')
|
||||
devenv.append('PATH', prefixed / 'googletest-1.17.0' / 'googlemock')
|
||||
devenv.append('PATH', prefixed / 'yaml-cpp-0.8.0')
|
||||
|
||||
meson.add_devenv(devenv)
|
||||
|
||||
endif
|
||||
57
src/cli.cpp
57
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 {
|
||||
namespace score {
|
||||
|
||||
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;
|
||||
|
||||
18
src/main.cpp
18
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) {
|
||||
auto config = score::ConfigLoader::load(config_path);
|
||||
return score::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,28 +1,56 @@
|
||||
add_project_arguments('-DCRPC_SERVER_API_EXPORT -pthread', language: 'cpp')
|
||||
|
||||
cloud_point_rpc_sources = files(
|
||||
'rpc_server.cpp',
|
||||
'service.cpp',
|
||||
'cli.cpp'
|
||||
'server_api.cpp',
|
||||
'rpc_coder.cpp'
|
||||
)
|
||||
|
||||
libcloud_point_rpc = static_library('cloud_point_rpc',
|
||||
libcloud_point_rpc = shared_library('cloud_point_rpc',
|
||||
cloud_point_rpc_sources,
|
||||
include_directories : inc,
|
||||
dependencies : [json_dep, thread_dep, glog_dep, yaml_dep, asio_dep],
|
||||
dependencies : [json_dep, thread_dep, glog_dep, yaml_dep, asio_dep, base64_dep],
|
||||
install : true)
|
||||
|
||||
cloud_point_rpc_dep = declare_dependency(
|
||||
include_directories : inc,
|
||||
link_with : libcloud_point_rpc,
|
||||
dependencies : [json_dep, glog_dep, yaml_dep, asio_dep])
|
||||
dependencies : [json_dep, glog_dep, yaml_dep, asio_dep, base64_dep])
|
||||
|
||||
# Test lib
|
||||
libcloud_point_rpc_test = shared_library('test_cloud_point',
|
||||
'test_api.cpp',
|
||||
dependencies: cloud_point_rpc_dep,
|
||||
install : true)
|
||||
|
||||
cloud_point_rpc_test_dep = declare_dependency(
|
||||
include_directories: inc,
|
||||
link_with: libcloud_point_rpc_test,
|
||||
dependencies: [cloud_point_rpc_dep]
|
||||
)
|
||||
|
||||
libcloud_point_rpc_cli = shared_library('libcloud_point_rpc_cli',
|
||||
'cli.cpp',
|
||||
include_directories : inc,
|
||||
dependencies : [cloud_point_rpc_dep],
|
||||
install : true)
|
||||
|
||||
cloud_point_rpc_cli_dep = declare_dependency(
|
||||
include_directories: inc,
|
||||
link_with: libcloud_point_rpc_cli,
|
||||
dependencies: [cloud_point_rpc_dep]
|
||||
)
|
||||
|
||||
# Client/CLI tool (legacy stdin/stdout)
|
||||
executable('cloud_point_rpc_cli',
|
||||
'main.cpp',
|
||||
dependencies : cloud_point_rpc_dep,
|
||||
['main.cpp', ],
|
||||
dependencies : cloud_point_rpc_cli_dep,
|
||||
install : true)
|
||||
|
||||
# Server executable (TCP)
|
||||
executable('cloud_point_rpc_server',
|
||||
'server_main.cpp',
|
||||
dependencies : cloud_point_rpc_dep,
|
||||
link_args : '-pthread',
|
||||
install : true)
|
||||
|
||||
42
src/rpc_coder.cpp
Normal file
42
src/rpc_coder.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// Created by vptyp on 11.03.2026.
|
||||
//
|
||||
#include "cloud_point_rpc/rpc_coder.h"
|
||||
|
||||
#include "libbase64.h"
|
||||
#include <glog/logging.h>
|
||||
namespace score {
|
||||
|
||||
Base64RPCCoder::Base64RPCCoder() = default;
|
||||
Base64RPCCoder::~Base64RPCCoder() = default;
|
||||
|
||||
/**
|
||||
* Tries to decode ASCII complained string to the
|
||||
* @param encoded ASCII complained base64 encoded string
|
||||
* @return vector of raw bytes << allocated on encoded.size() / 4 * 3 + 1 size
|
||||
*/
|
||||
std::vector<char> Base64RPCCoder::decode(const std::string& encoded) {
|
||||
DLOG(INFO) << "Base64RPCCoder::decode";
|
||||
std::vector<char> result((encoded.length() >> 2) * 3 + 1);
|
||||
size_t result_len = 0;
|
||||
base64_decode(encoded.data(), encoded.size(),
|
||||
result.data(), &result_len, 0);
|
||||
DLOG(INFO) << "result_len: " << result_len;
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param data raw byte stream
|
||||
* @return encoded base64 string
|
||||
*/
|
||||
std::string Base64RPCCoder::encode(const std::vector<char>& data) {
|
||||
DLOG(INFO) << "Base64RPCCoder::encode";
|
||||
size_t result_len = 0;
|
||||
std::string result(data.size() / 3 * 4 + 1, 0);
|
||||
base64_encode(data.data(), data.size(),
|
||||
result.data(), &result_len, 0
|
||||
);
|
||||
DLOG(INFO) << "result_len: " << result_len;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -1,61 +1,69 @@
|
||||
#include "cloud_point_rpc/rpc_server.hpp"
|
||||
#include <iostream>
|
||||
|
||||
#include <glog/logging.h>
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
namespace score {
|
||||
|
||||
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();
|
||||
}
|
||||
void RpcServer::register_method(const std::string& name, callback_t handler) {
|
||||
handlers_[name] = [handler](const nlohmann::json& j) -> nlohmann::json {
|
||||
rpc_string tmp;
|
||||
tmp.s = j.dump();
|
||||
rpc_string* res = handler(&tmp);
|
||||
return {res->s};
|
||||
};
|
||||
}
|
||||
|
||||
// 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 RpcServer::process(const std::string &request_str) {
|
||||
LOG(INFO) << 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();
|
||||
}
|
||||
|
||||
std::string method = request["method"];
|
||||
json id = request["id"];
|
||||
json params = request.value("params", json::object());
|
||||
// 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();
|
||||
}
|
||||
|
||||
auto it = handlers_.find(method);
|
||||
if (it == handlers_.end()) {
|
||||
return create_error(-32601, "Method not found", id).dump();
|
||||
}
|
||||
std::string method = request["method"];
|
||||
json id = request["id"];
|
||||
json params = request.value("params", json::object());
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
|
||||
68
src/server_api.cpp
Normal file
68
src/server_api.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
#include "cloud_point_rpc/config.hpp"
|
||||
#include "cloud_point_rpc/rpc_server.hpp"
|
||||
#include "cloud_point_rpc/tcp_server.hpp"
|
||||
#include <glog/logging.h>
|
||||
#include "server_api.h"
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
static std::list<std::unique_ptr<rpc_string>> gc;
|
||||
score::RpcServer rpc_server;
|
||||
std::unique_ptr<score::TcpServer> server = nullptr;
|
||||
|
||||
extern "C" {
|
||||
|
||||
const char* crpc_str_get_data(const rpc_string* that) {
|
||||
return that->s.c_str();
|
||||
}
|
||||
|
||||
uint64_t crpc_str_get_size(const rpc_string* that){
|
||||
return that->s.size();
|
||||
}
|
||||
|
||||
rpc_string* crpc_str_create(const char* data, uint64_t size){
|
||||
gc.push_back(std::make_unique<rpc_string>(data, size));
|
||||
return gc.back().get();
|
||||
}
|
||||
|
||||
void crpc_str_destroy(rpc_string* that){
|
||||
auto it = std::ranges::find(gc, that, &std::unique_ptr<rpc_string>::get);
|
||||
if(it != gc.end())
|
||||
gc.erase(it);
|
||||
}
|
||||
|
||||
|
||||
void crpc_init(const char* config_path) {
|
||||
google::InitGoogleLogging("CloudPointRPC");
|
||||
if(config_path == nullptr) {
|
||||
LOG(INFO) << "config_path was not provided";
|
||||
}
|
||||
try {
|
||||
auto config = score::ConfigLoader::load(config_path);
|
||||
LOG(INFO) << "Loaded config from " << config_path;
|
||||
|
||||
server = std::make_unique<score::TcpServer>(config.server.ip, config.server.port,
|
||||
[&](const std::string &request) {
|
||||
return rpc_server.process(
|
||||
request);
|
||||
});
|
||||
server->start();
|
||||
} catch (const std::exception &e) {
|
||||
LOG(ERROR) << "Fatal error: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void crpc_deinit() {
|
||||
if(server)
|
||||
server->join();
|
||||
server.reset();
|
||||
gc.clear();
|
||||
}
|
||||
|
||||
void crpc_add_method(callback_t cb, rpc_string* name) {
|
||||
rpc_server.register_method(name->s, cb);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 = score::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
|
||||
score::Service service(config.test_data);
|
||||
score::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);
|
||||
}
|
||||
);
|
||||
score::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;
|
||||
}
|
||||
|
||||
@ -1,38 +1,30 @@
|
||||
#include "cloud_point_rpc/service.hpp"
|
||||
|
||||
namespace cloud_point_rpc {
|
||||
namespace score {
|
||||
|
||||
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
|
||||
|
||||
174
src/test_api.cpp
Normal file
174
src/test_api.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
#include "test_api.h"
|
||||
#include "cloud_point_rpc/rpc_server.hpp"
|
||||
#include "server_api.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <glog/logging.h>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
class TestThread {
|
||||
static std::string make_jsonrpc(const std::string &method_name) {
|
||||
return std::format(R"(
|
||||
{{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "{}",
|
||||
"params": {{}},
|
||||
"id": 1
|
||||
}}
|
||||
)",
|
||||
method_name);
|
||||
}
|
||||
|
||||
public:
|
||||
TestThread() = default;
|
||||
~TestThread() { join(); }
|
||||
|
||||
void routine() {
|
||||
size_t distance{0};
|
||||
std::unique_lock lock(mtx);
|
||||
const std::stop_token stoken = thr.get_stop_token();
|
||||
lock.unlock();
|
||||
while (!stoken.stop_requested()) {
|
||||
lock.lock();
|
||||
if (!calls_queue.empty()) {
|
||||
auto front = calls_queue.front();
|
||||
DLOG(INFO) << front << " will try to call";
|
||||
calls_queue.pop();
|
||||
LOG(INFO) << server.process(make_jsonrpc(front));
|
||||
} else if (state.load() && !methods.empty()) {
|
||||
auto it = methods.begin();
|
||||
std::advance(it, distance++ % methods.size());
|
||||
DLOG(INFO) << *it << " : Method at this position";
|
||||
LOG(INFO) << server.process(make_jsonrpc(*it));
|
||||
}
|
||||
|
||||
if (state.load() && calls_queue.empty())
|
||||
cv.wait_for(lock, thr_sleep, [&] {
|
||||
return stoken.stop_requested();
|
||||
});
|
||||
|
||||
lock.unlock();
|
||||
}
|
||||
LOG(INFO) << "Stopped";
|
||||
}
|
||||
|
||||
void start() { thr = std::jthread(&TestThread::routine, this); }
|
||||
void join() {
|
||||
if (thr.joinable()) {
|
||||
DLOG(INFO) << "Requested thread stop";
|
||||
thr.request_stop();
|
||||
cv.notify_one();
|
||||
thr.join();
|
||||
}
|
||||
}
|
||||
void add_method(const callback_t cb, rpc_string *name) {
|
||||
LOG(INFO) << "Trying to add method: " << name->s;
|
||||
std::lock_guard lock(mtx);
|
||||
if (methods.contains(name->s)) {
|
||||
LOG(INFO) << "Method already exists: " << name->s;
|
||||
return;
|
||||
}
|
||||
methods.emplace(name->s);
|
||||
server.register_method(name->s, cb);
|
||||
}
|
||||
|
||||
int remove_method(const rpc_string *name) {
|
||||
LOG(INFO) << "Trying to remove method: " << name->s;
|
||||
std::lock_guard lock(mtx);
|
||||
int result = 0;
|
||||
auto it = std::find(methods.begin(), methods.end(), name->s);
|
||||
if (it != methods.end()) {
|
||||
methods.erase(it);
|
||||
} else {
|
||||
LOG(ERROR) << "Method not found: " << name->s;
|
||||
result = -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void call(const rpc_string *name) {
|
||||
std::lock_guard lock(mtx);
|
||||
LOG(INFO) << server.process(name->s);
|
||||
}
|
||||
|
||||
void set_duration(uint64_t duration_ms) {
|
||||
LOG(INFO) << "Trying to install sleep duration: " << duration_ms;
|
||||
std::lock_guard lock(mtx);
|
||||
thr_sleep = std::chrono::milliseconds(duration_ms);
|
||||
}
|
||||
|
||||
uint64_t get_duration() {
|
||||
std::lock_guard lock(mtx);
|
||||
return thr_sleep.count();
|
||||
}
|
||||
|
||||
void add_queue_call(const std::string &name) {
|
||||
std::lock_guard lock(mtx);
|
||||
calls_queue.emplace(name);
|
||||
}
|
||||
|
||||
void auto_call(bool state) {
|
||||
this->state.store(state, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
std::lock_guard lock(mtx);
|
||||
calls_queue = std::queue<std::string>();
|
||||
methods.clear();
|
||||
state.store(true, std::memory_order_relaxed);
|
||||
server = score::RpcServer();
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<bool> state{true};
|
||||
std::condition_variable cv;
|
||||
std::queue<std::string> calls_queue{};
|
||||
std::set<std::string> methods{};
|
||||
std::jthread thr;
|
||||
std::mutex mtx;
|
||||
score::RpcServer server;
|
||||
std::chrono::duration<int64_t, std::milli> thr_sleep{50};
|
||||
} test;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void crpc_test_init() {
|
||||
if (!google::IsGoogleLoggingInitialized())
|
||||
google::InitGoogleLogging("TestRPC");
|
||||
try {
|
||||
test.start();
|
||||
} catch (const std::exception &e) {
|
||||
LOG(ERROR) << "Fatal error: " << e.what();
|
||||
}
|
||||
}
|
||||
void crpc_test_deinit() {
|
||||
test.join();
|
||||
crpc_deinit();
|
||||
test.reset();
|
||||
}
|
||||
|
||||
void crpc_test_add_method(callback_t cb, rpc_string *name) {
|
||||
test.add_method(cb, name);
|
||||
}
|
||||
|
||||
void crpc_test_change_duration(uint64_t duration_ms) {
|
||||
test.set_duration(duration_ms);
|
||||
}
|
||||
|
||||
uint64_t crpc_test_duration() { return test.get_duration(); }
|
||||
|
||||
int crpc_test_remove_method(rpc_string *name) {
|
||||
return test.remove_method(name);
|
||||
}
|
||||
|
||||
void crpc_test_schedule_call(rpc_string *name) { test.add_queue_call(name->s); }
|
||||
|
||||
void crpc_test_auto_call(uint32_t state) {
|
||||
test.auto_call(static_cast<bool>(state));
|
||||
}
|
||||
}
|
||||
14
subprojects/aklomp-base64.wrap
Normal file
14
subprojects/aklomp-base64.wrap
Normal file
@ -0,0 +1,14 @@
|
||||
[wrap-file]
|
||||
directory = base64-0.5.2
|
||||
source_url = https://github.com/aklomp/base64/archive/refs/tags/v0.5.2.tar.gz
|
||||
source_filename = base64-0.5.2.tar.gz
|
||||
source_hash = 723a0f9f4cf44cf79e97bcc315ec8f85e52eb104c8882942c3f2fba95acc080d
|
||||
source_fallback_url = https://wrapdb.mesonbuild.com/v2/aklomp-base64_0.5.2-1/get_source/base64-0.5.2.tar.gz
|
||||
patch_filename = aklomp-base64_0.5.2-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/aklomp-base64_0.5.2-1/get_patch
|
||||
patch_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/aklomp-base64_0.5.2-1/aklomp-base64_0.5.2-1_patch.zip
|
||||
patch_hash = 9805354b8c0333fe0123c10d8c62356ef1d0d67a2689a348d18f73bddc1e2b10
|
||||
wrapdb_version = 0.5.2-1
|
||||
|
||||
[provide]
|
||||
dependency_names = base64
|
||||
@ -1,11 +1,14 @@
|
||||
test_sources = files(
|
||||
'test_rpc.cpp',
|
||||
'test_integration.cpp',
|
||||
'test_cli.cpp'
|
||||
'test_tcp.cpp',
|
||||
'test_cli.cpp',
|
||||
'test_c_api.cpp',
|
||||
'test_base64.cpp'
|
||||
)
|
||||
|
||||
test_exe = executable('unit_tests',
|
||||
test_sources,
|
||||
dependencies : [cloud_point_rpc_dep, gtest_dep, gtest_main_dep, gmock_dep])
|
||||
dependencies : [cloud_point_rpc_dep, cloud_point_rpc_cli_dep, cloud_point_rpc_test_dep, json_dep, gtest_dep, gtest_main_dep, gmock_dep])
|
||||
|
||||
test('unit_tests', test_exe)
|
||||
|
||||
31
tests/test_base64.cpp
Normal file
31
tests/test_base64.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// Created by vptyp on 11.03.2026.
|
||||
//
|
||||
#include <chrono>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <thread>
|
||||
|
||||
#include "cloud_point_rpc/config.hpp"
|
||||
#include "cloud_point_rpc/rpc_coder.h"
|
||||
|
||||
|
||||
class Base64Test : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
|
||||
}
|
||||
void TearDown() override {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(Base64Test, EncodeDecode) {
|
||||
std::vector raw{'H', 'e', 'l', 'l', 'o', 'w', '\0'};
|
||||
score::Base64RPCCoder coder;
|
||||
auto encoded = coder.encode(raw);
|
||||
LOG(INFO) << "encoded: " << encoded;
|
||||
auto decoded = coder.decode(encoded);
|
||||
EXPECT_EQ(std::ranges::equal(decoded, raw), true);
|
||||
LOG(INFO) << "done";
|
||||
}
|
||||
169
tests/test_c_api.cpp
Normal file
169
tests/test_c_api.cpp
Normal file
@ -0,0 +1,169 @@
|
||||
#include "cloud_point_rpc/rpc_server.hpp"
|
||||
#include "server_api.h"
|
||||
#include "test_api.h"
|
||||
#include <future>
|
||||
#include <glog/logging.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
class TestCApi : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
FLAGS_logtostderr = true;
|
||||
if (!google::IsGoogleLoggingInitialized())
|
||||
google::InitGoogleLogging("TestRPC");
|
||||
EXPECT_NO_THROW(crpc_test_init());
|
||||
}
|
||||
|
||||
void TearDown() override { crpc_test_deinit(); }
|
||||
};
|
||||
|
||||
TEST_F(TestCApi, Base) {
|
||||
|
||||
rpc_string name;
|
||||
name.s = "test";
|
||||
static std::promise<bool> task;
|
||||
std::future<bool> called = task.get_future();
|
||||
DLOG(INFO) << "Test";
|
||||
crpc_test_add_method(
|
||||
+[](rpc_string *) -> rpc_string * {
|
||||
static bool installed = false;
|
||||
DLOG(INFO) << "Trying to do something";
|
||||
if (!installed) {
|
||||
DLOG(INFO) << "Trying to install";
|
||||
installed = true;
|
||||
task.set_value(installed);
|
||||
}
|
||||
DLOG(INFO) << "Go out";
|
||||
return crpc_str_create("res", sizeof("res"));
|
||||
},
|
||||
&name);
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
const std::future_status res = called.wait_for(500ms);
|
||||
EXPECT_NE(res, std::future_status::timeout);
|
||||
EXPECT_EQ(called.get(), true);
|
||||
|
||||
DLOG(INFO) << "DONE";
|
||||
}
|
||||
|
||||
TEST_F(TestCApi, AddedMultiple) {
|
||||
constexpr int N = 4;
|
||||
std::array<std::promise<bool>, N> tasks;
|
||||
std::array<std::pair<std::future<bool>, rpc_string>, N> called;
|
||||
|
||||
// The Bridge: A static pointer local to this test function
|
||||
static std::array<std::promise<bool>, N>* bridge;
|
||||
bridge = &tasks;
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
called[i].first = tasks[i].get_future();
|
||||
std::string n = "test" + std::to_string(i);
|
||||
called[i].second = rpc_string{n.c_str(), n.size()};
|
||||
}
|
||||
|
||||
auto register_idx = [&]<size_t I>() {
|
||||
crpc_test_add_method(+[](rpc_string*) -> rpc_string* {
|
||||
static bool installed = false;
|
||||
if (!installed) {
|
||||
installed = true;
|
||||
(*bridge)[I].set_value(true);
|
||||
}
|
||||
return crpc_str_create("res", sizeof("res"));
|
||||
}, &called[I].second);
|
||||
};
|
||||
|
||||
register_idx.template operator()<0>();
|
||||
register_idx.template operator()<1>();
|
||||
register_idx.template operator()<2>();
|
||||
register_idx.template operator()<3>();
|
||||
|
||||
auto test_idx = [&]<size_t I>() {
|
||||
using namespace std::chrono_literals;
|
||||
const std::future_status res = called[I].first.wait_for(1000ms);
|
||||
EXPECT_NE(res, std::future_status::timeout);
|
||||
EXPECT_EQ(called[I].first.get(), true);
|
||||
LOG(INFO) << "Done" << "; task=" << I;
|
||||
};
|
||||
|
||||
test_idx.template operator()<0>();
|
||||
test_idx.template operator()<1>();
|
||||
test_idx.template operator()<2>();
|
||||
test_idx.template operator()<3>();
|
||||
}
|
||||
|
||||
TEST_F(TestCApi, RemoveMethod) {
|
||||
rpc_string name{"test", sizeof("test") - 1};
|
||||
EXPECT_EQ(crpc_test_remove_method(&name), -1);
|
||||
}
|
||||
|
||||
TEST_F(TestCApi, ChangeDuration) {
|
||||
constexpr int dur = 1e3;
|
||||
EXPECT_NO_THROW(crpc_test_change_duration(dur));
|
||||
EXPECT_EQ(crpc_test_duration(), dur);
|
||||
LOG(INFO) << "Test";
|
||||
}
|
||||
|
||||
TEST_F(TestCApi, ScheduleCall) {
|
||||
EXPECT_NO_THROW(crpc_test_auto_call(0));
|
||||
constexpr int N = 4;
|
||||
std::array<std::promise<bool>, N> tasks;
|
||||
std::array<std::pair<std::future<bool>, rpc_string>, N> called;
|
||||
|
||||
// The Bridge: A static pointer local to this test function
|
||||
static std::array<std::promise<bool>, N>* bridge;
|
||||
bridge = &tasks;
|
||||
LOG(INFO) << "Started Schedule Call";
|
||||
for (int i = 0; i < N; i++) {
|
||||
called[i].first = tasks[i].get_future();
|
||||
std::string n = "test" + std::to_string(i);
|
||||
called[i].second = rpc_string{n.c_str(), n.size()};
|
||||
}
|
||||
|
||||
auto register_idx = [&]<size_t I>() {
|
||||
crpc_test_add_method(+[](rpc_string*) -> rpc_string* {
|
||||
static bool installed = false;
|
||||
if (!installed) {
|
||||
installed = true;
|
||||
(*bridge)[I].set_value(true);
|
||||
}
|
||||
return crpc_str_create("res", sizeof("res"));
|
||||
}, &called[I].second);
|
||||
};
|
||||
auto test_idx = [&]<size_t I>() {
|
||||
using namespace std::chrono_literals;
|
||||
const std::future_status res = called[I].first.wait_for(500ms);
|
||||
EXPECT_NE(res, std::future_status::timeout);
|
||||
EXPECT_EQ(called[I].first.get(), true);
|
||||
};
|
||||
|
||||
auto schedule_call = []<size_t I>() {
|
||||
rpc_string test{"test", sizeof("test") - 1};
|
||||
test.s += std::to_string(I);
|
||||
LOG(INFO) << "Trying to add :" << test.s;
|
||||
crpc_test_schedule_call(&test);
|
||||
};
|
||||
|
||||
register_idx.template operator()<0>();
|
||||
register_idx.template operator()<1>();
|
||||
register_idx.template operator()<2>();
|
||||
register_idx.template operator()<3>();
|
||||
schedule_call.template operator()<0>();
|
||||
schedule_call.template operator()<1>();
|
||||
schedule_call.template operator()<2>();
|
||||
schedule_call.template operator()<3>();
|
||||
test_idx.template operator()<0>();
|
||||
test_idx.template operator()<1>();
|
||||
test_idx.template operator()<2>();
|
||||
test_idx.template operator()<3>();
|
||||
}
|
||||
|
||||
TEST_F(TestCApi, String) {
|
||||
rpc_string name;
|
||||
name.s = "test";
|
||||
EXPECT_EQ(name.s.c_str(), crpc_str_get_data(&name));
|
||||
EXPECT_EQ(name.s.size(), crpc_str_get_size(&name));
|
||||
|
||||
auto creation = crpc_str_create("test 2222", sizeof("test 2222"));
|
||||
EXPECT_NO_THROW(crpc_str_destroy(creation));
|
||||
}
|
||||
@ -1,39 +1,38 @@
|
||||
#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;
|
||||
using namespace score;
|
||||
|
||||
class CliTest : public ::testing::Test {
|
||||
protected:
|
||||
public:
|
||||
void start() {
|
||||
tcp_server->start();
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
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,10 +47,11 @@ 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};
|
||||
});
|
||||
this->start();
|
||||
input << "1" << std::endl;
|
||||
input << "0" << std::endl;
|
||||
|
||||
@ -60,8 +60,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;
|
||||
using namespace score;
|
||||
|
||||
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;
|
||||
using namespace score;
|
||||
|
||||
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<score::TcpServer>(
|
||||
"127.0.0.1", 12345, [this](const std::string &request) {
|
||||
EXPECT_EQ(request, expected_);
|
||||
std::string msg = "Echo: " + request;
|
||||
auto v = score::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<score::TcpServer> server_;
|
||||
};
|
||||
|
||||
TEST(SerializeTest, Base) {
|
||||
uint64_t value{123};
|
||||
auto res = score::serialize(value);
|
||||
EXPECT_EQ(value, score::deserialize<uint64_t>(res));
|
||||
}
|
||||
|
||||
TEST_F(TcpTest, EchoTest) {
|
||||
constexpr std::string_view msg = "Hello, TCP Server!";
|
||||
ExpectedResponse(msg.data());
|
||||
score::TCPConnector connector("127.0.0.1", 12345);
|
||||
auto res = connector.Send(msg.data());
|
||||
}
|
||||
|
||||
TEST_F(TcpTest, HugeBuffer) {
|
||||
static constexpr uint64_t w = 1280, h = 720, c = 3;
|
||||
std::string data(w * h * c, 77);
|
||||
ExpectedResponse(data);
|
||||
score::TCPConnector connector("127.0.0.1", 12345);
|
||||
auto res = connector.Send(data);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user