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    /// Publishes pending changes to the provider's durable storage.
40    ///
41    /// Providers without an explicit persistence boundary use the default
42    /// no-op implementation. This hook does not flush active transaction
43    /// overlays held by the DBMS context.
44    fn flush(&mut self) -> MemoryResult<()> {
45        Ok(())
46    }
47}
48
49/// An implementation of [`MemoryProvider`] that uses heap memory for testing purposes.
50#[derive(Debug, Default)]
51pub struct HeapMemoryProvider {
52    memory: Vec<u8>,
53}
54
55impl HeapMemoryProvider {
56    /// Returns the in-bounds byte range `offset..offset + len`.
57    ///
58    /// # Errors
59    ///
60    /// Returns [`MemoryError::OutOfBounds`] if the range overflows or ends
61    /// past the current memory size.
62    fn checked_range(&self, offset: u64, len: usize) -> MemoryResult<std::ops::Range<usize>> {
63        let end = offset
64            .checked_add(len as u64)
65            .filter(|end| *end <= self.size())
66            .ok_or(MemoryError::OutOfBounds)?;
67        // `end` is bounded by `self.memory.len()`, so both fit in `usize`.
68        Ok(offset as usize..end as usize)
69    }
70}
71
72impl MemoryProvider for HeapMemoryProvider {
73    const PAGE_SIZE: u64 = WASM_PAGE_SIZE; // 64 KiB
74
75    fn grow(&mut self, new_pages: u64) -> MemoryResult<u64> {
76        let previous_size = self.size();
77        let new_size = new_pages
78            .checked_mul(Self::PAGE_SIZE)
79            .and_then(|additional| previous_size.checked_add(additional))
80            .and_then(|size| usize::try_from(size).ok())
81            .ok_or(MemoryError::FailedToAllocatePage)?;
82        self.memory
83            .try_reserve_exact(new_size - self.memory.len())
84            .map_err(|_| MemoryError::FailedToAllocatePage)?;
85        self.memory.resize(new_size, 0);
86        Ok(previous_size)
87    }
88
89    fn size(&self) -> u64 {
90        self.memory.len() as u64
91    }
92
93    fn pages(&self) -> u64 {
94        self.size() / Self::PAGE_SIZE
95    }
96
97    fn read(&mut self, offset: u64, buf: &mut [u8]) -> MemoryResult<()> {
98        let range = self.checked_range(offset, buf.len())?;
99        buf.copy_from_slice(&self.memory[range]);
100        Ok(())
101    }
102
103    fn write(&mut self, offset: u64, buf: &[u8]) -> MemoryResult<()> {
104        let range = self.checked_range(offset, buf.len())?;
105        self.memory[range].copy_from_slice(buf);
106        Ok(())
107    }
108}
109
110#[cfg(test)]
111mod tests {
112
113    use super::*;
114
115    #[test]
116    fn test_should_grow_heap_memory() {
117        let mut provider = HeapMemoryProvider::default();
118        assert_eq!(provider.size(), 0);
119
120        let previous_size = provider.grow(2).unwrap();
121        assert_eq!(previous_size, 0);
122        assert_eq!(provider.size(), 2 * HeapMemoryProvider::PAGE_SIZE);
123
124        let previous_size = provider.grow(1).unwrap();
125        assert_eq!(previous_size, 2 * HeapMemoryProvider::PAGE_SIZE);
126        assert_eq!(provider.size(), 3 * HeapMemoryProvider::PAGE_SIZE);
127    }
128
129    #[test]
130    fn test_should_read_and_write_heap_memory() {
131        let mut provider = HeapMemoryProvider::default();
132        provider.grow(1).unwrap(); // grow by 1 page (64 KiB)
133        let data_to_write = vec![1, 2, 3, 4, 5];
134        provider.write(0, &data_to_write).unwrap();
135        let mut buffer = vec![0; 5];
136        provider.read(0, &mut buffer).unwrap();
137        assert_eq!(buffer, data_to_write);
138    }
139
140    #[test]
141    fn test_should_not_read_out_of_bounds_heap_memory() {
142        let mut provider = HeapMemoryProvider::default();
143        provider.grow(1).unwrap(); // grow by 1 page (64 KiB)
144        let mut buffer = vec![0; 10];
145        let result = provider.read(HeapMemoryProvider::PAGE_SIZE - 5, &mut buffer);
146        assert!(result.is_err());
147        assert!(matches!(result.err().unwrap(), MemoryError::OutOfBounds));
148    }
149
150    #[test]
151    fn test_should_not_write_out_of_bounds_heap_memory() {
152        let mut provider = HeapMemoryProvider::default();
153        provider.grow(1).unwrap(); // grow by 1 page (64 KiB)
154        let data_to_write = vec![1, 2, 3, 4, 5];
155        let result = provider.write(HeapMemoryProvider::PAGE_SIZE - 3, &data_to_write);
156        assert!(result.is_err());
157        assert!(matches!(result.err().unwrap(), MemoryError::OutOfBounds));
158    }
159
160    #[test]
161    fn test_should_reject_extreme_offsets_heap_memory() {
162        let mut provider = HeapMemoryProvider::default();
163        provider.grow(1).unwrap();
164
165        let mut buf = [0u8; 1];
166        assert!(matches!(
167            provider.read(u64::MAX, &mut buf),
168            Err(MemoryError::OutOfBounds)
169        ));
170        assert!(matches!(
171            provider.write(u64::MAX, &[1]),
172            Err(MemoryError::OutOfBounds)
173        ));
174    }
175
176    #[test]
177    fn test_should_reject_overflowing_growth_heap_memory() {
178        let mut provider = HeapMemoryProvider::default();
179        assert!(provider.grow(u64::MAX).is_err());
180        assert_eq!(provider.size(), 0);
181    }
182
183    #[test]
184    fn test_grow_returns_error_when_allocation_fails() {
185        let mut provider = HeapMemoryProvider::default();
186        provider.grow(1).unwrap();
187
188        // 2^40 pages = 2^56 bytes: representable, but never allocatable.
189        assert!(provider.grow(1 << 40).is_err());
190        assert_eq!(provider.size(), HeapMemoryProvider::PAGE_SIZE);
191    }
192
193    #[test]
194    fn test_should_get_amount_of_pages_heap_memory() {
195        let mut provider = HeapMemoryProvider::default();
196        assert_eq!(provider.pages(), 0);
197
198        provider.grow(3).unwrap(); // grow by 3 pages
199        assert_eq!(provider.pages(), 3);
200
201        provider.grow(2).unwrap(); // grow by 2 more pages
202        assert_eq!(provider.pages(), 5);
203    }
204}