Skip to main content

nros/
sizes.rs

1//! Single source of truth for FFI opaque-storage sizes.
2//!
3//! Each `export_size!` invocation produces two artefacts:
4//!
5//! * `pub const FOO_SIZE: usize = core::mem::size_of::<T>();` — a normal
6//!   Rust const suitable for in-crate `const _: () = assert!(...)` checks and
7//!   direct use by `no_std` consumers.
8//! * `pub static __NROS_SIZE_FOO: [u8; FOO_SIZE] = [0; FOO_SIZE];` — an
9//!   array-sized static whose *symbol storage size* in the compiled rlib
10//!   equals `FOO_SIZE`. `nros-c`/`nros-cpp` build scripts read the sizes out
11//!   via [`nros_sizes_build::extract_sizes`](../../../nros-sizes-build/index.html)
12//!   to derive opaque-storage macros for the generated C/C++ headers.
13//!
14//! Feature gating follows the rest of the crate: the statics only exist when
15//! an RMW backend (`rmw-zenoh` / `rmw-xrce` / `rmw-cyclonedds` / `rmw-cffi`) is
16//! active, which is exactly the condition under which the `Rmw*` type
17//! aliases resolve. Workspace-level `cargo check` without any RMW feature
18//! sees this module as empty.
19
20#[cfg(feature = "rmw-cffi")]
21mod rmw_sizes {
22    use crate::internals::{
23        RmwPublisher, RmwServiceClient, RmwServiceServer, RmwSession, RmwSubscriber,
24    };
25
26    // Phase 77.25: per-name v0-mangled markers so the probe works
27    // under fat LTO. Each call to `export_size!(NAME = Ty)` expands to
28    // a distinct generic fn `__nros_size_NAME<const N: usize>` plus a
29    // monomorphised fn-pointer static. The monomorphisation's v0
30    // mangled symbol name contains both the name ("NAME") *and* the
31    // const-generic value (the size) — e.g. demangles as
32    // `nros::sizes::rmw_sizes::__nros_size_PUBLISHER_SIZE::<48>`.
33    // Symbol names survive LTO because the linker still needs them,
34    // even when the object file is LLVM bitcode and `object::parse`
35    // can't read symbol byte sizes. The original `__NROS_SIZE_<NAME>`
36    // static kept for backwards-compat is also emitted for consumers
37    // that still walk the legacy path.
38    #[doc(hidden)]
39    pub mod _size_markers {}
40
41    macro_rules! export_size {
42        ($vis:vis $name:ident = $ty:ty) => {
43            $vis const $name: usize = core::mem::size_of::<$ty>();
44            paste::paste! {
45                #[cfg_attr(feature = "ffi-size-markers", used)]
46                #[unsafe(no_mangle)]
47                #[doc(hidden)]
48                pub static [<__NROS_SIZE_ $name>]: [u8; $name] = [0u8; $name];
49
50                #[doc(hidden)]
51                #[allow(non_snake_case)]
52                #[inline(never)]
53                pub fn [<__nros_size_ $name>]<const N: usize>() -> usize { N }
54
55                #[cfg_attr(feature = "ffi-size-markers", used)]
56                #[doc(hidden)]
57                pub static [<__NROS_SIZE_FN_ $name>]: fn() -> usize =
58                    [<__nros_size_ $name>]::<{ $name }>;
59            }
60        };
61    }
62
63    export_size!(pub SESSION_SIZE        = RmwSession);
64    export_size!(pub PUBLISHER_SIZE      = RmwPublisher);
65    export_size!(pub SUBSCRIBER_SIZE     = RmwSubscriber);
66    export_size!(pub SERVICE_CLIENT_SIZE = RmwServiceClient);
67    export_size!(pub SERVICE_SERVER_SIZE = RmwServiceServer);
68
69    // Phase 122.3.c.3 — L1 polling-mode Raw* handles. Defaults to
70    // `DEFAULT_RX_BUF_SIZE` on each const-generic slot, which is
71    // exactly the value `nros-c::config::MESSAGE_BUFFER_SIZE` resolves
72    // to (both derive from `NROS_SUBSCRIPTION_BUFFER_SIZE` via
73    // `nros-node`'s build.rs). nros-c's build.rs reads these probe
74    // values and emits the matching `*_OPAQUE_U64S` macros into the
75    // per-build variant header so C struct `_opaque` storage agrees
76    // with the Rust struct layout. Without this probe, cbindgen
77    // ships a `_OPAQUE_U64S = 1` placeholder which silently
78    // truncates the C-side `_opaque` slot.
79    export_size!(pub RAW_SUBSCRIPTION_SIZE   = nros_node::RawSubscription);
80    export_size!(pub RAW_SERVICE_SERVER_SIZE = nros_node::RawServiceServer);
81    export_size!(pub RAW_SERVICE_CLIENT_SIZE = nros_node::RawServiceClient);
82
83    // Phase 122.3.c.6 — typeless action `*Core` types are the raw
84    // L1 polling-mode entities (typed `ActionServer<A>` / `ActionClient<A>`
85    // wrap them with serdes glue). Default const generics resolve to
86    // `DEFAULT_RX_BUF_SIZE` (= `MESSAGE_BUFFER_SIZE` in nros-c) and
87    // `MAX_GOALS = 4`, matching the L2 callback path.
88    export_size!(pub RAW_ACTION_SERVER_SIZE = nros_node::ActionServerCore);
89    export_size!(pub RAW_ACTION_CLIENT_SIZE = nros_node::ActionClientCore);
90
91    // phase-271 — the C/C++ `_opaque` executor buffer holds the `Executor<'static>`
92    // header AND the per-entry storage backing carved from the same buffer, so it
93    // must be sized for the combined `#[repr(C)]` layout, not bare `Executor`.
94    export_size!(pub EXECUTOR_SIZE       = nros_node::ExecutorInlineStorage);
95    export_size!(pub GUARD_CONDITION_SIZE = nros_node::GuardConditionHandle);
96    export_size!(pub LIFECYCLE_CTX_SIZE  = nros_node::lifecycle::LifecyclePollingNodeCtx);
97    // Phase 91.C: nros-c's `ActionServerInternal` embeds this nros-node
98    // type as a typed field. cbindgen (which can't recurse into deps)
99    // emits the field as `ActionServerRawHandle handle;` referencing a
100    // type it cannot define. nros-c's build.rs reads this size and emits
101    // an opaque type-compatible declaration into nros_config_generated.h
102    // so the cbindgen output is self-contained.
103    export_size!(pub ACTION_SERVER_RAW_HANDLE_SIZE = nros_node::ActionServerRawHandle);
104
105    // Layout-mirror struct for `nros_c::action::ActionServerInternal`.
106    // ActionServerInternal lives in the `nros-c` crate (it embeds C-API
107    // pointer types like `*mut nros_action_server_t`), so it can't be
108    // referenced from `nros` directly. This mirror has the same `#[repr(C)]`
109    // field shape — `*mut c_void` and `unsafe extern "C" fn(*mut c_void, ...)`
110    // pointer slots — and therefore the same byte size, since fn-pointer
111    // size is independent of parameter types. nros-c asserts at compile
112    // time that `size_of::<ActionServerInternal>() ==
113    // size_of::<ActionServerInternalLayout>()`.
114    use core::ffi::c_void;
115    type CGoalCallbackLayout =
116        unsafe extern "C" fn(*mut c_void, *const c_void, *const u8, usize, *mut c_void) -> i32;
117    type CCancelCallbackLayout =
118        Option<unsafe extern "C" fn(*const c_void, i32, *mut c_void) -> i32>;
119    type CAcceptedCallbackLayout =
120        Option<unsafe extern "C" fn(*mut c_void, *const c_void, *mut c_void)>;
121
122    #[repr(C)]
123    #[doc(hidden)]
124    pub struct ActionServerInternalLayout {
125        pub handle: nros_node::ActionServerRawHandle,
126        pub executor_ptr: *mut c_void,
127        pub c_goal_callback: CGoalCallbackLayout,
128        pub c_cancel_callback: CCancelCallbackLayout,
129        pub c_accepted_callback: CAcceptedCallbackLayout,
130        pub c_context: *mut c_void,
131        pub server_ptr: *mut c_void,
132    }
133    export_size!(pub ACTION_SERVER_INTERNAL_SIZE = ActionServerInternalLayout);
134
135    // Layout-mirrors for nros-cpp's `CppActionServer` and `CppActionClient`.
136    //
137    // Same approach as `ActionServerInternalLayout` above: nros-cpp's
138    // wrapper structs live in a downstream crate but their byte sizes can
139    // be reconstructed from the field shape. This eliminates the
140    // hand-math in `nros-cpp/build.rs` (was Phase 87.11).
141    //
142    // The C++-side `nros::ActionServer<A>` / `nros::ActionClient<A>`
143    // classes hold opaque storage sized to these probe values. nros-cpp
144    // asserts `size_of::<CppActionServer>() == size_of::<CppActionServerLayout>()`
145    // (and the same for CppActionClient) so any field-shape drift in the
146    // real wrapper trips the build immediately.
147
148    type CppGoalCallbackLayout =
149        unsafe extern "C" fn(*const [u8; 16], *const u8, usize, *mut c_void) -> i32;
150    type CppCancelCallbackLayout = unsafe extern "C" fn(*const [u8; 16], *mut c_void) -> i32;
151
152    // Byte-shape mirror of one of `nros-cpp`'s `nros_cpp_qos_t` policy enums
153    // (`nros_cpp_qos_reliability_t` et al). These are `#[repr(C)]` fieldless
154    // enums, so their width follows the *target C ABI*: `c_int` (4 bytes) on
155    // x86_64, but **1 byte on ARM EABI** (`armv7a-nuttx-eabihf` defaults to
156    // `-fshort-enums`). Mirroring them as `c_int` (the pre-fix shape) over-sized
157    // the qos block by 12 bytes on ARM and tripped the `CppActionServer`
158    // layout assert. A `#[repr(C)]` enum here tracks the same short-enum width
159    // on every target. Variant count is irrelevant to width while ≤ 255 (all
160    // four real enums have ≤ 4 variants), so one placeholder mirrors all four.
161    #[repr(C)]
162    #[doc(hidden)]
163    pub enum CppQosEnumLayout {
164        A = 0,
165        B = 1,
166    }
167
168    // Phase 193.4b: byte-shape mirror of `nros-cpp`'s `nros_cpp_qos_t`
169    // (4 C-ABI enums + `c_int` depth + 3×u32 + u8). The action server now
170    // stores the create-time QoS until registration; the real
171    // `CppActionServer` field-shape assert keeps this in sync.
172    #[repr(C)]
173    #[doc(hidden)]
174    pub struct CppQosLayout {
175        pub reliability: CppQosEnumLayout,
176        pub durability: CppQosEnumLayout,
177        pub history: CppQosEnumLayout,
178        pub liveliness_kind: CppQosEnumLayout,
179        pub depth: core::ffi::c_int,
180        pub deadline_ms: u32,
181        pub lifespan_ms: u32,
182        pub liveliness_lease_ms: u32,
183        pub avoid_ros_namespace_conventions: u8,
184    }
185
186    // Phase 87.6 thin-wrapper: `action_name` / `type_name` / `type_hash`
187    // buffers moved to the C++ `nros::ActionServer<A>` class.
188    // Phase 104.C.9.b: `node_id` field added so action create→register
189    // can route through the per-Node session.
190    // Phase 193.4b: `qos` field added so create-time QoS reaches the three
191    // underlying service servers at register time.
192    #[repr(C)]
193    #[doc(hidden)]
194    pub struct CppActionServerLayout {
195        pub handle: Option<nros_node::ActionServerRawHandle>,
196        pub goal_cb: Option<CppGoalCallbackLayout>,
197        pub cancel_cb: Option<CppCancelCallbackLayout>,
198        pub cb_ctx: *mut c_void,
199        pub node_id: u8,
200        pub _reserved: [u8; 7],
201        pub qos: CppQosLayout,
202    }
203    export_size!(pub CPP_ACTION_SERVER_SIZE = CppActionServerLayout);
204
205    type CppActionGoalResponseCb = Option<unsafe extern "C" fn(bool, *const [u8; 16], *mut c_void)>;
206    type CppActionFeedbackCb =
207        Option<unsafe extern "C" fn(*const [u8; 16], *const u8, usize, *mut c_void)>;
208    type CppActionResultCb =
209        Option<unsafe extern "C" fn(*const [u8; 16], i32, *const u8, usize, *mut c_void)>;
210
211    #[repr(C)]
212    #[doc(hidden)]
213    pub struct CppActionClientCallbacksLayout {
214        pub goal_response: CppActionGoalResponseCb,
215        pub feedback: CppActionFeedbackCb,
216        pub result: CppActionResultCb,
217        pub context: *mut c_void,
218    }
219
220    // Phase 87.6 thin-wrapper: `action_name` buffer moved to the C++
221    // `nros::ActionClient<A>` class.
222    #[repr(C)]
223    #[doc(hidden)]
224    pub struct CppActionClientLayout {
225        pub callbacks: CppActionClientCallbacksLayout,
226        pub arena_entry_index: i32,
227        pub executor_ptr: *mut c_void,
228    }
229    export_size!(pub CPP_ACTION_CLIENT_SIZE = CppActionClientLayout);
230}
231
232#[cfg(feature = "rmw-cffi")]
233pub use rmw_sizes::*;