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    // SAFETY:
99    // `vec`, being mutable and allocated to `size` bytes, is safe to pass to ic0.stable_read with no offset.
100    // ic0.stable_read writes to all of `vec[0..size]`, so `set_len` is safe to call with the new size.
101    unsafe {
102        ic0::stable64_read(vec.as_ptr() as u64, 0, size as u64);
103        vec.set_len(size);
104    }
105    vec
106}
107
108/// Performs generic IO (read, write, and seek) on stable memory.
109///
110/// Warning: When using write functionality, this will overwrite any existing
111/// data in stable memory as it writes, so ensure you set the `offset` value
112/// accordingly if you wish to preserve existing data.
113///
114/// Will attempt to grow the memory as it writes,
115/// and keep offsets and total capacity.
116#[derive(Debug)]
117pub struct StableIO<M: StableMemory = CanisterStableMemory> {
118    /// The offset of the next write.
119    offset: u64,
120
121    /// The capacity, in pages.
122    capacity: u64,
123
124    /// The stable memory to write data to.
125    memory: M,
126}
127
128impl Default for StableIO {
129    fn default() -> Self {
130        Self::with_memory(CanisterStableMemory::default(), 0)
131    }
132}
133
134impl<M: StableMemory> StableIO<M> {
135    /// Creates a new `StableIO` which writes to the selected memory
136    pub fn with_memory(memory: M, offset: u64) -> Self {
137        let capacity = memory.stable_size();
138        Self {
139            offset,
140            capacity,
141            memory,
142        }
143    }
144
145    /// Returns the offset of the writer
146    pub fn offset(&self) -> u64 {
147        self.offset
148    }
149
150    /// Attempts to grow the memory by adding new pages.
151    pub fn grow(&mut self, new_pages: u64) -> Result<(), StableMemoryError> {
152        let old_page_count = self.memory.stable_grow(new_pages)?;
153        self.capacity = old_page_count + new_pages;
154        Ok(())
155    }
156
157    /// Writes a byte slice to the buffer.
158    ///
159    /// # Errors
160    ///
161    /// When it cannot grow the memory to accommodate the new data.
162    pub fn write(&mut self, buf: &[u8]) -> Result<usize, StableMemoryError> {
163        let required_capacity_bytes = self.offset + buf.len() as u64;
164        let required_capacity_pages = required_capacity_bytes.div_ceil(WASM_PAGE_SIZE_IN_BYTES);
165        let current_pages = self.capacity;
166        let additional_pages_required = required_capacity_pages.saturating_sub(current_pages);
167
168        if additional_pages_required > 0 {
169            self.grow(additional_pages_required)?;
170        }
171
172        self.memory.stable_write(self.offset, buf);
173        self.offset += buf.len() as u64;
174        Ok(buf.len())
175    }
176
177    /// Reads data from the stable memory location specified by an offset.
178    ///
179    /// # Errors
180    ///
181    /// The stable memory size is cached on creation of the StableReader.
182    /// Therefore, in following scenario, it will get an `OutOfBounds` error:
183    /// 1. Create a StableReader
184    /// 2. Write some data to the stable memory which causes it grow
185    /// 3. call `read()` to read the newly written bytes
186    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, StableMemoryError> {
187        let capacity_bytes = self.capacity * WASM_PAGE_SIZE_IN_BYTES;
188        let read_buf = if buf.len() as u64 + self.offset > capacity_bytes {
189            if self.offset < capacity_bytes {
190                // When usize=u32:
191                //   (capacity_bytes - self.offset) < buf.len() <= u32::MAX == usize::MAX.
192                // So the cast below won't panic.
193                &mut buf[..(capacity_bytes - self.offset).try_into().unwrap()]
194            } else {
195                return Err(StableMemoryError::OutOfBounds);
196            }
197        } else {
198            buf
199        };
200        self.memory.stable_read(self.offset, read_buf);
201        self.offset += read_buf.len() as u64;
202        Ok(read_buf.len())
203    }
204
205    // Helper used to implement io::Seek
206    fn seek(&mut self, offset: io::SeekFrom) -> io::Result<u64> {
207        self.offset = match offset {
208            io::SeekFrom::Start(offset) => offset,
209            io::SeekFrom::End(offset) => {
210                ((self.capacity * WASM_PAGE_SIZE_IN_BYTES) as i64 + offset) as u64
211            }
212            io::SeekFrom::Current(offset) => (self.offset as i64 + offset) as u64,
213        };
214
215        Ok(self.offset)
216    }
217}
218
219impl<M: StableMemory> io::Write for StableIO<M> {
220    fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
221        self.write(buf)
222            .map_err(|e| io::Error::new(io::ErrorKind::OutOfMemory, e))
223    }
224
225    fn flush(&mut self) -> Result<(), io::Error> {
226        // Noop.
227        Ok(())
228    }
229}
230
231impl<M: StableMemory> io::Read for StableIO<M> {
232    fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
233        Self::read(self, buf).or(Ok(0)) // Read defines EOF to be success
234    }
235}
236
237impl<M: StableMemory> io::Seek for StableIO<M> {
238    fn seek(&mut self, offset: io::SeekFrom) -> io::Result<u64> {
239        self.seek(offset)
240    }
241}
242
243// impl_stable_io!(u32);
244// impl_stable_io!(u64);
245
246/// A writer to the stable memory.
247///
248/// Warning: This will overwrite any existing data in stable memory as it writes, so ensure you set
249/// the `offset` value accordingly if you wish to preserve existing data.
250///
251/// Will attempt to grow the memory as it writes,
252/// and keep offsets and total capacity.
253#[derive(Debug)]
254pub struct StableWriter<M: StableMemory = CanisterStableMemory>(StableIO<M>);
255
256#[allow(clippy::derivable_impls)]
257impl Default for StableWriter {
258    #[inline]
259    fn default() -> Self {
260        Self(StableIO::default())
261    }
262}
263
264impl<M: StableMemory> StableWriter<M> {
265    /// Creates a new `StableWriter` which writes to the selected memory
266    #[inline]
267    pub fn with_memory(memory: M, offset: u64) -> Self {
268        Self(StableIO::<M>::with_memory(memory, offset))
269    }
270
271    /// Returns the offset of the writer
272    #[inline]
273    pub fn offset(&self) -> u64 {
274        self.0.offset()
275    }
276
277    /// Attempts to grow the memory by adding new pages.
278    #[inline]
279    pub fn grow(&mut self, new_pages: u64) -> Result<(), StableMemoryError> {
280        self.0.grow(new_pages)
281    }
282
283    /// Writes a byte slice to the buffer.
284    ///
285    /// The only condition where this will
286    /// error out is if it cannot grow the memory.
287    #[inline]
288    pub fn write(&mut self, buf: &[u8]) -> Result<usize, StableMemoryError> {
289        self.0.write(buf)
290    }
291}
292
293impl<M: StableMemory> io::Write for StableWriter<M> {
294    #[inline]
295    fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
296        io::Write::write(&mut self.0, buf)
297    }
298
299    #[inline]
300    fn flush(&mut self) -> Result<(), io::Error> {
301        io::Write::flush(&mut self.0)
302    }
303}
304
305impl<M: StableMemory> io::Seek for StableWriter<M> {
306    #[inline]
307    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
308        io::Seek::seek(&mut self.0, pos)
309    }
310}
311
312impl<M: StableMemory> From<StableIO<M>> for StableWriter<M> {
313    fn from(io: StableIO<M>) -> Self {
314        Self(io)
315    }
316}
317
318/// A writer to the stable memory which first writes the bytes to an in memory buffer and flushes
319/// the buffer to stable memory each time it becomes full.
320///
321/// Warning: This will overwrite any existing data in stable memory as it writes, so ensure you set
322/// the `offset` value accordingly if you wish to preserve existing data.
323///
324/// Note: Each call to grow or write to stable memory is a relatively expensive operation, so pick a
325/// buffer size large enough to avoid excessive calls to stable memory.
326#[derive(Debug)]
327pub struct BufferedStableWriter<M: StableMemory = CanisterStableMemory> {
328    inner: io::BufWriter<StableWriter<M>>,
329}
330
331impl BufferedStableWriter {
332    /// Creates a new `BufferedStableWriter`
333    pub fn new(buffer_size: usize) -> BufferedStableWriter {
334        BufferedStableWriter::with_writer(buffer_size, StableWriter::default())
335    }
336}
337
338impl<M: StableMemory> BufferedStableWriter<M> {
339    /// Creates a new `BufferedStableWriter` which writes to the selected memory
340    pub fn with_writer(buffer_size: usize, writer: StableWriter<M>) -> BufferedStableWriter<M> {
341        BufferedStableWriter {
342            inner: io::BufWriter::with_capacity(buffer_size, writer),
343        }
344    }
345
346    /// Returns the offset of the writer
347    pub fn offset(&self) -> u64 {
348        self.inner.get_ref().offset()
349    }
350}
351
352impl<M: StableMemory> io::Write for BufferedStableWriter<M> {
353    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
354        self.inner.write(buf)
355    }
356
357    fn flush(&mut self) -> io::Result<()> {
358        self.inner.flush()
359    }
360}
361
362impl<M: StableMemory> io::Seek for BufferedStableWriter<M> {
363    #[inline]
364    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
365        io::Seek::seek(&mut self.inner, pos)
366    }
367}
368
369// A reader to the stable memory.
370///
371/// Keeps an offset and reads off stable memory consecutively.
372#[derive(Debug)]
373pub struct StableReader<M: StableMemory = CanisterStableMemory>(StableIO<M>);
374
375#[allow(clippy::derivable_impls)]
376impl Default for StableReader {
377    fn default() -> Self {
378        Self(StableIO::default())
379    }
380}
381
382impl<M: StableMemory> StableReader<M> {
383    /// Creates a new `StableReader` which reads from the selected memory
384    #[inline]
385    pub fn with_memory(memory: M, offset: u64) -> Self {
386        Self(StableIO::<M>::with_memory(memory, offset))
387    }
388
389    /// Returns the offset of the reader
390    #[inline]
391    pub fn offset(&self) -> u64 {
392        self.0.offset()
393    }
394
395    /// Reads data from the stable memory location specified by an offset.
396    ///
397    /// Note:
398    /// The stable memory size is cached on creation of the StableReader.
399    /// Therefore, in following scenario, it will get an `OutOfBounds` error:
400    /// 1. Create a StableReader
401    /// 2. Write some data to the stable memory which causes it grow
402    /// 3. call `read()` to read the newly written bytes
403    #[inline]
404    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, StableMemoryError> {
405        self.0.read(buf)
406    }
407}
408
409impl<M: StableMemory> io::Read for StableReader<M> {
410    #[inline]
411    fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
412        io::Read::read(&mut self.0, buf)
413    }
414}
415
416impl<M: StableMemory> io::Seek for StableReader<M> {
417    #[inline]
418    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
419        io::Seek::seek(&mut self.0, pos)
420    }
421}
422
423impl<M: StableMemory> From<StableIO<M>> for StableReader<M> {
424    fn from(io: StableIO<M>) -> Self {
425        Self(io)
426    }
427}
428
429/// A reader to the stable memory which reads bytes a chunk at a time as each chunk is required.
430#[derive(Debug)]
431pub struct BufferedStableReader<M: StableMemory = CanisterStableMemory> {
432    inner: io::BufReader<StableReader<M>>,
433}
434
435impl BufferedStableReader {
436    /// Creates a new `BufferedStableReader`
437    pub fn new(buffer_size: usize) -> BufferedStableReader {
438        BufferedStableReader::with_reader(buffer_size, StableReader::default())
439    }
440}
441
442impl<M: StableMemory> BufferedStableReader<M> {
443    /// Creates a new `BufferedStableReader` which reads from the selected memory
444    pub fn with_reader(buffer_size: usize, reader: StableReader<M>) -> BufferedStableReader<M> {
445        BufferedStableReader {
446            inner: io::BufReader::with_capacity(buffer_size, reader),
447        }
448    }
449
450    /// Returns the offset of the reader
451    pub fn offset(&self) -> u64 {
452        self.inner.get_ref().offset()
453    }
454}
455
456impl<M: StableMemory> io::Read for BufferedStableReader<M> {
457    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
458        self.inner.read(buf)
459    }
460}
461
462impl<M: StableMemory> io::Seek for BufferedStableReader<M> {
463    #[inline]
464    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
465        io::Seek::seek(&mut self.inner, pos)
466    }
467}