1#![no_std]
19
20#[cfg(feature = "alloc")]
21extern crate alloc;
22
23#[cfg(feature = "std")]
24extern crate std;
25
26use core::{cell::UnsafeCell, ffi::c_void, sync::atomic::Ordering};
27
28use nros_rmw::{
29 MessageInfo, Publisher, QosDurabilityPolicy, QosHistoryPolicy, QosReliabilityPolicy,
30 QosSettings, ServiceClientTrait, ServiceInfo, ServiceRequest, ServiceServerTrait, Session,
31 TopicInfo, TransportError,
32};
33
34#[cfg(feature = "alloc")]
38pub mod rust_adapter;
39
40#[cfg(feature = "alloc")]
41pub use rust_adapter::{RustBackend, RustBackendAdapter};
42
43pub mod section;
46
47pub type NrosRmwRet = i32;
59
60pub const NROS_RMW_RET_OK: NrosRmwRet = 0;
62pub const NROS_RMW_RET_ERROR: NrosRmwRet = -1;
64pub const NROS_RMW_RET_TIMEOUT: NrosRmwRet = -2;
66pub const NROS_RMW_RET_BAD_ALLOC: NrosRmwRet = -3;
68pub const NROS_RMW_RET_INVALID_ARGUMENT: NrosRmwRet = -4;
70pub const NROS_RMW_RET_UNSUPPORTED: NrosRmwRet = -5;
72pub const NROS_RMW_RET_INCOMPATIBLE_QOS: NrosRmwRet = -6;
74pub const NROS_RMW_RET_TOPIC_NAME_INVALID: NrosRmwRet = -7;
76pub const NROS_RMW_RET_NODE_NAME_NON_EXISTENT: NrosRmwRet = -8;
78pub const NROS_RMW_RET_LOAN_NOT_SUPPORTED: NrosRmwRet = -9;
80pub const NROS_RMW_RET_NO_DATA: NrosRmwRet = -10;
82pub const NROS_RMW_RET_WOULD_BLOCK: NrosRmwRet = -11;
84pub const NROS_RMW_RET_BUFFER_TOO_SMALL: NrosRmwRet = -12;
86pub const NROS_RMW_RET_MESSAGE_TOO_LARGE: NrosRmwRet = -13;
88
89#[cfg(feature = "c-stub-test")]
94unsafe extern "C" {
95 fn nros_c_stub_make_ops(out: *mut core::ffi::c_void);
96 fn nros_c_stub_reset_counters();
97 fn nros_c_stub_get_open_calls() -> u32;
98 fn nros_c_stub_get_close_calls() -> u32;
99 fn nros_c_stub_get_write_calls() -> u32;
100 fn nros_c_stub_get_read_calls() -> u32;
101}
102#[cfg(feature = "c-stub-test")]
103#[doc(hidden)]
104pub fn _c_stub_transport_vtable_anchor() -> [*const core::ffi::c_void; 6] {
105 [
106 nros_c_stub_make_ops as *const _,
107 nros_c_stub_reset_counters as *const _,
108 nros_c_stub_get_open_calls as *const _,
109 nros_c_stub_get_close_calls as *const _,
110 nros_c_stub_get_write_calls as *const _,
111 nros_c_stub_get_read_calls as *const _,
112 ]
113}
114pub const NROS_RMW_RET_INCOMPATIBLE_ABI: NrosRmwRet = -14;
120
121pub const NROS_RMW_RET_NO_BACKEND: NrosRmwRet = -15;
125
126pub const NROS_RMW_RET_AMBIGUOUS_BACKEND: NrosRmwRet = -16;
130
131pub const NROS_RMW_RET_UNKNOWN_BACKEND: NrosRmwRet = -17;
134
135pub const NROS_RMW_RET_CONNECTION_FAILED: NrosRmwRet = -18;
141
142pub fn ret_from_error(err: &TransportError) -> NrosRmwRet {
149 match err {
150 TransportError::Timeout => NROS_RMW_RET_TIMEOUT,
151 TransportError::WouldBlock => NROS_RMW_RET_WOULD_BLOCK,
152 TransportError::TooLarge => NROS_RMW_RET_MESSAGE_TOO_LARGE,
153 TransportError::BufferTooSmall => NROS_RMW_RET_BUFFER_TOO_SMALL,
154 TransportError::MessageTooLarge => NROS_RMW_RET_MESSAGE_TOO_LARGE,
155 TransportError::InvalidArgument => NROS_RMW_RET_INVALID_ARGUMENT,
156 TransportError::InvalidConfig => NROS_RMW_RET_INVALID_ARGUMENT,
157 TransportError::Unsupported => NROS_RMW_RET_UNSUPPORTED,
158 TransportError::BadAlloc => NROS_RMW_RET_BAD_ALLOC,
159 TransportError::IncompatibleQos => NROS_RMW_RET_INCOMPATIBLE_QOS,
160 TransportError::TopicNameInvalid => NROS_RMW_RET_TOPIC_NAME_INVALID,
161 TransportError::NodeNameNonExistent => NROS_RMW_RET_NODE_NAME_NON_EXISTENT,
162 TransportError::LoanNotSupported => NROS_RMW_RET_LOAN_NOT_SUPPORTED,
163 TransportError::NoData => NROS_RMW_RET_NO_DATA,
164 TransportError::IncompatibleAbi => NROS_RMW_RET_INCOMPATIBLE_ABI,
165 TransportError::ConnectionFailed | TransportError::Disconnected => {
175 NROS_RMW_RET_CONNECTION_FAILED
176 }
177 _ => NROS_RMW_RET_ERROR,
181 }
182}
183
184pub fn error_from_ret(ret: NrosRmwRet) -> TransportError {
195 match ret {
196 NROS_RMW_RET_OK => {
197 TransportError::Backend("ok (logic error: positive ret_t at error site)")
198 }
199 NROS_RMW_RET_ERROR => TransportError::Backend("rmw_ret error"),
200 NROS_RMW_RET_TIMEOUT => TransportError::Timeout,
201 NROS_RMW_RET_BAD_ALLOC => TransportError::BadAlloc,
202 NROS_RMW_RET_INVALID_ARGUMENT => TransportError::InvalidArgument,
203 NROS_RMW_RET_UNSUPPORTED => TransportError::Unsupported,
204 NROS_RMW_RET_INCOMPATIBLE_QOS => TransportError::IncompatibleQos,
205 NROS_RMW_RET_TOPIC_NAME_INVALID => TransportError::TopicNameInvalid,
206 NROS_RMW_RET_NODE_NAME_NON_EXISTENT => TransportError::NodeNameNonExistent,
207 NROS_RMW_RET_LOAN_NOT_SUPPORTED => TransportError::LoanNotSupported,
208 NROS_RMW_RET_NO_DATA => TransportError::NoData,
209 NROS_RMW_RET_WOULD_BLOCK => TransportError::WouldBlock,
210 NROS_RMW_RET_BUFFER_TOO_SMALL => TransportError::BufferTooSmall,
211 NROS_RMW_RET_MESSAGE_TOO_LARGE => TransportError::MessageTooLarge,
212 NROS_RMW_RET_INCOMPATIBLE_ABI => TransportError::IncompatibleAbi,
213 NROS_RMW_RET_CONNECTION_FAILED => TransportError::ConnectionFailed,
220 _ => TransportError::Backend("unknown rmw_ret_t"),
221 }
222}
223
224#[repr(u8)]
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub enum NrosRmwLivelinessKind {
237 None = 0,
238 Automatic = 1,
239 ManualByTopic = 2,
240 ManualByNode = 3,
241}
242
243#[repr(C)]
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub struct NrosRmwQos {
248 pub reliability: u8,
250 pub durability: u8,
252 pub history: u8,
254 pub liveliness_kind: u8,
256 pub depth: u16,
258 pub tx_express: u8,
263 pub _reserved0: u8,
265
266 pub deadline_ms: u32,
269 pub lifespan_ms: u32,
271 pub liveliness_lease_ms: u32,
273 pub avoid_ros_namespace_conventions: u8,
277 pub _reserved1: [u8; 3],
279 pub rx_buffer_hint: u32,
285}
286
287pub const NROS_RMW_QOS_PROFILE_DEFAULT: NrosRmwQos = NrosRmwQos {
289 reliability: 1, durability: 0, history: 0, liveliness_kind: NrosRmwLivelinessKind::Automatic as u8,
293 depth: 10,
294 tx_express: 0,
295 _reserved0: 0,
296 deadline_ms: 0,
297 lifespan_ms: 0,
298 liveliness_lease_ms: 0,
299 avoid_ros_namespace_conventions: 0,
300 _reserved1: [0; 3],
301 rx_buffer_hint: 0,
302};
303
304pub const NROS_RMW_QOS_PROFILE_SENSOR_DATA: NrosRmwQos = NrosRmwQos {
306 reliability: 0, durability: 0, history: 0, liveliness_kind: NrosRmwLivelinessKind::Automatic as u8,
310 depth: 5,
311 tx_express: 0,
312 _reserved0: 0,
313 deadline_ms: 0,
314 lifespan_ms: 0,
315 liveliness_lease_ms: 0,
316 avoid_ros_namespace_conventions: 0,
317 _reserved1: [0; 3],
318 rx_buffer_hint: 0,
319};
320
321pub const NROS_RMW_QOS_PROFILE_SERVICES_DEFAULT: NrosRmwQos = NROS_RMW_QOS_PROFILE_DEFAULT;
323
324pub const NROS_RMW_QOS_PROFILE_PARAMETERS: NrosRmwQos = NrosRmwQos {
326 depth: 1000,
327 ..NROS_RMW_QOS_PROFILE_DEFAULT
328};
329
330pub const NROS_RMW_QOS_PROFILE_SYSTEM_DEFAULT: NrosRmwQos = NROS_RMW_QOS_PROFILE_DEFAULT;
332
333#[repr(C)]
335pub struct NrosRmwSession {
336 pub node_name: *const u8,
338 pub namespace_: *const u8,
340 pub _reserved: [u8; 8],
343 pub backend_data: *mut c_void,
345}
346
347#[repr(C)]
353pub struct NrosRmwPublisher {
354 pub topic_name: *const u8,
356 pub type_name: *const u8,
358 pub qos: NrosRmwQos,
359 pub can_loan_messages: bool,
361 pub _reserved: [u8; 7],
363 pub backend_data: *mut c_void,
365}
366
367#[repr(C)]
369pub struct NrosRmwSubscriber {
370 pub topic_name: *const u8,
372 pub type_name: *const u8,
374 pub qos: NrosRmwQos,
375 pub can_loan_messages: bool,
377 pub _reserved: [u8; 7],
379 pub backend_data: *mut c_void,
381}
382
383#[repr(C)]
385pub struct NrosRmwServiceServer {
386 pub service_name: *const u8,
388 pub type_name: *const u8,
390 pub _reserved: [u8; 8],
392 pub backend_data: *mut c_void,
394}
395
396#[repr(C)]
398pub struct NrosRmwServiceClient {
399 pub service_name: *const u8,
401 pub type_name: *const u8,
403 pub _reserved: [u8; 8],
405 pub backend_data: *mut c_void,
407}
408
409impl From<QosSettings> for NrosRmwQos {
410 fn from(qos: QosSettings) -> Self {
411 Self {
412 reliability: match qos.reliability {
413 QosReliabilityPolicy::BestEffort => 0,
414 QosReliabilityPolicy::Reliable => 1,
415 },
416 durability: match qos.durability {
417 QosDurabilityPolicy::Volatile => 0,
418 QosDurabilityPolicy::TransientLocal => 1,
419 },
420 history: match qos.history {
421 QosHistoryPolicy::KeepLast => 0,
422 QosHistoryPolicy::KeepAll => 1,
423 },
424 liveliness_kind: qos.liveliness_kind as u8,
425 depth: qos.depth.min(u16::MAX as u32) as u16,
429 tx_express: qos.tx_express as u8,
430 _reserved0: 0,
431 deadline_ms: qos.deadline_ms,
432 lifespan_ms: qos.lifespan_ms,
433 liveliness_lease_ms: qos.liveliness_lease_ms,
434 avoid_ros_namespace_conventions: qos.avoid_ros_namespace_conventions as u8,
435 _reserved1: [0; 3],
436 rx_buffer_hint: 0,
437 }
438 }
439}
440
441#[repr(C)]
453pub struct NrosRmwVtable {
454 pub open: unsafe extern "C" fn(
456 locator: *const u8,
457 mode: u8,
458 domain_id: u32,
459 node_name: *const u8,
460 out: *mut NrosRmwSession,
461 ) -> NrosRmwRet,
462 pub close: unsafe extern "C" fn(session: *mut NrosRmwSession) -> NrosRmwRet,
463 pub drive_io: unsafe extern "C" fn(session: *mut NrosRmwSession, timeout_ms: i32) -> NrosRmwRet,
464
465 pub create_publisher: unsafe extern "C" fn(
467 session: *mut NrosRmwSession,
468 topic_name: *const u8,
469 type_name: *const u8,
470 type_hash: *const u8,
471 domain_id: u32,
472 qos: *const NrosRmwQos,
473 out: *mut NrosRmwPublisher,
474 ) -> NrosRmwRet,
475 pub destroy_publisher: unsafe extern "C" fn(publisher: *mut NrosRmwPublisher),
476 pub publish_raw: unsafe extern "C" fn(
477 publisher: *mut NrosRmwPublisher,
478 data: *const u8,
479 len: usize,
480 ) -> NrosRmwRet,
481
482 pub create_subscriber: unsafe extern "C" fn(
484 session: *mut NrosRmwSession,
485 topic_name: *const u8,
486 type_name: *const u8,
487 type_hash: *const u8,
488 domain_id: u32,
489 qos: *const NrosRmwQos,
490 out: *mut NrosRmwSubscriber,
491 ) -> NrosRmwRet,
492 pub destroy_subscriber: unsafe extern "C" fn(subscriber: *mut NrosRmwSubscriber),
493 pub try_recv_raw: unsafe extern "C" fn(
494 subscriber: *mut NrosRmwSubscriber,
495 buf: *mut u8,
496 buf_len: usize,
497 ) -> i32,
498 pub has_data: unsafe extern "C" fn(subscriber: *mut NrosRmwSubscriber) -> i32,
499
500 pub create_service_server: unsafe extern "C" fn(
503 session: *mut NrosRmwSession,
504 service_name: *const u8,
505 type_name: *const u8,
506 type_hash: *const u8,
507 domain_id: u32,
508 qos: *const NrosRmwQos,
509 out: *mut NrosRmwServiceServer,
510 ) -> NrosRmwRet,
511 pub destroy_service_server: unsafe extern "C" fn(server: *mut NrosRmwServiceServer),
512 pub try_recv_request: unsafe extern "C" fn(
513 server: *mut NrosRmwServiceServer,
514 buf: *mut u8,
515 buf_len: usize,
516 seq_out: *mut i64,
517 ) -> i32,
518 pub has_request: unsafe extern "C" fn(server: *mut NrosRmwServiceServer) -> i32,
519 pub send_reply: unsafe extern "C" fn(
520 server: *mut NrosRmwServiceServer,
521 seq: i64,
522 data: *const u8,
523 len: usize,
524 ) -> NrosRmwRet,
525
526 pub create_service_client: unsafe extern "C" fn(
528 session: *mut NrosRmwSession,
529 service_name: *const u8,
530 type_name: *const u8,
531 type_hash: *const u8,
532 domain_id: u32,
533 qos: *const NrosRmwQos,
534 out: *mut NrosRmwServiceClient,
535 ) -> NrosRmwRet,
536 pub destroy_service_client: unsafe extern "C" fn(client: *mut NrosRmwServiceClient),
537 pub call_raw: unsafe extern "C" fn(
538 client: *mut NrosRmwServiceClient,
539 request: *const u8,
540 req_len: usize,
541 reply_buf: *mut u8,
542 reply_buf_len: usize,
543 ) -> i32,
544
545 pub send_request_raw: Option<
547 unsafe extern "C" fn(
548 client: *mut NrosRmwServiceClient,
549 request: *const u8,
550 req_len: usize,
551 ) -> NrosRmwRet,
552 >,
553 pub try_recv_reply_raw: Option<
554 unsafe extern "C" fn(
555 client: *mut NrosRmwServiceClient,
556 reply_buf: *mut u8,
557 reply_buf_len: usize,
558 ) -> i32,
559 >,
560
561 pub register_subscriber_event: unsafe extern "C" fn(
563 subscriber: *mut NrosRmwSubscriber,
564 kind: NrosRmwEventKind,
565 deadline_ms: u32,
566 cb: NrosRmwEventCallback,
567 user_context: *mut c_void,
568 ) -> NrosRmwRet,
569
570 pub register_publisher_event: unsafe extern "C" fn(
571 publisher: *mut NrosRmwPublisher,
572 kind: NrosRmwEventKind,
573 deadline_ms: u32,
574 cb: NrosRmwEventCallback,
575 user_context: *mut c_void,
576 ) -> NrosRmwRet,
577
578 pub assert_publisher_liveliness:
580 unsafe extern "C" fn(publisher: *mut NrosRmwPublisher) -> NrosRmwRet,
581
582 pub next_deadline_ms: Option<unsafe extern "C" fn(session: *const NrosRmwSession) -> i32>,
586
587 pub set_wake_callback: Option<
597 unsafe extern "C" fn(
598 session: *mut NrosRmwSession,
599 cb: Option<unsafe extern "C" fn(ctx: *mut core::ffi::c_void)>,
600 ctx: *mut core::ffi::c_void,
601 ) -> NrosRmwRet,
602 >,
603
604 pub pub_loan: Option<
609 unsafe extern "C" fn(
610 publisher: *mut NrosRmwPublisher,
611 requested_len: usize,
612 out_buf: *mut *mut u8,
613 out_cap: *mut usize,
614 out_token: *mut *mut core::ffi::c_void,
615 ) -> NrosRmwRet,
616 >,
617 pub pub_commit: Option<
620 unsafe extern "C" fn(
621 publisher: *mut NrosRmwPublisher,
622 token: *mut core::ffi::c_void,
623 actual_len: usize,
624 ) -> NrosRmwRet,
625 >,
626 pub pub_discard: Option<
629 unsafe extern "C" fn(publisher: *mut NrosRmwPublisher, token: *mut core::ffi::c_void),
630 >,
631
632 pub sub_borrow: Option<
637 unsafe extern "C" fn(
638 subscriber: *mut NrosRmwSubscriber,
639 out_buf: *mut *const u8,
640 out_len: *mut usize,
641 out_token: *mut *mut core::ffi::c_void,
642 ) -> i32,
643 >,
644 pub sub_release: Option<
647 unsafe extern "C" fn(subscriber: *mut NrosRmwSubscriber, token: *mut core::ffi::c_void),
648 >,
649
650 pub service_server_available:
660 Option<unsafe extern "C" fn(client: *mut NrosRmwServiceClient) -> i32>,
661
662 pub try_recv_sequence: Option<
675 unsafe extern "C" fn(
676 subscriber: *mut NrosRmwSubscriber,
677 buf: *mut u8,
678 per_msg_cap: usize,
679 max_msgs: usize,
680 out_lens: *mut usize,
681 ) -> i32,
682 >,
683
684 pub publish_streamed: Option<
696 unsafe extern "C" fn(
697 publisher: *mut NrosRmwPublisher,
698 size_cb: unsafe extern "C" fn(
699 out_total_len: *mut usize,
700 user_ctx: *mut core::ffi::c_void,
701 ),
702 chunk_cb: unsafe extern "C" fn(
703 out_buf: *mut u8,
704 cap: usize,
705 out_written: *mut usize,
706 user_ctx: *mut core::ffi::c_void,
707 ),
708 user_ctx: *mut core::ffi::c_void,
709 ) -> NrosRmwRet,
710 >,
711
712 pub ping_session:
722 Option<unsafe extern "C" fn(session: *mut NrosRmwSession, timeout_ms: i32) -> NrosRmwRet>,
723
724 pub subscriber_supports_in_place:
730 Option<unsafe extern "C" fn(subscriber: *mut NrosRmwSubscriber) -> i32>,
731
732 pub process_raw_in_place: Option<
739 unsafe extern "C" fn(
740 subscriber: *mut NrosRmwSubscriber,
741 ctx: *mut core::ffi::c_void,
742 cb: unsafe extern "C" fn(ctx: *mut core::ffi::c_void, ptr: *const u8, len: usize),
743 ) -> i32,
744 >,
745}
746
747#[repr(u8)]
754#[derive(Debug, Clone, Copy, PartialEq, Eq)]
755pub enum NrosRmwEventKind {
756 LivelinessChanged = 0,
757 RequestedDeadlineMissed = 1,
758 MessageLost = 2,
759 LivelinessLost = 3,
760 OfferedDeadlineMissed = 4,
761}
762
763impl From<nros_rmw::EventKind> for NrosRmwEventKind {
764 fn from(k: nros_rmw::EventKind) -> Self {
765 use nros_rmw::EventKind as K;
766 match k {
767 K::LivelinessChanged => NrosRmwEventKind::LivelinessChanged,
768 K::RequestedDeadlineMissed => NrosRmwEventKind::RequestedDeadlineMissed,
769 K::MessageLost => NrosRmwEventKind::MessageLost,
770 K::LivelinessLost => NrosRmwEventKind::LivelinessLost,
771 K::OfferedDeadlineMissed => NrosRmwEventKind::OfferedDeadlineMissed,
772 _ => NrosRmwEventKind::MessageLost, }
774 }
775}
776
777#[repr(C)]
779#[derive(Debug, Clone, Copy, Default)]
780pub struct NrosRmwLivelinessChangedStatus {
781 pub alive_count: u16,
782 pub not_alive_count: u16,
783 pub alive_count_change: i16,
784 pub not_alive_count_change: i16,
785}
786
787#[repr(C)]
789#[derive(Debug, Clone, Copy, Default)]
790pub struct NrosRmwCountStatus {
791 pub total_count: u32,
792 pub total_count_change: u32,
793}
794
795#[repr(C)]
798pub union NrosRmwEventPayload {
799 pub liveliness_changed: NrosRmwLivelinessChangedStatus,
800 pub count: NrosRmwCountStatus,
801}
802
803pub type NrosRmwEventCallback = unsafe extern "C" fn(
805 kind: NrosRmwEventKind,
806 payload: *const NrosRmwEventPayload,
807 user_context: *mut c_void,
808);
809
810pub const MAX_BACKENDS: usize = parse_max_backends(env!("NROS_RMW_MAX_BACKENDS"));
834
835const fn parse_max_backends(s: &str) -> usize {
836 let bytes = s.as_bytes();
837 let mut i = 0usize;
838 let mut acc: usize = 0;
839 while i < bytes.len() {
840 let d = bytes[i];
841 assert!(
842 d.is_ascii_digit(),
843 "NROS_RMW_MAX_BACKENDS must be a decimal integer"
844 );
845 acc = acc * 10 + (d - b'0') as usize;
846 i += 1;
847 }
848 acc
849}
850
851const BACKEND_NAME_MAX: usize = 32;
854
855#[repr(C)]
856struct BackendSlot {
857 name: [u8; BACKEND_NAME_MAX],
860 vtable: *const NrosRmwVtable,
861}
862
863impl BackendSlot {
864 const fn empty() -> Self {
865 Self {
866 name: [0u8; BACKEND_NAME_MAX],
867 vtable: core::ptr::null(),
868 }
869 }
870
871 #[inline]
872 fn is_empty(&self) -> bool {
873 self.name[0] == 0
874 }
875
876 #[inline]
877 fn name_matches(&self, candidate: &[u8]) -> bool {
878 if self.is_empty() {
879 return false;
880 }
881 let mut i = 0usize;
883 while i < self.name.len() && i < candidate.len() {
884 if self.name[i] == 0 {
885 return false; }
887 if self.name[i] != candidate[i] {
888 return false;
889 }
890 i += 1;
891 }
892 i == candidate.len() && (i == self.name.len() || self.name[i] == 0)
894 }
895}
896
897unsafe impl Sync for BackendSlot {}
903
904#[doc(hidden)]
918pub struct Registry {
919 slots: core::cell::UnsafeCell<[BackendSlot; MAX_BACKENDS]>,
920 len: portable_atomic::AtomicUsize,
921}
922
923impl Registry {
924 #[doc(hidden)]
925 pub const fn new() -> Self {
926 let slots = {
927 #[allow(clippy::declare_interior_mutable_const)]
928 const E: BackendSlot = BackendSlot::empty();
929 [E; MAX_BACKENDS]
930 };
931 Self {
932 slots: core::cell::UnsafeCell::new(slots),
933 len: portable_atomic::AtomicUsize::new(0),
934 }
935 }
936
937 #[inline]
940 unsafe fn slot(&self, i: usize) -> &BackendSlot {
941 unsafe { &(*self.slots.get())[i] }
944 }
945
946 #[inline]
950 #[allow(clippy::mut_from_ref)]
951 unsafe fn slot_mut(&self, i: usize) -> &mut BackendSlot {
952 unsafe { &mut (*self.slots.get())[i] }
954 }
955}
956
957unsafe impl Sync for Registry {}
959
960#[unsafe(no_mangle)]
968static REGISTRY: Registry = Registry::new();
969
970#[inline]
972fn registry() -> &'static Registry {
973 ®ISTRY
974}
975
976const MESSAGE_INFO_SLOTS: usize = 64;
988
989struct MessageInfoSlot {
990 key: portable_atomic::AtomicUsize,
991 valid: portable_atomic::AtomicBool,
992 info: UnsafeCell<MessageInfo>,
993 #[cfg(all(feature = "alloc", feature = "safety-e2e"))]
994 validate_requested: portable_atomic::AtomicBool,
995 #[cfg(all(feature = "alloc", feature = "safety-e2e"))]
996 integrity_valid: portable_atomic::AtomicBool,
997 #[cfg(all(feature = "alloc", feature = "safety-e2e"))]
998 integrity: UnsafeCell<nros_rmw::IntegrityStatus>,
999}
1000
1001impl MessageInfoSlot {
1002 const fn empty() -> Self {
1003 Self {
1004 key: portable_atomic::AtomicUsize::new(0),
1005 valid: portable_atomic::AtomicBool::new(false),
1006 info: UnsafeCell::new(MessageInfo::new()),
1007 #[cfg(all(feature = "alloc", feature = "safety-e2e"))]
1008 validate_requested: portable_atomic::AtomicBool::new(false),
1009 #[cfg(all(feature = "alloc", feature = "safety-e2e"))]
1010 integrity_valid: portable_atomic::AtomicBool::new(false),
1011 #[cfg(all(feature = "alloc", feature = "safety-e2e"))]
1012 integrity: UnsafeCell::new(nros_rmw::IntegrityStatus {
1013 gap: 0,
1014 duplicate: false,
1015 crc_valid: None,
1016 }),
1017 }
1018 }
1019}
1020
1021unsafe impl Sync for MessageInfoSlot {}
1025
1026static MESSAGE_INFO_TABLE: [MessageInfoSlot; MESSAGE_INFO_SLOTS] = {
1027 #[allow(clippy::declare_interior_mutable_const)]
1028 const E: MessageInfoSlot = MessageInfoSlot::empty();
1029 [E; MESSAGE_INFO_SLOTS]
1030};
1031
1032fn lookup_message_info_slot(key: usize) -> Option<&'static MessageInfoSlot> {
1033 if key == 0 {
1034 return None;
1035 }
1036 MESSAGE_INFO_TABLE
1037 .iter()
1038 .find(|slot| slot.key.load(Ordering::Acquire) == key)
1039}
1040
1041#[cfg(feature = "alloc")]
1042fn get_or_insert_message_info_slot(key: usize) -> Option<&'static MessageInfoSlot> {
1043 if key == 0 {
1044 return None;
1045 }
1046 for slot in &MESSAGE_INFO_TABLE {
1047 let current = slot.key.load(Ordering::Acquire);
1048 if current == key {
1049 return Some(slot);
1050 }
1051 if current == 0
1052 && slot
1053 .key
1054 .compare_exchange(0, key, Ordering::AcqRel, Ordering::Acquire)
1055 .is_ok()
1056 {
1057 return Some(slot);
1058 }
1059 }
1060 None
1061}
1062
1063#[cfg(feature = "alloc")]
1064pub(crate) fn store_cffi_message_info(key: usize, info: Option<MessageInfo>) {
1065 let Some(slot) = get_or_insert_message_info_slot(key) else {
1066 return;
1067 };
1068 match info {
1069 Some(info) => {
1070 unsafe {
1074 *slot.info.get() = info;
1075 }
1076 slot.valid.store(true, Ordering::Release);
1077 }
1078 None => slot.valid.store(false, Ordering::Release),
1079 }
1080}
1081
1082fn take_cffi_message_info(key: usize) -> Option<MessageInfo> {
1083 let slot = lookup_message_info_slot(key)?;
1084 if !slot.valid.swap(false, Ordering::AcqRel) {
1085 return None;
1086 }
1087 Some(unsafe { *slot.info.get() })
1090}
1091
1092#[cfg(all(feature = "alloc", feature = "safety-e2e"))]
1093fn request_cffi_integrity_status(key: usize) {
1094 let Some(slot) = get_or_insert_message_info_slot(key) else {
1095 return;
1096 };
1097 slot.integrity_valid.store(false, Ordering::Release);
1098 slot.validate_requested.store(true, Ordering::Release);
1099}
1100
1101#[cfg(all(feature = "alloc", feature = "safety-e2e"))]
1102pub(crate) fn take_cffi_integrity_request(key: usize) -> bool {
1103 lookup_message_info_slot(key)
1104 .map(|slot| slot.validate_requested.swap(false, Ordering::AcqRel))
1105 .unwrap_or(false)
1106}
1107
1108#[cfg(all(feature = "alloc", feature = "safety-e2e"))]
1109pub(crate) fn store_cffi_integrity_status(key: usize, status: nros_rmw::IntegrityStatus) {
1110 let Some(slot) = get_or_insert_message_info_slot(key) else {
1111 return;
1112 };
1113 unsafe {
1116 *slot.integrity.get() = status;
1117 }
1118 slot.integrity_valid.store(true, Ordering::Release);
1119}
1120
1121#[cfg(all(feature = "alloc", feature = "safety-e2e"))]
1122fn take_cffi_integrity_status(key: usize) -> Option<nros_rmw::IntegrityStatus> {
1123 let slot = lookup_message_info_slot(key)?;
1124 if !slot.integrity_valid.swap(false, Ordering::AcqRel) {
1125 return None;
1126 }
1127 Some(unsafe { *slot.integrity.get() })
1128}
1129
1130fn clear_cffi_message_info(key: usize) {
1131 let Some(slot) = lookup_message_info_slot(key) else {
1132 return;
1133 };
1134 slot.valid.store(false, Ordering::Release);
1135 #[cfg(all(feature = "alloc", feature = "safety-e2e"))]
1136 {
1137 slot.validate_requested.store(false, Ordering::Release);
1138 slot.integrity_valid.store(false, Ordering::Release);
1139 }
1140 slot.key.store(0, Ordering::Release);
1141}
1142
1143#[deprecated(
1162 since = "0.2.0",
1163 note = "use nros_rmw_cffi_register_named with the backend's canonical name; the unnamed shim will be removed"
1164)]
1165#[unsafe(no_mangle)]
1166pub unsafe extern "C" fn nros_rmw_cffi_register(vtable: *const NrosRmwVtable) -> NrosRmwRet {
1167 unsafe { nros_rmw_cffi_register_named(c"default".as_ptr(), vtable) }
1168}
1169
1170#[unsafe(no_mangle)]
1195pub unsafe extern "C" fn nros_rmw_cffi_register_named(
1196 name: *const core::ffi::c_char,
1197 vtable: *const NrosRmwVtable,
1198) -> NrosRmwRet {
1199 if name.is_null() || vtable.is_null() {
1200 return NROS_RMW_RET_INVALID_ARGUMENT;
1201 }
1202 let name_u8 = name.cast::<u8>();
1203
1204 let mut len = 0usize;
1207 while len < BACKEND_NAME_MAX {
1208 let b = unsafe { *name_u8.add(len) };
1209 if b == 0 {
1210 break;
1211 }
1212 len += 1;
1213 }
1214 if len == 0 {
1215 return NROS_RMW_RET_INVALID_ARGUMENT;
1216 }
1217 if unsafe { *name_u8.add(len) } != 0 {
1219 return NROS_RMW_RET_INVALID_ARGUMENT;
1220 }
1221
1222 let name_bytes = unsafe { core::slice::from_raw_parts(name_u8, len) };
1223
1224 let current_len = registry().len.load(Ordering::Acquire);
1226 for i in 0..current_len {
1227 let slot = unsafe { registry().slot(i) };
1229 if slot.name_matches(name_bytes) {
1230 unsafe {
1234 let slot_mut = registry().slot_mut(i);
1235 slot_mut.vtable = vtable;
1236 }
1237 core::sync::atomic::fence(Ordering::Release);
1238 return NROS_RMW_RET_OK;
1239 }
1240 }
1241
1242 let idx = registry().len.fetch_add(1, Ordering::AcqRel);
1244 if idx >= MAX_BACKENDS {
1245 registry().len.store(MAX_BACKENDS, Ordering::Release);
1249 return NROS_RMW_RET_ERROR;
1250 }
1251
1252 unsafe {
1254 let slot = registry().slot_mut(idx);
1255 slot.name[..len].copy_from_slice(name_bytes);
1256 slot.name[len] = 0;
1257 slot.vtable = vtable;
1258 }
1259 core::sync::atomic::fence(Ordering::Release);
1262 NROS_RMW_RET_OK
1263}
1264
1265#[unsafe(no_mangle)]
1272pub unsafe extern "C" fn nros_rmw_cffi_lookup(
1273 name: *const core::ffi::c_char,
1274) -> *const NrosRmwVtable {
1275 if name.is_null() {
1276 return core::ptr::null();
1277 }
1278 let name_u8 = name.cast::<u8>();
1279 let mut len = 0usize;
1280 while len < BACKEND_NAME_MAX {
1281 if unsafe { *name_u8.add(len) } == 0 {
1282 break;
1283 }
1284 len += 1;
1285 }
1286 if len == 0 || len == BACKEND_NAME_MAX {
1287 return core::ptr::null();
1288 }
1289 let name_bytes = unsafe { core::slice::from_raw_parts(name_u8, len) };
1290
1291 let current_len = registry().len.load(Ordering::Acquire);
1292 for i in 0..current_len {
1293 let slot = unsafe { registry().slot(i) };
1296 if slot.name_matches(name_bytes) {
1297 return slot.vtable;
1298 }
1299 }
1300 core::ptr::null()
1301}
1302
1303#[unsafe(no_mangle)]
1312pub unsafe extern "C" fn nros_rmw_cffi_registered_names(
1313 buf: *mut *const core::ffi::c_char,
1314 cap: usize,
1315) -> usize {
1316 let n = registry().len.load(Ordering::Acquire);
1317 if !buf.is_null() && cap > 0 {
1318 let limit = n.min(cap);
1319 for i in 0..limit {
1320 let slot = unsafe { registry().slot(i) };
1322 unsafe {
1323 buf.add(i)
1324 .write(slot.name.as_ptr() as *const core::ffi::c_char)
1325 };
1326 }
1327 }
1328 n
1329}
1330
1331#[inline]
1336pub fn backend_registered() -> bool {
1337 registry().len.load(Ordering::Acquire) > 0
1338}
1339
1340fn default_vtable() -> Option<&'static NrosRmwVtable> {
1345 let n = registry().len.load(Ordering::Acquire);
1346 if n == 0 {
1347 return None;
1348 }
1349 let slot = unsafe { registry().slot(0) };
1352 if slot.vtable.is_null() {
1353 return None;
1354 }
1355 Some(unsafe { &*slot.vtable })
1356}
1357
1358pub enum BackendResolution {
1360 Single(&'static NrosRmwVtable),
1362 NoBackend,
1365 Ambiguous,
1368 Unknown,
1371}
1372
1373pub fn resolve_backend(selector: Option<&[u8]>) -> BackendResolution {
1391 let n = registry().len.load(Ordering::Acquire);
1392 if let Some(name) = selector {
1393 let mut i = 0usize;
1394 while i < n {
1395 let slot = unsafe { registry().slot(i) };
1397 if slot.name_matches(name) {
1398 if slot.vtable.is_null() {
1399 return BackendResolution::Unknown;
1400 }
1401 return BackendResolution::Single(unsafe { &*slot.vtable });
1402 }
1403 i += 1;
1404 }
1405 return BackendResolution::Unknown;
1406 }
1407 match n {
1408 0 => BackendResolution::NoBackend,
1409 1 => default_vtable()
1410 .map(BackendResolution::Single)
1411 .unwrap_or(BackendResolution::NoBackend),
1412 _ => BackendResolution::Ambiguous,
1413 }
1414}
1415
1416pub fn backend_resolution_to_ret(res: &BackendResolution) -> NrosRmwRet {
1421 match res {
1422 BackendResolution::Single(_) => NROS_RMW_RET_OK,
1423 BackendResolution::NoBackend => NROS_RMW_RET_NO_BACKEND,
1424 BackendResolution::Ambiguous => NROS_RMW_RET_AMBIGUOUS_BACKEND,
1425 BackendResolution::Unknown => NROS_RMW_RET_UNKNOWN_BACKEND,
1426 }
1427}
1428
1429#[unsafe(no_mangle)]
1447pub unsafe extern "C" fn nros_rmw_cffi_set_custom_transport(
1448 ops: *const nros_rmw::NrosTransportOps,
1449) -> NrosRmwRet {
1450 if ops.is_null() {
1451 let _ = unsafe { nros_rmw::set_custom_transport(None) };
1453 return NROS_RMW_RET_OK;
1454 }
1455 let copy = unsafe { *ops };
1456 match unsafe { nros_rmw::set_custom_transport(Some(copy)) } {
1457 Ok(()) => NROS_RMW_RET_OK,
1458 Err(e) => ret_from_error(&e),
1459 }
1460}
1461
1462fn get_vtable() -> Result<&'static NrosRmwVtable, TransportError> {
1463 default_vtable().ok_or(TransportError::InvalidArgument)
1468}
1469
1470fn to_c_str<const N: usize>(s: &str, buf: &mut [u8; N]) -> *const u8 {
1477 let len = s.len().min(N - 1);
1478 buf[..len].copy_from_slice(&s.as_bytes()[..len]);
1479 buf[len] = 0;
1480 buf.as_ptr()
1481}
1482
1483fn cstr_buf_to_str<const N: usize>(buf: &[u8; N]) -> &str {
1489 let len = buf.iter().position(|&b| b == 0).unwrap_or(N);
1490 core::str::from_utf8(&buf[..len]).unwrap_or("")
1494}
1495
1496const NAME_BUF_LEN: usize = 256;
1518const HASH_BUF_LEN: usize = 128;
1519
1520pub struct CffiSession {
1522 vtable: &'static NrosRmwVtable,
1523 node_name_buf: [u8; NAME_BUF_LEN],
1525 namespace_buf: [u8; NAME_BUF_LEN],
1529 backend_data: *mut c_void,
1531}
1532
1533impl CffiSession {
1534 fn make_view(&mut self) -> NrosRmwSession {
1535 NrosRmwSession {
1536 node_name: self.node_name_buf.as_ptr(),
1537 namespace_: self.namespace_buf.as_ptr(),
1538 _reserved: [0u8; 8],
1539 backend_data: self.backend_data,
1540 }
1541 }
1542
1543 fn entity_view(
1561 &self,
1562 node_name: Option<&str>,
1563 namespace: &str,
1564 nn_buf: &mut [u8; NAME_BUF_LEN],
1565 ns_buf: &mut [u8; NAME_BUF_LEN],
1566 ) -> NrosRmwSession {
1567 let node_name_ptr = match node_name {
1568 Some(n) if !n.is_empty() => to_c_str(n, nn_buf),
1569 _ => self.node_name_buf.as_ptr(),
1570 };
1571 let namespace_ptr = if namespace.is_empty() {
1572 self.namespace_buf.as_ptr()
1573 } else {
1574 to_c_str(namespace, ns_buf)
1575 };
1576 NrosRmwSession {
1577 node_name: node_name_ptr,
1578 namespace_: namespace_ptr,
1579 _reserved: [0u8; 8],
1580 backend_data: self.backend_data,
1581 }
1582 }
1583
1584 pub fn node_name(&self) -> &str {
1586 cstr_buf_to_str(&self.node_name_buf)
1587 }
1588
1589 pub fn open(
1596 locator: &str,
1597 mode: u8,
1598 domain_id: u32,
1599 node_name: &str,
1600 ) -> Result<Self, TransportError> {
1601 let vtable = get_vtable()?;
1602 Self::open_with_vtable(vtable, locator, mode, domain_id, node_name)
1603 }
1604
1605 pub fn open_named(
1610 rmw_name: &str,
1611 locator: &str,
1612 mode: u8,
1613 domain_id: u32,
1614 node_name: &str,
1615 ) -> Result<Self, TransportError> {
1616 let mut name_buf = [0u8; BACKEND_NAME_MAX];
1619 if rmw_name.len() >= BACKEND_NAME_MAX {
1620 return Err(TransportError::InvalidArgument);
1621 }
1622 name_buf[..rmw_name.len()].copy_from_slice(rmw_name.as_bytes());
1623 let raw = unsafe { nros_rmw_cffi_lookup(name_buf.as_ptr() as *const _) };
1625 if raw.is_null() {
1626 return Err(TransportError::InvalidArgument);
1627 }
1628 let vtable = unsafe { &*raw };
1630 Self::open_with_vtable(vtable, locator, mode, domain_id, node_name)
1631 }
1632
1633 fn open_with_vtable(
1634 vtable: &'static NrosRmwVtable,
1635 locator: &str,
1636 mode: u8,
1637 domain_id: u32,
1638 node_name: &str,
1639 ) -> Result<Self, TransportError> {
1640 let mut loc_buf = [0u8; NAME_BUF_LEN];
1641 let loc_ptr = to_c_str(locator, &mut loc_buf);
1642
1643 let mut session = Self {
1644 vtable,
1645 node_name_buf: [0u8; NAME_BUF_LEN],
1646 namespace_buf: [0u8; NAME_BUF_LEN],
1647 backend_data: core::ptr::null_mut(),
1648 };
1649 let _ = to_c_str(node_name, &mut session.node_name_buf);
1650
1651 let mut view = NrosRmwSession {
1652 node_name: session.node_name_buf.as_ptr(),
1653 namespace_: session.namespace_buf.as_ptr(),
1654 _reserved: [0u8; 8],
1655 backend_data: core::ptr::null_mut(),
1656 };
1657 let ret = unsafe {
1658 (vtable.open)(
1659 loc_ptr,
1660 mode,
1661 domain_id,
1662 session.node_name_buf.as_ptr(),
1663 &mut view,
1664 )
1665 };
1666 #[cfg(feature = "std")]
1672 if std::env::var_os("NROS_RMW_TRACE_OPEN").is_some() {
1673 std::eprintln!(
1674 "[nros-rmw-cffi] open: locator={locator:?} mode={mode} ret={ret} backend_data={:p}",
1675 view.backend_data,
1676 );
1677 }
1678 if ret != NROS_RMW_RET_OK {
1679 return Err(error_from_ret(ret));
1680 }
1681 if view.backend_data.is_null() {
1682 return Err(TransportError::ConnectionFailed);
1683 }
1684 session.backend_data = view.backend_data;
1685 Ok(session)
1686 }
1687}
1688
1689impl Session for CffiSession {
1690 type Error = TransportError;
1691 type PublisherHandle = CffiPublisher;
1692 type SubscriberHandle = CffiSubscriber;
1693 type ServiceServerHandle = CffiServiceServer;
1694 type ServiceClientHandle = CffiServiceClient;
1695
1696 fn create_publisher(
1697 &mut self,
1698 topic: &TopicInfo,
1699 qos: QosSettings,
1700 ) -> Result<CffiPublisher, TransportError> {
1701 let mut hash_buf = [0u8; HASH_BUF_LEN];
1702 let hash_ptr = to_c_str(topic.type_hash, &mut hash_buf);
1703 let mut qos_struct = NrosRmwQos::from(qos);
1704 qos_struct.tx_express = (topic.tx_express || qos.tx_express) as u8;
1709
1710 let mut pub_state = CffiPublisher {
1711 vtable: self.vtable,
1712 topic_name_buf: [0u8; NAME_BUF_LEN],
1713 type_name_buf: [0u8; NAME_BUF_LEN],
1714 qos: qos_struct,
1715 can_loan_messages: false,
1716 backend_data: core::ptr::null_mut(),
1717 };
1718 let topic_ptr = to_c_str(topic.name, &mut pub_state.topic_name_buf);
1719 let type_ptr = to_c_str(topic.type_name, &mut pub_state.type_name_buf);
1720
1721 let mut view = NrosRmwPublisher {
1722 topic_name: topic_ptr,
1723 type_name: type_ptr,
1724 qos: qos_struct,
1725 can_loan_messages: false,
1726 _reserved: [0u8; 7],
1727 backend_data: core::ptr::null_mut(),
1728 };
1729 let mut nn_buf = [0u8; NAME_BUF_LEN];
1731 let mut ns_buf = [0u8; NAME_BUF_LEN];
1732 let mut session_view =
1733 self.entity_view(topic.node_name, topic.namespace, &mut nn_buf, &mut ns_buf);
1734 let ret = unsafe {
1735 (self.vtable.create_publisher)(
1736 &mut session_view,
1737 topic_ptr,
1738 type_ptr,
1739 hash_ptr,
1740 topic.domain_id,
1741 &qos_struct,
1742 &mut view,
1743 )
1744 };
1745 if ret != NROS_RMW_RET_OK {
1746 return Err(error_from_ret(ret));
1747 }
1748 if view.backend_data.is_null() {
1749 return Err(TransportError::PublisherCreationFailed);
1750 }
1751 pub_state.backend_data = view.backend_data;
1752 pub_state.can_loan_messages = view.can_loan_messages;
1753 Ok(pub_state)
1754 }
1755
1756 fn create_subscriber(
1757 &mut self,
1758 topic: &TopicInfo,
1759 qos: QosSettings,
1760 ) -> Result<CffiSubscriber, TransportError> {
1761 let mut hash_buf = [0u8; HASH_BUF_LEN];
1762 let hash_ptr = to_c_str(topic.type_hash, &mut hash_buf);
1763 let mut qos_struct = NrosRmwQos::from(qos);
1764 qos_struct.rx_buffer_hint = topic.rx_buffer_hint.min(u32::MAX as usize) as u32;
1767
1768 let mut sub_state = CffiSubscriber {
1769 vtable: self.vtable,
1770 topic_name_buf: [0u8; NAME_BUF_LEN],
1771 type_name_buf: [0u8; NAME_BUF_LEN],
1772 qos: qos_struct,
1773 can_loan_messages: false,
1774 backend_data: core::ptr::null_mut(),
1775 supports_in_place: false,
1776 };
1777 let topic_ptr = to_c_str(topic.name, &mut sub_state.topic_name_buf);
1778 let type_ptr = to_c_str(topic.type_name, &mut sub_state.type_name_buf);
1779
1780 let mut view = NrosRmwSubscriber {
1781 topic_name: topic_ptr,
1782 type_name: type_ptr,
1783 qos: qos_struct,
1784 can_loan_messages: false,
1785 _reserved: [0u8; 7],
1786 backend_data: core::ptr::null_mut(),
1787 };
1788 let mut nn_buf = [0u8; NAME_BUF_LEN];
1790 let mut ns_buf = [0u8; NAME_BUF_LEN];
1791 let mut session_view =
1792 self.entity_view(topic.node_name, topic.namespace, &mut nn_buf, &mut ns_buf);
1793 let ret = unsafe {
1794 (self.vtable.create_subscriber)(
1795 &mut session_view,
1796 topic_ptr,
1797 type_ptr,
1798 hash_ptr,
1799 topic.domain_id,
1800 &qos_struct,
1801 &mut view,
1802 )
1803 };
1804 if ret != NROS_RMW_RET_OK {
1805 return Err(error_from_ret(ret));
1806 }
1807 if view.backend_data.is_null() {
1808 return Err(TransportError::SubscriberCreationFailed);
1809 }
1810 sub_state.backend_data = view.backend_data;
1811 sub_state.can_loan_messages = view.can_loan_messages;
1812 sub_state.supports_in_place = match sub_state.vtable.subscriber_supports_in_place {
1814 Some(f) => {
1815 let mut v = sub_state.make_view();
1816 unsafe { f(&mut v) == 1 }
1817 }
1818 None => false,
1819 };
1820 Ok(sub_state)
1821 }
1822
1823 fn create_service_server(
1824 &mut self,
1825 service: &ServiceInfo,
1826 qos: QosSettings,
1827 ) -> Result<CffiServiceServer, TransportError> {
1828 let qos_struct = NrosRmwQos::from(qos);
1829 let mut hash_buf = [0u8; HASH_BUF_LEN];
1830 let hash_ptr = to_c_str(service.type_hash, &mut hash_buf);
1831
1832 let mut srv_state = CffiServiceServer {
1833 vtable: self.vtable,
1834 service_name_buf: [0u8; NAME_BUF_LEN],
1835 type_name_buf: [0u8; NAME_BUF_LEN],
1836 backend_data: core::ptr::null_mut(),
1837 };
1838 let svc_ptr = to_c_str(service.name, &mut srv_state.service_name_buf);
1839 let type_ptr = to_c_str(service.type_name, &mut srv_state.type_name_buf);
1840
1841 let mut view = NrosRmwServiceServer {
1842 service_name: svc_ptr,
1843 type_name: type_ptr,
1844 _reserved: [0u8; 8],
1845 backend_data: core::ptr::null_mut(),
1846 };
1847 let mut nn_buf = [0u8; NAME_BUF_LEN];
1849 let mut ns_buf = [0u8; NAME_BUF_LEN];
1850 let mut session_view = self.entity_view(
1851 service.node_name,
1852 service.namespace,
1853 &mut nn_buf,
1854 &mut ns_buf,
1855 );
1856 let ret = unsafe {
1857 (self.vtable.create_service_server)(
1858 &mut session_view,
1859 svc_ptr,
1860 type_ptr,
1861 hash_ptr,
1862 service.domain_id,
1863 &qos_struct,
1864 &mut view,
1865 )
1866 };
1867 if ret != NROS_RMW_RET_OK {
1868 return Err(error_from_ret(ret));
1869 }
1870 if view.backend_data.is_null() {
1871 return Err(TransportError::ServiceServerCreationFailed);
1872 }
1873 srv_state.backend_data = view.backend_data;
1874 Ok(srv_state)
1875 }
1876
1877 fn create_service_client(
1878 &mut self,
1879 service: &ServiceInfo,
1880 qos: QosSettings,
1881 ) -> Result<CffiServiceClient, TransportError> {
1882 let qos_struct = NrosRmwQos::from(qos);
1883 let mut hash_buf = [0u8; HASH_BUF_LEN];
1884 let hash_ptr = to_c_str(service.type_hash, &mut hash_buf);
1885
1886 let mut cli_state = CffiServiceClient {
1887 vtable: self.vtable,
1888 service_name_buf: [0u8; NAME_BUF_LEN],
1889 type_name_buf: [0u8; NAME_BUF_LEN],
1890 backend_data: core::ptr::null_mut(),
1891 pending_len: 0,
1892 };
1893 let svc_ptr = to_c_str(service.name, &mut cli_state.service_name_buf);
1894 let type_ptr = to_c_str(service.type_name, &mut cli_state.type_name_buf);
1895
1896 let mut view = NrosRmwServiceClient {
1897 service_name: svc_ptr,
1898 type_name: type_ptr,
1899 _reserved: [0u8; 8],
1900 backend_data: core::ptr::null_mut(),
1901 };
1902 let mut nn_buf = [0u8; NAME_BUF_LEN];
1904 let mut ns_buf = [0u8; NAME_BUF_LEN];
1905 let mut session_view = self.entity_view(
1906 service.node_name,
1907 service.namespace,
1908 &mut nn_buf,
1909 &mut ns_buf,
1910 );
1911 let ret = unsafe {
1912 (self.vtable.create_service_client)(
1913 &mut session_view,
1914 svc_ptr,
1915 type_ptr,
1916 hash_ptr,
1917 service.domain_id,
1918 &qos_struct,
1919 &mut view,
1920 )
1921 };
1922 if ret != NROS_RMW_RET_OK {
1923 return Err(error_from_ret(ret));
1924 }
1925 if view.backend_data.is_null() {
1926 return Err(TransportError::ServiceClientCreationFailed);
1927 }
1928 cli_state.backend_data = view.backend_data;
1929 Ok(cli_state)
1930 }
1931
1932 fn close(&mut self) -> Result<(), TransportError> {
1933 if self.backend_data.is_null() {
1934 return Ok(());
1935 }
1936 let mut view = self.make_view();
1937 let ret = unsafe { (self.vtable.close)(&mut view) };
1938 if ret != NROS_RMW_RET_OK {
1939 return Err(error_from_ret(ret));
1940 }
1941 self.backend_data = core::ptr::null_mut();
1942 Ok(())
1943 }
1944
1945 fn drive_io(&mut self, timeout_ms: i32) -> Result<(), TransportError> {
1946 let mut view = self.make_view();
1947 let ret = unsafe { (self.vtable.drive_io)(&mut view, timeout_ms) };
1948 if ret != NROS_RMW_RET_OK {
1949 return Err(error_from_ret(ret));
1950 }
1951 Ok(())
1952 }
1953
1954 fn next_deadline_ms(&self) -> Option<u32> {
1955 let f = self.vtable.next_deadline_ms?;
1956 let view = NrosRmwSession {
1960 node_name: self.node_name_buf.as_ptr(),
1961 namespace_: self.namespace_buf.as_ptr(),
1962 _reserved: [0u8; 8],
1963 backend_data: self.backend_data,
1964 };
1965 let ret = unsafe { f(&view as *const _) };
1966 if ret < 0 { None } else { Some(ret as u32) }
1967 }
1968
1969 unsafe fn set_wake_callback(
1970 &mut self,
1971 cb: Option<unsafe extern "C" fn(ctx: *mut core::ffi::c_void)>,
1972 ctx: *mut core::ffi::c_void,
1973 ) {
1974 let Some(f) = self.vtable.set_wake_callback else {
1975 return;
1976 };
1977 let mut view = NrosRmwSession {
1978 node_name: self.node_name_buf.as_ptr(),
1979 namespace_: self.namespace_buf.as_ptr(),
1980 _reserved: [0u8; 8],
1981 backend_data: self.backend_data,
1982 };
1983 let _ = unsafe { f(&mut view as *mut _, cb, ctx) };
1986 }
1987
1988 fn supports_wake_callback(&self) -> bool {
1989 self.vtable.set_wake_callback.is_some()
1994 }
1995
1996 fn ping_session(&mut self, timeout_ms: i32) -> Result<(), TransportError> {
1997 let Some(f) = self.vtable.ping_session else {
2002 return Err(TransportError::Unsupported);
2003 };
2004 let mut view = self.make_view();
2005 let rc = unsafe { f(&mut view, timeout_ms) };
2006 if rc == NROS_RMW_RET_OK {
2007 Ok(())
2008 } else {
2009 Err(error_from_ret(rc))
2010 }
2011 }
2012
2013 fn supported_qos_policies(&self) -> nros_rmw::QosPolicyMask {
2028 use nros_rmw::QosPolicyMask;
2029 QosPolicyMask::CORE
2030 | QosPolicyMask::DURABILITY_TRANSIENT_LOCAL
2031 | QosPolicyMask::AVOID_ROS_NAMESPACE_CONVENTIONS
2032 | QosPolicyMask::DEADLINE
2033 | QosPolicyMask::LIFESPAN
2034 | QosPolicyMask::LIVELINESS_AUTOMATIC
2035 | QosPolicyMask::LIVELINESS_MANUAL_BY_TOPIC
2036 | QosPolicyMask::LIVELINESS_MANUAL_BY_NODE
2037 | QosPolicyMask::LIVELINESS_LEASE
2038 }
2039}
2040
2041impl Drop for CffiSession {
2042 fn drop(&mut self) {
2043 if !self.backend_data.is_null() {
2044 let mut view = self.make_view();
2045 unsafe { (self.vtable.close)(&mut view) };
2046 }
2047 }
2048}
2049
2050pub struct CffiPublisher {
2056 vtable: &'static NrosRmwVtable,
2057 topic_name_buf: [u8; NAME_BUF_LEN],
2058 type_name_buf: [u8; NAME_BUF_LEN],
2059 qos: NrosRmwQos,
2060 can_loan_messages: bool,
2061 backend_data: *mut c_void,
2062}
2063
2064impl CffiPublisher {
2065 fn make_view(&mut self) -> NrosRmwPublisher {
2066 NrosRmwPublisher {
2067 topic_name: self.topic_name_buf.as_ptr(),
2068 type_name: self.type_name_buf.as_ptr(),
2069 qos: self.qos,
2070 can_loan_messages: self.can_loan_messages,
2071 _reserved: [0u8; 7],
2072 backend_data: self.backend_data,
2073 }
2074 }
2075
2076 pub fn topic_name(&self) -> &str {
2079 cstr_buf_to_str(&self.topic_name_buf)
2080 }
2081
2082 pub fn type_name(&self) -> &str {
2084 cstr_buf_to_str(&self.type_name_buf)
2085 }
2086
2087 pub fn qos(&self) -> NrosRmwQos {
2089 self.qos
2090 }
2091
2092 pub fn can_loan_messages(&self) -> bool {
2095 self.can_loan_messages
2096 }
2097}
2098
2099#[cfg(feature = "lending")]
2104pub struct CffiSlot<'a> {
2105 buf: *mut u8,
2106 cap: usize,
2107 cursor: usize,
2108 token: *mut c_void,
2109 publisher: Option<&'a CffiPublisher>,
2112 fallback: bool,
2118}
2119
2120#[cfg(feature = "lending")]
2121impl<'a> CffiSlot<'a> {
2122 pub fn set_len(&mut self, len: usize) {
2126 debug_assert!(len <= self.cap);
2127 self.cursor = len.min(self.cap);
2128 }
2129}
2130
2131#[cfg(all(feature = "lending", feature = "alloc"))]
2138struct ArenaStaging {
2139 buf: alloc::vec::Vec<u8>,
2140}
2141
2142#[cfg(feature = "lending")]
2143impl<'a> AsMut<[u8]> for CffiSlot<'a> {
2144 fn as_mut(&mut self) -> &mut [u8] {
2145 unsafe { core::slice::from_raw_parts_mut(self.buf, self.cap) }
2150 }
2151}
2152
2153#[cfg(feature = "lending")]
2154impl<'a> Drop for CffiSlot<'a> {
2155 fn drop(&mut self) {
2156 if self.publisher.is_none() {
2157 return;
2159 }
2160 if self.fallback {
2161 #[cfg(feature = "alloc")]
2163 unsafe {
2164 let _ = alloc::boxed::Box::from_raw(self.token as *mut ArenaStaging);
2165 }
2166 return;
2167 }
2168 if let Some(p) = self.publisher
2169 && let Some(discard) = p.vtable.pub_discard
2170 {
2171 let mut view = NrosRmwPublisher {
2175 topic_name: p.topic_name_buf.as_ptr(),
2176 type_name: p.type_name_buf.as_ptr(),
2177 qos: p.qos,
2178 can_loan_messages: p.can_loan_messages,
2179 _reserved: [0u8; 7],
2180 backend_data: p.backend_data,
2181 };
2182 unsafe { discard(&mut view, self.token) };
2186 }
2187 }
2188}
2189
2190#[cfg(feature = "lending")]
2191impl nros_rmw::SlotLending for CffiPublisher {
2192 type Slot<'a> = CffiSlot<'a>;
2193
2194 fn try_lend_slot(&self, len: usize) -> Result<Option<CffiSlot<'_>>, TransportError> {
2195 let Some(loan) = self.vtable.pub_loan else {
2196 #[cfg(feature = "alloc")]
2203 {
2204 let mut staging = alloc::boxed::Box::new(ArenaStaging {
2205 buf: alloc::vec![0u8; len],
2206 });
2207 let buf_ptr = staging.buf.as_mut_ptr();
2208 let token = alloc::boxed::Box::into_raw(staging) as *mut c_void;
2209 return Ok(Some(CffiSlot {
2210 buf: buf_ptr,
2211 cap: len,
2212 cursor: len,
2213 token,
2214 publisher: Some(self),
2215 fallback: true,
2216 }));
2217 }
2218 #[cfg(not(feature = "alloc"))]
2219 {
2220 let _ = len;
2221 return Ok(None);
2222 }
2223 };
2224 let mut view = NrosRmwPublisher {
2225 topic_name: self.topic_name_buf.as_ptr(),
2226 type_name: self.type_name_buf.as_ptr(),
2227 qos: self.qos,
2228 can_loan_messages: self.can_loan_messages,
2229 _reserved: [0u8; 7],
2230 backend_data: self.backend_data,
2231 };
2232 let mut out_buf: *mut u8 = core::ptr::null_mut();
2233 let mut out_cap: usize = 0;
2234 let mut out_token: *mut c_void = core::ptr::null_mut();
2235 let ret = unsafe { loan(&mut view, len, &mut out_buf, &mut out_cap, &mut out_token) };
2238 if ret == NROS_RMW_RET_WOULD_BLOCK || ret == NROS_RMW_RET_NO_DATA {
2239 return Ok(None);
2240 }
2241 if ret != NROS_RMW_RET_OK {
2242 return Err(error_from_ret(ret));
2243 }
2244 if out_buf.is_null() || out_cap < len {
2245 if let Some(discard) = self.vtable.pub_discard {
2248 unsafe { discard(&mut view, out_token) };
2249 }
2250 return Ok(None);
2251 }
2252 Ok(Some(CffiSlot {
2253 buf: out_buf,
2254 cap: out_cap,
2255 cursor: len,
2256 token: out_token,
2257 publisher: Some(self),
2258 fallback: false,
2259 }))
2260 }
2261
2262 fn commit_slot(&self, mut slot: CffiSlot<'_>) -> Result<(), TransportError> {
2263 let publisher = slot
2265 .publisher
2266 .take()
2267 .ok_or(TransportError::InvalidArgument)?;
2268 debug_assert!(core::ptr::eq(publisher, self));
2269 if slot.fallback {
2270 #[cfg(feature = "alloc")]
2274 {
2275 let staging =
2278 unsafe { alloc::boxed::Box::from_raw(slot.token as *mut ArenaStaging) };
2279 let bytes = &staging.buf[..slot.cursor.min(staging.buf.len())];
2280 return Publisher::publish_raw(self, bytes);
2281 }
2282 #[cfg(not(feature = "alloc"))]
2283 {
2284 return Err(TransportError::Unsupported);
2285 }
2286 }
2287 let commit = self.vtable.pub_commit.ok_or(TransportError::Unsupported)?;
2288 let mut view = NrosRmwPublisher {
2289 topic_name: self.topic_name_buf.as_ptr(),
2290 type_name: self.type_name_buf.as_ptr(),
2291 qos: self.qos,
2292 can_loan_messages: self.can_loan_messages,
2293 _reserved: [0u8; 7],
2294 backend_data: self.backend_data,
2295 };
2296 let len = slot.cursor;
2297 let token = slot.token;
2298 let ret = unsafe { commit(&mut view, token, len) };
2301 if ret != NROS_RMW_RET_OK {
2302 return Err(error_from_ret(ret));
2303 }
2304 Ok(())
2305 }
2306}
2307
2308impl Publisher for CffiPublisher {
2309 type Error = TransportError;
2310
2311 fn publish_raw(&self, data: &[u8]) -> Result<(), TransportError> {
2312 let mut view = NrosRmwPublisher {
2313 topic_name: self.topic_name_buf.as_ptr(),
2314 type_name: self.type_name_buf.as_ptr(),
2315 qos: self.qos,
2316 can_loan_messages: self.can_loan_messages,
2317 _reserved: [0u8; 7],
2318 backend_data: self.backend_data,
2319 };
2320 let ret = unsafe { (self.vtable.publish_raw)(&mut view, data.as_ptr(), data.len()) };
2321 if ret != NROS_RMW_RET_OK {
2322 return Err(error_from_ret(ret));
2323 }
2324 Ok(())
2325 }
2326
2327 unsafe fn publish_streamed(
2328 &self,
2329 size_cb: unsafe extern "C" fn(out_total_len: *mut usize, user_ctx: *mut core::ffi::c_void),
2330 chunk_cb: unsafe extern "C" fn(
2331 out_buf: *mut u8,
2332 cap: usize,
2333 out_written: *mut usize,
2334 user_ctx: *mut core::ffi::c_void,
2335 ),
2336 user_ctx: *mut core::ffi::c_void,
2337 ) -> Result<(), TransportError> {
2338 if let Some(f) = self.vtable.publish_streamed {
2345 let mut view = NrosRmwPublisher {
2346 topic_name: self.topic_name_buf.as_ptr(),
2347 type_name: self.type_name_buf.as_ptr(),
2348 qos: self.qos,
2349 can_loan_messages: self.can_loan_messages,
2350 _reserved: [0u8; 7],
2351 backend_data: self.backend_data,
2352 };
2353 let ret = unsafe { f(&mut view, size_cb, chunk_cb, user_ctx) };
2354 if ret != NROS_RMW_RET_OK {
2355 return Err(error_from_ret(ret));
2356 }
2357 return Ok(());
2358 }
2359 const STAGE_CAP: usize = 4096;
2364 let mut total = 0usize;
2365 unsafe { size_cb(&mut total as *mut usize, user_ctx) };
2366 if total > STAGE_CAP {
2367 return Err(TransportError::BufferTooSmall);
2368 }
2369 let mut stage = [0u8; STAGE_CAP];
2370 let mut written_so_far = 0usize;
2371 while written_so_far < total {
2372 let mut chunk_written = 0usize;
2373 let remaining = total - written_so_far;
2374 unsafe {
2375 chunk_cb(
2376 stage.as_mut_ptr().add(written_so_far),
2377 remaining,
2378 &mut chunk_written as *mut usize,
2379 user_ctx,
2380 );
2381 }
2382 if chunk_written == 0 {
2383 return Err(TransportError::BufferTooSmall);
2384 }
2385 written_so_far += chunk_written;
2386 }
2387 self.publish_raw(&stage[..total])
2388 }
2389
2390 fn buffer_error(&self) -> TransportError {
2391 TransportError::BufferTooSmall
2392 }
2393
2394 fn serialization_error(&self) -> TransportError {
2395 TransportError::SerializationError
2396 }
2397
2398 fn unsupported_event_error(&self) -> TransportError {
2399 TransportError::Unsupported
2400 }
2401
2402 unsafe fn register_event_callback(
2403 &mut self,
2404 kind: nros_rmw::EventKind,
2405 deadline_ms: u32,
2406 cb: nros_rmw::EventCallback,
2407 user_ctx: *mut core::ffi::c_void,
2408 ) -> Result<(), TransportError> {
2409 let mut view = NrosRmwPublisher {
2410 topic_name: self.topic_name_buf.as_ptr(),
2411 type_name: self.type_name_buf.as_ptr(),
2412 qos: self.qos,
2413 can_loan_messages: self.can_loan_messages,
2414 _reserved: [0u8; 7],
2415 backend_data: self.backend_data,
2416 };
2417 let cb: NrosRmwEventCallback =
2421 unsafe { core::mem::transmute::<nros_rmw::EventCallback, NrosRmwEventCallback>(cb) };
2422 let ret = unsafe {
2423 (self.vtable.register_publisher_event)(
2424 &mut view,
2425 kind.into(),
2426 deadline_ms,
2427 cb,
2428 user_ctx,
2429 )
2430 };
2431 if ret != NROS_RMW_RET_OK {
2432 return Err(error_from_ret(ret));
2433 }
2434 Ok(())
2435 }
2436
2437 fn assert_liveliness(&self) -> Result<(), TransportError> {
2438 let view_ptr = self as *const _ as *mut Self;
2443 let mut view = unsafe { (*view_ptr).make_view() };
2444 let ret = unsafe { (self.vtable.assert_publisher_liveliness)(&mut view) };
2445 if ret != NROS_RMW_RET_OK {
2446 return Err(error_from_ret(ret));
2447 }
2448 Ok(())
2449 }
2450}
2451
2452impl Drop for CffiPublisher {
2453 fn drop(&mut self) {
2454 if !self.backend_data.is_null() {
2455 let mut view = self.make_view();
2456 unsafe { (self.vtable.destroy_publisher)(&mut view) };
2457 }
2458 }
2459}
2460
2461pub struct CffiSubscriber {
2467 vtable: &'static NrosRmwVtable,
2468 topic_name_buf: [u8; NAME_BUF_LEN],
2469 type_name_buf: [u8; NAME_BUF_LEN],
2470 qos: NrosRmwQos,
2471 can_loan_messages: bool,
2472 backend_data: *mut c_void,
2473 supports_in_place: bool,
2476}
2477
2478impl CffiSubscriber {
2479 fn make_view(&mut self) -> NrosRmwSubscriber {
2480 NrosRmwSubscriber {
2481 topic_name: self.topic_name_buf.as_ptr(),
2482 type_name: self.type_name_buf.as_ptr(),
2483 qos: self.qos,
2484 can_loan_messages: self.can_loan_messages,
2485 _reserved: [0u8; 7],
2486 backend_data: self.backend_data,
2487 }
2488 }
2489
2490 fn run_process_in_place<G: FnOnce(&[u8])>(&mut self, f: G) -> Result<bool, TransportError> {
2496 let Some(slot) = self.vtable.process_raw_in_place else {
2497 return Err(TransportError::MessageTooLarge);
2498 };
2499 unsafe extern "C" fn cb_tramp<G: FnOnce(&[u8])>(
2500 ctx: *mut c_void,
2501 ptr: *const u8,
2502 len: usize,
2503 ) {
2504 let cell = unsafe { &mut *(ctx as *mut Option<G>) };
2505 if let Some(g) = cell.take() {
2506 g(unsafe { core::slice::from_raw_parts(ptr, len) });
2507 }
2508 }
2509 let mut cell: Option<G> = Some(f);
2510 let mut view = self.make_view();
2511 let rc = unsafe {
2512 slot(
2513 &mut view,
2514 &mut cell as *mut Option<G> as *mut c_void,
2515 cb_tramp::<G>,
2516 )
2517 };
2518 if rc == NROS_RMW_RET_NO_DATA {
2519 Ok(false)
2520 } else if rc < 0 {
2521 Err(error_from_ret(rc))
2522 } else {
2523 Ok(rc > 0)
2524 }
2525 }
2526
2527 pub fn topic_name(&self) -> &str {
2528 cstr_buf_to_str(&self.topic_name_buf)
2529 }
2530
2531 pub fn type_name(&self) -> &str {
2532 cstr_buf_to_str(&self.type_name_buf)
2533 }
2534
2535 pub fn qos(&self) -> NrosRmwQos {
2536 self.qos
2537 }
2538
2539 pub fn can_loan_messages(&self) -> bool {
2542 self.can_loan_messages
2543 }
2544}
2545
2546#[cfg(feature = "lending")]
2550pub struct CffiView<'a> {
2551 buf: *const u8,
2552 len: usize,
2553 token: *mut c_void,
2554 subscriber: Option<&'a mut CffiSubscriber>,
2555}
2556
2557#[cfg(feature = "lending")]
2558impl<'a> AsRef<[u8]> for CffiView<'a> {
2559 fn as_ref(&self) -> &[u8] {
2560 unsafe { core::slice::from_raw_parts(self.buf, self.len) }
2565 }
2566}
2567
2568#[cfg(feature = "lending")]
2569impl<'a> Drop for CffiView<'a> {
2570 fn drop(&mut self) {
2571 if let Some(sub) = self.subscriber.take()
2572 && let Some(release) = sub.vtable.sub_release
2573 {
2574 let mut view = sub.make_view();
2575 unsafe { release(&mut view, self.token) };
2578 }
2579 }
2580}
2581
2582#[cfg(feature = "lending")]
2583impl nros_rmw::SlotBorrowing for CffiSubscriber {
2584 type View<'a> = CffiView<'a>;
2585
2586 fn try_borrow(&mut self) -> Result<Option<CffiView<'_>>, TransportError> {
2587 let Some(borrow) = self.vtable.sub_borrow else {
2588 return Ok(None);
2592 };
2593 let mut view = self.make_view();
2594 let mut out_buf: *const u8 = core::ptr::null();
2595 let mut out_len: usize = 0;
2596 let mut out_token: *mut c_void = core::ptr::null_mut();
2597 let rc = unsafe { borrow(&mut view, &mut out_buf, &mut out_len, &mut out_token) };
2600 if rc == 0 {
2601 return Ok(None);
2603 }
2604 if rc < 0 {
2605 return Err(error_from_ret(rc));
2606 }
2607 if out_buf.is_null() {
2608 return Ok(None);
2609 }
2610 let len = (rc as usize).min(out_len.max(rc as usize));
2611 Ok(Some(CffiView {
2612 buf: out_buf,
2613 len,
2614 token: out_token,
2615 subscriber: Some(self),
2616 }))
2617 }
2618}
2619
2620impl nros_rmw::Subscriber for CffiSubscriber {
2621 type Error = TransportError;
2622
2623 fn supports_process_in_place(&self) -> bool {
2624 self.supports_in_place
2625 }
2626
2627 fn process_raw_in_place(&mut self, f: impl FnOnce(&[u8])) -> Result<bool, Self::Error> {
2628 self.run_process_in_place(f)
2629 }
2630
2631 fn has_data(&self) -> bool {
2632 let view_ptr = self as *const _ as *mut Self;
2636 let mut view = unsafe { (*view_ptr).make_view() };
2637 let rc = unsafe { (self.vtable.has_data)(&mut view) };
2638 rc > 0
2639 }
2640
2641 fn try_recv_raw(&mut self, buf: &mut [u8]) -> Result<Option<usize>, TransportError> {
2642 let mut view = self.make_view();
2643 let rc = unsafe { (self.vtable.try_recv_raw)(&mut view, buf.as_mut_ptr(), buf.len()) };
2644 if rc == NROS_RMW_RET_NO_DATA {
2645 return Ok(None);
2646 }
2647 if rc < 0 {
2648 return Err(error_from_ret(rc));
2649 }
2650 if rc == 0 {
2651 return Ok(None);
2652 }
2653 Ok(Some(rc as usize))
2654 }
2655
2656 fn try_recv_raw_with_info(
2657 &mut self,
2658 buf: &mut [u8],
2659 ) -> Result<Option<(usize, Option<MessageInfo>)>, TransportError> {
2660 let key = self.backend_data as usize;
2661 self.try_recv_raw(buf)
2662 .map(|opt| opt.map(|len| (len, take_cffi_message_info(key))))
2663 }
2664
2665 #[cfg(all(feature = "alloc", feature = "safety-e2e"))]
2666 fn try_recv_validated(
2667 &mut self,
2668 buf: &mut [u8],
2669 ) -> Result<Option<(usize, nros_rmw::IntegrityStatus)>, Self::Error> {
2670 let key = self.backend_data as usize;
2671 request_cffi_integrity_status(key);
2672 self.try_recv_raw(buf).map(|opt| {
2673 opt.map(|len| {
2674 (
2675 len,
2676 take_cffi_integrity_status(key).unwrap_or(nros_rmw::IntegrityStatus {
2677 gap: 0,
2678 duplicate: false,
2679 crc_valid: None,
2680 }),
2681 )
2682 })
2683 })
2684 }
2685
2686 fn try_recv_sequence(
2687 &mut self,
2688 buf: &mut [u8],
2689 per_msg_cap: usize,
2690 max_msgs: usize,
2691 out_lens: &mut [usize],
2692 ) -> Result<usize, TransportError> {
2693 if let Some(f) = self.vtable.try_recv_sequence {
2700 if per_msg_cap == 0 || max_msgs == 0 {
2701 return Ok(0);
2702 }
2703 let limit = max_msgs.min(out_lens.len());
2704 if buf.len() < limit.saturating_mul(per_msg_cap) {
2705 return Err(TransportError::BufferTooSmall);
2706 }
2707 let mut view = self.make_view();
2708 let rc = unsafe {
2709 f(
2710 &mut view,
2711 buf.as_mut_ptr(),
2712 per_msg_cap,
2713 limit,
2714 out_lens.as_mut_ptr(),
2715 )
2716 };
2717 if rc < 0 {
2718 return Err(error_from_ret(rc));
2719 }
2720 return Ok(rc as usize);
2721 }
2722 if per_msg_cap == 0 || max_msgs == 0 {
2729 return Ok(0);
2730 }
2731 let limit = max_msgs.min(out_lens.len());
2732 if buf.len() < limit.saturating_mul(per_msg_cap) {
2733 return Err(TransportError::BufferTooSmall);
2734 }
2735 let mut count = 0;
2736 for i in 0..limit {
2737 let slot = &mut buf[i * per_msg_cap..(i + 1) * per_msg_cap];
2738 match self.try_recv_raw(slot)? {
2739 Some(len) => {
2740 out_lens[i] = len;
2741 count += 1;
2742 }
2743 None => break,
2744 }
2745 }
2746 Ok(count)
2747 }
2748
2749 fn deserialization_error(&self) -> TransportError {
2750 TransportError::DeserializationError
2751 }
2752
2753 fn unsupported_event_error(&self) -> TransportError {
2754 TransportError::Unsupported
2755 }
2756
2757 unsafe fn register_event_callback(
2758 &mut self,
2759 kind: nros_rmw::EventKind,
2760 deadline_ms: u32,
2761 cb: nros_rmw::EventCallback,
2762 user_ctx: *mut core::ffi::c_void,
2763 ) -> Result<(), TransportError> {
2764 let mut view = self.make_view();
2765 let cb: NrosRmwEventCallback =
2766 unsafe { core::mem::transmute::<nros_rmw::EventCallback, NrosRmwEventCallback>(cb) };
2767 let ret = unsafe {
2768 (self.vtable.register_subscriber_event)(
2769 &mut view,
2770 kind.into(),
2771 deadline_ms,
2772 cb,
2773 user_ctx,
2774 )
2775 };
2776 if ret != NROS_RMW_RET_OK {
2777 return Err(error_from_ret(ret));
2778 }
2779 Ok(())
2780 }
2781}
2782
2783impl Drop for CffiSubscriber {
2784 fn drop(&mut self) {
2785 if !self.backend_data.is_null() {
2786 clear_cffi_message_info(self.backend_data as usize);
2787 let mut view = self.make_view();
2788 unsafe { (self.vtable.destroy_subscriber)(&mut view) };
2789 }
2790 }
2791}
2792
2793pub struct CffiServiceServer {
2799 vtable: &'static NrosRmwVtable,
2800 service_name_buf: [u8; NAME_BUF_LEN],
2801 type_name_buf: [u8; NAME_BUF_LEN],
2802 backend_data: *mut c_void,
2803}
2804
2805impl CffiServiceServer {
2806 fn make_view(&mut self) -> NrosRmwServiceServer {
2807 NrosRmwServiceServer {
2808 service_name: self.service_name_buf.as_ptr(),
2809 type_name: self.type_name_buf.as_ptr(),
2810 _reserved: [0u8; 8],
2811 backend_data: self.backend_data,
2812 }
2813 }
2814
2815 pub fn service_name(&self) -> &str {
2816 cstr_buf_to_str(&self.service_name_buf)
2817 }
2818
2819 pub fn type_name(&self) -> &str {
2820 cstr_buf_to_str(&self.type_name_buf)
2821 }
2822}
2823
2824impl ServiceServerTrait for CffiServiceServer {
2825 type Error = TransportError;
2826
2827 fn has_request(&self) -> bool {
2828 let view_ptr = self as *const _ as *mut Self;
2829 let mut view = unsafe { (*view_ptr).make_view() };
2830 let rc = unsafe { (self.vtable.has_request)(&mut view) };
2831 rc > 0
2832 }
2833
2834 fn try_recv_request<'a>(
2835 &mut self,
2836 buf: &'a mut [u8],
2837 ) -> Result<Option<ServiceRequest<'a>>, TransportError> {
2838 let mut seq: i64 = 0;
2839 let mut view = self.make_view();
2840 let rc = unsafe {
2841 (self.vtable.try_recv_request)(&mut view, buf.as_mut_ptr(), buf.len(), &mut seq)
2842 };
2843 if rc == NROS_RMW_RET_NO_DATA {
2844 return Ok(None);
2845 }
2846 if rc < 0 {
2847 return Err(error_from_ret(rc));
2848 }
2849 if rc == 0 {
2850 return Ok(None);
2851 }
2852 let len = rc as usize;
2853 Ok(Some(ServiceRequest {
2854 data: &buf[..len],
2855 sequence_number: seq,
2856 }))
2857 }
2858
2859 fn send_reply(&mut self, sequence_number: i64, data: &[u8]) -> Result<(), TransportError> {
2860 let mut view = self.make_view();
2861 let ret = unsafe {
2862 (self.vtable.send_reply)(&mut view, sequence_number, data.as_ptr(), data.len())
2863 };
2864 if ret != NROS_RMW_RET_OK {
2865 return Err(error_from_ret(ret));
2866 }
2867 Ok(())
2868 }
2869}
2870
2871impl Drop for CffiServiceServer {
2872 fn drop(&mut self) {
2873 if !self.backend_data.is_null() {
2874 let mut view = self.make_view();
2875 unsafe { (self.vtable.destroy_service_server)(&mut view) };
2876 }
2877 }
2878}
2879
2880pub struct CffiServiceClient {
2886 vtable: &'static NrosRmwVtable,
2887 service_name_buf: [u8; NAME_BUF_LEN],
2888 type_name_buf: [u8; NAME_BUF_LEN],
2889 backend_data: *mut c_void,
2890 pending_len: usize,
2897}
2898
2899impl CffiServiceClient {
2900 fn make_view(&mut self) -> NrosRmwServiceClient {
2901 NrosRmwServiceClient {
2902 service_name: self.service_name_buf.as_ptr(),
2903 type_name: self.type_name_buf.as_ptr(),
2904 _reserved: [0u8; 8],
2905 backend_data: self.backend_data,
2906 }
2907 }
2908
2909 pub fn service_name(&self) -> &str {
2910 cstr_buf_to_str(&self.service_name_buf)
2911 }
2912
2913 pub fn type_name(&self) -> &str {
2914 cstr_buf_to_str(&self.type_name_buf)
2915 }
2916}
2917
2918impl ServiceClientTrait for CffiServiceClient {
2919 type Error = TransportError;
2920
2921 #[allow(deprecated)]
2922 fn call_raw(&mut self, request: &[u8], reply_buf: &mut [u8]) -> Result<usize, TransportError> {
2923 let mut view = self.make_view();
2924 let rc = unsafe {
2925 (self.vtable.call_raw)(
2926 &mut view,
2927 request.as_ptr(),
2928 request.len(),
2929 reply_buf.as_mut_ptr(),
2930 reply_buf.len(),
2931 )
2932 };
2933 if rc < 0 {
2934 return Err(error_from_ret(rc));
2935 }
2936 Ok(rc as usize)
2937 }
2938
2939 fn send_request_raw(&mut self, request: &[u8]) -> Result<(), TransportError> {
2940 let Some(f) = self.vtable.send_request_raw else {
2951 return Err(TransportError::Unsupported);
2952 };
2953 let mut view = self.make_view();
2954 let rc = unsafe { f(&mut view, request.as_ptr(), request.len()) };
2955 if rc != NROS_RMW_RET_OK {
2956 return Err(error_from_ret(rc));
2957 }
2958 self.pending_len = request.len().max(1);
2959 Ok(())
2960 }
2961
2962 fn try_recv_reply_raw(
2963 &mut self,
2964 reply_buf: &mut [u8],
2965 ) -> Result<Option<usize>, TransportError> {
2966 let Some(f) = self.vtable.try_recv_reply_raw else {
2970 return Err(TransportError::Unsupported);
2971 };
2972 let mut view = self.make_view();
2973 let rc = unsafe { f(&mut view, reply_buf.as_mut_ptr(), reply_buf.len()) };
2974 if rc == NROS_RMW_RET_NO_DATA {
2975 return Ok(None);
2976 }
2977 if rc < 0 {
2978 self.pending_len = 0;
2979 return Err(error_from_ret(rc));
2980 }
2981 self.pending_len = 0;
2982 Ok(Some(rc as usize))
2983 }
2984
2985 fn server_available(&self) -> Result<bool, TransportError> {
2986 let Some(f) = self.vtable.service_server_available else {
2987 return Err(TransportError::Unsupported);
2988 };
2989 let mut view = NrosRmwServiceClient {
2996 service_name: self.service_name_buf.as_ptr(),
2997 type_name: self.type_name_buf.as_ptr(),
2998 _reserved: [0u8; 8],
2999 backend_data: self.backend_data,
3000 };
3001 let rc = unsafe { f(&mut view) };
3002 match rc {
3003 0 => Ok(false),
3004 1 => Ok(true),
3005 n if n < 0 => Err(error_from_ret(n)),
3006 _ => Ok(true),
3010 }
3011 }
3012}
3013
3014impl Drop for CffiServiceClient {
3015 fn drop(&mut self) {
3016 if !self.backend_data.is_null() {
3017 let mut view = self.make_view();
3018 unsafe { (self.vtable.destroy_service_client)(&mut view) };
3019 }
3020 }
3021}
3022
3023#[derive(Default)]
3029pub struct CffiRmw;
3030
3031impl nros_rmw::Rmw for CffiRmw {
3032 type Session = CffiSession;
3033 type Error = TransportError;
3034
3035 fn open(self, config: &nros_rmw::RmwConfig) -> Result<CffiSession, TransportError> {
3036 let mode = match config.mode {
3037 nros_rmw::SessionMode::Client => 0u8,
3038 nros_rmw::SessionMode::Peer => 1u8,
3039 };
3040 CffiSession::open(config.locator, mode, config.domain_id, config.node_name)
3041 }
3042}
3043
3044impl CffiRmw {
3045 pub fn open_with_rmw(
3049 rmw_name: &str,
3050 config: &nros_rmw::RmwConfig,
3051 ) -> Result<CffiSession, TransportError> {
3052 let mode = match config.mode {
3053 nros_rmw::SessionMode::Client => 0u8,
3054 nros_rmw::SessionMode::Peer => 1u8,
3055 };
3056 CffiSession::open_named(
3057 rmw_name,
3058 config.locator,
3059 mode,
3060 config.domain_id,
3061 config.node_name,
3062 )
3063 }
3064}
3065
3066#[cfg(test)]
3080#[allow(static_mut_refs)]
3081mod tests {
3082 use super::*;
3083 use nros_rmw::{Rmw, RmwConfig, Session, SessionMode, TopicInfo};
3084
3085 static mut STUB_OPEN_CALLED: bool = false;
3088 static mut STUB_CREATE_PUB_CALLED: bool = false;
3089 static mut STUB_PUBLISH_CALLED: bool = false;
3090 static mut STUB_LAST_TOPIC_NAME: [u8; 64] = [0u8; 64];
3091 static mut STUB_LAST_TYPE_NAME: [u8; 64] = [0u8; 64];
3092 static mut STUB_LAST_QOS: NrosRmwQos = NrosRmwQos {
3093 reliability: 0,
3094 durability: 0,
3095 history: 0,
3096 liveliness_kind: 0,
3097 depth: 0,
3098 tx_express: 0,
3099 _reserved0: 0,
3100 deadline_ms: 0,
3101 lifespan_ms: 0,
3102 liveliness_lease_ms: 0,
3103 avoid_ros_namespace_conventions: 0,
3104 _reserved1: [0; 3],
3105 rx_buffer_hint: 0,
3106 };
3107
3108 unsafe fn copy_cstr(src: *const u8, dst: &mut [u8]) {
3112 let mut i = 0;
3113 while i < dst.len() {
3114 let b = unsafe { *src.add(i) };
3115 dst[i] = b;
3116 if b == 0 {
3117 break;
3118 }
3119 i += 1;
3120 }
3121 }
3122
3123 unsafe extern "C" fn stub_open(
3124 _locator: *const u8,
3125 _mode: u8,
3126 _domain_id: u32,
3127 _node_name: *const u8,
3128 out: *mut NrosRmwSession,
3129 ) -> NrosRmwRet {
3130 unsafe {
3131 STUB_OPEN_CALLED = true;
3132 (*out).backend_data = 0xDEAD_BEEFusize as *mut c_void;
3133 }
3134 NROS_RMW_RET_OK
3135 }
3136
3137 unsafe extern "C" fn stub_close(_session: *mut NrosRmwSession) -> NrosRmwRet {
3138 NROS_RMW_RET_OK
3139 }
3140
3141 unsafe extern "C" fn stub_drive_io(
3142 _session: *mut NrosRmwSession,
3143 _timeout_ms: i32,
3144 ) -> NrosRmwRet {
3145 NROS_RMW_RET_OK
3146 }
3147
3148 unsafe extern "C" fn stub_create_publisher(
3149 _session: *mut NrosRmwSession,
3150 _topic_name: *const u8,
3151 _type_name: *const u8,
3152 _type_hash: *const u8,
3153 _domain_id: u32,
3154 qos: *const NrosRmwQos,
3155 out: *mut NrosRmwPublisher,
3156 ) -> NrosRmwRet {
3157 unsafe {
3159 STUB_CREATE_PUB_CALLED = true;
3160 copy_cstr((*out).topic_name, &mut STUB_LAST_TOPIC_NAME);
3161 copy_cstr((*out).type_name, &mut STUB_LAST_TYPE_NAME);
3162 STUB_LAST_QOS = *qos;
3163 (*out).backend_data = 0xCAFEusize as *mut c_void;
3164 (*out).can_loan_messages = true;
3165 }
3166 NROS_RMW_RET_OK
3167 }
3168
3169 unsafe extern "C" fn stub_destroy_publisher(_publisher: *mut NrosRmwPublisher) {}
3170
3171 unsafe extern "C" fn stub_publish_raw(
3172 publisher: *mut NrosRmwPublisher,
3173 _data: *const u8,
3174 _len: usize,
3175 ) -> NrosRmwRet {
3176 unsafe {
3179 STUB_PUBLISH_CALLED = true;
3180 assert_eq!((*publisher).backend_data as usize, 0xCAFE);
3181 let mut buf = [0u8; 64];
3182 copy_cstr((*publisher).topic_name, &mut buf);
3183 assert_eq!(&buf[..], &STUB_LAST_TOPIC_NAME);
3184 }
3185 NROS_RMW_RET_OK
3186 }
3187
3188 unsafe extern "C" fn stub_create_subscriber(
3189 _: *mut NrosRmwSession,
3190 _: *const u8,
3191 _: *const u8,
3192 _: *const u8,
3193 _: u32,
3194 _: *const NrosRmwQos,
3195 out: *mut NrosRmwSubscriber,
3196 ) -> NrosRmwRet {
3197 unsafe {
3198 (*out).backend_data = core::ptr::dangling_mut::<c_void>();
3199 }
3200 NROS_RMW_RET_OK
3201 }
3202 unsafe extern "C" fn stub_destroy_subscriber(_: *mut NrosRmwSubscriber) {}
3203 unsafe extern "C" fn stub_try_recv_raw(_: *mut NrosRmwSubscriber, _: *mut u8, _: usize) -> i32 {
3204 0
3205 }
3206 unsafe extern "C" fn stub_has_data(_: *mut NrosRmwSubscriber) -> i32 {
3207 0
3208 }
3209
3210 unsafe extern "C" fn stub_create_service_server(
3211 _: *mut NrosRmwSession,
3212 _: *const u8,
3213 _: *const u8,
3214 _: *const u8,
3215 _: u32,
3216 _: *const NrosRmwQos,
3217 out: *mut NrosRmwServiceServer,
3218 ) -> NrosRmwRet {
3219 unsafe {
3220 (*out).backend_data = core::ptr::dangling_mut::<c_void>();
3221 }
3222 NROS_RMW_RET_OK
3223 }
3224 unsafe extern "C" fn stub_destroy_service_server(_: *mut NrosRmwServiceServer) {}
3225 unsafe extern "C" fn stub_try_recv_request(
3226 _: *mut NrosRmwServiceServer,
3227 _: *mut u8,
3228 _: usize,
3229 _: *mut i64,
3230 ) -> i32 {
3231 NROS_RMW_RET_NO_DATA
3232 }
3233 unsafe extern "C" fn stub_has_request(_: *mut NrosRmwServiceServer) -> i32 {
3234 0
3235 }
3236 unsafe extern "C" fn stub_send_reply(
3237 _: *mut NrosRmwServiceServer,
3238 _: i64,
3239 _: *const u8,
3240 _: usize,
3241 ) -> NrosRmwRet {
3242 NROS_RMW_RET_OK
3243 }
3244
3245 unsafe extern "C" fn stub_create_service_client(
3246 _: *mut NrosRmwSession,
3247 _: *const u8,
3248 _: *const u8,
3249 _: *const u8,
3250 _: u32,
3251 _: *const NrosRmwQos,
3252 out: *mut NrosRmwServiceClient,
3253 ) -> NrosRmwRet {
3254 unsafe {
3255 (*out).backend_data = core::ptr::dangling_mut::<c_void>();
3256 }
3257 NROS_RMW_RET_OK
3258 }
3259 unsafe extern "C" fn stub_destroy_service_client(_: *mut NrosRmwServiceClient) {}
3260 unsafe extern "C" fn stub_call_raw(
3261 _: *mut NrosRmwServiceClient,
3262 _: *const u8,
3263 _: usize,
3264 _: *mut u8,
3265 _: usize,
3266 ) -> i32 {
3267 0
3268 }
3269
3270 unsafe extern "C" fn stub_register_subscriber_event(
3271 _: *mut NrosRmwSubscriber,
3272 _: NrosRmwEventKind,
3273 _: u32,
3274 _: NrosRmwEventCallback,
3275 _: *mut c_void,
3276 ) -> NrosRmwRet {
3277 NROS_RMW_RET_UNSUPPORTED
3278 }
3279 unsafe extern "C" fn stub_register_publisher_event(
3280 _: *mut NrosRmwPublisher,
3281 _: NrosRmwEventKind,
3282 _: u32,
3283 _: NrosRmwEventCallback,
3284 _: *mut c_void,
3285 ) -> NrosRmwRet {
3286 NROS_RMW_RET_UNSUPPORTED
3287 }
3288 unsafe extern "C" fn stub_assert_publisher_liveliness(_: *mut NrosRmwPublisher) -> NrosRmwRet {
3289 NROS_RMW_RET_UNSUPPORTED
3290 }
3291
3292 static STUB_VTABLE: NrosRmwVtable = NrosRmwVtable {
3293 open: stub_open,
3294 close: stub_close,
3295 drive_io: stub_drive_io,
3296 create_publisher: stub_create_publisher,
3297 destroy_publisher: stub_destroy_publisher,
3298 publish_raw: stub_publish_raw,
3299 create_subscriber: stub_create_subscriber,
3300 destroy_subscriber: stub_destroy_subscriber,
3301 try_recv_raw: stub_try_recv_raw,
3302 has_data: stub_has_data,
3303 create_service_server: stub_create_service_server,
3304 destroy_service_server: stub_destroy_service_server,
3305 try_recv_request: stub_try_recv_request,
3306 has_request: stub_has_request,
3307 send_reply: stub_send_reply,
3308 create_service_client: stub_create_service_client,
3309 destroy_service_client: stub_destroy_service_client,
3310 call_raw: stub_call_raw,
3311 send_request_raw: None,
3312 try_recv_reply_raw: None,
3313 register_subscriber_event: stub_register_subscriber_event,
3314 register_publisher_event: stub_register_publisher_event,
3315 assert_publisher_liveliness: stub_assert_publisher_liveliness,
3316 next_deadline_ms: None,
3317 set_wake_callback: None,
3318 pub_loan: None,
3319 pub_commit: None,
3320 pub_discard: None,
3321 sub_borrow: None,
3322 sub_release: None,
3323 service_server_available: None,
3324 try_recv_sequence: None,
3325 publish_streamed: None,
3326 ping_session: None,
3327 subscriber_supports_in_place: None,
3328 process_raw_in_place: None,
3329 };
3330
3331 #[test]
3332 fn service_server_no_data_maps_to_none() {
3333 use nros_rmw::ServiceServerTrait as _;
3334
3335 let mut server = CffiServiceServer {
3336 vtable: &STUB_VTABLE,
3337 service_name_buf: [0u8; NAME_BUF_LEN],
3338 type_name_buf: [0u8; NAME_BUF_LEN],
3339 backend_data: core::ptr::dangling_mut::<c_void>(),
3340 };
3341 let mut buf = [0u8; 16];
3342
3343 assert!(server.try_recv_request(&mut buf).unwrap().is_none());
3344 }
3345
3346 #[test]
3347 fn typed_struct_roundtrip() {
3348 let ret = unsafe { nros_rmw_cffi_register_named(c"default".as_ptr(), &STUB_VTABLE) };
3350 assert_eq!(ret, NROS_RMW_RET_OK);
3351
3352 let cfg = RmwConfig {
3354 mode: SessionMode::Client,
3355 locator: "tcp/127.0.0.1:7447",
3356 domain_id: 0,
3357 node_name: "test_node",
3358 namespace: "",
3359 properties: &[],
3360 };
3361 let mut session = Rmw::open(CffiRmw, &cfg).expect("session open");
3362 assert!(unsafe { STUB_OPEN_CALLED });
3363 assert_eq!(session.node_name(), "test_node");
3364
3365 let topic = TopicInfo::new("/chatter", "std_msgs/msg/Int32", "RIHS01_abc");
3368 let qos = nros_rmw::QosSettings::default();
3369 let publisher = session
3370 .create_publisher(&topic, qos)
3371 .expect("publisher create");
3372 assert!(unsafe { STUB_CREATE_PUB_CALLED });
3373 let topic_buf = unsafe { &STUB_LAST_TOPIC_NAME };
3374 assert_eq!(
3375 core::str::from_utf8(topic_buf)
3376 .unwrap_or("")
3377 .trim_end_matches('\0'),
3378 "/chatter"
3379 );
3380 let type_buf = unsafe { &STUB_LAST_TYPE_NAME };
3381 assert_eq!(
3382 core::str::from_utf8(type_buf)
3383 .unwrap_or("")
3384 .trim_end_matches('\0'),
3385 "std_msgs/msg/Int32"
3386 );
3387
3388 assert_eq!(publisher.topic_name(), "/chatter");
3390 assert_eq!(publisher.type_name(), "std_msgs/msg/Int32");
3391 assert!(publisher.can_loan_messages());
3392
3393 use nros_rmw::Publisher as _;
3396 publisher.publish_raw(&[1u8, 2, 3]).expect("publish");
3397 assert!(unsafe { STUB_PUBLISH_CALLED });
3398 }
3399}