Skip to main content

nros_platform_cffi/
lib.rs

1//! Rust mirror of the canonical C ABI in `<nros/platform.h>`.
2//!
3//! Every nros binary links exactly one platform implementation; the
4//! free `extern "C"` symbols declared below are resolved at link time.
5//! There is no runtime registration step. To inject a platform from
6//! C, drop a translation unit defining the symbols (or link against
7//! a static library that does).
8//!
9//! Rust platform crates implement the [`nros_platform_api`] traits as
10//! before; a sibling `-cffi` shim crate re-exports the Rust impl as
11//! `#[unsafe(no_mangle)] extern "C"` symbols matching the names in the
12//! header. That separation lets the same Rust impl serve both
13//! trait-driven Rust callers and C-ABI consumers.
14//!
15//! # Usage
16//!
17//! - C implementor: implement the functions in `<nros/platform.h>` and
18//!   link against the nros binary.
19//! - Rust consumer: enable the `platform-cffi` feature on
20//!   `nros-platform`; [`CffiPlatform`] dispatches every trait call to
21//!   the linked C symbols.
22//!
23//! # Companion
24//!
25//! Platform sits one tier below RMW. The Phase 117 RMW vtable
26//! (`<nros/rmw_vtable.h>`) is a runtime-pluggable struct; the
27//! platform layer is link-time-bound free symbols. Different choice
28//! because RMW backends genuinely swap per session (zenoh vs cyclonedds
29//! vs xrce in the same binary at test time) while a platform is fixed
30//! for the life of a binary.
31
32#![no_std]
33#![allow(clippy::not_unsafe_ptr_arg_deref)]
34
35use core::ffi::c_void;
36
37// Anchor symbol so downstream crates can chain `#[used]` statics to
38// keep this rlib in the link graph. Without an explicit reference,
39// rustc elides the rlib (it's mostly extern decls + a trait impl that
40// gets inlined into callers), and the build.rs `cargo:rustc-link-lib=`
41// directive for `libnros_platform_posix.a` is dropped along with it,
42// leaving every `nros_platform_*` symbol unresolved at the binary
43// link step.
44#[cfg(feature = "posix-c-port")]
45#[doc(hidden)]
46#[inline(never)]
47pub extern "C" fn _nros_force_link_cffi() {}
48
49// ============================================================================
50// Canonical ABI declarations
51// ----------------------------------------------------------------------------
52// Hand-written mirror of `include/nros/platform.h`. Field order, names,
53// and types track the header byte-for-byte. Updates land in the header
54// first, then here.
55// ============================================================================
56
57unsafe extern "C" {
58    // -- Clock --
59    pub fn nros_platform_clock_ms() -> u64;
60    pub fn nros_platform_clock_us() -> u64;
61
62    // -- Alloc --
63    pub fn nros_platform_alloc(size: usize) -> *mut c_void;
64    pub fn nros_platform_realloc(ptr: *mut c_void, size: usize) -> *mut c_void;
65    pub fn nros_platform_dealloc(ptr: *mut c_void);
66    pub fn nros_platform_heap_used_bytes() -> usize;
67    pub fn nros_platform_heap_total_bytes() -> usize;
68
69    // -- Sleep --
70    pub fn nros_platform_sleep_us(us: usize);
71    pub fn nros_platform_sleep_ms(ms: usize);
72    pub fn nros_platform_sleep_s(s: usize);
73
74    // -- Yield --
75    pub fn nros_platform_yield_now();
76
77    // -- Random --
78    pub fn nros_platform_random_u8() -> u8;
79    pub fn nros_platform_random_u16() -> u16;
80    pub fn nros_platform_random_u32() -> u32;
81    pub fn nros_platform_random_u64() -> u64;
82    pub fn nros_platform_random_fill(buf: *mut c_void, len: usize);
83
84    // -- Time (wall clock) --
85    pub fn nros_platform_time_now_ms() -> u64;
86    pub fn nros_platform_time_since_epoch_secs() -> u32;
87    pub fn nros_platform_time_since_epoch_nanos() -> u32;
88
89    // -- Tasks --
90    pub fn nros_platform_task_init(
91        task: *mut c_void,
92        attr: *mut c_void,
93        entry: Option<unsafe extern "C" fn(*mut c_void) -> *mut c_void>,
94        arg: *mut c_void,
95    ) -> i8;
96    pub fn nros_platform_task_join(task: *mut c_void) -> i8;
97    pub fn nros_platform_task_detach(task: *mut c_void) -> i8;
98    pub fn nros_platform_task_cancel(task: *mut c_void) -> i8;
99    pub fn nros_platform_task_exit();
100    pub fn nros_platform_task_free(task: *mut *mut c_void);
101
102    // -- Mutex (non-recursive) --
103    pub fn nros_platform_mutex_init(m: *mut c_void) -> i8;
104    pub fn nros_platform_mutex_drop(m: *mut c_void) -> i8;
105    pub fn nros_platform_mutex_lock(m: *mut c_void) -> i8;
106    pub fn nros_platform_mutex_try_lock(m: *mut c_void) -> i8;
107    pub fn nros_platform_mutex_unlock(m: *mut c_void) -> i8;
108
109    // -- Mutex (recursive) --
110    pub fn nros_platform_mutex_rec_init(m: *mut c_void) -> i8;
111    pub fn nros_platform_mutex_rec_drop(m: *mut c_void) -> i8;
112    pub fn nros_platform_mutex_rec_lock(m: *mut c_void) -> i8;
113    pub fn nros_platform_mutex_rec_try_lock(m: *mut c_void) -> i8;
114    pub fn nros_platform_mutex_rec_unlock(m: *mut c_void) -> i8;
115
116    // -- Condvar --
117    pub fn nros_platform_condvar_init(cv: *mut c_void) -> i8;
118    pub fn nros_platform_condvar_drop(cv: *mut c_void) -> i8;
119    pub fn nros_platform_condvar_signal(cv: *mut c_void) -> i8;
120    pub fn nros_platform_condvar_signal_all(cv: *mut c_void) -> i8;
121    /// Phase 124.B.7.a — ISR-safe variant of `signal`. See
122    /// `<nros/platform.h>` for per-platform contract.
123    pub fn nros_platform_condvar_signal_from_isr(cv: *mut c_void) -> i8;
124    pub fn nros_platform_condvar_wait(cv: *mut c_void, m: *mut c_void) -> i8;
125    pub fn nros_platform_condvar_wait_until(cv: *mut c_void, m: *mut c_void, abstime: u64) -> i8;
126
127    // -- Wake primitive (Phase 130) --
128    // Binary-semaphore-shaped primitive for the executor's wake_flag /
129    // spin_once cv-wait pair. See `<nros/platform.h>` for per-platform
130    // contract and ISR-safety rules.
131    pub fn nros_platform_wake_init(w: *mut c_void) -> i8;
132    pub fn nros_platform_wake_drop(w: *mut c_void) -> i8;
133    pub fn nros_platform_wake_wait_ms(w: *mut c_void, timeout_ms: u32) -> i8;
134    pub fn nros_platform_wake_signal(w: *mut c_void) -> i8;
135    pub fn nros_platform_wake_signal_from_isr(w: *mut c_void) -> i8;
136    pub fn nros_platform_wake_storage_size() -> usize;
137    pub fn nros_platform_wake_storage_align() -> usize;
138
139    // -- Critical section (Phase 121.9) --
140    pub fn nros_platform_critical_section_acquire() -> u32;
141    pub fn nros_platform_critical_section_release(token: u32);
142
143    // -- Logging (Phase 88) --
144    pub fn nros_platform_log_write(
145        severity: u8,
146        name_ptr: *const u8,
147        name_len: usize,
148        msg_ptr: *const u8,
149        msg_len: usize,
150    );
151    pub fn nros_platform_log_flush();
152}
153
154/// Board-supplied writer fn type. ONLY meaningful on platforms whose
155/// `nros_platform_log_write` impl is itself a thin dispatcher to a
156/// board-registered fn (FreeRTOS, ThreadX, bare-metal). On platforms
157/// with a native logger (POSIX, Zephyr, ESP-IDF, NuttX), the symbol
158/// is absent and the board should not link against it.
159pub type NrosPlatformLogWriterFn = unsafe extern "C" fn(
160    severity: u8,
161    name_ptr: *const u8,
162    name_len: usize,
163    msg_ptr: *const u8,
164    msg_len: usize,
165);
166
167/// Board-supplied flush fn type. Pass `None` to
168/// [`nros_platform_register_log_writer`] when the writer is fully
169/// synchronous.
170pub type NrosPlatformLogFlushFn = unsafe extern "C" fn();
171
172unsafe extern "C" {
173    /// Register a board writer + optional flusher (Phase 88.9).
174    /// Available only when the linked platform impl is one of the
175    /// no-native-logger backends (FreeRTOS / ThreadX / bare-metal).
176    pub fn nros_platform_register_log_writer(
177        writer: Option<NrosPlatformLogWriterFn>,
178        flusher: Option<NrosPlatformLogFlushFn>,
179    );
180}
181
182// ============================================================================
183// Phase 121.6.rust-mirror — extended canonical ABI
184// ----------------------------------------------------------------------------
185// Mirrors `<nros/platform_timer.h>` + `<nros/platform_net.h>`. Declarations
186// only — definitions are supplied by whichever provider the binary links
187// (a per-RTOS C port via 121.6.<port>-c, or a future macro-expanded Rust
188// impl). Anyone NOT pulling these via `CffiPlatform`'s extended-surface
189// trait impls (those land in a follow-up commit) gets dead-code-stripped
190// extern refs at link time — no symbol resolution required.
191// ============================================================================
192
193unsafe extern "C" {
194    // -- Timer (platform_timer.h) --
195    pub fn nros_platform_timer_create_periodic(
196        period_us: u32,
197        callback: unsafe extern "C" fn(*mut c_void),
198        user_data: *mut c_void,
199    ) -> *mut c_void;
200    pub fn nros_platform_timer_create_oneshot(
201        timeout_us: u32,
202        callback: unsafe extern "C" fn(*mut c_void),
203        user_data: *mut c_void,
204    ) -> *mut c_void;
205    pub fn nros_platform_timer_destroy(handle: *mut c_void);
206    pub fn nros_platform_timer_cancel(handle: *mut c_void) -> i8;
207
208    // -- TCP (platform_net.h) --
209    pub fn nros_platform_tcp_create_endpoint(
210        ep: *mut c_void,
211        address: *const u8,
212        port: *const u8,
213    ) -> i8;
214    pub fn nros_platform_tcp_free_endpoint(ep: *mut c_void);
215    pub fn nros_platform_tcp_open(
216        sock: *mut c_void,
217        endpoint: *const c_void,
218        timeout_ms: u32,
219    ) -> i8;
220    pub fn nros_platform_tcp_listen(sock: *mut c_void, endpoint: *const c_void) -> i8;
221    pub fn nros_platform_tcp_close(sock: *mut c_void);
222    pub fn nros_platform_tcp_read(sock: *const c_void, buf: *mut u8, len: usize) -> usize;
223    pub fn nros_platform_tcp_read_exact(sock: *const c_void, buf: *mut u8, len: usize) -> usize;
224    pub fn nros_platform_tcp_send(sock: *const c_void, buf: *const u8, len: usize) -> usize;
225
226    // -- UDP unicast --
227    pub fn nros_platform_udp_create_endpoint(
228        ep: *mut c_void,
229        address: *const u8,
230        port: *const u8,
231    ) -> i8;
232    pub fn nros_platform_udp_free_endpoint(ep: *mut c_void);
233    pub fn nros_platform_udp_open(
234        sock: *mut c_void,
235        endpoint: *const c_void,
236        timeout_ms: u32,
237    ) -> i8;
238    pub fn nros_platform_udp_listen(
239        sock: *mut c_void,
240        endpoint: *const c_void,
241        timeout_ms: u32,
242    ) -> i8;
243    pub fn nros_platform_udp_close(sock: *mut c_void);
244    pub fn nros_platform_udp_read(sock: *const c_void, buf: *mut u8, len: usize) -> usize;
245    pub fn nros_platform_udp_read_exact(sock: *const c_void, buf: *mut u8, len: usize) -> usize;
246    pub fn nros_platform_udp_send(
247        sock: *const c_void,
248        buf: *const u8,
249        len: usize,
250        endpoint: *const c_void,
251    ) -> usize;
252    pub fn nros_platform_udp_set_recv_timeout(sock: *const c_void, timeout_ms: u32);
253
254    // -- UDP multicast --
255    pub fn nros_platform_udp_mcast_open(
256        sock: *mut c_void,
257        endpoint: *const c_void,
258        lep: *mut c_void,
259        timeout_ms: u32,
260        iface: *const u8,
261    ) -> i8;
262    pub fn nros_platform_udp_mcast_listen(
263        sock: *mut c_void,
264        endpoint: *const c_void,
265        timeout_ms: u32,
266        iface: *const u8,
267        join: *const u8,
268    ) -> i8;
269    pub fn nros_platform_udp_mcast_close(
270        sockrecv: *mut c_void,
271        socksend: *mut c_void,
272        rep: *const c_void,
273        lep: *const c_void,
274    );
275    pub fn nros_platform_udp_mcast_read(
276        sock: *const c_void,
277        buf: *mut u8,
278        len: usize,
279        lep: *const c_void,
280        addr: *mut c_void,
281    ) -> usize;
282    pub fn nros_platform_udp_mcast_read_exact(
283        sock: *const c_void,
284        buf: *mut u8,
285        len: usize,
286        lep: *const c_void,
287        addr: *mut c_void,
288    ) -> usize;
289    pub fn nros_platform_udp_mcast_send(
290        sock: *const c_void,
291        buf: *const u8,
292        len: usize,
293        endpoint: *const c_void,
294    ) -> usize;
295
296    // -- Socket helpers --
297    pub fn nros_platform_socket_set_non_blocking(sock: *const c_void) -> i8;
298    pub fn nros_platform_socket_accept(sock_in: *const c_void, sock_out: *mut c_void) -> i8;
299    pub fn nros_platform_socket_close(sock: *mut c_void);
300    pub fn nros_platform_socket_wait_event(peers: *mut c_void, mutex: *mut c_void) -> i8;
301
302    // -- Network poll --
303    pub fn nros_platform_network_poll();
304}
305
306// ============================================================================
307// Return codes (mirrors header)
308// ============================================================================
309
310/// Mirrors C `nros_platform_ret_t`.
311pub type NrosPlatformRet = i32;
312
313pub const NROS_PLATFORM_RET_OK: NrosPlatformRet = 0;
314pub const NROS_PLATFORM_RET_ERROR: NrosPlatformRet = -1;
315pub const NROS_PLATFORM_RET_UNSUPPORTED: NrosPlatformRet = -5;
316
317// ============================================================================
318// CffiPlatform — trait impls dispatching to the linked C symbols
319// ============================================================================
320
321/// Zero-sized type implementing the platform traits via the canonical
322/// `nros_platform_*` C symbols.
323///
324/// The crate that pulls `CffiPlatform` into a final binary is
325/// responsible for ensuring the symbols are supplied at link time
326/// (either by a C translation unit or a Rust `-cffi` shim crate).
327pub struct CffiPlatform;
328
329impl nros_platform_api::PlatformClock for CffiPlatform {
330    #[inline]
331    fn clock_ms() -> u64 {
332        unsafe { nros_platform_clock_ms() }
333    }
334
335    #[inline]
336    fn clock_us() -> u64 {
337        unsafe { nros_platform_clock_us() }
338    }
339}
340
341impl nros_platform_api::PlatformAlloc for CffiPlatform {
342    #[inline]
343    fn alloc(size: usize) -> *mut c_void {
344        unsafe { nros_platform_alloc(size) }
345    }
346
347    #[inline]
348    fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
349        unsafe { nros_platform_realloc(ptr, size) }
350    }
351
352    #[inline]
353    fn dealloc(ptr: *mut c_void) {
354        unsafe { nros_platform_dealloc(ptr) }
355    }
356
357    #[inline]
358    fn heap_used_bytes() -> usize {
359        unsafe { nros_platform_heap_used_bytes() }
360    }
361
362    #[inline]
363    fn heap_total_bytes() -> usize {
364        unsafe { nros_platform_heap_total_bytes() }
365    }
366}
367
368impl nros_platform_api::PlatformSleep for CffiPlatform {
369    #[inline]
370    fn sleep_us(us: usize) {
371        unsafe { nros_platform_sleep_us(us) }
372    }
373
374    #[inline]
375    fn sleep_ms(ms: usize) {
376        unsafe { nros_platform_sleep_ms(ms) }
377    }
378
379    #[inline]
380    fn sleep_s(s: usize) {
381        unsafe { nros_platform_sleep_s(s) }
382    }
383}
384
385impl nros_platform_api::PlatformYield for CffiPlatform {
386    #[inline]
387    fn yield_now() {
388        unsafe { nros_platform_yield_now() }
389    }
390}
391
392// Phase 110.D — `PlatformScheduler` is satisfied by the existing yield
393// symbol; per-thread scheduling controls land when a C consumer needs
394// hard-RT preemption.
395impl nros_platform_api::PlatformScheduler for CffiPlatform {
396    #[inline]
397    fn yield_now() {
398        unsafe { nros_platform_yield_now() }
399    }
400}
401
402// Phase 110.E.b — `PlatformTimer` dispatches to the
403// `nros_platform_timer_*` C ABI declared above. Backed by
404// `nros-platform-posix/src/timer.c` on POSIX (POSIX `timer_create` +
405// `SIGEV_THREAD` trampoline); each RTOS port supplies its own
406// `timer.c` mirroring the canonical signatures.
407//
408// `TimerHandle` is a `*mut c_void` newtype so the trait's
409// `Send + Sync` bound holds. Safety: the C layer owns the heap
410// record behind the pointer; Rust just shuttles the opaque handle
411// between `create_*` and `destroy` / `cancel`.
412#[derive(Debug)]
413pub struct CffiTimerHandle(*mut c_void);
414
415// SAFETY: the underlying `*mut c_void` is an opaque platform-owned
416// handle (POSIX `timer_t` wrapped in a heap record, FreeRTOS
417// `TimerHandle_t`, etc.). The Rust side never dereferences it; the
418// only operations are forwarding it back to `destroy` / `cancel`.
419// Send + Sync are required by the trait so the executor can stash
420// the handle across thread boundaries.
421unsafe impl Send for CffiTimerHandle {}
422unsafe impl Sync for CffiTimerHandle {}
423
424impl nros_platform_api::PlatformTimer for CffiPlatform {
425    type TimerHandle = CffiTimerHandle;
426
427    fn create_periodic(
428        period_us: u32,
429        callback: extern "C" fn(*mut c_void),
430        user_data: *mut c_void,
431    ) -> Result<Self::TimerHandle, nros_platform_api::TimerError> {
432        // `extern "C" fn` coerces structurally to `unsafe extern "C"
433        // fn` — both have the same ABI; Rust just demands the unsafe
434        // version at the C call site.
435        let cb: unsafe extern "C" fn(*mut c_void) = callback;
436        let raw = unsafe { nros_platform_timer_create_periodic(period_us, cb, user_data) };
437        if raw.is_null() {
438            // The C layer returns NULL for both "unsupported on this
439            // platform" (default stub) and "syscall failed" (POSIX
440            // EINVAL / kernel error). The runtime treats both the
441            // same way (drop back to the polled-clock fallback), so
442            // surface `KernelError` to differentiate from the
443            // trait-default `Unsupported` that fires when the C
444            // symbol isn't linked at all.
445            return Err(nros_platform_api::TimerError::KernelError);
446        }
447        Ok(CffiTimerHandle(raw))
448    }
449
450    fn create_oneshot(
451        timeout_us: u32,
452        callback: extern "C" fn(*mut c_void),
453        user_data: *mut c_void,
454    ) -> Result<Self::TimerHandle, nros_platform_api::TimerError> {
455        let cb: unsafe extern "C" fn(*mut c_void) = callback;
456        let raw = unsafe { nros_platform_timer_create_oneshot(timeout_us, cb, user_data) };
457        if raw.is_null() {
458            return Err(nros_platform_api::TimerError::KernelError);
459        }
460        Ok(CffiTimerHandle(raw))
461    }
462
463    fn destroy(handle: Self::TimerHandle) {
464        unsafe { nros_platform_timer_destroy(handle.0) }
465    }
466
467    fn cancel(handle: &mut Self::TimerHandle) -> bool {
468        let rc = unsafe { nros_platform_timer_cancel(handle.0) };
469        // `1` = cancellation prevented the callback from firing;
470        // `0` / `-1` = already fired (or error — treated as "not
471        // cancelled in time" by the caller).
472        rc == 1
473    }
474}
475
476impl nros_platform_api::PlatformRandom for CffiPlatform {
477    #[inline]
478    fn random_u8() -> u8 {
479        unsafe { nros_platform_random_u8() }
480    }
481
482    #[inline]
483    fn random_u16() -> u16 {
484        unsafe { nros_platform_random_u16() }
485    }
486
487    #[inline]
488    fn random_u32() -> u32 {
489        unsafe { nros_platform_random_u32() }
490    }
491
492    #[inline]
493    fn random_u64() -> u64 {
494        unsafe { nros_platform_random_u64() }
495    }
496
497    #[inline]
498    fn random_fill(buf: *mut c_void, len: usize) {
499        unsafe { nros_platform_random_fill(buf, len) }
500    }
501}
502
503impl nros_platform_api::PlatformTime for CffiPlatform {
504    #[inline]
505    fn time_now_ms() -> u64 {
506        unsafe { nros_platform_time_now_ms() }
507    }
508
509    #[inline]
510    fn time_since_epoch_secs() -> u32 {
511        unsafe { nros_platform_time_since_epoch_secs() }
512    }
513
514    #[inline]
515    fn time_since_epoch_nanos() -> u32 {
516        unsafe { nros_platform_time_since_epoch_nanos() }
517    }
518}
519
520impl nros_platform_api::PlatformThreading for CffiPlatform {
521    fn task_init(
522        task: *mut c_void,
523        attr: *mut c_void,
524        entry: Option<unsafe extern "C" fn(*mut c_void) -> *mut c_void>,
525        arg: *mut c_void,
526    ) -> i8 {
527        unsafe { nros_platform_task_init(task, attr, entry, arg) }
528    }
529    fn task_join(task: *mut c_void) -> i8 {
530        unsafe { nros_platform_task_join(task) }
531    }
532    fn task_detach(task: *mut c_void) -> i8 {
533        unsafe { nros_platform_task_detach(task) }
534    }
535    fn task_cancel(task: *mut c_void) -> i8 {
536        unsafe { nros_platform_task_cancel(task) }
537    }
538    fn task_exit() {
539        unsafe { nros_platform_task_exit() }
540    }
541    fn task_free(task: *mut *mut c_void) {
542        unsafe { nros_platform_task_free(task) }
543    }
544    fn mutex_init(m: *mut c_void) -> i8 {
545        unsafe { nros_platform_mutex_init(m) }
546    }
547    fn mutex_drop(m: *mut c_void) -> i8 {
548        unsafe { nros_platform_mutex_drop(m) }
549    }
550    fn mutex_lock(m: *mut c_void) -> i8 {
551        unsafe { nros_platform_mutex_lock(m) }
552    }
553    fn mutex_try_lock(m: *mut c_void) -> i8 {
554        unsafe { nros_platform_mutex_try_lock(m) }
555    }
556    fn mutex_unlock(m: *mut c_void) -> i8 {
557        unsafe { nros_platform_mutex_unlock(m) }
558    }
559    fn mutex_rec_init(m: *mut c_void) -> i8 {
560        unsafe { nros_platform_mutex_rec_init(m) }
561    }
562    fn mutex_rec_drop(m: *mut c_void) -> i8 {
563        unsafe { nros_platform_mutex_rec_drop(m) }
564    }
565    fn mutex_rec_lock(m: *mut c_void) -> i8 {
566        unsafe { nros_platform_mutex_rec_lock(m) }
567    }
568    fn mutex_rec_try_lock(m: *mut c_void) -> i8 {
569        unsafe { nros_platform_mutex_rec_try_lock(m) }
570    }
571    fn mutex_rec_unlock(m: *mut c_void) -> i8 {
572        unsafe { nros_platform_mutex_rec_unlock(m) }
573    }
574    fn condvar_init(cv: *mut c_void) -> i8 {
575        unsafe { nros_platform_condvar_init(cv) }
576    }
577    fn condvar_drop(cv: *mut c_void) -> i8 {
578        unsafe { nros_platform_condvar_drop(cv) }
579    }
580    fn condvar_signal(cv: *mut c_void) -> i8 {
581        unsafe { nros_platform_condvar_signal(cv) }
582    }
583    fn condvar_signal_all(cv: *mut c_void) -> i8 {
584        unsafe { nros_platform_condvar_signal_all(cv) }
585    }
586    fn condvar_signal_from_isr(cv: *mut c_void) -> i8 {
587        unsafe { nros_platform_condvar_signal_from_isr(cv) }
588    }
589    fn condvar_wait(cv: *mut c_void, m: *mut c_void) -> i8 {
590        unsafe { nros_platform_condvar_wait(cv, m) }
591    }
592    fn condvar_wait_until(cv: *mut c_void, m: *mut c_void, abstime: u64) -> i8 {
593        unsafe { nros_platform_condvar_wait_until(cv, m, abstime) }
594    }
595    fn wake_init(w: *mut c_void) -> i8 {
596        unsafe { nros_platform_wake_init(w) }
597    }
598    fn wake_drop(w: *mut c_void) -> i8 {
599        unsafe { nros_platform_wake_drop(w) }
600    }
601    fn wake_wait_ms(w: *mut c_void, timeout_ms: u32) -> i8 {
602        unsafe { nros_platform_wake_wait_ms(w, timeout_ms) }
603    }
604    fn wake_signal(w: *mut c_void) -> i8 {
605        unsafe { nros_platform_wake_signal(w) }
606    }
607    fn wake_signal_from_isr(w: *mut c_void) -> i8 {
608        unsafe { nros_platform_wake_signal_from_isr(w) }
609    }
610    fn wake_storage_size() -> usize {
611        unsafe { nros_platform_wake_storage_size() }
612    }
613    fn wake_storage_align() -> usize {
614        unsafe { nros_platform_wake_storage_align() }
615    }
616}
617
618// ============================================================================
619// Phase 121.3.deprecate-rust-migrate — extended-surface trait impls
620// ----------------------------------------------------------------------------
621// CffiPlatform dispatches PlatformTcp / PlatformUdp / PlatformUdpMulticast /
622// PlatformSocketHelpers / PlatformNetworkPoll trait calls through the
623// `unsafe extern "C"` declarations above. Whichever provider supplies the
624// matching symbol set (a per-RTOS Rust crate with `cffi-export` on, or a
625// hand-written C port) backs the dispatch transparently.
626// ============================================================================
627
628impl nros_platform_api::PlatformTcp for CffiPlatform {
629    fn create_endpoint(ep: *mut c_void, address: *const u8, port: *const u8) -> i8 {
630        unsafe { nros_platform_tcp_create_endpoint(ep, address, port) }
631    }
632    fn free_endpoint(ep: *mut c_void) {
633        unsafe { nros_platform_tcp_free_endpoint(ep) }
634    }
635    fn open(sock: *mut c_void, endpoint: *const c_void, timeout_ms: u32) -> i8 {
636        unsafe { nros_platform_tcp_open(sock, endpoint, timeout_ms) }
637    }
638    fn listen(sock: *mut c_void, endpoint: *const c_void) -> i8 {
639        unsafe { nros_platform_tcp_listen(sock, endpoint) }
640    }
641    fn close(sock: *mut c_void) {
642        unsafe { nros_platform_tcp_close(sock) }
643    }
644    fn read(sock: *const c_void, buf: *mut u8, len: usize) -> usize {
645        unsafe { nros_platform_tcp_read(sock, buf, len) }
646    }
647    fn read_exact(sock: *const c_void, buf: *mut u8, len: usize) -> usize {
648        unsafe { nros_platform_tcp_read_exact(sock, buf, len) }
649    }
650    fn send(sock: *const c_void, buf: *const u8, len: usize) -> usize {
651        unsafe { nros_platform_tcp_send(sock, buf, len) }
652    }
653}
654
655impl nros_platform_api::PlatformUdp for CffiPlatform {
656    fn create_endpoint(ep: *mut c_void, address: *const u8, port: *const u8) -> i8 {
657        unsafe { nros_platform_udp_create_endpoint(ep, address, port) }
658    }
659    fn free_endpoint(ep: *mut c_void) {
660        unsafe { nros_platform_udp_free_endpoint(ep) }
661    }
662    fn open(sock: *mut c_void, endpoint: *const c_void, timeout_ms: u32) -> i8 {
663        unsafe { nros_platform_udp_open(sock, endpoint, timeout_ms) }
664    }
665    fn listen(sock: *mut c_void, endpoint: *const c_void, timeout_ms: u32) -> i8 {
666        unsafe { nros_platform_udp_listen(sock, endpoint, timeout_ms) }
667    }
668    fn close(sock: *mut c_void) {
669        unsafe { nros_platform_udp_close(sock) }
670    }
671    fn read(sock: *const c_void, buf: *mut u8, len: usize) -> usize {
672        unsafe { nros_platform_udp_read(sock, buf, len) }
673    }
674    fn read_exact(sock: *const c_void, buf: *mut u8, len: usize) -> usize {
675        unsafe { nros_platform_udp_read_exact(sock, buf, len) }
676    }
677    fn send(sock: *const c_void, buf: *const u8, len: usize, endpoint: *const c_void) -> usize {
678        unsafe { nros_platform_udp_send(sock, buf, len, endpoint) }
679    }
680    fn set_recv_timeout(sock: *const c_void, timeout_ms: u32) {
681        unsafe { nros_platform_udp_set_recv_timeout(sock, timeout_ms) }
682    }
683}
684
685impl nros_platform_api::PlatformUdpMulticast for CffiPlatform {
686    fn mcast_open(
687        sock: *mut c_void,
688        endpoint: *const c_void,
689        lep: *mut c_void,
690        timeout_ms: u32,
691        iface: *const u8,
692    ) -> i8 {
693        unsafe { nros_platform_udp_mcast_open(sock, endpoint, lep, timeout_ms, iface) }
694    }
695    fn mcast_listen(
696        sock: *mut c_void,
697        endpoint: *const c_void,
698        timeout_ms: u32,
699        iface: *const u8,
700        join: *const u8,
701    ) -> i8 {
702        unsafe { nros_platform_udp_mcast_listen(sock, endpoint, timeout_ms, iface, join) }
703    }
704    fn mcast_close(
705        sockrecv: *mut c_void,
706        socksend: *mut c_void,
707        rep: *const c_void,
708        lep: *const c_void,
709    ) {
710        unsafe { nros_platform_udp_mcast_close(sockrecv, socksend, rep, lep) }
711    }
712    fn mcast_read(
713        sock: *const c_void,
714        buf: *mut u8,
715        len: usize,
716        lep: *const c_void,
717        addr: *mut c_void,
718    ) -> usize {
719        unsafe { nros_platform_udp_mcast_read(sock, buf, len, lep, addr) }
720    }
721    fn mcast_read_exact(
722        sock: *const c_void,
723        buf: *mut u8,
724        len: usize,
725        lep: *const c_void,
726        addr: *mut c_void,
727    ) -> usize {
728        unsafe { nros_platform_udp_mcast_read_exact(sock, buf, len, lep, addr) }
729    }
730    fn mcast_send(
731        sock: *const c_void,
732        buf: *const u8,
733        len: usize,
734        endpoint: *const c_void,
735    ) -> usize {
736        unsafe { nros_platform_udp_mcast_send(sock, buf, len, endpoint) }
737    }
738}
739
740impl nros_platform_api::PlatformSocketHelpers for CffiPlatform {
741    fn set_non_blocking(sock: *const c_void) -> i8 {
742        unsafe { nros_platform_socket_set_non_blocking(sock) }
743    }
744    fn accept(sock_in: *const c_void, sock_out: *mut c_void) -> i8 {
745        unsafe { nros_platform_socket_accept(sock_in, sock_out) }
746    }
747    fn close(sock: *mut c_void) {
748        unsafe { nros_platform_socket_close(sock) }
749    }
750    fn wait_event(peers: *mut c_void, mutex: *mut c_void) -> i8 {
751        unsafe { nros_platform_socket_wait_event(peers, mutex) }
752    }
753}
754
755impl nros_platform_api::PlatformNetworkPoll for CffiPlatform {
756    fn network_poll() {
757        unsafe { nros_platform_network_poll() }
758    }
759}
760
761impl nros_platform_api::PlatformCriticalSection for CffiPlatform {
762    fn acquire() -> u32 {
763        unsafe { nros_platform_critical_section_acquire() }
764    }
765    fn release(token: u32) {
766        unsafe { nros_platform_critical_section_release(token) }
767    }
768}
769
770impl nros_platform_api::PlatformLog for CffiPlatform {
771    fn write(severity: u8, name: &[u8], message: &[u8]) {
772        // SAFETY: extern decl matches the C ABI byte-for-byte; the
773        // pointer/length pairs come from `&[u8]` references that
774        // outlive the call.
775        unsafe {
776            nros_platform_log_write(
777                severity,
778                name.as_ptr(),
779                name.len(),
780                message.as_ptr(),
781                message.len(),
782            );
783        }
784    }
785
786    fn flush() {
787        // SAFETY: no args, no preconditions.
788        unsafe { nros_platform_log_flush() };
789    }
790}
791
792// ============================================================================
793// Phase 121.2 — export_*! macros
794// ----------------------------------------------------------------------------
795// Each macro emits the `#[unsafe(no_mangle)] extern "C"` definitions for one
796// capability group. The macro callee must implement the matching
797// `nros_platform_api::Platform*` trait; the trait bound is checked at the
798// macro-expansion site, so a missing impl produces a clear compile error in
799// the caller crate.
800//
801// Naming the symbols exactly matches `<nros/platform.h>`. Add a new ABI
802// symbol in three coordinated places, all inside this crate:
803//   1. declare it in `include/nros/platform.h`,
804//   2. declare it in the `unsafe extern "C" { … }` block above,
805//   3. emit it from the appropriate `export_*!` macro below.
806// ============================================================================
807
808/// Emit `nros_platform_clock_{ms,us}` delegating to
809/// `<$ty as PlatformClock>`.
810#[macro_export]
811macro_rules! nros_platform_export_clock {
812    ($ty:ty) => {
813        #[unsafe(no_mangle)]
814        pub extern "C" fn nros_platform_clock_ms() -> u64 {
815            <$ty as ::nros_platform_api::PlatformClock>::clock_ms()
816        }
817        #[unsafe(no_mangle)]
818        pub extern "C" fn nros_platform_clock_us() -> u64 {
819            <$ty as ::nros_platform_api::PlatformClock>::clock_us()
820        }
821    };
822}
823
824/// Emit `nros_platform_{alloc,realloc,dealloc}` delegating to
825/// `<$ty as PlatformAlloc>`.
826#[macro_export]
827macro_rules! nros_platform_export_alloc {
828    ($ty:ty) => {
829        #[unsafe(no_mangle)]
830        pub extern "C" fn nros_platform_alloc(size: usize) -> *mut ::core::ffi::c_void {
831            <$ty as ::nros_platform_api::PlatformAlloc>::alloc(size)
832        }
833        #[unsafe(no_mangle)]
834        pub extern "C" fn nros_platform_realloc(
835            ptr: *mut ::core::ffi::c_void,
836            size: usize,
837        ) -> *mut ::core::ffi::c_void {
838            <$ty as ::nros_platform_api::PlatformAlloc>::realloc(ptr, size)
839        }
840        #[unsafe(no_mangle)]
841        pub extern "C" fn nros_platform_dealloc(ptr: *mut ::core::ffi::c_void) {
842            <$ty as ::nros_platform_api::PlatformAlloc>::dealloc(ptr)
843        }
844        #[unsafe(no_mangle)]
845        pub extern "C" fn nros_platform_heap_used_bytes() -> usize {
846            <$ty as ::nros_platform_api::PlatformAlloc>::heap_used_bytes()
847        }
848        #[unsafe(no_mangle)]
849        pub extern "C" fn nros_platform_heap_total_bytes() -> usize {
850            <$ty as ::nros_platform_api::PlatformAlloc>::heap_total_bytes()
851        }
852    };
853}
854
855/// Emit `nros_platform_sleep_{us,ms,s}` delegating to
856/// `<$ty as PlatformSleep>`.
857#[macro_export]
858macro_rules! nros_platform_export_sleep {
859    ($ty:ty) => {
860        #[unsafe(no_mangle)]
861        pub extern "C" fn nros_platform_sleep_us(us: usize) {
862            <$ty as ::nros_platform_api::PlatformSleep>::sleep_us(us)
863        }
864        #[unsafe(no_mangle)]
865        pub extern "C" fn nros_platform_sleep_ms(ms: usize) {
866            <$ty as ::nros_platform_api::PlatformSleep>::sleep_ms(ms)
867        }
868        #[unsafe(no_mangle)]
869        pub extern "C" fn nros_platform_sleep_s(s: usize) {
870            <$ty as ::nros_platform_api::PlatformSleep>::sleep_s(s)
871        }
872    };
873}
874
875/// Emit `nros_platform_yield_now` delegating to
876/// `<$ty as PlatformYield>`.
877#[macro_export]
878macro_rules! nros_platform_export_yield {
879    ($ty:ty) => {
880        #[unsafe(no_mangle)]
881        pub extern "C" fn nros_platform_yield_now() {
882            <$ty as ::nros_platform_api::PlatformYield>::yield_now()
883        }
884    };
885}
886
887/// Emit `nros_platform_random_*` delegating to `<$ty as PlatformRandom>`.
888#[macro_export]
889macro_rules! nros_platform_export_random {
890    ($ty:ty) => {
891        #[unsafe(no_mangle)]
892        pub extern "C" fn nros_platform_random_u8() -> u8 {
893            <$ty as ::nros_platform_api::PlatformRandom>::random_u8()
894        }
895        #[unsafe(no_mangle)]
896        pub extern "C" fn nros_platform_random_u16() -> u16 {
897            <$ty as ::nros_platform_api::PlatformRandom>::random_u16()
898        }
899        #[unsafe(no_mangle)]
900        pub extern "C" fn nros_platform_random_u32() -> u32 {
901            <$ty as ::nros_platform_api::PlatformRandom>::random_u32()
902        }
903        #[unsafe(no_mangle)]
904        pub extern "C" fn nros_platform_random_u64() -> u64 {
905            <$ty as ::nros_platform_api::PlatformRandom>::random_u64()
906        }
907        #[unsafe(no_mangle)]
908        pub extern "C" fn nros_platform_random_fill(buf: *mut ::core::ffi::c_void, len: usize) {
909            <$ty as ::nros_platform_api::PlatformRandom>::random_fill(buf, len)
910        }
911    };
912}
913
914/// Emit `nros_platform_time_*` delegating to `<$ty as PlatformTime>`.
915#[macro_export]
916macro_rules! nros_platform_export_time {
917    ($ty:ty) => {
918        #[unsafe(no_mangle)]
919        pub extern "C" fn nros_platform_time_now_ms() -> u64 {
920            <$ty as ::nros_platform_api::PlatformTime>::time_now_ms()
921        }
922        #[unsafe(no_mangle)]
923        pub extern "C" fn nros_platform_time_since_epoch_secs() -> u32 {
924            <$ty as ::nros_platform_api::PlatformTime>::time_since_epoch_secs()
925        }
926        #[unsafe(no_mangle)]
927        pub extern "C" fn nros_platform_time_since_epoch_nanos() -> u32 {
928            <$ty as ::nros_platform_api::PlatformTime>::time_since_epoch_nanos()
929        }
930    };
931}
932
933/// Emit `nros_platform_task_*`, `nros_platform_mutex_*`,
934/// `nros_platform_mutex_rec_*`, and `nros_platform_condvar_*` delegating
935/// to `<$ty as PlatformThreading>`. Skip this macro on platforms without
936/// kernel threads.
937#[macro_export]
938macro_rules! nros_platform_export_threading {
939    ($ty:ty) => {
940        #[unsafe(no_mangle)]
941        pub extern "C" fn nros_platform_task_init(
942            task: *mut ::core::ffi::c_void,
943            attr: *mut ::core::ffi::c_void,
944            entry: ::core::option::Option<
945                unsafe extern "C" fn(*mut ::core::ffi::c_void) -> *mut ::core::ffi::c_void,
946            >,
947            arg: *mut ::core::ffi::c_void,
948        ) -> i8 {
949            <$ty as ::nros_platform_api::PlatformThreading>::task_init(task, attr, entry, arg)
950        }
951        #[unsafe(no_mangle)]
952        pub extern "C" fn nros_platform_task_join(task: *mut ::core::ffi::c_void) -> i8 {
953            <$ty as ::nros_platform_api::PlatformThreading>::task_join(task)
954        }
955        #[unsafe(no_mangle)]
956        pub extern "C" fn nros_platform_task_detach(task: *mut ::core::ffi::c_void) -> i8 {
957            <$ty as ::nros_platform_api::PlatformThreading>::task_detach(task)
958        }
959        #[unsafe(no_mangle)]
960        pub extern "C" fn nros_platform_task_cancel(task: *mut ::core::ffi::c_void) -> i8 {
961            <$ty as ::nros_platform_api::PlatformThreading>::task_cancel(task)
962        }
963        #[unsafe(no_mangle)]
964        pub extern "C" fn nros_platform_task_exit() {
965            <$ty as ::nros_platform_api::PlatformThreading>::task_exit()
966        }
967        #[unsafe(no_mangle)]
968        pub extern "C" fn nros_platform_task_free(task: *mut *mut ::core::ffi::c_void) {
969            <$ty as ::nros_platform_api::PlatformThreading>::task_free(task)
970        }
971        #[unsafe(no_mangle)]
972        pub extern "C" fn nros_platform_mutex_init(m: *mut ::core::ffi::c_void) -> i8 {
973            <$ty as ::nros_platform_api::PlatformThreading>::mutex_init(m)
974        }
975        #[unsafe(no_mangle)]
976        pub extern "C" fn nros_platform_mutex_drop(m: *mut ::core::ffi::c_void) -> i8 {
977            <$ty as ::nros_platform_api::PlatformThreading>::mutex_drop(m)
978        }
979        #[unsafe(no_mangle)]
980        pub extern "C" fn nros_platform_mutex_lock(m: *mut ::core::ffi::c_void) -> i8 {
981            <$ty as ::nros_platform_api::PlatformThreading>::mutex_lock(m)
982        }
983        #[unsafe(no_mangle)]
984        pub extern "C" fn nros_platform_mutex_try_lock(m: *mut ::core::ffi::c_void) -> i8 {
985            <$ty as ::nros_platform_api::PlatformThreading>::mutex_try_lock(m)
986        }
987        #[unsafe(no_mangle)]
988        pub extern "C" fn nros_platform_mutex_unlock(m: *mut ::core::ffi::c_void) -> i8 {
989            <$ty as ::nros_platform_api::PlatformThreading>::mutex_unlock(m)
990        }
991        #[unsafe(no_mangle)]
992        pub extern "C" fn nros_platform_mutex_rec_init(m: *mut ::core::ffi::c_void) -> i8 {
993            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_init(m)
994        }
995        #[unsafe(no_mangle)]
996        pub extern "C" fn nros_platform_mutex_rec_drop(m: *mut ::core::ffi::c_void) -> i8 {
997            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_drop(m)
998        }
999        #[unsafe(no_mangle)]
1000        pub extern "C" fn nros_platform_mutex_rec_lock(m: *mut ::core::ffi::c_void) -> i8 {
1001            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_lock(m)
1002        }
1003        #[unsafe(no_mangle)]
1004        pub extern "C" fn nros_platform_mutex_rec_try_lock(m: *mut ::core::ffi::c_void) -> i8 {
1005            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_try_lock(m)
1006        }
1007        #[unsafe(no_mangle)]
1008        pub extern "C" fn nros_platform_mutex_rec_unlock(m: *mut ::core::ffi::c_void) -> i8 {
1009            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_unlock(m)
1010        }
1011        #[unsafe(no_mangle)]
1012        pub extern "C" fn nros_platform_condvar_init(cv: *mut ::core::ffi::c_void) -> i8 {
1013            <$ty as ::nros_platform_api::PlatformThreading>::condvar_init(cv)
1014        }
1015        #[unsafe(no_mangle)]
1016        pub extern "C" fn nros_platform_condvar_drop(cv: *mut ::core::ffi::c_void) -> i8 {
1017            <$ty as ::nros_platform_api::PlatformThreading>::condvar_drop(cv)
1018        }
1019        #[unsafe(no_mangle)]
1020        pub extern "C" fn nros_platform_condvar_signal(cv: *mut ::core::ffi::c_void) -> i8 {
1021            <$ty as ::nros_platform_api::PlatformThreading>::condvar_signal(cv)
1022        }
1023        #[unsafe(no_mangle)]
1024        pub extern "C" fn nros_platform_condvar_signal_all(cv: *mut ::core::ffi::c_void) -> i8 {
1025            <$ty as ::nros_platform_api::PlatformThreading>::condvar_signal_all(cv)
1026        }
1027        #[unsafe(no_mangle)]
1028        pub extern "C" fn nros_platform_condvar_signal_from_isr(
1029            cv: *mut ::core::ffi::c_void,
1030        ) -> i8 {
1031            <$ty as ::nros_platform_api::PlatformThreading>::condvar_signal_from_isr(cv)
1032        }
1033        #[unsafe(no_mangle)]
1034        pub extern "C" fn nros_platform_condvar_wait(
1035            cv: *mut ::core::ffi::c_void,
1036            m: *mut ::core::ffi::c_void,
1037        ) -> i8 {
1038            <$ty as ::nros_platform_api::PlatformThreading>::condvar_wait(cv, m)
1039        }
1040        #[unsafe(no_mangle)]
1041        pub extern "C" fn nros_platform_condvar_wait_until(
1042            cv: *mut ::core::ffi::c_void,
1043            m: *mut ::core::ffi::c_void,
1044            abstime: u64,
1045        ) -> i8 {
1046            <$ty as ::nros_platform_api::PlatformThreading>::condvar_wait_until(cv, m, abstime)
1047        }
1048        // Phase 130 — wake primitive (binary semaphore shape).
1049        #[unsafe(no_mangle)]
1050        pub extern "C" fn nros_platform_wake_init(w: *mut ::core::ffi::c_void) -> i8 {
1051            <$ty as ::nros_platform_api::PlatformThreading>::wake_init(w)
1052        }
1053        #[unsafe(no_mangle)]
1054        pub extern "C" fn nros_platform_wake_drop(w: *mut ::core::ffi::c_void) -> i8 {
1055            <$ty as ::nros_platform_api::PlatformThreading>::wake_drop(w)
1056        }
1057        #[unsafe(no_mangle)]
1058        pub extern "C" fn nros_platform_wake_wait_ms(
1059            w: *mut ::core::ffi::c_void,
1060            timeout_ms: u32,
1061        ) -> i8 {
1062            <$ty as ::nros_platform_api::PlatformThreading>::wake_wait_ms(w, timeout_ms)
1063        }
1064        #[unsafe(no_mangle)]
1065        pub extern "C" fn nros_platform_wake_signal(w: *mut ::core::ffi::c_void) -> i8 {
1066            <$ty as ::nros_platform_api::PlatformThreading>::wake_signal(w)
1067        }
1068        #[unsafe(no_mangle)]
1069        pub extern "C" fn nros_platform_wake_signal_from_isr(w: *mut ::core::ffi::c_void) -> i8 {
1070            <$ty as ::nros_platform_api::PlatformThreading>::wake_signal_from_isr(w)
1071        }
1072        #[unsafe(no_mangle)]
1073        pub extern "C" fn nros_platform_wake_storage_size() -> usize {
1074            <$ty as ::nros_platform_api::PlatformThreading>::wake_storage_size()
1075        }
1076        #[unsafe(no_mangle)]
1077        pub extern "C" fn nros_platform_wake_storage_align() -> usize {
1078            <$ty as ::nros_platform_api::PlatformThreading>::wake_storage_align()
1079        }
1080    };
1081}
1082
1083/// Phase 121.9 — emit the two `nros_platform_critical_section_*`
1084/// symbols by delegating to the caller's `PlatformCriticalSection`
1085/// impl.
1086#[macro_export]
1087macro_rules! nros_platform_export_critical_section {
1088    ($ty:ty) => {
1089        #[unsafe(no_mangle)]
1090        pub extern "C" fn nros_platform_critical_section_acquire() -> u32 {
1091            <$ty as ::nros_platform_api::PlatformCriticalSection>::acquire()
1092        }
1093        #[unsafe(no_mangle)]
1094        pub extern "C" fn nros_platform_critical_section_release(token: u32) {
1095            <$ty as ::nros_platform_api::PlatformCriticalSection>::release(token)
1096        }
1097    };
1098}
1099
1100/// Phase 88.11 — emit `nros_platform_log_write` + `nros_platform_log_flush`
1101/// from a `PlatformLog`-implementing ZST. Use this on bare-metal /
1102/// custom platforms (mps2-an385, stm32f4, esp32-baremetal, …) that
1103/// don't ship a separate C implementation file. The implementor's
1104/// `write` receives the rendered body + logger name as `&[u8]` slices.
1105#[macro_export]
1106macro_rules! nros_platform_export_log {
1107    ($ty:ty) => {
1108        #[unsafe(no_mangle)]
1109        pub extern "C" fn nros_platform_log_write(
1110            severity: u8,
1111            name_ptr: *const u8,
1112            name_len: usize,
1113            msg_ptr: *const u8,
1114            msg_len: usize,
1115        ) {
1116            // SAFETY: caller passes valid `&[u8]` slices that outlive
1117            // the call; empty-name case (name_ptr=null, name_len=0)
1118            // collapses to an empty slice.
1119            let name: &[u8] = if name_ptr.is_null() || name_len == 0 {
1120                &[]
1121            } else {
1122                unsafe { ::core::slice::from_raw_parts(name_ptr, name_len) }
1123            };
1124            let msg: &[u8] = if msg_ptr.is_null() || msg_len == 0 {
1125                &[]
1126            } else {
1127                unsafe { ::core::slice::from_raw_parts(msg_ptr, msg_len) }
1128            };
1129            <$ty as ::nros_platform_api::PlatformLog>::write(severity, name, msg);
1130        }
1131        #[unsafe(no_mangle)]
1132        pub extern "C" fn nros_platform_log_flush() {
1133            <$ty as ::nros_platform_api::PlatformLog>::flush()
1134        }
1135        /// Phase 88.16.H — ABI-mirror parity. Direct-impl
1136        /// platforms (`mps2-an385`, `stm32f4`, …) route every
1137        /// record through `PlatformLog::write`, so the runtime
1138        /// swap slot is meaningless to them. The header mirror
1139        /// nonetheless declares `nros_platform_register_log_writer`,
1140        /// so the macro emits a no-op stub to satisfy the
1141        /// ABI-mirror check. Fn-ptr-slot platforms (FreeRTOS /
1142        /// ThreadX / NuttX) don't call this macro — their C body
1143        /// ships the real strong definition.
1144        #[unsafe(no_mangle)]
1145        pub extern "C" fn nros_platform_register_log_writer(
1146            _writer: ::core::option::Option<
1147                unsafe extern "C" fn(
1148                    severity: u8,
1149                    name_ptr: *const u8,
1150                    name_len: usize,
1151                    msg_ptr: *const u8,
1152                    msg_len: usize,
1153                ),
1154            >,
1155            _flusher: ::core::option::Option<unsafe extern "C" fn()>,
1156        ) {
1157        }
1158    };
1159}
1160
1161/// Convenience: emit every `nros_platform_*` symbol declared in
1162/// `<nros/platform.h>` by delegating to the corresponding
1163/// `nros_platform_api::Platform*` trait method on `$ty`. The caller must
1164/// implement every trait covered by the capability macros.
1165///
1166/// Logging (`nros_platform_export_log!`) is NOT part of this convenience
1167/// macro: bare-metal platforms typically need to supply a writer
1168/// (`hprintln!` / `defmt::info!`) that requires extra deps not all
1169/// platforms link against. Call `nros_platform_export_log!` separately
1170/// after the platform crate implements `PlatformLog`.
1171#[macro_export]
1172macro_rules! nros_platform_export {
1173    ($ty:ty) => {
1174        $crate::nros_platform_export_clock!($ty);
1175        $crate::nros_platform_export_alloc!($ty);
1176        $crate::nros_platform_export_sleep!($ty);
1177        $crate::nros_platform_export_yield!($ty);
1178        $crate::nros_platform_export_random!($ty);
1179        $crate::nros_platform_export_time!($ty);
1180        $crate::nros_platform_export_threading!($ty);
1181        $crate::nros_platform_export_critical_section!($ty);
1182    };
1183}
1184
1185// ============================================================================
1186// Phase 121.6.macros — extended-surface export macros
1187// ----------------------------------------------------------------------------
1188// `nros_platform_export_net!` mirrors `<nros/platform_net.h>` 1:1; trait
1189// signatures match the C ABI byte-for-byte. `nros_platform_export_timer!`
1190// adapts the Rust `PlatformTimer` trait's `Result<TimerHandle, _>` to the
1191// C ABI's `*mut c_void` (NULL on error). The caller's `TimerHandle`
1192// associated type must be `*mut c_void` — enforced at macro-expansion
1193// time via a `where` clause on the emitted dispatch functions.
1194// ============================================================================
1195
1196/// Emit every `nros_platform_timer_*` symbol declared in
1197/// `<nros/platform_timer.h>` by delegating to the corresponding
1198/// `PlatformTimer` trait method on `$ty`.
1199///
1200/// **Constraint:** the implementor's `TimerHandle` associated type
1201/// must be `*mut core::ffi::c_void` so the macro can pass the handle
1202/// through the C ABI unchanged. Implementations using kernel-specific
1203/// handle types should wrap them in a `*mut c_void` (typically by
1204/// `Box::into_raw` + a thin newtype) before exporting.
1205#[macro_export]
1206macro_rules! nros_platform_export_timer {
1207    ($ty:ty) => {
1208        // Compile-time guard: handle must be pointer-sized so the
1209        // transmute below is sound. PlatformTimer requires Send +
1210        // Sync + 'static, which `*mut c_void` itself fails — so
1211        // callers wrap their handle in a `#[repr(transparent)]`
1212        // newtype that implements those (PosixTimerHandle, etc.).
1213        // We round-trip through transmute at the C ABI boundary.
1214        const _: () = {
1215            if ::core::mem::size_of::<<$ty as ::nros_platform_api::PlatformTimer>::TimerHandle>()
1216                != ::core::mem::size_of::<*mut ::core::ffi::c_void>()
1217            {
1218                panic!(
1219                    "nros_platform_export_timer! requires \
1220                     PlatformTimer::TimerHandle to be pointer-sized"
1221                );
1222            }
1223        };
1224
1225        #[unsafe(no_mangle)]
1226        pub extern "C" fn nros_platform_timer_create_periodic(
1227            period_us: u32,
1228            callback: extern "C" fn(*mut ::core::ffi::c_void),
1229            user_data: *mut ::core::ffi::c_void,
1230        ) -> *mut ::core::ffi::c_void {
1231            match <$ty as ::nros_platform_api::PlatformTimer>::create_periodic(
1232                period_us, callback, user_data,
1233            ) {
1234                Ok(h) => unsafe {
1235                    ::core::mem::transmute_copy::<
1236                        <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle,
1237                        *mut ::core::ffi::c_void,
1238                    >(&::core::mem::ManuallyDrop::new(h))
1239                },
1240                Err(_) => ::core::ptr::null_mut(),
1241            }
1242        }
1243        #[unsafe(no_mangle)]
1244        pub extern "C" fn nros_platform_timer_create_oneshot(
1245            timeout_us: u32,
1246            callback: extern "C" fn(*mut ::core::ffi::c_void),
1247            user_data: *mut ::core::ffi::c_void,
1248        ) -> *mut ::core::ffi::c_void {
1249            match <$ty as ::nros_platform_api::PlatformTimer>::create_oneshot(
1250                timeout_us, callback, user_data,
1251            ) {
1252                Ok(h) => unsafe {
1253                    ::core::mem::transmute_copy::<
1254                        <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle,
1255                        *mut ::core::ffi::c_void,
1256                    >(&::core::mem::ManuallyDrop::new(h))
1257                },
1258                Err(_) => ::core::ptr::null_mut(),
1259            }
1260        }
1261        #[unsafe(no_mangle)]
1262        pub extern "C" fn nros_platform_timer_destroy(handle: *mut ::core::ffi::c_void) {
1263            let h: <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle = unsafe {
1264                ::core::mem::transmute_copy::<
1265                    *mut ::core::ffi::c_void,
1266                    <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle,
1267                >(&handle)
1268            };
1269            <$ty as ::nros_platform_api::PlatformTimer>::destroy(h)
1270        }
1271        #[unsafe(no_mangle)]
1272        pub extern "C" fn nros_platform_timer_cancel(handle: *mut ::core::ffi::c_void) -> i8 {
1273            let mut h: <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle = unsafe {
1274                ::core::mem::transmute_copy::<
1275                    *mut ::core::ffi::c_void,
1276                    <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle,
1277                >(&handle)
1278            };
1279            if <$ty as ::nros_platform_api::PlatformTimer>::cancel(&mut h) {
1280                1
1281            } else {
1282                0
1283            }
1284        }
1285    };
1286}
1287
1288/// Emit every `nros_platform_tcp_*` / `nros_platform_udp_*` /
1289/// `nros_platform_udp_mcast_*` / `nros_platform_socket_*` /
1290/// `nros_platform_network_poll` symbol declared in
1291/// `<nros/platform_net.h>` by delegating to the corresponding trait
1292/// method on `$ty`. The caller must implement `PlatformTcp`,
1293/// `PlatformUdp`, `PlatformUdpMulticast`, `PlatformSocketHelpers`, and
1294/// `PlatformNetworkPoll`.
1295#[macro_export]
1296macro_rules! nros_platform_export_net {
1297    ($ty:ty) => {
1298        // ---- TCP ----
1299        #[unsafe(no_mangle)]
1300        pub extern "C" fn nros_platform_tcp_create_endpoint(
1301            ep: *mut ::core::ffi::c_void,
1302            address: *const u8,
1303            port: *const u8,
1304        ) -> i8 {
1305            <$ty as ::nros_platform_api::PlatformTcp>::create_endpoint(ep, address, port)
1306        }
1307        #[unsafe(no_mangle)]
1308        pub extern "C" fn nros_platform_tcp_free_endpoint(ep: *mut ::core::ffi::c_void) {
1309            <$ty as ::nros_platform_api::PlatformTcp>::free_endpoint(ep)
1310        }
1311        #[unsafe(no_mangle)]
1312        pub extern "C" fn nros_platform_tcp_open(
1313            sock: *mut ::core::ffi::c_void,
1314            endpoint: *const ::core::ffi::c_void,
1315            timeout_ms: u32,
1316        ) -> i8 {
1317            <$ty as ::nros_platform_api::PlatformTcp>::open(sock, endpoint, timeout_ms)
1318        }
1319        #[unsafe(no_mangle)]
1320        pub extern "C" fn nros_platform_tcp_listen(
1321            sock: *mut ::core::ffi::c_void,
1322            endpoint: *const ::core::ffi::c_void,
1323        ) -> i8 {
1324            <$ty as ::nros_platform_api::PlatformTcp>::listen(sock, endpoint)
1325        }
1326        #[unsafe(no_mangle)]
1327        pub extern "C" fn nros_platform_tcp_close(sock: *mut ::core::ffi::c_void) {
1328            <$ty as ::nros_platform_api::PlatformTcp>::close(sock)
1329        }
1330        #[unsafe(no_mangle)]
1331        pub extern "C" fn nros_platform_tcp_read(
1332            sock: *const ::core::ffi::c_void,
1333            buf: *mut u8,
1334            len: usize,
1335        ) -> usize {
1336            <$ty as ::nros_platform_api::PlatformTcp>::read(sock, buf, len)
1337        }
1338        #[unsafe(no_mangle)]
1339        pub extern "C" fn nros_platform_tcp_read_exact(
1340            sock: *const ::core::ffi::c_void,
1341            buf: *mut u8,
1342            len: usize,
1343        ) -> usize {
1344            <$ty as ::nros_platform_api::PlatformTcp>::read_exact(sock, buf, len)
1345        }
1346        #[unsafe(no_mangle)]
1347        pub extern "C" fn nros_platform_tcp_send(
1348            sock: *const ::core::ffi::c_void,
1349            buf: *const u8,
1350            len: usize,
1351        ) -> usize {
1352            <$ty as ::nros_platform_api::PlatformTcp>::send(sock, buf, len)
1353        }
1354
1355        // ---- UDP unicast ----
1356        #[unsafe(no_mangle)]
1357        pub extern "C" fn nros_platform_udp_create_endpoint(
1358            ep: *mut ::core::ffi::c_void,
1359            address: *const u8,
1360            port: *const u8,
1361        ) -> i8 {
1362            <$ty as ::nros_platform_api::PlatformUdp>::create_endpoint(ep, address, port)
1363        }
1364        #[unsafe(no_mangle)]
1365        pub extern "C" fn nros_platform_udp_free_endpoint(ep: *mut ::core::ffi::c_void) {
1366            <$ty as ::nros_platform_api::PlatformUdp>::free_endpoint(ep)
1367        }
1368        #[unsafe(no_mangle)]
1369        pub extern "C" fn nros_platform_udp_open(
1370            sock: *mut ::core::ffi::c_void,
1371            endpoint: *const ::core::ffi::c_void,
1372            timeout_ms: u32,
1373        ) -> i8 {
1374            <$ty as ::nros_platform_api::PlatformUdp>::open(sock, endpoint, timeout_ms)
1375        }
1376        #[unsafe(no_mangle)]
1377        pub extern "C" fn nros_platform_udp_listen(
1378            sock: *mut ::core::ffi::c_void,
1379            endpoint: *const ::core::ffi::c_void,
1380            timeout_ms: u32,
1381        ) -> i8 {
1382            <$ty as ::nros_platform_api::PlatformUdp>::listen(sock, endpoint, timeout_ms)
1383        }
1384        #[unsafe(no_mangle)]
1385        pub extern "C" fn nros_platform_udp_close(sock: *mut ::core::ffi::c_void) {
1386            <$ty as ::nros_platform_api::PlatformUdp>::close(sock)
1387        }
1388        #[unsafe(no_mangle)]
1389        pub extern "C" fn nros_platform_udp_read(
1390            sock: *const ::core::ffi::c_void,
1391            buf: *mut u8,
1392            len: usize,
1393        ) -> usize {
1394            <$ty as ::nros_platform_api::PlatformUdp>::read(sock, buf, len)
1395        }
1396        #[unsafe(no_mangle)]
1397        pub extern "C" fn nros_platform_udp_read_exact(
1398            sock: *const ::core::ffi::c_void,
1399            buf: *mut u8,
1400            len: usize,
1401        ) -> usize {
1402            <$ty as ::nros_platform_api::PlatformUdp>::read_exact(sock, buf, len)
1403        }
1404        #[unsafe(no_mangle)]
1405        pub extern "C" fn nros_platform_udp_send(
1406            sock: *const ::core::ffi::c_void,
1407            buf: *const u8,
1408            len: usize,
1409            endpoint: *const ::core::ffi::c_void,
1410        ) -> usize {
1411            <$ty as ::nros_platform_api::PlatformUdp>::send(sock, buf, len, endpoint)
1412        }
1413        #[unsafe(no_mangle)]
1414        pub extern "C" fn nros_platform_udp_set_recv_timeout(
1415            sock: *const ::core::ffi::c_void,
1416            timeout_ms: u32,
1417        ) {
1418            <$ty as ::nros_platform_api::PlatformUdp>::set_recv_timeout(sock, timeout_ms)
1419        }
1420
1421        // ---- UDP multicast ----
1422        #[unsafe(no_mangle)]
1423        pub extern "C" fn nros_platform_udp_mcast_open(
1424            sock: *mut ::core::ffi::c_void,
1425            endpoint: *const ::core::ffi::c_void,
1426            lep: *mut ::core::ffi::c_void,
1427            timeout_ms: u32,
1428            iface: *const u8,
1429        ) -> i8 {
1430            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_open(
1431                sock, endpoint, lep, timeout_ms, iface,
1432            )
1433        }
1434        #[unsafe(no_mangle)]
1435        pub extern "C" fn nros_platform_udp_mcast_listen(
1436            sock: *mut ::core::ffi::c_void,
1437            endpoint: *const ::core::ffi::c_void,
1438            timeout_ms: u32,
1439            iface: *const u8,
1440            join: *const u8,
1441        ) -> i8 {
1442            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_listen(
1443                sock, endpoint, timeout_ms, iface, join,
1444            )
1445        }
1446        #[unsafe(no_mangle)]
1447        pub extern "C" fn nros_platform_udp_mcast_close(
1448            sockrecv: *mut ::core::ffi::c_void,
1449            socksend: *mut ::core::ffi::c_void,
1450            rep: *const ::core::ffi::c_void,
1451            lep: *const ::core::ffi::c_void,
1452        ) {
1453            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_close(
1454                sockrecv, socksend, rep, lep,
1455            )
1456        }
1457        #[unsafe(no_mangle)]
1458        pub extern "C" fn nros_platform_udp_mcast_read(
1459            sock: *const ::core::ffi::c_void,
1460            buf: *mut u8,
1461            len: usize,
1462            lep: *const ::core::ffi::c_void,
1463            addr: *mut ::core::ffi::c_void,
1464        ) -> usize {
1465            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_read(
1466                sock, buf, len, lep, addr,
1467            )
1468        }
1469        #[unsafe(no_mangle)]
1470        pub extern "C" fn nros_platform_udp_mcast_read_exact(
1471            sock: *const ::core::ffi::c_void,
1472            buf: *mut u8,
1473            len: usize,
1474            lep: *const ::core::ffi::c_void,
1475            addr: *mut ::core::ffi::c_void,
1476        ) -> usize {
1477            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_read_exact(
1478                sock, buf, len, lep, addr,
1479            )
1480        }
1481        #[unsafe(no_mangle)]
1482        pub extern "C" fn nros_platform_udp_mcast_send(
1483            sock: *const ::core::ffi::c_void,
1484            buf: *const u8,
1485            len: usize,
1486            endpoint: *const ::core::ffi::c_void,
1487        ) -> usize {
1488            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_send(sock, buf, len, endpoint)
1489        }
1490
1491        // ---- Socket helpers ----
1492        #[unsafe(no_mangle)]
1493        pub extern "C" fn nros_platform_socket_set_non_blocking(
1494            sock: *const ::core::ffi::c_void,
1495        ) -> i8 {
1496            <$ty as ::nros_platform_api::PlatformSocketHelpers>::set_non_blocking(sock)
1497        }
1498        #[unsafe(no_mangle)]
1499        pub extern "C" fn nros_platform_socket_accept(
1500            sock_in: *const ::core::ffi::c_void,
1501            sock_out: *mut ::core::ffi::c_void,
1502        ) -> i8 {
1503            <$ty as ::nros_platform_api::PlatformSocketHelpers>::accept(sock_in, sock_out)
1504        }
1505        #[unsafe(no_mangle)]
1506        pub extern "C" fn nros_platform_socket_close(sock: *mut ::core::ffi::c_void) {
1507            <$ty as ::nros_platform_api::PlatformSocketHelpers>::close(sock)
1508        }
1509        #[unsafe(no_mangle)]
1510        pub extern "C" fn nros_platform_socket_wait_event(
1511            peers: *mut ::core::ffi::c_void,
1512            mutex: *mut ::core::ffi::c_void,
1513        ) -> i8 {
1514            <$ty as ::nros_platform_api::PlatformSocketHelpers>::wait_event(peers, mutex)
1515        }
1516
1517        // ---- Network poll ----
1518        #[unsafe(no_mangle)]
1519        pub extern "C" fn nros_platform_network_poll() {
1520            <$ty as ::nros_platform_api::PlatformNetworkPoll>::network_poll()
1521        }
1522    };
1523}
1524
1525// ============================================================================
1526// Test-only self-export
1527// ----------------------------------------------------------------------------
1528// `cargo test -p nros-platform-cffi` builds a test binary that links the
1529// rlib. The `unsafe extern "C"` declarations above would fail to link
1530// without definitions; we satisfy them by invoking the macro on a dummy
1531// `TestPlatform` ZST defined here. This doubles as a smoke test that
1532// every macro arm expands and that the trait dispatch resolves.
1533//
1534// Real platform crates supply their own definitions via the same macro
1535// and never compile this module (it is gated on `cfg(test)`).
1536// ============================================================================
1537
1538#[cfg(all(test, not(feature = "c-stub-test"), not(feature = "posix-c-port")))]
1539mod test_self_export {
1540    use core::ffi::c_void;
1541    use nros_platform_api::{
1542        PlatformAlloc, PlatformClock, PlatformRandom, PlatformSleep, PlatformThreading,
1543        PlatformTime, PlatformYield,
1544    };
1545
1546    pub struct TestPlatform;
1547
1548    impl PlatformClock for TestPlatform {
1549        fn clock_ms() -> u64 {
1550            0
1551        }
1552        fn clock_us() -> u64 {
1553            0
1554        }
1555    }
1556    impl PlatformAlloc for TestPlatform {
1557        fn alloc(_: usize) -> *mut c_void {
1558            core::ptr::null_mut()
1559        }
1560        fn realloc(_: *mut c_void, _: usize) -> *mut c_void {
1561            core::ptr::null_mut()
1562        }
1563        fn dealloc(_: *mut c_void) {}
1564    }
1565    impl PlatformSleep for TestPlatform {
1566        fn sleep_us(_: usize) {}
1567        fn sleep_ms(_: usize) {}
1568        fn sleep_s(_: usize) {}
1569    }
1570    impl PlatformYield for TestPlatform {
1571        fn yield_now() {}
1572    }
1573    impl PlatformRandom for TestPlatform {
1574        fn random_u8() -> u8 {
1575            0
1576        }
1577        fn random_u16() -> u16 {
1578            0
1579        }
1580        fn random_u32() -> u32 {
1581            0
1582        }
1583        fn random_u64() -> u64 {
1584            0
1585        }
1586        fn random_fill(_: *mut c_void, _: usize) {}
1587    }
1588    impl PlatformTime for TestPlatform {
1589        fn time_now_ms() -> u64 {
1590            0
1591        }
1592        fn time_since_epoch_secs() -> u32 {
1593            0
1594        }
1595        fn time_since_epoch_nanos() -> u32 {
1596            0
1597        }
1598    }
1599    impl PlatformThreading for TestPlatform {
1600        fn task_init(
1601            _: *mut c_void,
1602            _: *mut c_void,
1603            _: Option<unsafe extern "C" fn(*mut c_void) -> *mut c_void>,
1604            _: *mut c_void,
1605        ) -> i8 {
1606            -1
1607        }
1608        fn task_join(_: *mut c_void) -> i8 {
1609            -1
1610        }
1611        fn task_detach(_: *mut c_void) -> i8 {
1612            -1
1613        }
1614        fn task_cancel(_: *mut c_void) -> i8 {
1615            -1
1616        }
1617        fn task_exit() {}
1618        fn task_free(_: *mut *mut c_void) {}
1619        fn mutex_init(_: *mut c_void) -> i8 {
1620            0
1621        }
1622        fn mutex_drop(_: *mut c_void) -> i8 {
1623            0
1624        }
1625        fn mutex_lock(_: *mut c_void) -> i8 {
1626            0
1627        }
1628        fn mutex_try_lock(_: *mut c_void) -> i8 {
1629            0
1630        }
1631        fn mutex_unlock(_: *mut c_void) -> i8 {
1632            0
1633        }
1634        fn mutex_rec_init(_: *mut c_void) -> i8 {
1635            0
1636        }
1637        fn mutex_rec_drop(_: *mut c_void) -> i8 {
1638            0
1639        }
1640        fn mutex_rec_lock(_: *mut c_void) -> i8 {
1641            0
1642        }
1643        fn mutex_rec_try_lock(_: *mut c_void) -> i8 {
1644            0
1645        }
1646        fn mutex_rec_unlock(_: *mut c_void) -> i8 {
1647            0
1648        }
1649        fn condvar_init(_: *mut c_void) -> i8 {
1650            0
1651        }
1652        fn condvar_drop(_: *mut c_void) -> i8 {
1653            0
1654        }
1655        fn condvar_signal(_: *mut c_void) -> i8 {
1656            0
1657        }
1658        fn condvar_signal_all(_: *mut c_void) -> i8 {
1659            0
1660        }
1661        fn condvar_wait(_: *mut c_void, _: *mut c_void) -> i8 {
1662            0
1663        }
1664        fn condvar_wait_until(_: *mut c_void, _: *mut c_void, _: u64) -> i8 {
1665            0
1666        }
1667    }
1668    impl ::nros_platform_api::PlatformCriticalSection for TestPlatform {
1669        fn acquire() -> u32 {
1670            0
1671        }
1672        fn release(_: u32) {}
1673    }
1674
1675    /// Pointer-sized newtype wrapping `*mut c_void` so the
1676    /// PlatformTimer Send + Sync + 'static bound is satisfied.
1677    /// The transmute inside `nros_platform_export_timer!` rests on
1678    /// this being `#[repr(transparent)]` over a pointer.
1679    #[repr(transparent)]
1680    #[derive(Clone, Copy)]
1681    pub struct TestTimerHandle(pub *mut c_void);
1682    unsafe impl Send for TestTimerHandle {}
1683    unsafe impl Sync for TestTimerHandle {}
1684
1685    impl ::nros_platform_api::PlatformTimer for TestPlatform {
1686        type TimerHandle = TestTimerHandle;
1687        // create_periodic / create_oneshot / destroy / cancel inherit
1688        // the trait's default impls (return TimerError::Unsupported /
1689        // no-op destroy / false cancel) — fine for export-emission
1690        // verification.
1691    }
1692
1693    crate::nros_platform_export!(TestPlatform);
1694    crate::nros_platform_export_timer!(TestPlatform);
1695
1696    #[test]
1697    fn macro_expansion_dispatches() {
1698        // Touch every group through the FFI surface to confirm the
1699        // generated symbols are reachable and dispatch resolves.
1700        assert_eq!(super::CffiPlatform::clock_ms(), 0);
1701        assert_eq!(
1702            <super::CffiPlatform as ::nros_platform_api::PlatformAlloc>::alloc(0),
1703            core::ptr::null_mut(),
1704        );
1705        <super::CffiPlatform as ::nros_platform_api::PlatformYield>::yield_now();
1706    }
1707
1708    #[test]
1709    fn timer_macro_emits() {
1710        // Default impl returns Unsupported → null handle.
1711        let h = unsafe {
1712            super::nros_platform_timer_create_periodic(1000, noop_callback, core::ptr::null_mut())
1713        };
1714        assert!(h.is_null(), "default Unsupported impl must surface as NULL");
1715    }
1716
1717    extern "C" fn noop_callback(_: *mut c_void) {}
1718}