1use std::collections::{BTreeMap, BTreeSet};
29use std::io::Write;
30use std::path::Path;
31
32use mentedb_core::MemoryNode;
33use mentedb_core::error::{MenteError, MenteResult};
34use mentedb_core::types::MemoryId;
35
36use crate::page::{PAGE_DATA_SIZE, PageId};
37use crate::wal::{WalEntry, WalEntryType};
38
39pub const MEMORY_MAP_FILE: &str = "memory_map.bin";
41
42const MAGIC: &[u8; 8] = b"MDBMMAP1";
43const FORMAT_VERSION: u32 = 1;
44
45pub(crate) type Token = [u8; 16];
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub struct MappedMemory {
51 pub id: MemoryId,
52 pub page: PageId,
53 pub stamp: u64,
56}
57
58pub(crate) fn decode_memory(data: &[u8]) -> MenteResult<MemoryNode> {
62 let data = &data[..data.len().min(PAGE_DATA_SIZE)];
63 let mut prefix = [0u8; 4];
64 let n = data.len().min(4);
65 prefix[..n].copy_from_slice(&data[..n]);
66 let len = u32::from_le_bytes(prefix) as usize;
67 if len == 0 || len + 4 > PAGE_DATA_SIZE {
68 return Err(MenteError::Storage(format!(
69 "invalid memory node length prefix: {len}"
70 )));
71 }
72 if 4 + len > data.len() {
73 return Err(MenteError::Serialization(
76 "memory node runs past the written data".into(),
77 ));
78 }
79 serde_json::from_slice(&data[4..4 + len]).map_err(|e| MenteError::Serialization(e.to_string()))
80}
81
82fn random_u64() -> u64 {
84 let (a, b) = uuid::Uuid::new_v4().as_u64_pair();
85 a ^ b.rotate_left(17)
86}
87
88pub(crate) fn new_token() -> Token {
89 *uuid::Uuid::new_v4().as_bytes()
90}
91
92pub(crate) fn entry_stamp(token: &Token, lsn: u64) -> u64 {
98 fn mix(mut z: u64) -> u64 {
99 z = z.wrapping_add(0x9E37_79B9_7F4A_7C15);
100 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
101 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
102 z ^ (z >> 31)
103 }
104 let mut a = [0u8; 8];
105 let mut b = [0u8; 8];
106 a.copy_from_slice(&token[..8]);
107 b.copy_from_slice(&token[8..]);
108 mix(u64::from_le_bytes(a) ^ mix(u64::from_le_bytes(b) ^ mix(lsn)))
109}
110
111#[derive(Debug, Clone, Default, PartialEq, Eq)]
113pub(crate) struct MemoryTable {
114 pages: BTreeMap<u64, (MemoryId, u64)>,
116 free: BTreeSet<u64>,
118}
119
120impl MemoryTable {
121 pub(crate) fn set_page(&mut self, page: u64, id: Option<MemoryId>, stamp: u64) {
124 self.free.remove(&page);
125 match id {
126 Some(id) => {
127 self.pages.insert(page, (id, stamp));
128 }
129 None => {
130 self.pages.remove(&page);
131 }
132 }
133 }
134
135 pub(crate) fn set_free(&mut self, page: u64) {
137 self.pages.remove(&page);
138 self.free.insert(page);
139 }
140
141 pub(crate) fn set_scanned(&mut self, page: u64, id: MemoryId) {
144 self.set_page(page, Some(id), random_u64());
145 }
146
147 pub(crate) fn apply(&mut self, entry: &WalEntry, token: &Token) -> bool {
150 match entry.entry_type {
151 WalEntryType::PageWrite => {
152 let id = decode_memory(&entry.data).ok().map(|n| n.id);
153 self.set_page(entry.page_id, id, entry_stamp(token, entry.lsn));
154 true
155 }
156 WalEntryType::PageFree => {
157 self.set_free(entry.page_id);
158 true
159 }
160 WalEntryType::Checkpoint | WalEntryType::Commit => false,
161 }
162 }
163
164 pub(crate) fn free_pages(&self) -> &BTreeSet<u64> {
166 &self.free
167 }
168
169 fn max_page(&self) -> Option<u64> {
171 let a = self.pages.keys().next_back().copied();
172 let b = self.free.iter().next_back().copied();
173 a.max(b)
174 }
175
176 pub(crate) fn memories(&self) -> Vec<MappedMemory> {
178 self.pages
179 .iter()
180 .map(|(&page, &(id, stamp))| MappedMemory {
181 id,
182 page: PageId(page),
183 stamp,
184 })
185 .collect()
186 }
187
188 pub(crate) fn get(&self, page: u64) -> Option<(MemoryId, u64)> {
190 self.pages.get(&page).copied()
191 }
192}
193
194#[derive(Debug, Clone)]
196pub(crate) struct MapAnchor {
197 pub(crate) token: Token,
198 pub(crate) page_count: u64,
201 pub(crate) table: MemoryTable,
202 pub(crate) pending: std::collections::HashMap<u64, Option<(MemoryId, u64)>>,
208}
209
210impl MapAnchor {
211 fn position(&self, entries: &[WalEntry]) -> Option<usize> {
213 entries
214 .iter()
215 .rposition(|e| e.entry_type == WalEntryType::Checkpoint && e.data == self.token)
216 }
217
218 pub(crate) fn advance(&mut self, entries: &[WalEntry]) -> Option<bool> {
222 let pos = self.position(entries)?;
223 let mut changed = false;
224 for e in &entries[pos + 1..] {
225 changed |= self.table.apply(e, &self.token);
226 }
227 self.pending.clear();
228 Some(changed)
229 }
230
231 pub(crate) fn note_write(&mut self, page: u64, id: Option<MemoryId>, lsn: u64) {
233 let stamp = entry_stamp(&self.token, lsn);
234 self.pending.insert(page, id.map(|i| (i, stamp)));
235 }
236
237 pub(crate) fn current(&self, page: u64) -> Option<(MemoryId, u64)> {
239 match self.pending.get(&page) {
240 Some(v) => *v,
241 None => self.table.get(page),
242 }
243 }
244
245 pub(crate) fn fits(&self, page_count: u64) -> bool {
248 self.page_count <= page_count
249 && self.table.max_page().is_none_or(|m| m < self.page_count)
250 && !self.table.pages.contains_key(&0)
251 && !self.table.free.contains(&0)
252 }
253}
254
255fn put_u64(buf: &mut Vec<u8>, v: u64) {
256 buf.extend_from_slice(&v.to_le_bytes());
257}
258
259fn encode(anchor: &MapAnchor) -> Vec<u8> {
260 let t = &anchor.table;
261 let mut buf = Vec::with_capacity(64 + t.pages.len() * 32 + t.free.len() * 8);
262 buf.extend_from_slice(MAGIC);
263 buf.extend_from_slice(&FORMAT_VERSION.to_le_bytes());
264 buf.extend_from_slice(&anchor.token);
265 put_u64(&mut buf, anchor.page_count);
266 put_u64(&mut buf, t.pages.len() as u64);
267 for (&page, &(id, stamp)) in &t.pages {
268 put_u64(&mut buf, page);
269 buf.extend_from_slice(id.0.as_bytes());
270 put_u64(&mut buf, stamp);
271 }
272 put_u64(&mut buf, t.free.len() as u64);
273 for &page in &t.free {
274 put_u64(&mut buf, page);
275 }
276 let crc = crc32fast::hash(&buf);
277 buf.extend_from_slice(&crc.to_le_bytes());
278 buf
279}
280
281struct Reader<'a> {
282 buf: &'a [u8],
283 pos: usize,
284}
285
286impl Reader<'_> {
287 fn take(&mut self, n: usize) -> MenteResult<&[u8]> {
288 let end = self
289 .pos
290 .checked_add(n)
291 .filter(|&e| e <= self.buf.len())
292 .ok_or_else(|| MenteError::Storage("memory map snapshot truncated".into()))?;
293 let s = &self.buf[self.pos..end];
294 self.pos = end;
295 Ok(s)
296 }
297
298 fn u64(&mut self) -> MenteResult<u64> {
299 let mut b = [0u8; 8];
300 b.copy_from_slice(self.take(8)?);
301 Ok(u64::from_le_bytes(b))
302 }
303}
304
305fn decode(bytes: &[u8]) -> MenteResult<MapAnchor> {
306 let bad = |what: &str| MenteError::Storage(format!("memory map snapshot {what}"));
307 if bytes.len() < MAGIC.len() + 4 + 16 + 8 + 8 + 8 + 4 {
308 return Err(bad("too short"));
309 }
310 let (body, crc_bytes) = bytes.split_at(bytes.len() - 4);
311 let mut crc = [0u8; 4];
312 crc.copy_from_slice(crc_bytes);
313 if crc32fast::hash(body) != u32::from_le_bytes(crc) {
314 return Err(bad("checksum mismatch"));
315 }
316 let mut r = Reader { buf: body, pos: 0 };
317 if r.take(8)? != MAGIC {
318 return Err(bad("magic mismatch"));
319 }
320 let mut v = [0u8; 4];
321 v.copy_from_slice(r.take(4)?);
322 if u32::from_le_bytes(v) != FORMAT_VERSION {
323 return Err(bad("version mismatch"));
324 }
325 let mut token = [0u8; 16];
326 token.copy_from_slice(r.take(16)?);
327 let page_count = r.u64()?;
328 let n_pages = r.u64()?;
329 let mut table = MemoryTable::default();
330 for _ in 0..n_pages {
331 let page = r.u64()?;
332 let id = uuid::Uuid::from_slice(r.take(16)?).map_err(|e| bad(&e.to_string()))?;
333 let stamp = r.u64()?;
334 table.pages.insert(page, (MemoryId(id), stamp));
335 }
336 let n_free = r.u64()?;
337 for _ in 0..n_free {
338 table.free.insert(r.u64()?);
339 }
340 if r.pos != body.len() {
341 return Err(bad("has trailing bytes"));
342 }
343 Ok(MapAnchor {
344 token,
345 page_count,
346 table,
347 pending: Default::default(),
348 })
349}
350
351pub(crate) fn read_snapshot(dir: &Path) -> MenteResult<Option<MapAnchor>> {
353 match std::fs::read(dir.join(MEMORY_MAP_FILE)) {
354 Ok(bytes) => decode(&bytes).map(Some),
355 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
356 Err(e) => Err(e.into()),
357 }
358}
359
360pub(crate) fn write_snapshot(dir: &Path, anchor: &MapAnchor) -> MenteResult<()> {
363 let path = dir.join(MEMORY_MAP_FILE);
364 let tmp = dir.join(format!("{MEMORY_MAP_FILE}.tmp"));
365 {
366 let mut file = std::fs::File::create(&tmp)?;
367 file.write_all(&encode(anchor))?;
368 file.sync_all()?;
369 }
370 std::fs::rename(&tmp, &path)?;
371 Ok(())
372}
373
374pub(crate) fn remove_snapshot(dir: &Path) {
376 let _ = std::fs::remove_file(dir.join(MEMORY_MAP_FILE));
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 fn anchor() -> MapAnchor {
384 let mut table = MemoryTable::default();
385 table.set_scanned(1, MemoryId::new());
386 table.set_scanned(3, MemoryId::new());
387 table.set_free(2);
388 MapAnchor {
389 token: new_token(),
390 page_count: 4,
391 table,
392 pending: Default::default(),
393 }
394 }
395
396 #[test]
397 fn snapshot_roundtrips() {
398 let dir = tempfile::tempdir().unwrap();
399 let a = anchor();
400 write_snapshot(dir.path(), &a).unwrap();
401 let b = read_snapshot(dir.path()).unwrap().unwrap();
402 assert_eq!(a.token, b.token);
403 assert_eq!(a.page_count, b.page_count);
404 assert_eq!(a.table, b.table);
405 assert!(!dir.path().join(format!("{MEMORY_MAP_FILE}.tmp")).exists());
406 }
407
408 #[test]
409 fn corrupt_snapshot_is_rejected() {
410 let dir = tempfile::tempdir().unwrap();
411 write_snapshot(dir.path(), &anchor()).unwrap();
412 let path = dir.path().join(MEMORY_MAP_FILE);
413 let mut bytes = std::fs::read(&path).unwrap();
414 let mid = bytes.len() / 2;
415 bytes[mid] ^= 0xff;
416 std::fs::write(&path, &bytes).unwrap();
417 assert!(read_snapshot(dir.path()).is_err());
418 std::fs::write(&path, &bytes[..10]).unwrap();
419 assert!(read_snapshot(dir.path()).is_err());
420 std::fs::remove_file(&path).unwrap();
421 assert!(read_snapshot(dir.path()).unwrap().is_none());
422 }
423
424 #[test]
425 fn fits_rejects_pages_past_the_count() {
426 let a = anchor();
427 assert!(a.fits(4));
428 assert!(a.fits(10));
429 assert!(!a.fits(3), "page file smaller than the snapshot");
430 let mut b = anchor();
431 b.page_count = 3;
432 assert!(!b.fits(10), "page 3 recorded beyond its own page count");
433 }
434}