Skip to content

FFI Binding

github-actions[bot] edited this page Aug 27, 2026 · 1 revision

FFI Binding (C ABI)

Enable the ffi feature and the crate exports a stable extern "C" surface you can call from Kotlin (JNI), Swift, C#, Go, or plain C. It is the same engine — same code, same semantics — behind a C bridge.

[dependencies]
typebit = { version = "0.1", features = ["ffi"] }
cargo run --example ffi_demo --features ffi

The callback table: HostCbs

typebit_engine_new takes a pointer to a #[repr(C)] callback table. Every field is a C function pointer; the 18 required callbacks must be non-null or the engine constructor returns NULL (it validates the whole table before first use). resolve_host is optional (may be NULL — the DHT then stays dormant; HTTP/UDP trackers are unaffected).

typedef struct {
    void *ctx;                                    // your context, passed back
    uint64_t (*now_ms)(void *);
    void (*fill_random)(void *, uint8_t *, size_t);
    void (*log)(void *, int level, const char *msg);
    int  (*http_get)(void *, const char *url, uint64_t timeout_ms,
                     uint8_t *out, size_t cap, size_t *written);
    int  (*tcp_connect)(void *, const uint8_t *ip4, uint16_t port, uint32_t *out_id);
    int  (*tcp_connect_done)(void *, uint32_t id);
    int  (*tcp_send)(void *, uint32_t id, const uint8_t *data, size_t len, size_t *sent);
    int  (*tcp_recv)(void *, uint32_t id, uint8_t *buf, size_t cap, size_t *got);
    void (*tcp_close)(void *, uint32_t id);
    int  (*udp_open)(void *, uint16_t port);
    int  (*udp_send)(void *, const uint8_t *ip4, uint16_t port, const uint8_t *data, size_t len);
    int  (*udp_recv)(void *, uint8_t *buf, size_t cap, uint8_t *ip4, uint16_t *port, size_t *got);
    int  (*disk_open)(void *, const char *path);
    int  (*disk_read)(void *, uint32_t id, uint64_t off, uint8_t *buf, size_t cap, size_t *got);
    int  (*disk_write)(void *, uint32_t id, uint64_t off, const uint8_t *data, size_t len);
    int  (*disk_prealloc)(void *, uint32_t id, uint64_t size);
    int  (*disk_flush)(void *, uint32_t id);
    void (*disk_close)(void *, uint32_t id);
    int  (*resolve_host)(void *, const char *host, uint16_t port, uint8_t *out_ip4, uint16_t *out_port); /* optional */
} HostCbs;

Return codes: FFI_OK = 0, FFI_ERR = -1, FFI_WOULD_BLOCK = -2, FFI_NOT_FOUND = -3, FFI_INVALID = -4. The engine treats FFI_WOULD_BLOCK from tcp_recv/udp_recv exactly like Err(Error::WouldBlock) — do not block in the callbacks.

Engine functions

C function Notes
typebit_engine_new(cbs, listen_port, cache_bytes, dht_enabled) -> TypeBitEngine* NULL on bad table. dht_enabled is 0/1.
typebit_engine_free(e) destroy the handle
typebit_engine_add_torrent(e, data, len, save_dir, out_hash, out_hash_len) -> int writes 20/32-byte infohash
typebit_engine_add_magnet(e, uri, save_dir, out_hash, out_hash_len) -> int same
typebit_engine_start(e, hash, hash_len) -> int hash_len 20 (v1) or 32 (v2)
typebit_engine_pause(e, hash, hash_len) -> int
typebit_engine_resume(e, hash, hash_len) -> int
typebit_engine_remove(e, hash, hash_len) -> int
typebit_engine_progress(e, hash, hash_len, double *out) -> int 0.0..=1.0
typebit_engine_tick(e) -> int call on a ~100 ms timer
typebit_engine_take_event(e, out, out_cap) -> int 1 = event written, 0 = none

typebit_engine_take_event returns one event per call; call it in a loop until it returns 0. The engine buffers internally, so events are never dropped.

Event wire format

[kind:u8] followed by per-kind payload (all integers big-endian):

kind event payload
1 PeerConnected 32-byte infohash + 4-byte IPv4 + 2-byte port
2 PieceVerified infohash + 4-byte piece
3 HashFailure infohash + 4-byte piece
4 TorrentComplete infohash
5 MetadataComplete infohash
6 MetadataFailed infohash
7 TrackerAnnounced infohash + 4-byte peer count
8 DhtNodeCount 4-byte count
9 PeerBanned infohash + 4-byte IPv4 + 2-byte port + reason code
10 PortMapping phase byte + 2-byte external port (0 = unknown)
11 Error code byte + NUL-terminated detail string

Minimal C example

#include <stdint.h>
#include <stdio.h>
#include <string.h>

/* forward declarations of your callbacks; all 18 required ones */
uint64_t my_now_ms(void *ctx) { /* ... */ return 0; }
/* ... fill_random, log, http_get, tcp_*, udp_*, disk_* ... */

int main(void) {
    HostCbs cbs = {0};
    cbs.ctx = NULL;
    cbs.now_ms = my_now_ms;
    /* ... assign every required callback ... */

    TypeBitEngine *e = typebit_engine_new(&cbs, 6881, 256 * 1024 * 1024, 1);
    if (!e) { fprintf(stderr, "bad callback table\n"); return 1; }

    uint8_t hash[32]; size_t hash_len = 0;
    if (typebit_engine_add_magnet(e,
        "magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567",
        "/tmp/dl", hash, &hash_len) != 0) { /* ... */ }
    typebit_engine_start(e, hash, hash_len);

    for (;;) {
        typebit_engine_tick(e);
        uint8_t ev[64];
        while (typebit_engine_take_event(e, ev, sizeof(ev)) == 1) {
            printf("event kind=%d\n", ev[0]);
        }
        /* sleep ~100 ms */
    }
    typebit_engine_free(e);
    return 0;
}

See examples/ffi_demo.rs for a complete self-testing C ABI harness.

Clone this wiki locally