wasm_dbms_memory/
unclaimed_pages.rs1use std::borrow::Cow;
13
14use wasm_dbms_api::prelude::{
15 DEFAULT_ALIGNMENT, DataSize, DecodeError, Encode, MSize, MemoryError, MemoryResult, Page,
16 PageOffset,
17};
18
19const HEADER_SIZE: u64 = 4;
21const ENTRY_SIZE: u64 = 4;
23
24pub const UNCLAIMED_PAGES_CAPACITY: u32 = {
29 let max_bytes = MSize::MAX as u64;
30 let entries = (max_bytes - HEADER_SIZE) / ENTRY_SIZE;
31 entries as u32
32};
33
34#[derive(Debug, Clone, Default, PartialEq, Eq)]
39pub struct UnclaimedPages {
40 pages: Vec<Page>,
41}
42
43impl UnclaimedPages {
44 pub fn new() -> Self {
46 Self::default()
47 }
48
49 pub fn len(&self) -> usize {
51 self.pages.len()
52 }
53
54 pub fn remaining_capacity(&self) -> u32 {
56 UNCLAIMED_PAGES_CAPACITY - (self.pages.len() as u32)
57 }
58
59 pub fn is_empty(&self) -> bool {
61 self.pages.is_empty()
62 }
63
64 pub fn pop(&mut self) -> Option<Page> {
66 self.pages.pop()
67 }
68
69 pub fn push(&mut self, page: Page) -> MemoryResult<()> {
75 if self.pages.len() as u32 >= UNCLAIMED_PAGES_CAPACITY {
76 return Err(MemoryError::UnclaimedPagesFull {
77 capacity: UNCLAIMED_PAGES_CAPACITY,
78 });
79 }
80 self.pages.push(page);
81 Ok(())
82 }
83
84 pub fn as_slice(&self) -> &[Page] {
86 &self.pages
87 }
88}
89
90impl Encode for UnclaimedPages {
91 const SIZE: DataSize = DataSize::Dynamic;
92 const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
93
94 fn encode(&'_ self) -> Cow<'_, [u8]> {
95 let mut buf = Vec::with_capacity(self.size() as usize);
96 buf.extend_from_slice(&(self.pages.len() as u32).to_le_bytes());
97 for &page in &self.pages {
98 buf.extend_from_slice(&page.to_le_bytes());
99 }
100 Cow::Owned(buf)
101 }
102
103 fn decode(data: Cow<[u8]>) -> MemoryResult<Self>
104 where
105 Self: Sized,
106 {
107 if data.len() < HEADER_SIZE as usize {
108 return Err(MemoryError::DecodeError(DecodeError::TooShort));
109 }
110 let count = u32::from_le_bytes(data[0..4].try_into()?) as usize;
111 if count > UNCLAIMED_PAGES_CAPACITY as usize {
112 return Err(MemoryError::UnclaimedPagesFull {
113 capacity: UNCLAIMED_PAGES_CAPACITY,
114 });
115 }
116 let required = HEADER_SIZE as usize + count * ENTRY_SIZE as usize;
118 if data.len() < required {
119 return Err(MemoryError::DecodeError(DecodeError::TooShort));
120 }
121 let pages = data[HEADER_SIZE as usize..required]
122 .as_chunks::<{ ENTRY_SIZE as usize }>()
123 .0
124 .iter()
125 .map(|entry| Page::from_le_bytes(*entry))
126 .collect();
127 Ok(Self { pages })
128 }
129
130 fn size(&self) -> MSize {
131 (HEADER_SIZE as MSize) + (self.pages.len() as MSize) * (ENTRY_SIZE as MSize)
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use wasm_dbms_api::prelude::DecodeError;
138
139 use super::*;
140
141 #[test]
142 fn test_should_be_empty_by_default() {
143 let ledger = UnclaimedPages::new();
144 assert!(ledger.is_empty());
145 assert_eq!(ledger.len(), 0);
146 }
147
148 #[test]
149 fn test_should_push_and_pop() {
150 let mut ledger = UnclaimedPages::new();
151 ledger.push(10).expect("push");
152 ledger.push(20).expect("push");
153 ledger.push(30).expect("push");
154
155 assert_eq!(ledger.len(), 3);
156 assert_eq!(ledger.pop(), Some(30));
157 assert_eq!(ledger.pop(), Some(20));
158 assert_eq!(ledger.pop(), Some(10));
159 assert_eq!(ledger.pop(), None);
160 }
161
162 #[test]
163 fn test_should_round_trip_encode_decode() {
164 let mut ledger = UnclaimedPages::new();
165 for page in [3u32, 5, 7, 11] {
166 ledger.push(page).expect("push");
167 }
168
169 let encoded = ledger.encode();
170 let decoded = UnclaimedPages::decode(encoded).expect("decode");
171 assert_eq!(ledger, decoded);
172 }
173
174 #[test]
175 fn test_should_decode_empty_buffer_as_empty_ledger() {
176 let buf = vec![0u8; 65536];
177 let ledger = UnclaimedPages::decode(Cow::Owned(buf)).expect("decode");
178 assert!(ledger.is_empty());
179 }
180
181 #[test]
182 fn test_should_reject_truncated_buffers() {
183 let short_header = UnclaimedPages::decode(Cow::Borrowed(&[1u8, 0][..]));
185 assert!(matches!(
186 short_header,
187 Err(MemoryError::DecodeError(DecodeError::TooShort))
188 ));
189
190 let missing_entry = UnclaimedPages::decode(Cow::Borrowed(&[1u8, 0, 0, 0][..]));
192 assert!(matches!(
193 missing_entry,
194 Err(MemoryError::DecodeError(DecodeError::TooShort))
195 ));
196 }
197
198 #[test]
199 fn test_should_reject_push_when_full() {
200 let mut ledger = UnclaimedPages {
201 pages: vec![0; UNCLAIMED_PAGES_CAPACITY as usize],
202 };
203 let err = ledger.push(42).expect_err("push at capacity");
204 assert!(matches!(err, MemoryError::UnclaimedPagesFull { .. }));
205 }
206
207 #[test]
208 fn test_size_matches_encoded_length() {
209 let mut ledger = UnclaimedPages::new();
210 ledger.push(1).expect("push");
211 ledger.push(2).expect("push");
212 let encoded = ledger.encode();
213 assert_eq!(ledger.size() as usize, encoded.len());
214 }
215}