# C FFI Bindings

The `quicproquo-ffi` crate provides a synchronous C API for the quicproquo
messaging client. It wraps the async `quicproquo-client` library behind an
opaque handle, so C, Python, Swift, or any language with C FFI support can
connect, authenticate, send messages, and receive messages.

Each `QpqHandle` owns a Tokio runtime internally; FFI functions use
`runtime.block_on()` to bridge synchronous C callers to the async Rust
internals.

## Building

```bash
# Shared library (.so / .dylib / .dll) + static archive (.a)
cargo build --release -p quicproquo-ffi
```

Output:

| Platform | Shared library                              | Static library                          |
|----------|---------------------------------------------|-----------------------------------------|
| Linux    | `target/release/libquicproquo_ffi.so`       | `target/release/libquicproquo_ffi.a`    |
| macOS    | `target/release/libquicproquo_ffi.dylib`    | `target/release/libquicproquo_ffi.a`    |
| Windows  | `target/release/quicproquo_ffi.dll`         | `target/release/quicproquo_ffi.lib`     |

## Status Codes

| Code | Constant           | Meaning                                  |
|------|--------------------|------------------------------------------|
| 0    | `QPQ_OK`           | Success                                  |
| 1    | `QPQ_ERROR`        | Generic error (check `qpq_last_error`)   |
| 2    | `QPQ_AUTH_FAILED`  | OPAQUE authentication failed             |
| 3    | `QPQ_TIMEOUT`      | Receive timed out with no messages       |
| 4    | `QPQ_NOT_CONNECTED`| Handle is null or not logged in          |

## C API Reference

All functions use the `extern "C"` calling convention. All string parameters
must be valid, non-null, null-terminated UTF-8. The opaque handle type is
`QpqHandle *`.

### `qpq_connect`

```c
QpqHandle *qpq_connect(
    const char *server,       /* "host:port", e.g. "127.0.0.1:7000" */
    const char *ca_cert,      /* path to CA certificate file (DER) */
    const char *server_name   /* TLS server name, e.g. "localhost" */
);
```

Creates a Tokio runtime, performs a health check against the server, and
returns a heap-allocated opaque handle. Returns `NULL` on failure (invalid
arguments, server unreachable, or runtime creation failed).

### `qpq_login`

```c
int32_t qpq_login(
    QpqHandle *handle,        /* handle from qpq_connect */
    const char *username,     /* OPAQUE username */
    const char *password      /* OPAQUE password */
);
```

Authenticates with the server using OPAQUE (password-authenticated key
exchange). On success the handle is marked as logged-in and subsequent
`qpq_send`/`qpq_receive` calls use the authenticated session.

**Returns:** `QPQ_OK` on success, `QPQ_AUTH_FAILED` on bad credentials,
`QPQ_NOT_CONNECTED` if the handle is null, or `QPQ_ERROR` on other failures.

### `qpq_send`

```c
int32_t qpq_send(
    QpqHandle *handle,        /* handle from qpq_connect */
    const char *recipient,    /* recipient username (null-terminated) */
    const uint8_t *message,   /* message bytes (UTF-8, not null-terminated) */
    size_t message_len        /* length of message in bytes */
);
```

Resolves the recipient by username, then sends an MLS-encrypted message
through the server. The `message` buffer must contain valid UTF-8 of at least
`message_len` bytes. The handle must be logged in.

**Returns:** `QPQ_OK` on success, `QPQ_NOT_CONNECTED` if not logged in, or
`QPQ_ERROR` on failure (recipient not found, network error, etc.).

### `qpq_receive`

```c
int32_t qpq_receive(
    QpqHandle *handle,        /* handle from qpq_connect */
    uint32_t timeout_ms,      /* maximum wait time in milliseconds */
    char **out_json           /* output: heap-allocated JSON string */
);
```

Blocks up to `timeout_ms` milliseconds waiting for pending messages. On
success, `*out_json` points to a null-terminated JSON string containing an
array of decrypted message strings (e.g., `["hello","world"]`). The caller
**must** free this string with `qpq_free_string`.

**Returns:** `QPQ_OK` on success (even if the array is empty),
`QPQ_TIMEOUT` if the wait expires with no messages, `QPQ_NOT_CONNECTED` if
not logged in, or `QPQ_ERROR` on failure.

### `qpq_disconnect`

```c
void qpq_disconnect(QpqHandle *handle);
```

Shuts down the Tokio runtime and frees the handle. After this call, the
handle must not be used again. Passing `NULL` is a safe no-op.

### `qpq_last_error`

```c
const char *qpq_last_error(const QpqHandle *handle);
```

Returns the last error message recorded on the handle, or `NULL` if no error
has occurred. The returned pointer is valid **only** until the next FFI call
on the same handle. Do **not** free this pointer -- it is owned by the handle.

### `qpq_free_string`

```c
void qpq_free_string(char *ptr);
```

Frees a string previously returned by `qpq_receive` via the `out_json`
output parameter. Passing `NULL` is a safe no-op. Do **not** use this to
free strings from `qpq_last_error`.

## Memory Management Rules

1. **`QpqHandle`** is heap-allocated by `qpq_connect` and freed by
   `qpq_disconnect`. Do not use the handle after disconnecting.
2. **`out_json` from `qpq_receive`** is heap-allocated. Free it with
   `qpq_free_string`.
3. **`qpq_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.
4. All `const char *` input parameters are borrowed for the duration of the
   call and not stored beyond it.

## Error Handling Pattern

Every function that returns `int32_t` uses the status codes above. The
recommended pattern is:

```c
int rc = qpq_login(handle, "alice", "password123");
if (rc != QPQ_OK) {
    const char *err = qpq_last_error(handle);
    fprintf(stderr, "login failed (code %d): %s\n", rc, err ? err : "unknown");
    qpq_disconnect(handle);
    return 1;
}
```

## Example: C Usage

```c
#include <stdio.h>
#include <string.h>

/* Link with: -lquicproquo_ffi -lpthread -ldl -lm */

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 *, unsigned long);
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 *);

#define QPQ_OK 0

int main(void) {
    QpqHandle *h = qpq_connect("127.0.0.1:7000", "server-cert.der", "localhost");
    if (!h) {
        fprintf(stderr, "connection failed\n");
        return 1;
    }

    if (qpq_login(h, "alice", "secret") != QPQ_OK) {
        fprintf(stderr, "login failed: %s\n", qpq_last_error(h));
        qpq_disconnect(h);
        return 1;
    }

    /* Send a message */
    const char *msg = "hello from C";
    if (qpq_send(h, "bob", (const unsigned char *)msg, strlen(msg)) != QPQ_OK) {
        fprintf(stderr, "send failed: %s\n", qpq_last_error(h));
    }

    /* Receive messages (5 second timeout) */
    char *json = NULL;
    int rc = qpq_receive(h, 5000, &json);
    if (rc == QPQ_OK && json) {
        printf("received: %s\n", json);
        qpq_free_string(json);
    }

    qpq_disconnect(h);
    return 0;
}
```

Compile and link:

```bash
gcc -o qpq_demo qpq_demo.c -L target/release -lquicproquo_ffi -lpthread -ldl -lm
LD_LIBRARY_PATH=target/release ./qpq_demo
```

## Python Bindings

A ready-made Python `ctypes` wrapper is provided in
[`examples/python/qpq_client.py`](https://github.com/nickvidal/quicproquo/tree/main/examples/python).

```bash
# Build the FFI library first
cargo build --release -p quicproquo-ffi

# Run the Python client
python examples/python/qpq_client.py \
    --server 127.0.0.1:7000 \
    --ca-cert server-cert.der \
    --username alice --password secret \
    --receive --timeout 5000
```

Set `QPQ_FFI_LIB=/path/to/libquicproquo_ffi.so` to override automatic
library discovery.
