Skip to main content

nylon_ring/
lib.rs

1//! ABI-facing types and plugin-definition helpers for Nylon Ring.
2//!
3//! Borrowed views such as [`NrStr`] and [`NrBytes`] do not carry Rust
4//! lifetimes across the ABI boundary. Their accessors are therefore unsafe:
5//! callers must guarantee that the referenced memory remains valid while the
6//! returned borrow is used.
7
8use std::ffi::c_void;
9use std::fmt;
10
11pub mod plugin;
12
13pub use plugin::{AsyncSession, AsyncTask, Plugin, Reply, Session};
14
15/// The single ABI version implemented by this crate, negotiated through the
16/// `nylon_ring_get_plugin` export. See `docs/ABI_EVOLUTION.md`.
17pub const ABI_VERSION: u32 = 2;
18
19/// Status code passed across the Nylon Ring ABI.
20///
21/// This is a transparent integer wrapper rather than a Rust enum so an
22/// unknown value from a newer plugin remains well-defined. Values `7..=u32::MAX`
23/// are reserved for future ABI versions.
24#[repr(transparent)]
25#[derive(Copy, Clone, PartialEq, Eq, Hash)]
26pub struct NrStatus(u32);
27
28#[allow(non_upper_case_globals)]
29impl NrStatus {
30    pub const Ok: Self = Self(0);
31    pub const Err: Self = Self(1);
32    pub const Invalid: Self = Self(2);
33    pub const Unsupported: Self = Self(3);
34    /// Streaming completed normally.
35    pub const StreamEnd: Self = Self(4);
36    /// A plugin panic was contained at the FFI boundary.
37    pub const Panic: Self = Self(5);
38    /// A bounded stream queue cannot currently accept another frame.
39    pub const Backpressure: Self = Self(6);
40
41    /// Creates a status from its stable wire value.
42    pub const fn from_raw(value: u32) -> Self {
43        Self(value)
44    }
45
46    /// Returns the stable wire value.
47    pub const fn as_raw(self) -> u32 {
48        self.0
49    }
50
51    /// Returns whether this status terminates a response stream.
52    pub const fn is_terminal(self) -> bool {
53        matches!(
54            self,
55            Self::Err | Self::Invalid | Self::Unsupported | Self::StreamEnd | Self::Panic
56        )
57    }
58}
59
60impl fmt::Debug for NrStatus {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        let name = match *self {
63            Self::Ok => "Ok",
64            Self::Err => "Err",
65            Self::Invalid => "Invalid",
66            Self::Unsupported => "Unsupported",
67            Self::StreamEnd => "StreamEnd",
68            Self::Panic => "Panic",
69            Self::Backpressure => "Backpressure",
70            Self(value) => return f.debug_tuple("Unknown").field(&value).finish(),
71        };
72        f.write_str(name)
73    }
74}
75
76/// A UTF-8 string slice with a pointer and length.
77/// This struct is `#[repr(C)]` and ABI-stable.
78///
79/// On 64-bit targets `_reserved` occupies the four bytes after `len` that
80/// would otherwise be implicit padding. Producers must set it to zero.
81#[repr(C)]
82#[derive(Debug, Copy, Clone, Default)]
83pub struct NrStr {
84    pub ptr: *const u8,
85    pub len: u32,
86    pub _reserved: u32,
87}
88
89/// A byte slice with a pointer and length.
90/// This struct is `#[repr(C)]` and ABI-stable.
91#[repr(C)]
92#[derive(Debug, Copy, Clone, Default)]
93pub struct NrBytes {
94    pub ptr: *const u8,
95    pub len: u64,
96}
97
98/// A key-value pair of strings.
99#[repr(C)]
100#[derive(Debug, Copy, Clone, Default)]
101pub struct NrKV {
102    pub key: NrStr,
103    pub value: NrStr,
104}
105
106/// Error returned when an ABI string or byte view is malformed.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub enum NrViewError {
109    /// A non-empty view has a null pointer.
110    NullPointer,
111    /// The ABI length cannot be represented by this platform's `usize`.
112    LengthOverflow,
113    /// A string view does not contain valid UTF-8.
114    InvalidUtf8(std::str::Utf8Error),
115}
116
117impl fmt::Display for NrViewError {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        match self {
120            Self::NullPointer => f.write_str("non-empty ABI view has a null pointer"),
121            Self::LengthOverflow => f.write_str("ABI view length exceeds usize"),
122            Self::InvalidUtf8(error) => write!(f, "ABI string is not valid UTF-8: {error}"),
123        }
124    }
125}
126
127impl std::error::Error for NrViewError {
128    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
129        match self {
130            Self::InvalidUtf8(error) => Some(error),
131            Self::NullPointer | Self::LengthOverflow => None,
132        }
133    }
134}
135
136/// A key-value pair with any type as value.
137/// This struct is `#[repr(C)]` and ABI-stable.
138#[repr(C)]
139#[derive(Debug, Default)]
140pub struct NrKVAny {
141    key: NrStr,
142    key_storage: NrVec<u8>,
143    pub value: NrAny,
144}
145
146/// Index slot for hash table lookup.
147/// This struct is `#[repr(C)]` and ABI-stable.
148#[repr(C)]
149#[derive(Debug, Copy, Clone, Default)]
150pub struct NrIndexSlot {
151    pub hash: u64,
152    pub entry_idx: u32, // index into entries
153    pub state: u8,      // 0=empty, 1=full, 2=tombstone
154    pub _pad: [u8; 3],
155}
156
157/// A map/dictionary type implemented as a vector of key-value pairs with hash index.
158/// This struct is `#[repr(C)]` and ABI-stable.
159#[repr(C)]
160#[derive(Debug, Default)]
161pub struct NrMap {
162    entries: NrVec<NrKVAny>,
163    index: NrVec<NrIndexSlot>, // hash index table
164    used: u32,                 // number of full slots
165    tomb: u32,                 // number of tombstones
166}
167
168/// A type-erased value that can hold any data type.
169/// This struct is `#[repr(C)]` and ABI-stable.
170#[repr(C)]
171#[derive(Debug)]
172pub struct NrAny {
173    /// Pointer to the data
174    data: *mut c_void,
175    /// Size of the data in bytes
176    size: u64,
177    /// Type identifier (user-defined tag)
178    type_tag: u32,
179    /// Optional clone function pointer.
180    clone_fn: Option<unsafe extern "C" fn(*const c_void) -> *mut c_void>,
181    /// Optional destructor function pointer (can be null)
182    drop_fn: Option<unsafe extern "C" fn(*mut c_void)>,
183}
184
185/// A vector with a pointer, length, and capacity.
186/// This struct is `#[repr(C)]` and ABI-stable.
187///
188/// Owned values carry a producer-side drop callback so the receiving module
189/// never deallocates them with the wrong allocator. Borrowed foreign values
190/// use `owned = 0` and are never freed by this type. Prefer [`NrBytes`] for
191/// ordinary borrowed input.
192#[repr(C)]
193#[derive(Debug)]
194pub struct NrVec<T> {
195    ptr: *mut T,
196    len: usize,
197    cap: usize,
198    owned: u8,
199    _reserved: [u8; 7],
200    drop_fn: Option<unsafe extern "C" fn(*mut T, usize, usize)>,
201}
202
203impl<T> Default for NrVec<T> {
204    fn default() -> Self {
205        Self {
206            ptr: std::ptr::null_mut(),
207            len: 0,
208            cap: 0,
209            owned: 0,
210            _reserved: [0; 7],
211            drop_fn: None,
212        }
213    }
214}
215
216impl Default for NrAny {
217    fn default() -> Self {
218        Self {
219            data: std::ptr::null_mut(),
220            size: 0,
221            type_tag: 0,
222            clone_fn: None,
223            drop_fn: None,
224        }
225    }
226}
227
228/// A tuple of two elements.
229/// This struct is `#[repr(C)]` and ABI-stable.
230#[repr(C)]
231#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
232pub struct NrTuple<A, B> {
233    pub a: A,
234    pub b: B,
235}
236
237/// A callee-owned byte buffer handed across the ABI without a copy.
238///
239/// Contract: the consumer calls `release` exactly once when it is done with
240/// the bytes (possibly from a different thread than the producer), and the
241/// producer keeps `ptr..ptr+len` valid and immutable until then. A `None`
242/// release means there is nothing to free (static or arena-backed data).
243#[repr(C)]
244#[derive(Debug)]
245pub struct NrOwnedBytes {
246    pub ptr: *const u8,
247    pub len: u64,
248    pub owner_ctx: *mut c_void,
249    pub release: Option<unsafe extern "C" fn(owner_ctx: *mut c_void, ptr: *const u8, len: u64)>,
250}
251
252impl NrOwnedBytes {
253    /// An empty payload with nothing to release.
254    pub const fn empty() -> Self {
255        Self {
256            ptr: std::ptr::null(),
257            len: 0,
258            owner_ctx: std::ptr::null_mut(),
259            release: None,
260        }
261    }
262
263    /// Borrows static data; no release callback is needed.
264    pub const fn from_static(bytes: &'static [u8]) -> Self {
265        Self {
266            ptr: bytes.as_ptr(),
267            len: bytes.len() as u64,
268            owner_ctx: std::ptr::null_mut(),
269            release: None,
270        }
271    }
272
273    /// Transfers ownership of a `Vec` allocation; the consumer's release
274    /// call frees it in the producer's allocator (capacity rides along in
275    /// `owner_ctx`, so this needs no extra allocation).
276    pub fn from_vec(vec: Vec<u8>) -> Self {
277        unsafe extern "C" fn release_vec(owner_ctx: *mut c_void, ptr: *const u8, len: u64) {
278            if !ptr.is_null() {
279                // SAFETY: reconstructs exactly the parts taken in from_vec.
280                drop(unsafe {
281                    Vec::from_raw_parts(ptr.cast_mut(), len as usize, owner_ctx as usize)
282                });
283            }
284        }
285        let mut vec = std::mem::ManuallyDrop::new(vec);
286        Self {
287            ptr: vec.as_mut_ptr(),
288            len: vec.len() as u64,
289            owner_ctx: vec.capacity() as *mut c_void,
290            release: Some(release_vec),
291        }
292    }
293}
294
295/// A host-owned output buffer leased to the plugin.
296///
297/// Contract: the plugin may write `ptr..ptr+cap` until it either commits the
298/// lease (passing `token` back) or responds through any other channel for
299/// the same `sid` — both consume the lease, and touching the buffer after
300/// that is undefined behavior. A failed acquire returns a null `ptr`; the
301/// plugin must fall back to another response path. At most one lease per
302/// `sid` is outstanding at a time.
303#[repr(C)]
304#[derive(Debug)]
305pub struct NrBufferLease {
306    pub ptr: *mut u8,
307    pub cap: u64,
308    pub token: u64,
309}
310
311impl NrBufferLease {
312    /// The lease returned when the host cannot grant one.
313    pub const fn failed() -> Self {
314        Self {
315            ptr: std::ptr::null_mut(),
316            cap: 0,
317            token: 0,
318        }
319    }
320
321    /// Whether the host declined the lease.
322    pub fn is_failed(&self) -> bool {
323        self.ptr.is_null()
324    }
325}
326
327/// Host callback table.
328#[repr(C)]
329#[derive(Debug, Copy, Clone)]
330pub struct NrHostVTable {
331    /// Delivers a response the host copies into its own memory. The payload
332    /// may be borrowed (`owned = 0`); an owned payload is freed through its
333    /// `drop_fn` after the copy.
334    pub send_result: unsafe extern "C" fn(
335        host_ctx: *mut c_void,
336        sid: u64,
337        status: NrStatus,
338        payload: NrVec<u8>,
339    ) -> NrStatus,
340    /// Delivers a response without a host-side copy; the host takes
341    /// ownership of the payload and calls its release exactly once.
342    pub send_result_owned: unsafe extern "C" fn(
343        host_ctx: *mut c_void,
344        sid: u64,
345        status: NrStatus,
346        payload: NrOwnedBytes,
347    ) -> NrStatus,
348    /// Leases a host-owned buffer of at least `capacity` bytes for the
349    /// response to `sid`. Fails (null `ptr`) for unknown sids, streaming
350    /// sids, and sids that already hold an outstanding lease.
351    pub acquire_result_buffer:
352        unsafe extern "C" fn(host_ctx: *mut c_void, sid: u64, capacity: u64) -> NrBufferLease,
353    /// Commits the leased buffer as the response to `sid`:
354    /// `initialized_len` bytes (at most the leased capacity) must have been
355    /// written. On `Ok` the buffer now belongs to the host; on `Invalid`
356    /// with a live lease (bad token or oversized length) the lease stays
357    /// valid and may be committed again.
358    pub commit_result_buffer: unsafe extern "C" fn(
359        host_ctx: *mut c_void,
360        sid: u64,
361        status: NrStatus,
362        token: u64,
363        initialized_len: u64,
364    ) -> NrStatus,
365}
366
367/// Returned by `resolve_entry` for a name the plugin does not export.
368pub const NR_ENTRY_UNKNOWN: u32 = u32::MAX;
369
370/// Plugin function table.
371#[repr(C)]
372#[derive(Debug, Copy, Clone)]
373pub struct NrPluginVTable {
374    pub init: Option<
375        unsafe extern "C" fn(host_ctx: *mut c_void, host_vtable: *const NrHostVTable) -> NrStatus,
376    >,
377
378    pub handle: Option<unsafe extern "C" fn(entry: NrStr, sid: u64, payload: NrBytes) -> NrStatus>,
379
380    pub shutdown: Option<unsafe extern "C" fn()>,
381
382    pub stream_data: Option<unsafe extern "C" fn(sid: u64, data: NrBytes) -> NrStatus>,
383
384    pub stream_close: Option<unsafe extern "C" fn(sid: u64) -> NrStatus>,
385
386    /// Maps an entry name to a stable dispatch id, or [`NR_ENTRY_UNKNOWN`].
387    /// The id stays valid for the lifetime of the loaded plugin instance;
388    /// hosts resolve once and then call through `handle_by_id`, skipping
389    /// the per-call name comparison.
390    pub resolve_entry: Option<unsafe extern "C" fn(entry: NrStr) -> u32>,
391
392    /// Dispatches by an id from `resolve_entry`; an id the plugin never
393    /// issued reports `Invalid`. Both fields must be present together.
394    pub handle_by_id: Option<unsafe extern "C" fn(id: u32, sid: u64, payload: NrBytes) -> NrStatus>,
395}
396
397/// Metadata exported by the plugin through `nylon_ring_get_plugin`.
398#[repr(C)]
399#[derive(Debug, Copy, Clone)]
400pub struct NrPluginInfo {
401    pub abi_version: u32,
402    pub struct_size: u32,
403
404    pub name: NrStr,
405    pub version: NrStr,
406
407    pub plugin_ctx: *mut c_void,
408    pub vtable: *const NrPluginVTable,
409}
410
411/// Host extension table for state management.
412/// This is an optional extension that does not modify the core ABI.
413#[repr(C)]
414#[derive(Debug, Copy, Clone)]
415pub struct NrHostExt {
416    /// Set state for a given sid and key.
417    /// Returns a status code indicating whether the state was stored.
418    pub set_state: unsafe extern "C" fn(
419        host_ctx: *mut c_void,
420        sid: u64,
421        key: NrStr,
422        value: NrBytes,
423    ) -> NrStatus,
424
425    /// Get state for a given sid and key.
426    /// Returns an owned, empty vector if the key is not found.
427    pub get_state: unsafe extern "C" fn(host_ctx: *mut c_void, sid: u64, key: NrStr) -> NrVec<u8>,
428}
429
430// Safety: NrHostExt is ABI-stable data carrier.
431unsafe impl Send for NrHostExt {}
432unsafe impl Sync for NrHostExt {}
433
434#[macro_export]
435macro_rules! define_plugin {
436    (
437        init: $init_fn:path,
438        shutdown: $shutdown_fn:path,
439        entries: {
440            $($entry_name:literal => $handler_fn:path),* $(,)?
441        }
442        $(, stream_handlers: {
443            data: $stream_data_fn:path,
444            close: $stream_close_fn:path $(,)?
445        })?
446    ) => {
447        // Static VTable
448        static PLUGIN_VTABLE: $crate::NrPluginVTable = $crate::NrPluginVTable {
449            init: Some(plugin_init_wrapper),
450            handle: Some(plugin_handle_wrapper),
451            shutdown: Some(plugin_shutdown_wrapper),
452            stream_data: Some(plugin_stream_data_wrapper),
453            stream_close: Some(plugin_stream_close_wrapper),
454            resolve_entry: Some(plugin_resolve_entry_wrapper),
455            handle_by_id: Some(plugin_handle_by_id_wrapper),
456        };
457
458        // Static Plugin Info
459        static PLUGIN_INFO: $crate::NrPluginInfo = $crate::NrPluginInfo {
460            abi_version: $crate::ABI_VERSION,
461            struct_size: std::mem::size_of::<$crate::NrPluginInfo>() as u32,
462            name: $crate::NrStr::from_static(env!("CARGO_PKG_NAME")),
463            version: $crate::NrStr::from_static(env!("CARGO_PKG_VERSION")),
464            plugin_ctx: std::ptr::null_mut(),
465            vtable: &PLUGIN_VTABLE,
466        };
467
468        // Exported Entry Point
469        #[unsafe(no_mangle)]
470        pub extern "C" fn nylon_ring_get_plugin() -> *const $crate::NrPluginInfo {
471            &PLUGIN_INFO
472        }
473
474        // Wrappers
475        unsafe extern "C" fn plugin_init_wrapper(
476            host_ctx: *mut std::ffi::c_void,
477            host_vtable: *const $crate::NrHostVTable,
478        ) -> $crate::NrStatus {
479            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
480                $init_fn(host_ctx, host_vtable)
481            }))
482            .unwrap_or($crate::NrStatus::Panic)
483        }
484
485        unsafe extern "C" fn plugin_shutdown_wrapper() {
486            let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
487                $shutdown_fn();
488            }));
489        }
490
491        unsafe extern "C" fn plugin_handle_wrapper(
492            entry: $crate::NrStr,
493            sid: u64,
494            payload: $crate::NrBytes,
495        ) -> $crate::NrStatus {
496            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
497                // Entry names are matched byte-wise: a non-UTF-8 entry cannot
498                // equal any literal, so it still falls through to Invalid
499                // without paying for per-call UTF-8 validation.
500                let entry_bytes = match unsafe { entry.as_bytes() } {
501                    Ok(entry) => entry,
502                    Err(_) => return $crate::NrStatus::Invalid,
503                };
504                $(
505                    if entry_bytes == $entry_name.as_bytes() {
506                        return unsafe { $handler_fn(sid, payload) };
507                    }
508                )*
509                $crate::NrStatus::Invalid
510            }))
511            .unwrap_or($crate::NrStatus::Panic)
512        }
513
514        // Integer entry dispatch: the dispatch id is the entry's
515        // index in the `entries` list, resolved once by the host.
516        static PLUGIN_ENTRY_NAMES: &[&[u8]] = &[$($entry_name.as_bytes()),*];
517        static PLUGIN_ENTRY_HANDLERS: &[unsafe fn(u64, $crate::NrBytes) -> $crate::NrStatus] =
518            &[$($handler_fn as unsafe fn(u64, $crate::NrBytes) -> $crate::NrStatus),*];
519
520        unsafe extern "C" fn plugin_resolve_entry_wrapper(entry: $crate::NrStr) -> u32 {
521            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
522                let entry_bytes = match unsafe { entry.as_bytes() } {
523                    Ok(entry) => entry,
524                    Err(_) => return $crate::NR_ENTRY_UNKNOWN,
525                };
526                for (id, name) in PLUGIN_ENTRY_NAMES.iter().enumerate() {
527                    if entry_bytes == *name {
528                        return id as u32;
529                    }
530                }
531                $crate::NR_ENTRY_UNKNOWN
532            }))
533            .unwrap_or($crate::NR_ENTRY_UNKNOWN)
534        }
535
536        unsafe extern "C" fn plugin_handle_by_id_wrapper(
537            id: u32,
538            sid: u64,
539            payload: $crate::NrBytes,
540        ) -> $crate::NrStatus {
541            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
542                match PLUGIN_ENTRY_HANDLERS.get(id as usize) {
543                    Some(handler) => unsafe { handler(sid, payload) },
544                    None => $crate::NrStatus::Invalid,
545                }
546            }))
547            .unwrap_or($crate::NrStatus::Panic)
548        }
549
550        unsafe extern "C" fn plugin_stream_data_wrapper(
551            sid: u64,
552            data: $crate::NrBytes,
553        ) -> $crate::NrStatus {
554            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
555                let _ = (sid, data);
556                $(
557                    return unsafe { $stream_data_fn(sid, data) };
558                )?
559                #[allow(unreachable_code)]
560                $crate::NrStatus::Unsupported
561            }))
562            .unwrap_or($crate::NrStatus::Panic)
563        }
564
565        unsafe extern "C" fn plugin_stream_close_wrapper(
566            sid: u64,
567        ) -> $crate::NrStatus {
568            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
569                let _ = sid;
570                $(
571                    return unsafe { $stream_close_fn(sid) };
572                )?
573                #[allow(unreachable_code)]
574                $crate::NrStatus::Unsupported
575            }))
576            .unwrap_or($crate::NrStatus::Panic)
577        }
578    };
579}
580
581impl NrStr {
582    /// Creates a borrowed ABI string view.
583    ///
584    /// The returned value must not be used to access the string after `s` is
585    /// invalidated. Use [`NrStr::as_str`] only while the source is alive.
586    pub fn new(s: &str) -> Self {
587        let len = u32::try_from(s.len()).expect("NrStr cannot represent strings larger than 4 GiB");
588        Self {
589            ptr: s.as_ptr(),
590            len,
591            _reserved: 0,
592        }
593    }
594
595    /// Creates a borrowed ABI view from a static string.
596    pub const fn from_static(s: &'static str) -> Self {
597        assert!(
598            s.len() <= u32::MAX as usize,
599            "static string is too large for NrStr"
600        );
601        Self {
602            ptr: s.as_ptr(),
603            len: s.len() as u32,
604            _reserved: 0,
605        }
606    }
607
608    /// Reads this ABI view as UTF-8.
609    ///
610    /// # Safety
611    ///
612    /// For a non-empty view, `ptr` must be valid for reads of `len` bytes for
613    /// the lifetime of the returned reference. The pointed-to memory must not
614    /// be mutated while that reference exists.
615    pub unsafe fn as_str<'a>(&self) -> Result<&'a str, NrViewError> {
616        let bytes = unsafe { view_bytes(self.ptr, u64::from(self.len))? };
617        std::str::from_utf8(bytes).map_err(NrViewError::InvalidUtf8)
618    }
619
620    /// Reads this ABI view's raw bytes without validating UTF-8.
621    ///
622    /// # Safety
623    ///
624    /// For a non-empty view, `ptr` must be valid for reads of `len` bytes for
625    /// the lifetime of the returned reference. The pointed-to memory must not
626    /// be mutated while that reference exists.
627    pub unsafe fn as_bytes<'a>(&self) -> Result<&'a [u8], NrViewError> {
628        unsafe { view_bytes(self.ptr, u64::from(self.len)) }
629    }
630
631    /// Returns whether this view contains no bytes.
632    pub const fn is_empty(&self) -> bool {
633        self.len == 0
634    }
635
636    /// Returns the view length in bytes.
637    pub const fn len(&self) -> usize {
638        self.len as usize
639    }
640
641    /// Returns the underlying raw pointer.
642    pub const fn as_ptr(&self) -> *const u8 {
643        self.ptr
644    }
645
646    /// Resets this view without freeing the borrowed memory.
647    pub fn clear(&mut self) {
648        self.ptr = std::ptr::null();
649        self.len = 0;
650        self._reserved = 0;
651    }
652}
653
654unsafe fn view_bytes<'a>(ptr: *const u8, len: u64) -> Result<&'a [u8], NrViewError> {
655    if len == 0 {
656        return Ok(&[]);
657    }
658    if ptr.is_null() {
659        return Err(NrViewError::NullPointer);
660    }
661    let len = usize::try_from(len).map_err(|_| NrViewError::LengthOverflow)?;
662    Ok(unsafe { std::slice::from_raw_parts(ptr, len) })
663}
664
665impl NrBytes {
666    /// Creates a borrowed ABI byte view.
667    pub fn from_slice(s: &[u8]) -> Self {
668        Self {
669            ptr: s.as_ptr(),
670            len: u64::try_from(s.len()).expect("NrBytes length does not fit in u64"),
671        }
672    }
673
674    /// Reads this ABI byte view.
675    ///
676    /// # Safety
677    ///
678    /// For a non-empty view, `ptr` must be valid for reads of `len` bytes for
679    /// the lifetime of the returned reference. The pointed-to memory must not
680    /// be mutated while that reference exists.
681    pub unsafe fn as_slice<'a>(&self) -> Result<&'a [u8], NrViewError> {
682        unsafe { view_bytes(self.ptr, self.len) }
683    }
684
685    /// Returns whether this view contains no bytes.
686    pub const fn is_empty(&self) -> bool {
687        self.len == 0
688    }
689
690    /// Returns the view length in bytes.
691    pub const fn len(&self) -> u64 {
692        self.len
693    }
694}
695
696impl Clone for NrKVAny {
697    fn clone(&self) -> Self {
698        Self::new(self.key(), self.value.clone())
699    }
700}
701
702impl Clone for NrMap {
703    fn clone(&self) -> Self {
704        Self {
705            entries: self.entries.clone(),
706            index: self.index.clone(),
707            used: self.used,
708            tomb: self.tomb,
709        }
710    }
711}
712
713impl Clone for NrAny {
714    /// Clone an `NrAny`.
715    ///
716    /// # Safety / ABI v1 limitation
717    ///
718    /// `NrAny` is type-erased and ABI v1 has no `clone_fn` in the struct.
719    /// Cloning a value that owns heap resources (i.e. has a `drop_fn`) by
720    /// memcpy would shallow-copy any inner pointers and cause a double-free
721    /// when both copies are dropped. Therefore:
722    ///
723    /// * Null payload  → returns `default()` (null).
724    /// * `drop_fn = None` (POD payload) → safe deep copy via byte memcpy.
725    /// * `drop_fn = Some(_)` → **panics**, because we cannot safely deep-copy
726    ///   without a type-aware `clone_fn`. Use `drop_fn = None` and a POD
727    ///   representation, or wait for ABI v2 which adds `clone_fn`.
728    fn clone(&self) -> Self {
729        if self.data.is_null() {
730            return Self::default();
731        }
732        let clone_fn = self
733            .clone_fn
734            .expect("non-null NrAny values always have a clone function");
735        let data = unsafe { clone_fn(self.data.cast_const()) };
736        assert!(!data.is_null(), "NrAny clone callback failed");
737        Self {
738            data,
739            size: self.size,
740            type_tag: self.type_tag,
741            clone_fn: self.clone_fn,
742            drop_fn: self.drop_fn,
743        }
744    }
745}
746
747impl<T: Clone> Clone for NrVec<T> {
748    fn clone(&self) -> Self {
749        if self.len == 0 {
750            return Self::default();
751        }
752        let v = self.as_slice().to_vec();
753        Self::from_vec(v)
754    }
755}
756
757impl NrKV {
758    pub fn new(key: &str, value: &str) -> Self {
759        Self {
760            key: NrStr::new(key),
761            value: NrStr::new(value),
762        }
763    }
764
765    pub fn from_nr_str(key: NrStr, value: NrStr) -> Self {
766        Self { key, value }
767    }
768}
769
770impl NrKVAny {
771    pub fn new(key: &str, value: NrAny) -> Self {
772        let key_storage = NrVec::from_vec(key.as_bytes().to_vec());
773        let key = NrStr::new(
774            std::str::from_utf8(key_storage.as_slice())
775                .expect("key bytes originate from a valid UTF-8 string"),
776        );
777        Self {
778            key,
779            key_storage,
780            value,
781        }
782    }
783
784    /// Copies a borrowed ABI string into an owned key.
785    ///
786    /// # Safety
787    ///
788    /// `key` must point to readable memory for its declared length.
789    pub unsafe fn from_nr_str(key: NrStr, value: NrAny) -> Result<Self, NrViewError> {
790        let key = unsafe { key.as_str()? };
791        Ok(Self::new(key, value))
792    }
793
794    /// Returns the owned key as a string.
795    pub fn key(&self) -> &str {
796        // The storage is copied from UTF-8 and cannot be mutated externally.
797        unsafe { std::str::from_utf8_unchecked(self.key_storage.as_slice()) }
798    }
799}
800
801// Hash function: FNV-1a
802#[inline]
803fn hash_str(s: &str) -> u64 {
804    const FNV_OFFSET: u64 = 0xcbf29ce484222325;
805    const FNV_PRIME: u64 = 0x100000001b3;
806    let mut h = FNV_OFFSET;
807    for &b in s.as_bytes() {
808        h ^= b as u64;
809        h = h.wrapping_mul(FNV_PRIME);
810    }
811    h
812}
813
814impl NrMap {
815    pub fn new() -> Self {
816        Self::default()
817    }
818
819    #[inline]
820    fn index_len(&self) -> usize {
821        self.index.len
822    }
823
824    fn ensure_index(&mut self) {
825        // Create index when we have enough entries (threshold = 8)
826        if self.index.is_empty() && self.entries.len >= 8 {
827            self.rehash(16);
828        }
829    }
830
831    fn rehash(&mut self, mut new_cap: usize) {
832        // Make it a power of 2 for fast masking
833        new_cap = new_cap.next_power_of_two().max(16);
834
835        // Create empty slots
836        let mut slots = Vec::with_capacity(new_cap);
837        slots.resize_with(new_cap, NrIndexSlot::default);
838
839        self.index = NrVec::from_vec(slots);
840        self.used = 0;
841        self.tomb = 0;
842
843        // Insert all entries into index
844        for i in 0..self.entries.len {
845            let kv = unsafe { &*self.entries.ptr.add(i) };
846            let k = kv.key();
847            let entry_idx =
848                u32::try_from(i).expect("NrMap cannot contain more than u32::MAX entries");
849            self.index_insert(hash_str(k), entry_idx);
850        }
851    }
852
853    #[inline]
854    fn should_grow(&self) -> bool {
855        // Load factor approximately > 0.7 or too many tombstones
856        if self.index.is_empty() {
857            return false;
858        }
859        let occupied = u64::from(self.used) + u64::from(self.tomb);
860        occupied * 10 >= self.index_len() as u64 * 7
861    }
862
863    fn maybe_grow(&mut self) {
864        if self.should_grow() {
865            let cap = self.index_len();
866            self.rehash(cap * 2);
867        }
868    }
869
870    fn index_insert(&mut self, hash: u64, entry_idx: u32) {
871        let cap = self.index_len();
872        if cap == 0 {
873            return;
874        }
875        let mask = cap - 1;
876        let mut pos = (hash as usize) & mask;
877        let mut first_tomb: Option<usize> = None;
878
879        for _ in 0..cap {
880            let slot = unsafe { &mut *self.index.ptr.add(pos) };
881            match slot.state {
882                0 => {
883                    let target = first_tomb.unwrap_or(pos);
884                    let s2 = unsafe { &mut *self.index.ptr.add(target) };
885                    s2.hash = hash;
886                    s2.entry_idx = entry_idx;
887                    s2.state = 1;
888                    if first_tomb.is_some() {
889                        self.tomb -= 1;
890                    }
891                    self.used += 1;
892                    return;
893                }
894                2 if first_tomb.is_none() => first_tomb = Some(pos),
895                _ => {}
896            }
897            pos = (pos + 1) & mask;
898        }
899
900        // Table is unexpectedly full -> rehash and try again
901        let cap2 = cap * 2;
902        self.rehash(cap2);
903        self.index_insert(hash, entry_idx);
904    }
905
906    pub fn insert(&mut self, key: &str, value: NrAny) {
907        // If key exists, replace the value (set behavior)
908        if let Some(v) = self.get_mut(key) {
909            *v = value;
910            return;
911        }
912
913        assert!(
914            self.entries.len < u32::MAX as usize,
915            "NrMap cannot contain more than u32::MAX entries"
916        );
917        let kv = NrKVAny::new(key, value);
918        self.entries.push(kv);
919
920        self.ensure_index();
921        if !self.index.is_empty() {
922            self.maybe_grow();
923            let idx = u32::try_from(self.entries.len - 1)
924                .expect("NrMap cannot contain more than u32::MAX entries");
925            self.index_insert(hash_str(key), idx);
926        }
927    }
928
929    /// Copies an ABI string key and inserts the value.
930    ///
931    /// # Safety
932    ///
933    /// `key` must point to readable memory for its declared length.
934    pub unsafe fn insert_nr(&mut self, key: NrStr, value: NrAny) -> Result<(), NrViewError> {
935        let key_str = unsafe { key.as_str()? };
936        // If key exists, replace the value (set behavior)
937        if let Some(v) = self.get_mut(key_str) {
938            *v = value;
939            return Ok(());
940        }
941
942        assert!(
943            self.entries.len < u32::MAX as usize,
944            "NrMap cannot contain more than u32::MAX entries"
945        );
946        let kv = NrKVAny::new(key_str, value);
947        self.entries.push(kv);
948
949        self.ensure_index();
950        if !self.index.is_empty() {
951            self.maybe_grow();
952            let idx = u32::try_from(self.entries.len - 1)
953                .expect("NrMap cannot contain more than u32::MAX entries");
954            self.index_insert(hash_str(key_str), idx);
955        }
956        Ok(())
957    }
958
959    pub fn get(&self, key: &str) -> Option<&NrAny> {
960        if self.index.is_empty() {
961            // Fallback to linear search (acceptable for small maps)
962            for kv in self.entries.iter() {
963                if kv.key() == key {
964                    return Some(&kv.value);
965                }
966            }
967            return None;
968        }
969
970        let h = hash_str(key);
971        let cap = self.index.len;
972        let mask = cap - 1;
973        let mut pos = (h as usize) & mask;
974
975        for _ in 0..cap {
976            let slot = unsafe { &*self.index.ptr.add(pos) };
977            match slot.state {
978                0 => return None, // Empty slot found, key doesn't exist
979                1 if slot.hash == h => {
980                    let kv = unsafe { &*self.entries.ptr.add(slot.entry_idx as usize) };
981                    if kv.key() == key {
982                        return Some(&kv.value);
983                    }
984                }
985                _ => {}
986            }
987            pos = (pos + 1) & mask;
988        }
989        None
990    }
991
992    pub fn get_mut(&mut self, key: &str) -> Option<&mut NrAny> {
993        if self.index.is_empty() {
994            for kv in self.entries.iter_mut() {
995                if kv.key() == key {
996                    return Some(&mut kv.value);
997                }
998            }
999            return None;
1000        }
1001
1002        let h = hash_str(key);
1003        let cap = self.index.len;
1004        let mask = cap - 1;
1005        let mut pos = (h as usize) & mask;
1006
1007        for _ in 0..cap {
1008            let slot = unsafe { &*self.index.ptr.add(pos) };
1009            match slot.state {
1010                0 => return None,
1011                1 if slot.hash == h => {
1012                    let kv = unsafe { &mut *self.entries.ptr.add(slot.entry_idx as usize) };
1013                    if kv.key() == key {
1014                        return Some(&mut kv.value);
1015                    }
1016                }
1017                _ => {}
1018            }
1019            pos = (pos + 1) & mask;
1020        }
1021        None
1022    }
1023
1024    pub fn remove(&mut self, key: &str) -> Option<NrKVAny> {
1025        // Find the entry and its exact hash slot. Remembering the slot avoids
1026        // deleting the wrong entry when two keys have the same hash.
1027        let (idx, removed_slot) = if self.index.is_empty() {
1028            // Fallback to linear search
1029            (self.entries.iter().position(|kv| kv.key() == key)?, None)
1030        } else {
1031            // Use hash lookup
1032            let h = hash_str(key);
1033            let cap = self.index.len;
1034            let mask = cap - 1;
1035            let mut pos = (h as usize) & mask;
1036            let mut found: Option<(usize, usize)> = None;
1037
1038            for _ in 0..cap {
1039                let slot = unsafe { &*self.index.ptr.add(pos) };
1040                match slot.state {
1041                    0 => break, // Empty slot found, key doesn't exist
1042                    1 if slot.hash == h => {
1043                        let entry_idx = slot.entry_idx as usize;
1044                        let kv = unsafe { &*self.entries.ptr.add(entry_idx) };
1045                        if kv.key() == key {
1046                            found = Some((entry_idx, pos));
1047                            break;
1048                        }
1049                    }
1050                    _ => {}
1051                }
1052                pos = (pos + 1) & mask;
1053            }
1054
1055            let (entry_idx, slot) = found?;
1056            (entry_idx, Some(slot))
1057        };
1058
1059        let last = self.entries.len - 1;
1060
1061        // take removed
1062        let removed = unsafe { std::ptr::read(self.entries.ptr.add(idx)) };
1063
1064        if idx != last {
1065            // Move last into idx (swap_remove)
1066            unsafe {
1067                let last_val = std::ptr::read(self.entries.ptr.add(last));
1068                std::ptr::write(self.entries.ptr.add(idx), last_val);
1069            }
1070
1071            // Update index for the moved entry (last -> idx)
1072            if !self.index.is_empty() {
1073                let h_last = unsafe {
1074                    let kv = &*self.entries.ptr.add(idx);
1075                    hash_str(kv.key())
1076                };
1077                let cap = self.index.len;
1078                let mask = cap - 1;
1079                let mut pos = (h_last as usize) & mask;
1080
1081                for _ in 0..cap {
1082                    let slot = unsafe { &mut *self.index.ptr.add(pos) };
1083                    if slot.state == 1 && slot.entry_idx == last as u32 {
1084                        slot.entry_idx = idx as u32;
1085                        break;
1086                    }
1087                    pos = (pos + 1) & mask;
1088                }
1089            }
1090        }
1091
1092        self.entries.len -= 1;
1093
1094        // Remove slot from index (mark as tombstone or rehash)
1095        if let Some(pos) = removed_slot {
1096            let slot = unsafe { &mut *self.index.ptr.add(pos) };
1097            debug_assert_eq!(slot.state, 1);
1098            slot.state = 2;
1099            self.used -= 1;
1100            self.tomb += 1;
1101
1102            // Rehash if too many tombstones
1103            if self.should_grow() {
1104                self.rehash(self.index_len().max(16));
1105            }
1106        }
1107
1108        Some(removed)
1109    }
1110
1111    pub fn len(&self) -> usize {
1112        self.entries.len
1113    }
1114
1115    pub fn is_empty(&self) -> bool {
1116        self.entries.len == 0
1117    }
1118
1119    pub fn clear(&mut self) {
1120        self.entries.clear();
1121        self.index = NrVec::default();
1122        self.used = 0;
1123        self.tomb = 0;
1124    }
1125}
1126
1127impl NrAny {
1128    /// Stores a clonable, thread-safe Rust value behind the ABI container.
1129    pub fn new<T: Clone + Send + Sync + 'static>(value: T, type_tag: u32) -> Self {
1130        let size = std::mem::size_of::<T>() as u64;
1131        let data = Box::into_raw(Box::new(value)) as *mut c_void;
1132        Self {
1133            data,
1134            size,
1135            type_tag,
1136            clone_fn: Some(clone_any::<T>),
1137            drop_fn: Some(drop_any::<T>),
1138        }
1139    }
1140
1141    /// Stores an owned copy of a byte slice as a `Vec<u8>`.
1142    pub fn from_bytes(bytes: &[u8], type_tag: u32) -> Self {
1143        Self::new(bytes.to_vec(), type_tag)
1144    }
1145
1146    /// Returns a typed raw pointer after checking the stored byte size.
1147    ///
1148    /// The user-defined type tag is not a Rust type identifier. The caller
1149    /// must verify [`NrAny::type_tag`] before dereferencing the returned pointer.
1150    pub fn as_ptr<T>(&self) -> Result<*const T, NrStatus> {
1151        if self.data.is_null() {
1152            return Err(NrStatus::Invalid);
1153        }
1154        let expected_size = std::mem::size_of::<T>() as u64;
1155        if self.size != expected_size {
1156            return Err(NrStatus::Err);
1157        }
1158        Ok(self.data as *const T)
1159    }
1160
1161    pub fn as_mut_ptr<T>(&mut self) -> Result<*mut T, NrStatus> {
1162        if self.data.is_null() {
1163            return Err(NrStatus::Invalid);
1164        }
1165        let expected_size = std::mem::size_of::<T>() as u64;
1166        if self.size != expected_size {
1167            return Err(NrStatus::Err);
1168        }
1169        Ok(self.data as *mut T)
1170    }
1171
1172    pub fn is_null(&self) -> bool {
1173        self.data.is_null()
1174    }
1175
1176    pub fn type_tag(&self) -> u32 {
1177        self.type_tag
1178    }
1179
1180    pub fn size(&self) -> u64 {
1181        self.size
1182    }
1183}
1184
1185unsafe extern "C" fn clone_any<T: Clone>(ptr: *const c_void) -> *mut c_void {
1186    if !ptr.is_null() {
1187        return std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1188            let value = unsafe { &*ptr.cast::<T>() };
1189            Box::into_raw(Box::new(value.clone())).cast::<c_void>()
1190        }))
1191        .unwrap_or(std::ptr::null_mut());
1192    }
1193    std::ptr::null_mut()
1194}
1195
1196unsafe extern "C" fn drop_any<T>(ptr: *mut c_void) {
1197    if !ptr.is_null() {
1198        let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
1199            drop(Box::from_raw(ptr.cast::<T>()));
1200        }));
1201    }
1202}
1203
1204impl Drop for NrAny {
1205    fn drop(&mut self) {
1206        if let Some(drop_fn) = self.drop_fn
1207            && !self.data.is_null()
1208        {
1209            unsafe {
1210                drop_fn(self.data);
1211            }
1212        }
1213    }
1214}
1215
1216impl NrPluginInfo {
1217    pub fn compatible(&self, expected_abi_version: u32) -> bool {
1218        self.abi_version == expected_abi_version
1219    }
1220}
1221
1222impl NrVec<u8> {
1223    /// Copies an ABI byte view into an owned vector.
1224    ///
1225    /// # Safety
1226    ///
1227    /// `bytes` must point to readable memory for its declared length.
1228    pub unsafe fn from_nr_bytes(bytes: NrBytes) -> Result<Self, NrViewError> {
1229        let v = unsafe { bytes.as_slice()? }.to_vec();
1230        Ok(Self::from_vec(v))
1231    }
1232
1233    pub fn from_string(s: String) -> Self {
1234        Self::from_vec(s.into_bytes())
1235    }
1236}
1237
1238impl<T> NrVec<T> {
1239    /// Takes ownership of a Rust vector without copying its allocation.
1240    pub fn from_vec(v: Vec<T>) -> Self {
1241        let mut v = std::mem::ManuallyDrop::new(v);
1242        let ptr = v.as_mut_ptr();
1243        let len = v.len();
1244        let cap = v.capacity();
1245        Self {
1246            ptr,
1247            len,
1248            cap,
1249            owned: 1,
1250            _reserved: [0; 7],
1251            drop_fn: Some(drop_vec::<T>),
1252        }
1253    }
1254
1255    /// Copies the elements into a vector owned by the current module.
1256    ///
1257    /// The source allocation is released by the allocator-specific callback
1258    /// supplied by the module that created this `NrVec`.
1259    pub fn into_vec(self) -> Vec<T>
1260    where
1261        T: Clone,
1262    {
1263        self.as_slice().to_vec()
1264    }
1265
1266    fn push(&mut self, value: T) {
1267        if self.owned == 0 && self.ptr.is_null() && self.len == 0 && self.cap == 0 {
1268            self.owned = 1;
1269            self.drop_fn = Some(drop_vec::<T>);
1270        }
1271        assert_eq!(self.owned, 1, "cannot mutate a borrowed NrVec");
1272        if self.len == self.cap {
1273            self.reserve(1);
1274        }
1275        unsafe {
1276            std::ptr::write(self.ptr.add(self.len), value);
1277        }
1278        self.len += 1;
1279    }
1280
1281    fn clear(&mut self) {
1282        assert_eq!(self.owned, 1, "cannot mutate a borrowed NrVec");
1283        while self.len > 0 {
1284            self.len -= 1;
1285            unsafe {
1286                std::ptr::drop_in_place(self.ptr.add(self.len));
1287            }
1288        }
1289    }
1290
1291    fn reserve(&mut self, additional: usize) {
1292        assert_eq!(self.owned, 1, "cannot resize a borrowed NrVec");
1293        if std::mem::size_of::<T>() == 0 {
1294            assert!(
1295                self.len.checked_add(additional).is_some(),
1296                "capacity overflow"
1297            );
1298            if self.cap == 0 {
1299                self.ptr = std::ptr::NonNull::<T>::dangling().as_ptr();
1300                self.cap = usize::MAX;
1301            }
1302            return;
1303        }
1304
1305        let available = self.cap - self.len;
1306        if available < additional {
1307            let required = self.len.checked_add(additional).expect("capacity overflow");
1308            let new_cap = if self.cap == 0 {
1309                std::cmp::max(1, required)
1310            } else {
1311                std::cmp::max(self.cap.saturating_mul(2), required)
1312            };
1313
1314            let new_layout = match std::alloc::Layout::array::<T>(new_cap) {
1315                Ok(layout) => layout,
1316                Err(_) => {
1317                    // Layout calculation overflow - trigger allocation error
1318                    std::alloc::handle_alloc_error(
1319                        std::alloc::Layout::from_size_align(usize::MAX, 1)
1320                            .unwrap_or_else(|_| std::alloc::Layout::new::<u8>()),
1321                    )
1322                }
1323            };
1324
1325            let new_ptr = if self.cap == 0 {
1326                unsafe { std::alloc::alloc(new_layout) }
1327            } else {
1328                let old_layout = match std::alloc::Layout::array::<T>(self.cap) {
1329                    Ok(layout) => layout,
1330                    Err(_) => {
1331                        // This should never happen since we successfully allocated before
1332                        // But handle it defensively
1333                        std::alloc::handle_alloc_error(new_layout)
1334                    }
1335                };
1336                unsafe { std::alloc::realloc(self.ptr as *mut u8, old_layout, new_layout.size()) }
1337            };
1338
1339            if new_ptr.is_null() {
1340                std::alloc::handle_alloc_error(new_layout);
1341            }
1342
1343            self.ptr = new_ptr as *mut T;
1344            self.cap = new_cap;
1345        }
1346    }
1347
1348    pub fn capacity(&self) -> usize {
1349        self.cap
1350    }
1351
1352    pub fn len(&self) -> usize {
1353        self.len
1354    }
1355
1356    pub fn is_empty(&self) -> bool {
1357        self.len == 0
1358    }
1359
1360    pub fn as_ptr(&self) -> *const T {
1361        self.ptr
1362    }
1363
1364    pub fn as_mut_ptr(&mut self) -> *mut T {
1365        self.ptr
1366    }
1367}
1368
1369impl<T> Drop for NrVec<T> {
1370    fn drop(&mut self) {
1371        if self.owned == 1
1372            && let Some(drop_fn) = self.drop_fn
1373        {
1374            unsafe {
1375                drop_fn(self.ptr, self.len, self.cap);
1376            }
1377        }
1378    }
1379}
1380
1381unsafe extern "C" fn drop_vec<T>(ptr: *mut T, len: usize, cap: usize) {
1382    let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1383        if cap != 0 {
1384            unsafe {
1385                drop(Vec::from_raw_parts(ptr, len, cap));
1386            }
1387        }
1388    }));
1389}
1390
1391impl<T> NrVec<T> {
1392    pub fn iter(&self) -> std::slice::Iter<'_, T> {
1393        self.as_slice().iter()
1394    }
1395
1396    fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> {
1397        self.as_mut_slice().iter_mut()
1398    }
1399
1400    pub fn as_slice(&self) -> &[T] {
1401        if self.len == 0 {
1402            &[]
1403        } else {
1404            unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
1405        }
1406    }
1407
1408    fn as_mut_slice(&mut self) -> &mut [T] {
1409        if self.len == 0 {
1410            &mut []
1411        } else {
1412            unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
1413        }
1414    }
1415}
1416
1417impl<'a, T> IntoIterator for &'a NrVec<T> {
1418    type Item = &'a T;
1419    type IntoIter = std::slice::Iter<'a, T>;
1420
1421    fn into_iter(self) -> Self::IntoIter {
1422        self.iter()
1423    }
1424}
1425
1426impl<T: Clone> IntoIterator for NrVec<T> {
1427    type Item = T;
1428    type IntoIter = std::vec::IntoIter<T>;
1429
1430    fn into_iter(self) -> Self::IntoIter {
1431        self.into_vec().into_iter()
1432    }
1433}
1434
1435// Safety: These types are ABI-stable data carriers.
1436// Users must ensure that the pointers they contain are valid and accessed safely.
1437unsafe impl Send for NrStr {}
1438unsafe impl Sync for NrStr {}
1439
1440unsafe impl Send for NrBytes {}
1441unsafe impl Sync for NrBytes {}
1442
1443unsafe impl Send for NrKV {}
1444unsafe impl Sync for NrKV {}
1445
1446unsafe impl Send for NrKVAny {}
1447unsafe impl Sync for NrKVAny {}
1448
1449unsafe impl Send for NrMap {}
1450unsafe impl Sync for NrMap {}
1451
1452unsafe impl Send for NrAny {}
1453unsafe impl Sync for NrAny {}
1454
1455unsafe impl Send for NrHostVTable {}
1456unsafe impl Sync for NrHostVTable {}
1457
1458unsafe impl Send for NrPluginVTable {}
1459unsafe impl Sync for NrPluginVTable {}
1460
1461unsafe impl Send for NrPluginInfo {}
1462unsafe impl Sync for NrPluginInfo {}
1463
1464unsafe impl<T: Send> Send for NrVec<T> {}
1465unsafe impl<T: Sync> Sync for NrVec<T> {}
1466
1467unsafe impl<A: Send, B: Send> Send for NrTuple<A, B> {}
1468unsafe impl<A: Sync, B: Sync> Sync for NrTuple<A, B> {}
1469
1470#[cfg(test)]
1471mod tests {
1472    use super::*;
1473    use std::mem::{align_of, offset_of, size_of};
1474
1475    unsafe fn test_plugin_init(_: *mut c_void, _: *const NrHostVTable) -> NrStatus {
1476        NrStatus::Ok
1477    }
1478
1479    unsafe fn panicking_handler(_: u64, _: NrBytes) -> NrStatus {
1480        panic!("test panic must not cross the FFI boundary");
1481    }
1482
1483    fn test_plugin_shutdown() {}
1484
1485    define_plugin! {
1486        init: test_plugin_init,
1487        shutdown: test_plugin_shutdown,
1488        entries: {
1489            "panic" => panicking_handler,
1490        }
1491    }
1492
1493    #[test]
1494    fn test_layout() {
1495        // Verify NrStr layout (ptr + u32)
1496        // On 64-bit: 8 bytes ptr + 4 bytes len + 4 bytes padding = 16 bytes
1497        assert_eq!(size_of::<NrStr>(), 16);
1498        assert_eq!(align_of::<NrStr>(), 8);
1499
1500        // Verify NrBytes layout (ptr + u64)
1501        // On 64-bit: 8 bytes ptr + 8 bytes len = 16 bytes
1502        assert_eq!(size_of::<NrBytes>(), 16);
1503        assert_eq!(align_of::<NrBytes>(), 8);
1504
1505        // ptr + len + cap + ownership/reserved + allocator-specific drop fn
1506        assert_eq!(size_of::<NrVec<u8>>(), 40);
1507        assert_eq!(align_of::<NrVec<u8>>(), 8);
1508
1509        // Verify NrTuple layout (A + B)
1510        // u64 + u64 = 16 bytes
1511        assert_eq!(size_of::<NrTuple<u64, u64>>(), 16);
1512        assert_eq!(align_of::<NrTuple<u64, u64>>(), 8);
1513
1514        // Verify NrKV layout (NrStr + NrStr)
1515        // 16 + 16 = 32 bytes
1516        assert_eq!(size_of::<NrKV>(), 32);
1517        assert_eq!(align_of::<NrKV>(), 8);
1518    }
1519
1520    /// Mirror of the `_Static_assert` block in `c/nylon_ring.h` — the two
1521    /// must stay identical so drift in either direction fails a build.
1522    #[test]
1523    fn test_layout_matches_c_header() {
1524        assert_eq!(size_of::<NrStatus>(), 4);
1525
1526        assert_eq!(size_of::<NrStr>(), 16);
1527        assert_eq!(offset_of!(NrStr, ptr), 0);
1528        assert_eq!(offset_of!(NrStr, len), 8);
1529        assert_eq!(offset_of!(NrStr, _reserved), 12);
1530
1531        assert_eq!(size_of::<NrBytes>(), 16);
1532        assert_eq!(offset_of!(NrBytes, ptr), 0);
1533        assert_eq!(offset_of!(NrBytes, len), 8);
1534
1535        assert_eq!(size_of::<NrVec<u8>>(), 40);
1536        assert_eq!(offset_of!(NrVec<u8>, ptr), 0);
1537        assert_eq!(offset_of!(NrVec<u8>, len), 8);
1538        assert_eq!(offset_of!(NrVec<u8>, cap), 16);
1539        assert_eq!(offset_of!(NrVec<u8>, owned), 24);
1540        assert_eq!(offset_of!(NrVec<u8>, _reserved), 25);
1541        assert_eq!(offset_of!(NrVec<u8>, drop_fn), 32);
1542
1543        assert_eq!(size_of::<NrOwnedBytes>(), 32);
1544        assert_eq!(offset_of!(NrOwnedBytes, ptr), 0);
1545        assert_eq!(offset_of!(NrOwnedBytes, len), 8);
1546        assert_eq!(offset_of!(NrOwnedBytes, owner_ctx), 16);
1547        assert_eq!(offset_of!(NrOwnedBytes, release), 24);
1548
1549        assert_eq!(size_of::<NrBufferLease>(), 24);
1550        assert_eq!(offset_of!(NrBufferLease, ptr), 0);
1551        assert_eq!(offset_of!(NrBufferLease, cap), 8);
1552        assert_eq!(offset_of!(NrBufferLease, token), 16);
1553
1554        assert_eq!(size_of::<NrHostVTable>(), 32);
1555        assert_eq!(offset_of!(NrHostVTable, send_result), 0);
1556        assert_eq!(offset_of!(NrHostVTable, send_result_owned), 8);
1557        assert_eq!(offset_of!(NrHostVTable, acquire_result_buffer), 16);
1558        assert_eq!(offset_of!(NrHostVTable, commit_result_buffer), 24);
1559
1560        assert_eq!(size_of::<NrPluginVTable>(), 56);
1561        assert_eq!(offset_of!(NrPluginVTable, init), 0);
1562        assert_eq!(offset_of!(NrPluginVTable, handle), 8);
1563        assert_eq!(offset_of!(NrPluginVTable, shutdown), 16);
1564        assert_eq!(offset_of!(NrPluginVTable, stream_data), 24);
1565        assert_eq!(offset_of!(NrPluginVTable, stream_close), 32);
1566        assert_eq!(offset_of!(NrPluginVTable, resolve_entry), 40);
1567        assert_eq!(offset_of!(NrPluginVTable, handle_by_id), 48);
1568
1569        assert_eq!(size_of::<NrPluginInfo>(), 56);
1570        assert_eq!(offset_of!(NrPluginInfo, abi_version), 0);
1571        assert_eq!(offset_of!(NrPluginInfo, struct_size), 4);
1572        assert_eq!(offset_of!(NrPluginInfo, name), 8);
1573        assert_eq!(offset_of!(NrPluginInfo, version), 24);
1574        assert_eq!(offset_of!(NrPluginInfo, plugin_ctx), 40);
1575        assert_eq!(offset_of!(NrPluginInfo, vtable), 48);
1576    }
1577
1578    #[test]
1579    fn test_nr_vec() {
1580        let mut v = NrVec::<u32>::default();
1581        assert_eq!(v.len, 0);
1582        assert_eq!(v.cap, 0);
1583
1584        v.push(1);
1585        assert_eq!(v.len, 1);
1586        assert!(v.cap >= 1);
1587        unsafe {
1588            assert_eq!(*v.ptr, 1);
1589        }
1590
1591        v.push(2);
1592        assert_eq!(v.len, 2);
1593        unsafe {
1594            assert_eq!(*v.ptr.add(1), 2);
1595        }
1596
1597        v.reserve(10);
1598        assert!(v.cap >= 12); // 2 + 10
1599
1600        v.clear();
1601        assert_eq!(v.len, 0);
1602        assert!(v.cap >= 12);
1603    }
1604    #[test]
1605    fn test_nr_vec_iter() {
1606        let mut v = NrVec::<u32>::default();
1607        v.push(1);
1608        v.push(2);
1609        v.push(3);
1610
1611        let mut iter = v.iter();
1612        assert_eq!(iter.next(), Some(&1));
1613        assert_eq!(iter.next(), Some(&2));
1614        assert_eq!(iter.next(), Some(&3));
1615        assert_eq!(iter.next(), None);
1616    }
1617
1618    #[test]
1619    fn test_nr_vec_iter_mut() {
1620        let mut v = NrVec::<u32>::default();
1621        v.push(1);
1622        v.push(2);
1623        v.push(3);
1624
1625        for x in v.iter_mut() {
1626            *x *= 2;
1627        }
1628
1629        let mut iter = v.iter();
1630        assert_eq!(iter.next(), Some(&2));
1631        assert_eq!(iter.next(), Some(&4));
1632        assert_eq!(iter.next(), Some(&6));
1633        assert_eq!(iter.next(), None);
1634    }
1635
1636    #[test]
1637    fn test_nr_vec_into_iter() {
1638        let mut v = NrVec::<u32>::default();
1639        v.push(1);
1640        v.push(2);
1641        v.push(3);
1642
1643        let mut iter = v.into_iter();
1644        assert_eq!(iter.next(), Some(1));
1645        assert_eq!(iter.next(), Some(2));
1646        assert_eq!(iter.next(), Some(3));
1647        assert_eq!(iter.next(), None);
1648    }
1649
1650    #[test]
1651    fn test_nr_vec_empty_and_zero_sized_values() {
1652        assert!(NrVec::<u8>::default().into_vec().is_empty());
1653
1654        let mut values = NrVec::default();
1655        values.push(());
1656        values.push(());
1657        assert_eq!(values.len(), 2);
1658        assert_eq!(values.into_iter().count(), 2);
1659    }
1660
1661    #[test]
1662    fn test_nr_vec_collect() {
1663        let mut v = NrVec::<u32>::default();
1664        v.push(10);
1665        v.push(20);
1666
1667        let collected: Vec<u32> = v.iter().cloned().collect();
1668        assert_eq!(collected, vec![10, 20]);
1669    }
1670
1671    #[test]
1672    fn test_nr_map() {
1673        let mut map = NrMap::new();
1674        assert!(map.is_empty());
1675        assert_eq!(map.len(), 0);
1676
1677        // Insert string values
1678        let str_value1 = NrAny::new(String::from("value1"), 1);
1679        let str_value2 = NrAny::new(String::from("value2"), 1);
1680        map.insert("key1", str_value1);
1681        map.insert("key2", str_value2);
1682        assert_eq!(map.len(), 2);
1683
1684        // Get and verify string values
1685        let value1 = map.get("key1").unwrap();
1686        let str_ptr1 = value1.as_ptr::<String>().unwrap();
1687        unsafe {
1688            assert_eq!(*str_ptr1, "value1");
1689        }
1690
1691        let value2 = map.get("key2").unwrap();
1692        let str_ptr2 = value2.as_ptr::<String>().unwrap();
1693        unsafe {
1694            assert_eq!(*str_ptr2, "value2");
1695        }
1696
1697        assert!(map.get("key3").is_none());
1698
1699        // Test that get_mut returns a mutable reference
1700        let value_mut = map.get_mut("key1");
1701        assert!(value_mut.is_some());
1702
1703        // Insert integer value
1704        let int_value = NrAny::new(42i32, 2);
1705        map.insert("key3", int_value);
1706        assert_eq!(map.len(), 3);
1707
1708        let int_val = map.get("key3").unwrap();
1709        let int_ptr = int_val.as_ptr::<i32>().unwrap();
1710        unsafe {
1711            assert_eq!(*int_ptr, 42);
1712        }
1713
1714        let removed = map.remove("key2");
1715        assert!(removed.is_some());
1716        assert_eq!(map.len(), 2);
1717        assert!(map.get("key2").is_none());
1718
1719        map.clear();
1720        assert!(map.is_empty());
1721
1722        // Keys are copied into the map and remain valid after the source dies.
1723        {
1724            let temporary_key = String::from("owned-key");
1725            map.insert(&temporary_key, NrAny::new(7_u32, 2));
1726        }
1727        let stored = map.get("owned-key").unwrap();
1728        assert_eq!(unsafe { *stored.as_ptr::<u32>().unwrap() }, 7);
1729
1730        // Exercise the indexed path, then verify clear resets the index.
1731        for index in 0..16 {
1732            map.insert(&format!("key-{index}"), NrAny::new(index, 2));
1733        }
1734        for index in 0..16 {
1735            assert!(map.remove(&format!("key-{index}")).is_some());
1736        }
1737        map.clear();
1738        assert!(map.get("missing").is_none());
1739    }
1740
1741    #[test]
1742    fn test_nr_any() {
1743        let any_int = NrAny::new(42i32, 1);
1744        assert!(!any_int.is_null());
1745        assert_eq!(any_int.type_tag(), 1);
1746        assert_eq!(any_int.size(), std::mem::size_of::<i32>() as u64);
1747
1748        let ptr = any_int.as_ptr::<i32>().unwrap();
1749        unsafe {
1750            assert_eq!(*ptr, 42);
1751        }
1752
1753        let any_string = NrAny::new(String::from("hello"), 2);
1754        assert_eq!(any_string.type_tag(), 2);
1755        let str_ptr = any_string.as_ptr::<String>().unwrap();
1756        unsafe {
1757            assert_eq!(*str_ptr, "hello");
1758        }
1759        let cloned_string = any_string.clone();
1760        let cloned_ptr = cloned_string.as_ptr::<String>().unwrap();
1761        unsafe {
1762            assert_eq!(*cloned_ptr, "hello");
1763            assert_ne!(str_ptr, cloned_ptr);
1764        }
1765
1766        let any_bytes = NrAny::from_bytes(b"test", 3);
1767        assert_eq!(any_bytes.type_tag(), 3);
1768        assert_eq!(any_bytes.size(), std::mem::size_of::<Vec<u8>>() as u64);
1769        let bytes_ptr = any_bytes.as_ptr::<Vec<u8>>().unwrap();
1770        unsafe {
1771            assert_eq!(&*bytes_ptr, b"test");
1772        }
1773
1774        let default_any = NrAny::default();
1775        assert!(default_any.is_null());
1776        assert_eq!(default_any.type_tag(), 0);
1777        assert_eq!(default_any.size(), 0);
1778
1779        // Test null pointer error
1780        assert_eq!(default_any.as_ptr::<i32>(), Err(NrStatus::Invalid));
1781        let mut default_any_mut = NrAny::default();
1782        assert_eq!(default_any_mut.as_mut_ptr::<i32>(), Err(NrStatus::Invalid));
1783
1784        // Test type mismatch error
1785        let any_int = NrAny::new(42i32, 1);
1786        assert_eq!(any_int.as_ptr::<u64>(), Err(NrStatus::Err)); // i32 size != u64 size
1787        let mut any_int_mut = NrAny::new(42i32, 1);
1788        assert_eq!(any_int_mut.as_mut_ptr::<u64>(), Err(NrStatus::Err));
1789    }
1790
1791    #[test]
1792    fn test_borrowed_view_validation() {
1793        let empty = NrStr::default();
1794        assert_eq!(unsafe { empty.as_str() }.unwrap(), "");
1795
1796        let invalid_utf8 = [0xff];
1797        let invalid = NrStr {
1798            ptr: invalid_utf8.as_ptr(),
1799            len: 1,
1800            _reserved: 0,
1801        };
1802        assert!(matches!(
1803            unsafe { invalid.as_str() },
1804            Err(NrViewError::InvalidUtf8(_))
1805        ));
1806
1807        let null_bytes = NrBytes {
1808            ptr: std::ptr::null(),
1809            len: 1,
1810        };
1811        assert_eq!(
1812            unsafe { null_bytes.as_slice() },
1813            Err(NrViewError::NullPointer)
1814        );
1815    }
1816
1817    #[test]
1818    fn plugin_macro_contains_panics_and_rejects_invalid_utf8() {
1819        let panic_entry = NrStr::new("panic");
1820        assert_eq!(
1821            unsafe { plugin_handle_wrapper(panic_entry, 1, NrBytes::default()) },
1822            NrStatus::Panic
1823        );
1824
1825        let invalid_utf8 = [0xff];
1826        let invalid_entry = NrStr {
1827            ptr: invalid_utf8.as_ptr(),
1828            len: 1,
1829            _reserved: 0,
1830        };
1831        assert_eq!(
1832            unsafe { plugin_handle_wrapper(invalid_entry, 2, NrBytes::default()) },
1833            NrStatus::Invalid
1834        );
1835    }
1836}