Rename all project references from quicproquo/qpq to quicprochat/qpc across documentation, Docker configuration, CI workflows, packaging scripts, operational configs, and build tooling. - Docker: crate paths, binary names, user/group, data dirs, env vars - CI: workflow crate references, binary names, artifact names - Docs: all markdown files under docs/, SDK READMEs, book.toml - Packaging: OpenWrt Makefile, init script, UCI config (file renames) - Scripts: justfile, dev-shell, screenshot, cross-compile, ai_team - Operations: Prometheus config, alert rules, Grafana dashboard - Config: .env.example (QPQ_* → QPC_*), CODEOWNERS paths - Top-level: README, CONTRIBUTING, ROADMAP, CLAUDE.md
3.9 KiB
3.9 KiB
C FFI Bindings
The C FFI layer (crates/quicprochat-ffi/) provides synchronous C-callable functions that wrap the Rust client library. This is the foundation for language bindings in Python (CFFI), Swift, Kotlin/JNI, Java, and Ruby.
Building
cargo build --release -p quicprochat-ffi
This produces:
- Linux:
target/release/libquicprochat_ffi.so - macOS:
target/release/libquicprochat_ffi.dylib - Windows:
target/release/quicprochat_ffi.dll
API
Status Codes
#define QPC_OK 0
#define QPC_ERROR 1
#define QPC_AUTH_FAILED 2
#define QPC_TIMEOUT 3
#define QPC_NOT_CONNECTED 4
Functions
// Connect to a server. Returns opaque handle or NULL on failure.
QpqHandle* qpc_connect(
const char* server, // "host:port"
const char* ca_cert, // path to CA certificate PEM
const char* server_name // TLS SNI name
);
// Authenticate with OPAQUE. Returns status code.
int qpc_login(
QpqHandle* handle,
const char* username,
const char* password
);
// Send a message to a recipient (by username).
int qpc_send(
QpqHandle* handle,
const char* recipient, // recipient username
const uint8_t* message, // message bytes
size_t message_len
);
// Receive pending messages. Blocks up to timeout_ms.
// On success, *out_json is a JSON array of strings.
int qpc_receive(
QpqHandle* handle,
uint32_t timeout_ms,
char** out_json // caller must free with qpc_free_string
);
// Disconnect and free the handle.
void qpc_disconnect(QpqHandle* handle);
// Get last error message (valid until next FFI call on this handle).
const char* qpc_last_error(const QpqHandle* handle);
// Free a string returned by qpc_receive.
void qpc_free_string(char* ptr);
Usage Example (C)
#include <stdio.h>
#include <string.h>
// Forward declarations (or include a header).
typedef struct QpqHandle QpqHandle;
extern QpqHandle* qpc_connect(const char*, const char*, const char*);
extern int qpc_login(QpqHandle*, const char*, const char*);
extern int qpc_send(QpqHandle*, const char*, const unsigned char*, size_t);
extern int qpc_receive(QpqHandle*, unsigned int, char**);
extern void qpc_disconnect(QpqHandle*);
extern const char* qpc_last_error(const QpqHandle*);
extern void qpc_free_string(char*);
int main(void) {
QpqHandle* h = qpc_connect("127.0.0.1:5001", "ca.pem", "localhost");
if (!h) {
fprintf(stderr, "connect failed\n");
return 1;
}
int rc = qpc_login(h, "alice", "password123");
if (rc != 0) {
fprintf(stderr, "login failed: %s\n", qpc_last_error(h));
qpc_disconnect(h);
return 1;
}
const char* msg = "hello from C!";
qpc_send(h, "bob", (const unsigned char*)msg, strlen(msg));
char* json = NULL;
rc = qpc_receive(h, 5000, &json);
if (rc == 0 && json) {
printf("received: %s\n", json);
qpc_free_string(json);
}
qpc_disconnect(h);
return 0;
}
Compile with:
gcc -o demo demo.c -L target/release -lquicprochat_ffi
Memory Management
qpc_connectreturns a heap-allocated handle. The caller must callqpc_disconnectto free it.qpc_receivewrites a heap-allocated JSON string to*out_json. The caller must callqpc_free_stringto free it.qpc_last_errorreturns a pointer owned by the handle. Do not free it. It is valid until the next FFI call on the same handle.
Thread Safety
Each QpqHandle owns its own Tokio runtime. Concurrent calls on the same handle are not safe. Create separate handles for concurrent use.
Internals
The FFI layer bridges synchronous C callers to the async Rust client:
C caller ─── qpc_login() ───► QpqHandle ─── runtime.block_on() ───► async Rust client
Each handle contains:
- A Tokio runtime for blocking on async operations
- Server connection parameters
- Login state and error buffer