1use crate::LedgerCommitStore;
2use ic_stable_structures::{Memory, Storable, storable::Bound};
3use serde::{Deserialize, Serialize};
4use std::borrow::Cow;
5use thiserror::Error;
6
7pub const STABLE_CELL_MAGIC: &[u8; 3] = b"SCL";
9pub const STABLE_CELL_LAYOUT_VERSION: u8 = 1;
11pub const STABLE_CELL_HEADER_SIZE: usize = 8;
13pub const STABLE_CELL_VALUE_OFFSET: u64 = 8;
15const WASM_PAGE_SIZE: u64 = 65_536;
16
17#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
28pub struct StableCellLedgerRecord {
29 store: LedgerCommitStore,
30}
31
32impl StableCellLedgerRecord {
33 #[must_use]
35 pub const fn new(store: LedgerCommitStore) -> Self {
36 Self { store }
37 }
38
39 #[must_use]
41 pub const fn store(&self) -> &LedgerCommitStore {
42 &self.store
43 }
44
45 pub const fn store_mut(&mut self) -> &mut LedgerCommitStore {
47 &mut self.store
48 }
49
50 #[must_use]
52 pub fn into_store(self) -> LedgerCommitStore {
53 self.store
54 }
55}
56
57impl Storable for StableCellLedgerRecord {
58 const BOUND: Bound = Bound::Unbounded;
59
60 fn to_bytes(&self) -> Cow<'_, [u8]> {
61 Cow::Owned(serialize_record(self))
62 }
63
64 fn into_bytes(self) -> Vec<u8> {
65 serialize_record(&self)
66 }
67
68 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
69 decode_stable_cell_ledger_record(&bytes).unwrap_or_else(|err| {
70 panic!("StableCellLedgerRecord deserialize failed: {err}");
71 })
72 }
73}
74
75#[derive(Clone, Debug, Eq, Error, PartialEq)]
80pub enum StableCellPayloadError {
81 #[error("memory is not an ic-stable-structures Cell")]
83 NotStableCell,
84 #[error("unsupported stable-cell layout version {version}")]
86 UnsupportedVersion {
87 version: u8,
89 },
90 #[error("stable-cell payload length {value_len} exceeds available bytes {available_bytes}")]
92 InvalidLength {
93 value_len: u64,
95 available_bytes: u64,
97 },
98 #[error("stable-cell payload length {value_len} cannot fit in usize")]
100 LengthOverflow {
101 value_len: u64,
103 },
104}
105
106pub fn decode_stable_cell_payload<M: Memory>(
112 memory: &M,
113) -> Result<Vec<u8>, StableCellPayloadError> {
114 let mut header = [0; STABLE_CELL_HEADER_SIZE];
115 memory.read(0, &mut header);
116 if &header[0..3] != STABLE_CELL_MAGIC {
117 return Err(StableCellPayloadError::NotStableCell);
118 }
119 if header[3] != STABLE_CELL_LAYOUT_VERSION {
120 return Err(StableCellPayloadError::UnsupportedVersion { version: header[3] });
121 }
122
123 let value_len = u64::from(u32::from_le_bytes([
124 header[4], header[5], header[6], header[7],
125 ]));
126 let available_bytes = memory.size().saturating_mul(WASM_PAGE_SIZE);
127 let payload_capacity = available_bytes.saturating_sub(STABLE_CELL_VALUE_OFFSET);
128 if value_len > payload_capacity {
129 return Err(StableCellPayloadError::InvalidLength {
130 value_len,
131 available_bytes: payload_capacity,
132 });
133 }
134 let value_len = usize::try_from(value_len)
135 .map_err(|_| StableCellPayloadError::LengthOverflow { value_len })?;
136
137 let mut bytes = vec![0; value_len];
138 memory.read(STABLE_CELL_VALUE_OFFSET, &mut bytes);
139 Ok(bytes)
140}
141
142pub fn decode_stable_cell_ledger_record(
148 bytes: &[u8],
149) -> Result<StableCellLedgerRecord, serde_cbor::Error> {
150 serde_cbor::from_slice(bytes)
151}
152
153fn serialize_record(record: &StableCellLedgerRecord) -> Vec<u8> {
154 serde_cbor::to_vec(record).unwrap_or_else(|err| {
155 panic!("StableCellLedgerRecord serialize failed: {err}");
156 })
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use ic_stable_structures::{Cell, VectorMemory};
163
164 #[test]
165 fn stable_cell_ledger_record_round_trips_through_cell() {
166 let memory = VectorMemory::default();
167 let record = StableCellLedgerRecord::default();
168 let cell = Cell::init(memory.clone(), record.clone());
169
170 assert_eq!(cell.get(), &record);
171 let payload = decode_stable_cell_payload(&memory).expect("decode stable cell payload");
172 let decoded = StableCellLedgerRecord::from_bytes(Cow::Owned(payload));
173 assert_eq!(decoded, record);
174 }
175
176 #[test]
177 fn stable_cell_payload_rejects_non_cell_memory() {
178 let memory = VectorMemory::default();
179 memory.grow(1);
180 memory.write(0, b"BAD");
181
182 assert_eq!(
183 decode_stable_cell_payload(&memory),
184 Err(StableCellPayloadError::NotStableCell)
185 );
186 }
187}