grafos_collections/
vec.rs

1//! Growable arrays backed by leased fabric memory.
2//!
3//! This module provides two array types:
4//!
5//! - [`FabricVec<T>`] stores elements in fixed-size slots within a single
6//!   memory lease. When the lease is full, [`grow()`](FabricVec::grow)
7//!   acquires additional leases and chains them together. The
8//!   [`push()`](FabricVec::push) method calls `grow()` automatically.
9//!
10//! - [`VarFabricVec<T>`] stores variable-size elements using an index+heap
11//!   layout. The index region holds fixed-size `(offset, len)` entries that
12//!   point into a heap region where the actual serialized data lives. This
13//!   avoids wasting space when element sizes vary widely.
14//!
15//! # FabricVec memory layout
16//!
17//! ```text
18//! Offset 0:  [header]  len: u64 (8 bytes) | capacity: u64 (8 bytes) | stride: u64 (8 bytes)
19//! Offset 24: [data]    element 0 (stride bytes) | element 1 | ...
20//! ```
21//!
22//! Each element slot is `stride` bytes wide, determined at construction time.
23//! Within each slot, the first 4 bytes store the serialized element length
24//! as a little-endian `u32`, followed by the postcard-serialized bytes.
25//! Variable-size types must fit within `stride - 4` serialized bytes;
26//! exceeding it returns [`FabricError::CapacityExceeded`].
27//!
28//! When a `FabricVec` grows, additional leases are acquired and stored in
29//! an overflow chain. Elements in overflow regions are addressed by
30//! computing which region contains the target index.
31//!
32//! # VarFabricVec memory layout
33//!
34//! ```text
35//! Offset 0:  [header]       len: u64 (8) | index_cap: u64 (8) | heap_size: u64 (8) | heap_used: u64 (8)
36//! Offset 32: [index region] entry 0 (12 bytes: offset u64 + len u32) | entry 1 | ...
37//! After index: [heap region] variable-length serialized data packed contiguously
38//! ```
39//!
40//! # Examples
41//!
42//! ```rust
43//! use grafos_collections::vec::FabricVec;
44//! use grafos_std::mem::MemBuilder;
45//!
46//! # grafos_std::host::reset_mock();
47//! # grafos_std::host::mock_set_fbmu_arena_size(65536);
48//! let lease = MemBuilder::new().min_bytes(4096).acquire()?;
49//! let mut v: FabricVec<u32> = FabricVec::new(lease, 16)?;
50//!
51//! v.push(&10)?;
52//! v.push(&20)?;
53//! assert_eq!(v.get(0)?, 10);
54//! assert_eq!(v.pop()?, Some(20));
55//! # Ok::<(), grafos_std::FabricError>(())
56//! ```
57
58extern crate alloc;
59use alloc::vec;
60use alloc::vec::Vec;
61
62use grafos_std::error::{FabricError, Result};
63use grafos_std::mem::{MemBuilder, MemLease};
64
65use serde::{de::DeserializeOwned, Serialize};
66
67const HEADER_SIZE: u64 = 24; // len(8) + capacity(8) + stride(8)
68
69/// A growable array stored in leased fabric memory.
70///
71/// Elements are serialized via postcard into fixed-size slots within the
72/// arena. The collection owns the underlying [`MemLease`] and releases it
73/// when dropped.
74///
75/// `FabricVec` is lease-scoped. It does not provide placement, quorum,
76/// replica-set membership, or cross-domain recovery by itself; use
77/// higher-level replicated resources when those guarantees are required.
78///
79/// # Type parameters
80///
81/// `T` must implement `Serialize + DeserializeOwned`. The serialized form
82/// of each element must fit within `stride - 4` bytes (4 bytes are used
83/// for the length prefix).
84///
85/// # Example
86///
87/// ```rust
88/// use grafos_collections::vec::FabricVec;
89/// use grafos_std::mem::MemBuilder;
90///
91/// # grafos_std::host::reset_mock();
92/// # grafos_std::host::mock_set_fbmu_arena_size(65536);
93/// let mut v: FabricVec<u32> = FabricVec::with_capacity(100, 16)?;
94/// v.push(&42)?;
95/// v.push(&99)?;
96///
97/// for item in v.iter() {
98///     println!("{}", item?);
99/// }
100/// # Ok::<(), grafos_std::FabricError>(())
101/// ```
102pub struct FabricVec<T> {
103    lease: MemLease,
104    len: u64,
105    capacity: u64,
106    stride: u64,
107    overflow: Vec<OverflowRegion>,
108    _marker: core::marker::PhantomData<T>,
109}
110
111/// An additional memory region chained after the primary lease is full.
112struct OverflowRegion {
113    lease: MemLease,
114    capacity: u64,
115}
116
117impl<T: Serialize + DeserializeOwned> FabricVec<T> {
118    /// Create a new `FabricVec` that takes ownership of the given lease.
119    ///
120    /// `stride` is the slot width in bytes. Each slot stores a 4-byte
121    /// length prefix plus the postcard-serialized element, so the maximum
122    /// serialized element size is `stride - 4`.
123    ///
124    /// Capacity is computed as `(arena_size - 24) / stride`.
125    ///
126    /// # Errors
127    ///
128    /// Returns [`FabricError::CapacityExceeded`] if the arena is too small
129    /// to hold even the 24-byte header.
130    pub fn new(lease: MemLease, stride: usize) -> Result<Self> {
131        let arena = lease.mem().arena_size()?;
132        if arena < HEADER_SIZE {
133            return Err(FabricError::CapacityExceeded);
134        }
135        let stride = stride as u64;
136        let capacity = (arena - HEADER_SIZE) / stride;
137        let v = FabricVec {
138            lease,
139            len: 0,
140            capacity,
141            stride,
142            overflow: Vec::new(),
143            _marker: core::marker::PhantomData,
144        };
145        v.write_header()?;
146        Ok(v)
147    }
148
149    /// Create a new `FabricVec` by acquiring a lease sized for `cap` elements
150    /// of `stride` bytes each.
151    ///
152    /// This is a convenience constructor that calls `MemBuilder::new().min_bytes(...).acquire()`
153    /// and then `FabricVec::new()`.
154    ///
155    /// # Errors
156    ///
157    /// Returns [`FabricError::CapacityExceeded`] if the host cannot provide
158    /// an arena large enough for `cap * stride + 24` bytes.
159    pub fn with_capacity(cap: usize, stride: usize) -> Result<Self> {
160        let needed = HEADER_SIZE + (cap as u64) * (stride as u64);
161        let lease = MemBuilder::new().min_bytes(needed).acquire()?;
162        Self::new(lease, stride)
163    }
164
165    /// Push an element onto the end of the vector.
166    ///
167    /// If the vector is full, `grow()` is called to acquire a new memory
168    /// lease and chain it. Returns [`FabricError::CapacityExceeded`] only
169    /// if `grow()` fails (the host cannot allocate more memory).
170    pub fn push(&mut self, item: &T) -> Result<()> {
171        if self.len >= self.capacity {
172            self.grow()?;
173        }
174        let bytes = postcard::to_allocvec(item).map_err(|_| FabricError::IoError(-1))?;
175        if bytes.len() as u64 > self.stride {
176            return Err(FabricError::CapacityExceeded);
177        }
178        let (mem, offset) = self.resolve_slot(self.len);
179        let mut slot = vec![0u8; self.stride as usize];
180        let len_bytes = (bytes.len() as u32).to_le_bytes();
181        slot[..4].copy_from_slice(&len_bytes);
182        slot[4..4 + bytes.len()].copy_from_slice(&bytes);
183        mem.write(offset, &slot)?;
184        self.len += 1;
185        self.write_header()?;
186        Ok(())
187    }
188
189    /// Acquire a new memory lease and chain it to this vector, increasing
190    /// total capacity.
191    ///
192    /// The new region has the same stride and approximately the same
193    /// capacity as the primary region.
194    ///
195    /// # Errors
196    ///
197    /// Returns [`FabricError::CapacityExceeded`] if the host cannot
198    /// allocate more memory.
199    pub fn grow(&mut self) -> Result<()> {
200        let primary_cap = (self.lease.mem().arena_size()? - HEADER_SIZE) / self.stride;
201        let needed = HEADER_SIZE + primary_cap * self.stride;
202        let new_lease = MemBuilder::new().min_bytes(needed).acquire()?;
203        let arena = new_lease.mem().arena_size()?;
204        let new_cap = (arena - HEADER_SIZE) / self.stride;
205        self.capacity += new_cap;
206        self.overflow.push(OverflowRegion {
207            lease: new_lease,
208            capacity: new_cap,
209        });
210        self.write_header()?;
211        Ok(())
212    }
213
214    /// Remove and return the last element, or `None` if the vector is empty.
215    pub fn pop(&mut self) -> Result<Option<T>> {
216        if self.len == 0 {
217            return Ok(None);
218        }
219        let item = self.get(self.len as usize - 1)?;
220        self.len -= 1;
221        self.write_header()?;
222        Ok(Some(item))
223    }
224
225    /// Read the element at `index`.
226    ///
227    /// # Errors
228    ///
229    /// Returns [`FabricError::CapacityExceeded`] if `index >= len`.
230    pub fn get(&self, index: usize) -> Result<T> {
231        if index as u64 >= self.len {
232            return Err(FabricError::CapacityExceeded);
233        }
234        let (mem, offset) = self.resolve_slot(index as u64);
235        let raw = mem.read(offset, self.stride as u32)?;
236        let ser_len = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]) as usize;
237        postcard::from_bytes(&raw[4..4 + ser_len]).map_err(|_| FabricError::IoError(-1))
238    }
239
240    /// Overwrite the element at `index`.
241    ///
242    /// # Errors
243    ///
244    /// Returns [`FabricError::CapacityExceeded`] if `index >= len`.
245    pub fn set(&mut self, index: usize, item: &T) -> Result<()> {
246        if index as u64 >= self.len {
247            return Err(FabricError::CapacityExceeded);
248        }
249        let bytes = postcard::to_allocvec(item).map_err(|_| FabricError::IoError(-1))?;
250        if bytes.len() as u64 > self.stride {
251            return Err(FabricError::CapacityExceeded);
252        }
253        let (mem, offset) = self.resolve_slot(index as u64);
254        let mut slot = vec![0u8; self.stride as usize];
255        let len_bytes = (bytes.len() as u32).to_le_bytes();
256        slot[..4].copy_from_slice(&len_bytes);
257        slot[4..4 + bytes.len()].copy_from_slice(&bytes);
258        mem.write(offset, &slot)?;
259        Ok(())
260    }
261
262    /// Returns the number of elements in the vector.
263    pub fn len(&self) -> usize {
264        self.len as usize
265    }
266
267    /// Returns `true` if the vector contains no elements.
268    pub fn is_empty(&self) -> bool {
269        self.len == 0
270    }
271
272    /// Returns the maximum number of elements the vector can hold.
273    pub fn capacity(&self) -> usize {
274        self.capacity as usize
275    }
276
277    /// Clear the vector, setting len to 0.
278    pub fn clear(&mut self) -> Result<()> {
279        self.len = 0;
280        self.write_header()
281    }
282
283    /// Returns an iterator over the elements.
284    pub fn iter(&self) -> FabricVecIter<'_, T> {
285        FabricVecIter {
286            vec: self,
287            index: 0,
288        }
289    }
290
291    /// Returns the lease ID of the primary memory lease for external
292    /// renewal management (e.g. via [`grafos_leasekit::RenewalManager`]).
293    pub fn lease_id(&self) -> u128 {
294        self.lease.lease_id()
295    }
296
297    /// Returns the expiry time (unix seconds) of the primary memory lease
298    /// for external renewal management.
299    pub fn expires_at_unix_secs(&self) -> u64 {
300        self.lease.expires_at_unix_secs()
301    }
302
303    /// Resolve an element index to the (FabricMem, byte-offset) pair for
304    /// the slot, traversing into overflow regions when necessary.
305    fn resolve_slot(&self, index: u64) -> (&grafos_std::mem::FabricMem, u64) {
306        let primary_cap = self.primary_capacity();
307        if index < primary_cap {
308            return (self.lease.mem(), HEADER_SIZE + index * self.stride);
309        }
310        let mut remaining = index - primary_cap;
311        for region in &self.overflow {
312            if remaining < region.capacity {
313                let offset = HEADER_SIZE + remaining * self.stride;
314                return (region.lease.mem(), offset);
315            }
316            remaining -= region.capacity;
317        }
318        // Should never reach here if len/capacity invariants hold.
319        // Return primary lease at offset 0 as a fallback -- the subsequent
320        // read/write will likely return an error.
321        (self.lease.mem(), 0)
322    }
323
324    /// Capacity of just the primary lease (excluding overflow).
325    fn primary_capacity(&self) -> u64 {
326        let arena = self.lease.mem().arena_size().unwrap_or(HEADER_SIZE);
327        (arena - HEADER_SIZE) / self.stride
328    }
329
330    fn write_header(&self) -> Result<()> {
331        let mut hdr = [0u8; HEADER_SIZE as usize];
332        hdr[0..8].copy_from_slice(&self.len.to_le_bytes());
333        hdr[8..16].copy_from_slice(&self.capacity.to_le_bytes());
334        hdr[16..24].copy_from_slice(&self.stride.to_le_bytes());
335        self.lease.mem().write(0, &hdr)
336    }
337}
338
339/// Iterator over elements of a [`FabricVec`].
340///
341/// Each call to `next()` performs a remote read to fetch and deserialize
342/// one element. There is no prefetching or batching.
343///
344/// Yields `Result<T>` -- a read or deserialization failure produces an
345/// `Err` item rather than panicking.
346pub struct FabricVecIter<'a, T> {
347    vec: &'a FabricVec<T>,
348    index: usize,
349}
350
351impl<'a, T: Serialize + DeserializeOwned> Iterator for FabricVecIter<'a, T> {
352    type Item = Result<T>;
353
354    fn next(&mut self) -> Option<Self::Item> {
355        if self.index >= self.vec.len() {
356            return None;
357        }
358        let result = self.vec.get(self.index);
359        self.index += 1;
360        Some(result)
361    }
362
363    fn size_hint(&self) -> (usize, Option<usize>) {
364        let remaining = self.vec.len() - self.index;
365        (remaining, Some(remaining))
366    }
367}
368
369// ---------------------------------------------------------------------------
370// VarFabricVec — variable-size element vector
371// ---------------------------------------------------------------------------
372
373const VAR_HEADER_SIZE: u64 = 32; // len(8) + index_cap(8) + heap_size(8) + heap_used(8)
374const INDEX_ENTRY_SIZE: u64 = 12; // offset(8) + len(4)
375
376/// A vector that stores variable-size elements in leased fabric memory.
377///
378/// Unlike [`FabricVec`] which uses fixed-stride slots, `VarFabricVec` uses
379/// an index+heap layout. The index region holds fixed-size 12-byte entries
380/// `(offset: u64, len: u32)` that point into a heap region where the actual
381/// postcard-serialized data is packed contiguously. This avoids wasting
382/// space when serialized element sizes vary widely (e.g. `String`,
383/// `Vec<u8>`).
384///
385/// # Trade-offs
386///
387/// - Better space efficiency for variable-size types.
388/// - Two remote reads per `get()` (index entry + heap data) instead of one.
389/// - No in-place `set()` if the new element is larger than the old one
390///   (the old heap space is not reclaimed; a compacting GC is not provided).
391///
392/// # Example
393///
394/// ```rust
395/// use grafos_collections::vec::VarFabricVec;
396/// use grafos_std::mem::MemBuilder;
397///
398/// # grafos_std::host::reset_mock();
399/// # grafos_std::host::mock_set_fbmu_arena_size(65536);
400/// let lease = MemBuilder::new().min_bytes(8192).acquire()?;
401/// let mut v: VarFabricVec<String> = VarFabricVec::new(lease, 128)?;
402///
403/// v.push(&"hello".into())?;
404/// v.push(&"world, this is a longer string".into())?;
405/// assert_eq!(v.get(0)?, "hello".to_string());
406/// assert_eq!(v.len(), 2);
407/// # Ok::<(), grafos_std::FabricError>(())
408/// ```
409pub struct VarFabricVec<T> {
410    lease: MemLease,
411    len: u64,
412    index_cap: u64,
413    heap_start: u64,
414    heap_size: u64,
415    heap_used: u64,
416    _marker: core::marker::PhantomData<T>,
417}
418
419impl<T: Serialize + DeserializeOwned> VarFabricVec<T> {
420    /// Create a new `VarFabricVec` over the given lease.
421    ///
422    /// `max_elements` determines the number of index entries reserved.
423    /// The remaining arena space after the header and index region is
424    /// used as the heap for element data.
425    ///
426    /// # Errors
427    ///
428    /// Returns [`FabricError::CapacityExceeded`] if the arena is too small
429    /// for the header, index region, and at least 1 byte of heap.
430    pub fn new(lease: MemLease, max_elements: usize) -> Result<Self> {
431        let arena = lease.mem().arena_size()?;
432        let index_cap = max_elements as u64;
433        let heap_start = VAR_HEADER_SIZE + index_cap * INDEX_ENTRY_SIZE;
434        if arena <= heap_start {
435            return Err(FabricError::CapacityExceeded);
436        }
437        let heap_size = arena - heap_start;
438        let v = VarFabricVec {
439            lease,
440            len: 0,
441            index_cap,
442            heap_start,
443            heap_size,
444            heap_used: 0,
445            _marker: core::marker::PhantomData,
446        };
447        v.write_header()?;
448        Ok(v)
449    }
450
451    /// Create a new `VarFabricVec` by acquiring a lease.
452    ///
453    /// `max_elements` is the index capacity. `avg_element_bytes` is a hint
454    /// for sizing the heap region. The actual arena requested is:
455    /// `header + index + max_elements * avg_element_bytes`.
456    pub fn with_capacity(max_elements: usize, avg_element_bytes: usize) -> Result<Self> {
457        let needed = VAR_HEADER_SIZE
458            + (max_elements as u64) * INDEX_ENTRY_SIZE
459            + (max_elements as u64) * (avg_element_bytes as u64);
460        let lease = MemBuilder::new().min_bytes(needed).acquire()?;
461        Self::new(lease, max_elements)
462    }
463
464    /// Push an element onto the end of the vector.
465    ///
466    /// Serializes the element and appends it to the heap region.
467    ///
468    /// # Errors
469    ///
470    /// - [`FabricError::CapacityExceeded`] if the index region is full or
471    ///   the heap cannot fit the serialized element.
472    pub fn push(&mut self, item: &T) -> Result<()> {
473        if self.len >= self.index_cap {
474            return Err(FabricError::CapacityExceeded);
475        }
476        let bytes = postcard::to_allocvec(item).map_err(|_| FabricError::IoError(-1))?;
477        if self.heap_used + bytes.len() as u64 > self.heap_size {
478            return Err(FabricError::CapacityExceeded);
479        }
480        // Write data to heap
481        let data_offset = self.heap_start + self.heap_used;
482        self.lease.mem().write(data_offset, &bytes)?;
483        // Write index entry
484        let idx_offset = VAR_HEADER_SIZE + self.len * INDEX_ENTRY_SIZE;
485        let mut entry = [0u8; INDEX_ENTRY_SIZE as usize];
486        entry[0..8].copy_from_slice(&self.heap_used.to_le_bytes());
487        entry[8..12].copy_from_slice(&(bytes.len() as u32).to_le_bytes());
488        self.lease.mem().write(idx_offset, &entry)?;
489
490        self.heap_used += bytes.len() as u64;
491        self.len += 1;
492        self.write_header()?;
493        Ok(())
494    }
495
496    /// Remove and return the last element, or `None` if empty.
497    ///
498    /// The heap space used by the popped element is reclaimed only if it
499    /// was the last item appended (heap_used is rewound).
500    pub fn pop(&mut self) -> Result<Option<T>> {
501        if self.len == 0 {
502            return Ok(None);
503        }
504        let item = self.get(self.len as usize - 1)?;
505        // Read the index entry to reclaim heap space
506        let idx_offset = VAR_HEADER_SIZE + (self.len - 1) * INDEX_ENTRY_SIZE;
507        let entry = self.lease.mem().read(idx_offset, INDEX_ENTRY_SIZE as u32)?;
508        let heap_offset = u64::from_le_bytes([
509            entry[0], entry[1], entry[2], entry[3], entry[4], entry[5], entry[6], entry[7],
510        ]);
511        let data_len = u32::from_le_bytes([entry[8], entry[9], entry[10], entry[11]]) as u64;
512        // Reclaim if this was at the end of the heap
513        if heap_offset + data_len == self.heap_used {
514            self.heap_used = heap_offset;
515        }
516        self.len -= 1;
517        self.write_header()?;
518        Ok(Some(item))
519    }
520
521    /// Read the element at `index`.
522    ///
523    /// Performs two remote reads: one for the index entry and one for the
524    /// heap data.
525    pub fn get(&self, index: usize) -> Result<T> {
526        if index as u64 >= self.len {
527            return Err(FabricError::CapacityExceeded);
528        }
529        let idx_offset = VAR_HEADER_SIZE + (index as u64) * INDEX_ENTRY_SIZE;
530        let entry = self.lease.mem().read(idx_offset, INDEX_ENTRY_SIZE as u32)?;
531        let heap_offset = u64::from_le_bytes([
532            entry[0], entry[1], entry[2], entry[3], entry[4], entry[5], entry[6], entry[7],
533        ]);
534        let data_len = u32::from_le_bytes([entry[8], entry[9], entry[10], entry[11]]);
535        let data = self
536            .lease
537            .mem()
538            .read(self.heap_start + heap_offset, data_len)?;
539        postcard::from_bytes(&data).map_err(|_| FabricError::IoError(-1))
540    }
541
542    /// Returns the number of elements.
543    pub fn len(&self) -> usize {
544        self.len as usize
545    }
546
547    /// Returns `true` if the vector is empty.
548    pub fn is_empty(&self) -> bool {
549        self.len == 0
550    }
551
552    /// Returns the maximum number of index entries.
553    pub fn index_capacity(&self) -> usize {
554        self.index_cap as usize
555    }
556
557    /// Returns the total heap space in bytes.
558    pub fn heap_size(&self) -> u64 {
559        self.heap_size
560    }
561
562    /// Returns the heap space used so far in bytes.
563    pub fn heap_used(&self) -> u64 {
564        self.heap_used
565    }
566
567    /// Clear the vector, resetting len and heap_used to 0.
568    pub fn clear(&mut self) -> Result<()> {
569        self.len = 0;
570        self.heap_used = 0;
571        self.write_header()
572    }
573
574    /// Returns the lease ID of the memory lease for external renewal
575    /// management (e.g. via [`grafos_leasekit::RenewalManager`]).
576    pub fn lease_id(&self) -> u128 {
577        self.lease.lease_id()
578    }
579
580    /// Returns the expiry time (unix seconds) of the memory lease for
581    /// external renewal management.
582    pub fn expires_at_unix_secs(&self) -> u64 {
583        self.lease.expires_at_unix_secs()
584    }
585
586    /// Returns an iterator over the elements.
587    pub fn iter(&self) -> VarFabricVecIter<'_, T> {
588        VarFabricVecIter {
589            vec: self,
590            index: 0,
591        }
592    }
593
594    fn write_header(&self) -> Result<()> {
595        let mut hdr = [0u8; VAR_HEADER_SIZE as usize];
596        hdr[0..8].copy_from_slice(&self.len.to_le_bytes());
597        hdr[8..16].copy_from_slice(&self.index_cap.to_le_bytes());
598        hdr[16..24].copy_from_slice(&self.heap_size.to_le_bytes());
599        hdr[24..32].copy_from_slice(&self.heap_used.to_le_bytes());
600        self.lease.mem().write(0, &hdr)
601    }
602}
603
604/// Iterator over elements of a [`VarFabricVec`].
605pub struct VarFabricVecIter<'a, T> {
606    vec: &'a VarFabricVec<T>,
607    index: usize,
608}
609
610impl<'a, T: Serialize + DeserializeOwned> Iterator for VarFabricVecIter<'a, T> {
611    type Item = Result<T>;
612
613    fn next(&mut self) -> Option<Self::Item> {
614        if self.index >= self.vec.len() {
615            return None;
616        }
617        let result = self.vec.get(self.index);
618        self.index += 1;
619        Some(result)
620    }
621
622    fn size_hint(&self) -> (usize, Option<usize>) {
623        let remaining = self.vec.len() - self.index;
624        (remaining, Some(remaining))
625    }
626}
627
628#[cfg(test)]
629mod tests {
630    use super::*;
631    use grafos_std::host;
632    use serde::{Deserialize, Serialize};
633
634    fn setup(arena_size: u64) -> MemLease {
635        host::reset_mock();
636        host::mock_set_fbmu_arena_size(arena_size);
637        MemBuilder::new().acquire().expect("acquire")
638    }
639
640    #[test]
641    fn push_pop_roundtrip() {
642        let lease = setup(4096);
643        let mut v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
644        assert!(v.is_empty());
645
646        v.push(&10).expect("push 10");
647        v.push(&20).expect("push 20");
648        v.push(&30).expect("push 30");
649        assert_eq!(v.len(), 3);
650
651        assert_eq!(v.pop().expect("pop"), Some(30));
652        assert_eq!(v.pop().expect("pop"), Some(20));
653        assert_eq!(v.pop().expect("pop"), Some(10));
654        assert_eq!(v.pop().expect("pop"), None);
655        assert!(v.is_empty());
656    }
657
658    #[test]
659    fn get_set() {
660        let lease = setup(4096);
661        let mut v: FabricVec<u64> = FabricVec::new(lease, 16).expect("new");
662
663        v.push(&100).expect("push");
664        v.push(&200).expect("push");
665        v.push(&300).expect("push");
666
667        assert_eq!(v.get(0).expect("get"), 100);
668        assert_eq!(v.get(1).expect("get"), 200);
669        assert_eq!(v.get(2).expect("get"), 300);
670
671        v.set(1, &999).expect("set");
672        assert_eq!(v.get(1).expect("get"), 999);
673    }
674
675    #[test]
676    fn get_out_of_bounds() {
677        let lease = setup(4096);
678        let v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
679        assert_eq!(v.get(0).unwrap_err(), FabricError::CapacityExceeded);
680    }
681
682    #[test]
683    fn iter_elements() {
684        let lease = setup(4096);
685        let mut v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
686        for i in 0..5u32 {
687            v.push(&i).expect("push");
688        }
689        let collected: alloc::vec::Vec<u32> = v.iter().map(|r| r.expect("iter")).collect();
690        assert_eq!(collected, alloc::vec![0, 1, 2, 3, 4]);
691    }
692
693    #[test]
694    fn vec_lease_accessors() {
695        let lease = setup(4096);
696        let v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
697        assert_ne!(v.lease_id(), 0);
698        assert!(v.expires_at_unix_secs() > 0);
699    }
700
701    #[test]
702    fn clear_resets_len() {
703        let lease = setup(4096);
704        let mut v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
705        v.push(&1).expect("push");
706        v.push(&2).expect("push");
707        assert_eq!(v.len(), 2);
708        v.clear().expect("clear");
709        assert_eq!(v.len(), 0);
710        assert!(v.is_empty());
711    }
712
713    #[test]
714    fn capacity_exceeded_when_grow_fails() {
715        // Arena = header(24) + 2 * stride(16) = 56 bytes
716        let lease = setup(56);
717        let mut v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
718        assert_eq!(v.capacity(), 2);
719
720        v.push(&1).expect("push 1");
721        v.push(&2).expect("push 2");
722        // Shrink mock arena so grow() cannot allocate
723        host::mock_set_fbmu_arena_size(0);
724        assert_eq!(v.push(&3).unwrap_err(), FabricError::CapacityExceeded);
725    }
726
727    #[test]
728    fn with_capacity_creates_appropriately_sized_vec() {
729        host::reset_mock();
730        host::mock_set_fbmu_arena_size(65536);
731
732        let v: FabricVec<u64> = FabricVec::with_capacity(100, 16).expect("with_capacity");
733        assert!(v.capacity() >= 100);
734    }
735
736    #[derive(Debug, PartialEq, Serialize, Deserialize)]
737    struct TestStruct {
738        name: alloc::string::String,
739        value: u64,
740    }
741
742    #[test]
743    fn variable_size_elements() {
744        let lease = setup(8192);
745        let mut v: FabricVec<TestStruct> = FabricVec::new(lease, 64).expect("new");
746
747        let a = TestStruct {
748            name: alloc::string::String::from("alpha"),
749            value: 42,
750        };
751        let b = TestStruct {
752            name: alloc::string::String::from("beta"),
753            value: 99,
754        };
755
756        v.push(&a).expect("push a");
757        v.push(&b).expect("push b");
758
759        assert_eq!(v.get(0).expect("get 0"), a);
760        assert_eq!(v.get(1).expect("get 1"), b);
761    }
762
763    #[test]
764    fn variable_size_elements_different_serialized_lengths() {
765        // Verify that elements with very different serialized sizes coexist
766        // correctly in the same FabricVec, as long as each fits in stride.
767        let lease = setup(8192);
768        let mut v: FabricVec<TestStruct> = FabricVec::new(lease, 128).expect("new");
769
770        let short = TestStruct {
771            name: alloc::string::String::from("x"),
772            value: 1,
773        };
774        let long = TestStruct {
775            name: alloc::string::String::from("a]relatively_long_name_for_testing_purposes"),
776            value: u64::MAX,
777        };
778        let empty = TestStruct {
779            name: alloc::string::String::new(),
780            value: 0,
781        };
782
783        v.push(&short).expect("push short");
784        v.push(&long).expect("push long");
785        v.push(&empty).expect("push empty");
786
787        assert_eq!(v.get(0).expect("get 0"), short);
788        assert_eq!(v.get(1).expect("get 1"), long);
789        assert_eq!(v.get(2).expect("get 2"), empty);
790
791        // Overwrite a short element with a longer one at the same index
792        v.set(0, &long).expect("set 0 to long");
793        assert_eq!(v.get(0).expect("get 0 after set"), long);
794    }
795
796    #[test]
797    fn variable_size_element_exceeds_stride_returns_error() {
798        // stride=16, length prefix uses 4 bytes, so max serialized size is
799        // stride (the check is bytes.len() > stride). A string whose postcard
800        // serialization exceeds stride should be rejected.
801        let lease = setup(4096);
802        let mut v: FabricVec<alloc::string::String> = FabricVec::new(lease, 16).expect("new");
803
804        // A 20-byte string will serialize to >16 bytes (1-byte varint length + 20 chars)
805        let big = alloc::string::String::from("12345678901234567890");
806        assert_eq!(v.push(&big).unwrap_err(), FabricError::CapacityExceeded);
807        assert!(v.is_empty());
808    }
809
810    #[test]
811    fn capacity_boundary_fill_then_pop_then_refill() {
812        // Arena exactly fits header(24) + 4 * stride(16) = 88 bytes → capacity 4.
813        // Fill completely, pop all, then refill to verify reuse.
814        let lease = setup(88);
815        let mut v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
816        assert_eq!(v.capacity(), 4);
817
818        // Fill to capacity
819        for i in 0..4u32 {
820            v.push(&(i * 10)).expect("push");
821        }
822        assert_eq!(v.len(), 4);
823        // Prevent grow so push fails
824        host::mock_set_fbmu_arena_size(0);
825        assert_eq!(v.push(&99).unwrap_err(), FabricError::CapacityExceeded);
826        // Restore arena for later allocations
827        host::mock_set_fbmu_arena_size(88);
828
829        // Drain fully
830        for i in (0..4u32).rev() {
831            assert_eq!(v.pop().expect("pop"), Some(i * 10));
832        }
833        assert!(v.is_empty());
834
835        // Refill with different values (len was reset, so primary region is reused)
836        for i in 100..104u32 {
837            v.push(&i).expect("re-push");
838        }
839        assert_eq!(v.len(), 4);
840        for (idx, expected) in (100..104u32).enumerate() {
841            assert_eq!(v.get(idx).expect("get"), expected);
842        }
843    }
844
845    #[test]
846    fn arena_too_small_for_header() {
847        // Arena smaller than HEADER_SIZE (24 bytes) should fail construction.
848        let lease = setup(16);
849        let result: core::result::Result<FabricVec<u32>, _> = FabricVec::new(lease, 16);
850        match result {
851            Err(FabricError::CapacityExceeded) => {}
852            other => panic!("expected CapacityExceeded, got {:?}", other.err()),
853        }
854    }
855
856    #[test]
857    fn set_out_of_bounds() {
858        let lease = setup(4096);
859        let mut v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
860        v.push(&42).expect("push");
861        // index 1 is out of bounds (len == 1)
862        assert_eq!(v.set(1, &99).unwrap_err(), FabricError::CapacityExceeded);
863        // index 0 is still valid
864        assert_eq!(v.get(0).expect("get"), 42);
865    }
866
867    #[test]
868    fn iter_size_hint() {
869        let lease = setup(4096);
870        let mut v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
871        for i in 0..5u32 {
872            v.push(&i).expect("push");
873        }
874        let mut iter = v.iter();
875        assert_eq!(iter.size_hint(), (5, Some(5)));
876        iter.next();
877        assert_eq!(iter.size_hint(), (4, Some(4)));
878    }
879
880    // --- FabricVec capacity growth tests ---
881
882    #[test]
883    fn grow_chains_new_lease() {
884        // Create a tiny arena that can hold 2 elements, then grow.
885        host::reset_mock();
886        host::mock_set_fbmu_arena_size(56); // header(24) + 2*stride(16) = 56
887        let lease = MemBuilder::new().acquire().expect("acquire");
888        let mut v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
889        assert_eq!(v.capacity(), 2);
890
891        v.push(&10).expect("push 10");
892        v.push(&20).expect("push 20");
893
894        // Now bump the mock arena so the next alloc succeeds
895        host::mock_set_fbmu_arena_size(56);
896        v.push(&30).expect("push 30 (triggers grow)");
897        assert!(v.capacity() >= 3);
898        assert_eq!(v.len(), 3);
899
900        assert_eq!(v.get(0).expect("get 0"), 10);
901        assert_eq!(v.get(1).expect("get 1"), 20);
902        assert_eq!(v.get(2).expect("get 2"), 30);
903    }
904
905    #[test]
906    fn grow_multiple_overflow_regions() {
907        host::reset_mock();
908        // Each region holds 2 elements (header=24 + 2*16=56)
909        host::mock_set_fbmu_arena_size(56);
910        let lease = MemBuilder::new().acquire().expect("acquire");
911        let mut v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
912        assert_eq!(v.capacity(), 2);
913
914        // Push 6 elements across 3 regions
915        for i in 0..6u32 {
916            v.push(&(i * 10)).expect("push");
917        }
918        assert_eq!(v.len(), 6);
919        assert!(v.capacity() >= 6);
920
921        // Verify all elements readable
922        for i in 0..6u32 {
923            assert_eq!(v.get(i as usize).expect("get"), i * 10);
924        }
925
926        // Pop all in reverse
927        for i in (0..6u32).rev() {
928            assert_eq!(v.pop().expect("pop"), Some(i * 10));
929        }
930        assert!(v.is_empty());
931    }
932
933    #[test]
934    fn grow_iter_spans_regions() {
935        host::reset_mock();
936        host::mock_set_fbmu_arena_size(56); // 2 elements per region
937        let lease = MemBuilder::new().acquire().expect("acquire");
938        let mut v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
939
940        for i in 0..5u32 {
941            v.push(&i).expect("push");
942        }
943        let collected: alloc::vec::Vec<u32> = v.iter().map(|r| r.expect("iter")).collect();
944        assert_eq!(collected, alloc::vec![0, 1, 2, 3, 4]);
945    }
946
947    #[test]
948    fn grow_set_in_overflow_region() {
949        host::reset_mock();
950        host::mock_set_fbmu_arena_size(56); // 2 per region
951        let lease = MemBuilder::new().acquire().expect("acquire");
952        let mut v: FabricVec<u32> = FabricVec::new(lease, 16).expect("new");
953
954        v.push(&1).expect("push");
955        v.push(&2).expect("push");
956        v.push(&3).expect("push"); // in overflow region
957
958        v.set(2, &99).expect("set in overflow");
959        assert_eq!(v.get(2).expect("get"), 99);
960    }
961
962    // --- FabricVec with String and Vec<u8> elements ---
963
964    #[test]
965    fn fabric_vec_string_elements() {
966        let lease = setup(8192);
967        let mut v: FabricVec<alloc::string::String> = FabricVec::new(lease, 64).expect("new");
968
969        let strings = alloc::vec![
970            alloc::string::String::from("hello"),
971            alloc::string::String::from("world"),
972            alloc::string::String::from(""),
973            alloc::string::String::from("a longer string with more characters"),
974        ];
975        for s in &strings {
976            v.push(s).expect("push");
977        }
978        for (i, s) in strings.iter().enumerate() {
979            assert_eq!(&v.get(i).expect("get"), s);
980        }
981    }
982
983    #[test]
984    fn fabric_vec_vec_u8_elements() {
985        let lease = setup(8192);
986        let mut v: FabricVec<alloc::vec::Vec<u8>> = FabricVec::new(lease, 64).expect("new");
987
988        let items: alloc::vec::Vec<alloc::vec::Vec<u8>> =
989            alloc::vec![alloc::vec![1, 2, 3], alloc::vec![], alloc::vec![255; 50],];
990        for item in &items {
991            v.push(item).expect("push");
992        }
993        for (i, item) in items.iter().enumerate() {
994            assert_eq!(&v.get(i).expect("get"), item);
995        }
996    }
997
998    // --- VarFabricVec tests ---
999
1000    #[test]
1001    fn var_vec_push_pop_roundtrip() {
1002        let lease = setup(8192);
1003        let mut v: VarFabricVec<alloc::string::String> =
1004            VarFabricVec::new(lease, 100).expect("new");
1005
1006        v.push(&alloc::string::String::from("short")).expect("push");
1007        v.push(&alloc::string::String::from(
1008            "a much longer string that takes more heap space",
1009        ))
1010        .expect("push");
1011        v.push(&alloc::string::String::from("")).expect("push");
1012        assert_eq!(v.len(), 3);
1013
1014        assert_eq!(
1015            v.get(0).expect("get 0"),
1016            alloc::string::String::from("short")
1017        );
1018        assert_eq!(
1019            v.get(1).expect("get 1"),
1020            alloc::string::String::from("a much longer string that takes more heap space")
1021        );
1022        assert_eq!(v.get(2).expect("get 2"), alloc::string::String::from(""));
1023
1024        assert_eq!(v.pop().expect("pop"), Some(alloc::string::String::from("")));
1025        assert_eq!(v.len(), 2);
1026    }
1027
1028    #[test]
1029    fn var_vec_mixed_sizes_efficient() {
1030        let lease = setup(8192);
1031        let mut v: VarFabricVec<alloc::vec::Vec<u8>> = VarFabricVec::new(lease, 100).expect("new");
1032
1033        // Push items of very different sizes
1034        v.push(&alloc::vec![42u8]).expect("push tiny");
1035        v.push(&alloc::vec![0u8; 100]).expect("push medium");
1036        v.push(&alloc::vec![255u8; 500]).expect("push large");
1037
1038        assert_eq!(v.len(), 3);
1039        assert_eq!(v.get(0).expect("get 0"), alloc::vec![42u8]);
1040        assert_eq!(v.get(1).expect("get 1"), alloc::vec![0u8; 100]);
1041        assert_eq!(v.get(2).expect("get 2"), alloc::vec![255u8; 500]);
1042
1043        // Heap usage should be roughly the sum of serialized sizes, not
1044        // 3 * max_stride.
1045        assert!(v.heap_used() < 1000);
1046    }
1047
1048    #[test]
1049    fn var_vec_with_capacity() {
1050        host::reset_mock();
1051        host::mock_set_fbmu_arena_size(65536);
1052        let v: VarFabricVec<alloc::string::String> =
1053            VarFabricVec::with_capacity(50, 32).expect("with_capacity");
1054        assert_eq!(v.index_capacity(), 50);
1055        assert!(v.heap_size() > 0);
1056    }
1057
1058    #[test]
1059    fn var_vec_clear() {
1060        let lease = setup(8192);
1061        let mut v: VarFabricVec<u32> = VarFabricVec::new(lease, 100).expect("new");
1062        v.push(&1).expect("push");
1063        v.push(&2).expect("push");
1064        v.clear().expect("clear");
1065        assert!(v.is_empty());
1066        assert_eq!(v.heap_used(), 0);
1067    }
1068
1069    #[test]
1070    fn var_vec_iter() {
1071        let lease = setup(8192);
1072        let mut v: VarFabricVec<u32> = VarFabricVec::new(lease, 100).expect("new");
1073        for i in 0..5u32 {
1074            v.push(&(i * 10)).expect("push");
1075        }
1076        let collected: alloc::vec::Vec<u32> = v.iter().map(|r| r.expect("iter")).collect();
1077        assert_eq!(collected, alloc::vec![0, 10, 20, 30, 40]);
1078    }
1079
1080    #[test]
1081    fn var_vec_heap_full_returns_error() {
1082        // Create a small arena where the heap fills up before the index.
1083        // header(32) + 10 * index_entry(12) = 152 bytes for index
1084        // With a 256-byte arena, heap = 256 - 152 = 104 bytes
1085        let lease = setup(256);
1086        let mut v: VarFabricVec<alloc::vec::Vec<u8>> = VarFabricVec::new(lease, 10).expect("new");
1087        // Each Vec<u8> of 60 bytes serializes to ~62 bytes (varint len prefix + data)
1088        v.push(&alloc::vec![0u8; 60]).expect("push 1");
1089        // Second push should fail — not enough heap
1090        let result = v.push(&alloc::vec![0u8; 60]);
1091        assert_eq!(result.unwrap_err(), FabricError::CapacityExceeded);
1092        assert_eq!(v.len(), 1);
1093    }
1094
1095    #[test]
1096    fn var_vec_index_full_returns_error() {
1097        // header(32) + 2 * index_entry(12) = 56, plus heap. With a small
1098        // index_cap=2, the third push should fail.
1099        let lease = setup(4096);
1100        let mut v: VarFabricVec<u32> = VarFabricVec::new(lease, 2).expect("new");
1101        v.push(&1).expect("push 1");
1102        v.push(&2).expect("push 2");
1103        assert_eq!(v.push(&3).unwrap_err(), FabricError::CapacityExceeded);
1104    }
1105
1106    #[test]
1107    fn var_vec_pop_reclaims_heap() {
1108        let lease = setup(8192);
1109        let mut v: VarFabricVec<alloc::string::String> =
1110            VarFabricVec::new(lease, 100).expect("new");
1111        v.push(&alloc::string::String::from("first")).expect("push");
1112        let after_first = v.heap_used();
1113        v.push(&alloc::string::String::from("second"))
1114            .expect("push");
1115        let after_second = v.heap_used();
1116        assert!(after_second > after_first);
1117
1118        // Pop reclaims the last element's heap
1119        v.pop().expect("pop");
1120        assert_eq!(v.heap_used(), after_first);
1121    }
1122}