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_until(cv: *mut c_void, m: *mut c_void, abstime: u64) -> i8 {
396        unsafe { nros_platform_condvar_wait_until(cv, m, abstime) }
397    }
398    fn wake_init(w: *mut c_void) -> i8 {
399        unsafe { nros_platform_wake_init(w) }
400    }
401    fn wake_drop(w: *mut c_void) -> i8 {
402        unsafe { nros_platform_wake_drop(w) }
403    }
404    fn wake_wait_ms(w: *mut c_void, timeout_ms: u32) -> i8 {
405        unsafe { nros_platform_wake_wait_ms(w, timeout_ms) }
406    }
407    fn wake_signal(w: *mut c_void) -> i8 {
408        unsafe { nros_platform_wake_signal(w) }
409    }
410    fn wake_signal_from_isr(w: *mut c_void) -> i8 {
411        unsafe { nros_platform_wake_signal_from_isr(w) }
412    }
413    // `wake_park_until_us` / `wake_park_granularity_us` are deliberately NOT
414    // forwarded. `platform.h` marks them OPTIONAL — Zephyr, ThreadX, FreeRTOS
415    // and POSIX (so NuttX) define them (phase-436 W7, C1, C2); the bare-metal
416    // ports do not (C3) — and this impl is linked into EVERY C port, so forwarding
417    // made an optional symbol a required one: `undefined reference to
418    // nros_platform_wake_park_until_us` on POSIX, FreeRTOS, NuttX and every
419    // other C port (`check-staticlib-symbols`, `check-c`). The trait defaults
420    // (-1 "cannot park", 0 "no granularity") are the honest answer for a shim
421    // that cannot know whether the port it is linked beside defines them. The
422    // ports that do are reached directly by their board entries
423    // (`nros::port_park::install_port_park`), never through this impl.
424    fn wake_storage_size() -> usize {
425        unsafe { nros_platform_wake_storage_size() }
426    }
427    fn wake_storage_align() -> usize {
428        unsafe { nros_platform_wake_storage_align() }
429    }
430    fn task_storage_size() -> usize {
431        unsafe { nros_platform_task_storage_size() }
432    }
433    fn task_storage_align() -> usize {
434        unsafe { nros_platform_task_storage_align() }
435    }
436}
437
438// ============================================================================
439// Phase 121.3.deprecate-rust-migrate — extended-surface trait impls
440// ----------------------------------------------------------------------------
441// CffiPlatform dispatches PlatformTcp / PlatformUdp / PlatformUdpMulticast /
442// PlatformSocketHelpers / PlatformNetworkPoll trait calls through the
443// `unsafe extern "C"` declarations above. Whichever provider supplies the
444// matching symbol set (a per-RTOS Rust crate with `cffi-export` on, or a
445// hand-written C port) backs the dispatch transparently.
446// ============================================================================
447
448impl nros_platform_api::PlatformTcp for CffiPlatform {
449    fn create_endpoint(ep: *mut c_void, address: *const u8, port: *const u8) -> i8 {
450        unsafe { nros_platform_tcp_create_endpoint(ep, address, port) }
451    }
452    fn free_endpoint(ep: *mut c_void) {
453        unsafe { nros_platform_tcp_free_endpoint(ep) }
454    }
455    fn open(sock: *mut c_void, endpoint: *const c_void, timeout_ms: u32) -> i8 {
456        unsafe { nros_platform_tcp_open(sock, endpoint, timeout_ms) }
457    }
458    fn listen(sock: *mut c_void, endpoint: *const c_void) -> i8 {
459        unsafe { nros_platform_tcp_listen(sock, endpoint) }
460    }
461    fn close(sock: *mut c_void) {
462        unsafe { nros_platform_tcp_close(sock) }
463    }
464    fn read(sock: *const c_void, buf: *mut u8, len: usize) -> usize {
465        unsafe { nros_platform_tcp_read(sock, buf, len) }
466    }
467    fn read_exact(sock: *const c_void, buf: *mut u8, len: usize) -> usize {
468        unsafe { nros_platform_tcp_read_exact(sock, buf, len) }
469    }
470    fn send(sock: *const c_void, buf: *const u8, len: usize) -> usize {
471        unsafe { nros_platform_tcp_send(sock, buf, len) }
472    }
473}
474
475impl nros_platform_api::PlatformUdp for CffiPlatform {
476    fn create_endpoint(ep: *mut c_void, address: *const u8, port: *const u8) -> i8 {
477        unsafe { nros_platform_udp_create_endpoint(ep, address, port) }
478    }
479    fn free_endpoint(ep: *mut c_void) {
480        unsafe { nros_platform_udp_free_endpoint(ep) }
481    }
482    fn open(sock: *mut c_void, endpoint: *const c_void, timeout_ms: u32) -> i8 {
483        unsafe { nros_platform_udp_open(sock, endpoint, timeout_ms) }
484    }
485    fn listen(sock: *mut c_void, endpoint: *const c_void, timeout_ms: u32) -> i8 {
486        unsafe { nros_platform_udp_listen(sock, endpoint, timeout_ms) }
487    }
488    fn close(sock: *mut c_void) {
489        unsafe { nros_platform_udp_close(sock) }
490    }
491    fn read(sock: *const c_void, buf: *mut u8, len: usize) -> usize {
492        unsafe { nros_platform_udp_read(sock, buf, len) }
493    }
494    fn read_exact(sock: *const c_void, buf: *mut u8, len: usize) -> usize {
495        unsafe { nros_platform_udp_read_exact(sock, buf, len) }
496    }
497    fn send(sock: *const c_void, buf: *const u8, len: usize, endpoint: *const c_void) -> usize {
498        unsafe { nros_platform_udp_send(sock, buf, len, endpoint) }
499    }
500    fn set_recv_timeout(sock: *const c_void, timeout_ms: u32) {
501        unsafe { nros_platform_udp_set_recv_timeout(sock, timeout_ms) }
502    }
503}
504
505impl nros_platform_api::PlatformUdpMulticast for CffiPlatform {
506    fn mcast_open(
507        sock: *mut c_void,
508        endpoint: *const c_void,
509        lep: *mut c_void,
510        timeout_ms: u32,
511        iface: *const u8,
512    ) -> i8 {
513        unsafe { nros_platform_udp_mcast_open(sock, endpoint, lep, timeout_ms, iface) }
514    }
515    fn mcast_listen(
516        sock: *mut c_void,
517        endpoint: *const c_void,
518        timeout_ms: u32,
519        iface: *const u8,
520        join: *const u8,
521    ) -> i8 {
522        unsafe { nros_platform_udp_mcast_listen(sock, endpoint, timeout_ms, iface, join) }
523    }
524    fn mcast_close(
525        sockrecv: *mut c_void,
526        socksend: *mut c_void,
527        rep: *const c_void,
528        lep: *const c_void,
529    ) {
530        unsafe { nros_platform_udp_mcast_close(sockrecv, socksend, rep, lep) }
531    }
532    fn mcast_read(
533        sock: *const c_void,
534        buf: *mut u8,
535        len: usize,
536        lep: *const c_void,
537        addr: *mut c_void,
538    ) -> usize {
539        unsafe { nros_platform_udp_mcast_read(sock, buf, len, lep, addr) }
540    }
541    fn mcast_read_exact(
542        sock: *const c_void,
543        buf: *mut u8,
544        len: usize,
545        lep: *const c_void,
546        addr: *mut c_void,
547    ) -> usize {
548        unsafe { nros_platform_udp_mcast_read_exact(sock, buf, len, lep, addr) }
549    }
550    fn mcast_send(
551        sock: *const c_void,
552        buf: *const u8,
553        len: usize,
554        endpoint: *const c_void,
555    ) -> usize {
556        unsafe { nros_platform_udp_mcast_send(sock, buf, len, endpoint) }
557    }
558}
559
560impl nros_platform_api::PlatformSocketHelpers for CffiPlatform {
561    fn set_non_blocking(sock: *const c_void) -> i8 {
562        unsafe { nros_platform_socket_set_non_blocking(sock) }
563    }
564    fn accept(sock_in: *const c_void, sock_out: *mut c_void) -> i8 {
565        unsafe { nros_platform_socket_accept(sock_in, sock_out) }
566    }
567    fn close(sock: *mut c_void) {
568        unsafe { nros_platform_socket_close(sock) }
569    }
570    fn wait_event(peers: *mut c_void, mutex: *mut c_void) -> i8 {
571        unsafe { nros_platform_socket_wait_event(peers, mutex) }
572    }
573}
574
575impl nros_platform_api::PlatformNetworkPoll for CffiPlatform {
576    fn network_poll() {
577        unsafe { nros_platform_network_poll() }
578    }
579}
580
581impl nros_platform_api::PlatformCriticalSection for CffiPlatform {
582    fn acquire() -> u32 {
583        unsafe { nros_platform_critical_section_acquire() }
584    }
585    fn release(token: u32) {
586        unsafe { nros_platform_critical_section_release(token) }
587    }
588}
589
590impl nros_platform_api::PlatformLog for CffiPlatform {
591    fn write(severity: u8, name: &[u8], message: &[u8]) {
592        // SAFETY: extern decl matches the C ABI byte-for-byte; the
593        // pointer/length pairs come from `&[u8]` references that
594        // outlive the call.
595        unsafe {
596            nros_platform_log_write(
597                severity,
598                name.as_ptr(),
599                name.len(),
600                message.as_ptr(),
601                message.len(),
602            );
603        }
604    }
605
606    fn flush() {
607        // SAFETY: no args, no preconditions.
608        unsafe { nros_platform_log_flush() };
609    }
610}
611
612// ============================================================================
613// Phase 121.2 — export_*! macros
614// ----------------------------------------------------------------------------
615// Each macro emits the `#[unsafe(no_mangle)] extern "C"` definitions for one
616// capability group. The macro callee must implement the matching
617// `nros_platform_api::Platform*` trait; the trait bound is checked at the
618// macro-expansion site, so a missing impl produces a clear compile error in
619// the caller crate.
620//
621// Naming the symbols exactly matches `<nros/platform.h>`. Add a new ABI
622// symbol in three coordinated places, all inside this crate:
623//   1. declare it in `include/nros/platform.h`,
624//   2. declare it in the `unsafe extern "C" { … }` block above,
625//   3. emit it from the appropriate `export_*!` macro below.
626// ============================================================================
627
628/// Emit `nros_platform_clock_{ms,us}` delegating to
629/// `<$ty as PlatformClock>`.
630#[macro_export]
631macro_rules! nros_platform_export_clock {
632    ($ty:ty) => {
633        #[unsafe(no_mangle)]
634        pub extern "C" fn nros_platform_clock_ns() -> u64 {
635            <$ty as ::nros_platform_api::PlatformClock>::clock_ns()
636        }
637        #[unsafe(no_mangle)]
638        pub extern "C" fn nros_platform_clock_resolution_ns() -> u64 {
639            <$ty as ::nros_platform_api::PlatformClock>::clock_resolution_ns()
640        }
641        /// issue 0758 — a Rust port that does not override
642        /// `PlatformClock::epoch_us` emits this and answers `0`, which is
643        /// the honest reading for a platform with no wall clock.
644        #[unsafe(no_mangle)]
645        pub extern "C" fn nros_platform_epoch_us() -> u64 {
646            <$ty as ::nros_platform_api::PlatformClock>::epoch_us()
647        }
648    };
649}
650
651/// Emit `nros_platform_panic` delegating to `<$ty as PlatformPanic>`
652/// (phase-366 / RFC-0077).
653///
654/// NOT in `nros_platform_export!` deliberately. The fatal path is the IMAGE's,
655/// and a Rust port that exports it unconditionally would be the same defect
656/// this API exists to remove: a library claiming the image's ending. A port
657/// calls this only when it is the one supplying the behaviour.
658#[macro_export]
659macro_rules! nros_platform_export_panic {
660    ($ty:ty) => {
661        #[unsafe(no_mangle)]
662        pub extern "C" fn nros_platform_panic(msg: *const u8, len: usize) -> ! {
663            <$ty as ::nros_platform_api::PlatformPanic>::panic(msg, len)
664        }
665    };
666}
667
668/// Emit `nros_platform_{alloc,realloc,dealloc}` delegating to
669/// `<$ty as PlatformAlloc>`.
670#[macro_export]
671macro_rules! nros_platform_export_alloc {
672    ($ty:ty) => {
673        #[unsafe(no_mangle)]
674        pub extern "C" fn nros_platform_alloc(size: usize) -> *mut ::core::ffi::c_void {
675            <$ty as ::nros_platform_api::PlatformAlloc>::alloc(size)
676        }
677        #[unsafe(no_mangle)]
678        pub extern "C" fn nros_platform_realloc(
679            ptr: *mut ::core::ffi::c_void,
680            size: usize,
681        ) -> *mut ::core::ffi::c_void {
682            <$ty as ::nros_platform_api::PlatformAlloc>::realloc(ptr, size)
683        }
684        #[unsafe(no_mangle)]
685        pub extern "C" fn nros_platform_dealloc(ptr: *mut ::core::ffi::c_void) {
686            <$ty as ::nros_platform_api::PlatformAlloc>::dealloc(ptr)
687        }
688        #[unsafe(no_mangle)]
689        pub extern "C" fn nros_platform_heap_used_bytes() -> usize {
690            <$ty as ::nros_platform_api::PlatformAlloc>::heap_used_bytes()
691        }
692        #[unsafe(no_mangle)]
693        pub extern "C" fn nros_platform_heap_total_bytes() -> usize {
694            <$ty as ::nros_platform_api::PlatformAlloc>::heap_total_bytes()
695        }
696    };
697}
698
699/// Emit `nros_platform_sleep_{us,ms,s}` delegating to
700/// `<$ty as PlatformSleep>`.
701#[macro_export]
702macro_rules! nros_platform_export_sleep {
703    ($ty:ty) => {
704        #[unsafe(no_mangle)]
705        pub extern "C" fn nros_platform_sleep_us(us: usize) {
706            <$ty as ::nros_platform_api::PlatformSleep>::sleep_us(us)
707        }
708        #[unsafe(no_mangle)]
709        pub extern "C" fn nros_platform_sleep_ms(ms: usize) {
710            <$ty as ::nros_platform_api::PlatformSleep>::sleep_ms(ms)
711        }
712        #[unsafe(no_mangle)]
713        pub extern "C" fn nros_platform_sleep_s(s: usize) {
714            <$ty as ::nros_platform_api::PlatformSleep>::sleep_s(s)
715        }
716    };
717}
718
719/// Emit `nros_platform_yield_now` delegating to
720/// `<$ty as PlatformYield>`.
721#[macro_export]
722macro_rules! nros_platform_export_yield {
723    ($ty:ty) => {
724        #[unsafe(no_mangle)]
725        pub extern "C" fn nros_platform_yield_now() {
726            <$ty as ::nros_platform_api::PlatformYield>::yield_now()
727        }
728    };
729}
730
731/// Emit `nros_platform_random_*` delegating to `<$ty as PlatformRandom>`.
732#[macro_export]
733macro_rules! nros_platform_export_random {
734    ($ty:ty) => {
735        #[unsafe(no_mangle)]
736        pub extern "C" fn nros_platform_random_u8() -> u8 {
737            <$ty as ::nros_platform_api::PlatformRandom>::random_u8()
738        }
739        #[unsafe(no_mangle)]
740        pub extern "C" fn nros_platform_random_u16() -> u16 {
741            <$ty as ::nros_platform_api::PlatformRandom>::random_u16()
742        }
743        #[unsafe(no_mangle)]
744        pub extern "C" fn nros_platform_random_u32() -> u32 {
745            <$ty as ::nros_platform_api::PlatformRandom>::random_u32()
746        }
747        #[unsafe(no_mangle)]
748        pub extern "C" fn nros_platform_random_u64() -> u64 {
749            <$ty as ::nros_platform_api::PlatformRandom>::random_u64()
750        }
751        #[unsafe(no_mangle)]
752        pub extern "C" fn nros_platform_random_fill(buf: *mut ::core::ffi::c_void, len: usize) {
753            <$ty as ::nros_platform_api::PlatformRandom>::random_fill(buf, len)
754        }
755    };
756}
757
758/// Emit `nros_platform_time_*` delegating to `<$ty as PlatformTime>`.
759#[macro_export]
760macro_rules! nros_platform_export_time {
761    ($ty:ty) => {
762        #[unsafe(no_mangle)]
763        pub extern "C" fn nros_platform_time_now_ns() -> u64 {
764            <$ty as ::nros_platform_api::PlatformTime>::time_now_ns()
765        }
766    };
767}
768
769/// Emit `nros_platform_task_*`, `nros_platform_mutex_*`,
770/// `nros_platform_mutex_rec_*`, and `nros_platform_condvar_*` delegating
771/// to `<$ty as PlatformThreading>`. Skip this macro on platforms without
772/// kernel threads.
773#[macro_export]
774macro_rules! nros_platform_export_threading {
775    ($ty:ty) => {
776        #[unsafe(no_mangle)]
777        pub extern "C" fn nros_platform_task_init(
778            task: *mut ::core::ffi::c_void,
779            attr: *mut ::core::ffi::c_void,
780            entry: ::core::option::Option<
781                unsafe extern "C" fn(*mut ::core::ffi::c_void) -> *mut ::core::ffi::c_void,
782            >,
783            arg: *mut ::core::ffi::c_void,
784        ) -> i8 {
785            <$ty as ::nros_platform_api::PlatformThreading>::task_init(task, attr, entry, arg)
786        }
787        #[unsafe(no_mangle)]
788        pub extern "C" fn nros_platform_task_stack_unused_bytes() -> usize {
789            <$ty as ::nros_platform_api::PlatformThreading>::task_stack_unused_bytes()
790        }
791        #[unsafe(no_mangle)]
792        pub extern "C" fn nros_platform_task_stack_unused_bytes_upto(limit: usize) -> usize {
793            <$ty as ::nros_platform_api::PlatformThreading>::task_stack_unused_bytes_upto(limit)
794        }
795        #[unsafe(no_mangle)]
796        pub extern "C" fn nros_platform_task_join(task: *mut ::core::ffi::c_void) -> i8 {
797            <$ty as ::nros_platform_api::PlatformThreading>::task_join(task)
798        }
799        #[unsafe(no_mangle)]
800        pub extern "C" fn nros_platform_task_detach(task: *mut ::core::ffi::c_void) -> i8 {
801            <$ty as ::nros_platform_api::PlatformThreading>::task_detach(task)
802        }
803        #[unsafe(no_mangle)]
804        pub extern "C" fn nros_platform_task_cancel(task: *mut ::core::ffi::c_void) -> i8 {
805            <$ty as ::nros_platform_api::PlatformThreading>::task_cancel(task)
806        }
807        #[unsafe(no_mangle)]
808        pub extern "C" fn nros_platform_task_exit() {
809            <$ty as ::nros_platform_api::PlatformThreading>::task_exit()
810        }
811        #[unsafe(no_mangle)]
812        pub extern "C" fn nros_platform_task_free(task: *mut *mut ::core::ffi::c_void) {
813            <$ty as ::nros_platform_api::PlatformThreading>::task_free(task)
814        }
815        #[unsafe(no_mangle)]
816        pub extern "C" fn nros_platform_mutex_init(m: *mut ::core::ffi::c_void) -> i8 {
817            <$ty as ::nros_platform_api::PlatformThreading>::mutex_init(m)
818        }
819        #[unsafe(no_mangle)]
820        pub extern "C" fn nros_platform_mutex_drop(m: *mut ::core::ffi::c_void) -> i8 {
821            <$ty as ::nros_platform_api::PlatformThreading>::mutex_drop(m)
822        }
823        #[unsafe(no_mangle)]
824        pub extern "C" fn nros_platform_mutex_lock(m: *mut ::core::ffi::c_void) -> i8 {
825            <$ty as ::nros_platform_api::PlatformThreading>::mutex_lock(m)
826        }
827        #[unsafe(no_mangle)]
828        pub extern "C" fn nros_platform_mutex_try_lock(m: *mut ::core::ffi::c_void) -> i8 {
829            <$ty as ::nros_platform_api::PlatformThreading>::mutex_try_lock(m)
830        }
831        #[unsafe(no_mangle)]
832        pub extern "C" fn nros_platform_mutex_unlock(m: *mut ::core::ffi::c_void) -> i8 {
833            <$ty as ::nros_platform_api::PlatformThreading>::mutex_unlock(m)
834        }
835        #[unsafe(no_mangle)]
836        pub extern "C" fn nros_platform_mutex_rec_init(m: *mut ::core::ffi::c_void) -> i8 {
837            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_init(m)
838        }
839        #[unsafe(no_mangle)]
840        pub extern "C" fn nros_platform_mutex_rec_drop(m: *mut ::core::ffi::c_void) -> i8 {
841            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_drop(m)
842        }
843        #[unsafe(no_mangle)]
844        pub extern "C" fn nros_platform_mutex_rec_lock(m: *mut ::core::ffi::c_void) -> i8 {
845            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_lock(m)
846        }
847        #[unsafe(no_mangle)]
848        pub extern "C" fn nros_platform_mutex_rec_try_lock(m: *mut ::core::ffi::c_void) -> i8 {
849            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_try_lock(m)
850        }
851        #[unsafe(no_mangle)]
852        pub extern "C" fn nros_platform_mutex_rec_unlock(m: *mut ::core::ffi::c_void) -> i8 {
853            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_unlock(m)
854        }
855        #[unsafe(no_mangle)]
856        pub extern "C" fn nros_platform_condvar_init(cv: *mut ::core::ffi::c_void) -> i8 {
857            <$ty as ::nros_platform_api::PlatformThreading>::condvar_init(cv)
858        }
859        #[unsafe(no_mangle)]
860        pub extern "C" fn nros_platform_condvar_drop(cv: *mut ::core::ffi::c_void) -> i8 {
861            <$ty as ::nros_platform_api::PlatformThreading>::condvar_drop(cv)
862        }
863        #[unsafe(no_mangle)]
864        pub extern "C" fn nros_platform_condvar_signal(cv: *mut ::core::ffi::c_void) -> i8 {
865            <$ty as ::nros_platform_api::PlatformThreading>::condvar_signal(cv)
866        }
867        #[unsafe(no_mangle)]
868        pub extern "C" fn nros_platform_condvar_signal_all(cv: *mut ::core::ffi::c_void) -> i8 {
869            <$ty as ::nros_platform_api::PlatformThreading>::condvar_signal_all(cv)
870        }
871        #[unsafe(no_mangle)]
872        pub extern "C" fn nros_platform_condvar_signal_from_isr(
873            cv: *mut ::core::ffi::c_void,
874        ) -> i8 {
875            <$ty as ::nros_platform_api::PlatformThreading>::condvar_signal_from_isr(cv)
876        }
877        #[unsafe(no_mangle)]
878        pub extern "C" fn nros_platform_condvar_wait_until(
879            cv: *mut ::core::ffi::c_void,
880            m: *mut ::core::ffi::c_void,
881            abstime: u64,
882        ) -> i8 {
883            <$ty as ::nros_platform_api::PlatformThreading>::condvar_wait_until(cv, m, abstime)
884        }
885        // Phase 130 — wake primitive (binary semaphore shape).
886        #[unsafe(no_mangle)]
887        pub extern "C" fn nros_platform_wake_init(w: *mut ::core::ffi::c_void) -> i8 {
888            <$ty as ::nros_platform_api::PlatformThreading>::wake_init(w)
889        }
890        #[unsafe(no_mangle)]
891        pub extern "C" fn nros_platform_wake_drop(w: *mut ::core::ffi::c_void) -> i8 {
892            <$ty as ::nros_platform_api::PlatformThreading>::wake_drop(w)
893        }
894        #[unsafe(no_mangle)]
895        pub extern "C" fn nros_platform_wake_wait_ms(
896            w: *mut ::core::ffi::c_void,
897            timeout_ms: u32,
898        ) -> i8 {
899            <$ty as ::nros_platform_api::PlatformThreading>::wake_wait_ms(w, timeout_ms)
900        }
901        #[unsafe(no_mangle)]
902        pub extern "C" fn nros_platform_wake_signal(w: *mut ::core::ffi::c_void) -> i8 {
903            <$ty as ::nros_platform_api::PlatformThreading>::wake_signal(w)
904        }
905        #[unsafe(no_mangle)]
906        pub extern "C" fn nros_platform_wake_signal_from_isr(w: *mut ::core::ffi::c_void) -> i8 {
907            <$ty as ::nros_platform_api::PlatformThreading>::wake_signal_from_isr(w)
908        }
909        #[unsafe(no_mangle)]
910        pub extern "C" fn nros_platform_wake_park_until_us(
911            w: *mut ::core::ffi::c_void,
912            deadline_us: u64,
913        ) -> i8 {
914            <$ty as ::nros_platform_api::PlatformThreading>::wake_park_until_us(w, deadline_us)
915        }
916        #[unsafe(no_mangle)]
917        pub extern "C" fn nros_platform_wake_park_granularity_us() -> u64 {
918            <$ty as ::nros_platform_api::PlatformThreading>::wake_park_granularity_us()
919        }
920        #[unsafe(no_mangle)]
921        pub extern "C" fn nros_platform_task_storage_size() -> usize {
922            <$ty as ::nros_platform_api::PlatformThreading>::task_storage_size()
923        }
924        #[unsafe(no_mangle)]
925        pub extern "C" fn nros_platform_task_storage_align() -> usize {
926            <$ty as ::nros_platform_api::PlatformThreading>::task_storage_align()
927        }
928        #[unsafe(no_mangle)]
929        pub extern "C" fn nros_platform_wake_storage_size() -> usize {
930            <$ty as ::nros_platform_api::PlatformThreading>::wake_storage_size()
931        }
932        #[unsafe(no_mangle)]
933        pub extern "C" fn nros_platform_wake_storage_align() -> usize {
934            <$ty as ::nros_platform_api::PlatformThreading>::wake_storage_align()
935        }
936        /// phase-364 W3 — the attribute defaults are pure data, so this is
937        /// emitted concretely rather than dispatched through the trait: every
938        /// port's answer is identical, and a trait method would only invite a
939        /// port to get it wrong.
940        #[unsafe(no_mangle)]
941        pub extern "C" fn nros_platform_task_attr_init(
942            attr: *mut $crate::generated::nros_platform_task_attr_t,
943        ) {
944            if attr.is_null() {
945                return;
946            }
947            // SAFETY: non-null, and the caller owns writable storage of this
948            // type by the ABI contract.
949            unsafe {
950                (*attr).name = ::core::ptr::null();
951                (*attr).stack_bytes = 0;
952                (*attr).stack_mem = ::core::ptr::null_mut();
953                (*attr).priority = i32::MIN;
954                (*attr).core = -1;
955                (*attr).flags = 0;
956            }
957        }
958        // NOTE: `nros_platform_task_storage_{size,align}` are emitted above,
959        // beside their `wake_storage_*` siblings — not here with the lock
960        // family. A second copy landed here and made every port that invokes
961        // this macro fail with E0428; both spans pointed at the same macro
962        // call, which is what a macro emitting a symbol twice looks like.
963        #[unsafe(no_mangle)]
964        pub extern "C" fn nros_platform_mutex_storage_size() -> usize {
965            <$ty as ::nros_platform_api::PlatformThreading>::mutex_storage_size()
966        }
967        #[unsafe(no_mangle)]
968        pub extern "C" fn nros_platform_mutex_storage_align() -> usize {
969            <$ty as ::nros_platform_api::PlatformThreading>::mutex_storage_align()
970        }
971        #[unsafe(no_mangle)]
972        pub extern "C" fn nros_platform_mutex_rec_storage_size() -> usize {
973            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_storage_size()
974        }
975        #[unsafe(no_mangle)]
976        pub extern "C" fn nros_platform_mutex_rec_storage_align() -> usize {
977            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_storage_align()
978        }
979        #[unsafe(no_mangle)]
980        pub extern "C" fn nros_platform_condvar_storage_size() -> usize {
981            <$ty as ::nros_platform_api::PlatformThreading>::condvar_storage_size()
982        }
983        #[unsafe(no_mangle)]
984        pub extern "C" fn nros_platform_condvar_storage_align() -> usize {
985            <$ty as ::nros_platform_api::PlatformThreading>::condvar_storage_align()
986        }
987    };
988}
989
990/// Phase 121.9 — emit the two `nros_platform_critical_section_*`
991/// symbols by delegating to the caller's `PlatformCriticalSection`
992/// impl.
993#[macro_export]
994macro_rules! nros_platform_export_critical_section {
995    ($ty:ty) => {
996        #[unsafe(no_mangle)]
997        pub extern "C" fn nros_platform_critical_section_acquire() -> u32 {
998            <$ty as ::nros_platform_api::PlatformCriticalSection>::acquire()
999        }
1000        #[unsafe(no_mangle)]
1001        pub extern "C" fn nros_platform_critical_section_release(token: u32) {
1002            <$ty as ::nros_platform_api::PlatformCriticalSection>::release(token)
1003        }
1004    };
1005}
1006
1007/// Phase 88.11 — emit `nros_platform_log_write` + `nros_platform_log_flush`
1008/// from a `PlatformLog`-implementing ZST. Use this on bare-metal /
1009/// custom platforms (mps2-an385, stm32f4, esp32-baremetal, …) that
1010/// don't ship a separate C implementation file. The implementor's
1011/// `write` receives the rendered body + logger name as `&[u8]` slices.
1012#[macro_export]
1013macro_rules! nros_platform_export_log {
1014    ($ty:ty) => {
1015        #[unsafe(no_mangle)]
1016        pub extern "C" fn nros_platform_log_write(
1017            severity: u8,
1018            name_ptr: *const u8,
1019            name_len: usize,
1020            msg_ptr: *const u8,
1021            msg_len: usize,
1022        ) {
1023            // SAFETY: caller passes valid `&[u8]` slices that outlive
1024            // the call; empty-name case (name_ptr=null, name_len=0)
1025            // collapses to an empty slice.
1026            let name: &[u8] = if name_ptr.is_null() || name_len == 0 {
1027                &[]
1028            } else {
1029                unsafe { ::core::slice::from_raw_parts(name_ptr, name_len) }
1030            };
1031            let msg: &[u8] = if msg_ptr.is_null() || msg_len == 0 {
1032                &[]
1033            } else {
1034                unsafe { ::core::slice::from_raw_parts(msg_ptr, msg_len) }
1035            };
1036            <$ty as ::nros_platform_api::PlatformLog>::write(severity, name, msg);
1037        }
1038        #[unsafe(no_mangle)]
1039        pub extern "C" fn nros_platform_log_flush() {
1040            <$ty as ::nros_platform_api::PlatformLog>::flush()
1041        }
1042        /// Phase 88.16.H — ABI-mirror parity. Direct-impl
1043        /// platforms (`mps2-an385`, `stm32f4`, …) route every
1044        /// record through `PlatformLog::write`, so the runtime
1045        /// swap slot is meaningless to them. The header mirror
1046        /// nonetheless declares `nros_platform_register_log_writer`,
1047        /// so the macro emits a no-op stub to satisfy the
1048        /// ABI-mirror check. Fn-ptr-slot platforms (FreeRTOS /
1049        /// ThreadX / NuttX) don't call this macro — their C body
1050        /// ships the real strong definition.
1051        #[unsafe(no_mangle)]
1052        pub extern "C" fn nros_platform_register_log_writer(
1053            _writer: ::core::option::Option<
1054                unsafe extern "C" fn(
1055                    severity: u8,
1056                    name_ptr: *const u8,
1057                    name_len: usize,
1058                    msg_ptr: *const u8,
1059                    msg_len: usize,
1060                ),
1061            >,
1062            _flusher: ::core::option::Option<unsafe extern "C" fn()>,
1063        ) {
1064        }
1065    };
1066}
1067
1068/// Convenience: emit every `nros_platform_*` symbol declared in
1069/// `<nros/platform.h>` by delegating to the corresponding
1070/// `nros_platform_api::Platform*` trait method on `$ty`. The caller must
1071/// implement every trait covered by the capability macros.
1072///
1073/// Logging (`nros_platform_export_log!`) is NOT part of this convenience
1074/// macro: bare-metal platforms typically need to supply a writer
1075/// (`hprintln!` / `defmt::info!`) that requires extra deps not all
1076/// platforms link against. Call `nros_platform_export_log!` separately
1077/// after the platform crate implements `PlatformLog`.
1078#[macro_export]
1079macro_rules! nros_platform_export {
1080    ($ty:ty) => {
1081        $crate::nros_platform_export_clock!($ty);
1082        $crate::nros_platform_export_alloc!($ty);
1083        $crate::nros_platform_export_sleep!($ty);
1084        $crate::nros_platform_export_yield!($ty);
1085        $crate::nros_platform_export_random!($ty);
1086        $crate::nros_platform_export_time!($ty);
1087        $crate::nros_platform_export_threading!($ty);
1088        $crate::nros_platform_export_critical_section!($ty);
1089    };
1090}
1091
1092// ============================================================================
1093// Phase 121.6.macros — extended-surface export macros
1094// ----------------------------------------------------------------------------
1095// `nros_platform_export_net!` mirrors `<nros/platform_net.h>` 1:1; trait
1096// signatures match the C ABI byte-for-byte. `nros_platform_export_timer!`
1097// adapts the Rust `PlatformTimer` trait's `Result<TimerHandle, _>` to the
1098// C ABI's `*mut c_void` (NULL on error). The caller's `TimerHandle`
1099// associated type must be `*mut c_void` — enforced at macro-expansion
1100// time via a `where` clause on the emitted dispatch functions.
1101// ============================================================================
1102
1103/// Emit every `nros_platform_timer_*` symbol declared in
1104/// `<nros/platform_timer.h>` by delegating to the corresponding
1105/// `PlatformTimer` trait method on `$ty`.
1106///
1107/// **Constraint:** the implementor's `TimerHandle` associated type
1108/// must be `*mut core::ffi::c_void` so the macro can pass the handle
1109/// through the C ABI unchanged. Implementations using kernel-specific
1110/// handle types should wrap them in a `*mut c_void` (typically by
1111/// `Box::into_raw` + a thin newtype) before exporting.
1112#[macro_export]
1113macro_rules! nros_platform_export_timer {
1114    ($ty:ty) => {
1115        // Compile-time guard: handle must be pointer-sized so the
1116        // transmute below is sound. PlatformTimer requires Send +
1117        // Sync + 'static, which `*mut c_void` itself fails — so
1118        // callers wrap their handle in a `#[repr(transparent)]`
1119        // newtype that implements those (PosixTimerHandle, etc.).
1120        // We round-trip through transmute at the C ABI boundary.
1121        const _: () = {
1122            if ::core::mem::size_of::<<$ty as ::nros_platform_api::PlatformTimer>::TimerHandle>()
1123                != ::core::mem::size_of::<*mut ::core::ffi::c_void>()
1124            {
1125                panic!(
1126                    "nros_platform_export_timer! requires \
1127                     PlatformTimer::TimerHandle to be pointer-sized"
1128                );
1129            }
1130        };
1131
1132        #[unsafe(no_mangle)]
1133        pub extern "C" fn nros_platform_timer_create_periodic(
1134            period_us: u32,
1135            callback: extern "C" fn(*mut ::core::ffi::c_void),
1136            user_data: *mut ::core::ffi::c_void,
1137        ) -> *mut ::core::ffi::c_void {
1138            match <$ty as ::nros_platform_api::PlatformTimer>::create_periodic(
1139                period_us, callback, user_data,
1140            ) {
1141                Ok(h) => unsafe {
1142                    ::core::mem::transmute_copy::<
1143                        <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle,
1144                        *mut ::core::ffi::c_void,
1145                    >(&::core::mem::ManuallyDrop::new(h))
1146                },
1147                Err(_) => ::core::ptr::null_mut(),
1148            }
1149        }
1150        #[unsafe(no_mangle)]
1151        pub extern "C" fn nros_platform_timer_create_oneshot(
1152            timeout_us: u32,
1153            callback: extern "C" fn(*mut ::core::ffi::c_void),
1154            user_data: *mut ::core::ffi::c_void,
1155        ) -> *mut ::core::ffi::c_void {
1156            match <$ty as ::nros_platform_api::PlatformTimer>::create_oneshot(
1157                timeout_us, callback, user_data,
1158            ) {
1159                Ok(h) => unsafe {
1160                    ::core::mem::transmute_copy::<
1161                        <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle,
1162                        *mut ::core::ffi::c_void,
1163                    >(&::core::mem::ManuallyDrop::new(h))
1164                },
1165                Err(_) => ::core::ptr::null_mut(),
1166            }
1167        }
1168        #[unsafe(no_mangle)]
1169        pub extern "C" fn nros_platform_timer_destroy(handle: *mut ::core::ffi::c_void) {
1170            let h: <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle = unsafe {
1171                ::core::mem::transmute_copy::<
1172                    *mut ::core::ffi::c_void,
1173                    <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle,
1174                >(&handle)
1175            };
1176            <$ty as ::nros_platform_api::PlatformTimer>::destroy(h)
1177        }
1178        #[unsafe(no_mangle)]
1179        pub extern "C" fn nros_platform_timer_cancel(handle: *mut ::core::ffi::c_void) -> i8 {
1180            let mut h: <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle = unsafe {
1181                ::core::mem::transmute_copy::<
1182                    *mut ::core::ffi::c_void,
1183                    <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle,
1184                >(&handle)
1185            };
1186            if <$ty as ::nros_platform_api::PlatformTimer>::cancel(&mut h) {
1187                1
1188            } else {
1189                0
1190            }
1191        }
1192    };
1193}
1194
1195/// Emit every `nros_platform_tcp_*` / `nros_platform_udp_*` /
1196/// `nros_platform_udp_mcast_*` / `nros_platform_socket_*` /
1197/// `nros_platform_network_poll` symbol declared in
1198/// `<nros/platform_net.h>` by delegating to the corresponding trait
1199/// method on `$ty`. The caller must implement `PlatformTcp`,
1200/// `PlatformUdp`, `PlatformUdpMulticast`, `PlatformSocketHelpers`, and
1201/// `PlatformNetworkPoll`.
1202#[macro_export]
1203macro_rules! nros_platform_export_net {
1204    ($ty:ty) => {
1205        // ---- TCP ----
1206        #[unsafe(no_mangle)]
1207        pub extern "C" fn nros_platform_tcp_create_endpoint(
1208            ep: *mut ::core::ffi::c_void,
1209            address: *const u8,
1210            port: *const u8,
1211        ) -> i8 {
1212            <$ty as ::nros_platform_api::PlatformTcp>::create_endpoint(ep, address, port)
1213        }
1214        #[unsafe(no_mangle)]
1215        pub extern "C" fn nros_platform_tcp_free_endpoint(ep: *mut ::core::ffi::c_void) {
1216            <$ty as ::nros_platform_api::PlatformTcp>::free_endpoint(ep)
1217        }
1218        #[unsafe(no_mangle)]
1219        pub extern "C" fn nros_platform_tcp_open(
1220            sock: *mut ::core::ffi::c_void,
1221            endpoint: *const ::core::ffi::c_void,
1222            timeout_ms: u32,
1223        ) -> i8 {
1224            <$ty as ::nros_platform_api::PlatformTcp>::open(sock, endpoint, timeout_ms)
1225        }
1226        #[unsafe(no_mangle)]
1227        pub extern "C" fn nros_platform_tcp_listen(
1228            sock: *mut ::core::ffi::c_void,
1229            endpoint: *const ::core::ffi::c_void,
1230        ) -> i8 {
1231            <$ty as ::nros_platform_api::PlatformTcp>::listen(sock, endpoint)
1232        }
1233        #[unsafe(no_mangle)]
1234        pub extern "C" fn nros_platform_tcp_close(sock: *mut ::core::ffi::c_void) {
1235            <$ty as ::nros_platform_api::PlatformTcp>::close(sock)
1236        }
1237        #[unsafe(no_mangle)]
1238        pub extern "C" fn nros_platform_tcp_read(
1239            sock: *const ::core::ffi::c_void,
1240            buf: *mut u8,
1241            len: usize,
1242        ) -> usize {
1243            <$ty as ::nros_platform_api::PlatformTcp>::read(sock, buf, len)
1244        }
1245        #[unsafe(no_mangle)]
1246        pub extern "C" fn nros_platform_tcp_read_exact(
1247            sock: *const ::core::ffi::c_void,
1248            buf: *mut u8,
1249            len: usize,
1250        ) -> usize {
1251            <$ty as ::nros_platform_api::PlatformTcp>::read_exact(sock, buf, len)
1252        }
1253        #[unsafe(no_mangle)]
1254        pub extern "C" fn nros_platform_tcp_send(
1255            sock: *const ::core::ffi::c_void,
1256            buf: *const u8,
1257            len: usize,
1258        ) -> usize {
1259            <$ty as ::nros_platform_api::PlatformTcp>::send(sock, buf, len)
1260        }
1261
1262        // ---- UDP unicast ----
1263        #[unsafe(no_mangle)]
1264        pub extern "C" fn nros_platform_udp_create_endpoint(
1265            ep: *mut ::core::ffi::c_void,
1266            address: *const u8,
1267            port: *const u8,
1268        ) -> i8 {
1269            <$ty as ::nros_platform_api::PlatformUdp>::create_endpoint(ep, address, port)
1270        }
1271        #[unsafe(no_mangle)]
1272        pub extern "C" fn nros_platform_udp_free_endpoint(ep: *mut ::core::ffi::c_void) {
1273            <$ty as ::nros_platform_api::PlatformUdp>::free_endpoint(ep)
1274        }
1275        #[unsafe(no_mangle)]
1276        pub extern "C" fn nros_platform_udp_open(
1277            sock: *mut ::core::ffi::c_void,
1278            endpoint: *const ::core::ffi::c_void,
1279            timeout_ms: u32,
1280        ) -> i8 {
1281            <$ty as ::nros_platform_api::PlatformUdp>::open(sock, endpoint, timeout_ms)
1282        }
1283        #[unsafe(no_mangle)]
1284        pub extern "C" fn nros_platform_udp_listen(
1285            sock: *mut ::core::ffi::c_void,
1286            endpoint: *const ::core::ffi::c_void,
1287            timeout_ms: u32,
1288        ) -> i8 {
1289            <$ty as ::nros_platform_api::PlatformUdp>::listen(sock, endpoint, timeout_ms)
1290        }
1291        #[unsafe(no_mangle)]
1292        pub extern "C" fn nros_platform_udp_close(sock: *mut ::core::ffi::c_void) {
1293            <$ty as ::nros_platform_api::PlatformUdp>::close(sock)
1294        }
1295        #[unsafe(no_mangle)]
1296        pub extern "C" fn nros_platform_udp_read(
1297            sock: *const ::core::ffi::c_void,
1298            buf: *mut u8,
1299            len: usize,
1300        ) -> usize {
1301            <$ty as ::nros_platform_api::PlatformUdp>::read(sock, buf, len)
1302        }
1303        #[unsafe(no_mangle)]
1304        pub extern "C" fn nros_platform_udp_read_exact(
1305            sock: *const ::core::ffi::c_void,
1306            buf: *mut u8,
1307            len: usize,
1308        ) -> usize {
1309            <$ty as ::nros_platform_api::PlatformUdp>::read_exact(sock, buf, len)
1310        }
1311        #[unsafe(no_mangle)]
1312        pub extern "C" fn nros_platform_udp_send(
1313            sock: *const ::core::ffi::c_void,
1314            buf: *const u8,
1315            len: usize,
1316            endpoint: *const ::core::ffi::c_void,
1317        ) -> usize {
1318            <$ty as ::nros_platform_api::PlatformUdp>::send(sock, buf, len, endpoint)
1319        }
1320        #[unsafe(no_mangle)]
1321        pub extern "C" fn nros_platform_udp_set_recv_timeout(
1322            sock: *const ::core::ffi::c_void,
1323            timeout_ms: u32,
1324        ) {
1325            <$ty as ::nros_platform_api::PlatformUdp>::set_recv_timeout(sock, timeout_ms)
1326        }
1327
1328        // ---- UDP multicast ----
1329        #[unsafe(no_mangle)]
1330        pub extern "C" fn nros_platform_udp_mcast_open(
1331            sock: *mut ::core::ffi::c_void,
1332            endpoint: *const ::core::ffi::c_void,
1333            lep: *mut ::core::ffi::c_void,
1334            timeout_ms: u32,
1335            iface: *const u8,
1336        ) -> i8 {
1337            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_open(
1338                sock, endpoint, lep, timeout_ms, iface,
1339            )
1340        }
1341        #[unsafe(no_mangle)]
1342        pub extern "C" fn nros_platform_udp_mcast_listen(
1343            sock: *mut ::core::ffi::c_void,
1344            endpoint: *const ::core::ffi::c_void,
1345            timeout_ms: u32,
1346            iface: *const u8,
1347            join: *const u8,
1348        ) -> i8 {
1349            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_listen(
1350                sock, endpoint, timeout_ms, iface, join,
1351            )
1352        }
1353        #[unsafe(no_mangle)]
1354        pub extern "C" fn nros_platform_udp_mcast_close(
1355            sockrecv: *mut ::core::ffi::c_void,
1356            socksend: *mut ::core::ffi::c_void,
1357            rep: *const ::core::ffi::c_void,
1358            lep: *const ::core::ffi::c_void,
1359        ) {
1360            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_close(
1361                sockrecv, socksend, rep, lep,
1362            )
1363        }
1364        #[unsafe(no_mangle)]
1365        pub extern "C" fn nros_platform_udp_mcast_read(
1366            sock: *const ::core::ffi::c_void,
1367            buf: *mut u8,
1368            len: usize,
1369            lep: *const ::core::ffi::c_void,
1370            addr: *mut ::core::ffi::c_void,
1371        ) -> usize {
1372            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_read(
1373                sock, buf, len, lep, addr,
1374            )
1375        }
1376        #[unsafe(no_mangle)]
1377        pub extern "C" fn nros_platform_udp_mcast_read_exact(
1378            sock: *const ::core::ffi::c_void,
1379            buf: *mut u8,
1380            len: usize,
1381            lep: *const ::core::ffi::c_void,
1382            addr: *mut ::core::ffi::c_void,
1383        ) -> usize {
1384            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_read_exact(
1385                sock, buf, len, lep, addr,
1386            )
1387        }
1388        #[unsafe(no_mangle)]
1389        pub extern "C" fn nros_platform_udp_mcast_send(
1390            sock: *const ::core::ffi::c_void,
1391            buf: *const u8,
1392            len: usize,
1393            endpoint: *const ::core::ffi::c_void,
1394        ) -> usize {
1395            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_send(sock, buf, len, endpoint)
1396        }
1397
1398        // ---- Socket helpers ----
1399        #[unsafe(no_mangle)]
1400        pub extern "C" fn nros_platform_socket_set_non_blocking(
1401            sock: *const ::core::ffi::c_void,
1402        ) -> i8 {
1403            <$ty as ::nros_platform_api::PlatformSocketHelpers>::set_non_blocking(sock)
1404        }
1405        #[unsafe(no_mangle)]
1406        pub extern "C" fn nros_platform_socket_accept(
1407            sock_in: *const ::core::ffi::c_void,
1408            sock_out: *mut ::core::ffi::c_void,
1409        ) -> i8 {
1410            <$ty as ::nros_platform_api::PlatformSocketHelpers>::accept(sock_in, sock_out)
1411        }
1412        #[unsafe(no_mangle)]
1413        pub extern "C" fn nros_platform_socket_close(sock: *mut ::core::ffi::c_void) {
1414            <$ty as ::nros_platform_api::PlatformSocketHelpers>::close(sock)
1415        }
1416        #[unsafe(no_mangle)]
1417        pub extern "C" fn nros_platform_socket_wait_event(
1418            peers: *mut ::core::ffi::c_void,
1419            mutex: *mut ::core::ffi::c_void,
1420        ) -> i8 {
1421            <$ty as ::nros_platform_api::PlatformSocketHelpers>::wait_event(peers, mutex)
1422        }
1423
1424        // ---- Network poll ----
1425        #[unsafe(no_mangle)]
1426        pub extern "C" fn nros_platform_network_poll() {
1427            <$ty as ::nros_platform_api::PlatformNetworkPoll>::network_poll()
1428        }
1429    };
1430}
1431
1432// ============================================================================
1433// Test-only self-export
1434// ----------------------------------------------------------------------------
1435// `cargo test -p nros-platform-cffi` builds a test binary that links the
1436// rlib. The `unsafe extern "C"` declarations above would fail to link
1437// without definitions; we satisfy them by invoking the macro on a dummy
1438// `TestPlatform` ZST defined here. This doubles as a smoke test that
1439// every macro arm expands and that the trait dispatch resolves.
1440//
1441// Real platform crates supply their own definitions via the same macro
1442// and never compile this module (it is gated on `cfg(test)`).
1443// ============================================================================
1444
1445#[cfg(all(test, not(feature = "c-stub-test"), not(feature = "posix-c-port")))]
1446mod test_self_export {
1447    use core::ffi::c_void;
1448    use nros_platform_api::{
1449        PlatformAlloc, PlatformClock, PlatformRandom, PlatformSleep, PlatformThreading,
1450        PlatformTime, PlatformYield,
1451    };
1452
1453    pub struct TestPlatform;
1454
1455    impl PlatformClock for TestPlatform {
1456        fn clock_ns() -> u64 {
1457            0
1458        }
1459        fn clock_resolution_ns() -> u64 {
1460            1
1461        }
1462    }
1463    impl PlatformAlloc for TestPlatform {
1464        fn alloc(_: usize) -> *mut c_void {
1465            core::ptr::null_mut()
1466        }
1467        fn realloc(_: *mut c_void, _: usize) -> *mut c_void {
1468            core::ptr::null_mut()
1469        }
1470        fn dealloc(_: *mut c_void) {}
1471    }
1472    impl PlatformSleep for TestPlatform {
1473        fn sleep_us(_: usize) {}
1474        fn sleep_ms(_: usize) {}
1475        fn sleep_s(_: usize) {}
1476    }
1477    impl PlatformYield for TestPlatform {
1478        fn yield_now() {}
1479    }
1480    impl PlatformRandom for TestPlatform {
1481        fn random_u8() -> u8 {
1482            0
1483        }
1484        fn random_u16() -> u16 {
1485            0
1486        }
1487        fn random_u32() -> u32 {
1488            0
1489        }
1490        fn random_u64() -> u64 {
1491            0
1492        }
1493        fn random_fill(_: *mut c_void, _: usize) {}
1494    }
1495    impl PlatformTime for TestPlatform {
1496        fn time_now_ns() -> u64 {
1497            0
1498        }
1499    }
1500    impl PlatformThreading for TestPlatform {
1501        fn task_init(
1502            _: *mut c_void,
1503            _: *mut c_void,
1504            _: Option<unsafe extern "C" fn(*mut c_void) -> *mut c_void>,
1505            _: *mut c_void,
1506        ) -> i8 {
1507            -1
1508        }
1509        fn task_join(_: *mut c_void) -> i8 {
1510            -1
1511        }
1512        fn task_detach(_: *mut c_void) -> i8 {
1513            -1
1514        }
1515        fn task_cancel(_: *mut c_void) -> i8 {
1516            -1
1517        }
1518        fn task_exit() {}
1519        fn task_free(_: *mut *mut c_void) {}
1520        fn mutex_init(_: *mut c_void) -> i8 {
1521            0
1522        }
1523        fn mutex_drop(_: *mut c_void) -> i8 {
1524            0
1525        }
1526        fn mutex_lock(_: *mut c_void) -> i8 {
1527            0
1528        }
1529        fn mutex_try_lock(_: *mut c_void) -> i8 {
1530            0
1531        }
1532        fn mutex_unlock(_: *mut c_void) -> i8 {
1533            0
1534        }
1535        fn mutex_rec_init(_: *mut c_void) -> i8 {
1536            0
1537        }
1538        fn mutex_rec_drop(_: *mut c_void) -> i8 {
1539            0
1540        }
1541        fn mutex_rec_lock(_: *mut c_void) -> i8 {
1542            0
1543        }
1544        fn mutex_rec_try_lock(_: *mut c_void) -> i8 {
1545            0
1546        }
1547        fn mutex_rec_unlock(_: *mut c_void) -> i8 {
1548            0
1549        }
1550        fn condvar_init(_: *mut c_void) -> i8 {
1551            0
1552        }
1553        fn condvar_drop(_: *mut c_void) -> i8 {
1554            0
1555        }
1556        fn condvar_signal(_: *mut c_void) -> i8 {
1557            0
1558        }
1559        fn condvar_signal_all(_: *mut c_void) -> i8 {
1560            0
1561        }
1562        fn condvar_wait_until(_: *mut c_void, _: *mut c_void, _: u64) -> i8 {
1563            0
1564        }
1565    }
1566    impl ::nros_platform_api::PlatformCriticalSection for TestPlatform {
1567        fn acquire() -> u32 {
1568            0
1569        }
1570        fn release(_: u32) {}
1571    }
1572
1573    /// Pointer-sized newtype wrapping `*mut c_void` so the
1574    /// PlatformTimer Send + Sync + 'static bound is satisfied.
1575    /// The transmute inside `nros_platform_export_timer!` rests on
1576    /// this being `#[repr(transparent)]` over a pointer.
1577    #[repr(transparent)]
1578    #[derive(Clone, Copy)]
1579    pub struct TestTimerHandle(pub *mut c_void);
1580    unsafe impl Send for TestTimerHandle {}
1581    unsafe impl Sync for TestTimerHandle {}
1582
1583    impl ::nros_platform_api::PlatformTimer for TestPlatform {
1584        type TimerHandle = TestTimerHandle;
1585        // create_periodic / create_oneshot / destroy / cancel inherit
1586        // the trait's default impls (return TimerError::Unsupported /
1587        // no-op destroy / false cancel) — fine for export-emission
1588        // verification.
1589    }
1590
1591    crate::nros_platform_export!(TestPlatform);
1592    crate::nros_platform_export_timer!(TestPlatform);
1593
1594    #[test]
1595    fn macro_expansion_dispatches() {
1596        // Touch every group through the FFI surface to confirm the
1597        // generated symbols are reachable and dispatch resolves.
1598        assert_eq!(super::CffiPlatform::clock_ns(), 0);
1599        assert_eq!(
1600            <super::CffiPlatform as ::nros_platform_api::PlatformAlloc>::alloc(0),
1601            core::ptr::null_mut(),
1602        );
1603        <super::CffiPlatform as ::nros_platform_api::PlatformYield>::yield_now();
1604    }
1605
1606    #[test]
1607    fn timer_macro_emits() {
1608        // Default impl returns Unsupported → null handle.
1609        let h = unsafe {
1610            super::nros_platform_timer_create_periodic(
1611                1000,
1612                Some(noop_callback),
1613                core::ptr::null_mut(),
1614            )
1615        };
1616        assert!(h.is_null(), "default Unsupported impl must surface as NULL");
1617    }
1618
1619    // bindgen wraps C function-pointer parameters in `Option`, and the
1620    // pointee is `unsafe extern "C"` — issue #545.
1621    unsafe extern "C" fn noop_callback(_: *mut c_void) {}
1622}