ic_cdk/api/stable/
mod.rs

1//! APIs to manage stable memory.
2//!
3//! You can check the [Internet Computer Specification](https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-stable-memory)
4//! for a in-depth explanation of stable memory.
5mod canister;
6
7pub use canister::CanisterStableMemory;
8use std::{error, fmt, io};
9
10/// WASM page size in bytes.
11pub const WASM_PAGE_SIZE_IN_BYTES: u64 = 64 * 1024; // 64KB
12
13static CANISTER_STABLE_MEMORY: CanisterStableMemory = CanisterStableMemory {};
14
15/// A trait defining the stable memory API which each canister running on the IC can make use of
16pub trait StableMemory {
17    /// Gets current size of the stable memory (in WASM pages).
18    fn stable_size(&self) -> u64;
19
20    /// Attempts to grow the stable memory by `new_pages` (added pages).
21    ///
22    /// Returns an error if it wasn't possible. Otherwise, returns the previous
23    /// size that was reserved.
24    ///
25    /// *Note*: Pages are 64KiB in WASM.
26    fn stable_grow(&self, new_pages: u64) -> Result<u64, StableMemoryError>;
27
28    /// Writes data to the stable memory location specified by an offset.
29    ///
30    /// Warning - this will panic if `offset + buf.len()` exceeds the current size of stable memory.
31    /// Use `stable_grow` to request more stable memory if needed.
32    fn stable_write(&self, offset: u64, buf: &[u8]);
33
34    /// Reads data from the stable memory location specified by an offset.
35    fn stable_read(&self, offset: u64, buf: &mut [u8]);
36}
37
38/// A possible error value when dealing with stable memory.
39#[derive(Debug)]
40pub enum StableMemoryError {
41    /// No more stable memory could be allocated.
42    OutOfMemory,
43    /// Attempted to read more stable memory than had been allocated.
44    OutOfBounds,
45}
46
47impl fmt::Display for StableMemoryError {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            Self::OutOfMemory => f.write_str("Out of memory"),
51            Self::OutOfBounds => f.write_str("Read exceeds allocated memory"),
52        }
53    }
54}
55
56impl error::Error for StableMemoryError {}
57
58/// Gets current size of the stable memory (in WASM pages).
59pub fn stable_size() -> u64 {
60    CANISTER_STABLE_MEMORY.stable_size()
61}
62
63/// Attempts to grow the stable memory by `new_pages` (added pages).
64///
65/// Returns an error if it wasn't possible. Otherwise, returns the previous
66/// size that was reserved.
67///
68/// *Note*: Pages are 64KiB in WASM.
69pub fn stable_grow(new_pages: u64) -> Result<u64, StableMemoryError> {
70    CANISTER_STABLE_MEMORY.stable_grow(new_pages)
71}
72
73/// Writes data to the stable memory location specified by an offset.
74///
75/// Warning - this will panic if `offset + buf.len()` exceeds the current size of stable memory.
76/// Use `stable_grow` to request more stable memory if needed.
77pub fn stable_write(offset: u64, buf: &[u8]) {
78    CANISTER_STABLE_MEMORY.stable_write(offset, buf);
79}
80
81/// Reads data from the stable memory location specified by an offset.
82pub fn stable_read(offset: u64, buf: &mut [u8]) {
83    CANISTER_STABLE_MEMORY.stable_read(offset, buf);
84}
85
86/// Returns a copy of the stable memory.
87///
88/// This will map the whole memory (even if not all of it has been written to).
89///
90/// # Panics
91///
92/// When the bytes of the stable memory cannot fit into a `Vec` which constrained by the usize.
93pub fn stable_bytes() -> Vec<u8> {
94    let size = (stable_size() << 16)
95        .try_into()
96        .expect("overflow: stable memory too large to read in one go");
97    let mut vec = Vec::with_capacity(size);
98    ic0::stable64_read_uninit(&mut vec.spare_capacity_mut()[..size], 0);
99    // SAFETY: ic0.stable_read writes to all of `vec[0..size]`, so `set_len` is safe to call with the new size.
100    unsafe {
101        vec.set_len(size);
102    }
103    vec
104}
105
106/// Performs generic IO (read, write, and seek) on stable memory.
107///
108/// Warning: When using write functionality, this will overwrite any existing
109/// data in stable memory as it writes, so ensure you set the `offset` value
110/// accordingly if you wish to preserve existing data.
111///
112/// Will attempt to grow the memory as it writes,
113/// and keep offsets and total capacity.
114#[derive(Debug)]
115pub struct StableIO<M: StableMemory = CanisterStableMemory> {
116    /// The offset of the next write.
117    offset: u64,
118
119    /// The capacity, in pages.
120    capacity: u64,
121
122    /// The stable memory to write data to.
123    memory: M,
124}
125
126impl Default for StableIO {
127    fn default() -> Self {
128        Self::with_memory(CanisterStableMemory::default(), 0)
129    }
130}
131
132impl<M: StableMemory> StableIO<M> {
133    /// Creates a new `StableIO` which writes to the selected memory
134    pub fn with_memory(memory: M, offset: u64) -> Self {
135        let capacity = memory.stable_size();
136        Self {
137            offset,
138            capacity,
139            memory,
140        }
141    }
142
143    /// Returns the offset of the writer
144    pub fn offset(&self) -> u64 {
145        self.offset
146    }
147
148    /// Attempts to grow the memory by adding new pages.
149    pub fn grow(&mut self, new_pages: u64) -> Result<(), StableMemoryError> {
150        let old_page_count = self.memory.stable_grow(new_pages)?;
151        self.capacity = old_page_count + new_pages;
152        Ok(())
153    }
154
155    /// Writes a byte slice to the buffer.
156    ///
157    /// # Errors
158    ///
159    /// When it cannot grow the memory to accommodate the new data.
160    pub fn write(&mut self, buf: &[u8]) -> Result<usize, StableMemoryError> {
161        let required_capacity_bytes = self.offset + buf.len() as u64;
162        let required_capacity_pages = required_capacity_bytes.div_ceil(WASM_PAGE_SIZE_IN_BYTES);
163        let current_pages = self.capacity;
164        let additional_pages_required = required_capacity_pages.saturating_sub(current_pages);
165
166        if additional_pages_required > 0 {
167            self.grow(additional_pages_required)?;
168        }
169
170        self.memory.stable_write(self.offset, buf);
171        self.offset += buf.len() as u64;
172        Ok(buf.len())
173    }
174
175    /// Reads data from the stable memory location specified by an offset.
176    ///
177    /// # Errors
178    ///
179    /// The stable memory size is cached on creation of the `StableReader`.
180    /// Therefore, in following scenario, it will get an `OutOfBounds` error:
181    /// 1. Create a `StableReader`
182    /// 2. Write some data to the stable memory which causes it grow
183    /// 3. call `read()` to read the newly written bytes
184    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, StableMemoryError> {
185        let capacity_bytes = self.capacity * WASM_PAGE_SIZE_IN_BYTES;
186        let read_buf = if buf.len() as u64 + self.offset > capacity_bytes {
187            if self.offset < capacity_bytes {
188                // When usize=u32:
189                //   (capacity_bytes - self.offset) < buf.len() <= u32::MAX == usize::MAX.
190                // So the cast below won't panic.
191                &mut buf[..(capacity_bytes - self.offset).try_into().unwrap()]
192            } else {
193                return Err(StableMemoryError::OutOfBounds);
194            }
195        } else {
196            buf
197        };
198        self.memory.stable_read(self.offset, read_buf);
199        self.offset += read_buf.len() as u64;
200        Ok(read_buf.len())
201    }
202
203    // Helper used to implement io::Seek
204    fn seek(&mut self, offset: io::SeekFrom) -> io::Result<u64> {
205        self.offset = match offset {
206            io::SeekFrom::Start(offset) => offset,
207            io::SeekFrom::End(offset) => {
208                ((self.capacity * WASM_PAGE_SIZE_IN_BYTES) as i64 + offset) as u64
209            }
210            io::SeekFrom::Current(offset) => (self.offset as i64 + offset) as u64,
211        };
212
213        Ok(self.offset)
214    }
215}
216
217impl<M: StableMemory> io::Write for StableIO<M> {
218    fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
219        self.write(buf)
220            .map_err(|e| io::Error::new(io::ErrorKind::OutOfMemory, e))
221    }
222
223    fn flush(&mut self) -> Result<(), io::Error> {
224        // Noop.
225        Ok(())
226    }
227}
228
229impl<M: StableMemory> io::Read for StableIO<M> {
230    fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
231        Self::read(self, buf).or(Ok(0)) // Read defines EOF to be success
232    }
233}
234
235impl<M: StableMemory> io::Seek for StableIO<M> {
236    fn seek(&mut self, offset: io::SeekFrom) -> io::Result<u64> {
237        self.seek(offset)
238    }
239}
240
241// impl_stable_io!(u32);
242// impl_stable_io!(u64);
243
244/// A writer to the stable memory.
245///
246/// Warning: This will overwrite any existing data in stable memory as it writes, so ensure you set
247/// the `offset` value accordingly if you wish to preserve existing data.
248///
249/// Will attempt to grow the memory as it writes,
250/// and keep offsets and total capacity.
251#[derive(Debug)]
252pub struct StableWriter<M: StableMemory = CanisterStableMemory>(StableIO<M>);
253
254#[allow(clippy::derivable_impls)]
255impl Default for StableWriter {
256    #[inline]
257    fn default() -> Self {
258        Self(StableIO::default())
259    }
260}
261
262impl<M: StableMemory> StableWriter<M> {
263    /// Creates a new `StableWriter` which writes to the selected memory
264    #[inline]
265    pub fn with_memory(memory: M, offset: u64) -> Self {
266        Self(StableIO::<M>::with_memory(memory, offset))
267    }
268
269    /// Returns the offset of the writer
270    #[inline]
271    pub fn offset(&self) -> u64 {
272        self.0.offset()
273    }
274
275    /// Attempts to grow the memory by adding new pages.
276    #[inline]
277    pub fn grow(&mut self, new_pages: u64) -> Result<(), StableMemoryError> {
278        self.0.grow(new_pages)
279    }
280
281    /// Writes a byte slice to the buffer.
282    ///
283    /// The only condition where this will
284    /// error out is if it cannot grow the memory.
285    #[inline]
286    pub fn write(&mut self, buf: &[u8]) -> Result<usize, StableMemoryError> {
287        self.0.write(buf)
288    }
289}
290
291impl<M: StableMemory> io::Write for StableWriter<M> {
292    #[inline]
293    fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
294        io::Write::write(&mut self.0, buf)
295    }
296
297    #[inline]
298    fn flush(&mut self) -> Result<(), io::Error> {
299        io::Write::flush(&mut self.0)
300    }
301}
302
303impl<M: StableMemory> io::Seek for StableWriter<M> {
304    #[inline]
305    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
306        io::Seek::seek(&mut self.0, pos)
307    }
308}
309
310impl<M: StableMemory> From<StableIO<M>> for StableWriter<M> {
311    fn from(io: StableIO<M>) -> Self {
312        Self(io)
313    }
314}
315
316/// A writer to the stable memory which first writes the bytes to an in memory buffer and flushes
317/// the buffer to stable memory each time it becomes full.
318///
319/// Warning: This will overwrite any existing data in stable memory as it writes, so ensure you set
320/// the `offset` value accordingly if you wish to preserve existing data.
321///
322/// Note: Each call to grow or write to stable memory is a relatively expensive operation, so pick a
323/// buffer size large enough to avoid excessive calls to stable memory.
324#[derive(Debug)]
325pub struct BufferedStableWriter<M: StableMemory = CanisterStableMemory> {
326    inner: io::BufWriter<StableWriter<M>>,
327}
328
329impl BufferedStableWriter {
330    /// Creates a new `BufferedStableWriter`
331    pub fn new(buffer_size: usize) -> BufferedStableWriter {
332        BufferedStableWriter::with_writer(buffer_size, StableWriter::default())
333    }
334}
335
336impl<M: StableMemory> BufferedStableWriter<M> {
337    /// Creates a new `BufferedStableWriter` which writes to the selected memory
338    pub fn with_writer(buffer_size: usize, writer: StableWriter<M>) -> BufferedStableWriter<M> {
339        BufferedStableWriter {
340            inner: io::BufWriter::with_capacity(buffer_size, writer),
341        }
342    }
343
344    /// Returns the offset of the writer
345    pub fn offset(&self) -> u64 {
346        self.inner.get_ref().offset()
347    }
348}
349
350impl<M: StableMemory> io::Write for BufferedStableWriter<M> {
351    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
352        self.inner.write(buf)
353    }
354
355    fn flush(&mut self) -> io::Result<()> {
356        self.inner.flush()
357    }
358}
359
360impl<M: StableMemory> io::Seek for BufferedStableWriter<M> {
361    #[inline]
362    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
363        io::Seek::seek(&mut self.inner, pos)
364    }
365}
366
367// A reader to the stable memory.
368///
369/// Keeps an offset and reads off stable memory consecutively.
370#[derive(Debug)]
371pub struct StableReader<M: StableMemory = CanisterStableMemory>(StableIO<M>);
372
373#[allow(clippy::derivable_impls)]
374impl Default for StableReader {
375    fn default() -> Self {
376        Self(StableIO::default())
377    }
378}
379
380impl<M: StableMemory> StableReader<M> {
381    /// Creates a new `StableReader` which reads from the selected memory
382    #[inline]
383    pub fn with_memory(memory: M, offset: u64) -> Self {
384        Self(StableIO::<M>::with_memory(memory, offset))
385    }
386
387    /// Returns the offset of the reader
388    #[inline]
389    pub fn offset(&self) -> u64 {
390        self.0.offset()
391    }
392
393    /// Reads data from the stable memory location specified by an offset.
394    ///
395    /// Note:
396    /// The stable memory size is cached on creation of the `StableReader`.
397    /// Therefore, in following scenario, it will get an `OutOfBounds` error:
398    /// 1. Create a `StableReader`
399    /// 2. Write some data to the stable memory which causes it grow
400    /// 3. call `read()` to read the newly written bytes
401    #[inline]
402    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, StableMemoryError> {
403        self.0.read(buf)
404    }
405}
406
407impl<M: StableMemory> io::Read for StableReader<M> {
408    #[inline]
409    fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
410        io::Read::read(&mut self.0, buf)
411    }
412}
413
414impl<M: StableMemory> io::Seek for StableReader<M> {
415    #[inline]
416    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
417        io::Seek::seek(&mut self.0, pos)
418    }
419}
420
421impl<M: StableMemory> From<StableIO<M>> for StableReader<M> {
422    fn from(io: StableIO<M>) -> Self {
423        Self(io)
424    }
425}
426
427/// A reader to the stable memory which reads bytes a chunk at a time as each chunk is required.
428#[derive(Debug)]
429pub struct BufferedStableReader<M: StableMemory = CanisterStableMemory> {
430    inner: io::BufReader<StableReader<M>>,
431}
432
433impl BufferedStableReader {
434    /// Creates a new `BufferedStableReader`
435    pub fn new(buffer_size: usize) -> BufferedStableReader {
436        BufferedStableReader::with_reader(buffer_size, StableReader::default())
437    }
438}
439
440impl<M: StableMemory> BufferedStableReader<M> {
441    /// Creates a new `BufferedStableReader` which reads from the selected memory
442    pub fn with_reader(buffer_size: usize, reader: StableReader<M>) -> BufferedStableReader<M> {
443        BufferedStableReader {
444            inner: io::BufReader::with_capacity(buffer_size, reader),
445        }
446    }
447
448    /// Returns the offset of the reader
449    pub fn offset(&self) -> u64 {
450        self.inner.get_ref().offset()
451    }
452}
453
454impl<M: StableMemory> io::Read for BufferedStableReader<M> {
455    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
456        self.inner.read(buf)
457    }
458}
459
460impl<M: StableMemory> io::Seek for BufferedStableReader<M> {
461    #[inline]
462    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
463        io::Seek::seek(&mut self.inner, pos)
464    }
465}