Use the C ABI (FFI)
citadel-client-ffi (crates/citadel-client-ffi) exposes a small, stable C
ABI over the Rust citadel-client SDK so native code
(including game engines) can consume Citadel without reimplementing the protocol.
The header crates/citadel-client-ffi/include/citadel_client.h is the source of
truth and is committed for consumers.
For the full surface, see the C ABI reference.
Build the library
Section titled “Build the library”cargo build -p citadel-client-ffi --release# Produces, under target/release/:# libcitadel_client_ffi.{dylib,so} or citadel_client_ffi.dll (cdylib)# libcitadel_client_ffi.a or .lib (staticlib)The header is regenerated on build into the include/ directory (a committed copy
is provided).
Minimal C example
Section titled “Minimal C example”#include "citadel_client.h"#include <stdio.h>
int main(void) { CitadelClient *c = NULL; if (citadel_client_connect_websocket("ws://127.0.0.1:7352/", &c) != CITADEL_STATUS_OK) return 1;
const unsigned char body[] = {0, 0, 0, 0, 0, 0, 0, 0}; // e.g. a position citadel_client_send(c, /*kind*/ 1, body, sizeof(body), /*reliable*/ true);
unsigned short kind; unsigned char buf[256]; size_t len; bool trunc; for (;;) { CitadelStatus s = citadel_client_poll(c, &kind, buf, sizeof(buf), &len, &trunc); if (s == CITADEL_STATUS_OK) { /* handle a relayed peer message of `len` bytes (kind == 2) */ } else if (s == CITADEL_STATUS_AGAIN) { /* nothing ready: sleep briefly, then continue */ } else { break; /* DISCONNECTED or an error */ } } citadel_client_free(c); return 0;}For QUIC, use citadel_client_connect_quic(addr, server_name, insecure, &c).
Set insecure = false in production to validate a public CA certificate and
the hostname; reserve insecure = true for the local self-signed cert.
- Poll-based receive. No callbacks cross the FFI boundary. Call
citadel_client_pollfrom your main loop. It returnsAGAINwhen nothing is ready andDISCONNECTEDwhen closed and drained. - Caller-owned buffers. Every byte/string transfer is pointer + length into
caller-provided buffers. The FFI never returns Rust-allocated buffers you must
separately free.
sendcopies your bytes;pollcopies the payload into yourbufand setsout_truncatedif it did not fit (retry with a larger buffer).out_lenis always the full payload length. - One owned handle. The handle from a
connect_*call is the only Rust-allocated object crossing the boundary. Callcitadel_client_freeexactly once; passingnullis a no-op. - Panic-safe. Every entrypoint catches panics and maps them to
CITADEL_STATUS_INTERNAL, so no Rust panic unwinds across C. - ABI versioned. Check
citadel_client_abi_versionagainstCITADEL_FFI_ABI_VERSIONto guard against mismatch.
Error details
Section titled “Error details”After a non-OK status, call citadel_client_last_error(handle, buf, cap) to copy
a NUL-terminated message into your buffer. It returns the number of bytes written
including the NUL.