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