1#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
6#![allow(unsafe_op_in_unsafe_fn, clippy::missing_safety_doc)]
7#[doc = " Status code. Zero on success.\n\n Phase 376 W3.d step B — the VALUES are upstream rmw's. `RMW_RET_OK` was\n already 0 on both sides; everything else moved from a negative code to\n upstream's positive one, so a status means the same number on both sides of\n the seam: OK 0, ERROR 1, TIMEOUT 2, UNSUPPORTED 3, BAD_ALLOC 10,\n INVALID_ARGUMENT 11, INCORRECT_RMW_IMPLEMENTATION 12,\n NODE_NAME_NON_EXISTENT 203.\n\n (Written as prose, not an indented block: bindgen copies this comment into\n `generated.rs` verbatim, and rustdoc reads an indented block there as a Rust\n DOCTEST — which then fails to compile. Caught by `cargo test`.)\n\n This is why step A had to come first. Eleven slots used to return a COUNT\n or a FLAG as a non-negative value and a status as a negative one; with\n `ERROR` at 1, a return of `1` would have meant both \"one message\" and\n \"failed\". Every one of those slots now reports through an out-parameter, so\n the sign carries nothing and the numbers are free to move.\n\n Codes upstream does not define live in the EXTENSION RANGE at 1000+, so a\n future upstream addition can never collide with one of ours. That range is\n the one place we knowingly add to upstream's namespace.\n\n Signedness is kept (`int32_t`, not an unsigned type) to match upstream's\n `rmw_ret_t` exactly. Nothing returns a negative value any more."]
10pub type rmw_ret_t = i32;
11#[doc = " Nanoseconds since a clock's epoch — upstream's `rmw_time_point_value_t`."]
12pub type rmw_time_point_value_t = i64;
13#[doc = " Visit one network flow endpoint. Return `false` to stop.\n\n Upstream fills an ALLOCATING `rmw_network_flow_endpoint_array_t` through an\n `rcutils_allocator_t *`. There is no allocator at this seam and the flow\n count is a property of the OS's routing, not of anything the caller can\n size in advance — so it streams, exactly like the graph slots."]
14pub type rmw_network_flow_endpoint_visit_fn = ::core::option::Option<
15 unsafe extern "C" fn(
16 ctx: *mut core::ffi::c_void,
17 endpoint: *const rmw_network_flow_endpoint_t,
18 ) -> bool,
19>;
20#[doc = " Visit a subscription's content filter. Return value ignored: there is\n exactly one filter per subscription, so this is a callback only to avoid\n handing back an allocated `rmw_subscription_content_filter_options_t`.\n\n `expression` and every `parameters[i]` are BORROWED for the call. A\n subscription with no filter is reported as `expression == NULL`, which is\n what upstream's empty options struct means."]
21pub type rmw_content_filter_visit_fn = ::core::option::Option<
22 unsafe extern "C" fn(
23 ctx: *mut core::ffi::c_void,
24 expression: *const core::ffi::c_char,
25 parameters: *const *const core::ffi::c_char,
26 parameter_count: usize,
27 ),
28>;
29pub type rmw_status_event_callback_t = ::core::option::Option<
30 unsafe extern "C" fn(
31 kind: rmw_event_type_t::Type,
32 payload: *const rmw_event_payload_t,
33 user_context: *mut core::ffi::c_void,
34 ),
35>;
36#[doc = " Upstream `rmw_event_callback_t` — the callback the `set_on_new_*` slots\n install. Distinct from `rmw_status_event_callback_t` above, which is the DDS\n STATUS-event callback; upstream binds this name to this shape and we now\n match it."]
37pub type rmw_event_callback_t = ::core::option::Option<
38 unsafe extern "C" fn(user_data: *const core::ffi::c_void, number_of_events: usize),
39>;
40#[doc = " Visit one node. `enclave` is NULL where the backend does not track one —\n which is what lets a single slot answer both `rmw_get_node_names` and\n `rmw_get_node_names_with_enclaves`. Return `false` to stop."]
41pub type rmw_node_visit_fn = ::core::option::Option<
42 unsafe extern "C" fn(
43 ctx: *mut core::ffi::c_void,
44 node_name: *const core::ffi::c_char,
45 node_namespace: *const core::ffi::c_char,
46 enclave: *const core::ffi::c_char,
47 ) -> bool,
48>;
49#[doc = " Visit one name and the types on it. `types_count` may legitimately be 0 on a\n partially discovered graph — reporting the name without a type beats\n dropping it. Return `false` to stop."]
50pub type rmw_names_and_types_visit_fn = ::core::option::Option<
51 unsafe extern "C" fn(
52 ctx: *mut core::ffi::c_void,
53 name: *const core::ffi::c_char,
54 types: *const *const core::ffi::c_char,
55 types_count: usize,
56 ) -> bool,
57>;
58#[doc = " Visit one discovered endpoint. Return `false` to stop."]
59pub type rmw_topic_endpoint_info_visit_fn = ::core::option::Option<
60 unsafe extern "C" fn(
61 ctx: *mut core::ffi::c_void,
62 info: *const rmw_topic_endpoint_info_t,
63 ) -> bool,
64>;
65#[doc = " Runtime-pluggable custom transport. The runtime never\n dereferences `user_data`; it's the caller's per-transport\n context, threaded back into every callback's first argument.\n\n THIS declaration is the ABI single source of truth (RFC-0054): Rust\n consumes the committed bindgen output of this header, and\n `nros_rmw::NrosTransportOps` is the hand-written Rust-side view kept in\n lockstep with it — not the other way round. The previous wording had that\n backwards (issue 0331). Layout equivalence is asserted on both sides: see\n `nros_transport_ops_t` in `nros-rmw-cffi/tests/c_stubs/abi_layout_check.c`\n and the `const _` size/align block beside\n `nros_rmw_cffi_set_custom_transport` in `nros-rmw-cffi/src/lib.rs`. Same\n layout, same threading contract, same return codes."]
66pub type nros_transport_ops_t = nros_transport_ops_s;
67#[doc = " Global identifier for a publisher — upstream `rmw_gid_t`, field for field.\n\n Phase 376 W4. Mirrors upstream exactly, including the 24-byte width and the\n `implementation_identifier`. The identifier matters MORE here than upstream:\n `nros_rmw_cffi_register_named` admits several backends in one image, so two\n gids are comparable only when it matches.\n\n Comparison is over the whole array, so a producer MUST zero-pad an\n identifier shorter than 24 bytes rather than leave the tail undefined —\n otherwise two gids naming the same entity compare unequal on stack garbage.\n\n **24, not 16, and that is a discrepancy worth knowing about.** Our own\n `MessageInfo::publisher_gid` (`nros-core`, `PUBLISHER_GID_SIZE`) is 16 bytes,\n while the Cyclone backend already computes 24-byte gids for the DDS graph\n (`entity_gid_24` in `graph.cpp`). Under upstream semantics those are the SAME\n identifier, so a gid obtained from a take cannot today be compared with one\n from `get_gid_for_publisher` without a documented mapping — and the narrower\n one truncates. The ABI takes upstream's width; reconciling `MessageInfo` is\n its own change and is NOT done here. */\n/** Bytes to READ — phase-406 W2.\n\n `len` is a FACT: how many bytes exist. Nothing is written through this, and\n `const` says so — `publish` handing a backend a mutable pointer is an\n invitation.\n\n PASSED BY VALUE. Two words, and there is nothing to report back."]
68#[repr(C)]
69#[derive(Debug, Copy, Clone)]
70pub struct rmw_byte_span_t {
71 pub data: *const u8,
72 pub len: usize,
73}
74#[doc = " Room to WRITE — phase-406 W2.\n\n This is upstream's `rmw_serialized_message_t` MINUS THE ALLOCATOR. That is\n not a coincidence and it is the whole argument: upstream's is an\n `rcutils_uint8_array_t`, `{buffer, buffer_length, buffer_capacity,\n allocator}`, and this ABI declined it because of the last field. Drop that\n field and the remaining three are exactly what a caller-owned destination\n needs, so the \"carries an allocator\" objection does not transfer to this.\n\n `capacity` is a LIMIT (in) and `len` is a RESULT (out). They are separate\n fields rather than one overloaded `len` because \"capacity on the way in,\n length on the way out\" is the `snprintf` ambiguity, and it is a bug\n generator.\n\n PASSED BY POINTER, always. The callee must set `len`, and a by-value copy\n would discard it — code that compiles, runs, and yields zero-length\n messages."]
75#[repr(C)]
76#[derive(Debug, Copy, Clone)]
77pub struct rmw_mut_byte_span_t {
78 pub data: *mut u8,
79 #[doc = " In: bytes available at `data`."]
80 pub capacity: usize,
81 #[doc = " Out: bytes actually written. Undefined on failure."]
82 pub len: usize,
83}
84#[doc = " A message type's identity — phase-406 W1.\n\n Upstream passes `const rosidl_message_type_support_t *`: a runtime-dispatch\n handle carrying `{typesupport_identifier, data, func}`, where `func` walks a\n type description at run time. This ABI resolves types at BUILD time, so\n there is nothing for `func` to do and the handle's contents do not cross.\n\n What DOES cross is the identity, and it was crossing as two loose\n `const char *` wedged between `topic_name` and `qos` — so the argument order\n did not even line up with upstream's, and every create slot took two\n arguments where ROS 2 takes one. Grouping them costs nothing: codegen emits\n one `static const rmw_message_type_support_t` per type, exactly as\n `ROSIDL_GET_MSG_TYPE_SUPPORT(...)` already hands back a pointer to a static.\n\n NAMED `rmw_`, NOT `rosidl_`, and not `nros_`. This ABI is a standard\n interface, so a vendor prefix would say the interface is ours — but\n `rosidl_message_type_support_t` belongs to `rosidl_runtime_c`, a package we\n do not implement, and redefining it would collide with a host build that\n legitimately has it in scope. Reusing `rmw_publisher_t` is safe because we\n ARE the rmw implementation and own that name; `rmw_` is ours to spend and\n neutral to a reader.\n\n Future type-carried data (a serialize/deserialize pair, a bounded-size hint)\n lands here without changing any slot's arity again — which matters, because\n appending to a hand-mirrored FFI struct is what `check-ffi-struct-mirrors`\n exists for."]
85#[repr(C)]
86#[derive(Debug, Copy, Clone)]
87pub struct rmw_message_type_support_t {
88 #[doc = " Fully-qualified ROS type, e.g. `\"std_msgs/msg/String\"`. Borrowed;\n must outlive every entity created with it, which a codegen `static`\n satisfies by construction."]
89 pub type_name: *const core::ffi::c_char,
90 #[doc = " RIHS type hash, e.g. `\"RIHS01_...\"`, or NULL where the backend does not\n carry one. NULL is \"not supplied\", never \"empty\"."]
91 pub type_hash: *const core::ffi::c_char,
92}
93#[doc = " A service type's identity. See @ref rmw_message_type_support_t.\n\n Separate from the message form for the reason upstream separates them:\n `rosidl_service_type_support_t` and `rosidl_message_type_support_t` are\n distinct types there, and collapsing them here would let a service type be\n passed where a message type is required with no diagnostic."]
94#[repr(C)]
95#[derive(Debug, Copy, Clone)]
96pub struct rmw_service_type_support_t {
97 #[doc = " Fully-qualified ROS service type, e.g. `\"example_interfaces/srv/AddTwoInts\"`."]
98 pub type_name: *const core::ffi::c_char,
99 #[doc = " RIHS type hash, or NULL."]
100 pub type_hash: *const core::ffi::c_char,
101}
102#[repr(C)]
103#[derive(Debug, Copy, Clone)]
104pub struct rmw_gid_t {
105 #[doc = " Which backend produced this gid; gids from different backends are not\n comparable. Borrowed, static for the life of the image."]
106 pub implementation_identifier: *const core::ffi::c_char,
107 #[doc = " The identifier bytes, zero-padded to the full width."]
108 pub data: [u8; 24usize],
109}
110#[doc = " Per-sample metadata — upstream `rmw_message_info_t`, field for field.\n\n Phase 376 W4. Today this metadata reaches Rust callers through\n `MESSAGE_INFO_TABLE`, a side table in `nros-rmw-cffi` keyed on the\n subscription's `backend_data` ADDRESS. That table is a workaround, not a\n design, and it never crosses the seam it exists for: only the Rust\n trampoline writes it, so a C or C++ backend has no symbol to call and\n message info is permanently absent for them. It also claims a pool slot per\n subscription and never releases it, so a reused handle address inherits the\n previous subscription's metadata.\n\n Passing the struct by pointer on the take call — which is what upstream does\n — removes all of that: the caller owns the storage, it lives exactly as long\n as the call, and every backend can fill it.\n\n Retiring the side table is NOT part of this change; the `take_with_info`\n slots are the mechanism that makes retiring it possible."]
111#[repr(C)]
112#[derive(Debug, Copy, Clone)]
113pub struct rmw_message_info_t {
114 #[doc = " Publisher's clock at publication, ns. 0 = no source timestamp."]
115 pub source_timestamp: rmw_time_point_value_t,
116 #[doc = " Subscriber's clock at reception, ns. 0 = receptions are not stamped."]
117 pub received_timestamp: rmw_time_point_value_t,
118 #[doc = " Publisher-side sequence, or\n `RMW_MESSAGE_INFO_SEQUENCE_NUMBER_UNSUPPORTED`. Whether this is real is\n what `feature_supported` answers."]
119 pub publication_sequence_number: u64,
120 #[doc = " Subscriber-side reception count, or the same sentinel."]
121 pub reception_sequence_number: u64,
122 #[doc = " Which publisher sent it. All-zero `data` = unknown."]
123 pub publisher_gid: rmw_gid_t,
124 #[doc = " True when the sample never left the image (Zephyr's\n `Z_FEATURE_LOCAL_SUBSCRIBER`, DDS intra-process)."]
125 pub from_intra_process: bool,
126}
127#[doc = " Full DDS-shaped QoS profile.\n\n Matches the field set of upstream `rmw_qos_profile_t`. Backends\n advertise per-policy support via the runtime's\n `supported_qos_policies()` query; entities created with a profile\n the active backend can't honour return\n `NROS_RMW_RET_INCOMPATIBLE_QOS` synchronously at create time\n — no silent downgrade.\n\n Zero-valued fields (\"off\") preserve the cheap default for apps\n that don't request the policy:\n - `deadline_ms = 0` → infinite deadline (no check).\n - `lifespan_ms = 0` → infinite lifespan (no expiry).\n - `liveliness_kind = NONE` → no liveliness tracking.\n - `liveliness_lease_ms = 0` → infinite lease.\n\n **Boundary semantics (phase-301, issue 0241).** Durations are u32\n MILLISECONDS; that width is part of the contract:\n - `0` = unset/no-check (matches upstream `RMW_QOS_*_DEFAULT`, the\n zero time — a \"real 0-duration\" is inexpressible upstream too).\n - `NROS_RMW_DURATION_INFINITE_MS` = explicit infinite.\n - Callers lowering finer-grained times MUST round sub-ms values UP\n to 1 ms (rounding down would silently turn a real deadline into\n \"no deadline\") and MUST reject values past the u32-ms range\n (other than the infinite sentinel) at create time\n (`NROS_RMW_RET_INVALID_ARGUMENT`) — never clamp.\n\n `depth` is `uint16_t` (max 65 535). Embedded ROS application queue\n depths are typically 1–100; the 16-bit width saves two bytes per\n entity vs the upstream 32-bit choice. A requested depth the width\n cannot represent is a create-time error, never a silent saturate\n (phase-301, issue 0241).\n\n **Pure policy mirror (phase-301, issue 0240).** Transport hints\n (`tx_express`, `rx_buffer_hint`) moved OUT of this struct into\n `rmw_publisher_options_t` / `rmw_subscription_options_t` —\n the upstream `rmw_publisher_options_t` / `rmw_subscription_options_t`\n home for exactly that class. QoS carries DDS policy only; hint growth\n no longer churns this ABI."]
128#[repr(C)]
129#[derive(Debug, Copy, Clone)]
130pub struct rmw_qos_profile_t {
131 #[doc = "< @see NROS_RMW_RELIABILITY_*"]
132 pub reliability: u8,
133 #[doc = "< @see NROS_RMW_DURABILITY_*"]
134 pub durability: u8,
135 #[doc = "< @see NROS_RMW_HISTORY_*"]
136 pub history: u8,
137 #[doc = "< @see rmw_liveliness_kind_t"]
138 pub liveliness_kind: u8,
139 pub depth: u16,
140 #[doc = "< Reserved; must be zero."]
141 pub _reserved0: u16,
142 #[doc = " Subscription: max acceptable inter-arrival time, ms. Publisher:\n max acceptable inter-publish (offered rate), ms.\n 0 = infinite (no deadline)."]
143 pub deadline_ms: u32,
144 #[doc = " Sample expiry, ms. Subscription filters samples older than\n this. 0 = infinite (no expiry)."]
145 pub lifespan_ms: u32,
146 #[doc = " Liveliness lease, ms. Publisher must assert liveliness\n within this window or be considered dead. 0 = infinite."]
147 pub liveliness_lease_ms: u32,
148 #[doc = " If non-zero, topic-name encoding skips the ROS `/rt/` prefix\n and uses raw application names. Matches upstream\n `avoid_ros_namespace_conventions`. `0` = false, non-zero =\n true. (`uint8_t` instead of `bool`; `sizeof(_Bool)` is impl-\n defined per C99 — `uint8_t` keeps the layout stable across\n toolchains.)"]
149 pub avoid_ros_namespace_conventions: u8,
150 #[doc = "< Reserved; must be zero."]
151 pub _reserved1: [u8; 3usize],
152}
153#[doc = " One discovered endpoint — upstream `rmw_topic_endpoint_info_t`.\n\n Every string is BORROWED for the duration of the visit that hands this out;\n a caller that needs one past the callback copies it. That is what lets the\n graph slots stream without an allocator."]
154#[repr(C)]
155#[derive(Debug, Copy, Clone)]
156pub struct rmw_topic_endpoint_info_t {
157 #[doc = " Node that owns the endpoint."]
158 pub node_name: *const core::ffi::c_char,
159 #[doc = " That node's namespace."]
160 pub node_namespace: *const core::ffi::c_char,
161 #[doc = " Fully-qualified type on the wire, e.g. `\"std_msgs/msg/Int32\"`."]
162 pub topic_type: *const core::ffi::c_char,
163 #[doc = " Publisher or subscription."]
164 pub endpoint_type: rmw_endpoint_type_t::Type,
165 #[doc = " The endpoint's identity; `data` all-zero when the backend has none."]
166 pub endpoint_gid: rmw_gid_t,
167 #[doc = " The GRANTED profile, not the requested one — which is the whole reason a\n consumer asks. A backend that cannot read back a remote's granted QoS\n reports what it MATCHED on, and must not substitute the local entity's\n requested profile."]
168 pub qos_profile: rmw_qos_profile_t,
169}
170#[doc = " One network flow endpoint — upstream `rmw_network_flow_endpoint_t`, field\n for field. Unlike the graph structs this one carries no pointers, so it\n costs nothing to mirror exactly and a caller may copy it wholesale."]
171#[repr(C)]
172#[derive(Debug, Copy, Clone)]
173pub struct rmw_network_flow_endpoint_t {
174 pub transport_protocol: rmw_transport_protocol_t::Type,
175 pub internet_protocol: rmw_internet_protocol_t::Type,
176 pub transport_port: u16,
177 #[doc = " Publisher-side only; 0 elsewhere."]
178 pub flow_label: u32,
179 #[doc = " Differentiated Services Code Point. Publisher-side only; 0 elsewhere."]
180 pub dscp: u8,
181 pub internet_address: [core::ffi::c_char; 48usize],
182}
183#[doc = " Publisher creation options — the home for publisher-side transport\n hints (upstream: `rmw_publisher_options_t`). Passed as a NULLable\n trailing param to `create_publisher`; NULL = all defaults.\n/\n/**\n Session creation options — the home for init-time context that\n `create_session`'s flat argument list cannot grow without another ABI break\n (issue 0808). Passed as a NULLable trailing param; NULL = all defaults.\n\n The carrier question 0808 opened had two candidates: encode this behind the\n locator string, or take one options struct. The struct wins on precedent —\n `rmw_publisher_options_t` and `rmw_subscription_options_t` already solved\n exactly this problem for entities, with the same NULLable-trailing-param\n shape — and on cost: parsing config out of a locator means every backend\n reimplements a parser, which is code size on a target plus a new class of\n silent misparse. One break, then the struct grows.\n\n Of Humble's eight `rmw_init_options_t` fields this carries the two that were\n GAPS (issue 0785). `domain_id` stays a named argument because every backend\n needs it; `security_options` remains declined on the target (a DDS-SROS2\n keystore path, and there is neither a filesystem nor a security plugin\n where this ABI runs); `allocator`, `instance_id`, `impl` and\n `implementation_identifier` are answered elsewhere or declined ABI-wide.\n/\n/**\n One backend-specific session configuration property.\n\n The counterpart of `RmwConfig::properties` on the Rust side, which every\n backend already accepts and which — until phase-206 W3 — NO non-Rust caller\n could reach: the cffi adapter built `properties: &[]` and threw the options\n pointer away, so a C or C++ entry could state no transport configuration at\n all, on any platform.\n\n Backend-specific by design. For zenoh this is zenoh-pico's run-time option\n set — `zp_config_insert(config, Z_CONFIG_<X>_KEY, value)`, which upstream\n calls \"the primary configuration method\" and which has no file format for\n the pico client — with the accepted names derived from zenoh-pico's own\n `config.h` (`zpico_config_keys.h`). A backend that does not recognise a name\n MUST fail the session rather than drop it: a silently ignored configuration\n line is indistinguishable from one that took effect.\n\n Both strings are NUL-terminated and BORROWED for the duration of the\n `create_session` call."]
184#[repr(C)]
185#[derive(Debug, Copy, Clone)]
186pub struct rmw_session_property_t {
187 #[doc = "< Property name; never NULL."]
188 pub key: *const core::ffi::c_char,
189 #[doc = "< Property value; never NULL."]
190 pub value: *const core::ffi::c_char,
191}
192#[repr(C)]
193#[derive(Debug, Copy, Clone)]
194pub struct rmw_session_options_t {
195 #[doc = " Restrict discovery to this host. Upstream `rmw_localhost_only_t`,\n narrowed to a flag: 0 = the system default, non-zero = localhost only.\n\n A backend that cannot restrict discovery must IGNORE this rather than\n fail — same contract as `mode`. Cyclone is the one that can honour it."]
196 pub localhost_only: u8,
197 #[doc = "< Reserved; must be zero."]
198 pub _reserved: [u8; 7usize],
199 #[doc = " The security enclave this session belongs to, or NULL.\n\n Borrowed for the duration of the call. Carried so\n `rmw_get_node_names_with_enclaves` stops being a HOLLOW grouping: the\n visitor's `enclave` argument was structurally always NULL because\n nothing in this ABI accepted one (issue 0785). A backend that does not\n track enclaves still reports NULL, which is now a fact about the\n backend rather than about the seam."]
200 pub enclave: *const core::ffi::c_char,
201 #[doc = " Backend-specific configuration properties, or NULL.\n\n Borrowed for the duration of the call, as is every string they point\n at. `property_count` must be 0 when this is NULL, and must not exceed\n `RMW_SESSION_MAX_PROPERTIES`; a violation is\n `RMW_RET_INVALID_ARGUMENT`, not a truncation."]
202 pub properties: *const rmw_session_property_t,
203 #[doc = "< Number of entries in `properties`."]
204 pub property_count: usize,
205}
206#[repr(C)]
207#[derive(Debug, Copy, Clone)]
208pub struct rmw_publisher_options_t {
209 #[doc = " phase-279 (#145) — express hint (`TopicInfo::tx_express` across\n the C ABI): non-zero = this publisher's samples bypass transport\n tx batching. A transport hint, not a DDS policy — no RxO\n matching."]
210 pub tx_express: u8,
211 #[doc = "< Reserved; must be zero."]
212 pub _reserved: [u8; 7usize],
213}
214#[doc = " Subscription creation options — the home for subscription-side\n transport hints (upstream: `rmw_subscription_options_t`). Passed as a\n NULLable trailing param to `create_subscription`; NULL = all defaults."]
215#[repr(C)]
216#[derive(Debug, Copy, Clone)]
217pub struct rmw_subscription_options_t {
218 #[doc = " Phase 231 (RFC-0038) — receive-buffer size hint, bytes, so a\n size-classing backend (zenoh-pico) can pick a small/large receive\n buffer. `0` = unset. A transport hint, not a DDS policy.\n\n Phase 403 W1 — the paragraph above is what this field MEANS; the rules\n below are what it OBLIGES, and they are normative. They were prose in\n RFC-0005 (\"Receive-buffer sizing: what a backend is obliged to do\") and\n nowhere else, so a third party reading only this header could not tell\n whether ignoring the hint was conformant. It is.\n\n - ADVISORY. A backend MAY ignore this field entirely. A backend with a\n single receive-buffer size is conformant, and so is one that rounds\n 68 up to 1024.\n - `0` IS \"THIS CALLER SAID NOTHING\", NOT \"THIS TYPE HAS NO BOUND\".\n Every message type carries a derived upper bound — bounded in the\n `.msg` (`string<=64`) or capped in `nros-codegen.toml`, and an\n unbounded type is a BUILD ERROR rather than something that falls back\n to a configured default — so the runtime always has a number to put\n here. `0` therefore reaches a backend only from a caller that\n supplied no options at all (`options == NULL`, or a zero-filled\n struct from a hand-rolled C caller). A backend must never read it as\n a claim that the type is unbounded, and must not treat a non-zero\n hint as a tighter bound than the type's own.\n - IT IS NOT THE TAKE BUFFER. `take`'s `buf` / `buf_len` are owned by\n the RUNTIME, and `buf_len` is authoritative on every call — including\n when it disagrees with this hint, and when it changes between calls\n for the same subscription. A backend must not cache a length from\n here and write `buf` against it.\n - MUST NOT LIE. A backend MUST NOT copy a sample that does not fit the\n caller's take buffer and return `NROS_RMW_RET_OK`. A sample larger\n than `buf_len` is reported as a failure — `*taken = false` with\n `NROS_RMW_RET_BUFFER_TOO_SMALL` — never as a truncated success. The\n runtime turns that into a diagnostic naming the buffer\n (`report_dropped_take`); a silent truncation is a corrupt message\n with no symptom at all. This is the ONE guarantee the hint carries,\n and it holds whether or not the backend honoured the hint.\n - NOT A WIRE BOUND. A remote publisher is bound by the `.msg`, never by\n our config, so this is no promise about incoming sample size. A\n backend must not size a fixed structure from it in a way that makes a\n larger sample undeliverable WITHOUT a diagnostic.\n\n The vtable's OPTIONAL `required_rx_bytes` is the other direction: how a\n backend says what it would rather the take buffer were."]
219 pub rx_buffer_hint: u32,
220 #[doc = "< Reserved; must be zero."]
221 pub _reserved: [u8; 4usize],
222}
223#[doc = " Per-process RMW session — the entity returned by `vtable->create_session`.\n\n Carries the node identity (used for diagnostics + wire-level\n topic-key derivation in some backends) plus the opaque\n backend-private state.\n\n The 8-byte `_reserved` slot is sized for a forthcoming\n `vtable: const struct nros_rmw_vtable_t *` field that Phase 104's\n multi-instance work will land here. Backends and runtime keep\n these bytes zero."]
224#[repr(C)]
225#[derive(Debug, Copy, Clone)]
226pub struct rmw_session_t {
227 #[doc = " Node name (borrowed from caller; outlives the session)."]
228 pub node_name: *const core::ffi::c_char,
229 #[doc = " Node namespace (borrowed from caller; outlives the session)."]
230 pub namespace_: *const core::ffi::c_char,
231 #[doc = " Reserved for future fields (Phase 104 vtable pointer slot);\n must be zero."]
232 pub _reserved: [u8; 8usize],
233 #[doc = " Opaque backend state. NULL for an uninitialised session."]
234 pub backend_data: *mut core::ffi::c_void,
235}
236#[doc = " A graph node — upstream `rmw_node_t`, minus what an image has no use for.\n\n Phase 376 W4. Storage is CALLER-OWNED, like every other entity here: the\n runtime hands `create_node` a zero-initialised shell and the backend writes\n its `backend_data` into it.\n\n **Why a node exists at all when an image opens ONE session.** The session\n half of that statement holds; the node half does not, and our own code says\n so. `Executor` keeps a node table, and `CffiSession::entity_view` exists\n SOLELY to fabricate a per-call session carrying the entity's owning-node\n identity — its own comment reads \"one session can host N graph nodes\". The\n zenoh backend then re-derives a node registry from that string by\n linear-scanning declared tokens. So node identity already reaches the\n backend, through a side channel, in every image.\n\n **Done, W5/B1 (2026-08-24).** `create_publisher` / `create_subscription` /\n `create_service` / `create_client` take `const rmw_node_t *` the way\n upstream does. That retired the `entity_view` fabrication — the shim now\n owns a node table and calls `create_node` once per distinct\n `(name, namespace)`, which is only true because `Executor::create_node`\n deduplicates (W5/B1.a).\n\n **Still owed:** zenoh's `ensure_node_liveliness` still linear-scans its own\n `per_node_liveliness` table. Retiring it needs a `create_node` method on the\n Rust `Rmw`/`Session` trait plus a trampoline in `RustBackendAdapter`, so\n that a Rust backend can be TOLD about a node the way a C one is. The slot\n and the table it needs both exist now; only that trait hop is missing. Do\n not read this paragraph as done — the first draft of this comment said the\n registry was retired, which it was not.\n\n Not carried from upstream: `implementation_identifier` and `data` (one\n image links one backend per session, so there is nothing to disambiguate).\n\n `session` IS carried, and is our `context`. Upstream's node reaches its\n context that way and every `rmw_create_*` relies on it; a node with no route\n to its session cannot be the only argument those slots get, which is what\n made this field the precondition for the whole change rather than a\n convenience. Set by the runtime BEFORE `create_node`, and stable for the\n node's life."]
237#[repr(C)]
238#[derive(Debug, Copy, Clone)]
239pub struct rmw_node_t {
240 #[doc = " Node name. Borrowed; outlives the node."]
241 pub name: *const core::ffi::c_char,
242 #[doc = " Node namespace. Borrowed; outlives the node."]
243 pub namespace_: *const core::ffi::c_char,
244 #[doc = " The session this node lives on — upstream's `context`. Set by the\n runtime before `create_node`; never NULL in a node the runtime hands to\n a slot. A backend reaches its own session state through\n `node->session->backend_data`."]
245 pub session: *mut rmw_session_t,
246 #[doc = " Reserved; must be zero."]
247 pub _reserved: [u8; 8usize],
248 #[doc = " Opaque backend state. NULL until `create_node` succeeds."]
249 pub backend_data: *mut core::ffi::c_void,
250}
251#[doc = " Publisher entity.\n\n Created by `vtable->create_publisher`; destroyed by\n `vtable->destroy_publisher`. The runtime owns the storage; the\n runtime fills `topic_name` / `type_name` / `qos` before the\n create call. The backend writes `backend_data`.\n\n `can_loan_messages` matches upstream `rmw_publisher_t`'s field of\n the same name — `true` means the backend exposes the\n `loan_publish` / `commit_publish` primitive (Phase 99).\n\n **The runtime DERIVES it; a backend does not write it** (issue 0814).\n Its value is exactly `vtable->borrow_loaned_message != NULL`, because\n that is the same fact, and a fact with two spellings drifts — this one\n had drifted in both directions at once. Anything a backend writes here\n is overwritten.\n\n This paragraph used to read \"the runtime reads it once at create time\n and picks the publish path accordingly; no per-call probe\". That was\n never true: nothing has ever branched on the field, and the publish\n path is chosen per call from `vtable->borrow_loaned_message` itself.\n\n To refuse a loan for a PARTICULAR entity, return\n `RMW_RET_UNSUPPORTED` from `borrow_loaned_message`. The per-entity\n answer belongs on the call, which the runtime consults; not on this\n flag, which it does not."]
252#[repr(C)]
253#[derive(Debug, Copy, Clone)]
254pub struct rmw_publisher_t {
255 #[doc = " Topic name (borrowed; outlives the publisher)."]
256 pub topic_name: *const core::ffi::c_char,
257 #[doc = " ROS-2-style fully-qualified type name\n (e.g., `\"std_msgs/msg/Int32\"`). Borrowed; outlives the publisher."]
258 pub type_name: *const core::ffi::c_char,
259 #[doc = " QoS subset honoured by this publisher."]
260 pub qos: rmw_qos_profile_t,
261 #[doc = " Backend exposes loan_publish / commit_publish (Phase 99).\n DERIVED by the runtime from `vtable->borrow_loaned_message`;\n a backend's write is overwritten (issue 0814)."]
262 pub can_loan_messages: bool,
263 #[doc = " Reserved for future fields; must be zero."]
264 pub _reserved: [u8; 7usize],
265 #[doc = " Opaque backend state. NULL if creation failed."]
266 pub backend_data: *mut core::ffi::c_void,
267}
268#[doc = " Subscription entity (phase-301: renamed from `subscriber` to the upstream `rmw_subscription_t` term). Same shape as the publisher; `can_loan_messages`\n means the backend exposes the receive-side loan primitive."]
269#[repr(C)]
270#[derive(Debug, Copy, Clone)]
271pub struct rmw_subscription_t {
272 #[doc = " Topic name (borrowed; outlives the subscription)."]
273 pub topic_name: *const core::ffi::c_char,
274 #[doc = " Fully-qualified type name. Borrowed."]
275 pub type_name: *const core::ffi::c_char,
276 #[doc = " QoS subset honoured by this subscription."]
277 pub qos: rmw_qos_profile_t,
278 #[doc = " Backend exposes loan_recv / release_recv (Phase 99).\n DERIVED by the runtime from `vtable->take_loaned_message`;\n a backend's write is overwritten (issue 0814)."]
279 pub can_loan_messages: bool,
280 #[doc = " Reserved for future fields; must be zero."]
281 pub _reserved: [u8; 7usize],
282 #[doc = " Opaque backend state. NULL if creation failed."]
283 pub backend_data: *mut core::ffi::c_void,
284}
285#[doc = " Service entity (phase-301: renamed from `service_server` to the upstream `rmw_service_t` term).\n\n Service entities have no QoS in the nros subset (the upstream\n `rmw_qos_profile_services_default` distinction does not generalise\n across non-DDS backends — see book `concepts/ros2-comparison.md`).\n\n No `can_loan_messages` field — service request/reply currently\n always goes through `take_request` / `send_response` byte-buffer\n APIs. If a future backend wants service-side lending, the\n `_reserved[8]` block accommodates the bool + 7 padding bytes\n without an ABI break."]
286#[repr(C)]
287#[derive(Debug, Copy, Clone)]
288pub struct rmw_service_t {
289 #[doc = " Service name (borrowed; outlives the server)."]
290 pub service_name: *const core::ffi::c_char,
291 #[doc = " Fully-qualified service type name (e.g.,\n `\"example_interfaces/srv/AddTwoInts\"`). Borrowed."]
292 pub type_name: *const core::ffi::c_char,
293 #[doc = " Reserved for future fields; must be zero."]
294 pub _reserved: [u8; 8usize],
295 #[doc = " Opaque backend state. NULL if creation failed."]
296 pub backend_data: *mut core::ffi::c_void,
297}
298#[doc = " Client entity (phase-301: renamed from `service_client` to the upstream `rmw_client_t` term). Same shape as the service."]
299#[repr(C)]
300#[derive(Debug, Copy, Clone)]
301pub struct rmw_client_t {
302 #[doc = " Service name (borrowed; outlives the client)."]
303 pub service_name: *const core::ffi::c_char,
304 #[doc = " Fully-qualified service type name. Borrowed."]
305 pub type_name: *const core::ffi::c_char,
306 #[doc = " Reserved for future fields; must be zero."]
307 pub _reserved: [u8; 8usize],
308 #[doc = " Opaque backend state. NULL if creation failed."]
309 pub backend_data: *mut core::ffi::c_void,
310}
311#[doc = " Liveliness payload. Mirrors the DDS\n `rmw_liveliness_changed_status_t` shape."]
312#[repr(C)]
313#[derive(Debug, Copy, Clone)]
314pub struct rmw_liveliness_changed_status_t {
315 pub alive_count: u16,
316 pub not_alive_count: u16,
317 pub alive_count_change: i16,
318 pub not_alive_count_change: i16,
319}
320#[doc = " Count payload. Used for `MESSAGE_LOST`,\n `REQUESTED_DEADLINE_MISSED`, `LIVELINESS_LOST`,\n `OFFERED_DEADLINE_MISSED` — all share the same shape."]
321#[repr(C)]
322#[derive(Debug, Copy, Clone)]
323pub struct rmw_count_status_t {
324 pub total_count: u32,
325 pub total_count_change: u32,
326}
327#[repr(C)]
328#[derive(Debug, Copy, Clone)]
329pub struct rmw_loan_token_t {
330 _unused: [u8; 0],
331}
332#[repr(C)]
333#[derive(Debug, Copy, Clone)]
334pub struct rmw_node_visitor_t {
335 pub visit: rmw_node_visit_fn,
336 pub ctx: *mut core::ffi::c_void,
337}
338#[repr(C)]
339#[derive(Debug, Copy, Clone)]
340pub struct rmw_names_and_types_visitor_t {
341 pub visit: rmw_names_and_types_visit_fn,
342 pub ctx: *mut core::ffi::c_void,
343}
344#[repr(C)]
345#[derive(Debug, Copy, Clone)]
346pub struct rmw_topic_endpoint_info_visitor_t {
347 pub visit: rmw_topic_endpoint_info_visit_fn,
348 pub ctx: *mut core::ffi::c_void,
349}
350#[repr(C)]
351#[derive(Debug, Copy, Clone)]
352pub struct nros_rmw_vtable_t {
353 #[doc = " Create a session (phase-301: renamed from `open` to the table's\n own `create_*` convention). The runtime supplies a\n zero-initialised `rmw_session_t` via @p out with\n `node_name` / `namespace_` already filled. The backend writes\n `out->backend_data`.\n\n @param mode One of `nros_rmw_session_mode_t`. Passed as `uint8_t`\n rather than the enum to keep the slot's width fixed\n across compilers. A backend with no peer/client\n distinction must IGNORE it, not reject it.\n @param options NULLable; NULL means every default. Issue 0808 — the\n home for init-time context this flat list cannot grow\n without another break. `mode` is NOT moved into it: doing\n so would be a second break for no gain, and it is already\n a named argument every backend reads. What moved in are the\n two fields issue 0785 measured as GAPS, `localhost_only`\n and `enclave`."]
354 pub create_session: ::core::option::Option<
355 unsafe extern "C" fn(
356 locator: *const core::ffi::c_char,
357 mode: u8,
358 domain_id: u32,
359 node_name: *const core::ffi::c_char,
360 options: *const rmw_session_options_t,
361 out: *mut rmw_session_t,
362 ) -> rmw_ret_t,
363 >,
364 pub destroy_session:
365 ::core::option::Option<unsafe extern "C" fn(session: *mut rmw_session_t) -> rmw_ret_t>,
366 pub drive_io: ::core::option::Option<
367 unsafe extern "C" fn(session: *mut rmw_session_t, timeout_ms: i32) -> rmw_ret_t,
368 >,
369 #[doc = " Create a publisher. The runtime fills `out->topic_name`,\n `out->type_name`, `out->qos` before this call; the backend\n writes `out->backend_data`. `out->can_loan_messages` is DERIVED\n by the runtime, not written here (issue 0814).\n `options` carries transport hints (phase-301: moved out of the\n QoS struct); NULL = all defaults."]
370 pub create_publisher: ::core::option::Option<
371 unsafe extern "C" fn(
372 node: *const rmw_node_t,
373 type_support: *const rmw_message_type_support_t,
374 topic_name: *const core::ffi::c_char,
375 domain_id: u32,
376 qos: *const rmw_qos_profile_t,
377 options: *const rmw_publisher_options_t,
378 out: *mut rmw_publisher_t,
379 ) -> rmw_ret_t,
380 >,
381 pub destroy_publisher:
382 ::core::option::Option<unsafe extern "C" fn(publisher: *mut rmw_publisher_t) -> rmw_ret_t>,
383 pub publish: ::core::option::Option<
384 unsafe extern "C" fn(
385 publisher: *const rmw_publisher_t,
386 payload: rmw_byte_span_t,
387 ) -> rmw_ret_t,
388 >,
389 #[doc = " `options` carries transport hints (phase-301: moved out of the\n QoS struct); NULL = all defaults."]
390 pub create_subscription: ::core::option::Option<
391 unsafe extern "C" fn(
392 node: *const rmw_node_t,
393 type_support: *const rmw_message_type_support_t,
394 topic_name: *const core::ffi::c_char,
395 domain_id: u32,
396 qos: *const rmw_qos_profile_t,
397 options: *const rmw_subscription_options_t,
398 out: *mut rmw_subscription_t,
399 ) -> rmw_ret_t,
400 >,
401 pub destroy_subscription: ::core::option::Option<
402 unsafe extern "C" fn(subscription: *mut rmw_subscription_t) -> rmw_ret_t,
403 >,
404 #[doc = " Upstream `rmw_take`. Phase 376 W3.b/W3.d step A.\n\n `*taken` says whether a message was copied; `*out_len` is\n how many bytes, meaningful only when taken. Both are\n written only on `NROS_RMW_RET_OK`.\n\n Second slot to retire `NROS_RMW_RET_NO_DATA`: an empty\n subscription is `taken = false` with OK, which is what\n upstream's `taken` out-parameter means.\n\n Deviations from upstream, declared:\n - `buf` / `buf_len` / `*out_len` replace upstream's typed\n `void *ros_message`. There is no typesupport indirection\n on target — the payload is bytes and the caller owns the\n buffer, so it needs the length back.\n - no `rmw_subscription_allocation_t *`: it is an OPAQUE\n per-implementation handle (`{const char *implementation_identifier;\n void *data;}` in Humble's `rmw/types.h` — no allocator in\n it), and the only thing that produces one is\n `rmw_init_subscription_allocation`, whose other parameters\n are a typesupport pointer and a sequence bound, both\n declined ABI-wide. Nothing can make one, so the argument has\n nothing to point at. Two earlier reasons here were wrong:\n \"pools are baked\" (issue 0777 — cyclonedds calls\n `ddsrt_calloc` on this very path) and then \"upstream\n pre-sizes an `rcutils_allocator_t`\" (there is none).\n\n Phase 403 W1 — `buf_len` is AUTHORITATIVE on every call, and a\n sample that does not fit it is a FAILURE (`*taken = false` with\n `NROS_RMW_RET_BUFFER_TOO_SMALL`), never a truncated success.\n That holds however the backend treated\n `rmw_subscription_options_t.rx_buffer_hint`, whose doc writes the\n rule out in full."]
405 pub take: ::core::option::Option<
406 unsafe extern "C" fn(
407 subscription: *const rmw_subscription_t,
408 out: *mut rmw_mut_byte_span_t,
409 taken: *mut bool,
410 ) -> rmw_ret_t,
411 >,
412 #[doc = " Phase 376 W3.d step A — status in the return, answer in the\n out-parameter, so no slot multiplexes a flag with a status.\n `*out_has_data` is written only on `NROS_RMW_RET_OK`.\n\n RTOS addition: upstream has no equivalent, because a hosted\n caller reaches for a wait-set. This is the poll a loop with\n no wait-set needs, and it allocates nothing.\n\n Logically read-only, and that is weaker than it sounds: zenoh's\n implementation fires deadline and liveliness callbacks from inside this\n probe and writes their cells, and cyclonedds' peeks its reader (which\n marks samples READ). The rule a backend must actually keep is that a\n probe may not CONSUME a message — the sample a `has_data` reports must\n still be there for the `take` that follows. The stronger \"must not\n mutate subscription state\" was recorded here and true of nobody\n (issue 0780)."]
413 pub has_data: ::core::option::Option<
414 unsafe extern "C" fn(
415 subscription: *mut rmw_subscription_t,
416 out_has_data: *mut bool,
417 ) -> rmw_ret_t,
418 >,
419 pub create_service: ::core::option::Option<
420 unsafe extern "C" fn(
421 node: *const rmw_node_t,
422 type_support: *const rmw_service_type_support_t,
423 service_name: *const core::ffi::c_char,
424 domain_id: u32,
425 qos: *const rmw_qos_profile_t,
426 out: *mut rmw_service_t,
427 ) -> rmw_ret_t,
428 >,
429 pub destroy_service:
430 ::core::option::Option<unsafe extern "C" fn(server: *mut rmw_service_t) -> rmw_ret_t>,
431 #[doc = " Upstream `rmw_take_request`. Phase 376 W3.b/W3.d step A.\n\n `*taken` says whether a request was copied, `*out_len` how\n many bytes, `*seq_out` the sequence number to reply against.\n All three are written only on `NROS_RMW_RET_OK`.\n\n Deviations from upstream, declared: the payload is bytes\n (`buf` / `buf_len` / `*out_len`) rather than a typed\n `void *ros_request`, and `*seq_out` stands in for\n `rmw_service_info_t *` — an RTOS reply needs the sequence\n and nothing else in that struct."]
432 pub take_request: ::core::option::Option<
433 unsafe extern "C" fn(
434 server: *const rmw_service_t,
435 request: *mut rmw_mut_byte_span_t,
436 seq_out: *mut i64,
437 taken: *mut bool,
438 ) -> rmw_ret_t,
439 >,
440 #[doc = " Phase 376 W3.d step A — the service-side sibling of\n `has_data`; same contract, same reason."]
441 pub has_request: ::core::option::Option<
442 unsafe extern "C" fn(server: *mut rmw_service_t, out_has_request: *mut bool) -> rmw_ret_t,
443 >,
444 pub send_response: ::core::option::Option<
445 unsafe extern "C" fn(
446 server: *const rmw_service_t,
447 seq: i64,
448 response: rmw_byte_span_t,
449 ) -> rmw_ret_t,
450 >,
451 pub create_client: ::core::option::Option<
452 unsafe extern "C" fn(
453 node: *const rmw_node_t,
454 type_support: *const rmw_service_type_support_t,
455 service_name: *const core::ffi::c_char,
456 domain_id: u32,
457 qos: *const rmw_qos_profile_t,
458 out: *mut rmw_client_t,
459 ) -> rmw_ret_t,
460 >,
461 pub destroy_client:
462 ::core::option::Option<unsafe extern "C" fn(client: *mut rmw_client_t) -> rmw_ret_t>,
463 #[doc = " Phase 130.4 — non-blocking send_request_raw. Phase-301: the\n deprecated blocking `call_raw` slot is DELETED (rmw has no\n blocking call); this + `try_recv_reply_raw` is the ONE\n request/reply path and both slots are now REQUIRED for a backend\n that supports services.\n\n Sends the request to the backend without blocking for a\n reply. Returns immediately.\n\n `*sequence_id` is the id the backend assigned, written only on\n `NROS_RMW_RET_OK`. Upstream returns it for one reason and it is the\n same reason here: a client with two calls outstanding has nothing else\n to match a reply against.\n\n Issue 0778 — this out-parameter was ABSENT until 2026-08-25, and every\n backend computed the id and threw it away (cyclonedds a\n `RequestId{guid, seq}`, zenoh a `fetch_add` into the rmw attachment,\n xrce `uxr_buffer_request`'s id). With nothing to correlate BY, each\n invented a policy: cyclonedds ABANDONED the first request when a second\n was sent, zenoh took FIRST REPLY WINS on the grounds that \"a queryable\n is idempotent at the application layer\" — which this ABI cannot\n enforce and which is false for `send_goal` and `SetParameters`, both of\n which travel this path. Same application code, different behaviour per\n transport. The id is what deletes both policies."]
464 pub send_request: ::core::option::Option<
465 unsafe extern "C" fn(
466 client: *const rmw_client_t,
467 request: rmw_byte_span_t,
468 sequence_id: *mut i64,
469 ) -> rmw_ret_t,
470 >,
471 #[doc = " Upstream `rmw_take_response`. Same shape and the same\n declared deviations as `take_request`.\n\n `*seq_out` is the `sequence_id` of the request this reply answers,\n written only when `*taken` is true. It is the other half of issue\n 0778: handing the id out at send time is useless if it does not come\n back. Mirrors `take_request`'s `seq_out`, which the SERVER side has\n always had — the asymmetry was the tell.\n\n (The paragraph that used to sit here described `>= 0` = bytes and\n \"other negative = backend error\", the pre-W3.d shape, three phases\n after step A moved the count to an out-parameter and step B made the\n errors positive.)"]
472 pub take_response: ::core::option::Option<
473 unsafe extern "C" fn(
474 client: *const rmw_client_t,
475 reply: *mut rmw_mut_byte_span_t,
476 seq_out: *mut i64,
477 taken: *mut bool,
478 ) -> rmw_ret_t,
479 >,
480 #[doc = " Register a callback for a subscription-side event. NULL function\n pointer = backend doesn't generate any subscription events.\n Specific kind unsupported on a backend that supports some\n events = `NROS_RMW_RET_UNSUPPORTED` return.\n `deadline_ms` is consulted for `REQUESTED_DEADLINE_MISSED`\n only; ignored otherwise."]
481 pub subscription_event_init: ::core::option::Option<
482 unsafe extern "C" fn(
483 subscription: *const rmw_subscription_t,
484 kind: rmw_event_type_t::Type,
485 deadline_ms: u32,
486 cb: rmw_status_event_callback_t,
487 user_context: *mut core::ffi::c_void,
488 ) -> rmw_ret_t,
489 >,
490 #[doc = " Upstream `rmw_take_event`, subscription side.\n\n `*taken` says whether an event was copied into `*out`; both are written\n only on `NROS_RMW_RET_OK`. `kind` selects which event to drain, and\n which member of the payload union is valid.\n\n Deviations from upstream, declared: no `rmw_event_t *` — that handle is\n declined, so the entity plus the kind identifies the event — and the\n payload is our `rmw_event_payload_t` union rather than a `void *` the\n caller must know the shape of.\n\n NULL is the normal answer for a backend that delivers status events\n through the `*_event_init` callback and has a safe context to do it\n from. It is NOT the right answer for a backend whose notifications\n arrive on a thread of its own."]
491 pub subscription_take_event: ::core::option::Option<
492 unsafe extern "C" fn(
493 subscription: *const rmw_subscription_t,
494 kind: rmw_event_type_t::Type,
495 out: *mut rmw_event_payload_t,
496 taken: *mut bool,
497 ) -> rmw_ret_t,
498 >,
499 #[doc = " Upstream `rmw_take_event`, publisher side. Same contract as\n `subscription_take_event`; `rmw_take_event` is recorded as GROUPED onto\n that one, since upstream has a single name for both."]
500 pub publisher_take_event: ::core::option::Option<
501 unsafe extern "C" fn(
502 publisher: *const rmw_publisher_t,
503 kind: rmw_event_type_t::Type,
504 out: *mut rmw_event_payload_t,
505 taken: *mut bool,
506 ) -> rmw_ret_t,
507 >,
508 pub publisher_event_init: ::core::option::Option<
509 unsafe extern "C" fn(
510 publisher: *const rmw_publisher_t,
511 kind: rmw_event_type_t::Type,
512 deadline_ms: u32,
513 cb: rmw_status_event_callback_t,
514 user_context: *mut core::ffi::c_void,
515 ) -> rmw_ret_t,
516 >,
517 #[doc = " Phase 108.B — manually assert this publisher's liveliness.\n Required for `MANUAL_BY_TOPIC` / `MANUAL_BY_NODE` liveliness\n kinds; no-op (return `NROS_RMW_RET_OK`) for other kinds.\n NULL function pointer = backend doesn't support manual\n liveliness; runtime returns `NROS_RMW_RET_OK` for AUTOMATIC /\n NONE callers and `NROS_RMW_RET_UNSUPPORTED` for MANUAL_*."]
518 pub publisher_assert_liveliness: ::core::option::Option<
519 unsafe extern "C" fn(publisher: *const rmw_publisher_t) -> rmw_ret_t,
520 >,
521 #[doc = " Phase 110.0 — backend's next internal-event deadline in\n milliseconds from now (lease keepalive, heartbeat, reader\n ACK-NACK timeout, etc.). The runtime caps its `drive_io`\n timeout against `min(user_timeout, timer_deadline, this)` so\n quiet links don't wake early, see no user-visible work, and\n round-trip back into `drive_io`.\n\n Phase 376 W3.d step A — `*out_ms` carries the value and\n `*has_deadline` whether there is one; both are written only\n on `NROS_RMW_RET_OK`.\n\n This slot was the ONE member of the eleven that step B's\n renumbering did not force: its old negative return was a\n \"no deadline\" SENTINEL, not an error code, so nothing would\n have collided. It is converted anyway because it had the\n shape every other conversion found a silent failure in — a\n backend that FAILED to compute its deadline returned `-1`\n and was read as \"quiet link\", which is exactly the reading\n that makes the executor sleep longer. It now has an error\n channel it never had.\n\n NULL function pointer is permitted — the runtime treats it\n the same as `*has_deadline = false`."]
522 pub next_deadline_ms: ::core::option::Option<
523 unsafe extern "C" fn(
524 session: *const rmw_session_t,
525 out_ms: *mut u32,
526 has_deadline: *mut bool,
527 ) -> rmw_ret_t,
528 >,
529 #[doc = " Phase 124.B.1 — executor wake callback.\n\n The runtime calls this once per session right after `open`\n with `cb` pointing at a runtime-supplied function and `ctx`\n pointing at the executor's wake state. The backend stores\n both in its per-session state and calls `cb(ctx)` whenever\n its transport-notification path fires — datagram arrival,\n condvar wake-up, select-fd ready, etc. The runtime cb does\n flag-write + condvar-signal atomically so a `spin_once`\n blocked on the wake condvar resumes immediately.\n\n `cb == NULL` clears any previously installed callback; the\n backend must drop the stored (cb, ctx) and never invoke\n again after this returns.\n\n NULL slot = backend has no asynchronous wake path (purely\n poll-driven: XRCE, bare-metal). The runtime still drains the\n session on its deadline-bound cv-wait boundary."]
530 pub set_wake_callback: ::core::option::Option<
531 unsafe extern "C" fn(
532 session: *mut rmw_session_t,
533 cb: ::core::option::Option<unsafe extern "C" fn(ctx: *mut core::ffi::c_void)>,
534 ctx: *mut core::ffi::c_void,
535 ) -> rmw_ret_t,
536 >,
537 #[doc = " Phase 124.A — zero-copy publisher loan.\n\n Reserve a writable slot of at least `requested_len` bytes inside\n the backend's outbound buffer. Returns:\n * `NROS_RMW_RET_OK` + writes `*out_buf` / `*out_cap` / `*out_token`.\n * `NROS_RMW_RET_TRY_AGAIN` if the backend has no slot\n available (caller may retry or fall back to a copy path).\n * `NROS_RMW_RET_INVALID_ARGUMENT` on bad pointers / size.\n\n `*out_cap` may exceed `requested_len`. The slot's bytes are\n valid until the matching `pub_commit` or `pub_discard` runs.\n `*out_token` is an opaque per-loan handle the backend uses to\n match commit / discard back to the right slot.\n\n NULL function pointer = backend doesn't natively lend; the\n runtime falls back to a per-publisher staging arena and emits\n a single memcpy on commit."]
538 pub borrow_loaned_message: ::core::option::Option<
539 unsafe extern "C" fn(
540 publisher: *const rmw_publisher_t,
541 requested_len: usize,
542 out_slot: *mut rmw_mut_byte_span_t,
543 out_token: *mut *mut rmw_loan_token_t,
544 ) -> rmw_ret_t,
545 >,
546 #[doc = " Phase 124.A — commit a previously loaned slot.\n\n `token` MUST be a value returned from a prior `pub_loan` on the\n same publisher. `actual_len` is the byte count actually\n written into the slot (≤ the loan's `out_cap`). Triggers the\n wire send.\n\n NULL = paired NULL with `pub_loan`."]
547 pub publish_loaned_message: ::core::option::Option<
548 unsafe extern "C" fn(
549 publisher: *const rmw_publisher_t,
550 token: *mut rmw_loan_token_t,
551 actual_len: usize,
552 ) -> rmw_ret_t,
553 >,
554 #[doc = " Phase 124.A — abandon a previously loaned slot.\n\n Releases the slot without sending. `token` MUST be a value\n returned from a prior `pub_loan` on the same publisher.\n\n NULL = paired NULL with `pub_loan`."]
555 pub return_loaned_message_from_publisher: ::core::option::Option<
556 unsafe extern "C" fn(
557 publisher: *const rmw_publisher_t,
558 token: *mut rmw_loan_token_t,
559 ) -> rmw_ret_t,
560 >,
561 #[doc = " Phase 124.A — zero-copy subscription borrow.\n\n Borrow a read-only view of the next available message in\n place, without copying into a caller buffer. Returns:\n * `>= 0` — message length; writes `*out_buf` / `*out_token`.\n * `0` — no message ready (subscription empty).\n * `< 0` — error (see `rmw_ret_t` codes negated).\n\n The view is valid until the matching `sub_release` runs.\n Only one borrow may be outstanding per subscription at a time —\n callers MUST release before requesting another borrow.\n\n NULL function pointer = backend doesn't natively borrow; the\n runtime falls back to `try_recv_raw` into a staging buffer. */\n/** Upstream `rmw_take_loaned_message`. Phase 376 W3.b/W3.d step A.\n\n `*taken` says whether a view was handed out; `*out_buf`,\n `*out_len` and `*out_token` describe it and are meaningful\n only when taken. All are written only on\n `NROS_RMW_RET_OK`.\n\n Before this, the length was returned AND written to\n `*out_len`, and the runtime used the return — so a backend\n that disagreed with itself had one of its two answers\n silently ignored. There is now one length.\n\n Deviation from upstream, declared: upstream loans a typed\n `void **loaned_message`; ours is a byte view plus an opaque\n token to release, because there is no typesupport on target\n and the backend owns the buffer until `sub_release`.\n\n **No backend fills this slot today** (Cyclone NULL, XRCE NULL, and the\n Rust adapter leaves it at `EMPTY_VTABLE`), so every `try_borrow` through\n the C ABI takes the copy fallback. The slot is carried, not deleted,\n because it is the only shape that can hand a view to a caller which\n outlives the call — `nros-c` / `nros-cpp` `try_borrow` — where\n `process_raw_in_place`'s scoped callback cannot reach. Zenoh's native\n zero-copy receive is live but arrives through Rust\n `SlotBorrowing for ZenohSubscriber`, not through here. Recorded so\n \"the slot exists\" is not read as \"the capability works\": issue 0781."]
562 pub take_loaned_message: ::core::option::Option<
563 unsafe extern "C" fn(
564 subscription: *const rmw_subscription_t,
565 out_view: *mut rmw_byte_span_t,
566 out_token: *mut *mut rmw_loan_token_t,
567 taken: *mut bool,
568 ) -> rmw_ret_t,
569 >,
570 #[doc = " Phase 124.A — release a previously borrowed view.\n\n `token` MUST be a value returned from a prior `sub_borrow`\n on the same subscription. Lets the next message advance into\n the buffer.\n\n NULL = paired NULL with `sub_borrow`."]
571 pub return_loaned_message_from_subscription: ::core::option::Option<
572 unsafe extern "C" fn(
573 subscription: *const rmw_subscription_t,
574 token: *mut rmw_loan_token_t,
575 ) -> rmw_ret_t,
576 >,
577 #[doc = " Phase 124.C.1 — service-server availability probe.\n\n Returns `1` if ≥ 1 matching server has been discovered on the\n RMW graph, `0` if none yet, or a negative `rmw_ret_t`\n constant on backend error. The runtime exposes this to user\n code as `nros_client_server_available()` /\n `Client<S>::server_available()` — clients use it to gate the\n first request so a startup-ordering race doesn't surface as\n a request-side timeout.\n\n Implementation notes per backend:\n - **Zenoh**: `z_session` tracks matched queryables via\n interest declarations.\n - **Cyclone DDS / dust-DDS**: built-in topic readers expose\n matched-pub counts.\n - **XRCE**: agent has no participant enumeration; return\n `NROS_RMW_RET_UNSUPPORTED`.\n\n NULL function pointer = backend cannot answer; the runtime\n surfaces `NROS_RMW_RET_UNSUPPORTED` to the caller.\n\n Phase 376 W3.d step A — upstream's shape: the STATUS is the\n return value and the answer is an out-parameter. Previously\n this slot multiplexed both through one `int32_t` (1 = yes,\n 0 = no, negative = error), which is what makes upstream's\n positive `RMW_RET_ERROR = 1` unadoptable — `1` would mean\n both \"available\" and \"failed\". Splitting them is what lets\n step B renumber at all.\n\n A backend writes `*out_available` only on\n `NROS_RMW_RET_OK`; on any error the caller's value is\n untouched. The old contract's tolerance for \"any positive\n value other than 1 means available\" is gone with the int:\n a `bool` has no non-spec value to be lenient about.\n\n Deviation from upstream, declared: no `node` parameter.\n `rmw_service_server_is_available` takes both a node and a\n client; an image has no node object to pass — the client\n reaches its session directly."]
578 pub service_server_is_available: ::core::option::Option<
579 unsafe extern "C" fn(client: *const rmw_client_t, out_available: *mut bool) -> rmw_ret_t,
580 >,
581 #[doc = " Phase 124.D.1 — burst-take.\n\n Drains up to `max_msgs` queued messages into a contiguous\n caller buffer in a single backend call, avoiding N × vtable\n dispatch when a burst-sensor subscription catches up on a\n backlog (e.g. a 100 Hz IMU feed polled at 10 Hz).\n\n Storage contract:\n * `buf` is a contiguous `max_msgs * per_msg_cap` block.\n * The i-th delivered message lives at `buf + i * per_msg_cap`\n and has byte length `out_lens[i]`.\n * `out_lens` is at least `max_msgs` entries long.\n\n Returns:\n * `>= 0` — count of messages taken (0..=max_msgs).\n * `< 0` — `rmw_ret_t` error code; partial drains MUST\n use the count form, not error-out.\n\n NULL function pointer = backend doesn't natively batch; the\n runtime emits a `try_recv_raw` loop fallback in\n `CffiSubscriber::try_recv_sequence`. The fallback gives\n identical observable behaviour (each call still costs N\n vtable hops) but lets user code commit to the batched API. */\n/** Upstream `rmw_take_sequence`. Phase 376 W3.b/W3.d step A —\n the COUNT moves to `*taken`, matching upstream's\n `size_t *taken`, and the return carries only a status.\n `*taken` is written only on `NROS_RMW_RET_OK`; a partial\n drain reports what it got rather than erroring.\n\n Issue 0971 — which leaves a question the count alone cannot\n answer: WHY the drain stopped. A batch that ends because a\n message did not fit `per_msg_cap` returns the same shape as\n one that drained the reader, and the message that stopped it\n is consumed — deliberately, for the reason the single take\n consumes it too: a sample left behind that no caller can\n ever take is a stuck subscription\n (`nros-verification`'s `try_recv_post_fix` /\n `no_silent_truncation`).\n\n So a backend that stops a drain for a reason the caller must\n hear PARKS that status on the subscription and returns it\n from the NEXT `take` or `take_sequence`, which takes nothing\n else that call. That rule is what makes the fallback note\n above true rather than aspirational: without it the runtime's\n `try_recv_raw` loop and a native batch answer the same\n condition differently — the loop erroring out and discarding\n the count it had already earned, the native path reporting a\n count and no reason."]
582 pub take_sequence: ::core::option::Option<
583 unsafe extern "C" fn(
584 subscription: *const rmw_subscription_t,
585 buf: *mut u8,
586 per_msg_cap: usize,
587 max_msgs: usize,
588 out_lens: *mut usize,
589 taken: *mut usize,
590 ) -> rmw_ret_t,
591 >,
592 #[doc = " Phase 124.E.1 — streamed publish.\n\n Caller hands the backend two callbacks. The backend invokes\n `size_cb` once to learn the total payload length, allocates\n a single slot of that size in its outbound buffer, then\n invokes `chunk_cb` repeatedly to fill the slot in chunks\n until the buffer is full. What it saves is the CALLER-side\n buffer holding a whole serialised message.\n\n That justification used to read \"saves the per-publisher\n staging buffer … where the staging buffer dominates `.bss`\",\n which described a design this tree does not have (issue 0782).\n There is no per-publisher buffer and none of it is in `.bss`:\n `EmbeddedPublisher::publish` serialises into a per-CALL STACK\n array of `DEFAULT_TX_BUF` (= `NROS_SUBSCRIPTION_BUFFER_SIZE`,\n 1024 by default), and the runtime's own NULL-slot fallback\n stages into a 4 KiB stack array and refuses anything larger.\n The saving is real and it is STACK — which on an MCU with\n small per-task stacks is the tighter budget of the two — but a\n reason has to name the thing it is about.\n\n Callback contract:\n * `size_cb(*out_total_len, user_ctx)` — write the exact\n total payload length, in bytes, to `*out_total_len`.\n Called exactly once per `publish_streamed` invocation.\n * `chunk_cb(out_buf, cap, *out_written, user_ctx)` —\n write up to `cap` bytes starting at `out_buf`, then\n report the count written via `*out_written`. The backend\n may call `chunk_cb` repeatedly until the total promised\n by `size_cb` has been delivered. `*out_written == 0`\n means EOF; the backend tears down the slot.\n\n Lesson from micro-ROS's\n `rmw_uros_set_continous_serialization_callbacks`: pass the\n callbacks per-call rather than binding them to publisher\n state, so different messages on the same publisher can use\n different serialisation strategies.\n\n NULL function pointer = backend doesn't stream; the runtime\n falls back to a one-shot staging buffer (capped at the\n configured `NROS_MAX_STREAM_CHUNK`) + `publish_raw`."]
593 pub publish_streamed: ::core::option::Option<
594 unsafe extern "C" fn(
595 publisher: *mut rmw_publisher_t,
596 size_cb: ::core::option::Option<
597 unsafe extern "C" fn(out_total_len: *mut usize, user_ctx: *mut core::ffi::c_void),
598 >,
599 chunk_cb: ::core::option::Option<
600 unsafe extern "C" fn(
601 out_buf: *mut u8,
602 cap: usize,
603 out_written: *mut usize,
604 user_ctx: *mut core::ffi::c_void,
605 ),
606 >,
607 user_ctx: *mut core::ffi::c_void,
608 ) -> rmw_ret_t,
609 >,
610 #[doc = " Phase 124.F.1 — session-level connectivity probe.\n\n Sends a wire-level round-trip probe (\"is the peer / agent /\n router still reachable?\") and waits up to `timeout_ms` for\n a reply. No discovery state required — cheaper than the\n service-availability probe (which needs matched-publication\n bookkeeping). Lesson from micro-ROS's\n `rmw_uros_ping_agent`.\n\n Returns:\n * `NROS_RMW_RET_OK` — peer responded within budget.\n * `NROS_RMW_RET_TIMEOUT` — no reply before `timeout_ms`.\n * `NROS_RMW_RET_UNSUPPORTED` — backend can't probe (DDS\n with no participant introspection).\n * other negative — backend error.\n\n Implementation notes per backend:\n - **Zenoh**: `z_send_ping` (or session keep-alive piggyback).\n - **XRCE**: `uxr_ping_agent_session_until_timeout`.\n - **DDS**: built-in participant ping if available, else\n `RET_UNSUPPORTED`.\n\n NULL function pointer = runtime surfaces\n `NROS_RMW_RET_UNSUPPORTED` to the caller."]
611 pub ping_session: ::core::option::Option<
612 unsafe extern "C" fn(session: *mut rmw_session_t, timeout_ms: i32) -> rmw_ret_t,
613 >,
614 #[doc = " Capability query: does this subscription support process_raw_in_place()?\n The runtime consults it once at subscription registration to choose\n in-place dispatch over the buffered (copying) path.\n\n `*out_supports` is written only on `NROS_RMW_RET_OK` (Phase 376 W3.d\n step A — capability out, status returned).\n\n **The capability is the CONJUNCTION** of this probe answering true and\n `process_raw_in_place` being non-NULL. Either alone is unsupported.\n\n Why the probe is not redundant with that nullity (issue 0781 proposed\n deleting it, and this is the counterexample that stopped it):\n `RustBackendAdapter::<R>::VTABLE` is a `const`, so it installs\n `process_raw_in_place` for EVERY `R: RustBackend` — the slot is non-NULL\n whatever the backend can do. The Rust-side answer is a runtime `&self`\n method (`Subscription::supports_process_in_place`) because\n `CffiSubscription` multiplexes over whichever backend registered, so it\n cannot become an associated const the vtable initializer could branch\n on. Today `nros-rmw-zenoh` says true and `nros-rmw-metadata` takes the\n `false` default behind an identically-shaped vtable: two backends, same\n nullity, different capability. Deriving from nullity would route\n metadata's subscriptions into in-place dispatch and every take would\n return `MessageTooLarge` from the trait default.\n\n A C backend that knows its answer at compile time (XRCE says true,\n Cyclone leaves both NULL) may express it with nullity alone; the\n conjunction makes that spelling correct without making it the only one.\n\n NULL function pointer = treated as unsupported (buffered path)."]
615 pub subscription_supports_in_place: ::core::option::Option<
616 unsafe extern "C" fn(
617 subscription: *mut rmw_subscription_t,
618 out_supports: *mut bool,
619 ) -> rmw_ret_t,
620 >,
621 #[doc = " Borrow one ready message in place: hand its raw CDR bytes to `cb` (with\n the opaque `ctx`) for the duration of the call, then release the slot.\n `cb` MUST NOT re-enter this subscription's receive.\n\n Deviation from upstream, declared. Avoiding the copy is NOT the reason —\n upstream already has a name for that, `rmw_take_loaned_message`, and we\n carry it (`take_loaned_message`). What this shape buys is that the\n borrow is SCOPED: it ends when `cb` returns, so there is no release\n token to hold and nothing a caller can forget. Upstream's loan is\n unscoped — a caller who misses\n `rmw_return_loaned_message_from_subscription` retires one entry of a\n fixed-depth receive ring for good, and a target with no reclaim and no\n swap does not get that entry back. That is the RTOS constraint; \"no\n copy\" is a property both shapes share.\n\n Both are carried because they answer different callers: this one serves\n dispatch from inside the executor, where the callback frame is the\n natural scope, and the loan pair serves `nros-c` / `nros-cpp`, whose\n `try_borrow` hands a view back to a caller that outlives the call.\n\n NULL function pointer = unsupported (the runtime uses the buffered\n path); see `subscription_supports_in_place` for the conjunction. */\n/** Phase 376 W3.d step A — \"did it process one\" moves to an\n out-parameter and the return is a plain status.\n\n This retires `NROS_RMW_RET_NO_DATA` from this slot: an empty\n subscription is `*out_processed = false` with\n `NROS_RMW_RET_OK`, which is upstream's `taken = false`\n semantics. A sentinel that means \"fine, but nothing\" is\n exactly the shape that makes a status enum ambiguous.\n\n `*out_processed` is written only on OK."]
622 pub process_raw_in_place: ::core::option::Option<
623 unsafe extern "C" fn(
624 subscription: *mut rmw_subscription_t,
625 ctx: *mut core::ffi::c_void,
626 cb: ::core::option::Option<
627 unsafe extern "C" fn(ctx: *mut core::ffi::c_void, message: rmw_byte_span_t),
628 >,
629 out_processed: *mut bool,
630 ) -> rmw_ret_t,
631 >,
632 #[doc = " Upstream `rmw_get_implementation_identifier`.\n\n The backend's name, static for the life of the image. A gid is only\n comparable with another carrying the same identifier, which matters here\n because `nros_rmw_cffi_register_named` admits several backends at once.\n\n RESERVED, and NULL in every backend. **There is no runtime fallback**:\n this doc said the runtime answered with the registry name, and no such\n code was ever written — nothing in the tree calls this slot, so a NULL\n one is not \"answered elsewhere\", it is unanswerable. Corrected 2026-08-29\n (phase-393 W2) after grepping for the promised fallback and finding only\n this sentence.\n\n Filling it is NOT what makes the identity correct. The identity that is\n load-bearing today is the one a backend stamps into `rmw_gid_t`, because\n `rmw_compare_gids_equal` compares that string before the bytes — so what\n matters is that a backend has ONE spelling of its name, not that it can\n be asked for it. Cyclone's `kImplementationIdentifier` is that shape.\n\n Do not fill this slot to move a counter: `check-rmw-slot-producers`\n classifies any slot with a producer as `produced` whether or not\n anything reads it, which is exactly the overstatement issue 0800 exists\n to catch. It earns a body when a CALLER exists — a bridge image asking\n which of two linked backends it is on."]
633 pub get_implementation_identifier:
634 ::core::option::Option<unsafe extern "C" fn() -> *const core::ffi::c_char>,
635 #[doc = " Upstream `rmw_get_serialization_format`.\n\n The backend's wire encoding, as its cross-image identity STRING\n (RFC-0088 D2: the `u8` discriminant is assigned per image and means\n nothing outside it; the name is what two images can agree on). Static\n for the life of the image, like the identifier above.\n\n PRODUCED by every backend since phase-421 W2. It was reserved until\n then for a stated reason — \"every backend here speaks CDR, nothing\n asks, so a body would be parity shape with no reader\" — and both halves\n of that reason have since stopped being true. uORB's wire is the PX4\n struct verbatim (RFC-0011), so it answers `\"uorb\"` where every other\n backend answers `\"cdr\"`; and `nros_rmw_cffi_register_named` admits\n several backends in one image, so a bridge has two sessions whose\n formats differ and one compile-time constant cannot describe both.\n `CffiSession::serialization_format` is the reader.\n\n This is the ONLY per-session answer. `nros_node::IMAGE_SERIALIZATION_\n FORMAT` and the generated `NROS_SERIALIZATION_FORMAT` macro are\n compile-time constants and are meaningful only in a single-backend\n image; ask the slot whenever the image links more than one.\n\n NULL slot: the runtime answers NULL — it does NOT guess `\"cdr\"`. A\n backend that does not declare its format has not told anyone what it\n speaks, and inventing an answer is how the identifier slot's doc came\n to promise a fallback nobody had written (corrected phase-393 W2)."]
636 pub get_serialization_format:
637 ::core::option::Option<unsafe extern "C" fn() -> *const core::ffi::c_char>,
638 #[doc = " Upstream `rmw_feature_supported`.\n\n Whether the backend populates an optional piece of CONTENT — upstream's\n two values both concern message-info sequence numbers. Deliberately not\n expressed as slot nullity: a NULL pointer says the backend cannot\n perform an OPERATION, which is a different question from whether the\n data an implemented operation returns is populated.\n\n NULL slot: the runtime answers `false` for every feature."]
639 pub feature_supported:
640 ::core::option::Option<unsafe extern "C" fn(feature: rmw_feature_t::Type) -> bool>,
641 #[doc = " Upstream `rmw_get_gid_for_publisher`. Exact parity.\n\n The backend zero-pads to the full width; see `rmw_gid_t`."]
642 pub get_gid_for_publisher: ::core::option::Option<
643 unsafe extern "C" fn(publisher: *const rmw_publisher_t, gid: *mut rmw_gid_t) -> rmw_ret_t,
644 >,
645 #[doc = " Upstream `rmw_publisher_count_matched_subscriptions`. Exact parity.\n\n Every backend already tracks this to implement liveliness events — see\n `service_server_is_available`, which is the same question one entity\n over. NULL where a backend has no discovery at all (XRCE)."]
646 pub publisher_count_matched_subscriptions: ::core::option::Option<
647 unsafe extern "C" fn(
648 publisher: *const rmw_publisher_t,
649 subscription_count: *mut usize,
650 ) -> rmw_ret_t,
651 >,
652 #[doc = " Upstream `rmw_subscription_count_matched_publishers`. Exact parity."]
653 pub subscription_count_matched_publishers: ::core::option::Option<
654 unsafe extern "C" fn(
655 subscription: *const rmw_subscription_t,
656 publisher_count: *mut usize,
657 ) -> rmw_ret_t,
658 >,
659 #[doc = " Upstream `rmw_publisher_get_actual_qos`. Exact parity.\n\n We bake the REQUESTED profile and, until now, never read back the\n GRANTED one. On DDS the two differ whenever a writer and reader\n negotiate, and the difference is exactly what answers \"why is nothing\n arriving\" — so a consumer that cannot ask has to guess.\n\n PARTIAL ANSWERS ARE ALLOWED (W5/B2). A backend that can determine four\n policies and not the fifth writes the four it knows and\n `*_UNKNOWN` for the fifth, then returns `NROS_RMW_RET_OK`. Until the\n policy values took upstream's numbering there was no `UNKNOWN` to write,\n so the contract had to be all-or-nothing — a partial answer would have\n been indistinguishable from a confident one, and W4 chose\n `NROS_RMW_RET_UNSUPPORTED` over lying.\n\n `NROS_RMW_RET_UNSUPPORTED` now means what it says: this backend has no\n read-back at all. It is NOT the answer for \"I know some of it\".\n\n A caller that treats `UNKNOWN` as a value rather than as an absence gets\n a wrong comparison, so `rmw_qos_profile_check_compatible` reports it as\n a WARNING rather than an incompatibility — upstream's\n `RMW_QOS_COMPATIBILITY_WARNING`, which was unreachable here until there\n was a sentinel to trigger it.\n\n Six upstream entry points, six slots, deliberately: the name rule is\n mechanical so that no alias table has to be authored and kept true.\n Backends share ONE helper and write six one-line thunks — sharing an\n implementation is free, sharing an ABI slot is not."]
660 pub publisher_get_actual_qos: ::core::option::Option<
661 unsafe extern "C" fn(
662 publisher: *const rmw_publisher_t,
663 qos: *mut rmw_qos_profile_t,
664 ) -> rmw_ret_t,
665 >,
666 #[doc = " Upstream `rmw_subscription_get_actual_qos`. Exact parity."]
667 pub subscription_get_actual_qos: ::core::option::Option<
668 unsafe extern "C" fn(
669 subscription: *const rmw_subscription_t,
670 qos: *mut rmw_qos_profile_t,
671 ) -> rmw_ret_t,
672 >,
673 #[doc = " Upstream `rmw_client_request_publisher_get_actual_qos`. Exact parity.\n\n The four service/client read-backs carry information available NOWHERE\n else: `rmw_client_t` and `rmw_service_t` have no `qos` field, and\n `create_client` / `create_service` take ONE profile for both\n directions, so the granted per-direction profile is otherwise\n unobservable."]
674 pub client_request_publisher_get_actual_qos: ::core::option::Option<
675 unsafe extern "C" fn(client: *const rmw_client_t, qos: *mut rmw_qos_profile_t) -> rmw_ret_t,
676 >,
677 #[doc = " Upstream `rmw_client_response_subscription_get_actual_qos`."]
678 pub client_response_subscription_get_actual_qos: ::core::option::Option<
679 unsafe extern "C" fn(client: *const rmw_client_t, qos: *mut rmw_qos_profile_t) -> rmw_ret_t,
680 >,
681 #[doc = " Upstream `rmw_service_request_subscription_get_actual_qos`."]
682 pub service_request_subscription_get_actual_qos: ::core::option::Option<
683 unsafe extern "C" fn(
684 service: *const rmw_service_t,
685 qos: *mut rmw_qos_profile_t,
686 ) -> rmw_ret_t,
687 >,
688 #[doc = " Upstream `rmw_service_response_publisher_get_actual_qos`."]
689 pub service_response_publisher_get_actual_qos: ::core::option::Option<
690 unsafe extern "C" fn(
691 service: *const rmw_service_t,
692 qos: *mut rmw_qos_profile_t,
693 ) -> rmw_ret_t,
694 >,
695 #[doc = " Upstream `rmw_publisher_wait_for_all_acked`.\n\n Blocks until every sample this publisher sent has been acknowledged, or\n the timeout elapses. Without it an image that publishes and then halts\n cannot know whether anything left the box.\n\n Deviation from upstream, declared: `uint32_t timeout_ms` for upstream's\n by-value `rmw_time_t`. Every duration in this ABI is u32 milliseconds\n (issue 0241) — one width, one unit, no per-call struct.\n\n Best-effort backends (zenoh best-effort, XRCE) leave this NULL."]
696 pub publisher_wait_for_all_acked: ::core::option::Option<
697 unsafe extern "C" fn(publisher: *const rmw_publisher_t, timeout_ms: u32) -> rmw_ret_t,
698 >,
699 #[doc = " Upstream `rmw_take_with_info`.\n\n `take` plus the sample's metadata, written to caller-owned storage. See\n `rmw_message_info_t` for why this is a pointer parameter rather than the\n side table the runtime uses today.\n\n Deviations from upstream, declared: the same two `take` declares —\n bytes (`buf`/`buf_len`/`*out_len`) instead of a typed `void *`, because\n there is no typesupport on target; and no allocation argument, because\n nothing in this ABI can produce upstream's opaque\n `rmw_subscription_allocation_t` — see `take`, which carries the full\n reason and the two wrong ones that preceded it.\n\n NULL slot: the runtime falls back to `take`, and the caller gets no\n metadata — which is exactly today's behaviour for every C backend."]
700 pub take_with_info: ::core::option::Option<
701 unsafe extern "C" fn(
702 subscription: *const rmw_subscription_t,
703 message: *mut rmw_mut_byte_span_t,
704 taken: *mut bool,
705 message_info: *mut rmw_message_info_t,
706 ) -> rmw_ret_t,
707 >,
708 #[doc = " Upstream `rmw_take_loaned_message_with_info`.\n\n `take_loaned_message` plus metadata; same deviations as that slot (a\n byte view and an opaque release token rather than a typed loan)."]
709 pub take_loaned_message_with_info: ::core::option::Option<
710 unsafe extern "C" fn(
711 subscription: *const rmw_subscription_t,
712 out_view: *mut rmw_byte_span_t,
713 out_token: *mut *mut rmw_loan_token_t,
714 taken: *mut bool,
715 message_info: *mut rmw_message_info_t,
716 ) -> rmw_ret_t,
717 >,
718 #[doc = " Upstream `rmw_get_node_names` AND `rmw_get_node_names_with_enclaves`.\n\n One slot, two upstream names: upstream split them only because appending\n to a fixed out-parameter list would have broken its ABI. A visitor has\n no such list, so the enclave is simply a fourth argument, NULL where\n untracked. Recorded in the checker's grouping table."]
719 pub get_node_names: ::core::option::Option<
720 unsafe extern "C" fn(
721 session: *const rmw_session_t,
722 visitor: rmw_node_visitor_t,
723 ) -> rmw_ret_t,
724 >,
725 #[doc = " Upstream `rmw_get_topic_names_and_types`."]
726 pub get_topic_names_and_types: ::core::option::Option<
727 unsafe extern "C" fn(
728 session: *const rmw_session_t,
729 no_demangle: bool,
730 visitor: rmw_names_and_types_visitor_t,
731 ) -> rmw_ret_t,
732 >,
733 #[doc = " Upstream `rmw_get_service_names_and_types`."]
734 pub get_service_names_and_types: ::core::option::Option<
735 unsafe extern "C" fn(
736 session: *const rmw_session_t,
737 visitor: rmw_names_and_types_visitor_t,
738 ) -> rmw_ret_t,
739 >,
740 #[doc = " Upstream `rmw_get_publisher_names_and_types_by_node`."]
741 pub get_publisher_names_and_types_by_node: ::core::option::Option<
742 unsafe extern "C" fn(
743 session: *const rmw_session_t,
744 node_name: *const core::ffi::c_char,
745 node_namespace: *const core::ffi::c_char,
746 no_demangle: bool,
747 visitor: rmw_names_and_types_visitor_t,
748 ) -> rmw_ret_t,
749 >,
750 #[doc = " Upstream `rmw_get_subscriber_names_and_types_by_node`."]
751 pub get_subscriber_names_and_types_by_node: ::core::option::Option<
752 unsafe extern "C" fn(
753 session: *const rmw_session_t,
754 node_name: *const core::ffi::c_char,
755 node_namespace: *const core::ffi::c_char,
756 no_demangle: bool,
757 visitor: rmw_names_and_types_visitor_t,
758 ) -> rmw_ret_t,
759 >,
760 #[doc = " Upstream `rmw_get_service_names_and_types_by_node`."]
761 pub get_service_names_and_types_by_node: ::core::option::Option<
762 unsafe extern "C" fn(
763 session: *const rmw_session_t,
764 node_name: *const core::ffi::c_char,
765 node_namespace: *const core::ffi::c_char,
766 visitor: rmw_names_and_types_visitor_t,
767 ) -> rmw_ret_t,
768 >,
769 #[doc = " Upstream `rmw_get_client_names_and_types_by_node`."]
770 pub get_client_names_and_types_by_node: ::core::option::Option<
771 unsafe extern "C" fn(
772 session: *const rmw_session_t,
773 node_name: *const core::ffi::c_char,
774 node_namespace: *const core::ffi::c_char,
775 visitor: rmw_names_and_types_visitor_t,
776 ) -> rmw_ret_t,
777 >,
778 #[doc = " Upstream `rmw_get_publishers_info_by_topic`."]
779 pub get_publishers_info_by_topic: ::core::option::Option<
780 unsafe extern "C" fn(
781 session: *const rmw_session_t,
782 topic_name: *const core::ffi::c_char,
783 no_mangle: bool,
784 visitor: rmw_topic_endpoint_info_visitor_t,
785 ) -> rmw_ret_t,
786 >,
787 #[doc = " Upstream `rmw_get_subscriptions_info_by_topic`."]
788 pub get_subscriptions_info_by_topic: ::core::option::Option<
789 unsafe extern "C" fn(
790 session: *const rmw_session_t,
791 topic_name: *const core::ffi::c_char,
792 no_mangle: bool,
793 visitor: rmw_topic_endpoint_info_visitor_t,
794 ) -> rmw_ret_t,
795 >,
796 #[doc = " Upstream `rmw_count_publishers`."]
797 pub count_publishers: ::core::option::Option<
798 unsafe extern "C" fn(
799 session: *const rmw_session_t,
800 topic_name: *const core::ffi::c_char,
801 count: *mut usize,
802 ) -> rmw_ret_t,
803 >,
804 #[doc = " Upstream `rmw_count_subscribers`."]
805 pub count_subscribers: ::core::option::Option<
806 unsafe extern "C" fn(
807 session: *const rmw_session_t,
808 topic_name: *const core::ffi::c_char,
809 count: *mut usize,
810 ) -> rmw_ret_t,
811 >,
812 #[doc = " Upstream `rmw_node_get_graph_guard_condition`.\n\n Registers a callback fired when the graph CHANGES. Upstream returns a\n guard condition the caller adds to a wait set; we have no wait set to\n add it to, and guard conditions are an executor concept here, so this is\n the `set_wake_callback` shape instead — the one guard condition whose\n trigger is genuinely backend knowledge.\n\n The callback is an EDGE, carrying no payload: delivering WHAT changed\n would mean buffering it, which is the graph cache a small target cannot\n afford.\n\n Named after upstream mechanically, per the campaign's rule, but the\n honest name for this shape is `set_on_graph_change_callback` — flagged\n for W5 rather than decided quietly here."]
813 pub node_get_graph_guard_condition: ::core::option::Option<
814 unsafe extern "C" fn(
815 session: *mut rmw_session_t,
816 callback: rmw_event_callback_t,
817 user_data: *const core::ffi::c_void,
818 ) -> rmw_ret_t,
819 >,
820 #[doc = " Upstream `rmw_create_node`.\n\n Declares a node on the graph. NULL slot is the expected implementation\n in a static image: the runtime still tracks the node, the backend simply\n has nothing to declare.\n\n Deviations from upstream, declared: no `rmw_context_t *` (an image has\n one session and reaches it directly), and the node is an OUT parameter\n rather than a returned pointer — no runtime allocation, the caller owns\n the storage, exactly as `create_publisher` does.\n\n The runtime calls this once per distinct `(name, namespace_)`."]
821 pub create_node: ::core::option::Option<
822 unsafe extern "C" fn(
823 session: *mut rmw_session_t,
824 name: *const core::ffi::c_char,
825 namespace_: *const core::ffi::c_char,
826 out: *mut rmw_node_t,
827 ) -> rmw_ret_t,
828 >,
829 #[doc = " Upstream `rmw_destroy_node`.\n\n Releases the backend's `backend_data`; the shell stays valid until its\n owner drops it. Called for every node the session created, from\n `close()`, BEFORE `destroy_session` — a backend's node state hangs off\n its session state, so the order is not free.\n\n NULL slot: nothing is called and the runtime simply forgets the node.\n Correct only for a backend that allocates nothing in `create_node`;\n a backend that fills `backend_data` there must fill this too, or it\n leaks one node's state per session close (issue 0800 — this slot had a\n producer in no backend and a consumer nowhere, so the leak was\n indistinguishable from an optional slot nobody needed)."]
830 pub destroy_node:
831 ::core::option::Option<unsafe extern "C" fn(node: *mut rmw_node_t) -> rmw_ret_t>,
832 #[doc = " Upstream `rmw_set_log_severity`. Exact parity.\n\n Sets the verbosity of the BACKEND's own logging — Cyclone's `dds_log`,\n zenoh-pico's log, the XRCE client's. Not `nros_log`: that is the\n runtime's logger, already runtime-settable through\n `nros_log::Logger::set_level`, and it needs no ABI to reach.\n\n This IS a slot rather than a plain ABI function, and the distinction is\n the one the pure functions turn on: \"what does this middleware print\"\n genuinely varies by middleware, so a per-backend answer is correct here\n rather than a defect. All three reference implementations\n (`librmw_{cyclonedds,fastrtps,zenoh}_cpp.so`) implement it with real\n bodies, which is the opposite of what they do for\n `rmw_get_serialized_message_size`.\n\n Phase 376 W5 — this was DECLINED, on the reasoning that \"log level is a\n build-time constant (nros_log); a runtime setter implies a mutable\n global\". Both clauses were false: `Logger::level` is an `AtomicU8` with a\n public `set_level` already used by tests, and the compile-time part is a\n CEILING that defaults open. The decline described a design we do not\n have.\n\n NULL slot: the backend has no adjustable logging, and the runtime\n surfaces `UNSUPPORTED`."]
833 pub set_log_severity: ::core::option::Option<
834 unsafe extern "C" fn(severity: rmw_log_severity_t::Type) -> rmw_ret_t,
835 >,
836 #[doc = " How many bytes of TAKE buffer this type actually needs, so the runtime\n can stop sizing `take`'s `buf` from one global constant.\n\n RTOS addition; upstream has no counterpart. Upstream's nearest thing is\n `rmw_get_serialized_message_size`, which is about a MESSAGE and which\n all three reference implementations leave unimplemented; this is about\n the buffer the CALLER must present, and the answer is the backend's\n because only the backend knows what its framing, size classes and\n attachment handling add on top of the payload.\n\n `type_name` and `type_hash` name the type (both BORROWED for the call;\n `type_hash` may be NULL exactly as in `create_subscription`). `hint` is\n the same number the runtime would put in\n `rmw_subscription_options_t.rx_buffer_hint`, and carries the same\n meaning, including that `0` says the CALLER stated nothing rather than\n that the type is unbounded — every message type has a derived bound or\n the build fails. On `NROS_RMW_RET_OK`, `*out_bytes` is the MINIMUM\n take-buffer length that is sufficient for this type at this hint: a\n `take` given that many bytes must not fail for want of room, and no\n smaller number has that property. It may exceed `hint` — a backend that\n frames or pads says so here rather than discovering it at `take` time —\n and it may be smaller.\n\n MINIMUM, tightened in phase-403 W4, and the word carries the whole\n value of the slot. A backend that keeps size CLASSES may not answer with\n the class it would round this type up to. Doing so is arithmetically\n safe and useless: a 68-byte type and a 1000-byte type sharing a class\n come back with one number, which is the global-constant answer the\n runtime already had without asking, and the runtime would spend the\n difference on every subscription in the image. Report what the type\n needs; the rounding is the backend's own business and stays there.\n If a backend genuinely cannot separate the two — its framing really\n does make the class size the floor — then the class size IS the\n minimum and answering it is correct; what is forbidden is reporting a\n rounding as though it were a requirement.\n\n This is a QUERY about a type, not about an entity: it is answerable\n before any subscription exists, which is the point — the runtime has to\n size the buffer in order to create one.\n\n NULL slot: FALLBACK class (RFC-0035's NULL-slot contract) — THE HINT IS\n THE ANSWER, never `UNSUPPORTED` to the caller. The runtime uses `hint`,\n and since a type without a derived bound is a build error, that is a\n real number rather than a fallback to a configured default. A backend\n that cannot answer for a PARTICULAR type returns\n `NROS_RMW_RET_UNSUPPORTED` and leaves `*out_bytes` untouched; the\n runtime falls back to the hint exactly as for a NULL slot.\n\n OPTIONAL on the merits, not for compatibility. nano-ros is unreleased\n and this ABI may be broken, so \"a mandatory slot breaks every\n out-of-tree backend\" is not the reason and must not be cited as one.\n Three reasons that survive without it:\n\n - A SLOT CANNOT BE REQUIRED BEFORE SOMETHING DISPATCHES IT. Required\n here means `first_missing_vtable_slot` REFUSES to register a backend\n that leaves it NULL, and `check-rmw-required-slots.sh` holds that set\n equal to the set the runtime `.expect()`s. Nothing calls this yet —\n phase-403 W3/W5 own the dispatch site — so requiring it now would\n refuse working backends over a function no caller reaches. That is\n issue 0349 exactly, and it cost three backends their registration\n once. (W4 filled the slot for zenoh-pico, which makes the slot\n PRODUCED; it is the CONSUMER that decides whether required is\n reachable, and there is still none.)\n - \"NO OPINION\" IS A REAL ANSWER, and mandatory does not delete it, only\n relocates it: five in-tree backends would each carry the same\n `*out_bytes = hint; return OK;` body, and the Rust ones would get it\n from a defaulted `RustBackend` trait method — the same special case,\n one layer up and less visible.\n - IT IS SLOT 75, AND TWO BACKENDS INITIALISE POSITIONALLY. uORB's\n C++14 initialiser stops at slot 17 and positional initialisation\n cannot skip, so reaching a mandatory slot 75 means writing 58\n meaningless entries to get there.\n\n Promotion stays cheap and stays open: making this required later is a\n change to the registration check, not to the struct.\n\n DECIDED 2026-08-31: this slot stays OPTIONAL permanently. Phase-403 W4\n recommended it after filling the slot for zenoh-pico, and that is now a\n ruling rather than a recommendation, so promotion is off the table\n unless the reason below stops being true.\n The first argument above dissolves once a dispatch site exists, but the\n other two do not, and they are the load-bearing ones: cyclonedds and\n XRCE keep ONE receive buffer, so \"no opinion\" is their true answer and\n requiring the slot only relocates it into identical bodies; and slot 75\n is out of reach of uORB's positional C++14 initialiser whatever the\n registration check says. A slot that most backends must fill with a\n restatement of the default is worse than a NULL whose meaning the\n header pins down, which this one does.\n\n It does not weaken `take`'s obligation. The runtime may pass a `buf_len`\n smaller than what this returned — it has its own memory to answer to —\n and a sample that does not fit is still a reported failure rather than\n a truncated success. Answering here is how a backend avoids that\n outcome, not how it licenses one."]
837 pub required_rx_bytes: ::core::option::Option<
838 unsafe extern "C" fn(
839 type_name: *const core::ffi::c_char,
840 type_hash: *const core::ffi::c_char,
841 hint: usize,
842 out_bytes: *mut usize,
843 ) -> rmw_ret_t,
844 >,
845}
846#[doc = " Runtime-pluggable custom transport. The runtime never\n dereferences `user_data`; it's the caller's per-transport\n context, threaded back into every callback's first argument.\n\n THIS declaration is the ABI single source of truth (RFC-0054): Rust\n consumes the committed bindgen output of this header, and\n `nros_rmw::NrosTransportOps` is the hand-written Rust-side view kept in\n lockstep with it — not the other way round. The previous wording had that\n backwards (issue 0331). Layout equivalence is asserted on both sides: see\n `nros_transport_ops_t` in `nros-rmw-cffi/tests/c_stubs/abi_layout_check.c`\n and the `const _` size/align block beside\n `nros_rmw_cffi_set_custom_transport` in `nros-rmw-cffi/src/lib.rs`. Same\n layout, same threading contract, same return codes."]
847#[repr(C)]
848#[derive(Debug, Copy, Clone)]
849pub struct nros_transport_ops_s {
850 #[doc = " ABI version. MUST equal `NROS_TRANSPORT_OPS_ABI_VERSION_V1`.\n Any other value is rejected at registration time with\n `NROS_RMW_RET_INCOMPATIBLE_ABI`."]
851 pub abi_version: u32,
852 #[doc = " Reserved padding for alignment stability across appends.\n Set to zero."]
853 pub _reserved: u32,
854 #[doc = " Opaque caller context, threaded back into every callback.\n Lifetime: must outlive the transport's active period\n (i.e. until `close` returns)."]
855 pub user_data: *mut core::ffi::c_void,
856 #[doc = " Open the underlying medium.\n\n @param user_data Caller-supplied context.\n @param params Opaque per-transport metadata (e.g. UART baud\n rate, USB-CDC endpoint id). May be NULL.\n @retval NROS_RMW_RET_OK on success.\n @retval <0 on failure (any `rmw_ret_t` error code)."]
857 pub open: ::core::option::Option<
858 unsafe extern "C" fn(
859 user_data: *mut core::ffi::c_void,
860 params: *const core::ffi::c_void,
861 ) -> i32,
862 >,
863 #[doc = " Tear the transport down. After `close` returns, the runtime\n will not invoke `read` or `write` on this transport unless\n `nros_set_custom_transport` is called again."]
864 pub close: ::core::option::Option<unsafe extern "C" fn(user_data: *mut core::ffi::c_void)>,
865 #[doc = " Send `len` bytes from `buf`. Must NOT block beyond a brief\n hardware retry; long blocking should surface as\n `NROS_RMW_RET_TIMEOUT`.\n\n @retval NROS_RMW_RET_OK on success.\n @retval <0 on failure (any `rmw_ret_t` error code)."]
866 pub write: ::core::option::Option<
867 unsafe extern "C" fn(user_data: *mut core::ffi::c_void, buf: *const u8, len: usize) -> i32,
868 >,
869 #[doc = " Receive up to `len` bytes into `buf` within `timeout_ms`.\n\n @retval >=0 number of bytes read (may be less than `len`).\n @retval <0 on error / timeout (any `rmw_ret_t` error\n code)."]
870 pub read: ::core::option::Option<
871 unsafe extern "C" fn(
872 user_data: *mut core::ffi::c_void,
873 buf: *mut u8,
874 len: usize,
875 timeout_ms: u32,
876 ) -> i32,
877 >,
878}
879pub const NROS_RMW_RET_EXTENSION_BASE: i32 = 1000;
880pub const NROS_RMW_RET_OK: i32 = 0;
881pub const NROS_RMW_RET_ERROR: i32 = 1;
882pub const NROS_RMW_RET_TIMEOUT: i32 = 2;
883pub const NROS_RMW_RET_BAD_ALLOC: i32 = 10;
884pub const NROS_RMW_RET_INVALID_ARGUMENT: i32 = 11;
885pub const NROS_RMW_RET_UNSUPPORTED: i32 = 3;
886pub const NROS_RMW_RET_INCOMPATIBLE_QOS: i32 = 1000;
887pub const NROS_RMW_RET_TOPIC_NAME_INVALID: i32 = 1001;
888pub const NROS_RMW_RET_NODE_NAME_NON_EXISTENT: i32 = 203;
889pub const NROS_RMW_RET_LOAN_NOT_SUPPORTED: i32 = 1002;
890pub const NROS_RMW_RET_NO_DATA: i32 = 1003;
891pub const NROS_RMW_RET_WOULD_BLOCK: i32 = 1004;
892pub const NROS_RMW_RET_BUFFER_TOO_SMALL: i32 = 1005;
893pub const NROS_RMW_RET_MESSAGE_TOO_LARGE: i32 = 1006;
894pub const NROS_RMW_RET_INCOMPATIBLE_ABI: i32 = 1007;
895pub const NROS_RMW_RET_NO_BACKEND: i32 = 1008;
896pub const NROS_RMW_RET_AMBIGUOUS_BACKEND: i32 = 1009;
897pub const NROS_RMW_RET_UNKNOWN_BACKEND: i32 = 1010;
898pub const NROS_RMW_RET_CONNECTION_FAILED: i32 = 1011;
899pub const NROS_RMW_RET_INVALID_CONFIG: i32 = 1012;
900pub const NROS_RMW_RET_INCORRECT_RMW_IMPLEMENTATION: i32 = 12;
901pub const NROS_RMW_RELIABILITY_SYSTEM_DEFAULT: i32 = 0;
902pub const NROS_RMW_RELIABILITY_RELIABLE: i32 = 1;
903pub const NROS_RMW_RELIABILITY_BEST_EFFORT: i32 = 2;
904pub const NROS_RMW_RELIABILITY_UNKNOWN: i32 = 3;
905pub const NROS_RMW_DURABILITY_SYSTEM_DEFAULT: i32 = 0;
906pub const NROS_RMW_DURABILITY_TRANSIENT_LOCAL: i32 = 1;
907pub const NROS_RMW_DURABILITY_VOLATILE: i32 = 2;
908pub const NROS_RMW_DURABILITY_UNKNOWN: i32 = 3;
909pub const NROS_RMW_HISTORY_SYSTEM_DEFAULT: i32 = 0;
910pub const NROS_RMW_HISTORY_KEEP_LAST: i32 = 1;
911pub const NROS_RMW_HISTORY_KEEP_ALL: i32 = 2;
912pub const NROS_RMW_HISTORY_UNKNOWN: i32 = 3;
913pub const RMW_GID_STORAGE_SIZE: i32 = 24;
914pub const RMW_INET_ADDRSTRLEN: i32 = 48;
915pub const NROS_RMW_DURATION_INFINITE_MS: i64 = 4294967295;
916pub const RMW_SESSION_MAX_PROPERTIES: i32 = 16;
917pub const NROS_RMW_VISITOR_DEFINED: i32 = 1;
918#[doc = " Borrow-shaped union the backend supplies to the registered\n callback. The `kind` argument selects which member is valid."]
919#[repr(C)]
920#[derive(Copy, Clone)]
921pub union rmw_event_payload_t {
922 pub liveliness_changed: rmw_liveliness_changed_status_t,
923 pub count: rmw_count_status_t,
924}
925pub mod rmw_feature_t {
926 #[doc = " Upstream `rmw_feature_t` — an optional piece of CONTENT a backend may or may\n not populate. Upstream defines exactly these two, both about whether\n message-info sequence numbers are real. Values mirror upstream's."]
927 pub type Type = core::ffi::c_uint;
928 pub const RMW_FEATURE_MESSAGE_INFO_PUBLICATION_SEQUENCE_NUMBER: Type = 0;
929 pub const RMW_FEATURE_MESSAGE_INFO_RECEPTION_SEQUENCE_NUMBER: Type = 1;
930}
931pub mod rmw_liveliness_kind_t {
932 #[doc = " Liveliness kind values for `rmw_qos_profile_t::liveliness_kind`.\n\n Upstream's numbering (W5/B2). `MANUAL_BY_NODE` and `MANUAL_BY_TOPIC` were\n SWAPPED here until 2026-08-24 — 2 meant BY_TOPIC to us and BY_NODE to\n upstream — which the cyclonedds backend then translated into a real DDS\n liveliness kind a ROS peer matches on."]
933 pub type Type = core::ffi::c_uint;
934 #[doc = " Let the middleware choose. Spelled `NONE` before W5/B2 and used the same\n way: nothing is asserted and nothing is tracked. Upstream has no\n separate `NONE`, so the two collapse onto value 0."]
935 pub const NROS_RMW_LIVELINESS_SYSTEM_DEFAULT: Type = 0;
936 #[doc = " Backend's keepalive task asserts liveliness automatically."]
937 pub const NROS_RMW_LIVELINESS_AUTOMATIC: Type = 1;
938 #[doc = " Application calls `assert_liveliness()` at the node level."]
939 pub const NROS_RMW_LIVELINESS_MANUAL_BY_NODE: Type = 2;
940 #[doc = " Application calls `assert_liveliness()` per topic explicitly."]
941 pub const NROS_RMW_LIVELINESS_MANUAL_BY_TOPIC: Type = 3;
942 #[doc = " The backend could not determine this policy."]
943 pub const NROS_RMW_LIVELINESS_UNKNOWN: Type = 4;
944}
945pub mod rmw_qos_compatibility_type_t {
946 #[doc = " Verdict of a QoS compatibility check. Upstream `rmw_qos_compatibility_type_t`,\n values included.\n\n `WARNING` means \"compatible as far as could be checked, but at least one\n policy on one side is `*_UNKNOWN`\" — the backend could not read it back.\n Reachable since W5/B2 gave the policies an UNKNOWN encoding; it was defined\n and unreachable before that, so the value could not be reused for anything\n else in the meantime.\n\n A definite clash OUTRANKS an unknown: if the policies that COULD be compared\n are already incompatible the verdict is `ERROR`, because softening it to a\n warning would hide something the caller can act on."]
947 pub type Type = core::ffi::c_uint;
948 pub const RMW_QOS_COMPATIBILITY_OK: Type = 0;
949 pub const RMW_QOS_COMPATIBILITY_WARNING: Type = 1;
950 pub const RMW_QOS_COMPATIBILITY_ERROR: Type = 2;
951}
952pub mod nros_rmw_qos_clash_t {
953 #[doc = " Which policies clashed, as a bitmask. A nano-ros extension: upstream reports\n the reason only as prose, which a target cannot act on."]
954 pub type Type = core::ffi::c_uint;
955 pub const NROS_RMW_QOS_CLASH_NONE: Type = 0;
956 pub const NROS_RMW_QOS_CLASH_RELIABILITY: Type = 1;
957 pub const NROS_RMW_QOS_CLASH_DURABILITY: Type = 2;
958 pub const NROS_RMW_QOS_CLASH_DEADLINE: Type = 4;
959 pub const NROS_RMW_QOS_CLASH_LIVELINESS_KIND: Type = 8;
960 pub const NROS_RMW_QOS_CLASH_LIVELINESS_LEASE: Type = 16;
961}
962pub mod rmw_log_severity_t {
963 #[doc = " Log severity — upstream `rmw_log_severity_t`, values included.\n\n The values are `rcutils`' ladder (`DEBUG 10`, `INFO 20`, …), not a dense\n 0..N, so they are written out rather than renumbered: a caller that has an\n `rcutils` severity in hand can pass it straight through.\n\n There is no `TRACE`. `nros_log::Severity` has one, and it maps to `DEBUG`\n crossing this seam — losing a distinction upstream never had is better than\n inventing a value a ROS-side caller cannot produce."]
964 pub type Type = core::ffi::c_uint;
965 pub const RMW_LOG_SEVERITY_UNSET: Type = 0;
966 pub const RMW_LOG_SEVERITY_DEBUG: Type = 10;
967 pub const RMW_LOG_SEVERITY_INFO: Type = 20;
968 pub const RMW_LOG_SEVERITY_WARN: Type = 30;
969 pub const RMW_LOG_SEVERITY_ERROR: Type = 40;
970 pub const RMW_LOG_SEVERITY_FATAL: Type = 50;
971}
972pub mod rmw_endpoint_type_t {
973 #[doc = " Which end of a topic an endpoint is — upstream `rmw_endpoint_type_t`."]
974 pub type Type = core::ffi::c_uint;
975 pub const RMW_ENDPOINT_INVALID: Type = 0;
976 pub const RMW_ENDPOINT_PUBLISHER: Type = 1;
977 pub const RMW_ENDPOINT_SUBSCRIPTION: Type = 2;
978}
979pub mod rmw_transport_protocol_t {
980 #[doc = " Transport protocol of a network flow — upstream `rmw_transport_protocol_t`,\n values included."]
981 pub type Type = core::ffi::c_uint;
982 pub const RMW_TRANSPORT_PROTOCOL_UNKNOWN: Type = 0;
983 pub const RMW_TRANSPORT_PROTOCOL_UDP: Type = 1;
984 pub const RMW_TRANSPORT_PROTOCOL_TCP: Type = 2;
985 pub const RMW_TRANSPORT_PROTOCOL_COUNT: Type = 3;
986}
987pub mod rmw_internet_protocol_t {
988 #[doc = " Internet protocol of a network flow — upstream `rmw_internet_protocol_t`."]
989 pub type Type = core::ffi::c_uint;
990 pub const RMW_INTERNET_PROTOCOL_UNKNOWN: Type = 0;
991 pub const RMW_INTERNET_PROTOCOL_IPV4: Type = 1;
992 pub const RMW_INTERNET_PROTOCOL_IPV6: Type = 2;
993 pub const RMW_INTERNET_PROTOCOL_COUNT: Type = 3;
994}
995pub mod rmw_event_type_t {
996 #[doc = " Tier-1 event kinds. Stable integer values; future kinds (Tier-2)\n extend the enum at end."]
997 pub type Type = core::ffi::c_uint;
998 #[doc = " Subscriber: a tracked publisher's liveliness state changed."]
999 pub const NROS_RMW_EVENT_LIVELINESS_CHANGED: Type = 0;
1000 #[doc = " Subscriber: an expected sample didn't arrive within the\n configured deadline."]
1001 pub const NROS_RMW_EVENT_REQUESTED_DEADLINE_MISSED: Type = 1;
1002 #[doc = " Subscriber: backend dropped a sample (overflow / etc.)."]
1003 pub const NROS_RMW_EVENT_MESSAGE_LOST: Type = 2;
1004 #[doc = " Publisher: this publisher missed its own liveliness assertion."]
1005 pub const NROS_RMW_EVENT_LIVELINESS_LOST: Type = 3;
1006 #[doc = " Publisher: this publisher promised X Hz, fell behind."]
1007 pub const NROS_RMW_EVENT_OFFERED_DEADLINE_MISSED: Type = 4;
1008}
1009pub mod nros_rmw_session_mode_t {
1010 #[doc = " Session mode for `create_session`'s @p mode parameter (issue 0331).\n\n These values were previously an undocumented bare `uint8_t` with no legal-\n value list — the only slot in the vtable without one — encoded inline as\n `0u8` / `1u8` at the Rust boundary.\n\n Divergence from `rmw.h`, recorded deliberately: Humble's\n `rmw_init_options_t` carries `instance_id`, `implementation_identifier`,\n `domain_id`, `security_options`, `localhost_only`, `enclave`, `allocator`\n and `impl` — and has NO session-mode concept. (This list said \"domain_id,\n enclave, security_options and discovery_options\" until 2026-08-24, which\n omitted five of the eight and named one, `discovery_options`, that is an\n IRON field and does not exist in the distro our recorded contract is taken\n from. What we do and do not carry from those eight is issue 0785.) This parameter is closest to zenoh's `whatami`, and a\n backend that has no such notion (cyclonedds, XRCE) is expected to IGNORE it\n rather than fail. Folding it into backend-private config behind the locator\n — so the agnostic vtable stops carrying a backend-shaped field — is the\n structural fix, and is not done here; see issue 0808. (This said \"see issue\n 0331\" until 2026-08-26. That issue documented this enum and stated in its own\n resolution that the structural half was NOT done, deferring it to issue 0330\n part 3 — which resolved a different concern entirely. Both are closed, so the\n fold was tracked by nothing; 0808 is its home.)"]
1011 pub type Type = core::ffi::c_uint;
1012 #[doc = " Connect to a router/agent as a client. The default."]
1013 pub const NROS_RMW_SESSION_MODE_CLIENT: Type = 0;
1014 #[doc = " Peer-to-peer, no router. Backends without a peer mode ignore this."]
1015 pub const NROS_RMW_SESSION_MODE_PEER: Type = 1;
1016}
1017unsafe extern "C" {
1018 #[doc = " Which policies of `offered` (a publisher's) and `requested` (a\n subscription's) are incompatible, as a bitmask — no strings, so an image\n that only needs the verdict never links the reason table.\n\n Argument order is upstream's: publisher profile first.\n\n Writes `*compatibility` and `*clash_mask` on `NROS_RMW_RET_OK`;\n `NROS_RMW_RET_INVALID_ARGUMENT` if either out-parameter is NULL."]
1019 pub fn nros_rmw_qos_incompatibility_mask(
1020 offered: rmw_qos_profile_t,
1021 requested: rmw_qos_profile_t,
1022 compatibility: *mut rmw_qos_compatibility_type_t::Type,
1023 clash_mask: *mut u32,
1024 ) -> rmw_ret_t;
1025}
1026unsafe extern "C" {
1027 #[doc = " Upstream `rmw_qos_profile_check_compatible`. Exact parity.\n\n `reason` may be NULL with `reason_size` 0 — the create-time path, which\n wants the verdict and nothing else.\n\n The reason is SELECTED, never FORMATTED: each clash bit maps to one\n `static const char[]` and they are appended by a bounded copy. Upstream's\n implementations use `snprintf`, which would drag the printf engine into\n images that deliberately excluded it.\n\n Truncation is NOT failure: the buffer is always NUL-terminated and the\n verdict is still written. Returning `BUFFER_TOO_SMALL` would make a\n small-buffer caller lose the load-bearing half of the answer."]
1028 pub fn rmw_qos_profile_check_compatible(
1029 publisher_profile: rmw_qos_profile_t,
1030 subscription_profile: rmw_qos_profile_t,
1031 compatibility: *mut rmw_qos_compatibility_type_t::Type,
1032 reason: *mut core::ffi::c_char,
1033 reason_size: usize,
1034 ) -> rmw_ret_t;
1035}
1036unsafe extern "C" {
1037 #[doc = " Upstream `rmw_compare_gids_equal`. Exact parity.\n\n Equal means the same `implementation_identifier` AND the same 24 bytes. Gids\n from different backends are never equal — which matters more here than\n upstream, because `nros_rmw_cffi_register_named` admits several backends in\n one image.\n\n Comparison is over the WHOLE array, so a producer must zero-pad; see\n `rmw_gid_t`."]
1038 pub fn rmw_compare_gids_equal(
1039 gid1: *const rmw_gid_t,
1040 gid2: *const rmw_gid_t,
1041 result: *mut bool,
1042 ) -> rmw_ret_t;
1043}
1044unsafe extern "C" {
1045 #[doc = " Register a custom RMW backend under the implicit name \"default\".\n Legacy single-arg form retained for source compatibility with\n backend ctors authored before the named registry (Phase 104.B.2).\n\n Deprecated (Phase 128.B.5): every in-tree backend now calls\n `nros_rmw_cffi_register_named` with its canonical name. The\n unnamed shim will be removed in a follow-up phase.\n Returns NROS_RMW_RET_OK."]
1046 pub fn nros_rmw_cffi_register(vtable: *const nros_rmw_vtable_t) -> rmw_ret_t;
1047}
1048unsafe extern "C" {
1049 #[doc = " Phase 104.B.2 — register a backend under a stable name. Multiple\n backends can coexist (bridge nodes); consumers select via\n `nros_rmw_cffi_lookup` or the higher-level\n `Executor::node_builder(...).rmw(...)` path.\n\n Names: UTF-8, NUL-terminated, ≤ 31 bytes (excluding NUL).\n Reserved: \"zenoh\", \"dds\", \"xrce\", \"cyclonedds\", future \"uorb\".\n \"default\" is the implicit name used by `nros_rmw_cffi_register`.\n\n Duplicate registration of the same name overwrites the previous\n vtable (idempotent for ctor-fires-twice).\n\n Returns:\n * NROS_RMW_RET_OK on success.\n * NROS_RMW_RET_INVALID_ARGUMENT if name or vtable is NULL,\n the name is empty, or exceeds 31 bytes.\n * NROS_RMW_RET_ERROR if the registry is full\n (NROS_RMW_MAX_BACKENDS reached)."]
1050 pub fn nros_rmw_cffi_register_named(
1051 name: *const core::ffi::c_char,
1052 vtable: *const nros_rmw_vtable_t,
1053 ) -> rmw_ret_t;
1054}
1055unsafe extern "C" {
1056 #[doc = " Look up a backend's vtable by name. Returns NULL if no backend is\n registered under `name`. The returned pointer is valid for the\n program's lifetime."]
1057 pub fn nros_rmw_cffi_lookup(name: *const core::ffi::c_char) -> *const nros_rmw_vtable_t;
1058}
1059unsafe extern "C" {
1060 #[doc = " Diagnostic helper — fills `buf` with pointers to up to `cap`\n registered backend names. Returns the total number of registered\n backends (may exceed `cap`; caller can re-query with a larger\n buffer). Pointer-valid for the program's lifetime. Pass\n `buf=NULL, cap=0` to query the count only."]
1061 pub fn nros_rmw_cffi_registered_names(buf: *mut *const core::ffi::c_char, cap: usize) -> usize;
1062}
1063unsafe extern "C" {
1064 #[doc = " Install a custom transport for subsequent session opens.\n\n The struct's contents are copied internally; the caller may\n stack-allocate. To clear the slot, pass NULL.\n\n The fn pointer is exported from the nano-ros C staticlib\n (`packages/api/nros-c/`), where it forwards to the\n `nros-rmw-cffi` registry. C++ consumers should include\n `<nros/transport.hpp>` (from `nros-cpp`) which calls this\n function under the hood.\n\n @retval NROS_RMW_RET_OK on success (transport installed or\n cleared).\n @retval NROS_RMW_RET_INCOMPATIBLE_ABI when `ops` is non-NULL but\n `ops->abi_version` does not match\n `NROS_TRANSPORT_OPS_ABI_VERSION_V1`. The previously\n installed transport (if any) is left untouched."]
1065 pub fn nros_rmw_cffi_set_custom_transport(ops: *const nros_transport_ops_t) -> rmw_ret_t;
1066}