Skip to main content

ic_storage/adapter/
vector.rs

1//! Owned fixed-stride slots with bounded, checksummed values and a Wire root.
2use super::{
3    Memory, Storable,
4    error::{CollectionError as Error, Result, ensure},
5    record,
6};
7use crate::{DefaultMemoryImpl, RuntimeMemory, hash};
8use serde::{Deserialize, Serialize};
9use std::{borrow::Cow, cell::Cell, marker::PhantomData};
10
11const MAGIC: [u8; 8] = *b"ICVECC01";
12const DATA: u64 = record::HEADER_BYTES;
13const SLOT_HEADER: usize = 12;
14
15#[derive(Clone, Serialize, Deserialize)]
16struct Root {
17    length: u64,
18    bound: (u32, bool),
19}
20
21///
22/// `StableVec`
23///
24/// Indexed stable values in fixed-stride slots. Values require a finite bound.
25/// Each slot protects its index, length and payload with an integrity checksum.
26/// Native interrupted writes poison the owner and may damage retained bytes.
27///
28pub struct StableVec<T: Storable, M: Memory = DefaultMemoryImpl> {
29    memory: RuntimeMemory<M>,
30    length: Cell<u64>,
31    poisoned: Cell<bool>,
32    record: PhantomData<T>,
33}
34
35impl<T: Storable, M: Memory> StableVec<T, M> {
36    /// Create only in an empty allocation. Invalid bounds refuse before writes.
37    ///
38    /// # Errors
39    /// Rejects nonempty memory, invalid encoding/bounds or capacity refusal before writing.
40    pub fn create(memory: RuntimeMemory<M>) -> Result<Self> {
41        ensure(memory.size() == 0, Error::NotEmpty)?;
42        Self::reset(memory)
43    }
44    /// Explicitly discard previous contents in this allocation.
45    ///
46    /// # Errors
47    /// Rejects invalid encoding/bounds or capacity refusal before replacing contents.
48    pub fn reset(memory: RuntimeMemory<M>) -> Result<Self> {
49        let bytes = Self::header(0)?;
50        record::reserve(&memory, DATA)?;
51        memory.write(0, &bytes);
52        Ok(Self {
53            memory,
54            length: Cell::new(0),
55            poisoned: Cell::new(false),
56            record: PhantomData,
57        })
58    }
59    /// Read and validate the current root without initializing or writing.
60    ///
61    /// # Errors
62    /// Rejects foreign, corrupt or oversized records and incompatible codec bounds.
63    pub fn load(memory: RuntimeMemory<M>) -> Result<Self> {
64        let root: Root = record::read(&memory, 0, DATA, MAGIC)?;
65        ensure(
66            root.bound == Self::bound()?,
67            Error::Corrupt("vector codec bound changed"),
68        )?;
69        ensure(
70            Self::end(root.length)?
71                <= memory
72                    .size()
73                    .checked_mul(crate::WASM_PAGE_SIZE_BYTES)
74                    .ok_or(Error::Budget)?,
75            Error::Corrupt("vector length exceeds memory"),
76        )?;
77        Ok(Self {
78            memory,
79            length: Cell::new(root.length),
80            poisoned: Cell::new(false),
81            record: PhantomData,
82        })
83    }
84    fn bound() -> Result<(u32, bool)> {
85        record::bound::<T>().ok_or(Error::Corrupt("vector requires a finite value bound"))
86    }
87    fn end(length: u64) -> Result<u64> {
88        let stride = u64::from(Self::bound()?.0) + SLOT_HEADER as u64;
89        DATA.checked_add(length.checked_mul(stride).ok_or(Error::Budget)?)
90            .ok_or(Error::Budget)
91    }
92    fn header(length: u64) -> Result<Vec<u8>> {
93        let bytes = record::encode(
94            MAGIC,
95            &Root {
96                length,
97                bound: Self::bound()?,
98            },
99        )?;
100        ensure(bytes.len() as u64 <= DATA, Error::Budget)?;
101        Ok(bytes)
102    }
103    fn healthy(&self) -> Result<()> {
104        ensure(!self.poisoned.get(), Error::Poisoned)
105    }
106
107    /// Decode an indexed value, checking length and checksum before its codec.
108    ///
109    /// # Errors
110    /// Reports structural, integrity or application decoding failures without writing.
111    ///
112    /// # Panics
113    /// Propagates backing panics or a faulty caller codec.
114    pub fn get(&self, index: u64) -> Result<Option<T>> {
115        self.healthy()?;
116        if index >= self.length.get() {
117            return Ok(None);
118        }
119        let offset = Self::end(index)?;
120        let mut header = [0; SLOT_HEADER];
121        self.memory.read(offset, &mut header);
122        let length = u32::from_le_bytes(header[..4].try_into().unwrap());
123        ensure(
124            length <= Self::bound()?.0,
125            Error::Corrupt("vector slot length exceeds bound"),
126        )?;
127        let mut bytes = vec![0; length as usize];
128        self.memory.read(offset + SLOT_HEADER as u64, &mut bytes);
129        let digest = u64::from_le_bytes(header[4..].try_into().unwrap());
130        ensure(
131            checksum(index, &header[..4], &bytes) == digest,
132            Error::Checksum,
133        )?;
134        T::check_bounds(&bytes)?;
135        Ok(Some(T::from_bytes(Cow::Owned(bytes))?))
136    }
137    /// Replace an existing value. Encoding and bounds admission precede writes.
138    ///
139    /// # Errors
140    /// Rejects invalid indices, encoding, bounds or capacity refusal before writes.
141    pub fn set(&self, index: u64, item: &T) -> Result<()> {
142        self.healthy()?;
143        ensure(index < self.length.get(), Error::Bounds)?;
144        let bytes = item.to_bytes_checked()?;
145        self.write_slot(index, &bytes)
146    }
147    fn write_slot(&self, index: u64, bytes: &[u8]) -> Result<()> {
148        let offset = Self::end(index)?;
149        let mut header = [0; SLOT_HEADER];
150        let length = u32::try_from(bytes.len()).map_err(|_| Error::Budget)?;
151        header[..4].copy_from_slice(&length.to_le_bytes());
152        let digest = checksum(index, &header[..4], bytes);
153        header[4..].copy_from_slice(&digest.to_le_bytes());
154        self.poisoned.set(true);
155        self.memory.write(offset + SLOT_HEADER as u64, bytes);
156        self.memory.write(offset, &header);
157        self.poisoned.set(false);
158        Ok(())
159    }
160    /// Append after reserving capacity; publish the new length last.
161    ///
162    /// # Errors
163    /// Rejects invalid encoding, bounds, arithmetic overflow or capacity refusal before writes.
164    pub fn push(&self, item: &T) -> Result<()> {
165        self.healthy()?;
166        let bytes = item.to_bytes_checked()?;
167        let length = self.length.get().checked_add(1).ok_or(Error::Budget)?;
168        let header = Self::header(length)?;
169        record::reserve(&self.memory, Self::end(length)?)?;
170        self.write_slot(length - 1, &bytes)?;
171        self.poisoned.set(true);
172        self.memory.write(0, &header);
173        self.length.set(length);
174        self.poisoned.set(false);
175        Ok(())
176    }
177    /// Remove the final value, retaining allocated capacity for reuse.
178    ///
179    /// # Errors
180    /// Rejects integrity, decoding or structural errors before changing the length.
181    pub fn pop(&self) -> Result<Option<T>> {
182        self.healthy()?;
183        let Some(length) = self.length.get().checked_sub(1) else {
184            return Ok(None);
185        };
186        let value = self.get(length)?;
187        let header = Self::header(length)?;
188        self.poisoned.set(true);
189        self.memory.write(0, &header);
190        self.length.set(length);
191        self.poisoned.set(false);
192        Ok(value)
193    }
194    /// Number of committed slots. A poisoned owner must be dropped.
195    ///
196    /// # Panics
197    /// A poisoned owner must be dropped before further observation.
198    pub fn len(&self) -> u64 {
199        self.healthy().expect("vector owner is poisoned");
200        self.length.get()
201    }
202    /// Whether the vector has no values.
203    pub fn is_empty(&self) -> bool {
204        self.len() == 0
205    }
206    /// Decode slots lazily, returning errors without panicking on malformed data.
207    pub fn iter(&self) -> impl DoubleEndedIterator<Item = Result<T>> + '_ {
208        (0..self.len()).map(|index| self.get(index)?.ok_or(Error::Bounds))
209    }
210}
211
212fn checksum(index: u64, length: &[u8], bytes: &[u8]) -> u64 {
213    let digest = hash::fnv64(hash::FNV_OFFSET, &index.to_le_bytes());
214    hash::fnv64(hash::fnv64(digest, length), bytes)
215}