# 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 ```sh 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 ```c #define QPC_OK 0 #define QPC_ERROR 1 #define QPC_AUTH_FAILED 2 #define QPC_TIMEOUT 3 #define QPC_NOT_CONNECTED 4 ``` ### Functions ```c // 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) ```c #include #include // 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: ```sh 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