ic_storage/adapter/
cell.rs1use super::{
3 Bound, Memory, Storable,
4 error::{CollectionError as Error, Result, ensure},
5};
6use crate::stable_cell::{STABLE_CELL_MAGIC, STABLE_CELL_VALUE_OFFSET};
7use crate::{
8 DefaultMemoryImpl, RuntimeMemory, STABLE_CELL_HEADER_SIZE, WASM_PAGE_SIZE_BYTES, hash,
9};
10use std::borrow::Cow;
11
12pub struct StableCell<T: Storable, M: Memory = DefaultMemoryImpl> {
19 memory: RuntimeMemory<M>,
20 value: T,
21}
22
23impl<T: Storable, M: Memory> StableCell<T, M> {
24 pub fn create(memory: RuntimeMemory<M>, value: T) -> Result<Self> {
29 ensure(memory.size() == 0, Error::NotEmpty)?;
30 Self::reset(memory, value)
31 }
32
33 pub fn reset(memory: RuntimeMemory<M>, value: T) -> Result<Self> {
38 Self::write(&memory, &value)?;
39 Ok(Self { memory, value })
40 }
41
42 pub fn load(memory: RuntimeMemory<M>, maximum: usize) -> Result<Self> {
51 let maximum = match T::BOUND {
52 Bound::Bounded { max_size, .. } => maximum.min(max_size as usize),
53 Bound::Unbounded => maximum,
54 };
55 let length = crate::stable_cell_payload_length(&memory, maximum)?;
56 let mut header = [0; STABLE_CELL_HEADER_SIZE];
57 memory.read(0, &mut header);
58 let mut bytes = vec![0; length];
59 memory.read(STABLE_CELL_VALUE_OFFSET, &mut bytes);
60 let expected = u64::from_le_bytes(header[16..24].try_into().unwrap());
61 ensure(checksum(&header, &bytes) == expected, Error::Checksum)?;
62 T::check_bounds(&bytes)?;
63 let value = T::from_bytes(Cow::Owned(bytes))?;
64 Ok(Self { memory, value })
65 }
66
67 pub const fn get(&self) -> &T {
69 &self.value
70 }
71
72 pub fn set(&mut self, value: T) -> Result<T> {
78 Self::write(&self.memory, &value)?;
79 Ok(std::mem::replace(&mut self.value, value))
80 }
81
82 fn write(memory: &RuntimeMemory<M>, value: &T) -> Result<()> {
83 let bytes = value.to_bytes_checked()?;
84 let length = u32::try_from(bytes.len()).map_err(|_| Error::Budget)?;
85 let pages = (STABLE_CELL_VALUE_OFFSET + u64::from(length)).div_ceil(WASM_PAGE_SIZE_BYTES);
86 let current = memory.size();
87 if pages > current {
88 memory.grow(pages - current).map_err(|_| Error::Growth)?;
89 }
90 let mut header = [0; STABLE_CELL_HEADER_SIZE];
91 header[..8].copy_from_slice(STABLE_CELL_MAGIC);
92 header[8..12].copy_from_slice(&length.to_le_bytes());
93 let digest = checksum(&header, &bytes);
94 header[16..24].copy_from_slice(&digest.to_le_bytes());
95 memory.write(STABLE_CELL_VALUE_OFFSET, &bytes);
96 memory.write(0, &header);
97 Ok(())
98 }
99}
100
101fn checksum(header: &[u8], payload: &[u8]) -> u64 {
102 hash::fnv64(hash::fnv64(hash::FNV_OFFSET, &header[..16]), payload)
103}