pub struct CffiSession { /* private fields */ }Expand description
Session backed by a C vtable.
Implementations§
Source§impl CffiSession
impl CffiSession
Sourcepub fn domain_id(&self) -> u32
pub fn domain_id(&self) -> u32
Domain this session was opened on. Authoritative: it is the value the backend actually got, not a re-derivation that can disagree with it.
Sourcepub fn serialization_format_cstr(&self) -> *const c_char
pub fn serialization_format_cstr(&self) -> *const c_char
RFC-0088 D4 / phase-421 W2 — the serialization format THIS session’s
backend speaks, as a NUL-terminated C string with 'static lifetime,
or NULL when the backend does not declare one.
Not <Self as Session>::SERIALIZATION_FORMAT. That const is the
trait default, "cdr", and it is a lie for this type specifically:
CffiSession is the one session that does not know its own backend at
compile time — the vtable arrives at run time through
nros_rmw_cffi_register_named, and an image may register several. The
per-session answer therefore has to come from the vtable, which is the
whole reason the slot got a body.
A NULL slot answers NULL rather than "cdr": a backend that declines
to say what it speaks has not said "cdr", and guessing on its behalf
is the mistake get_implementation_identifier’s doc made for two
phases (corrected phase-393 W2). Every in-tree backend fills the slot,
so NULL means a foreign or pre-phase-421 vtable.
Sourcepub fn serialization_format(&self) -> Option<&'static str>
pub fn serialization_format(&self) -> Option<&'static str>
serialization_format_cstr as a
Rust string. None for a NULL slot or a non-UTF-8 answer.
Sourcepub fn namespace(&self) -> &str
pub fn namespace(&self) -> &str
Node namespace passed at session-open time — issue 1444.
The sibling of node_name, and its absence was part
of why the buffer stayed empty unnoticed: node_name_buf had a reader
and namespace_buf had only the borrowed pointer a backend follows, so
nothing in Rust could ask what the session’s namespace was.
"" means the image declares none, which every backend reads as the
root. It is NOT normalised here — see namespace_buf.
Sourcepub fn open(
locator: &str,
mode: u8,
domain_id: u32,
node_name: &str,
namespace: &str,
) -> Result<Self, TransportError>
pub fn open( locator: &str, mode: u8, domain_id: u32, node_name: &str, namespace: &str, ) -> Result<Self, TransportError>
Open a new session via the default registered vtable (first entry in the registry — the RMW_IMPLEMENTATION-style fast path for single-backend builds).
For explicit backend selection in multi-backend (bridge)
binaries, use open_named.
Sourcepub fn open_with_properties(
locator: &str,
mode: u8,
domain_id: u32,
node_name: &str,
namespace: &str,
properties: &[(&str, &str)],
) -> Result<Self, TransportError>
pub fn open_with_properties( locator: &str, mode: u8, domain_id: u32, node_name: &str, namespace: &str, properties: &[(&str, &str)], ) -> Result<Self, TransportError>
phase-206 W3 — open a session carrying backend-specific configuration
properties (RmwConfig::properties across the C seam).
This is the rung that was missing. RmwConfig has carried
properties since it existed and every Rust backend reads them, but
nothing between the runtime and the vtable passed them on: this
function’s non-properties sibling handed create_session a NULL
options pointer, and the Rust-backend adapter on the far side built
properties: &[] unconditionally. So the only way to set a zenoh
listen endpoint, a TLS certificate or a scouting timeout was to build
an RmwConfig by hand in hosted Rust — a C or C++ image could state
none of it, on any platform.
Refused, never truncated: more than RMW_SESSION_MAX_PROPERTIES
entries, an empty key or value, or either longer than
SESSION_PROPERTY_BUF_LEN. All are TransportError::InvalidArgument.
Sourcepub fn open_named(
rmw_name: &str,
locator: &str,
mode: u8,
domain_id: u32,
node_name: &str,
namespace: &str,
) -> Result<Self, TransportError>
pub fn open_named( rmw_name: &str, locator: &str, mode: u8, domain_id: u32, node_name: &str, namespace: &str, ) -> Result<Self, TransportError>
Phase 104.C.1 — open a new session against a named backend.
Resolves rmw_name against the registry (Phase 104.B.2),
returns Err(TransportError::InvalidArgument) if no backend
is registered under that name.
Sourcepub fn open_named_with_properties(
rmw_name: &str,
locator: &str,
mode: u8,
domain_id: u32,
node_name: &str,
namespace: &str,
properties: &[(&str, &str)],
) -> Result<Self, TransportError>
pub fn open_named_with_properties( rmw_name: &str, locator: &str, mode: u8, domain_id: u32, node_name: &str, namespace: &str, properties: &[(&str, &str)], ) -> Result<Self, TransportError>
open_named carrying backend-specific
configuration properties — see open_with_properties.
Trait Implementations§
Source§impl Drop for CffiSession
impl Drop for CffiSession
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(user_data: *const c_void, count: usize)>,
user_data: *const c_void,
) -> Result<(), TransportError>
unsafe fn set_graph_change_callback( &mut self, callback: Option<unsafe extern "C" fn(user_data: *const c_void, count: 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.
Source§type Error = TransportError
type Error = TransportError
Source§type PublisherHandle = CffiPublisher
type PublisherHandle = CffiPublisher
Source§type SubscriptionHandle = CffiSubscription
type SubscriptionHandle = CffiSubscription
Source§type ServiceHandle = CffiService
type ServiceHandle = CffiService
Source§type ClientHandle = CffiClient
type ClientHandle = CffiClient
Source§fn create_publisher(
&mut self,
topic: &TopicInfo<'_>,
qos: QoSProfile,
) -> Result<CffiPublisher, TransportError>
fn create_publisher( &mut self, topic: &TopicInfo<'_>, qos: QoSProfile, ) -> Result<CffiPublisher, TransportError>
Source§fn create_subscription(
&mut self,
topic: &TopicInfo<'_>,
qos: QoSProfile,
) -> Result<CffiSubscription, TransportError>
fn create_subscription( &mut self, topic: &TopicInfo<'_>, qos: QoSProfile, ) -> Result<CffiSubscription, TransportError>
Source§fn create_service(
&mut self,
service: &ServiceInfo<'_>,
qos: QoSProfile,
) -> Result<CffiService, TransportError>
fn create_service( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, ) -> Result<CffiService, TransportError>
ServiceTrait::take_request. Read moreSource§fn create_client(
&mut self,
service: &ServiceInfo<'_>,
qos: QoSProfile,
) -> Result<CffiClient, TransportError>
fn create_client( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, ) -> Result<CffiClient, TransportError>
Source§fn close(&mut self) -> Result<(), TransportError>
fn close(&mut self) -> Result<(), TransportError>
Source§fn drive_io(&mut self, timeout_ms: i32) -> Result<(), TransportError>
fn drive_io(&mut self, timeout_ms: i32) -> Result<(), TransportError>
Source§fn next_deadline_ms(&self) -> Option<u32>
fn next_deadline_ms(&self) -> Option<u32>
Source§unsafe fn set_wake_callback(
&mut self,
cb: Option<unsafe extern "C" fn(ctx: *mut c_void)>,
ctx: *mut c_void,
)
unsafe fn set_wake_callback( &mut self, cb: Option<unsafe extern "C" fn(ctx: *mut c_void)>, ctx: *mut c_void, )
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. Read moreSource§fn supports_wake_callback(&self) -> bool
fn supports_wake_callback(&self) -> bool
set_wake_callback? Read moreSource§fn get_service_names_and_types(
&mut self,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), TransportError>
fn get_service_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), TransportError>
Self::get_topic_names_and_types, over servers and clients.Source§fn count_publishers(
&mut self,
topic_name: &str,
) -> Result<usize, TransportError>
fn count_publishers( &mut self, topic_name: &str, ) -> Result<usize, TransportError>
topic_name. Read moreSource§fn count_subscribers(
&mut self,
topic_name: &str,
) -> Result<usize, TransportError>
fn count_subscribers( &mut self, topic_name: &str, ) -> Result<usize, TransportError>
topic_name.
See Self::count_publishers for the caveats.Source§fn ping_session(&mut self, timeout_ms: i32) -> Result<(), TransportError>
fn ping_session(&mut self, timeout_ms: i32) -> Result<(), TransportError>
Source§const SERIALIZATION_FORMAT: &'static str = _
const SERIALIZATION_FORMAT: &'static str = _
rmw_get_serialization_format() reports it (“One middleware can only
have one encoding”). Read moreSource§const SERIALIZATION_FORMAT_ID: SerializationFormatId = nros_serdes::format::SerializationFormatId::Cdr
const SERIALIZATION_FORMAT_ID: SerializationFormatId = nros_serdes::format::SerializationFormatId::Cdr
Self::SERIALIZATION_FORMAT. Never
persisted, never compared across images — see nros_serdes::format.Source§fn 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>
inbox. Read more