use super::{
Memory, Storable,
error::{CollectionError as Error, Result, ensure},
record,
};
use crate::{DefaultMemoryImpl, RuntimeMemory, hash};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, cell::Cell, marker::PhantomData};
const MAGIC: [u8; 8] = *b"ICVECC01";
const DATA: u64 = record::HEADER_BYTES;
const SLOT_HEADER: usize = 12;
#[derive(Clone, Serialize, Deserialize)]
struct Root {
length: u64,
bound: (u32, bool),
}
pub struct StableVec<T: Storable, M: Memory = DefaultMemoryImpl> {
memory: RuntimeMemory<M>,
length: Cell<u64>,
poisoned: Cell<bool>,
record: PhantomData<T>,
}
impl<T: Storable, M: Memory> StableVec<T, M> {
pub fn create(memory: RuntimeMemory<M>) -> Result<Self> {
ensure(memory.size() == 0, Error::NotEmpty)?;
Self::reset(memory)
}
pub fn reset(memory: RuntimeMemory<M>) -> Result<Self> {
let bytes = Self::header(0)?;
record::reserve(&memory, DATA)?;
memory.write(0, &bytes);
Ok(Self {
memory,
length: Cell::new(0),
poisoned: Cell::new(false),
record: PhantomData,
})
}
pub fn load(memory: RuntimeMemory<M>) -> Result<Self> {
let root: Root = record::read(&memory, 0, DATA, MAGIC)?;
ensure(
root.bound == Self::bound()?,
Error::Corrupt("vector codec bound changed"),
)?;
ensure(
Self::end(root.length)?
<= memory
.size()
.checked_mul(crate::WASM_PAGE_SIZE_BYTES)
.ok_or(Error::Budget)?,
Error::Corrupt("vector length exceeds memory"),
)?;
Ok(Self {
memory,
length: Cell::new(root.length),
poisoned: Cell::new(false),
record: PhantomData,
})
}
fn bound() -> Result<(u32, bool)> {
record::bound::<T>().ok_or(Error::Corrupt("vector requires a finite value bound"))
}
fn end(length: u64) -> Result<u64> {
let stride = u64::from(Self::bound()?.0) + SLOT_HEADER as u64;
DATA.checked_add(length.checked_mul(stride).ok_or(Error::Budget)?)
.ok_or(Error::Budget)
}
fn header(length: u64) -> Result<Vec<u8>> {
let bytes = record::encode(
MAGIC,
&Root {
length,
bound: Self::bound()?,
},
)?;
ensure(bytes.len() as u64 <= DATA, Error::Budget)?;
Ok(bytes)
}
fn healthy(&self) -> Result<()> {
ensure(!self.poisoned.get(), Error::Poisoned)
}
pub fn get(&self, index: u64) -> Result<Option<T>> {
let mut bytes = Vec::new();
if !self.read_slot(index, &mut bytes)? {
return Ok(None);
}
Ok(Some(T::from_bytes(Cow::Owned(bytes))?))
}
fn read_slot(&self, index: u64, bytes: &mut Vec<u8>) -> Result<bool> {
self.healthy()?;
if index >= self.length.get() {
return Ok(false);
}
let offset = Self::end(index)?;
let (maximum, fixed) = Self::bound()?;
let mut header = [0; SLOT_HEADER];
if fixed {
let size = SLOT_HEADER
.checked_add(maximum as usize)
.ok_or(Error::Budget)?;
bytes.resize(size, 0);
self.memory.read(offset, bytes);
header.copy_from_slice(&bytes[..SLOT_HEADER]);
bytes.copy_within(SLOT_HEADER.., 0);
bytes.truncate(maximum as usize);
} else {
self.memory.read(offset, &mut header);
}
let length = u32::from_le_bytes(header[..4].try_into().unwrap());
ensure(
length <= maximum && (!fixed || length == maximum),
Error::Corrupt("vector slot length exceeds bound"),
)?;
if !fixed {
bytes.resize(length as usize, 0);
self.memory.read(offset + SLOT_HEADER as u64, bytes);
}
let digest = u64::from_le_bytes(header[4..].try_into().unwrap());
ensure(
checksum(index, &header[..4], bytes) == digest,
Error::Checksum,
)?;
T::check_bounds(bytes)?;
Ok(true)
}
pub fn set(&self, index: u64, item: &T) -> Result<()> {
self.healthy()?;
ensure(index < self.length.get(), Error::Bounds)?;
let bytes = item.to_bytes_checked()?;
self.write_slot(index, &bytes)
}
fn write_slot(&self, index: u64, bytes: &[u8]) -> Result<()> {
let offset = Self::end(index)?;
let mut header = [0; SLOT_HEADER];
let length = u32::try_from(bytes.len()).map_err(|_| Error::Budget)?;
header[..4].copy_from_slice(&length.to_le_bytes());
let digest = checksum(index, &header[..4], bytes);
header[4..].copy_from_slice(&digest.to_le_bytes());
self.poisoned.set(true);
self.memory.write(offset + SLOT_HEADER as u64, bytes);
self.memory.write(offset, &header);
self.poisoned.set(false);
Ok(())
}
pub fn push(&self, item: &T) -> Result<()> {
self.healthy()?;
let bytes = item.to_bytes_checked()?;
let length = self.length.get().checked_add(1).ok_or(Error::Budget)?;
let header = Self::header(length)?;
record::reserve(&self.memory, Self::end(length)?)?;
self.write_slot(length - 1, &bytes)?;
self.poisoned.set(true);
self.memory.write(0, &header);
self.length.set(length);
self.poisoned.set(false);
Ok(())
}
pub fn pop(&self) -> Result<Option<T>> {
self.healthy()?;
let Some(length) = self.length.get().checked_sub(1) else {
return Ok(None);
};
let value = self.get(length)?;
let header = Self::header(length)?;
self.poisoned.set(true);
self.memory.write(0, &header);
self.length.set(length);
self.poisoned.set(false);
Ok(value)
}
pub fn len(&self) -> u64 {
self.healthy().expect("vector owner is poisoned");
self.length.get()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> impl DoubleEndedIterator<Item = Result<T>> + '_ {
let mut bytes = Vec::new();
(0..self.len()).map(move |index| {
ensure(self.read_slot(index, &mut bytes)?, Error::Bounds)?;
Ok(T::from_bytes(Cow::Borrowed(&bytes))?)
})
}
}
fn checksum(index: u64, length: &[u8], bytes: &[u8]) -> u64 {
let digest = hash::fnv64(hash::FNV_OFFSET, &index.to_le_bytes());
hash::fnv64(hash::fnv64(digest, length), bytes)
}