Skip to main content

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}