ic_storage/
stable_cell.rs1use crate::{Memory, WASM_PAGE_SIZE_BYTES};
6
7pub const STABLE_CELL_HEADER_SIZE: usize = 24;
9
10const DISCRIMINATOR: [u8; 8] = *b"STORCELL";
11const VERSION: u32 = 1;
12const PAYLOAD_OFFSET: u64 = STABLE_CELL_HEADER_SIZE as u64;
13
14#[derive(Debug, Clone, Copy, Eq, PartialEq, thiserror::Error)]
21#[non_exhaustive]
22pub enum StableCellPayloadError {
23 #[error("Cell payload length {value_len} exceeds the admitted limit")]
25 TooLarge { value_len: u64 },
26 #[error("allocation does not contain supported Cell framing")]
28 NotStableCell,
29 #[error("Cell payload length {value_len} exceeds available bytes {available_bytes}")]
31 InvalidLength {
32 value_len: u64,
33 available_bytes: u64,
34 },
35}
36
37pub fn stable_cell_payload_length<M: Memory>(
47 memory: &M,
48 maximum: usize,
49) -> Result<usize, StableCellPayloadError> {
50 let capacity = memory
51 .size()
52 .checked_mul(WASM_PAGE_SIZE_BYTES)
53 .filter(|capacity| *capacity >= PAYLOAD_OFFSET)
54 .ok_or(StableCellPayloadError::NotStableCell)?;
55 let mut header = [0; STABLE_CELL_HEADER_SIZE];
56 memory.read(0, &mut header);
57 if header[..8] != DISCRIMINATOR
58 || header[8..12] != VERSION.to_le_bytes()
59 || header[12..16] != [0; 4]
60 {
61 return Err(StableCellPayloadError::NotStableCell);
62 }
63 let mut length = [0; 8];
64 length.copy_from_slice(&header[16..]);
65 let value_len = u64::from_le_bytes(length);
66 let available_bytes = capacity - PAYLOAD_OFFSET;
67 if value_len > available_bytes {
68 return Err(StableCellPayloadError::InvalidLength {
69 value_len,
70 available_bytes,
71 });
72 }
73 let length =
74 usize::try_from(value_len).map_err(|_| StableCellPayloadError::TooLarge { value_len })?;
75 if length > maximum || length > isize::MAX as usize {
76 return Err(StableCellPayloadError::TooLarge { value_len });
77 }
78 Ok(length)
79}
80
81fn header(value_len: u64) -> [u8; STABLE_CELL_HEADER_SIZE] {
82 let mut bytes = [0; STABLE_CELL_HEADER_SIZE];
83 bytes[..8].copy_from_slice(&DISCRIMINATOR);
84 bytes[8..12].copy_from_slice(&VERSION.to_le_bytes());
85 bytes[16..].copy_from_slice(&value_len.to_le_bytes());
86 bytes
87}
88
89pub fn read_payload<M: Memory>(memory: &M, length: usize) -> Vec<u8> {
90 let mut bytes = vec![0; length];
92 if !bytes.is_empty() {
93 memory.read(PAYLOAD_OFFSET, &mut bytes);
94 }
95 bytes
96}
97
98pub fn write_payload<M: Memory>(memory: &M, bytes: &[u8], previous_length: Option<usize>) -> bool {
99 let Ok(length) = u64::try_from(bytes.len()) else {
100 return false;
101 };
102 let Some(end) = PAYLOAD_OFFSET.checked_add(length) else {
103 return false;
104 };
105 if previous_length.is_none_or(|previous| bytes.len() > previous) {
108 let needed_pages = end.div_ceil(WASM_PAGE_SIZE_BYTES);
109 if needed_pages.checked_mul(WASM_PAGE_SIZE_BYTES).is_none() {
110 return false;
111 }
112 let current_pages = memory.size();
113 if current_pages.checked_mul(WASM_PAGE_SIZE_BYTES).is_none() {
114 return false;
115 }
116 if needed_pages > current_pages {
117 if memory.grow(needed_pages - current_pages) < 0 {
118 return false;
119 }
120 if memory
122 .size()
123 .checked_mul(WASM_PAGE_SIZE_BYTES)
124 .is_none_or(|capacity| capacity < end)
125 {
126 return false;
127 }
128 }
129 }
130 if !bytes.is_empty() {
131 memory.write(PAYLOAD_OFFSET, bytes);
132 }
133 if previous_length != Some(bytes.len()) {
136 memory.write(0, &header(length));
137 }
138 true
139}
140
141#[cfg(test)]
142mod tests;