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                // Entry names are matched byte-wise: a non-UTF-8 entry cannot
346                // equal any literal, so it still falls through to Invalid
347                // without paying for per-call UTF-8 validation.
348                let entry_bytes = match unsafe { entry.as_bytes() } {
349                    Ok(entry) => entry,
350                    Err(_) => return $crate::NrStatus::Invalid,
351                };
352                $(
353                    if entry_bytes == $entry_name.as_bytes() {
354                        return unsafe { $handler_fn(sid, payload) };
355                    }
356                )*
357                $crate::NrStatus::Invalid
358            }))
359            .unwrap_or($crate::NrStatus::Panic)
360        }
361
362        unsafe extern "C" fn plugin_stream_data_wrapper(
363            sid: u64,
364            data: $crate::NrBytes,
365        ) -> $crate::NrStatus {
366            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
367                let _ = (sid, data);
368                $(
369                    return unsafe { $stream_data_fn(sid, data) };
370                )?
371                #[allow(unreachable_code)]
372                $crate::NrStatus::Unsupported
373            }))
374            .unwrap_or($crate::NrStatus::Panic)
375        }
376
377        unsafe extern "C" fn plugin_stream_close_wrapper(
378            sid: u64,
379        ) -> $crate::NrStatus {
380            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
381                let _ = sid;
382                $(
383                    return unsafe { $stream_close_fn(sid) };
384                )?
385                #[allow(unreachable_code)]
386                $crate::NrStatus::Unsupported
387            }))
388            .unwrap_or($crate::NrStatus::Panic)
389        }
390    };
391}
392
393/// Metadata exported by the 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
407impl NrStr {
408    /// Creates a borrowed ABI string view.
409    ///
410    /// The returned value must not be used to access the string after `s` is
411    /// invalidated. Use [`NrStr::as_str`] only while the source is alive.
412    pub fn new(s: &str) -> Self {
413        let len = u32::try_from(s.len()).expect("NrStr cannot represent strings larger than 4 GiB");
414        Self {
415            ptr: s.as_ptr(),
416            len,
417            _reserved: 0,
418        }
419    }
420
421    /// Creates a borrowed ABI view from a static string.
422    pub const fn from_static(s: &'static str) -> Self {
423        assert!(
424            s.len() <= u32::MAX as usize,
425            "static string is too large for NrStr"
426        );
427        Self {
428            ptr: s.as_ptr(),
429            len: s.len() as u32,
430            _reserved: 0,
431        }
432    }
433
434    /// Reads this ABI view as UTF-8.
435    ///
436    /// # Safety
437    ///
438    /// For a non-empty view, `ptr` must be valid for reads of `len` bytes for
439    /// the lifetime of the returned reference. The pointed-to memory must not
440    /// be mutated while that reference exists.
441    pub unsafe fn as_str<'a>(&self) -> Result<&'a str, NrViewError> {
442        let bytes = unsafe { view_bytes(self.ptr, u64::from(self.len))? };
443        std::str::from_utf8(bytes).map_err(NrViewError::InvalidUtf8)
444    }
445
446    /// Reads this ABI view's raw bytes without validating UTF-8.
447    ///
448    /// # Safety
449    ///
450    /// For a non-empty view, `ptr` must be valid for reads of `len` bytes for
451    /// the lifetime of the returned reference. The pointed-to memory must not
452    /// be mutated while that reference exists.
453    pub unsafe fn as_bytes<'a>(&self) -> Result<&'a [u8], NrViewError> {
454        unsafe { view_bytes(self.ptr, u64::from(self.len)) }
455    }
456
457    /// Returns whether this view contains no bytes.
458    pub const fn is_empty(&self) -> bool {
459        self.len == 0
460    }
461
462    /// Returns the view length in bytes.
463    pub const fn len(&self) -> usize {
464        self.len as usize
465    }
466
467    /// Returns the underlying raw pointer.
468    pub const fn as_ptr(&self) -> *const u8 {
469        self.ptr
470    }
471
472    /// Resets this view without freeing the borrowed memory.
473    pub fn clear(&mut self) {
474        self.ptr = std::ptr::null();
475        self.len = 0;
476        self._reserved = 0;
477    }
478}
479
480unsafe fn view_bytes<'a>(ptr: *const u8, len: u64) -> Result<&'a [u8], NrViewError> {
481    if len == 0 {
482        return Ok(&[]);
483    }
484    if ptr.is_null() {
485        return Err(NrViewError::NullPointer);
486    }
487    let len = usize::try_from(len).map_err(|_| NrViewError::LengthOverflow)?;
488    Ok(unsafe { std::slice::from_raw_parts(ptr, len) })
489}
490
491impl NrBytes {
492    /// Creates a borrowed ABI byte view.
493    pub fn from_slice(s: &[u8]) -> Self {
494        Self {
495            ptr: s.as_ptr(),
496            len: u64::try_from(s.len()).expect("NrBytes length does not fit in u64"),
497        }
498    }
499
500    /// Reads this ABI byte view.
501    ///
502    /// # Safety
503    ///
504    /// For a non-empty view, `ptr` must be valid for reads of `len` bytes for
505    /// the lifetime of the returned reference. The pointed-to memory must not
506    /// be mutated while that reference exists.
507    pub unsafe fn as_slice<'a>(&self) -> Result<&'a [u8], NrViewError> {
508        unsafe { view_bytes(self.ptr, self.len) }
509    }
510
511    /// Returns whether this view contains no bytes.
512    pub const fn is_empty(&self) -> bool {
513        self.len == 0
514    }
515
516    /// Returns the view length in bytes.
517    pub const fn len(&self) -> u64 {
518        self.len
519    }
520}
521
522impl Clone for NrKVAny {
523    fn clone(&self) -> Self {
524        Self::new(self.key(), self.value.clone())
525    }
526}
527
528impl Clone for NrMap {
529    fn clone(&self) -> Self {
530        Self {
531            entries: self.entries.clone(),
532            index: self.index.clone(),
533            used: self.used,
534            tomb: self.tomb,
535        }
536    }
537}
538
539impl Clone for NrAny {
540    /// Clone an `NrAny`.
541    ///
542    /// # Safety / ABI v1 limitation
543    ///
544    /// `NrAny` is type-erased and ABI v1 has no `clone_fn` in the struct.
545    /// Cloning a value that owns heap resources (i.e. has a `drop_fn`) by
546    /// memcpy would shallow-copy any inner pointers and cause a double-free
547    /// when both copies are dropped. Therefore:
548    ///
549    /// * Null payload  → returns `default()` (null).
550    /// * `drop_fn = None` (POD payload) → safe deep copy via byte memcpy.
551    /// * `drop_fn = Some(_)` → **panics**, because we cannot safely deep-copy
552    ///   without a type-aware `clone_fn`. Use `drop_fn = None` and a POD
553    ///   representation, or wait for ABI v2 which adds `clone_fn`.
554    fn clone(&self) -> Self {
555        if self.data.is_null() {
556            return Self::default();
557        }
558        let clone_fn = self
559            .clone_fn
560            .expect("non-null NrAny values always have a clone function");
561        let data = unsafe { clone_fn(self.data.cast_const()) };
562        assert!(!data.is_null(), "NrAny clone callback failed");
563        Self {
564            data,
565            size: self.size,
566            type_tag: self.type_tag,
567            clone_fn: self.clone_fn,
568            drop_fn: self.drop_fn,
569        }
570    }
571}
572
573impl<T: Clone> Clone for NrVec<T> {
574    fn clone(&self) -> Self {
575        if self.len == 0 {
576            return Self::default();
577        }
578        let v = self.as_slice().to_vec();
579        Self::from_vec(v)
580    }
581}
582
583impl NrKV {
584    pub fn new(key: &str, value: &str) -> Self {
585        Self {
586            key: NrStr::new(key),
587            value: NrStr::new(value),
588        }
589    }
590
591    pub fn from_nr_str(key: NrStr, value: NrStr) -> Self {
592        Self { key, value }
593    }
594}
595
596impl NrKVAny {
597    pub fn new(key: &str, value: NrAny) -> Self {
598        let key_storage = NrVec::from_vec(key.as_bytes().to_vec());
599        let key = NrStr::new(
600            std::str::from_utf8(key_storage.as_slice())
601                .expect("key bytes originate from a valid UTF-8 string"),
602        );
603        Self {
604            key,
605            key_storage,
606            value,
607        }
608    }
609
610    /// Copies a borrowed ABI string into an owned key.
611    ///
612    /// # Safety
613    ///
614    /// `key` must point to readable memory for its declared length.
615    pub unsafe fn from_nr_str(key: NrStr, value: NrAny) -> Result<Self, NrViewError> {
616        let key = unsafe { key.as_str()? };
617        Ok(Self::new(key, value))
618    }
619
620    /// Returns the owned key as a string.
621    pub fn key(&self) -> &str {
622        // The storage is copied from UTF-8 and cannot be mutated externally.
623        unsafe { std::str::from_utf8_unchecked(self.key_storage.as_slice()) }
624    }
625}
626
627// Hash function: FNV-1a
628#[inline]
629fn hash_str(s: &str) -> u64 {
630    const FNV_OFFSET: u64 = 0xcbf29ce484222325;
631    const FNV_PRIME: u64 = 0x100000001b3;
632    let mut h = FNV_OFFSET;
633    for &b in s.as_bytes() {
634        h ^= b as u64;
635        h = h.wrapping_mul(FNV_PRIME);
636    }
637    h
638}
639
640impl NrMap {
641    pub fn new() -> Self {
642        Self::default()
643    }
644
645    #[inline]
646    fn index_len(&self) -> usize {
647        self.index.len
648    }
649
650    fn ensure_index(&mut self) {
651        // Create index when we have enough entries (threshold = 8)
652        if self.index.is_empty() && self.entries.len >= 8 {
653            self.rehash(16);
654        }
655    }
656
657    fn rehash(&mut self, mut new_cap: usize) {
658        // Make it a power of 2 for fast masking
659        new_cap = new_cap.next_power_of_two().max(16);
660
661        // Create empty slots
662        let mut slots = Vec::with_capacity(new_cap);
663        slots.resize_with(new_cap, NrIndexSlot::default);
664
665        self.index = NrVec::from_vec(slots);
666        self.used = 0;
667        self.tomb = 0;
668
669        // Insert all entries into index
670        for i in 0..self.entries.len {
671            let kv = unsafe { &*self.entries.ptr.add(i) };
672            let k = kv.key();
673            let entry_idx =
674                u32::try_from(i).expect("NrMap cannot contain more than u32::MAX entries");
675            self.index_insert(hash_str(k), entry_idx);
676        }
677    }
678
679    #[inline]
680    fn should_grow(&self) -> bool {
681        // Load factor approximately > 0.7 or too many tombstones
682        if self.index.is_empty() {
683            return false;
684        }
685        let occupied = u64::from(self.used) + u64::from(self.tomb);
686        occupied * 10 >= self.index_len() as u64 * 7
687    }
688
689    fn maybe_grow(&mut self) {
690        if self.should_grow() {
691            let cap = self.index_len();
692            self.rehash(cap * 2);
693        }
694    }
695
696    fn index_insert(&mut self, hash: u64, entry_idx: u32) {
697        let cap = self.index_len();
698        if cap == 0 {
699            return;
700        }
701        let mask = cap - 1;
702        let mut pos = (hash as usize) & mask;
703        let mut first_tomb: Option<usize> = None;
704
705        for _ in 0..cap {
706            let slot = unsafe { &mut *self.index.ptr.add(pos) };
707            match slot.state {
708                0 => {
709                    let target = first_tomb.unwrap_or(pos);
710                    let s2 = unsafe { &mut *self.index.ptr.add(target) };
711                    s2.hash = hash;
712                    s2.entry_idx = entry_idx;
713                    s2.state = 1;
714                    if first_tomb.is_some() {
715                        self.tomb -= 1;
716                    }
717                    self.used += 1;
718                    return;
719                }
720                2 if first_tomb.is_none() => first_tomb = Some(pos),
721                _ => {}
722            }
723            pos = (pos + 1) & mask;
724        }
725
726        // Table is unexpectedly full -> rehash and try again
727        let cap2 = cap * 2;
728        self.rehash(cap2);
729        self.index_insert(hash, entry_idx);
730    }
731
732    pub fn insert(&mut self, key: &str, value: NrAny) {
733        // If key exists, replace the value (set behavior)
734        if let Some(v) = self.get_mut(key) {
735            *v = value;
736            return;
737        }
738
739        assert!(
740            self.entries.len < u32::MAX as usize,
741            "NrMap cannot contain more than u32::MAX entries"
742        );
743        let kv = NrKVAny::new(key, value);
744        self.entries.push(kv);
745
746        self.ensure_index();
747        if !self.index.is_empty() {
748            self.maybe_grow();
749            let idx = u32::try_from(self.entries.len - 1)
750                .expect("NrMap cannot contain more than u32::MAX entries");
751            self.index_insert(hash_str(key), idx);
752        }
753    }
754
755    /// Copies an ABI string key and inserts the value.
756    ///
757    /// # Safety
758    ///
759    /// `key` must point to readable memory for its declared length.
760    pub unsafe fn insert_nr(&mut self, key: NrStr, value: NrAny) -> Result<(), NrViewError> {
761        let key_str = unsafe { key.as_str()? };
762        // If key exists, replace the value (set behavior)
763        if let Some(v) = self.get_mut(key_str) {
764            *v = value;
765            return Ok(());
766        }
767
768        assert!(
769            self.entries.len < u32::MAX as usize,
770            "NrMap cannot contain more than u32::MAX entries"
771        );
772        let kv = NrKVAny::new(key_str, value);
773        self.entries.push(kv);
774
775        self.ensure_index();
776        if !self.index.is_empty() {
777            self.maybe_grow();
778            let idx = u32::try_from(self.entries.len - 1)
779                .expect("NrMap cannot contain more than u32::MAX entries");
780            self.index_insert(hash_str(key_str), idx);
781        }
782        Ok(())
783    }
784
785    pub fn get(&self, key: &str) -> Option<&NrAny> {
786        if self.index.is_empty() {
787            // Fallback to linear search (acceptable for small maps)
788            for kv in self.entries.iter() {
789                if kv.key() == key {
790                    return Some(&kv.value);
791                }
792            }
793            return None;
794        }
795
796        let h = hash_str(key);
797        let cap = self.index.len;
798        let mask = cap - 1;
799        let mut pos = (h as usize) & mask;
800
801        for _ in 0..cap {
802            let slot = unsafe { &*self.index.ptr.add(pos) };
803            match slot.state {
804                0 => return None, // Empty slot found, key doesn't exist
805                1 if slot.hash == h => {
806                    let kv = unsafe { &*self.entries.ptr.add(slot.entry_idx as usize) };
807                    if kv.key() == key {
808                        return Some(&kv.value);
809                    }
810                }
811                _ => {}
812            }
813            pos = (pos + 1) & mask;
814        }
815        None
816    }
817
818    pub fn get_mut(&mut self, key: &str) -> Option<&mut NrAny> {
819        if self.index.is_empty() {
820            for kv in self.entries.iter_mut() {
821                if kv.key() == key {
822                    return Some(&mut kv.value);
823                }
824            }
825            return None;
826        }
827
828        let h = hash_str(key);
829        let cap = self.index.len;
830        let mask = cap - 1;
831        let mut pos = (h as usize) & mask;
832
833        for _ in 0..cap {
834            let slot = unsafe { &*self.index.ptr.add(pos) };
835            match slot.state {
836                0 => return None,
837                1 if slot.hash == h => {
838                    let kv = unsafe { &mut *self.entries.ptr.add(slot.entry_idx as usize) };
839                    if kv.key() == key {
840                        return Some(&mut kv.value);
841                    }
842                }
843                _ => {}
844            }
845            pos = (pos + 1) & mask;
846        }
847        None
848    }
849
850    pub fn remove(&mut self, key: &str) -> Option<NrKVAny> {
851        // Find the entry and its exact hash slot. Remembering the slot avoids
852        // deleting the wrong entry when two keys have the same hash.
853        let (idx, removed_slot) = if self.index.is_empty() {
854            // Fallback to linear search
855            (self.entries.iter().position(|kv| kv.key() == key)?, None)
856        } else {
857            // Use hash lookup
858            let h = hash_str(key);
859            let cap = self.index.len;
860            let mask = cap - 1;
861            let mut pos = (h as usize) & mask;
862            let mut found: Option<(usize, usize)> = None;
863
864            for _ in 0..cap {
865                let slot = unsafe { &*self.index.ptr.add(pos) };
866                match slot.state {
867                    0 => break, // Empty slot found, key doesn't exist
868                    1 if slot.hash == h => {
869                        let entry_idx = slot.entry_idx as usize;
870                        let kv = unsafe { &*self.entries.ptr.add(entry_idx) };
871                        if kv.key() == key {
872                            found = Some((entry_idx, pos));
873                            break;
874                        }
875                    }
876                    _ => {}
877                }
878                pos = (pos + 1) & mask;
879            }
880
881            let (entry_idx, slot) = found?;
882            (entry_idx, Some(slot))
883        };
884
885        let last = self.entries.len - 1;
886
887        // take removed
888        let removed = unsafe { std::ptr::read(self.entries.ptr.add(idx)) };
889
890        if idx != last {
891            // Move last into idx (swap_remove)
892            unsafe {
893                let last_val = std::ptr::read(self.entries.ptr.add(last));
894                std::ptr::write(self.entries.ptr.add(idx), last_val);
895            }
896
897            // Update index for the moved entry (last -> idx)
898            if !self.index.is_empty() {
899                let h_last = unsafe {
900                    let kv = &*self.entries.ptr.add(idx);
901                    hash_str(kv.key())
902                };
903                let cap = self.index.len;
904                let mask = cap - 1;
905                let mut pos = (h_last as usize) & mask;
906
907                for _ in 0..cap {
908                    let slot = unsafe { &mut *self.index.ptr.add(pos) };
909                    if slot.state == 1 && slot.entry_idx == last as u32 {
910                        slot.entry_idx = idx as u32;
911                        break;
912                    }
913                    pos = (pos + 1) & mask;
914                }
915            }
916        }
917
918        self.entries.len -= 1;
919
920        // Remove slot from index (mark as tombstone or rehash)
921        if let Some(pos) = removed_slot {
922            let slot = unsafe { &mut *self.index.ptr.add(pos) };
923            debug_assert_eq!(slot.state, 1);
924            slot.state = 2;
925            self.used -= 1;
926            self.tomb += 1;
927
928            // Rehash if too many tombstones
929            if self.should_grow() {
930                self.rehash(self.index_len().max(16));
931            }
932        }
933
934        Some(removed)
935    }
936
937    pub fn len(&self) -> usize {
938        self.entries.len
939    }
940
941    pub fn is_empty(&self) -> bool {
942        self.entries.len == 0
943    }
944
945    pub fn clear(&mut self) {
946        self.entries.clear();
947        self.index = NrVec::default();
948        self.used = 0;
949        self.tomb = 0;
950    }
951}
952
953impl NrAny {
954    /// Stores a clonable, thread-safe Rust value behind the ABI container.
955    pub fn new<T: Clone + Send + Sync + 'static>(value: T, type_tag: u32) -> Self {
956        let size = std::mem::size_of::<T>() as u64;
957        let data = Box::into_raw(Box::new(value)) as *mut c_void;
958        Self {
959            data,
960            size,
961            type_tag,
962            clone_fn: Some(clone_any::<T>),
963            drop_fn: Some(drop_any::<T>),
964        }
965    }
966
967    /// Stores an owned copy of a byte slice as a `Vec<u8>`.
968    pub fn from_bytes(bytes: &[u8], type_tag: u32) -> Self {
969        Self::new(bytes.to_vec(), type_tag)
970    }
971
972    /// Returns a typed raw pointer after checking the stored byte size.
973    ///
974    /// The user-defined type tag is not a Rust type identifier. The caller
975    /// must verify [`NrAny::type_tag`] before dereferencing the returned pointer.
976    pub fn as_ptr<T>(&self) -> Result<*const T, NrStatus> {
977        if self.data.is_null() {
978            return Err(NrStatus::Invalid);
979        }
980        let expected_size = std::mem::size_of::<T>() as u64;
981        if self.size != expected_size {
982            return Err(NrStatus::Err);
983        }
984        Ok(self.data as *const T)
985    }
986
987    pub fn as_mut_ptr<T>(&mut self) -> Result<*mut T, NrStatus> {
988        if self.data.is_null() {
989            return Err(NrStatus::Invalid);
990        }
991        let expected_size = std::mem::size_of::<T>() as u64;
992        if self.size != expected_size {
993            return Err(NrStatus::Err);
994        }
995        Ok(self.data as *mut T)
996    }
997
998    pub fn is_null(&self) -> bool {
999        self.data.is_null()
1000    }
1001
1002    pub fn type_tag(&self) -> u32 {
1003        self.type_tag
1004    }
1005
1006    pub fn size(&self) -> u64 {
1007        self.size
1008    }
1009}
1010
1011unsafe extern "C" fn clone_any<T: Clone>(ptr: *const c_void) -> *mut c_void {
1012    if !ptr.is_null() {
1013        return std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1014            let value = unsafe { &*ptr.cast::<T>() };
1015            Box::into_raw(Box::new(value.clone())).cast::<c_void>()
1016        }))
1017        .unwrap_or(std::ptr::null_mut());
1018    }
1019    std::ptr::null_mut()
1020}
1021
1022unsafe extern "C" fn drop_any<T>(ptr: *mut c_void) {
1023    if !ptr.is_null() {
1024        let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
1025            drop(Box::from_raw(ptr.cast::<T>()));
1026        }));
1027    }
1028}
1029
1030impl Drop for NrAny {
1031    fn drop(&mut self) {
1032        if let Some(drop_fn) = self.drop_fn
1033            && !self.data.is_null()
1034        {
1035            unsafe {
1036                drop_fn(self.data);
1037            }
1038        }
1039    }
1040}
1041
1042impl NrPluginInfo {
1043    pub fn compatible(&self, expected_abi_version: u32) -> bool {
1044        self.abi_version == expected_abi_version
1045    }
1046}
1047
1048impl NrVec<u8> {
1049    /// Copies an ABI byte view into an owned vector.
1050    ///
1051    /// # Safety
1052    ///
1053    /// `bytes` must point to readable memory for its declared length.
1054    pub unsafe fn from_nr_bytes(bytes: NrBytes) -> Result<Self, NrViewError> {
1055        let v = unsafe { bytes.as_slice()? }.to_vec();
1056        Ok(Self::from_vec(v))
1057    }
1058
1059    pub fn from_string(s: String) -> Self {
1060        Self::from_vec(s.into_bytes())
1061    }
1062}
1063
1064impl<T> NrVec<T> {
1065    /// Takes ownership of a Rust vector without copying its allocation.
1066    pub fn from_vec(v: Vec<T>) -> Self {
1067        let mut v = std::mem::ManuallyDrop::new(v);
1068        let ptr = v.as_mut_ptr();
1069        let len = v.len();
1070        let cap = v.capacity();
1071        Self {
1072            ptr,
1073            len,
1074            cap,
1075            owned: 1,
1076            _reserved: [0; 7],
1077            drop_fn: Some(drop_vec::<T>),
1078        }
1079    }
1080
1081    /// Copies the elements into a vector owned by the current module.
1082    ///
1083    /// The source allocation is released by the allocator-specific callback
1084    /// supplied by the module that created this `NrVec`.
1085    pub fn into_vec(self) -> Vec<T>
1086    where
1087        T: Clone,
1088    {
1089        self.as_slice().to_vec()
1090    }
1091
1092    fn push(&mut self, value: T) {
1093        if self.owned == 0 && self.ptr.is_null() && self.len == 0 && self.cap == 0 {
1094            self.owned = 1;
1095            self.drop_fn = Some(drop_vec::<T>);
1096        }
1097        assert_eq!(self.owned, 1, "cannot mutate a borrowed NrVec");
1098        if self.len == self.cap {
1099            self.reserve(1);
1100        }
1101        unsafe {
1102            std::ptr::write(self.ptr.add(self.len), value);
1103        }
1104        self.len += 1;
1105    }
1106
1107    fn clear(&mut self) {
1108        assert_eq!(self.owned, 1, "cannot mutate a borrowed NrVec");
1109        while self.len > 0 {
1110            self.len -= 1;
1111            unsafe {
1112                std::ptr::drop_in_place(self.ptr.add(self.len));
1113            }
1114        }
1115    }
1116
1117    fn reserve(&mut self, additional: usize) {
1118        assert_eq!(self.owned, 1, "cannot resize a borrowed NrVec");
1119        if std::mem::size_of::<T>() == 0 {
1120            assert!(
1121                self.len.checked_add(additional).is_some(),
1122                "capacity overflow"
1123            );
1124            if self.cap == 0 {
1125                self.ptr = std::ptr::NonNull::<T>::dangling().as_ptr();
1126                self.cap = usize::MAX;
1127            }
1128            return;
1129        }
1130
1131        let available = self.cap - self.len;
1132        if available < additional {
1133            let required = self.len.checked_add(additional).expect("capacity overflow");
1134            let new_cap = if self.cap == 0 {
1135                std::cmp::max(1, required)
1136            } else {
1137                std::cmp::max(self.cap.saturating_mul(2), required)
1138            };
1139
1140            let new_layout = match std::alloc::Layout::array::<T>(new_cap) {
1141                Ok(layout) => layout,
1142                Err(_) => {
1143                    // Layout calculation overflow - trigger allocation error
1144                    std::alloc::handle_alloc_error(
1145                        std::alloc::Layout::from_size_align(usize::MAX, 1)
1146                            .unwrap_or_else(|_| std::alloc::Layout::new::<u8>()),
1147                    )
1148                }
1149            };
1150
1151            let new_ptr = if self.cap == 0 {
1152                unsafe { std::alloc::alloc(new_layout) }
1153            } else {
1154                let old_layout = match std::alloc::Layout::array::<T>(self.cap) {
1155                    Ok(layout) => layout,
1156                    Err(_) => {
1157                        // This should never happen since we successfully allocated before
1158                        // But handle it defensively
1159                        std::alloc::handle_alloc_error(new_layout)
1160                    }
1161                };
1162                unsafe { std::alloc::realloc(self.ptr as *mut u8, old_layout, new_layout.size()) }
1163            };
1164
1165            if new_ptr.is_null() {
1166                std::alloc::handle_alloc_error(new_layout);
1167            }
1168
1169            self.ptr = new_ptr as *mut T;
1170            self.cap = new_cap;
1171        }
1172    }
1173
1174    pub fn capacity(&self) -> usize {
1175        self.cap
1176    }
1177
1178    pub fn len(&self) -> usize {
1179        self.len
1180    }
1181
1182    pub fn is_empty(&self) -> bool {
1183        self.len == 0
1184    }
1185
1186    pub fn as_ptr(&self) -> *const T {
1187        self.ptr
1188    }
1189
1190    pub fn as_mut_ptr(&mut self) -> *mut T {
1191        self.ptr
1192    }
1193}
1194
1195impl<T> Drop for NrVec<T> {
1196    fn drop(&mut self) {
1197        if self.owned == 1
1198            && let Some(drop_fn) = self.drop_fn
1199        {
1200            unsafe {
1201                drop_fn(self.ptr, self.len, self.cap);
1202            }
1203        }
1204    }
1205}
1206
1207unsafe extern "C" fn drop_vec<T>(ptr: *mut T, len: usize, cap: usize) {
1208    let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1209        if cap != 0 {
1210            unsafe {
1211                drop(Vec::from_raw_parts(ptr, len, cap));
1212            }
1213        }
1214    }));
1215}
1216
1217impl<T> NrVec<T> {
1218    pub fn iter(&self) -> std::slice::Iter<'_, T> {
1219        self.as_slice().iter()
1220    }
1221
1222    fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> {
1223        self.as_mut_slice().iter_mut()
1224    }
1225
1226    pub fn as_slice(&self) -> &[T] {
1227        if self.len == 0 {
1228            &[]
1229        } else {
1230            unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
1231        }
1232    }
1233
1234    fn as_mut_slice(&mut self) -> &mut [T] {
1235        if self.len == 0 {
1236            &mut []
1237        } else {
1238            unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
1239        }
1240    }
1241}
1242
1243impl<'a, T> IntoIterator for &'a NrVec<T> {
1244    type Item = &'a T;
1245    type IntoIter = std::slice::Iter<'a, T>;
1246
1247    fn into_iter(self) -> Self::IntoIter {
1248        self.iter()
1249    }
1250}
1251
1252impl<T: Clone> IntoIterator for NrVec<T> {
1253    type Item = T;
1254    type IntoIter = std::vec::IntoIter<T>;
1255
1256    fn into_iter(self) -> Self::IntoIter {
1257        self.into_vec().into_iter()
1258    }
1259}
1260
1261// Safety: These types are ABI-stable data carriers.
1262// Users must ensure that the pointers they contain are valid and accessed safely.
1263unsafe impl Send for NrStr {}
1264unsafe impl Sync for NrStr {}
1265
1266unsafe impl Send for NrBytes {}
1267unsafe impl Sync for NrBytes {}
1268
1269unsafe impl Send for NrKV {}
1270unsafe impl Sync for NrKV {}
1271
1272unsafe impl Send for NrKVAny {}
1273unsafe impl Sync for NrKVAny {}
1274
1275unsafe impl Send for NrMap {}
1276unsafe impl Sync for NrMap {}
1277
1278unsafe impl Send for NrAny {}
1279unsafe impl Sync for NrAny {}
1280
1281unsafe impl Send for NrHostVTable {}
1282unsafe impl Sync for NrHostVTable {}
1283
1284unsafe impl Send for NrPluginVTable {}
1285unsafe impl Sync for NrPluginVTable {}
1286
1287unsafe impl Send for NrPluginInfo {}
1288unsafe impl Sync for NrPluginInfo {}
1289
1290unsafe impl<T: Send> Send for NrVec<T> {}
1291unsafe impl<T: Sync> Sync for NrVec<T> {}
1292
1293unsafe impl<A: Send, B: Send> Send for NrTuple<A, B> {}
1294unsafe impl<A: Sync, B: Sync> Sync for NrTuple<A, B> {}
1295
1296#[cfg(test)]
1297mod tests {
1298    use super::*;
1299    use std::mem::{align_of, size_of};
1300
1301    unsafe fn test_plugin_init(_: *mut c_void, _: *const NrHostVTable) -> NrStatus {
1302        NrStatus::Ok
1303    }
1304
1305    unsafe fn panicking_handler(_: u64, _: NrBytes) -> NrStatus {
1306        panic!("test panic must not cross the FFI boundary");
1307    }
1308
1309    fn test_plugin_shutdown() {}
1310
1311    define_plugin! {
1312        init: test_plugin_init,
1313        shutdown: test_plugin_shutdown,
1314        entries: {
1315            "panic" => panicking_handler,
1316        }
1317    }
1318
1319    #[test]
1320    fn test_layout() {
1321        // Verify NrStr layout (ptr + u32)
1322        // On 64-bit: 8 bytes ptr + 4 bytes len + 4 bytes padding = 16 bytes
1323        assert_eq!(size_of::<NrStr>(), 16);
1324        assert_eq!(align_of::<NrStr>(), 8);
1325
1326        // Verify NrBytes layout (ptr + u64)
1327        // On 64-bit: 8 bytes ptr + 8 bytes len = 16 bytes
1328        assert_eq!(size_of::<NrBytes>(), 16);
1329        assert_eq!(align_of::<NrBytes>(), 8);
1330
1331        // ptr + len + cap + ownership/reserved + allocator-specific drop fn
1332        assert_eq!(size_of::<NrVec<u8>>(), 40);
1333        assert_eq!(align_of::<NrVec<u8>>(), 8);
1334
1335        // Verify NrTuple layout (A + B)
1336        // u64 + u64 = 16 bytes
1337        assert_eq!(size_of::<NrTuple<u64, u64>>(), 16);
1338        assert_eq!(align_of::<NrTuple<u64, u64>>(), 8);
1339
1340        // Verify NrKV layout (NrStr + NrStr)
1341        // 16 + 16 = 32 bytes
1342        assert_eq!(size_of::<NrKV>(), 32);
1343        assert_eq!(align_of::<NrKV>(), 8);
1344    }
1345
1346    #[test]
1347    fn test_nr_vec() {
1348        let mut v = NrVec::<u32>::default();
1349        assert_eq!(v.len, 0);
1350        assert_eq!(v.cap, 0);
1351
1352        v.push(1);
1353        assert_eq!(v.len, 1);
1354        assert!(v.cap >= 1);
1355        unsafe {
1356            assert_eq!(*v.ptr, 1);
1357        }
1358
1359        v.push(2);
1360        assert_eq!(v.len, 2);
1361        unsafe {
1362            assert_eq!(*v.ptr.add(1), 2);
1363        }
1364
1365        v.reserve(10);
1366        assert!(v.cap >= 12); // 2 + 10
1367
1368        v.clear();
1369        assert_eq!(v.len, 0);
1370        assert!(v.cap >= 12);
1371    }
1372    #[test]
1373    fn test_nr_vec_iter() {
1374        let mut v = NrVec::<u32>::default();
1375        v.push(1);
1376        v.push(2);
1377        v.push(3);
1378
1379        let mut iter = v.iter();
1380        assert_eq!(iter.next(), Some(&1));
1381        assert_eq!(iter.next(), Some(&2));
1382        assert_eq!(iter.next(), Some(&3));
1383        assert_eq!(iter.next(), None);
1384    }
1385
1386    #[test]
1387    fn test_nr_vec_iter_mut() {
1388        let mut v = NrVec::<u32>::default();
1389        v.push(1);
1390        v.push(2);
1391        v.push(3);
1392
1393        for x in v.iter_mut() {
1394            *x *= 2;
1395        }
1396
1397        let mut iter = v.iter();
1398        assert_eq!(iter.next(), Some(&2));
1399        assert_eq!(iter.next(), Some(&4));
1400        assert_eq!(iter.next(), Some(&6));
1401        assert_eq!(iter.next(), None);
1402    }
1403
1404    #[test]
1405    fn test_nr_vec_into_iter() {
1406        let mut v = NrVec::<u32>::default();
1407        v.push(1);
1408        v.push(2);
1409        v.push(3);
1410
1411        let mut iter = v.into_iter();
1412        assert_eq!(iter.next(), Some(1));
1413        assert_eq!(iter.next(), Some(2));
1414        assert_eq!(iter.next(), Some(3));
1415        assert_eq!(iter.next(), None);
1416    }
1417
1418    #[test]
1419    fn test_nr_vec_empty_and_zero_sized_values() {
1420        assert!(NrVec::<u8>::default().into_vec().is_empty());
1421
1422        let mut values = NrVec::default();
1423        values.push(());
1424        values.push(());
1425        assert_eq!(values.len(), 2);
1426        assert_eq!(values.into_iter().count(), 2);
1427    }
1428
1429    #[test]
1430    fn test_nr_vec_collect() {
1431        let mut v = NrVec::<u32>::default();
1432        v.push(10);
1433        v.push(20);
1434
1435        let collected: Vec<u32> = v.iter().cloned().collect();
1436        assert_eq!(collected, vec![10, 20]);
1437    }
1438
1439    #[test]
1440    fn test_nr_map() {
1441        let mut map = NrMap::new();
1442        assert!(map.is_empty());
1443        assert_eq!(map.len(), 0);
1444
1445        // Insert string values
1446        let str_value1 = NrAny::new(String::from("value1"), 1);
1447        let str_value2 = NrAny::new(String::from("value2"), 1);
1448        map.insert("key1", str_value1);
1449        map.insert("key2", str_value2);
1450        assert_eq!(map.len(), 2);
1451
1452        // Get and verify string values
1453        let value1 = map.get("key1").unwrap();
1454        let str_ptr1 = value1.as_ptr::<String>().unwrap();
1455        unsafe {
1456            assert_eq!(*str_ptr1, "value1");
1457        }
1458
1459        let value2 = map.get("key2").unwrap();
1460        let str_ptr2 = value2.as_ptr::<String>().unwrap();
1461        unsafe {
1462            assert_eq!(*str_ptr2, "value2");
1463        }
1464
1465        assert!(map.get("key3").is_none());
1466
1467        // Test that get_mut returns a mutable reference
1468        let value_mut = map.get_mut("key1");
1469        assert!(value_mut.is_some());
1470
1471        // Insert integer value
1472        let int_value = NrAny::new(42i32, 2);
1473        map.insert("key3", int_value);
1474        assert_eq!(map.len(), 3);
1475
1476        let int_val = map.get("key3").unwrap();
1477        let int_ptr = int_val.as_ptr::<i32>().unwrap();
1478        unsafe {
1479            assert_eq!(*int_ptr, 42);
1480        }
1481
1482        let removed = map.remove("key2");
1483        assert!(removed.is_some());
1484        assert_eq!(map.len(), 2);
1485        assert!(map.get("key2").is_none());
1486
1487        map.clear();
1488        assert!(map.is_empty());
1489
1490        // Keys are copied into the map and remain valid after the source dies.
1491        {
1492            let temporary_key = String::from("owned-key");
1493            map.insert(&temporary_key, NrAny::new(7_u32, 2));
1494        }
1495        let stored = map.get("owned-key").unwrap();
1496        assert_eq!(unsafe { *stored.as_ptr::<u32>().unwrap() }, 7);
1497
1498        // Exercise the indexed path, then verify clear resets the index.
1499        for index in 0..16 {
1500            map.insert(&format!("key-{index}"), NrAny::new(index, 2));
1501        }
1502        for index in 0..16 {
1503            assert!(map.remove(&format!("key-{index}")).is_some());
1504        }
1505        map.clear();
1506        assert!(map.get("missing").is_none());
1507    }
1508
1509    #[test]
1510    fn test_nr_any() {
1511        let any_int = NrAny::new(42i32, 1);
1512        assert!(!any_int.is_null());
1513        assert_eq!(any_int.type_tag(), 1);
1514        assert_eq!(any_int.size(), std::mem::size_of::<i32>() as u64);
1515
1516        let ptr = any_int.as_ptr::<i32>().unwrap();
1517        unsafe {
1518            assert_eq!(*ptr, 42);
1519        }
1520
1521        let any_string = NrAny::new(String::from("hello"), 2);
1522        assert_eq!(any_string.type_tag(), 2);
1523        let str_ptr = any_string.as_ptr::<String>().unwrap();
1524        unsafe {
1525            assert_eq!(*str_ptr, "hello");
1526        }
1527        let cloned_string = any_string.clone();
1528        let cloned_ptr = cloned_string.as_ptr::<String>().unwrap();
1529        unsafe {
1530            assert_eq!(*cloned_ptr, "hello");
1531            assert_ne!(str_ptr, cloned_ptr);
1532        }
1533
1534        let any_bytes = NrAny::from_bytes(b"test", 3);
1535        assert_eq!(any_bytes.type_tag(), 3);
1536        assert_eq!(any_bytes.size(), std::mem::size_of::<Vec<u8>>() as u64);
1537        let bytes_ptr = any_bytes.as_ptr::<Vec<u8>>().unwrap();
1538        unsafe {
1539            assert_eq!(&*bytes_ptr, b"test");
1540        }
1541
1542        let default_any = NrAny::default();
1543        assert!(default_any.is_null());
1544        assert_eq!(default_any.type_tag(), 0);
1545        assert_eq!(default_any.size(), 0);
1546
1547        // Test null pointer error
1548        assert_eq!(default_any.as_ptr::<i32>(), Err(NrStatus::Invalid));
1549        let mut default_any_mut = NrAny::default();
1550        assert_eq!(default_any_mut.as_mut_ptr::<i32>(), Err(NrStatus::Invalid));
1551
1552        // Test type mismatch error
1553        let any_int = NrAny::new(42i32, 1);
1554        assert_eq!(any_int.as_ptr::<u64>(), Err(NrStatus::Err)); // i32 size != u64 size
1555        let mut any_int_mut = NrAny::new(42i32, 1);
1556        assert_eq!(any_int_mut.as_mut_ptr::<u64>(), Err(NrStatus::Err));
1557    }
1558
1559    #[test]
1560    fn test_borrowed_view_validation() {
1561        let empty = NrStr::default();
1562        assert_eq!(unsafe { empty.as_str() }.unwrap(), "");
1563
1564        let invalid_utf8 = [0xff];
1565        let invalid = NrStr {
1566            ptr: invalid_utf8.as_ptr(),
1567            len: 1,
1568            _reserved: 0,
1569        };
1570        assert!(matches!(
1571            unsafe { invalid.as_str() },
1572            Err(NrViewError::InvalidUtf8(_))
1573        ));
1574
1575        let null_bytes = NrBytes {
1576            ptr: std::ptr::null(),
1577            len: 1,
1578        };
1579        assert_eq!(
1580            unsafe { null_bytes.as_slice() },
1581            Err(NrViewError::NullPointer)
1582        );
1583    }
1584
1585    #[test]
1586    fn plugin_macro_contains_panics_and_rejects_invalid_utf8() {
1587        let panic_entry = NrStr::new("panic");
1588        assert_eq!(
1589            unsafe { plugin_handle_wrapper(panic_entry, 1, NrBytes::default()) },
1590            NrStatus::Panic
1591        );
1592
1593        let invalid_utf8 = [0xff];
1594        let invalid_entry = NrStr {
1595            ptr: invalid_utf8.as_ptr(),
1596            len: 1,
1597            _reserved: 0,
1598        };
1599        assert_eq!(
1600            unsafe { plugin_handle_wrapper(invalid_entry, 2, NrBytes::default()) },
1601            NrStatus::Invalid
1602        );
1603    }
1604}