use std::mem::{size_of, MaybeUninit};
use wasmer::{ValueType, WasmPtr};
use crate::conversion::to_u32;
use crate::environment::{process_gas_info, Environment};
use crate::errors::{
CommunicationError, CommunicationResult, RegionValidationError, RegionValidationResult,
VmResult,
};
use crate::{BackendApi, GasInfo, Querier, Storage};
#[repr(C)]
#[derive(Default, Clone, Copy, Debug)]
pub struct Region {
pub offset: u32,
pub capacity: u32,
pub length: u32,
}
type RegionBytes = [u8; size_of::<Region>()];
impl Region {
fn from_wasm_bytes(bytes: RegionBytes) -> Self {
let offset = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let capacity = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
let length = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
Region {
offset,
capacity,
length,
}
}
fn into_wasm_bytes(self) -> RegionBytes {
let Region {
offset,
capacity,
length,
} = self;
let mut bytes = [0u8; 12];
bytes[0..4].copy_from_slice(&offset.to_le_bytes());
bytes[4..8].copy_from_slice(&capacity.to_le_bytes());
bytes[8..12].copy_from_slice(&length.to_le_bytes());
bytes
}
}
unsafe impl ValueType for Region {
fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit<u8>]) {
}
}
#[cfg(target_endian = "big")]
compile_error!("big endian systems are not supported");
pub fn read_region<A: BackendApi + 'static, S: Storage + 'static, Q: Querier + 'static>(
env: &Environment<A, S, Q>,
store: &mut wasmer::StoreMut<'_>,
ptr: u32,
max_length: usize,
) -> VmResult<Vec<u8>> {
let region = get_region(&env.memory(store), ptr)?;
let gas_info = GasInfo::with_cost(env.gas_config.read_region_cost(region.length as usize)?);
process_gas_info(env, store, gas_info)?;
if region.length > to_u32(max_length)? {
return Err(
CommunicationError::region_length_too_big(region.length as usize, max_length).into(),
);
}
let memory = env.memory(store);
let mut result = vec![0u8; region.length as usize];
memory
.read(region.offset as u64, &mut result)
.map_err(|_err| CommunicationError::region_access_err(region, memory.size().bytes().0))?;
Ok(result)
}
#[cfg(feature = "iterator")]
pub fn maybe_read_region<A: BackendApi + 'static, S: Storage + 'static, Q: Querier + 'static>(
env: &Environment<A, S, Q>,
store: &mut wasmer::StoreMut<'_>,
ptr: u32,
max_length: usize,
) -> VmResult<Option<Vec<u8>>> {
if ptr == 0 {
Ok(None)
} else {
read_region(env, store, ptr, max_length).map(Some)
}
}
pub fn write_region<A: BackendApi + 'static, S: Storage + 'static, Q: Querier + 'static>(
env: &Environment<A, S, Q>,
store: &mut wasmer::StoreMut<'_>,
ptr: u32,
data: &[u8],
) -> VmResult<()> {
let mut region = get_region(&env.memory(store), ptr)?;
let gas_info = GasInfo::with_cost(
env.gas_config
.write_region_cost
.total_cost(data.len() as u64)?,
);
process_gas_info(env, store, gas_info)?;
let region_capacity = region.capacity as usize;
if data.len() > region_capacity {
return Err(CommunicationError::region_too_small(region_capacity, data.len()).into());
}
let memory = &env.memory(store);
memory
.write(region.offset as u64, data)
.map_err(|_err| CommunicationError::region_access_err(region, memory.size().bytes().0))?;
region.length = data.len() as u32;
set_region(memory, ptr, region)?;
Ok(())
}
fn get_region(memory: &wasmer::MemoryView, offset: u32) -> CommunicationResult<Region> {
let wptr = WasmPtr::<RegionBytes>::new(offset);
let region = Region::from_wasm_bytes(wptr.deref(memory).read().map_err(|_err| {
CommunicationError::deref_err(offset, "Could not dereference this pointer to a Region")
})?);
validate_region(®ion)?;
Ok(region)
}
fn validate_region(region: &Region) -> RegionValidationResult<()> {
if region.offset == 0 {
return Err(RegionValidationError::zero_offset());
}
if region.length > region.capacity {
return Err(RegionValidationError::length_exceeds_capacity(
region.length,
region.capacity,
));
}
if region.capacity > (u32::MAX - region.offset) {
return Err(RegionValidationError::out_of_range(
region.offset,
region.capacity,
));
}
Ok(())
}
fn set_region(memory: &wasmer::MemoryView, offset: u32, data: Region) -> CommunicationResult<()> {
let wptr = WasmPtr::<RegionBytes>::new(offset);
wptr.deref(memory)
.write(data.into_wasm_bytes())
.map_err(|_err| {
CommunicationError::deref_err(offset, "Could not dereference this pointer to a Region")
})?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::mem;
use super::*;
#[test]
fn region_has_known_size() {
assert_eq!(mem::size_of::<Region>(), 12);
}
#[test]
fn validate_region_passes_for_valid_region() {
let region = Region {
offset: 23,
capacity: 500,
length: 0,
};
validate_region(®ion).unwrap();
let region = Region {
offset: 23,
capacity: 500,
length: 250,
};
validate_region(®ion).unwrap();
let region = Region {
offset: 23,
capacity: 500,
length: 500,
};
validate_region(®ion).unwrap();
let region = Region {
offset: u32::MAX,
capacity: 0,
length: 0,
};
validate_region(®ion).unwrap();
let region = Region {
offset: 1,
capacity: u32::MAX - 1,
length: 0,
};
validate_region(®ion).unwrap();
}
#[test]
fn validate_region_fails_for_zero_offset() {
let region = Region {
offset: 0,
capacity: 500,
length: 250,
};
let result = validate_region(®ion);
match result.unwrap_err() {
RegionValidationError::ZeroOffset { .. } => {}
e => panic!("Got unexpected error: {e:?}"),
}
}
#[test]
fn validate_region_fails_for_length_exceeding_capacity() {
let region = Region {
offset: 23,
capacity: 500,
length: 501,
};
let result = validate_region(®ion);
match result.unwrap_err() {
RegionValidationError::LengthExceedsCapacity {
length, capacity, ..
} => {
assert_eq!(length, 501);
assert_eq!(capacity, 500);
}
e => panic!("Got unexpected error: {e:?}"),
}
}
#[test]
fn validate_region_fails_when_exceeding_address_space() {
let region = Region {
offset: 23,
capacity: u32::MAX,
length: 501,
};
let result = validate_region(®ion);
match result.unwrap_err() {
RegionValidationError::OutOfRange {
offset, capacity, ..
} => {
assert_eq!(offset, 23);
assert_eq!(capacity, u32::MAX);
}
e => panic!("Got unexpected error: {e:?}"),
}
let region = Region {
offset: u32::MAX,
capacity: 1,
length: 0,
};
let result = validate_region(®ion);
match result.unwrap_err() {
RegionValidationError::OutOfRange {
offset, capacity, ..
} => {
assert_eq!(offset, u32::MAX);
assert_eq!(capacity, 1);
}
e => panic!("Got unexpected error: {e:?}"),
}
}
}