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::with_capacity(length);
92 if length != 0 {
93 #[expect(
99 unsafe_code,
100 reason = "The admitted read initializes fresh Vec capacity."
101 )]
102 unsafe {
103 memory.read_unsafe(PAYLOAD_OFFSET, bytes.as_mut_ptr(), length);
104 bytes.set_len(length);
105 }
106 }
107 bytes
108}
109
110pub fn write_payload<M: Memory>(memory: &M, bytes: &[u8], previous_length: Option<usize>) -> bool {
111 let Ok(length) = u64::try_from(bytes.len()) else {
112 return false;
113 };
114 let Some(end) = PAYLOAD_OFFSET.checked_add(length) else {
115 return false;
116 };
117 if previous_length.is_none_or(|previous| bytes.len() > previous) {
120 let needed_pages = end.div_ceil(WASM_PAGE_SIZE_BYTES);
121 if needed_pages.checked_mul(WASM_PAGE_SIZE_BYTES).is_none() {
122 return false;
123 }
124 let current_pages = memory.size();
125 if current_pages.checked_mul(WASM_PAGE_SIZE_BYTES).is_none() {
126 return false;
127 }
128 if needed_pages > current_pages {
129 if memory.grow(needed_pages - current_pages) < 0 {
130 return false;
131 }
132 if memory
134 .size()
135 .checked_mul(WASM_PAGE_SIZE_BYTES)
136 .is_none_or(|capacity| capacity < end)
137 {
138 return false;
139 }
140 }
141 }
142 if !bytes.is_empty() {
143 memory.write(PAYLOAD_OFFSET, bytes);
144 }
145 if previous_length != Some(bytes.len()) {
148 memory.write(0, &header(length));
149 }
150 true
151}
152
153#[cfg(test)]
154mod tests;