mod canister;
mod private;
#[cfg(test)]
mod tests;
pub use canister::CanisterStableMemory;
use std::{error, fmt, io};
const WASM_PAGE_SIZE_IN_BYTES: usize = 64 * 1024;
static CANISTER_STABLE_MEMORY: CanisterStableMemory = CanisterStableMemory {};
pub trait StableMemory {
fn stable_size(&self) -> u32;
fn stable64_size(&self) -> u64;
fn stable_grow(&self, new_pages: u32) -> Result<u32, StableMemoryError>;
fn stable64_grow(&self, new_pages: u64) -> Result<u64, StableMemoryError>;
fn stable_write(&self, offset: u32, buf: &[u8]);
fn stable64_write(&self, offset: u64, buf: &[u8]);
fn stable_read(&self, offset: u32, buf: &mut [u8]);
fn stable64_read(&self, offset: u64, buf: &mut [u8]);
}
pub fn stable_size() -> u32 {
CANISTER_STABLE_MEMORY.stable_size()
}
pub fn stable64_size() -> u64 {
CANISTER_STABLE_MEMORY.stable64_size()
}
#[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 {}
pub fn stable_grow(new_pages: u32) -> Result<u32, StableMemoryError> {
CANISTER_STABLE_MEMORY.stable_grow(new_pages)
}
pub fn stable64_grow(new_pages: u64) -> Result<u64, StableMemoryError> {
CANISTER_STABLE_MEMORY.stable64_grow(new_pages)
}
pub fn stable_write(offset: u32, buf: &[u8]) {
CANISTER_STABLE_MEMORY.stable_write(offset, buf)
}
pub fn stable64_write(offset: u64, buf: &[u8]) {
CANISTER_STABLE_MEMORY.stable64_write(offset, buf)
}
pub fn stable_read(offset: u32, buf: &mut [u8]) {
CANISTER_STABLE_MEMORY.stable_read(offset, buf)
}
pub fn stable64_read(offset: u64, buf: &mut [u8]) {
CANISTER_STABLE_MEMORY.stable64_read(offset, buf)
}
pub fn stable_bytes() -> Vec<u8> {
let size = (stable_size() as usize) << 16;
let mut vec = Vec::with_capacity(size);
unsafe {
ic0::stable_read(vec.as_ptr() as i32, 0, size as i32);
vec.set_len(size);
}
vec
}
pub struct StableIO<M: StableMemory = CanisterStableMemory, A: private::AddressSize = u32> {
offset: A,
capacity: A,
memory: M,
}
impl Default for StableIO {
fn default() -> Self {
Self::with_memory(CanisterStableMemory::default(), 0)
}
}
macro_rules! impl_stable_io {
($address:ty) => {
impl<M: private::StableMemory_<$address> + StableMemory> StableIO<M, $address> {
pub fn with_memory(memory: M, offset: $address) -> Self {
let capacity = memory.stable_size_();
Self {
offset,
capacity,
memory,
}
}
pub fn offset(&self) -> $address {
self.offset
}
pub fn grow(&mut self, new_pages: $address) -> 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 $address;
let required_capacity_pages =
((required_capacity_bytes + WASM_PAGE_SIZE_IN_BYTES as $address - 1)
/ WASM_PAGE_SIZE_IN_BYTES as $address);
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 $address;
Ok(buf.len())
}
pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, StableMemoryError> {
let capacity_bytes = self.capacity * WASM_PAGE_SIZE_IN_BYTES as $address;
let read_buf = if buf.len() as $address + self.offset > capacity_bytes {
if self.offset < capacity_bytes {
&mut buf[..(capacity_bytes - self.offset) as usize]
} else {
return Err(StableMemoryError::OutOfBounds);
}
} else {
buf
};
self.memory.stable_read_(self.offset, read_buf);
self.offset += read_buf.len() as $address;
Ok(read_buf.len())
}
fn seek(&mut self, offset: io::SeekFrom) -> io::Result<u64> {
self.offset = match offset {
io::SeekFrom::Start(offset) => offset as $address,
io::SeekFrom::End(offset) => {
((self.capacity * WASM_PAGE_SIZE_IN_BYTES as $address) as i64 + offset)
as $address
}
io::SeekFrom::Current(offset) => (self.offset as i64 + offset) as $address,
};
Ok(self.offset as u64)
}
}
impl<M: private::StableMemory_<$address> + StableMemory> io::Write
for StableIO<M, $address>
{
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(())
}
}
impl<M: private::StableMemory_<$address> + StableMemory> io::Read
for StableIO<M, $address>
{
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
Self::read(self, buf).or(Ok(0)) }
}
impl<M: private::StableMemory_<$address> + StableMemory> io::Seek
for StableIO<M, $address>
{
fn seek(&mut self, offset: io::SeekFrom) -> io::Result<u64> {
self.seek(offset)
}
}
};
}
impl_stable_io!(u32);
impl_stable_io!(u64);
pub struct StableWriter<M: StableMemory = CanisterStableMemory>(StableIO<M, u32>);
#[allow(clippy::derivable_impls)]
impl Default for StableWriter {
#[inline]
fn default() -> Self {
Self(StableIO::default())
}
}
impl<M: StableMemory> StableWriter<M> {
#[inline]
pub fn with_memory(memory: M, offset: usize) -> Self {
Self(StableIO::<M, u32>::with_memory(memory, offset as u32))
}
#[inline]
pub fn offset(&self) -> usize {
self.0.offset() as usize
}
#[inline]
pub fn grow(&mut self, new_pages: u32) -> Result<(), StableMemoryError> {
self.0.grow(new_pages)
}
#[inline]
pub fn write(&mut self, buf: &[u8]) -> Result<usize, StableMemoryError> {
self.0.write(buf)
}
}
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)
}
}
impl<M: StableMemory> io::Seek for StableWriter<M> {
#[inline]
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
io::Seek::seek(&mut self.0, pos)
}
}
impl<M: StableMemory> From<StableIO<M>> for StableWriter<M> {
fn from(io: StableIO<M>) -> Self {
Self(io)
}
}
pub struct BufferedStableWriter<M: StableMemory = CanisterStableMemory> {
inner: io::BufWriter<StableWriter<M>>,
}
impl BufferedStableWriter {
pub fn new(buffer_size: usize) -> BufferedStableWriter {
BufferedStableWriter::with_writer(buffer_size, StableWriter::default())
}
}
impl<M: StableMemory> BufferedStableWriter<M> {
pub fn with_writer(buffer_size: usize, writer: StableWriter<M>) -> BufferedStableWriter<M> {
BufferedStableWriter {
inner: io::BufWriter::with_capacity(buffer_size, writer),
}
}
pub fn offset(&self) -> usize {
self.inner.get_ref().offset()
}
}
impl<M: StableMemory> io::Write for BufferedStableWriter<M> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<M: StableMemory> io::Seek for BufferedStableWriter<M> {
#[inline]
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
io::Seek::seek(&mut self.inner, pos)
}
}
pub struct StableReader<M: StableMemory = CanisterStableMemory>(StableIO<M, u32>);
#[allow(clippy::derivable_impls)]
impl Default for StableReader {
fn default() -> Self {
Self(StableIO::default())
}
}
impl<M: StableMemory> StableReader<M> {
#[inline]
pub fn with_memory(memory: M, offset: usize) -> Self {
Self(StableIO::<M, u32>::with_memory(memory, offset as u32))
}
#[inline]
pub fn offset(&self) -> usize {
self.0.offset() as usize
}
#[inline]
pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, StableMemoryError> {
self.0.read(buf)
}
}
impl<M: StableMemory> io::Read for StableReader<M> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
io::Read::read(&mut self.0, buf)
}
}
impl<M: StableMemory> io::Seek for StableReader<M> {
#[inline]
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
io::Seek::seek(&mut self.0, pos)
}
}
impl<M: StableMemory> From<StableIO<M>> for StableReader<M> {
fn from(io: StableIO<M>) -> Self {
Self(io)
}
}
pub struct BufferedStableReader<M: StableMemory = CanisterStableMemory> {
inner: io::BufReader<StableReader<M>>,
}
impl BufferedStableReader {
pub fn new(buffer_size: usize) -> BufferedStableReader {
BufferedStableReader::with_reader(buffer_size, StableReader::default())
}
}
impl<M: StableMemory> BufferedStableReader<M> {
pub fn with_reader(buffer_size: usize, reader: StableReader<M>) -> BufferedStableReader<M> {
BufferedStableReader {
inner: io::BufReader::with_capacity(buffer_size, reader),
}
}
pub fn offset(&self) -> usize {
self.inner.get_ref().offset()
}
}
impl<M: StableMemory> io::Read for BufferedStableReader<M> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl<M: StableMemory> io::Seek for BufferedStableReader<M> {
#[inline]
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
io::Seek::seek(&mut self.inner, pos)
}
}