pub trait Session {
type Error;
type PublisherHandle;
type SubscriptionHandle;
type ServiceHandle;
type ClientHandle;
const SERIALIZATION_FORMAT: &'static str = _;
const SERIALIZATION_FORMAT_ID: SerializationFormatId = nros_serdes::format::SerializationFormatId::Cdr;
const SUPPORTS_CALLER_INBOX: bool = false;
Show 21 methods
// Required methods
fn create_publisher(
&mut self,
topic: &TopicInfo<'_>,
qos: QoSProfile,
) -> Result<Self::PublisherHandle, Self::Error>;
fn create_subscription(
&mut self,
topic: &TopicInfo<'_>,
qos: QoSProfile,
) -> Result<Self::SubscriptionHandle, Self::Error>;
fn create_service(
&mut self,
service: &ServiceInfo<'_>,
qos: QoSProfile,
) -> Result<Self::ServiceHandle, Self::Error>;
fn create_client(
&mut self,
service: &ServiceInfo<'_>,
qos: QoSProfile,
) -> Result<Self::ClientHandle, Self::Error>;
fn close(&mut self) -> Result<(), Self::Error>;
fn drive_io(&mut self, timeout_ms: i32) -> Result<(), Self::Error>;
// Provided methods
fn serialization_format(&self) -> Option<&'static str> { ... }
fn create_service_with_inbox(
&mut self,
service: &ServiceInfo<'_>,
qos: QoSProfile,
inbox: ServiceInboxSpec,
) -> Result<Self::ServiceHandle, Self::Error> { ... }
fn supported_qos_policies(&self) -> QoSPolicyMask { ... }
fn next_deadline_ms(&self) -> Option<u32> { ... }
unsafe fn set_wake_callback(
&mut self,
cb: Option<unsafe extern "C" fn(*mut c_void)>,
ctx: *mut c_void,
) { ... }
fn supports_wake_callback(&self) -> bool { ... }
fn ping_session(&mut self, timeout_ms: i32) -> Result<(), Self::Error>
where Self::Error: From<TransportError> { ... }
fn get_node_names(
&mut self,
visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool,
) -> Result<(), Self::Error>
where Self::Error: From<TransportError> { ... }
fn count_publishers(
&mut self,
topic_name: &str,
) -> Result<usize, Self::Error>
where Self::Error: From<TransportError> { ... }
fn count_subscribers(
&mut self,
topic_name: &str,
) -> Result<usize, Self::Error>
where Self::Error: From<TransportError> { ... }
fn get_topic_names_and_types(
&mut self,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), Self::Error>
where Self::Error: From<TransportError> { ... }
fn get_service_names_and_types(
&mut self,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), Self::Error>
where Self::Error: From<TransportError> { ... }
fn get_names_and_types_by_node(
&mut self,
kind: GraphEntityKind,
node_name: &str,
node_namespace: &str,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), Self::Error>
where Self::Error: From<TransportError> { ... }
fn get_endpoint_info_by_topic(
&mut self,
publishers: bool,
topic_name: &str,
visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool,
) -> Result<(), Self::Error>
where Self::Error: From<TransportError> { ... }
unsafe fn set_graph_change_callback(
&mut self,
callback: Option<unsafe extern "C" fn(*const c_void, usize)>,
user_data: *const c_void,
) -> Result<(), Self::Error>
where Self::Error: From<TransportError> { ... }
}Expand description
Transport session trait — the per-process anchor an RMW backend gives to the executor.
§Threading
&mut self on every method means the executor serialises all
session calls onto a single thread. A backend may rely on this
— no internal locking is required for create_* / close /
drive_io. Publisher / subscriber / service handles created
from the session, however, are typically used from worker
threads and must carry their own synchronisation (see the
Publisher / Subscription trait docs).
§Calling pattern
- Open the session (backend-specific factory; not on this trait).
create_*for every entity at startup. Creating entities mid- flight afterdrive_iohas run is allowed but not common.- The executor calls
drive_ioperiodically. Worker threads publish / receive in parallel. closeonce at shutdown. Entities must be dropped first.
Provided Associated Constants§
Sourceconst SERIALIZATION_FORMAT: &'static str = _
const SERIALIZATION_FORMAT: &'static str = _
RFC-0088 — the serialization format this backend speaks, as ROS 2’s
rmw_get_serialization_format() reports it (“One middleware can only
have one encoding”).
Defaulted to CDR because every backend in tree except uORB speaks it,
and a backend that speaks something else says so by overriding these
two. They travel together: SERIALIZATION_FORMAT is the identity that
crosses images (bridge config, tooling, the vtable slot) and
SERIALIZATION_FORMAT_ID is the image-local discriminant used for the
one-byte comparison a bridge makes at construction.
Sourceconst SERIALIZATION_FORMAT_ID: SerializationFormatId = nros_serdes::format::SerializationFormatId::Cdr
const SERIALIZATION_FORMAT_ID: SerializationFormatId = nros_serdes::format::SerializationFormatId::Cdr
Image-local discriminant for Self::SERIALIZATION_FORMAT. Never
persisted, never compared across images — see nros_serdes::format.
Sourceconst SUPPORTS_CALLER_INBOX: bool = false
const SUPPORTS_CALLER_INBOX: bool = false
phase-461 W2 – does this backend accept a caller-owned request ring
(ServiceInboxSpec::Caller)?
A const, so the family that would bring one picks at COMPILE time
and a Caller spec never degrades silently into the backend’s own
geometry (RFC-0052: a knob that is dropped is a knob that lies). The
caller reads this and passes ServiceInboxSpec::Backend where it is
false, which is how a family refuses at BUILD time rather than
discovering at boot that its ring went nowhere – nros-node’s
PARAM_INBOX is the worked example.
false is the right default and every backend in tree states it
explicitly with its own reason, because “this backend has no caller
ring” and “nobody thought about it” are different facts.
Required Associated Types§
Sourcetype PublisherHandle
type PublisherHandle
Publisher handle type
Sourcetype SubscriptionHandle
type SubscriptionHandle
Subscription handle type
Sourcetype ServiceHandle
type ServiceHandle
Service server handle type
Sourcetype ClientHandle
type ClientHandle
Service client handle type
Required Methods§
Sourcefn create_publisher(
&mut self,
topic: &TopicInfo<'_>,
qos: QoSProfile,
) -> Result<Self::PublisherHandle, Self::Error>
fn create_publisher( &mut self, topic: &TopicInfo<'_>, qos: QoSProfile, ) -> Result<Self::PublisherHandle, Self::Error>
Create a publisher bound to this session.
May allocate transport resources (zenoh declarations, DDS
writers). Returns a handle that outlives the call but not the
session — drop the handle before close().
Sourcefn create_subscription(
&mut self,
topic: &TopicInfo<'_>,
qos: QoSProfile,
) -> Result<Self::SubscriptionHandle, Self::Error>
fn create_subscription( &mut self, topic: &TopicInfo<'_>, qos: QoSProfile, ) -> Result<Self::SubscriptionHandle, Self::Error>
Create a subscriber bound to this session.
Subscribers may start receiving immediately after creation if the transport supports late-joining publishers. Late messages are buffered up to the QoS depth.
Sourcefn create_service(
&mut self,
service: &ServiceInfo<'_>,
qos: QoSProfile,
) -> Result<Self::ServiceHandle, Self::Error>
fn create_service( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, ) -> Result<Self::ServiceHandle, Self::Error>
Create a service server bound to this session. Replies are
matched to requests by the sequence number returned from
ServiceTrait::take_request.
qos is applied to both the request and reply endpoints (a
service is two DDS topics; rmw uses one profile for both). The
default is QoSProfile::services_default
(RELIABLE+VOLATILE+KEEP_LAST(10)).
The server receives through the backend’s own inbox. A builtin family
that sizes its own ring calls create_service_with_inbox instead.
Sourcefn create_client(
&mut self,
service: &ServiceInfo<'_>,
qos: QoSProfile,
) -> Result<Self::ClientHandle, Self::Error>
fn create_client( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, ) -> Result<Self::ClientHandle, Self::Error>
Create a service client bound to this session.
qos is applied to both the request and reply endpoints (a
service is two DDS topics; rmw uses one profile for both). The
default is QoSProfile::services_default
(RELIABLE+VOLATILE+KEEP_LAST(10)).
Sourcefn close(&mut self) -> Result<(), Self::Error>
fn close(&mut self) -> Result<(), Self::Error>
Close the session, releasing transport resources. All entity handles created from this session must already be dropped.
Sourcefn drive_io(&mut self, timeout_ms: i32) -> Result<(), Self::Error>
fn drive_io(&mut self, timeout_ms: i32) -> Result<(), Self::Error>
Drive transport I/O (poll network, dispatch callbacks).
Both zenoh-pico and XRCE-DDS are pull-based: they require the application to periodically call this method to read from the network socket and dispatch incoming messages to subscriber buffers.
timeout_ms is the maximum time to wait for data (0 = non-blocking;
negative values mean “block indefinitely” — see Phase 84.D7 for the
planned migration to core::time::Duration).
Required. There is no default body — both shipped backends
(zenoh and XRCE) must drive I/O, and a silent no-op default was a
trap for third-party implementers. If your backend genuinely
receives data via OS callbacks (push-based) and has nothing to do
here, return Ok(()) explicitly.
Provided Methods§
Sourcefn serialization_format(&self) -> Option<&'static str>
fn serialization_format(&self) -> Option<&'static str>
RFC-0088 — this session’s serialization format, as ROS 2’s
rmw_get_serialization_format() reports it.
Per session, not per process. ROS 2’s function takes no handle
because one process links one middleware; an Executor::open_multi
image links two, so the answer must be asked of the session.
None means the backend has NOT SAID, and that is not a synonym
for "cdr". The seam’s contract is stated once, for the
get_serialization_format slot in
packages/core/nros-rmw-abi/include/nros/rmw_vtable.h: “NULL slot: the
runtime answers NULL — it does NOT guess cdr.” This method is the
Rust face of that slot and carries the same absence. A &'static str
could not: the only value it had to hand back was the literal "cdr",
indistinguishable at every call site from a backend that declared CDR.
The default answers Some(Self::SERIALIZATION_FORMAT), which is right
for any backend whose format is a compile-time fact — it HAS said, in
the const. A session that dispatches to a backend chosen at run time —
the C-ABI adapter, whose vtable carries the answer — overrides this to
ask the backend, and hands on the None a NULL slot gives it.
Sourcefn create_service_with_inbox(
&mut self,
service: &ServiceInfo<'_>,
qos: QoSProfile,
inbox: ServiceInboxSpec,
) -> Result<Self::ServiceHandle, Self::Error>
fn create_service_with_inbox( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, inbox: ServiceInboxSpec, ) -> Result<Self::ServiceHandle, Self::Error>
Create a service server that receives through inbox.
phase-461 W2 / issue 1352 – the ROS parameter services are six queryables per node and the REP-2002 lifecycle services five, and on a board they are most of the image’s queryables. Their requests are bounded by the contract’s declared parameters and their clients are sequential, so they want one small slot where a user service wants four large ones. The family that knows that bound brings the ring; the backend keeps the per-queryable header.
The default body is Self::SUPPORTS_CALLER_INBOX = false spelled
out: it serves ServiceInboxSpec::Backend and nothing else. It can
only be reached with a Caller spec by a caller that ignored the
const, so the debug_assert names that – the REFUSAL itself is the
caller’s, at compile time, because a runtime error here would be a
boot failure where a build failure was available (nros-node’s
PARAM_INBOX is the worked example).
A backend that overrides this must also state the const true.
Sourcefn supported_qos_policies(&self) -> QoSPolicyMask
fn supported_qos_policies(&self) -> QoSPolicyMask
Phase 109 — report which QoS policies the active backend
honours. The runtime validates requested QoS against this mask
at entity-create time and returns
TransportError::IncompatibleQos if the requested profile
includes a policy the backend can’t enforce. No silent
downgrade.
§The mask is DERIVED, not authored — phase-428 W9
Every bit an implementation sets must be backed by a
nros-qos-honours: claim sited on the code that honours the policy,
and check-qos-mask-derivation re-measures those claims on every
push. Deleting the code deletes the claim, which is the property this
item exists to buy: before W9 this was a hand-written constant, so a
backend could advertise a policy it had never implemented and nothing
noticed. Three did — LIVELINESS_MANUAL_BY_NODE was advertised by
every backend in the tree and implemented by none.
“Honours” means the backend reads the field and either APPLIES it or
REFUSES a value it cannot serve. Passing the field on to someone else
(a discovery keyexpr, a lowered C struct) is not honouring it, and the
gate does not count it: see nros-qos-discovery-only.
§The default is EMPTY, deliberately
It returned QoSPolicyMask::CORE until phase-428 W9 — four policies
granted to any implementation that had written no code at all, which
is precisely the lie above with the trait as its author. An
implementation now states what it honours or is told it honours
nothing; the failure is loud (IncompatibleQos at create) rather than
a silent downgrade at runtime, which is the direction this trait has
chosen everywhere else.
§A C backend answers through the vtable — issue 1329
CffiSession is the one implementation that cannot answer from
compile-time knowledge: which backend registered is a run-time fact. It
returned the UNION of what any C backend it routes to honours, which
over-claims for each of them; it now asks the backend through
nros_rmw_vtable_t::supported_qos_policies and returns what it said. A
NULL slot there carries the same meaning as this default — the backend
has not said, so it honours nothing — deliberately, so the ABI and the
trait give one answer to one question.
Sourcefn next_deadline_ms(&self) -> Option<u32>
fn next_deadline_ms(&self) -> Option<u32>
Phase 110.0 — backend’s next internal-event deadline in milliseconds from now (lease keepalive, heartbeat, reader ACK-NACK timeout, etc.).
The executor caps its drive_io timeout against
min(user_timeout, timer_deadline, this) so quiet links don’t
wake early, see no user-visible work, and round-trip back into
drive_io. Returns None when the backend has no internal
deadlines or chooses not to expose them.
Default None keeps existing backends working unchanged; opt-in
per backend.
Sourceunsafe fn set_wake_callback(
&mut self,
cb: Option<unsafe extern "C" fn(*mut c_void)>,
ctx: *mut c_void,
)
unsafe fn set_wake_callback( &mut self, cb: Option<unsafe extern "C" fn(*mut c_void)>, ctx: *mut c_void, )
Phase 124.B.1 — install (or clear, when cb.is_none()) the
executor wake callback. The runtime calls this once per
session after open with cb pointing at a runtime-owned
function and ctx pointing at the executor’s wake state.
The backend stores (cb, ctx) in its per-session state and
calls cb(ctx) whenever its transport notification path
fires (datagram arrival, condvar wake, etc.) — the runtime
cb does flag-write + condvar-signal atomically, so a
spin_once blocked on the wake condvar resumes immediately
instead of waiting for the next poll iteration.
§Safety
When cb is Some, ctx must remain valid until the callback is
cleared or the session is closed. The backend may invoke cb(ctx) from
its transport notification path.
Default body: ignore the call. Poll-only backends (XRCE, bare-metal) leave the default in place; the executor still drains them on its deadline-bound cv-wait boundary.
Sourcefn supports_wake_callback(&self) -> bool
fn supports_wake_callback(&self) -> bool
Phase 130.4 — does this backend actually honour
set_wake_callback?
true means the backend installs the callback and will
fire it from its async notify path (worker thread, ISR,
signalfd, …). false (the default) means
set_wake_callback was a no-op — the executor must drive
I/O for the caller’s full timeout because no async wake
will pre-empt it.
The executor uses this to choose between the wake-primitive
wait (NodeWake::wait_ms / std::Condvar::wait_timeout_while)
and a direct drive_io(timeout_ms). Poll-only backends
(XRCE-DDS-Client, bare-metal smoltcp) return false;
event-driven backends (zenoh-pico with an RX task that
invokes the callback on packet arrival) return true.
Sourcefn ping_session(&mut self, timeout_ms: i32) -> Result<(), Self::Error>
fn ping_session(&mut self, timeout_ms: i32) -> Result<(), Self::Error>
Phase 124.F.1 — session-level connectivity probe.
Sends a wire-level round-trip probe and waits up to
timeout_ms. Ok(()) on reply, Err(TransportError::Timeout)
on no reply, Err(TransportError::Unsupported) when the
backend can’t probe (DDS without participant introspection).
Lesson from micro-ROS’s rmw_uros_ping_agent.
Default body: Err(Unsupported). Backends with a native
ping API (XRCE: uxr_ping_agent_session; zenoh, which has no
round-trip ping, sends one zpico_send_keep_alive frame) opt in by
overriding.
Sourcefn get_node_names(
&mut self,
visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool,
) -> Result<(), Self::Error>
fn get_node_names( &mut self, visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool, ) -> Result<(), Self::Error>
phase-381 W3 — enumerate the nodes this session can see.
A VISITOR, not a returned collection, because upstream’s
rcutils_string_array_t allocates two levels deep and there is no
allocator at this seam. A caller-provided buffer is worse than it looks:
the graph has no bound the CALLER can know. So the backend streams from
state it already holds, peak extra RAM is one entry, and a caller with a
bound stops early by returning false.
namespace and name are ROS names. enclave is None where the
backend does not track one — which is what lets this one method answer
both rmw_get_node_names and rmw_get_node_names_with_enclaves.
Every string is BORROWED for the duration of the call.
Must not block on the wire, and takes no timeout. It reports what has
ALREADY arrived, so the first call after startup legitimately returns a
partial graph — a backend feeds its view from drive_io. Letting this
block was considered and rejected: it would stall the executor’s only
thread inside an introspection call, on a runtime whose premise is that
there is no other thread to do the work.
Default body: Err(Unsupported) — a backend with no graph (XRCE) says
so, and the runtime can tell that from an empty graph.
Sourcefn count_publishers(&mut self, topic_name: &str) -> Result<usize, Self::Error>
fn count_publishers(&mut self, topic_name: &str) -> Result<usize, Self::Error>
phase-381 W3 — how many publishers this session can see on topic_name.
topic_name is a ROS name (/chatter); the backend mangles as needed.
Same warm-up caveat as Self::get_node_names: a count reflects what
has already been discovered, so it can be low right after startup and is
never a proof of absence.
Default body: Err(Unsupported) — distinct from Ok(0), which claims
there are none.
Sourcefn count_subscribers(&mut self, topic_name: &str) -> Result<usize, Self::Error>
fn count_subscribers(&mut self, topic_name: &str) -> Result<usize, Self::Error>
phase-381 W3 — how many subscribers this session can see on topic_name.
See Self::count_publishers for the caveats.
Sourcefn get_topic_names_and_types(
&mut self,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), Self::Error>
fn get_topic_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), Self::Error>
phase-381 W3 — every topic, with the types published or subscribed on it.
A visitor for the same reason as Self::get_node_names, and one call
per distinct NAME: the contract hands over a name and the types on it,
so a topic carrying two types is one visit with two entries, not two
visits.
types_count may legitimately be 0 on a partially discovered graph —
reporting the name without a type beats dropping it.
Default body: Err(Unsupported).
Sourcefn get_service_names_and_types(
&mut self,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), Self::Error>
fn get_service_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), Self::Error>
phase-381 W3 — every service, with its types. As
Self::get_topic_names_and_types, over servers and clients.
Sourcefn get_names_and_types_by_node(
&mut self,
kind: GraphEntityKind,
node_name: &str,
node_namespace: &str,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), Self::Error>
fn get_names_and_types_by_node( &mut self, kind: GraphEntityKind, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), Self::Error>
phase-381 W3 — what ONE named node publishes / subscribes / serves / calls.
node_name and node_namespace are ROS names; a node the graph has not
discovered yields no visits, which is NOT an error — see
Self::get_node_names for why an empty answer is “not seen yet”.
kind selects which of the four upstream *_by_node questions this
answers. One method rather than four because the four differ ONLY by
which entity kind they keep, and four trait methods would be four copies
of one filter.
The trait keeps the ABI’s subscriber vocabulary because
rmw_get_subscriber_names_and_types_by_node is what upstream rmw calls
it and RFC-0054 makes the C headers the SSoT. The USER-facing spelling
is per language and settled at that layer: rcl says subscriber, rclcpp
and rclrs say subscription.
Default body: Err(Unsupported).
Sourcefn get_endpoint_info_by_topic(
&mut self,
publishers: bool,
topic_name: &str,
visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool,
) -> Result<(), Self::Error>
fn get_endpoint_info_by_topic( &mut self, publishers: bool, topic_name: &str, visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool, ) -> Result<(), Self::Error>
phase-381 W3 — the endpoints on one topic, with the QoS each GRANTED.
publishers selects rmw_get_publishers_info_by_topic (true) or
rmw_get_subscriptions_info_by_topic (false).
The granted profile is the whole reason a consumer asks: “why is nothing
arriving” is usually a QoS incompatibility, and the REQUESTED profile
cannot answer it. A backend that cannot read a remote’s granted QoS says
so per field rather than echoing the request back — see
rmw_topic_endpoint_info_t.
Default body: Err(Unsupported).
Sourceunsafe fn set_graph_change_callback(
&mut self,
callback: Option<unsafe extern "C" fn(*const c_void, usize)>,
user_data: *const c_void,
) -> Result<(), Self::Error>
unsafe fn set_graph_change_callback( &mut self, callback: Option<unsafe extern "C" fn(*const c_void, usize)>, user_data: *const c_void, ) -> Result<(), Self::Error>
phase-467 Row 8 — install (or clear, when callback is None) the
callback the backend fires when the ROS GRAPH changes.
The Rust twin of rmw_vtable_t::node_get_graph_guard_condition, which
is upstream’s rmw_node_get_graph_guard_condition under the
set_wake_callback shape: upstream RETURNS a guard condition the caller
adds to a wait set, we have no wait set, and a guard condition here is
an executor concept the backend never hands out. So the edge is PUSHED,
not polled — and the ABI slot has said so since phase-376 W4, while
this is the trait twin it never had.
The callback is an EDGE and carries no payload. count is how many
changes the backend coalesced, never a description of WHAT changed:
delivering that would mean buffering the graph, which is the cache a
small target cannot afford.
A backend must not fire it for a no-op. The contract is “the graph
this session can SEE differs from what it saw”, which both real
backends already computed and threw away — zenoh in
zpico_graph_set_apply’s return value, Cyclone in
on_data_available’s first parameter.
§Safety
When callback is Some, user_data must remain valid until the
callback is cleared or the session is closed; the backend may invoke it
from a transport or worker thread. The installer owes the clear —
see Executor::clear_graph_change_signal, which is issue 1385’s lesson
applied before the hazard rather than after it.
Default body: Err(Unsupported) — a backend with no graph (XRCE, uORB)
says “cannot tell you” rather than accepting a callback it will never
fire. That answer is also the capability PROBE the runtime uses: a
None clear on a graphless backend returns Unsupported, so nothing
else has to be asked and no handle is consumed before the answer.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".
Implementations on Foreign Types§
Source§impl Session for CffiSession
impl Session for CffiSession
Source§const SUPPORTS_CALLER_INBOX: bool = false
const SUPPORTS_CALLER_INBOX: bool = false
phase-461 W2 / issue 1352 – this session FORWARDS over a C vtable, and the vtable has no slot that carries a ring.
create_service reaches the backend through
rmw_vtable.create_service (packages/core/nros-rmw-abi/include/nros/),
whose arguments are the node, the type support, the name, the domain
and the QoS. A caller-owned inbox is four more words, so accepting one
here means a second ABI slot and its bindgen regeneration – not a
default this session can answer true to on the backend’s behalf. It
stays false until that slot exists, which is the one thing standing
between nros-node’s PARAM_INBOX and the zenoh ring that would hold
it (the zenoh side has been caller-visible since W1).
Deliberately NOT a runtime question of the vtable: the const is what lets the caller refuse at COMPILE time, and a session that answered from a vtable installed later could only refuse at boot.
Source§fn serialization_format(&self) -> Option<&'static str>
fn serialization_format(&self) -> Option<&'static str>
The backend is chosen at run time here, so the trait’s compile-time
default would be a guess. Ask the vtable, and answer None when the
slot is NULL: a backend that installed no body has not said what it
speaks, which is the contract rmw_vtable.h states for the slot.
A forwarder onto the inherent
CffiSession::serialization_format,
which has always read the slot that way. Until phase-467 Q2 this impl
unwrap_or-ed the trait constant instead, so the one session that
cannot know its format at compile time was the one that guessed.
Source§unsafe fn set_graph_change_callback(
&mut self,
callback: Option<unsafe extern "C" fn(*const c_void, usize)>,
user_data: *const c_void,
) -> Result<(), TransportError>
unsafe fn set_graph_change_callback( &mut self, callback: Option<unsafe extern "C" fn(*const c_void, usize)>, user_data: *const c_void, ) -> Result<(), TransportError>
phase-467 Row 8 — forward to the backend’s graph-change slot; NULL means UNSUPPORTED.
This is the CONSUMER the slot never had. It was declared in
phase-376 W4 and read by nothing for five phases, so
check-rmw-slot-producers classified it inert and a family entry
carried the reason. Both real backends already received the graph-change
edge and discarded the discrimination — one line each — which is why the
expensive half of closing this was never the backends.
NULL surfaces Unsupported rather than Ok(()), for the same reason
the eleven enumeration slots do: accepting a callback that will never
fire is the silent difference RFC-0089 Part I refuses, and it is what
“hand back a guard condition nothing triggers” would look like from the
caller’s side.
Source§fn get_node_names(
&mut self,
visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool,
) -> Result<(), TransportError>
fn get_node_names( &mut self, visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool, ) -> Result<(), TransportError>
phase-381 W5/W6 — forward to the backend’s slot; NULL means UNSUPPORTED.
Without this the graph slots were unreachable for every C backend: the
trait default returns Unsupported, so a wired slot — cyclone’s, as of
W5 — would never be called and would look implemented while being dead
code. That is exactly the “a slot exists, therefore it works”
overstatement issue 0800 measured, one layer above where 0800 found it.
NULL surfaces Unsupported rather than an empty enumeration, which is
W6’s requirement: XRCE has no graph and must say “cannot tell you”, not
“nothing is there”.
Source§fn get_topic_names_and_types(
&mut self,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), TransportError>
fn get_topic_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), TransportError>
phase-381 / issue 0903 — the REST of the graph family.
W5 added get_node_names here and stopped, which made this the same
defect one method wide: every other graph call fell through to the trait
default and returned Unsupported, so the zenoh backend — which reaches
the runtime through this vtable — answered node names and NOTHING else.
Measured against a live rmw_zenoh_cpp talker: node enumeration worked
and get_topic_names_and_types returned empty, because the entity query
was never even STARTED.
Fixing one method of eleven is how the first version passed every unit test in the phase.
Source§fn get_names_and_types_by_node(
&mut self,
kind: GraphEntityKind,
node_name: &str,
node_namespace: &str,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), TransportError>
fn get_names_and_types_by_node( &mut self, kind: GraphEntityKind, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), TransportError>
phase-381 W4, wired here by the live acceptance run.
The zenoh shim implemented this and get_endpoint_info_by_topic all
along; CffiSession never dispatched them, so both fell through to the
trait default and every caller got Unsupported. That is issue 0903’s
third defect for the SIX slots the 0903 fix did not cover — it wired the
five that had a failing symptom in front of it and left these, and
check-rmw-slot-producers calls them produced either way because it
asks whether a slot has a producer, not whether anything reaches it.
Found by graph_interop.rs on its first run against a real peer, which
is the only place it could have been found.
Source§fn get_endpoint_info_by_topic(
&mut self,
publishers: bool,
topic_name: &str,
visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool,
) -> Result<(), TransportError>
fn get_endpoint_info_by_topic( &mut self, publishers: bool, topic_name: &str, visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool, ) -> Result<(), TransportError>
The endpoints on a topic — see Self::get_names_and_types_by_node for
why this was unreachable until the live run.
Source§fn supported_qos_policies(&self) -> QoSPolicyMask
fn supported_qos_policies(&self) -> QoSPolicyMask
This session MULTIPLEXES: it has no QoS behaviour of its own, so it
ROUTES the question to whichever backend registered, through
vtable.supported_qos_policies, and answers what that backend said.
nros-qos-mux: packages/rmw/cyclonedds packages/rmw/xrce packages/rmw/uorb
§This used to be a union, and a union over-claims for every member
Until issue 1329 the vtable had no slot to ask through, so this method
returned the UNION of what any backend it routes to honours. phase-428
W9 made that union DERIVED — every bit was claimed by a
nros-qos-honours: site in one of the crates above — which caught
LIVELINESS_MANUAL_BY_NODE, a bit advertised here and implemented by
nobody (issue 1328), and did nothing about the shape itself. Measured
2026-09-11, before the slot:
| policy | cyclonedds | xrce | uorb |
|---|---|---|---|
| reliability / durability / history / depth | yes | yes (to the Agent) | no |
| deadline / lifespan | yes | no | no |
| liveliness kind + lease | yes (AUTOMATIC, MANUAL_BY_TOPIC) | no | no |
| avoid_ros_namespace_conventions | no | pub + sub only | no |
Every no in that table was admitted here and then ignored downstream
— the no-silent-downgrade contract broken one layer below where it is
enforced. The answer is now the registered backend’s own, read once at
open (read_supported_qos_policies, cached in this session’s
qos_policies field); the union is gone and no second copy of it
survives to drift.
A backend that fills no slot has declared nothing, and this answers
NONE for it — loudly, with one WARN at open. See the slot’s header
block for why the absence resolves that way rather than to a union.