sedsnet/macros.rs
1//! Helper traits and macros for enum representations, little-endian numeric
2//! serialization, and typed logging.
3//!
4//! This module is primarily used by the FFI layers (`c_api`, `py_api`) and the
5//! core router/telemetry code to:
6//! - Enforce that certain enums are `#[repr(u32)]` / `#[repr(i32)]` at
7//! compile time (`ReprU32Enum`, `ReprI32Enum`).
8//! - Provide a uniform little-endian serialization interface (`LeBytes` +
9//! `impl_letype_num!`).
10//! - Implement a typed logging helper macro (`do_vec_log_typed!`) that
11//! reinterprets raw C buffers into typed vectors and routes them through
12//! the Rust `Router`.
13
14// ============================================================================
15// ReprU32Enum / ReprI32Enum: enum layout helpers
16// ============================================================================
17
18/// The `ReprU32Enum` trait marks enums that are represented as `u32` in
19/// packed / FFI form.
20///
21/// Types implementing this trait are expected to be:
22/// - `#[repr(u32)]`
23/// - fieldless enums (C-like)
24/// - `Copy`
25///
26/// The `MAX` associated constant is the maximum valid discriminant, used by
27/// helpers like `try_enum_from_u32`.
28pub trait ReprU32Enum: Copy + Sized {
29 /// Maximum valid numeric value for this enum (inclusive).
30 const MAX: u32;
31 /// Convert a valid raw value to this type.
32 fn from_u32(x: u32) -> Option<Self>;
33}
34
35/// Implement [`ReprU32Enum`] for a concrete `#[repr(u32)]` enum and perform
36/// a compile-time size check to ensure it really is the same size as `u32`.
37///
38/// # Example
39///
40/// ```text
41/// #[repr(u32)]
42/// enum Foo {
43/// A = 0,
44/// B = 1,
45/// }
46///
47/// impl_repr_u32_enum!(Foo, 1);
48/// ```
49#[macro_export]
50macro_rules! impl_repr_u32_enum {
51 ($ty:ty, $max:expr) => {
52 // Compile-time size check for this concrete type.
53 const _: [(); size_of::<$ty>()] = [(); size_of::<u32>()];
54
55 impl $crate::macros::ReprU32Enum for $ty {
56 const MAX: u32 = $max;
57
58 #[inline]
59 fn from_u32(x: u32) -> Option<Self> {
60 // SAFETY: `E` is promised to be a fieldless #[repr(u32)] enum (thus 4 bytes, Copy).
61 Some(unsafe { (&x as *const u32 as *const Self).read() })
62 }
63 }
64 };
65}
66
67/// The `ReprI32Enum` trait marks enums that are represented as `i32` in
68/// packed / FFI form.
69///
70/// Types implementing this trait are expected to be:
71/// - `#[repr(i32)]`
72/// - fieldless enums (C-like)
73/// - `Copy`
74///
75/// `MIN` / `MAX` define the inclusive numeric range of valid values.
76pub trait ReprI32Enum: Copy + Sized {
77 /// Maximum valid numeric value for this enum (inclusive).
78 const MAX: i32;
79 /// Minimum valid numeric value for this enum (inclusive).
80 const MIN: i32;
81}
82
83/// Implement [`ReprI32Enum`] for a concrete `#[repr(i32)]` enum and perform
84/// a compile-time size check to ensure it really is the same size as `i32`.
85///
86/// # Example
87///
88/// ```text
89/// #[repr(i32)]
90/// enum ErrCode {
91/// Foo = -1,
92/// Bar = -2,
93/// }
94///
95/// impl_repr_i32_enum!(ErrCode, ErrCode::Foo as i32, ErrCode::Bar as i32);
96/// ```
97#[macro_export]
98macro_rules! impl_repr_i32_enum {
99 ($ty:ty, $max:expr, $min:expr) => {
100 // Compile-time size check for this concrete type.
101 const _: [(); size_of::<$ty>()] = [(); size_of::<i32>()];
102
103 impl $crate::macros::ReprI32Enum for $ty {
104 const MAX: i32 = $max;
105 const MIN: i32 = $min;
106 }
107 };
108}
109
110// ============================================================================
111// LeBytes helper macro (numeric → little-endian bytes)
112// ============================================================================
113
114/// Implement the [`LeBytes`] trait for a numeric type with a fixed byte width.
115///
116/// This is used to unify little-endian serialization for primitive types
117/// like `u16`, `u32`, `f32`, etc.
118///
119/// `$t`: concrete numeric type (e.g. `u16`, `i32`, `f32`)
120/// `$w`: width in bytes (`size_of::<$t>()`)
121///
122/// # Example
123///
124/// ```text
125/// impl_letype_num!(u32, 4);
126/// impl_letype_num!(f32, 4);
127/// ```
128#[macro_export]
129macro_rules! impl_letype_num {
130 ($t:ty, $w:expr) => {
131 impl $crate::router::LeBytes for $t {
132 const WIDTH: usize = $w;
133
134 #[inline]
135 fn write_le(self, out: &mut [u8]) {
136 assert_eq!(out.len(), Self::WIDTH, "write_le: wrong out slice len");
137 out.copy_from_slice(&self.to_le_bytes());
138 }
139
140 #[inline]
141 fn from_le_slice(bytes: &[u8]) -> Self {
142 assert_eq!(bytes.len(), Self::WIDTH, "from_le_slice: wrong slice len");
143 let arr: [u8; $w] = bytes.try_into().expect("slice length mismatch");
144 <$t>::from_le_bytes(arr)
145 }
146 }
147 };
148}
149
150// ============================================================================
151// do_vec_log_typed: C-FFI → typed Vec<T> logger helper
152// ============================================================================
153
154/// Helper macro used by the C FFI (`c_api`) to:
155///
156/// 1. Reinterpret a raw pointer + count as a sequence of elements of type
157/// `$elem_ty` using unaligned little-endian reads.
158/// 2. Log those elements through a `Router` using the unified
159/// `call_log_or_queue` helper.
160/// 3. Map any vectorization failure into a `TelemetryError::Io` and return a
161/// C-style status code.
162///
163/// This keeps the FFI functions small and consistent.
164///
165/// **Parameters**
166///
167/// - `$router_ptr`: `*mut SedsRouter`
168/// - `$ty`: `DataType`
169/// - `$ts_opt`: `Option<u64>` timestamp
170/// - `$queue`: `bool` (true = queue, false = immediate log)
171/// - `$data_ptr`: `*const c_void` from C
172/// - `$count`: `usize` number of elements
173/// - `$elem_ty`: concrete Rust element type (e.g. `u16`, `i32`, `f32`)
174#[macro_export]
175macro_rules! do_vec_log_typed {
176 (
177 $router_ptr:expr, // *mut SedsRouter
178 $ty:expr, // DataType
179 $ts_opt:expr, // Option<u64>
180 $queue:expr, // bool
181 $data_ptr:expr, // *const c_void
182 $count:expr, // usize
183 $elem_ty:ty // concrete element type, e.g. u16 / f32
184 ) => {{
185 use alloc::vec::Vec;
186 use core::mem;
187
188 let mut tmp: Vec<$elem_ty> = Vec::with_capacity($count);
189 let base = $data_ptr as *const u8;
190
191 match $crate::c_api::vectorize_data::<$elem_ty>(
192 base,
193 $count,
194 mem::size_of::<$elem_ty>(),
195 &mut tmp,
196 ) {
197 Ok(()) => $crate::c_api::ok_or_status($crate::c_api::call_log_or_queue::<$elem_ty>(
198 $router_ptr,
199 $ty,
200 $ts_opt,
201 &tmp,
202 $queue,
203 )),
204 Err(_e) => {
205 $crate::c_api::status_from_err($crate::TelemetryError::Io("vectorize_data failed"))
206 }
207 }
208 }};
209}
210
211/// Helper macros for implementing `Packet` constructors and
212/// accessors for primitive slices.
213#[macro_export]
214macro_rules! impl_from_prim_slices {
215 ($($fn_name:ident, $elem_ty:ty);+ $(;)?) => {
216 $(
217 #[doc = concat!("Build a packet from a slice of little-endian `", stringify!($elem_ty), "` values.")]
218 #[inline]
219 pub fn $fn_name(
220 ty: DataType,
221 values: &[$elem_ty],
222 endpoints: &[DataEndpoint],
223 timestamp: u64,
224 ) -> TelemetryResult<Self> {
225 Self::from_prim_le_slice(ty, values, endpoints, timestamp)
226 }
227 )+
228 };
229}
230/// Helper macros for implementing `Packet` data accessors for
231/// primitive slices.
232#[macro_export]
233macro_rules! impl_data_as_prim {
234 ($($name:ident, $ty:ty, $variant:expr);+ $(;)?) => {
235 $(
236 #[doc = concat!("Decode payload as little-endian `", stringify!($ty), "` values.")]
237 #[inline]
238 pub fn $name(&self) -> TelemetryResult<Vec<$ty>> {
239 self._as_le_bytes::<$ty>($variant)
240 }
241 )+
242 };
243}
244
245/// Implement the [`LeDecode`] trait for a numeric type with fixed size.
246///
247/// This is used to unify little-endian deserialization for primitive types
248/// like `u16`, `u32`, `f32`, etc.
249///
250/// `$ty`: concrete numeric type (e.g. `u16`, `i32`, `f32`)
251///# Example
252/// ```text
253/// impl_ledecode_auto!(u32);
254/// impl_ledecode_auto!(f32);
255/// ```
256#[macro_export]
257macro_rules! impl_ledecode_auto {
258 ($ty:ty) => {
259 impl LeDecode for $ty {
260 const WIDTH: usize = core::mem::size_of::<$ty>();
261 #[inline]
262 fn from_le(slice: &[u8]) -> Self {
263 let arr: [u8; core::mem::size_of::<$ty>()] =
264 slice.try_into().expect("slice length mismatch");
265 <$ty>::from_le_bytes(arr)
266 }
267 }
268 };
269}