librns C ABI
Purpose
librns embeds Reticulum in-process for native hosts (C, C++, and similar FFI). It is a thin facade over pkg/node, destination, and link. Same wire stack as the daemon. Not a Python API and not a full Control API mirror.
For Go apps, prefer pkg/node directly. For a separate daemon and JSON/WebSocket, use the Control API.
Artifacts
| Artifact | Role |
|---|---|
| include/rns.h | Public C header |
| bin/librns.so | Shared library (Linux) |
| bin/darwin/*/librns.dylib | Shared library (macOS) |
| bin/windows/amd64/librns.dll | Shared library (Windows) |
| pkg/librns | Pure Go facade (tests and fuzz without CGO) |
| pkg/librns/capi | CGO //export shims |
| cmd/librns | -buildmode=c-shared entry |
| bindings/c/examples/smoke | Minimal C smoke program |
| bindings/c/examples/page-fetch | C NomadNet-style page fetch over librns |
| bindings/odin/examples/page-fetch | Odin NomadNet-style page fetch over librns |
| bindings/zig/examples/page-fetch | Zig NomadNet-style page fetch over librns |
| bindings/c/examples/pageserver | C NomadNet-style pageserver over librns |
| bindings/odin/examples/pageserver | Odin NomadNet-style pageserver over librns |
| bindings/zig/examples/pageserver | Zig NomadNet-style pageserver over librns |
| bindings/cpp/examples/smoke | Minimal C++ smoke program over librns |
| bindings/cpp/examples/page-fetch | C++ NomadNet-style page fetch over librns |
| bindings/cpp/examples/pageserver | C++ NomadNet-style pageserver over librns |
| bindings/odin | Idiomatic Odin bindings and tests over librns.so |
| bindings/zig | Idiomatic Zig bindings and tests over librns.so |
| bindings/cpp | Idiomatic C++17 bindings and tests over librns.so |
| bindings/dart | Dart FFI (ffi.dart) plus Control API client |
| bindings/rust | Idiomatic Rust bindings and tests over librns.so |
| bindings/python | ctypes Python bindings and tests over librns.so |
| bindings/lua | LuaJIT FFI bindings and tests over librns.so |
| bindings/swift | SwiftPM bindings and tests over librns.so |
| bindings/java | JNA Java bindings and tests over librns.so |
| bindings/kotlin | Kotlin facade over the Java JNA bindings |
Daemon builds stay CGO_ENABLED=0. Only build-librns turns CGO on.
Build and smoke
task build-librns
make -C bindings/c/examples/smoke
./bindings/c/examples/smoke/librns-smoke
Page fetch against a live NomadNet or pageserver peer:
make -C bindings/c/examples/page-fetch
./bindings/c/examples/page-fetch/librns-page-fetch \
-c /path/to/config \
<dest_hash>:/page/index.mu
make -C bindings/odin/examples/page-fetch
./bindings/odin/examples/page-fetch/odin-page-fetch \
-c /path/to/config \
<dest_hash>:/page/index.mu
make -C bindings/zig/examples/page-fetch
./bindings/zig/examples/page-fetch/zig-page-fetch \
-c /path/to/config \
<dest_hash>:/page/index.mu
make -C bindings/cpp/examples/page-fetch
./bindings/cpp/examples/page-fetch/cpp-page-fetch \
-c /path/to/config \
<dest_hash>:/page/index.mu
Configs need an online TCP or Backbone hub from directory.rns.recipes. config.example only has AutoInterface.
Pageserver peers (announce nomadnetwork.node and serve /page/index.mu):
make -C bindings/c/examples/pageserver
./bindings/c/examples/pageserver/librns-pageserver \
-c /path/to/config
make -C bindings/odin/examples/pageserver
./bindings/odin/examples/pageserver/odin-pageserver \
-c /path/to/config
make -C bindings/zig/examples/pageserver
./bindings/zig/examples/pageserver/zig-pageserver \
-c /path/to/config
make -C bindings/cpp/examples/pageserver
./bindings/cpp/examples/pageserver/cpp-pageserver \
-c /path/to/config
Needs a C/C++ toolchain and CGO. Output: bin/librns.so and a copy of the header under bin/rns.h. The Odin examples also need odin on PATH. The Zig examples need zig on PATH. The C++ examples need a C++17 compiler.
librns vs Control API
| librns | Control API |
|---|---|
| In-process | Separate reticulum-go process |
| rns_event_poll or callback | WebSocket events |
| C ABI / FFI | JSON over HTTP and WS |
| Caller-owned buffers | Base64 / hex JSON payloads |
| Node, link, request, path table, lifecycle | Sessions and full HTTP surface |
Supported surface
Authoritative names live in include/rns.h. Summary below.
Version and errors
| Function | Notes |
|---|---|
| rns_version | Returns RNS_API_VERSION string |
| rns_last_error | Copies last failing call message into caller buffer |
| Code | Meaning |
|---|---|
| RNS_OK | Success |
| RNS_ERR_INVALID_ARG | Bad argument (empty path, wrong hash length, NUL in path) |
| RNS_ERR_INVALID_HANDLE | Unknown or destroyed handle |
| RNS_ERR_NOT_FOUND | Unknown destination identity or request id |
| RNS_ERR_STATE | Wrong lifecycle state (not started, no identity) |
| RNS_ERR_IO | Config or identity file I/O |
| RNS_ERR_INTERNAL | Unexpected internal failure |
| RNS_ERR_TIMEOUT | Event poll timed out |
| RNS_ERR_TRUNCATED | Output buffer too small |
Node
| Function | Notes |
|---|---|
| rns_node_create | Empty path uses in-memory defaults with share_instance off |
| rns_node_start / rns_node_stop | Idempotent |
| rns_node_destroy | Stops if needed, clears callback, invalidates handle |
| rns_node_set_identity | Attach identity before destinations that need one |
| rns_node_pause | Network lost (OnNetworkLost) |
| rns_node_resume | Network available (OnNetworkAvailable) |
| rns_node_refresh_paths | Refresh watched paths, or pass packed 16-byte hashes |
Identity
| Function | Notes |
|---|---|
| rns_identity_generate | New software identity |
| rns_identity_load | Path from operator config. Rejects empty and NUL |
| rns_identity_save | Write identity to path (standard file layout) |
| rns_identity_destroy | Release handle |
| rns_identity_hash | Truncated hash as 32 hex chars |
Destination
| Function | Notes |
|---|---|
| rns_destination_create | App name required. Optional aspects. accepts_links wires inbound links |
| rns_destination_announce | Optional app data |
| rns_destination_hash | 16-byte truncated hash (RNS_HASH_LEN) |
| rns_destination_enable_ratchets | Path required for disk persist. Empty path enables in-memory ratchets |
| rns_destination_enforce_ratchets | Opt-in reject of identity-key ciphertext |
| rns_destination_destroy | Release handle |
| rns_destination_register_request_handler | Bridge path to RNS_EV_REQUEST_INCOMING |
Path and link
| Function | Notes |
|---|---|
| rns_path_request | Requires started node and 16-byte dest hash |
| rns_path_table | Snapshot into caller array. max_hops < 0 means no filter |
| rns_link_open | Outbound link. Waits a bitrate-sized path window, then handshake. Identity must be known from announce |
| rns_link_send | On established link |
| rns_link_send_resource | Transfer bytes as a link resource (optional rncp name) |
| rns_link_close | Teardown |
| rns_link_id | 16-byte link id |
| rns_link_request | Outbound request. Completion via response or failed events |
| rns_request_respond | Answer a pending RNS_EV_REQUEST_INCOMING with raw bytes |
| rns_request_respond_file | NomadNet /file/ response [filename, content] (auto resource when large) |
Events
| Function | Notes |
|---|---|
| rns_event_poll | Blocks up to timeout_ms. Returns RNS_ERR_TIMEOUT if empty |
| rns_set_event_callback | Optional. Drains the same queue. Pass NULL to clear |
| Kind | Meaning |
|---|---|
| RNS_EV_ANNOUNCE | Announce received |
| RNS_EV_LINK_ESTABLISHED | Link up (inbound or outbound) |
| RNS_EV_LINK_FAILED | Open failed or timed out |
| RNS_EV_LINK_DATA | Payload on link |
| RNS_EV_LINK_CLOSED | Link torn down |
| RNS_EV_REQUEST_INCOMING | Inbound request. Call rns_request_respond or rns_request_respond_file |
| RNS_EV_REQUEST_RESPONSE | Outbound request succeeded |
| RNS_EV_REQUEST_FAILED | Outbound request failed or timed out |
| RNS_EV_RESOURCE_STARTED | Inbound resource transfer started |
| RNS_EV_RESOURCE_CONCLUDED | Inbound resource assembled (path may hold rncp name) |
rns_event fields are filled by copy. Set app_data and app_data_cap before poll for variable payloads. Truncation sets app_data_truncated (and path_truncated / error_message_truncated when those strings do not fit).
The per-node queue is bounded. On overflow it drops the oldest event. Poll and callback share that queue. Prefer one consumer style at a time.
ABI rules
- Handles are opaque uint64_t. Destroy them before process exit.
- Never hold Go pointers across the ABI. The facade always copies.
- Paths are operator-chosen. Empty and embedded NUL are rejected.
- Empty config path is valid and means in-memory defaults (no shared-instance bind).
- Incoming request handlers block the link goroutine until respond or a 30s timeout.
Not in this ABI (yet)
- Full Control API surface (sessions, health JSON)
Grow the header only when a real host needs it. Keep RNS_API_VERSION in mind.
Platform artifacts
Build with task build-librns for the host .so, or:
sh scripts/build-librns-targets.sh linux windows darwin android
| Platform | Output |
|---|---|
| Linux | bin/librns.so |
| Windows | bin/windows/amd64/librns.dll |
| macOS | bin/darwin/amd64/librns.dylib or bin/darwin/arm64/librns.dylib |
| Android | bin/android//librns.so |
Embedders should call rns_version() and compare to RNS_API_VERSION from the header they compiled against. Current ABI is 1.5.
Typical flow
rns_node_create("")
rns_identity_generate / rns_identity_load
rns_node_set_identity
rns_node_start
rns_destination_create(..., accepts_links=1)
rns_destination_register_request_handler(dest, "/ping")
rns_destination_announce
peer: rns_event_poll -> RNS_EV_ANNOUNCE
peer: rns_link_open(dest_hash)
rns_event_poll -> RNS_EV_LINK_ESTABLISHED
rns_link_send / RNS_EV_LINK_DATA
rns_link_request / RNS_EV_REQUEST_RESPONSE
rns_link_close / RNS_EV_LINK_CLOSED
rns_node_stop
rns_node_destroy
Testing
| Kind | Where |
|---|---|
| Unit and edge | pkg/librns/*_test.go |
| Facade link integration | TestFacadeLinkOpenSendClose |
| Lifecycle, path table, callback | extended_test.go |
| Property | testing/quick drop-oldest and handle table |
| Fuzz | FuzzHandleTable, FuzzEventQueue, FuzzConfigPathCreate, FuzzValidatePath |
| C smoke | bindings/c/examples/smoke |
| Odin bindings | bindings/odin (task test-odin) |
| Zig bindings | bindings/zig (task test-zig) |
| C++ bindings | bindings/cpp (task test-cpp) |
| Dart FFI | bindings/dart (task test-dart) |
| Rust bindings | bindings/rust (task test-rust) |
| Python bindings | bindings/python (task test-python) |
| Lua bindings | bindings/lua (task test-lua) |
| Swift bindings | bindings/swift (task test-swift) |
| Java bindings | bindings/java (task test-java) |
| Kotlin bindings | bindings/kotlin (task test-kotlin) |
go test ./pkg/librns
task build-librns
make -C bindings/c/examples/smoke && ./bindings/c/examples/smoke/librns-smoke
task test-odin
task test-zig
task test-cpp
task test-dart
task test-rust
task test-python
Odin bindings
Path: bindings/odin/.
Idiomatic wrappers over the same C ABI (foreign import system:rns). Package import uses a collection rooted at bindings/odin:
import rns "rns:rns"
Coverage
| Area | Odin surface |
|---|---|
| Version and errors | version, last_error, error_string, Error |
| Node lifecycle | node_create, node_start, node_stop, node_destroy, node_pause, node_resume, node_set_identity, node_refresh_paths |
| Identity | identity_generate, identity_load, identity_save, identity_destroy, identity_hash, identity_hash_bytes, identity_public_key, identity_from_public_key, identity_sign, identity_verify |
| RSG / RSM | rsg_create, rsg_validate, rsg_sign_file, rsg_verify_file, rsm_verify |
| Destination | destination_create, destination_announce, destination_hash, destroy, request handler register |
| Path / interfaces | path_request, path_table, interfaces_list |
| Link and requests | link_open, link_send, link_close, link_id, link_request, request_respond |
| Events | event_poll, set_event_callback, Destination_Data, helpers for app data and hashes |
| Raw ABI | foreign rns_* procs in bindings/odin/rns/foreign.odin |
Linux only (matches librns.so). Requires Odin on PATH and a built shared library.
Build and test
task build-librns
task test-odin
# or
make -C bindings/odin test
make -C bindings/odin smoke
CI runs the same suite (test-odin job, pinned Odin dev-2026-06).
Tests cover version and node lifecycle, identity and destination helpers, and a live UDP announce, link, and send round trip through librns.so.
Zig bindings
Path: bindings/zig/.
Idiomatic wrappers over the same C ABI (@extern decls in src/c.zig). Import with a Zig package dependency on bindings/zig:
const rns = @import("rns");
Coverage
| Area | Zig surface |
|---|---|
| Version and errors | version, lastError, errorString, Error |
| Node lifecycle | nodeCreate, nodeStart, nodeStop, nodeDestroy, nodePause, nodeResume, nodeSetIdentity, nodeRefreshPaths |
| Identity | identityGenerate, identityLoad, identitySave, identityDestroy, identityHash, identityHashBytes, identityPublicKey, identityFromPublicKey, identitySign, identityVerify |
| RSG / RSM | rsgCreate, rsgValidate, rsgSignFile, rsgVerifyFile, rsmVerify |
| Destination | destinationCreate, destinationAnnounce, destinationHash, destroy, request handler register |
| Path / interfaces | pathRequest, pathTable, interfacesList |
| Link and requests | linkOpen, linkSend, linkClose, linkId, linkRequest, requestRespond |
| Events | eventPoll, setEventCallback, destination_data, helpers for app data and hashes |
| Raw ABI | c.rns_* in bindings/zig/src/c.zig |
Linux only (matches librns.so). Requires Zig 0.16.0 or later on PATH and a built shared library.
Build and test
task build-librns
task test-zig
# or
make -C bindings/zig test
CI runs the same suite (test-zig job, pinned Zig 0.16.0).
Tests cover version and node lifecycle, identity and destination helpers, and a live UDP announce, link, and send round trip through librns.so.
C++ bindings
Path: bindings/cpp/.
Idiomatic C++17 RAII wrappers over the same C ABI (include/rns.h). Include the umbrella header and link librns.so (plus bindings/cpp/src/event.cpp for the event callback trampoline, or the rns_cpp CMake target):
#include <rns/rns.hpp>
auto node_r = rns::Node::create(config_path);
if (!node_r.ok()) {
return 1;
}
auto node = std::move(node_r).value();
Coverage
| Area | C++ surface |
|---|---|
| Version and errors | version, last_error, error_string, Error, Result |
| Node lifecycle | Node::create, start, stop, pause, resume, set_identity, refresh_paths |
| Identity | Identity::generate, load, save, hash, hash_bytes, public_key, from_public_key, sign, verify |
| RSG / RSM | rsg_create, rsg_validate, rsg_sign_file, rsg_verify_file, rsm_verify |
| Destination | Destination::create, announce, hash, register_request_handler |
| Path / interfaces | path_request, path_table, interfaces_list |
| Link and requests | Link::open, send, close, id, request, request_respond |
| Events | Node::poll, set_event_callback, DestinationData, Event accessors |
Linux only (matches librns.so). Requires CMake and a C++17 compiler, plus a built shared library.
Build and test
task build-librns
task test-cpp
# or
make -C bindings/cpp test
CI runs the same suite (test-cpp job).
Tests cover version and node lifecycle, identity and destination helpers, and a live UDP announce, link, and send round trip through librns.so.
Dart FFI bindings
Path: bindings/dart/ (import package:rns_control/ffi.dart).
In-process dart:ffi wrappers over the same C ABI. First platforms: Linux desktop, Android (arm64-v8a, armeabi-v7a, x86_64), and Windows amd64.
import 'package:rns_control/ffi.dart';
final rns = Rns();
final node = rns.nodeCreate();
rns.nodeStart(node);
Artifacts
| Platform | Output |
|---|---|
| Linux | bin/librns.so |
| Android | bin/android//librns.so |
| Windows | bin/windows/amd64/librns.dll |
task build-librns
task build-librns-targets -- linux android windows
task test-dart
Android builds need an NDK (ANDROID_NDK_HOME). Windows cross-builds need mingw-w64 or Zig (scripts/cc-windows-zig.sh). Flutter apps copy Android ABIs into jniLibs and ship librns.dll beside the Windows runner.
For out-of-process Dart or Flutter without shipping native code, use the Control API client in the same package.
Rust bindings
Path: bindings/rust/.
Safe Rust wrappers over the same C ABI (extern "C" in src/ffi.rs). Link librns.so via build.rs / RNS_LIB_DIR.
use rns::{version, Identity, Node, API_VERSION};
assert_eq!(version(), API_VERSION);
Coverage
| Area | Rust surface |
|---|---|
| Version and errors | version, last_error, Error, Result |
| Node lifecycle | Node::create, start, stop, set_identity, pause, resume, event_poll |
| Identity | generate, load, save, hash_hex, hash_bytes, public_key, from_public_key, sign, verify |
| RSG / RSM | rsg_create, rsg_validate, rsm_verify |
| Interfaces | interfaces_list |
task build-librns
task test-rust
# or
make -C bindings/rust test
Python bindings
Path: bindings/python/.
ctypes wrappers over the same C ABI. Set RNS_LIB_PATH or place bin/librns.so on the default search path.
import rns
assert rns.version() == rns.API_VERSION
Coverage
| Area | Python surface |
|---|---|
| Version and errors | version, last_error, Error, map_code |
| Node lifecycle | Node.create, start, stop, set_identity, pause, resume, event_poll |
| Identity | generate, load, save, hash_hex, hash_bytes, public_key, from_public_key, sign, verify |
| RSG / RSM | rsg_create, rsg_validate, rsm_verify |
| Interfaces | interfaces_list |
task build-librns
task test-python
# or
make -C bindings/python test
Lua bindings
Path: bindings/lua/.
LuaJIT FFI wrappers over the same C ABI. Requires LuaJIT (ffi). Set RNS_LIB_PATH or place bin/librns.so on the default search path.
local rns = require("rns")
assert(rns.version() == rns.API_VERSION)
task build-librns
task test-lua
# or
make -C bindings/lua test
Swift bindings
Path: bindings/swift/.
SwiftPM library over the same C ABI (Sources/CRNS system module + idiomatic RNS target). Link librns via Makefile -Xlinker flags / LD_LIBRARY_PATH.
import RNS
assert(version() == API_VERSION)
task build-librns
task test-swift
# or
make -C bindings/swift test
Java bindings
Path: bindings/java/.
JNA wrappers over the same C ABI (io.quad4.rns). Downloads jna.jar on first build. Set RNS_LIB_PATH or place bin/librns.so on the default search path.
import io.quad4.rns.Rns;
assert Rns.version().equals(Rns.API_VERSION);
task build-librns
task test-java
# or
make -C bindings/java test
Kotlin bindings
Path: bindings/kotlin/.
Kotlin facade over the Java JNA bindings (io.quad4.rns.kotlin). Requires kotlinc and the Java package.
import io.quad4.rns.kotlin.RnsKt
check(RnsKt.version() == RnsKt.API_VERSION)
task build-librns
task test-kotlin
# or
make -C bindings/kotlin test