|
nros rmw-cffi
C vtable for plugging a third-party RMW backend into nros
|
C function table for plugging third-party RMW backends into nros. More...
#include <stdbool.h>#include <stdint.h>#include <stddef.h>#include "nros/rmw_ret.h"#include "nros/rmw_entity.h"#include "nros/rmw_event.h"
Go to the source code of this file.
Data Structures | |
| struct | nros_rmw_vtable_t |
| struct | rmw_names_and_types_visitor_t |
| struct | rmw_node_visitor_t |
| struct | rmw_topic_endpoint_info_visitor_t |
Macros | |
| #define | NROS_RMW_REGISTER_BACKEND(REGISTER_FN) |
| #define | NROS_RMW_VISITOR_DEFINED 1 |
Typedefs | |
| typedef struct rmw_loan_token_t | rmw_loan_token_t |
| typedef bool(* | rmw_names_and_types_visit_fn) (void *ctx, const char *name, const char *const *types, size_t types_count) |
| typedef bool(* | rmw_node_visit_fn) (void *ctx, const char *node_name, const char *node_namespace, const char *enclave) |
| typedef bool(* | rmw_topic_endpoint_info_visit_fn) (void *ctx, const rmw_topic_endpoint_info_t *info) |
Enumerations | |
| enum | nros_rmw_session_mode_t { NROS_RMW_SESSION_MODE_CLIENT = 0 , NROS_RMW_SESSION_MODE_PEER = 1 } |
Functions | |
| const nros_rmw_vtable_t * | nros_rmw_cffi_lookup (const char *name) |
| rmw_ret_t | nros_rmw_cffi_register (const nros_rmw_vtable_t *vtable) |
| rmw_ret_t | nros_rmw_cffi_register_named (const char *name, const nros_rmw_vtable_t *vtable) |
| size_t | nros_rmw_cffi_registered_names (const char **buf, size_t cap) |
C function table for plugging third-party RMW backends into nros.
Implement the functions in nros_rmw_vtable_t and call nros_rmw_cffi_register() before creating any nros sessions.
Storage ownership. The runtime owns the entity-struct storage (rmw_session_t, rmw_publisher_t, rmw_subscription_t, rmw_service_t, rmw_client_t). Each create_* call receives a runtime-allocated, zero-initialised struct via the out pointer; the backend writes its backend_data into it. (It does NOT write can_loan_messages: the runtime derives that from the loan slots — issue 0814.) The runtime fills the metadata fields (topic_name, type_name, qos) before calling create_*; the backend reads them through the same struct.
destroy_* releases the backend's backend_data only. The struct shell stays valid until the runtime drops its owner.
Return-value conventions. One rule, no exceptions:
rmw_ret_t. NROS_RMW_RET_OK (which is 0, upstream's value) on success; a POSITIVE named constant on failure, upstream's where upstream has a name for it and one above NROS_RMW_RET_EXTENSION_BASE where it does not. See <nros/rmw_ret.h>.bool *taken, size_t *out_len, bool *out_has_data. No slot multiplexes a count or a flag with a status, so no caller may test a status by its SIGN — gated by scripts/check-rmw-ret-sign.py.NROS_RMW_RET_OK unless a slot says otherwise.This paragraph described the PRE-W3.d ABI until 2026-08-24 and was wrong on five counts at once: it promised NEGATIVE error constants (step B adopted upstream's positive numbering), it said try_recv_* returned a byte count and has_data returned 1-or-0 (step A moved both to out-parameters), it said destroy_* returned void (W5 gave all six a status), and it named five slots that no longer exist — publish_raw, send_reply, try_recv_raw, try_recv_request, try_recv_reply_raw, all renamed by W3.b. Prose in the SSoT header is not covered by any of the shape gates, which compare DECLARATIONS against the header and never read what the header says about itself.
| #define NROS_RMW_REGISTER_BACKEND | ( | REGISTER_FN | ) |
Phase 249 P4b.2 — convenience macro for static-library backends. Place in exactly one TU per backend to self-register the backend on library load. REGISTER_FN is a no-arg function that calls nros_rmw_cffi_register_named for the backend.
Example: static void zenoh_register(void) { nros_rmw_cffi_register_named("zenoh", &VTABLE); } NROS_RMW_REGISTER_BACKEND(zenoh_register)
HOSTED (Rust + C/C++ on a hosted loader): the macro expands to an .init_array constructor (__attribute__((constructor))) that the loader fires before main() — hence before nros_support_init / nros::init — calling REGISTER_FN. The --whole-archive link keeps this object's .init_array slot. nros_rmw_cffi_register_named is idempotent (same-name overwrite), so re-registration is harmless. This consolidates the former linkme section walk onto the ctor (RFC-0042 §D3.3; the linkme distributed slice / section walker is deleted).
EMBEDDED (bare-metal / RTOS: Zephyr, NuttX, esp-idf, VxWorks): the loader does not run .init_array constructors on the startup path, so the macro expands to nothing. Registration is wired explicitly by the board / typed carrier via an explicit nros_rmw_<x>_register() call (phase-249 P1). For C/C++-via-cmake the nano_ros_link_rmw strong stub is the primary registration trigger (P2b/P4a); this hosted ctor is belt-and-suspenders.
Gating mirrors the cyclonedds vtable.cpp constructor (off the RTOS targets) and requires GCC/Clang constructor-attribute support.
| #define NROS_RMW_VISITOR_DEFINED 1 |
A visitor: the function and the context it needs — phase-406 W2.
Upstream fills a heap-owning out-parameter the caller must fini (rmw_names_and_types_t, rcutils_string_array_t). We visit instead: one call per entry, strings borrowed for the call, no allocation and nothing to leak — which is what makes the graph family available on a target with no allocator at all.
The pair was TWO arguments on twelve slots, and they are meaningless apart: a visit without its ctx cannot find its state, and a ctx without its visit goes nowhere. Grouping them is the same move as the type support, and it makes "pass one, forget the other" unrepresentable rather than merely discouraged.
fn == NULL is INVALID_ARGUMENT, never a silent no-op: a graph query that visits nothing is indistinguishable from an empty graph, which is the exact confusion issue 0903 cost days to.
Passed BY VALUE — two words, and nothing is written back through it.
| typedef struct rmw_loan_token_t rmw_loan_token_t |
An opaque per-loan handle — phase-406 W3.
Was a bare void *. The type says nothing a reader can use, and nothing stops a caller returning a publisher's loan token to a subscription, or a token from one backend to another: both compile, and both are undefined behaviour discovered at run time on a target with no allocator to notice.
An INCOMPLETE type costs nothing at the ABI — it is still a pointer — and makes those two mistakes a compile error. The backend defines the struct privately; nobody outside it may dereference one, which was already the contract and is now enforced rather than documented.
Issue 0781 asked whether the subscription-side loan slots earn their place given nothing implements them. The answer taken here is KEEP AND TYPE: a slot nobody can use safely is worse than one nobody uses.
| typedef bool(* rmw_names_and_types_visit_fn) (void *ctx, const char *name, const char *const *types, size_t types_count) |
Visit one name and the types on it. types_count may legitimately be 0 on a partially discovered graph — reporting the name without a type beats dropping it. Return false to stop.
| typedef bool(* rmw_node_visit_fn) (void *ctx, const char *node_name, const char *node_namespace, const char *enclave) |
Visit one node. enclave is NULL where the backend does not track one — which is what lets a single slot answer both rmw_get_node_names and rmw_get_node_names_with_enclaves. Return false to stop.
| typedef bool(* rmw_topic_endpoint_info_visit_fn) (void *ctx, const rmw_topic_endpoint_info_t *info) |
Visit one discovered endpoint. Return false to stop.
Session mode for create_session's mode parameter (issue 0331).
These values were previously an undocumented bare uint8_t with no legal- value list — the only slot in the vtable without one — encoded inline as 0u8 / 1u8 at the Rust boundary.
Divergence from rmw.h, recorded deliberately: Humble's rmw_init_options_t carries instance_id, implementation_identifier, domain_id, security_options, localhost_only, enclave, allocator and impl — and has NO session-mode concept. (This list said "domain_id,
enclave, security_options and discovery_options" until 2026-08-24, which omitted five of the eight and named one, discovery_options, that is an IRON field and does not exist in the distro our recorded contract is taken from. What we do and do not carry from those eight is issue 0785.) This parameter is closest to zenoh's whatami, and a backend that has no such notion (cyclonedds, XRCE) is expected to IGNORE it rather than fail. Folding it into backend-private config behind the locator — so the agnostic vtable stops carrying a backend-shaped field — is the structural fix, and is not done here; see issue 0808. (This said "see issue
0331" until 2026-08-26. That issue documented this enum and stated in its own resolution that the structural half was NOT done, deferring it to issue 0330 part 3 — which resolved a different concern entirely. Both are closed, so the fold was tracked by nothing; 0808 is its home.)
| Enumerator | |
|---|---|
| NROS_RMW_SESSION_MODE_CLIENT | Connect to a router/agent as a client. The default. |
| NROS_RMW_SESSION_MODE_PEER | Peer-to-peer, no router. Backends without a peer mode ignore this. |
| const nros_rmw_vtable_t * nros_rmw_cffi_lookup | ( | const char * | name | ) |
Look up a backend's vtable by name. Returns NULL if no backend is registered under name. The returned pointer is valid for the program's lifetime.
| rmw_ret_t nros_rmw_cffi_register | ( | const nros_rmw_vtable_t * | vtable | ) |
Register a custom RMW backend under the implicit name "default". Legacy single-arg form retained for source compatibility with backend ctors authored before the named registry (Phase 104.B.2).
Deprecated (Phase 128.B.5): every in-tree backend now calls nros_rmw_cffi_register_named with its canonical name. The unnamed shim will be removed in a follow-up phase. Returns NROS_RMW_RET_OK.
| rmw_ret_t nros_rmw_cffi_register_named | ( | const char * | name, |
| const nros_rmw_vtable_t * | vtable | ||
| ) |
Phase 104.B.2 — register a backend under a stable name. Multiple backends can coexist (bridge nodes); consumers select via nros_rmw_cffi_lookup or the higher-level Executor::node_builder(...).rmw(...) path.
Names: UTF-8, NUL-terminated, ≤ 31 bytes (excluding NUL). Reserved: "zenoh", "dds", "xrce", "cyclonedds", future "uorb". "default" is the implicit name used by nros_rmw_cffi_register.
Duplicate registration of the same name overwrites the previous vtable (idempotent for ctor-fires-twice).
Returns:
| size_t nros_rmw_cffi_registered_names | ( | const char ** | buf, |
| size_t | cap | ||
| ) |
Diagnostic helper — fills buf with pointers to up to cap registered backend names. Returns the total number of registered backends (may exceed cap; caller can re-query with a larger buffer). Pointer-valid for the program's lifetime. Pass buf=NULL, cap=0 to query the count only.