rns-embedded-ffi 0.5.2

FFI bindings and static library surface for embedded Reticulum runtimes.
Documentation
# rns-embedded-ffi

`rns-embedded-ffi` exposes two C-facing surfaces:

- legacy compatibility entrypoints for manual tick plus raw wire ingress/egress
- the `v1` node-centric API for lifecycle, status, send/broadcast, and event subscriptions

The public header is [include/rns_embedded_ffi.h](./include/rns_embedded_ffi.h).

Surface labels:

- stable core: v1 lifecycle, status, send/broadcast, capability probe, subscriptions, structured errors
- compatibility surface: legacy manual tick + raw wire ingress/egress
- extension surface: `RnsEmbeddedV1EventKind::Extension` with numeric IDs validated against [docs/fixtures/embedded/public-node-api-v1/extension-ids.json]../../../docs/fixtures/embedded/public-node-api-v1/extension-ids.json

## v1 API Summary

Use the `v1` API when you want a stable node handle instead of wiring transport primitives manually:

- `rns_embedded_v1_node_new`
- `rns_embedded_v1_get_capabilities`
- `rns_embedded_v1_node_start`
- `rns_embedded_v1_node_stop`
- `rns_embedded_v1_node_restart`
- `rns_embedded_v1_node_get_status`
- `rns_embedded_v1_node_send`
- `rns_embedded_v1_node_broadcast`
- `rns_embedded_v1_node_set_log_level`
- `rns_embedded_v1_node_subscribe_events`
- `rns_embedded_v1_subscription_next`
- `rns_embedded_v1_subscription_close`

The `v1` structs are self-describing:

- every public struct starts with `struct_size`
- every public struct carries `struct_version`
- callers should zero-init or use the provided default-producing functions before passing structs in

Capability probe rules:

- `capability_schema_version` versions the meaning of the capability payload itself
- `known_capability_bits` is the full bit registry this build understands
- `compile_time_capability_bits` is the feature set compiled into the library artifact
- `capability_bits` is the effective runtime-safe subset for the current build/profile
- wrappers must ignore unknown bits by masking with `known_capability_bits`
- `max_blocking_timeout_ms == 0` means blocking `next(timeout_ms>0)` is not available
- `driver_tick_target_ms` and `driver_tick_max_ms` are only non-zero in managed `std` builds

The stable node-error registry is generated from [docs/contracts/node-error-codes-v1.json](../../../docs/contracts/node-error-codes-v1.json) and documented in [docs/contracts/node-error-codes-v1.md](../../../docs/contracts/node-error-codes-v1.md).

## Minimal Flow

1. Create a node with `rns_embedded_v1_node_new()`.
2. Probe ABI/capabilities with `rns_embedded_v1_abi_version()` and `rns_embedded_v1_get_capabilities(...)`.
3. Fill `RnsEmbeddedV1NodeConfig` from `rns_embedded_v1_node_config_default()`.
4. Call `rns_embedded_v1_node_start(...)`.
5. Optionally create a subscription with `rns_embedded_v1_node_subscribe_events(...)`.
6. Use `rns_embedded_v1_node_send(...)` or `rns_embedded_v1_node_broadcast(...)`.
7. Read `RnsEmbeddedV1PollResult` + `RnsEmbeddedV1NodeEvent` with `rns_embedded_v1_subscription_next(...)`.
8. Close subscriptions, stop the node, then free the node handle.

## Example

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

int main(void) {
    RnsEmbeddedV1Node *node = rns_embedded_v1_node_new();
    if (!node) {
        return 1;
    }

    RnsEmbeddedV1Capabilities capabilities = {0};
    capabilities.struct_size = sizeof(capabilities);
    if (rns_embedded_v1_get_capabilities(&capabilities) != RNS_EMBEDDED_STATUS_OK) {
        rns_embedded_v1_node_free(node);
        return 1;
    }

    RnsEmbeddedV1NodeConfig config = rns_embedded_v1_node_config_default();
    RnsEmbeddedV1NodeError node_error = {0};
    node_error.struct_size = sizeof(node_error);
    if (rns_embedded_v1_node_start(node, &config, &node_error) != RNS_EMBEDDED_STATUS_OK) {
        fprintf(stderr, "start failed: %u\n", (unsigned)node_error.code);
        rns_embedded_v1_node_free(node);
        return 1;
    }

    RnsEmbeddedEventSubscription *subscription = NULL;
    if (rns_embedded_v1_node_subscribe_events(node, &subscription, &node_error) != RNS_EMBEDDED_STATUS_OK) {
        rns_embedded_v1_node_stop(node, &node_error);
        rns_embedded_v1_node_free(node);
        return 1;
    }

    uint8_t destination[16];
    memset(destination, 0x42, sizeof(destination));
    RnsEmbeddedV1SendReceipt receipt = {0};
    receipt.struct_size = sizeof(receipt);
    if (rns_embedded_v1_node_send(
            node,
            destination,
            (const uint8_t *)"hello",
            5,
            &receipt,
            &node_error) != RNS_EMBEDDED_STATUS_OK) {
        fprintf(stderr, "send failed: %u\n", (unsigned)node_error.code);
    }

    RnsEmbeddedV1PollResult poll = {0};
    poll.struct_size = sizeof(poll);
    RnsEmbeddedV1NodeEvent event = {0};
    event.struct_size = sizeof(event);
    if (rns_embedded_v1_subscription_next(subscription, 100, &poll, &event, &node_error) == RNS_EMBEDDED_STATUS_OK) {
        if (poll.kind == RNS_EMBEDDED_V1_POLL_EVENT) {
            printf("event kind=%u epoch=%llu\n",
                   (unsigned)event.kind,
                   (unsigned long long)event.epoch);
        }
    }

    rns_embedded_v1_subscription_close(subscription, &node_error);
    rns_embedded_v1_node_stop(node, &node_error);
    rns_embedded_v1_node_free(node);
    return 0;
}
```

Reference artifacts:

- managed host flow: [std_managed_node.rs]../rns-embedded-runtime/examples/std_managed_node.rs
- manual-tick runtime flow: [manual_tick_runtime.rs]../rns-embedded-runtime/examples/manual_tick_runtime.rs
- mobile/wrapper FFI flow: [mobile_wrapper_v1.c]./examples/mobile_wrapper_v1.c

## Legacy Compatibility Surface

The legacy API remains available for existing firmware and bridge code:

- `rns_embedded_node_new`
- `rns_embedded_node_tick`
- `rns_embedded_node_push_inbound_wire`
- `rns_embedded_node_take_outbound_wire`
- `rns_embedded_node_queue_message`

Use that surface when the integration owns transport progression explicitly and wants raw wire access.