Files
quicproquo/docs/sdk/c-ffi.md
Christian Nennemann 2e081ead8e chore: rename quicproquo → quicprochat in docs, Docker, CI, and packaging
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
2026-03-21 19:14:06 +01:00

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_connect returns a heap-allocated handle. The caller must call qpc_disconnect to free it.
  • qpc_receive writes a heap-allocated JSON string to *out_json. The caller must call qpc_free_string to free it.
  • qpc_last_error returns 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