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