use std::{error, fmt};
#[cfg(feature = "candid")]
pub const WASM_PAGE_SIZE_IN_BYTES: u64 = 64 * 1024;
static CANISTER_STABLE_MEMORY: CanisterStableMemory = CanisterStableMemory {};
pub trait StableMemory {
#[cfg(feature = "candid")]
fn stable_size(&self) -> u64;
fn stable_grow(&self, new_pages: u64) -> Result<u64, StableMemoryError>;
fn stable_write(&self, offset: u64, buf: &[u8]);
fn stable_read(&self, offset: u64, buf: &mut [u8]);
}
#[derive(Debug)]
pub enum StableMemoryError {
OutOfMemory,
OutOfBounds,
}
impl fmt::Display for StableMemoryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OutOfMemory => f.write_str("Out of memory"),
Self::OutOfBounds => f.write_str("Read exceeds allocated memory"),
}
}
}
impl error::Error for StableMemoryError {}
#[derive(Default, Debug, Copy, Clone)]
pub struct CanisterStableMemory {}
impl StableMemory for CanisterStableMemory {
#[cfg(feature = "candid")]
fn stable_size(&self) -> u64 {
ic0::stable64_size()
}
fn stable_grow(&self, new_pages: u64) -> Result<u64, StableMemoryError> {
match ic0::stable64_grow(new_pages) {
u64::MAX => Err(StableMemoryError::OutOfMemory),
x => Ok(x),
}
}
fn stable_write(&self, offset: u64, buf: &[u8]) {
ic0::stable64_write(buf, offset);
}
fn stable_read(&self, offset: u64, buf: &mut [u8]) {
ic0::stable64_read(buf, offset);
}
}
#[cfg(feature = "candid")]
pub fn stable_size() -> u64 {
CANISTER_STABLE_MEMORY.stable_size()
}
pub fn stable_grow(new_pages: u64) -> Result<u64, StableMemoryError> {
CANISTER_STABLE_MEMORY.stable_grow(new_pages)
}
pub fn stable_write(offset: u64, buf: &[u8]) {
CANISTER_STABLE_MEMORY.stable_write(offset, buf);
}
pub fn stable_read(offset: u64, buf: &mut [u8]) {
CANISTER_STABLE_MEMORY.stable_read(offset, buf);
}
#[cfg(feature = "candid")]
pub fn stable_bytes() -> Vec<u8> {
let size = (stable_size() << 16)
.try_into()
.expect("overflow: stable memory too large to read in one go");
let mut vec = Vec::with_capacity(size);
ic0::stable64_read_uninit(&mut vec.spare_capacity_mut()[..size], 0);
unsafe {
vec.set_len(size);
}
vec
}
#[cfg(feature = "candid")]
mod legacy {
use super::*;
use std::io;
#[derive(Debug)]
pub struct StableIO<M: StableMemory = CanisterStableMemory> {
offset: u64,
capacity: u64,
memory: M,
}
impl Default for StableIO {
fn default() -> Self {
Self::with_memory(CanisterStableMemory::default(), 0)
}
}
impl<M: StableMemory> StableIO<M> {
pub fn with_memory(memory: M, offset: u64) -> Self {
let capacity = memory.stable_size();
Self {
offset,
capacity,
memory,
}
}
pub fn grow(&mut self, new_pages: u64) -> Result<(), StableMemoryError> {
let old_page_count = self.memory.stable_grow(new_pages)?;
self.capacity = old_page_count + new_pages;
Ok(())
}
pub fn write(&mut self, buf: &[u8]) -> Result<usize, StableMemoryError> {
let required_capacity_bytes = self.offset + buf.len() as u64;
let required_capacity_pages = required_capacity_bytes.div_ceil(WASM_PAGE_SIZE_IN_BYTES);
let current_pages = self.capacity;
let additional_pages_required = required_capacity_pages.saturating_sub(current_pages);
if additional_pages_required > 0 {
self.grow(additional_pages_required)?;
}
self.memory.stable_write(self.offset, buf);
self.offset += buf.len() as u64;
Ok(buf.len())
}
}
impl<M: StableMemory> io::Write for StableIO<M> {
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
self.write(buf)
.map_err(|e| io::Error::new(io::ErrorKind::OutOfMemory, e))
}
fn flush(&mut self) -> Result<(), io::Error> {
Ok(())
}
}
#[derive(Debug)]
pub struct StableWriter<M: StableMemory = CanisterStableMemory>(StableIO<M>);
#[allow(clippy::derivable_impls)]
impl Default for StableWriter {
#[inline]
fn default() -> Self {
Self(StableIO::default())
}
}
impl<M: StableMemory> io::Write for StableWriter<M> {
#[inline]
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
io::Write::write(&mut self.0, buf)
}
#[inline]
fn flush(&mut self) -> Result<(), io::Error> {
io::Write::flush(&mut self.0)
}
}
}
#[cfg(feature = "candid")]
pub(super) use legacy::StableWriter;