Skip to main content

wasm_dbms_memory/
provider.rs

1// Rust guideline compliant 2026-02-28
2
3use wasm_dbms_api::prelude::{MemoryError, MemoryResult};
4
5/// The size of a WASM memory page in bytes (64 KiB).
6pub const WASM_PAGE_SIZE: u64 = 65536;
7
8/// Memory Provider trait defines the interface for interacting with the underlying memory.
9///
10/// Abstracting memory access allows different implementations for production
11/// (e.g. stable memory) and testing (heap-based).
12pub trait MemoryProvider {
13    /// The size of a memory page in bytes.
14    const PAGE_SIZE: u64;
15
16    /// Gets the current size of the memory in bytes.
17    fn size(&self) -> u64;
18
19    /// Gets the amount of pages currently allocated.
20    fn pages(&self) -> u64;
21
22    /// Attempts to grow the memory by `new_pages` (added pages).
23    ///
24    /// Returns an error if it wasn't possible. Otherwise, returns the previous size in bytes that was reserved.
25    ///
26    /// Actual reserved size after the growth will be `previous_size + (new_pages * PAGE_SIZE)`.
27    fn grow(&mut self, new_pages: u64) -> MemoryResult<u64>;
28
29    /// Reads data from memory starting at `offset` into the provided buffer `buf`.
30    ///
31    /// Returns an error if `offset + buf.len()` exceeds the current memory size.
32    fn read(&mut self, offset: u64, buf: &mut [u8]) -> MemoryResult<()>;
33
34    /// Writes data from the provided buffer `buf` into memory starting at `offset`.
35    ///
36    /// Returns an error if `offset + buf.len()` exceeds the current memory size.
37    fn write(&mut self, offset: u64, buf: &[u8]) -> MemoryResult<()>;
38}
39
40/// An implementation of [`MemoryProvider`] that uses heap memory for testing purposes.
41#[derive(Debug, Default)]
42pub struct HeapMemoryProvider {
43    memory: Vec<u8>,
44}
45
46impl HeapMemoryProvider {
47    /// Returns the in-bounds byte range `offset..offset + len`.
48    ///
49    /// # Errors
50    ///
51    /// Returns [`MemoryError::OutOfBounds`] if the range overflows or ends
52    /// past the current memory size.
53    fn checked_range(&self, offset: u64, len: usize) -> MemoryResult<std::ops::Range<usize>> {
54        let end = offset
55            .checked_add(len as u64)
56            .filter(|end| *end <= self.size())
57            .ok_or(MemoryError::OutOfBounds)?;
58        // `end` is bounded by `self.memory.len()`, so both fit in `usize`.
59        Ok(offset as usize..end as usize)
60    }
61}
62
63impl MemoryProvider for HeapMemoryProvider {
64    const PAGE_SIZE: u64 = WASM_PAGE_SIZE; // 64 KiB
65
66    fn grow(&mut self, new_pages: u64) -> MemoryResult<u64> {
67        let previous_size = self.size();
68        let new_size = new_pages
69            .checked_mul(Self::PAGE_SIZE)
70            .and_then(|additional| previous_size.checked_add(additional))
71            .and_then(|size| usize::try_from(size).ok())
72            .ok_or(MemoryError::FailedToAllocatePage)?;
73        self.memory
74            .try_reserve_exact(new_size - self.memory.len())
75            .map_err(|_| MemoryError::FailedToAllocatePage)?;
76        self.memory.resize(new_size, 0);
77        Ok(previous_size)
78    }
79
80    fn size(&self) -> u64 {
81        self.memory.len() as u64
82    }
83
84    fn pages(&self) -> u64 {
85        self.size() / Self::PAGE_SIZE
86    }
87
88    fn read(&mut self, offset: u64, buf: &mut [u8]) -> MemoryResult<()> {
89        let range = self.checked_range(offset, buf.len())?;
90        buf.copy_from_slice(&self.memory[range]);
91        Ok(())
92    }
93
94    fn write(&mut self, offset: u64, buf: &[u8]) -> MemoryResult<()> {
95        let range = self.checked_range(offset, buf.len())?;
96        self.memory[range].copy_from_slice(buf);
97        Ok(())
98    }
99}
100
101#[cfg(test)]
102mod tests {
103
104    use super::*;
105
106    #[test]
107    fn test_should_grow_heap_memory() {
108        let mut provider = HeapMemoryProvider::default();
109        assert_eq!(provider.size(), 0);
110
111        let previous_size = provider.grow(2).unwrap();
112        assert_eq!(previous_size, 0);
113        assert_eq!(provider.size(), 2 * HeapMemoryProvider::PAGE_SIZE);
114
115        let previous_size = provider.grow(1).unwrap();
116        assert_eq!(previous_size, 2 * HeapMemoryProvider::PAGE_SIZE);
117        assert_eq!(provider.size(), 3 * HeapMemoryProvider::PAGE_SIZE);
118    }
119
120    #[test]
121    fn test_should_read_and_write_heap_memory() {
122        let mut provider = HeapMemoryProvider::default();
123        provider.grow(1).unwrap(); // grow by 1 page (64 KiB)
124        let data_to_write = vec![1, 2, 3, 4, 5];
125        provider.write(0, &data_to_write).unwrap();
126        let mut buffer = vec![0; 5];
127        provider.read(0, &mut buffer).unwrap();
128        assert_eq!(buffer, data_to_write);
129    }
130
131    #[test]
132    fn test_should_not_read_out_of_bounds_heap_memory() {
133        let mut provider = HeapMemoryProvider::default();
134        provider.grow(1).unwrap(); // grow by 1 page (64 KiB)
135        let mut buffer = vec![0; 10];
136        let result = provider.read(HeapMemoryProvider::PAGE_SIZE - 5, &mut buffer);
137        assert!(result.is_err());
138        assert!(matches!(result.err().unwrap(), MemoryError::OutOfBounds));
139    }
140
141    #[test]
142    fn test_should_not_write_out_of_bounds_heap_memory() {
143        let mut provider = HeapMemoryProvider::default();
144        provider.grow(1).unwrap(); // grow by 1 page (64 KiB)
145        let data_to_write = vec![1, 2, 3, 4, 5];
146        let result = provider.write(HeapMemoryProvider::PAGE_SIZE - 3, &data_to_write);
147        assert!(result.is_err());
148        assert!(matches!(result.err().unwrap(), MemoryError::OutOfBounds));
149    }
150
151    #[test]
152    fn test_should_reject_extreme_offsets_heap_memory() {
153        let mut provider = HeapMemoryProvider::default();
154        provider.grow(1).unwrap();
155
156        let mut buf = [0u8; 1];
157        assert!(matches!(
158            provider.read(u64::MAX, &mut buf),
159            Err(MemoryError::OutOfBounds)
160        ));
161        assert!(matches!(
162            provider.write(u64::MAX, &[1]),
163            Err(MemoryError::OutOfBounds)
164        ));
165    }
166
167    #[test]
168    fn test_should_reject_overflowing_growth_heap_memory() {
169        let mut provider = HeapMemoryProvider::default();
170        assert!(provider.grow(u64::MAX).is_err());
171        assert_eq!(provider.size(), 0);
172    }
173
174    #[test]
175    fn test_grow_returns_error_when_allocation_fails() {
176        let mut provider = HeapMemoryProvider::default();
177        provider.grow(1).unwrap();
178
179        // 2^40 pages = 2^56 bytes: representable, but never allocatable.
180        assert!(provider.grow(1 << 40).is_err());
181        assert_eq!(provider.size(), HeapMemoryProvider::PAGE_SIZE);
182    }
183
184    #[test]
185    fn test_should_get_amount_of_pages_heap_memory() {
186        let mut provider = HeapMemoryProvider::default();
187        assert_eq!(provider.pages(), 0);
188
189        provider.grow(3).unwrap(); // grow by 3 pages
190        assert_eq!(provider.pages(), 3);
191
192        provider.grow(2).unwrap(); // grow by 2 more pages
193        assert_eq!(provider.pages(), 5);
194    }
195}