Offline Protocol C API
The Offline Protocol C API is the SDK’s local API called in process. Instead of one C function per SDK method, it gives you about a dozen functions: offline_protocol_create builds an engine from a JSON configuration, offline_protocol_call calls any method of the interface definition by name with a JSON object of parameters, and results and events come back as JSON text.
That shape was chosen for languages other than C. A typed C API would be about 220 functions and 46 structures with nested ownership. A JSON surface stays the same size as the SDK grows, because the dispatch is generated from the interface definition, so a Go or C# package written against these functions keeps working across releases. Your program reads results and events with the JSON library it already uses.
Header and shared libraries per OS
The header and the shared library come in every desktop archive on a GitHub release; the header is also in bindings/c/include in the SDK repository.
| OS | Shared library |
|---|---|
| Linux x86_64 and aarch64 | liboffline_protocol_uniffi.so |
| macOS arm64 | liboffline_protocol_uniffi.dylib |
| Windows x86_64 | offline_protocol_uniffi.dll, with its import library offline_protocol_uniffi.dll.lib |
The iOS XCFramework in the same release carries the same C functions under the same header. The Android libraries do not: the C binding is off in the Android builds.
Linking a C program against the shared library takes one line:
cc -I include app.c -L <libdir> -loffline_protocol_uniffi -Wl,-rpath,<libdir>
Releases do not ship a static archive, which is over ten times the size of the shared library. To build one, along with the shared library, from source:
cargo build --release -p offline-protocol-uniffi
# target/release/liboffline_protocol_uniffi.{so,dylib,a}
# (the static archive may land in target/release/deps/)
Linking it needs the system libraries rustc names, which this prints:
cargo rustc --release -p offline-protocol-uniffi --lib -- --print native-static-libs
Calling SDK methods with JSON
Method names, parameter names and the JSON form of every type are the local API’s, so its method table is the reference for what you can call. A send from the C quick start looks like this:
if (offline_protocol_call(engine, "send_message",
"{\"recipient\":\"off1...\",\"content\":\"hi\",\"priority\":\"Medium\"}",
&result, &err) == OfflineProtocolStatus_Ok) {
printf("queued %s\n", result); /* the message id */
offline_protocol_string_free(result);
} else {
fprintf(stderr, "send: %s (%d) %s\n", err.variant ? err.variant : "-", err.code, err.message);
offline_protocol_error_clear(&err);
}
The naming rules are short:
- Engine methods are bare (
send_message).MeshServicesmethods carryservices.andDataStoremethods carrydata.. - Parameters are a JSON object keyed by the definition’s parameter names;
NULLmeans none. An unknown key is refused, and an omitted field takes the definition’s default. - Bytes are padded standard base64, and enums are spelled as the definition spells them, such as
"WiFiDirect"or"Medium". - An engine refusal sets
err.variantto the error’s variant name anderr.codeto its position.
Every string the library returns is freed with offline_protocol_string_free, and nothing else is. Every entry point catches a panic and answers OfflineProtocolStatus_Internal instead of aborting your process. A few declarations take a callback and have their own functions: the event callback, the four transport notifications, initialize_mls with your own storage providers, and the storage conformance suite.
C++, Go, C# and Zig
Any language that calls C uses the API the same way: a thin wrapper binds the few functions and passes JSON strings.
- C++. Include the header directly; it compiles as C++. Catch exceptions inside callbacks, because a callback must not unwind into the library.
- Go. Call the functions through cgo. There is also the community generator
uniffi-bindgen-go, in a release that targets the UniFFI version the SDK pins; it binds UniFFI’s own ABI rather than the C API. - C#. Declare the functions for P/Invoke and load the shared library for the host OS.
- Zig. Import the header and link the library like any C library.
The same contract applies in each. A callback runs synchronously on whichever thread is inside the engine, must be safe from any thread and must not call back into the engine. Every function that takes a callback also takes a drop function and a ctx, and the library calls drop(ctx) exactly once, even when it refuses the registration, so the host never frees ctx itself. Instead of a callback you can poll with offline_protocol_poll_event; the queue keeps the newest 1,000 events.
Java has its own typed binding that calls the library through the foreign function API instead, and Rust can use the crates directly.
Host responsibilities with the C API
With the C API your process is the platform: the engine handles routing, encryption and delivery, and your code owns the radios, sockets and files:
- Transports. Drain each transport’s outbound queue through the
ble_,internet_,wifi_direct_,reticulum_andnostr_methods, perform the I/O, report the outcome and inject what you receive. The transport callbacks say when a queue has frames; the internet transport has none and is polled. - Storage. Write two storage providers in C, or use the built-in file stores through
initialize_mls_with_file_stores, which needs no callbacks. Prove a provider withoffline_protocol_run_storage_conformance. - Lifecycle. Call
start,stop, andprocesson a timer. For telemetry the binding fillsos, and you passos_majorand report backgrounding withnotify_app_state. - One engine per handle, freed once, with no call in flight when it is.
For transports ready-made on desktop and server hosts, the Python package ships the bridges. A program in any language can also share one engine run by the Python package’s offline-protocol-service, over JSON-RPC 2.0 on a WebSocket, or call a service on another device with an ordinary HTTP request.
C binding testing in CI
The C API is checked from C on every change. CI compiles two complete programs with -std=c99 -Wpedantic -Werror and runs them: main.c exercises the contract with a storage provider written in C against both the shared and the static library, and exchange.c runs two engines with the host carrying frames between them and forming an MLS session. The quick start in the README is extracted, compiled as written and run on Linux and macOS, and on Windows under cl -W4 -WX. The two programs also run under AddressSanitizer and UBSan, so a string freed twice or with the wrong free is reported where it happened.
Before a release publishes, it builds the quick start beside each shipped library and header, with the archive’s own link line, on macOS, Linux and Windows. The SDK is AGPL-3.0-only, with a commercial licence for proprietary use.