The RPC library implements a lightweight, efficient Remote Procedure Call mechanism for distributed embedded systems. Built on top of CSP (Cubesat Space Protocol), it enables seamless communication between nodes in satellite systems, supporting both ARM-based satellite modules and x86-based ground stations.
Features:
- Simple client-server architecture
- Binary protocol for minimal overhead
- Support for multiple programs and procedures per node
- Automatic code generation from JSON5 interface definitions
- Flexible serialization of primitives, strings, and complex types
- Built-in program discovery mechanisms
- Cross-platform compatibility (ARM/x86)
The RPC framework operates over CSP (Cubesat Space Protocol) and uses a binary message format with two main message types:
- RPC_MSG_CALL - Client Request
- Program ID (32-bit)
- Procedure ID (32-bit)
- Serialized argument data (variable length)
- RPC_MSG_REPLY - Server Response
- Amount (32-bit counter)
- Index (32-bit sequence)
- Serialized result data (variable length)
All multi-byte integers are serialized in big-endian (network byte order).
Programs are registered statically using the RPC_DECLARE_PROGRAM macro, which places them in a special .rpc_programs section. Each program contains:
- Program ID: Unique 32-bit identifier
- Program Name: Text description
- Handler: Callback function to dispatch procedures
- Procedures: Array of available procedures
- Context Data: Optional user-defined data passed to handler
The framework supports the following primitive types:
- Integers: int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t
- Floating Point: float, double
- Strings: Null-terminated character arrays
- Buffers: Binary data with length prefix
- Enums: Emun types
Initialization
int rpc_start_server(rpc_server_t *me);Start the RPC server main loop.
Status codes are returned from initialization and RPC call functions:
RPC_STATUS_OK(0): Operation successfulRPC_STATUS_ERR_COULD_NOT_CONNECT(-1): Connection failedRPC_STATUS_ERR_TIMEOUT(-2): Operation timeoutRPC_STATUS_ERR_NO_MEMORY(-3): Memory allocation failureRPC_STATUS_ERR_INVALID(-4): Invalid parametersRPC_STATUS_ERR_NOT_FOUND(-5): Program/procedure not foundRPC_STATUS_ERR_NOT_SUPPORTED(-6): Feature not supportedRPC_STATUS_ERR_INUSE(-7): Resource in use
The RPC code generator (generate_rpc.py) automatically creates client and server code from JSON5 interface specifications, eliminating manual serialization/deserialization boilerplate.
Usage
python3 generate_rpc.py <json5_file> [output_dir] [mode]Arguments
<json5_file>: Path to JSON5 interface definition[output_dir]: Output directory (defaults to input file directory)[mode]:client,server, orboth(default:both)
Generated Output Files
rpc_<program>.h: Common header with structure definitions and program constantsrpc_<program>_client.h: Client API declarationsrpc_<program>_client.c: Client implementation with init and invoke functionsrpc_<program>_server.h: Server handler declarationsrpc_<program>_server.c: Server dispatcher and response serialization
Interface specifications define programs and their procedures:
{
program: 'program_name', // Program identifier
address: 0x10000000, // Unique 32-bit program ID
enums: [
{
name: 'some_enum', // Name of the enum type
items: [
{
name: 'field_name',
value: '0', // Optional value
description: 'Optional field description',
}
]
}
],
procedures: [
{
name: 'procedure_name', // Procedure identifier
index: 0, // Procedure ID within program
request: [ // Request parameter list
{
name: 'field_name',
type: 'u32',
description: 'Optional field description',
default: '0' // Optional default value
}
],
response: [ // Response value list
{
name: 'result',
type: 'i32',
description: 'Return value'
}
]
}
]
}| JSON5 Type | C Type | Description |
|---|---|---|
| u8, x8 | uint8_t | Unsigned 8-bit integer |
| u16, x16 | uint16_t | Unsigned 16-bit integer |
| u32, x32 | uint32_t | Unsigned 32-bit integer |
| u64, x64 | uint64_t | Unsigned 64-bit integer |
| i8 | int8_t | Signed 8-bit integer |
| i16 | int16_t | Signed 16-bit integer |
| i32 | int32_t | Signed 32-bit integer |
| i64 | int64_t | Signed 64-bit integer |
| f32 | float | 32-bit floating point |
| f64 | double | 64-bit floating point |
| string | char * | Null-terminated string |
Strings are handled specially:
Request strings: Add
max_lengthto allocate local buffer{ name: 'text', type: 'string', max_length: 256 }
Response strings: Generated as
const char *pointers (caller must ensure lifetime)
Enums are handled specially:
enums: Set
typeas the name of the enum{ name: 'field_name', type: 'some_enum' }
Client-side Functions
rpc_<program>_<procedure>_init()- Initialize request struct with non-default parametersrpc_<program>_<procedure>()- Invoke RPC call with initialized request, return results
Server-side Functions
rpc_<program>_<procedure>_host()- User must implement - Core procedure logicrpc_handle_calls_<program>()- Auto-generated - Dispatcher routing incoming calls
Input: dsu.json5
{
program: 'dsu',
address: 0x10000000,
procedures: [{
name: 'peek_element',
index: 0,
request: [
{ name: 'block_id', type: 'u32' },
{ name: 'num_elements', type: 'u32', default: '1' }
],
response: [
{ name: 'vaddr', type: 'x64' },
{ name: 'size_actual', type: 'i32' }
]
}]
}Generate Code
python3 generate_rpc.py dsu.json5Output Files
rpc_dsu.h- Common structures and constantsrpc_dsu_client.h- Client APIrpc_dsu_client.c- Client implementationrpc_dsu_server.h- Server callback declarationsrpc_dsu_server.c- Server dispatcher
Server Implementation
The generated rpc_dsu_server.h declares:
void rpc_dsu_peek_element_host(uint16_t node, uint32_t timeout,
const rpc_dsu_peek_element_request_t *request,
rpc_dsu_peek_element_response_t *response);You implement this function in your server code to handle the RPC call.
Client Usage
rpc_dsu_peek_element_request_t request = rpc_dsu_peek_element_init(42, 10);
rpc_dsu_peek_element_response_t response;
int result = rpc_dsu_peek_element(remote_node, &request, &response);
if (result == RPC_STATUS_OK) {
printf("vaddr: 0x%llx\n", response.vaddr);
}The build system includes an automatic code generation step via Meson generator target. When you run the build, the generator will process your JSON5 files and produce the necessary C source and header files before compilation.:
meson setup build-0
meson compile -C build-0Generated files are placed in the build tree:
build-0/lib/cpstorage/rpc_dsu.h
build-0/lib/cpstorage/rpc_dsu_client.h
build-0/lib/cpstorage/rpc_dsu_client.c
build-0/lib/cpstorage/rpc_dsu_server.h
build-0/lib/cpstorage/rpc_dsu_server.c
- Define your RPC interface in JSON5
- Run the generator to produce headers and sources
- Implement server callback functions (
*_host()functions) - Link generated code into your application
- Call client functions from remote nodes or server initialization from local nodes