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