use crate::storable::Storable;
use crate::{Memory, WASM_PAGE_SIZE};
use std::borrow::{Borrow, Cow};
use std::fmt;
#[cfg(test)]
mod tests;
const MAGIC: &[u8; 3] = b"SCL"; const HEADER_V1_SIZE: u64 = 8;
const LAYOUT_VERSION: u8 = 1;
#[derive(Debug)]
struct HeaderV1 {
magic: [u8; 3],
version: u8,
value_length: u32,
}
#[derive(Debug, PartialEq, Eq)]
pub enum InitError {
IncompatibleVersion {
last_supported_version: u8,
decoded_version: u8,
},
ValueTooLarge { value_size: u64 },
}
impl fmt::Display for InitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InitError::IncompatibleVersion {
last_supported_version,
decoded_version,
} => write!(
f,
"Incompatible version: last supported version is {}, but the memory contains version {}",
last_supported_version, decoded_version
),
InitError::ValueTooLarge { value_size } => write!(
f,
"The initial value is too large to fit into the memory: {} bytes",
value_size
),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ValueError {
ValueTooLarge { value_size: u64 },
}
impl From<ValueError> for InitError {
fn from(e: ValueError) -> InitError {
match e {
ValueError::ValueTooLarge { value_size } => InitError::ValueTooLarge { value_size },
}
}
}
pub struct Cell<T: Storable, M: Memory> {
memory: M,
value: T,
}
impl<T: Storable, M: Memory> Cell<T, M> {
pub fn new(memory: M, value: T) -> Result<Self, ValueError> {
Self::flush_value(&memory, &value)?;
Ok(Self { memory, value })
}
pub fn init(memory: M, default_value: T) -> Result<Self, InitError> {
if memory.size() == 0 {
return Ok(Self::new(memory, default_value)?);
}
let header = Self::read_header(&memory);
if &header.magic != MAGIC {
return Ok(Self::new(memory, default_value)?);
}
if header.version != LAYOUT_VERSION {
return Err(InitError::IncompatibleVersion {
last_supported_version: LAYOUT_VERSION,
decoded_version: header.version,
});
}
Ok(Self {
value: Self::read_value(&memory, header.value_length),
memory,
})
}
fn read_value(memory: &M, len: u32) -> T {
let mut buf = vec![0; len as usize];
memory.read(HEADER_V1_SIZE, &mut buf);
T::from_bytes(Cow::Owned(buf))
}
fn read_header(memory: &M) -> HeaderV1 {
let mut magic: [u8; 3] = [0; 3];
let mut version: [u8; 1] = [0; 1];
let mut len: [u8; 4] = [0; 4];
memory.read(0, &mut magic);
memory.read(3, &mut version);
memory.read(4, &mut len);
HeaderV1 {
magic,
version: version[0],
value_length: u32::from_le_bytes(len),
}
}
pub fn get(&self) -> &T {
&self.value
}
pub fn into_memory(self) -> M {
self.memory
}
pub fn set(&mut self, value: T) -> Result<T, ValueError> {
Self::flush_value(&self.memory, &value)?;
Ok(std::mem::replace(&mut self.value, value))
}
fn flush_value(memory: &M, value: &T) -> Result<(), ValueError> {
let encoded = value.to_bytes();
let bytes: &[u8] = encoded.borrow();
let len = bytes.len();
if len > u32::MAX as usize {
return Err(ValueError::ValueTooLarge {
value_size: len as u64,
});
}
let size = memory.size();
let available_space = size * WASM_PAGE_SIZE;
if len as u64 > available_space.saturating_sub(HEADER_V1_SIZE) || size == 0 {
let grow_by =
(len as u64 + HEADER_V1_SIZE + WASM_PAGE_SIZE - size * WASM_PAGE_SIZE - 1)
/ WASM_PAGE_SIZE;
if memory.grow(grow_by) < 0 {
return Err(ValueError::ValueTooLarge {
value_size: len as u64,
});
}
}
debug_assert!(memory.size() * WASM_PAGE_SIZE >= len as u64 + HEADER_V1_SIZE);
let version = [LAYOUT_VERSION; 1];
memory.write(0, MAGIC);
memory.write(3, &version);
memory.write(4, &(len as u32).to_le_bytes());
memory.write(HEADER_V1_SIZE, bytes);
Ok(())
}
}