Skip to main content

Module ffi

Module ffi 

Source
Expand description

C FFI bindings for cross-language integration.

This module provides a C-compatible API for using Net from other languages (Python, Node.js, Go, etc.).

§Safety

All public FFI functions in this module accept raw pointers from C code. Each is declared pub unsafe extern "C" fn so the unsafety is explicit at the type level; the module-wide contract callers must uphold is:

  • Pointers are valid and properly aligned
  • Opaque handle pointers (*mut T) were produced by this crate’s matching constructor (Box::into_raw inside the FFI surface). Foreign-allocated pointers, even if valid and aligned, will UB when consumed by Box::from_raw in the corresponding _free.
  • String pointers point to valid UTF-8 data
  • Buffer sizes are accurate
  • Handles are not used after net_shutdown

The per-function # Safety rustdoc is intentionally suppressed at the module level — every entry point shares the same contract and the module doc-comment above (plus include/README.md) is the source of truth. Adding individual # Safety blocks would duplicate the same wording 200 times without adding signal.

§Thread Safety

All FFI functions are thread-safe. The event bus handle can be shared across threads.

§Tokio runtime restriction

Internal FFI ops (net_poll, net_flush, net_shutdown, net_redex_*, net_mesh_new, the cortex FFI, the mesh FFI) drive the bus’s tokio runtime via Runtime::block_on. That function panics with “Cannot start a runtime from within a runtime” if the calling thread is already inside a tokio runtime context. The functions are extern "C", so a panic unwinds across the FFI boundary into C / Go-cgo / Python / NAPI — undefined behavior.

The common-case C / Go / Python caller has no Rust tokio runtime, so this is unreachable for them. The narrow path is:

  • A Rust caller loads the cdylib and calls these functions from inside its own #[tokio::main] (or any thread that has called Runtime::enter()).
  • A non-Rust caller embeds a Rust library that runs its own tokio runtime and forwards calls into this cdylib on the same thread.

Both forms are unusual but reachable. Do not call any FFI op from a thread that already holds a tokio runtime context. If you must, spawn the FFI call on a fresh OS thread that doesn’t carry a runtime guard, or wrap the call with tokio::task::spawn_blocking(|| net_xxx(...)) to escape the worker pool.

net_init (mod.rs:284-316) hardens against this for runtime construction; the steady-state ops do not, since the cost of a Handle::try_current() check on every poll would be measurable for the common path that doesn’t hit the bug.

§catch_unwind + caller-held locks

Several FFI entries (net_blob_publish, net_blob_resolve, net_*_wait_for_token) wrap their body in std::panic::catch_unwind(AssertUnwindSafe(...)) so a panic during the call returns a typed NET_ERR_BLOB_PANIC / NET_ERR_PANIC code rather than unwinding across the FFI boundary. That stops the substrate-side undefined behavior, but it does NOT make the wrapped code transparently panic-safe from the caller’s perspective.

If the caller invokes an FFI op while holding an OS-level lock, a sync.Mutex (Go), threading.Lock (Python), or any other mutex with poisoning semantics, and the FFI body panics, the mutex is left in a poisoned state. Subsequent acquires on the same mutex by the caller observe the poisoning and either error (Rust parking_lot with poison_on_unwind) or deadlock (Go’s sync.Mutex doesn’t poison; the caller has observed a return value that may not reflect the state of the FFI op).

Recommended caller pattern: do not hold a caller-side lock across an FFI call. Acquire the lock, prepare the inputs, release the lock, then call the FFI. Re-acquire if you need to update caller state with the result.

The hazard is documented per-binding in:

  • Python: bindings/python/README.md (caller-mutex notes)
  • Node: bindings/node/README.md
  • Go: bindings/go/net/redex.go lifecycle docs
  • C: include/net.h (every wait-family declaration)

§Memory Management

  • Handles returned by net_init must be freed with net_shutdown
  • String buffers passed to net_poll are owned by the caller
  • Error codes are returned as integers (0 = success, negative = error)

§Example (C)

#include "net.h"

int main() {
    // Initialize with default config
    void* bus = net_init("{\"num_shards\": 4}");
    if (!bus) return 1;

    // Ingest an event
    int result = net_ingest(bus, "{\"token\": \"hello\"}", 19);
    if (result < 0) { /* handle error */ }

    // Poll events
    char buffer[65536];
    result = net_poll(bus, "{\"limit\": 100}", buffer, sizeof(buffer));

    // Shutdown
    net_shutdown(bus);
    return 0;
}

Modules§

aggregator
C FFI for the aggregator.registry RPC client + channel visibility setter. Stage 5 of SDK_AGGREGATOR_SUBNET_PLAN.md. Rides the net feature alongside ffi::mesh because every op needs a MeshNodeHandle. C FFI bindings for the aggregator-registry RPC client + fold-query client + channel-visibility setter (SDK_AGGREGATOR_SUBNET_PLAN.md stages 5 + 4-fold-query).
blob
C FFI for the Dataforts Phase 3 blob surface. Exposes the BlobRef wire codec, the global adapter registry, and the publish_blob / resolve_payload helpers for cgo / native consumers. C FFI for Dataforts Phase 3 blob storage.
handle_guard
C FFI for CortEX / NetDb / RedexFile. Requires netdb (for the unified facade) and redex-disk (for persistent storage paths on Redex / RedexFile). Go / cgo consumers target this surface.
mesh
C FFI for the encrypted-UDP mesh transport + channels. Requires the net feature (which brings in the crypto + transport). Go / cgo consumers target this surface alongside ffi::cortex. See the ffi::cortex note for why missing_docs is suppressed here. C FFI bindings for the encrypted-UDP mesh transport.
predicate
C FFI for stateless predicate evaluation (Phase 9c of CAPABILITY_SYSTEM_SDK_PLAN.md). Pure helpers — no handles, no state. Mirrors the SDK-layer evaluatePredicate / evaluate_predicate surface every binding ships, exposed at the C ABI for raw consumers (C / C++ / Zig / Swift / etc.). C FFI for stateless predicate evaluation (Phase 9c of CAPABILITY_SYSTEM_SDK_PLAN.md).
predicate_debug
C FFI for predicate debug-session helpers (Phase 9d of CAPABILITY_SYSTEM_SDK_PLAN.md). Pure helpers — single-eval evaluate_with_trace, corpus-wide aggregate_debug_report, and host-side redact_metadata_keys. Mirror what every other binding ships at the SDK layer; exposed at the C ABI for raw consumers. C FFI for predicate debug-session helpers (Phase 9d of CAPABILITY_SYSTEM_SDK_PLAN.md).
schema
C FFI for stateless capability-set validation (Phase 9a of CAPABILITY_SYSTEM_SDK_PLAN.md). Pure helper — caps_json in, report_json out. Mirrors the SDK-layer validate_capabilities surface, exposed at the C ABI for raw consumers. C FFI for stateless capability-set validation (Phase 9a of CAPABILITY_SYSTEM_SDK_PLAN.md).

Structs§

NetEvent
A single stored event for C consumers.
NetHandle
Opaque handle to an event bus instance.
NetPollResult
Poll result for C consumers.
NetReceipt
Ingestion receipt for C consumers.
NetStats
Stats for C consumers.

Enums§

NetError
Error codes returned by FFI functions.

Functions§

net_flush
Flush all pending batches to the adapter.
net_free_poll_result
Free the internal allocations of a poll result returned by net_poll_ex.
net_free_string
Free a string returned by Net functions.
net_generate_keypair
Generate a new Net keypair.
net_ingest
Ingest a single event.
net_ingest_batch
Ingest multiple events.
net_ingest_raw
Ingest a raw JSON string (fastest path).
net_ingest_raw_batch
Ingest multiple raw JSON strings (fastest batch path).
net_ingest_raw_ex
Ingest raw JSON with structured receipt.
net_init
Initialize a new event bus.
net_num_shards
Get the number of shards.
net_poll
Poll events from the bus.
net_poll_ex
Poll events with structured result (no JSON overhead).
net_shutdown
Shut down the event bus and free resources.
net_stats
Get event bus statistics.
net_stats_ex
Get stats without JSON serialization.
net_version
Get the library version.