Skip to main content

CffiSession

Struct CffiSession 

Source
pub struct CffiSession { /* private fields */ }
Expand description

Session backed by a C vtable.

Implementations§

Source§

impl CffiSession

Source

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.

Source

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.

Source

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.

Source

pub fn node_name(&self) -> &str

Node name passed at session-open time.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl Session for CffiSession

Source§

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>

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>

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>

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>

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>

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>

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

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:

policycycloneddsxrceuorb
reliability / durability / history / depthyesyes (to the Agent)no
deadline / lifespanyesnono
liveliness kind + leaseyes (AUTOMATIC, MANUAL_BY_TOPIC)nono
avoid_ros_namespace_conventionsnopub + sub onlyno

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

Error type for this session
Source§

type PublisherHandle = CffiPublisher

Publisher handle type
Source§

type SubscriptionHandle = CffiSubscription

Subscription handle type
Source§

type ServiceHandle = CffiService

Service server handle type
Source§

type ClientHandle = CffiClient

Service client handle type
Source§

fn create_publisher( &mut self, topic: &TopicInfo<'_>, qos: QoSProfile, ) -> Result<CffiPublisher, TransportError>

Create a publisher bound to this session. Read more
Source§

fn create_subscription( &mut self, topic: &TopicInfo<'_>, qos: QoSProfile, ) -> Result<CffiSubscription, TransportError>

Create a subscriber bound to this session. Read more
Source§

fn create_service( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, ) -> Result<CffiService, TransportError>

Create a service server bound to this session. Replies are matched to requests by the sequence number returned from ServiceTrait::take_request. Read more
Source§

fn create_client( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, ) -> Result<CffiClient, TransportError>

Create a service client bound to this session. Read more
Source§

fn close(&mut self) -> Result<(), TransportError>

Close the session, releasing transport resources. All entity handles created from this session must already be dropped.
Source§

fn drive_io(&mut self, timeout_ms: i32) -> Result<(), TransportError>

Drive transport I/O (poll network, dispatch callbacks). Read more
Source§

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.). Read more
Source§

unsafe fn set_wake_callback( &mut self, cb: Option<unsafe extern "C" fn(ctx: *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. Read more
Source§

fn supports_wake_callback(&self) -> bool

Phase 130.4 — does this backend actually honour set_wake_callback? Read more
Source§

fn get_service_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), TransportError>

phase-381 W3 — every service, with its types. As Self::get_topic_names_and_types, over servers and clients.
Source§

fn count_publishers( &mut self, topic_name: &str, ) -> Result<usize, TransportError>

phase-381 W3 — how many publishers this session can see on topic_name. Read more
Source§

fn count_subscribers( &mut self, topic_name: &str, ) -> Result<usize, TransportError>

phase-381 W3 — how many subscribers this session can see on topic_name. See Self::count_publishers for the caveats.
Source§

fn ping_session(&mut self, timeout_ms: i32) -> Result<(), TransportError>

Phase 124.F.1 — session-level connectivity probe. Read more
Source§

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”). Read more
Source§

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.
Source§

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. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.