Covers all official SDKs (Rust, Go, Python, TypeScript, C FFI), the v2 wire format with method ID tables, authentication flow, and a build-your-own-SDK guide with implementation checklist.
3.9 KiB
3.9 KiB
C FFI Bindings
The C FFI layer (crates/quicproquo-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 quicproquo-ffi
This produces:
- Linux:
target/release/libquicproquo_ffi.so - macOS:
target/release/libquicproquo_ffi.dylib - Windows:
target/release/quicproquo_ffi.dll
API
Status Codes
#define QPQ_OK 0
#define QPQ_ERROR 1
#define QPQ_AUTH_FAILED 2
#define QPQ_TIMEOUT 3
#define QPQ_NOT_CONNECTED 4
Functions
// Connect to a server. Returns opaque handle or NULL on failure.
QpqHandle* qpq_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 qpq_login(
QpqHandle* handle,
const char* username,
const char* password
);
// Send a message to a recipient (by username).
int qpq_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 qpq_receive(
QpqHandle* handle,
uint32_t timeout_ms,
char** out_json // caller must free with qpq_free_string
);
// Disconnect and free the handle.
void qpq_disconnect(QpqHandle* handle);
// Get last error message (valid until next FFI call on this handle).
const char* qpq_last_error(const QpqHandle* handle);
// Free a string returned by qpq_receive.
void qpq_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* qpq_connect(const char*, const char*, const char*);
extern int qpq_login(QpqHandle*, const char*, const char*);
extern int qpq_send(QpqHandle*, const char*, const unsigned char*, size_t);
extern int qpq_receive(QpqHandle*, unsigned int, char**);
extern void qpq_disconnect(QpqHandle*);
extern const char* qpq_last_error(const QpqHandle*);
extern void qpq_free_string(char*);
int main(void) {
QpqHandle* h = qpq_connect("127.0.0.1:5001", "ca.pem", "localhost");
if (!h) {
fprintf(stderr, "connect failed\n");
return 1;
}
int rc = qpq_login(h, "alice", "password123");
if (rc != 0) {
fprintf(stderr, "login failed: %s\n", qpq_last_error(h));
qpq_disconnect(h);
return 1;
}
const char* msg = "hello from C!";
qpq_send(h, "bob", (const unsigned char*)msg, strlen(msg));
char* json = NULL;
rc = qpq_receive(h, 5000, &json);
if (rc == 0 && json) {
printf("received: %s\n", json);
qpq_free_string(json);
}
qpq_disconnect(h);
return 0;
}
Compile with:
gcc -o demo demo.c -L target/release -lquicproquo_ffi
Memory Management
qpq_connectreturns a heap-allocated handle. The caller must callqpq_disconnectto free it.qpq_receivewrites a heap-allocated JSON string to*out_json. The caller must callqpq_free_stringto free it.qpq_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 ─── qpq_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