--- type: Protocol title: RPC Protocol description: JSON-RPC 2.0 wire protocol over TCP — request/response formats, error codes, all RPC methods (get-stereo-calibration, get-image-pair, get-available-methods, legacy methods), and handler registration. tags: [rpc, json-rpc, protocol, api, wire-format] --- # RPC Protocol ## JSON-RPC 2.0 The server implements **JSON-RPC 2.0** over TCP with length-prefixed framing (see [Architecture → Wire framing](architecture.md#wire-framing)). Batch requests are not supported — only single request objects are processed. ### Request format ```json { "jsonrpc": "2.0", "method": "", "params": {}, "id": } ``` The `params` field is accepted but currently ignored by all implemented methods. The `id` field is required; requests without it receive a `-32600 Invalid Request` error. ### Success response ```json { "jsonrpc": "2.0", "result": , "id": } ``` ### Error response ```json { "jsonrpc": "2.0", "error": { "code": , "message": "" }, "id": } ``` ### Error codes | Code | Meaning | When | |---|---|---| | `-32700` | Parse error | Request is not valid JSON | | `-32600` | Invalid Request | Missing `jsonrpc`, `method`, or `id` fields, or `jsonrpc != "2.0"` | | `-32601` | Method not found | No handler registered for the requested method name | | `-32000` | Server error | Handler threw an exception | > **Known issue**: Non-object JSON (arrays, strings, numbers, null) causes a `nlohmann::json::type_error` instead of a clean `-32600` response. See `tests/test_rpc_edge_cases.cpp`. Source: `src/rpc_server.cpp` — `RpcServer::process()` and `create_error()`. ## Methods ### `get-stereo-calibration` Returns full stereo rig calibration: intrinsics for both cameras, stereo rotation and translation, and sensor resolution. Called once by `CloudPointClient::connect()`. **Request:** ```json {"jsonrpc": "2.0", "method": "get-stereo-calibration", "id": 1} ``` **Response:** ```json { "left": { "camera_matrix": [9 doubles], "dist_coeffs": [5 doubles] }, "right": { "camera_matrix": [9 doubles], "dist_coeffs": [5 doubles] }, "rotation": [9 doubles], "translation": [3 doubles], "image_size": { "width": 640, "height": 480 } } ``` All matrices are row-major. `camera_matrix` is the 3×3 intrinsic matrix; `rotation` is the 3×3 left-to-right rotation (OpenCV convention); `translation` is in metres. Calibration arrays are **plain JSON doubles — not Base64**. Test mock defaults: fx=fy=800, cx=320, cy=240, zero distortion, R=I, T=[-0.06, 0, 0], 640×480. --- ### `get-image-pair` Returns a synchronised stereo frame as two base64-encoded images. Called on each `CloudPointClient::compute_cloud()`. **Request:** ```json {"jsonrpc": "2.0", "method": "get-image-pair", "id": 2} ``` **Response:** ```json { "frame": 42, "left": { "width": 640, "height": 480, "type": "BGR", "data": "" }, "right": { "width": 640, "height": 480, "type": "BGR", "data": "" } } ``` `data` is Base64-encoded raw pixel bytes (standard alphabet, no line breaks). `type` is `"BGR"` (3 ch), `"RGBA"` (4 ch), or `"DEPTH"` (1 ch float32). Unity must flip GPU readback vertically before encoding (wire format is top-left-origin row-major). DTOs: `StereoCalibrationDto` and `ImagePairDto` in `include/cloud_point_rpc/rpc_dto.hpp`. Decoded client-side via `Base64RPCCoder`. --- ### `get-available-methods` Returns the names of all methods registered on the server. Auto-registered in the `RpcServer` constructor — not added by the application. Useful for client-side discovery. **Request:** ```json {"jsonrpc": "2.0", "method": "get-available-methods", "id": 5} ``` **Response:** ```json {"jsonrpc": "2.0", "result": ["get-available-methods", "get-stereo-calibration", "get-image-pair"], "id": 5} ``` Result is a JSON array of method-name strings, including `get-available-methods` itself. Tested in `tests/test_integration.cpp` (`ClientRetrieveRemoteMethods`). Source: `src/rpc_server.cpp` — `RpcServer::RpcServer()` constructor; `get_method_names()`. --- ### `get-intrinsic-params` *(legacy)* Retrieves left-camera intrinsic parameters as a flat 3×3 matrix (row-major, 9 doubles). Not used by `CloudPointClient`; kept for backward compatibility. **Request:** ```json {"jsonrpc": "2.0", "method": "get-intrinsic-params", "id": 3} ``` **Response:** ```json {"jsonrpc": "2.0", "result": [fx, 0, cx, 0, fy, cy, 0, 0, 1], "id": 3} ``` Result: 9 plain JSON doubles. **Not Base64.** ### `get-extrinsic-params` *(legacy)* Retrieves left-camera extrinsic matrix as a flat 4×4 matrix (row-major, 16 doubles). Not used by `CloudPointClient`; kept for backward compatibility. **Request:** ```json {"jsonrpc": "2.0", "method": "get-extrinsic-params", "id": 4} ``` **Response:** ```json {"jsonrpc": "2.0", "result": [16 doubles], "id": 4} ``` Result: 16 plain JSON doubles. **Not Base64.** ### `get-cloud-point` Retrieves the current field-of-view point cloud. **Request:** ```json {"jsonrpc": "2.0", "method": "get-cloud-point", "id": 3} ``` **Response:** ```json {"jsonrpc": "2.0", "result": {"width": int, "height": int, "data": }, "id": 3} ``` Result type: `matrix WxH` (list of `[x, y, z]` points), Base64-encoded. > **Note**: The standalone mock server (`server_main.cpp`) returns these as raw JSON arrays (not Base64-encoded) via `Service`. The Base64 encoding is expected to be implemented on the Unity side per `API.md`. ## Handler registration Handlers are registered with `RpcServer::register_method()`. Two forms exist: ### C++ handler ```cpp rpc_server.register_method("get-intrinsic-params", [&](const json& params) { return service.get_intrinsic_params(); // returns vector }); ``` The handler returns `std::variant`. If a `json` is returned, it is placed directly in the `result` field. If a `std::string` is returned, it is placed as-is. ### C callback handler ```cpp rpc_string* my_callback(rpc_string* params_json) { // params_json->s contains the JSON params as a string // return a result string (JSON or raw) return crpc_str_create("result_data", 11); } rpc_server.register_method("my-method", my_callback); ``` The C callback receives the JSON `params` as a string in `rpc_string->s`. The return value's string is parsed as JSON if possible; otherwise it is used as a raw string in the `result` field. Source: `src/rpc_server.cpp` — `register_method(name, callback_t)` overload. ## Base64 encoding `include/cloud_point_rpc/rpc_coder.hpp`, `src/rpc_coder.cpp` - `Base64RPCCoder` implements the `IRPCCoder` interface with `encode()` and `decode()` methods. - Uses the [aklomp/base64](https://github.com/aklomp/base64) library (Meson wrap `subprojects/aklomp-base64.wrap`). - `encode(vector) → string` and `decode(string) → vector`. - Includes overflow protection: throws `std::length_error` if input exceeds safe size limits. > Per `API.md`: **only image pixel data** (`get-image-pair` `data` fields) uses Base64. Calibration arrays (`camera_matrix`, `dist_coeffs`, `rotation`, `translation`) and legacy params are plain JSON doubles — never Base64. Unity encodes images before sending; the C++ client decodes them via `Base64RPCCoder`. ## Client-side usage `include/cloud_point_rpc/rpc_client.hpp` `RpcClient` wraps `jsonrpccxx::JsonRpcClient` and provides typed access: ```cpp score::TCPConnector connector(ip, port); score::RpcClient client(connector); auto intrinsic = client.get_intrinsic_params(); // vector auto extrinsic = client.get_extrinsic_params(); // vector auto cloud = client.get_cloud_point(); // vector> ``` The interactive CLI (`src/cli.cpp`) and `minimal_client` (`src/minimal_client.cpp`) demonstrate client usage.