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