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