Skip to main content

wasm_dbms_memory/
memory_manager.rs

1// Rust guideline compliant 2026-04-27
2// X-WHERE-CLAUSE, M-CANONICAL-DOCS
3
4//! Memory manager for page-level memory operations.
5
6use wasm_dbms_api::prelude::{
7    DataSize, Encode, MSize, MemoryError, MemoryResult, Page, PageOffset,
8};
9
10use crate::memory_access::MemoryAccess;
11use crate::provider::MemoryProvider;
12
13/// Schema page (reserved page 0).
14pub const SCHEMA_PAGE: Page = 0;
15/// Unclaimed-pages ledger page (reserved page 1).
16pub const UNCLAIMED_PAGES_PAGE: Page = 1;
17/// Number of reserved pages allocated at initialization.
18pub const RESERVED_PAGES: u64 = 2;
19
20/// The memory manager handles page-level memory operations on top of a
21/// [`MemoryProvider`].
22pub struct MemoryManager<P>
23where
24    P: MemoryProvider,
25{
26    provider: P,
27}
28
29impl<P> MemoryManager<P>
30where
31    P: MemoryProvider,
32{
33    /// Initializes the memory manager and allocates the header and reserved
34    /// pages.
35    ///
36    /// # Panics
37    ///
38    /// Panics if the memory provider fails to initialize.
39    pub fn init(provider: P) -> Self {
40        let mut manager = MemoryManager { provider };
41
42        // Check whether the reserved pages are already allocated.
43        let current_pages = manager.provider.pages();
44        if current_pages >= RESERVED_PAGES {
45            return manager;
46        }
47
48        // Request the missing reserved pages (schema, unclaimed).
49        let missing = RESERVED_PAGES - current_pages;
50        if let Err(err) = manager.provider.grow(missing) {
51            panic!("Failed to grow memory during initialization: {err}");
52        }
53
54        manager
55    }
56
57    /// Returns the schema page.
58    pub const fn schema_page(&self) -> Page {
59        SCHEMA_PAGE
60    }
61
62    /// Returns the unclaimed-pages ledger page.
63    pub const fn unclaimed_pages_page(&self) -> Page {
64        UNCLAIMED_PAGES_PAGE
65    }
66
67    /// Consumes the manager and returns the underlying provider.
68    ///
69    /// Test-only helper that enables reload simulations without going
70    /// through the full DBMS context.
71    #[cfg(test)]
72    pub(crate) fn into_provider(self) -> P {
73        self.provider
74    }
75
76    /// Gets the last allocated page number.
77    pub fn last_page(&self) -> Option<Page> {
78        match self.provider.pages() {
79            0 => None,
80            n => Some(n as Page - 1),
81        }
82    }
83
84    /// Returns the total number of pages currently backed by the provider.
85    pub fn pages_count(&self) -> u64 {
86        self.provider.pages()
87    }
88
89    /// Calculates the absolute offset in memory given a page number and an
90    /// offset within that page.
91    fn absolute_offset(&self, page: Page, offset: PageOffset) -> u64 {
92        (page as u64)
93            .checked_mul(P::PAGE_SIZE)
94            .and_then(|page_offset| page_offset.checked_add(offset as u64))
95            .expect("Overflow when calculating absolute offset")
96    }
97
98    /// Checks if the specified page is allocated.
99    fn check_unallocated_page(
100        &self,
101        page: Page,
102        offset: PageOffset,
103        data_size: MSize,
104    ) -> MemoryResult<()> {
105        if self.last_page().is_none_or(|last_page| page > last_page) {
106            return Err(MemoryError::SegmentationFault {
107                page,
108                offset,
109                data_size: data_size as u64,
110                page_size: P::PAGE_SIZE,
111            });
112        }
113        Ok(())
114    }
115
116    /// Checks if the given offset is aligned according to the alignment
117    /// requirement of type `E`.
118    fn check_alignment<E>(&self, offset: PageOffset) -> MemoryResult<()>
119    where
120        E: Encode,
121    {
122        let alignment = E::ALIGNMENT as PageOffset;
123        if alignment != 0 && !offset.is_multiple_of(alignment) {
124            return Err(MemoryError::OffsetNotAligned { offset, alignment });
125        }
126        Ok(())
127    }
128}
129
130impl<P> MemoryAccess for MemoryManager<P>
131where
132    P: MemoryProvider,
133{
134    fn page_size(&self) -> u64 {
135        P::PAGE_SIZE
136    }
137
138    fn grow_one_page(&mut self) -> MemoryResult<Page> {
139        self.provider.grow(1)?;
140
141        // Zero the newly allocated page.
142        self.provider.write(
143            self.absolute_offset(self.last_page().unwrap_or(0), 0),
144            &vec![0u8; P::PAGE_SIZE as usize],
145        )?;
146
147        match self.last_page() {
148            Some(page) => Ok(page),
149            None => Err(MemoryError::FailedToAllocatePage),
150        }
151    }
152
153    fn zero_page(&mut self, page: Page) -> MemoryResult<()> {
154        if self.last_page().is_none_or(|last_page| page > last_page) {
155            return Err(MemoryError::SegmentationFault {
156                page,
157                offset: 0,
158                data_size: P::PAGE_SIZE,
159                page_size: P::PAGE_SIZE,
160            });
161        }
162
163        let absolute_offset = self.absolute_offset(page, 0);
164        let buffer = vec![0u8; P::PAGE_SIZE as usize];
165        self.provider.write(absolute_offset, &buffer)
166    }
167
168    fn read_at<D>(&mut self, page: Page, offset: PageOffset) -> MemoryResult<D>
169    where
170        D: Encode,
171    {
172        self.check_alignment::<D>(offset)?;
173
174        let mut buf = vec![
175            0u8;
176            match D::SIZE {
177                DataSize::Fixed(size) => size as usize,
178                DataSize::Dynamic => (P::PAGE_SIZE as usize).saturating_sub(offset as usize),
179            }
180        ];
181
182        let read = self.read_at_raw(page, offset, &mut buf)?;
183        // A short read means the value crosses the page end; decoding the
184        // zero-initialized tail would fabricate data.
185        if read < buf.len() {
186            return Err(MemoryError::SegmentationFault {
187                page,
188                offset,
189                data_size: buf.len() as u64,
190                page_size: P::PAGE_SIZE,
191            });
192        }
193
194        D::decode(std::borrow::Cow::Owned(buf))
195    }
196
197    fn write_at<E>(&mut self, page: Page, offset: PageOffset, data: &E) -> MemoryResult<()>
198    where
199        E: Encode,
200    {
201        self.check_unallocated_page(page, offset, data.size())?;
202        self.check_alignment::<E>(offset)?;
203
204        let encoded = data.encode();
205        // The padded footprint, not just the encoded bytes, must fit in the
206        // page: padding is written too and would otherwise spill into the
207        // next page.
208        let padded_len = align_up::<E>(encoded.len());
209
210        if offset as u64 + padded_len as u64 > P::PAGE_SIZE {
211            return Err(MemoryError::SegmentationFault {
212                page,
213                offset,
214                data_size: padded_len as u64,
215                page_size: P::PAGE_SIZE,
216            });
217        }
218
219        let absolute_offset = self.absolute_offset(page, offset);
220        self.provider.write(absolute_offset, encoded.as_ref())?;
221
222        // Zero padding bytes if any.
223        let padding = padded_len - encoded.len();
224        if padding > 0 {
225            let padding_offset = absolute_offset + encoded.len() as u64;
226            let padding_buffer = vec![0u8; padding];
227            self.provider
228                .write(padding_offset, padding_buffer.as_ref())?;
229        }
230
231        Ok(())
232    }
233
234    fn write_at_raw(&mut self, page: Page, offset: PageOffset, buf: &[u8]) -> MemoryResult<()> {
235        self.check_unallocated_page(page, offset, buf.len() as MSize)?;
236
237        if offset as u64 + buf.len() as u64 > P::PAGE_SIZE {
238            return Err(MemoryError::SegmentationFault {
239                page,
240                offset,
241                data_size: buf.len() as u64,
242                page_size: P::PAGE_SIZE,
243            });
244        }
245
246        let absolute_offset = self.absolute_offset(page, offset);
247        self.provider.write(absolute_offset, buf)
248    }
249
250    fn zero<E>(&mut self, page: Page, offset: PageOffset, data: &E) -> MemoryResult<()>
251    where
252        E: Encode,
253    {
254        self.check_unallocated_page(page, offset, data.size())?;
255        self.check_alignment::<E>(offset)?;
256
257        let length = align_up::<E>(data.size() as usize);
258
259        if offset as u64 + (length as u64) > P::PAGE_SIZE {
260            return Err(MemoryError::SegmentationFault {
261                page,
262                offset,
263                data_size: data.size() as u64,
264                page_size: P::PAGE_SIZE,
265            });
266        }
267
268        let absolute_offset = self.absolute_offset(page, offset);
269        let buffer = vec![0u8; length];
270        self.provider.write(absolute_offset, buffer.as_ref())
271    }
272
273    fn zero_raw(&mut self, page: Page, offset: PageOffset, len: PageOffset) -> MemoryResult<()> {
274        if self.last_page().is_none_or(|last_page| page > last_page) {
275            return Err(MemoryError::SegmentationFault {
276                page,
277                offset,
278                data_size: len as u64,
279                page_size: P::PAGE_SIZE,
280            });
281        }
282
283        if offset as u64 + len as u64 > P::PAGE_SIZE {
284            return Err(MemoryError::SegmentationFault {
285                page,
286                offset,
287                data_size: len as u64,
288                page_size: P::PAGE_SIZE,
289            });
290        }
291
292        let absolute_offset = self.absolute_offset(page, offset);
293        let buffer = vec![0u8; len as usize];
294        self.provider.write(absolute_offset, buffer.as_ref())
295    }
296
297    fn read_at_raw(
298        &mut self,
299        page: Page,
300        offset: PageOffset,
301        buf: &mut [u8],
302    ) -> MemoryResult<usize> {
303        if self.last_page().is_none_or(|last_page| page > last_page) {
304            return Err(MemoryError::SegmentationFault {
305                page,
306                offset,
307                data_size: buf.len() as u64,
308                page_size: P::PAGE_SIZE,
309            });
310        }
311
312        let read_len = ((P::PAGE_SIZE - offset as u64) as usize).min(buf.len());
313
314        let absolute_offset = self.absolute_offset(page, offset);
315        self.provider
316            .read(absolute_offset, buf[..read_len].as_mut())?;
317
318        Ok(read_len)
319    }
320}
321
322/// Gets the padding at the given offset to the next multiple of
323/// `E::ALIGNMENT`.
324#[inline]
325pub const fn align_up<E>(offset: usize) -> usize
326where
327    E: Encode,
328{
329    let alignment = E::ALIGNMENT as usize;
330    offset.div_ceil(alignment) * alignment
331}
332
333#[cfg(test)]
334mod tests {
335    use std::borrow::Cow;
336
337    use wasm_dbms_api::prelude::{
338        DEFAULT_ALIGNMENT, DataSize, MSize, MemoryError, MemoryResult, PageOffset, Text,
339    };
340
341    use super::*;
342    use crate::provider::HeapMemoryProvider;
343
344    /// Reserved page used as a scratch target by the raw read/write tests.
345    const SCRATCH_PAGE: Page = SCHEMA_PAGE;
346
347    fn make_mm() -> MemoryManager<HeapMemoryProvider> {
348        MemoryManager::init(HeapMemoryProvider::default())
349    }
350
351    #[test]
352    fn test_fixed_read_rejects_a_value_truncated_at_the_page_end() {
353        let mut mm = make_mm();
354        let page = mm.claim_page().expect("claim");
355        mm.write_at_raw(page, 65_532, &[1, 2, 3, 4])
356            .expect("write tail");
357
358        // 65_532 is 6-aligned, but only 4 of the 6 bytes are on the page.
359        let value: MemoryResult<crate::RecordAddress> = mm.read_at(page, 65_532);
360        assert!(matches!(value, Err(MemoryError::SegmentationFault { .. })));
361    }
362
363    #[test]
364    fn test_should_init_memory_manager() {
365        let mm = make_mm();
366        assert_eq!(mm.last_page(), Some(1));
367    }
368
369    #[test]
370    fn test_should_get_last_page() {
371        let mm = make_mm();
372        let last_page = mm.last_page();
373        assert_eq!(last_page, Some(1)); // schema and unclaimed-pages pages
374    }
375
376    #[test]
377    fn test_reserved_layout_is_schema_then_unclaimed_pages() {
378        let mm = make_mm();
379        assert_eq!(RESERVED_PAGES, 2);
380        assert_eq!(mm.schema_page(), 0);
381        assert_eq!(mm.unclaimed_pages_page(), 1);
382    }
383
384    #[test]
385    fn test_should_get_memory_page_size() {
386        let mm = make_mm();
387        let page_size = mm.page_size();
388        assert_eq!(page_size, HeapMemoryProvider::PAGE_SIZE);
389    }
390
391    #[test]
392    fn test_should_write_and_read_fixed_data_size() {
393        let mut mm = make_mm();
394        let data_to_write = FixedSizeData { a: 42, b: 1337 };
395        mm.write_at(SCRATCH_PAGE, 0, &data_to_write)
396            .expect("Failed to write data to scratch page");
397
398        let out: FixedSizeData = mm
399            .read_at(SCRATCH_PAGE, 0)
400            .expect("Failed to read data from scratch page");
401
402        assert_eq!(out, data_to_write);
403    }
404
405    #[test]
406    fn test_write_should_zero_padding() {
407        let mut mm = make_mm();
408        let data_to_write = Text("very_long_string".to_string());
409        mm.write_at(SCRATCH_PAGE, 0, &data_to_write)
410            .expect("Failed to write data to scratch page");
411
412        let mut buffer = vec![0; 32];
413        mm.read_at_raw(SCRATCH_PAGE, 0, &mut buffer)
414            .expect("Failed to read data from scratch page");
415
416        let non_zero_count = buffer.iter().filter(|&&b| b != 0).count();
417        assert_eq!(non_zero_count, data_to_write.size() as usize - 1);
418
419        let data_to_write_short = Text("short".to_string());
420        mm.write_at(SCRATCH_PAGE, 0, &data_to_write_short)
421            .expect("Failed to write data to scratch page");
422
423        let mut buffer = vec![0; 32];
424        mm.read_at_raw(SCRATCH_PAGE, 0, &mut buffer)
425            .expect("Failed to read data from scratch page");
426
427        let non_zero_count = buffer.iter().filter(|&&b| b != 0).count();
428        assert_eq!(non_zero_count, data_to_write_short.size() as usize - 1);
429    }
430
431    #[test]
432    fn test_should_zero_data() {
433        let mut mm = make_mm();
434        let data_to_write = FixedSizeData { a: 100, b: 200 };
435        mm.write_at(SCRATCH_PAGE, 48, &data_to_write)
436            .expect("Failed to write data to scratch page");
437
438        mm.zero(SCRATCH_PAGE, 48, &data_to_write)
439            .expect("Failed to zero data on scratch page");
440
441        let mut buffer = vec![0; 50];
442        mm.read_at_raw(SCRATCH_PAGE, 48, &mut buffer)
443            .expect("Failed to read data from scratch page");
444
445        assert!(buffer.iter().all(|&b| b == 0));
446    }
447
448    #[test]
449    fn test_should_zero_with_alignment() {
450        let mut mm = make_mm();
451        let data_to_write = FixedSizeData { a: 100, b: 200 };
452        mm.write_at(SCRATCH_PAGE, 0, &data_to_write)
453            .expect("Failed to write data to scratch page");
454        let data_to_write = FixedSizeData { a: 100, b: 200 };
455        mm.write_at(SCRATCH_PAGE, 6, &data_to_write)
456            .expect("Failed to write data to scratch page");
457
458        let data_with_alignment = DataWithAlignment { a: 100, b: 200 };
459        mm.zero(SCRATCH_PAGE, 0, &data_with_alignment)
460            .expect("Failed to zero data on scratch page");
461
462        let mut buffer = vec![0; 32];
463        mm.read_at_raw(SCRATCH_PAGE, 0, &mut buffer)
464            .expect("Failed to read data from scratch page");
465        assert!(
466            buffer.iter().all(|&b| b == 0),
467            "First 32 bytes are not zeroed"
468        );
469    }
470
471    #[test]
472    fn test_should_check_whether_write_is_aligned() {
473        let mut mm = make_mm();
474        let data_to_write = FixedSizeData { a: 100, b: 200 };
475        let res = mm.write_at(SCRATCH_PAGE, 2, &data_to_write);
476        assert!(matches!(res, Err(MemoryError::OffsetNotAligned { .. })));
477    }
478
479    #[test]
480    fn test_should_check_whether_read_is_aligned() {
481        let mut mm = make_mm();
482        let result: MemoryResult<FixedSizeData> = mm.read_at(SCRATCH_PAGE, 3);
483        assert!(matches!(result, Err(MemoryError::OffsetNotAligned { .. })));
484    }
485
486    #[test]
487    fn test_should_check_whether_zero_is_aligned() {
488        let mut mm = make_mm();
489        let data_to_zero = FixedSizeData { a: 1, b: 2 };
490        let result = mm.zero(SCRATCH_PAGE, 5, &data_to_zero);
491        assert!(matches!(result, Err(MemoryError::OffsetNotAligned { .. })));
492    }
493
494    #[test]
495    fn test_should_not_zero_unallocated_page() {
496        let mut mm = make_mm();
497        let data_to_zero = FixedSizeData { a: 1, b: 2 };
498        let result = mm.zero(10, 0, &data_to_zero);
499        assert!(matches!(result, Err(MemoryError::SegmentationFault { .. })));
500    }
501
502    #[test]
503    fn test_should_not_zero_out_of_bounds() {
504        let mut mm = make_mm();
505        let data_to_zero = FixedSizeData { a: 1, b: 2 };
506        let result = mm.zero(
507            SCRATCH_PAGE,
508            (HeapMemoryProvider::PAGE_SIZE - 4) as PageOffset,
509            &data_to_zero,
510        );
511        assert!(matches!(result, Err(MemoryError::SegmentationFault { .. })));
512    }
513
514    #[test]
515    fn test_should_read_raw() {
516        let mut mm = make_mm();
517        let data_to_write = vec![1u8, 2, 3, 4, 5];
518        mm.write_at_raw(SCRATCH_PAGE, 20, &data_to_write)
519            .expect("Failed to write raw data to scratch page");
520
521        let mut buf = vec![0u8; 5];
522        mm.read_at_raw(SCRATCH_PAGE, 20, &mut buf)
523            .expect("Failed to read raw data from scratch page");
524
525        assert_eq!(buf, data_to_write);
526    }
527
528    #[test]
529    fn test_should_fail_out_of_bounds_access() {
530        let mut mm = make_mm();
531        let data_to_write = FixedSizeData { a: 1, b: 2 };
532        let result = mm.write_at(
533            SCRATCH_PAGE,
534            (HeapMemoryProvider::PAGE_SIZE - 4) as PageOffset,
535            &data_to_write,
536        );
537        assert!(matches!(result, Err(MemoryError::SegmentationFault { .. })));
538
539        let result: MemoryResult<FixedSizeData> = mm.read_at(10, 0);
540        assert!(matches!(result, Err(MemoryError::SegmentationFault { .. })));
541        let result = mm.write_at(10, 0, &data_to_write);
542        assert!(matches!(result, Err(MemoryError::SegmentationFault { .. })));
543    }
544
545    #[test]
546    fn test_rejected_padded_write_does_not_touch_the_next_page() {
547        let mut mm = make_mm();
548        let first = mm.claim_page().expect("claim first");
549        let next = mm.claim_page().expect("claim next");
550        mm.write_at_raw(next, 0, &[0xAA; 4])
551            .expect("seed next page");
552
553        // 6 bytes at 65_530 fit, but padding to 10 bytes would reach 65_540.
554        let result = mm.write_at(first, 65_530, &TenAlignedData([3; 6]));
555        assert!(matches!(result, Err(MemoryError::SegmentationFault { .. })));
556
557        let mut buf = [0u8; 4];
558        mm.read_at_raw(next, 0, &mut buf).expect("read next page");
559        assert_eq!(buf, [0xAA; 4]);
560    }
561
562    #[test]
563    fn test_should_claim_new_page_by_growing() {
564        let mut mm = make_mm();
565        let initial_last_page = mm.last_page().unwrap();
566        let new_page = mm.claim_page().expect("Failed to claim new page");
567        assert_eq!(new_page, initial_last_page + 1);
568        let updated_last_page = mm.last_page().unwrap();
569        assert_eq!(updated_last_page, new_page);
570    }
571
572    #[test]
573    fn test_unclaim_then_claim_returns_same_page() {
574        let mut mm = make_mm();
575        let first = mm.claim_page().expect("claim");
576        // Write a non-zero pattern so we can assert it's zeroed on unclaim.
577        mm.write_at_raw(first, 0, &[0xAB, 0xCD, 0xEF, 0x42])
578            .expect("write");
579
580        mm.unclaim_page(first).expect("unclaim");
581
582        // The reserved page should hand the same page back.
583        let reused = mm.claim_page().expect("claim again");
584        assert_eq!(reused, first);
585
586        // Page contents must be zeroed by unclaim.
587        let mut buf = [0u8; 4];
588        mm.read_at_raw(reused, 0, &mut buf).expect("read");
589        assert_eq!(buf, [0u8; 4]);
590    }
591
592    #[test]
593    fn test_claim_after_exhausting_unclaimed_grows_memory() {
594        let mut mm = make_mm();
595        let page_a = mm.claim_page().expect("claim a");
596        let page_b = mm.claim_page().expect("claim b");
597
598        mm.unclaim_page(page_a).expect("unclaim a");
599        mm.unclaim_page(page_b).expect("unclaim b");
600
601        // Pop both unclaimed pages. The third claim must grow.
602        let _ = mm.claim_page().expect("reuse 1");
603        let _ = mm.claim_page().expect("reuse 2");
604        let high_water_before = mm.last_page().unwrap();
605        let grown = mm.claim_page().expect("grow");
606        assert_eq!(grown, high_water_before + 1);
607    }
608
609    #[test]
610    fn test_unclaim_persists_across_reload() {
611        let mut provider = HeapMemoryProvider::default();
612        {
613            let mut mm = MemoryManager::init(provider);
614            let first = mm.claim_page().expect("claim");
615            mm.unclaim_page(first).expect("unclaim");
616            provider = mm.into_provider();
617        }
618        let mut mm = MemoryManager::init(provider);
619        let reused = mm.claim_page().expect("claim after reload");
620        assert_eq!(reused, RESERVED_PAGES as Page);
621    }
622
623    #[test]
624    fn test_zero_page_zeros_full_page_contents() {
625        let mut mm = make_mm();
626        let page = mm.claim_page().expect("claim");
627        // Write to several offsets across the page.
628        mm.write_at_raw(page, 0, &[1, 2, 3, 4]).expect("write 0");
629        mm.write_at_raw(page, 30_000, &[9, 9]).expect("write 30k");
630
631        mm.zero_page(page).expect("zero_page");
632
633        let mut buf = [0u8; 4];
634        mm.read_at_raw(page, 0, &mut buf).expect("read");
635        assert_eq!(buf, [0u8; 4]);
636        let mut buf = [0u8; 2];
637        mm.read_at_raw(page, 30_000, &mut buf).expect("read");
638        assert_eq!(buf, [0u8; 2]);
639    }
640
641    #[test]
642    fn test_zero_page_rejects_unallocated_page() {
643        let mut mm = make_mm();
644        let result = mm.zero_page(99);
645        assert!(matches!(result, Err(MemoryError::SegmentationFault { .. })));
646    }
647
648    #[test]
649    fn test_should_check_unallocated_page() {
650        let mm = make_mm();
651        let result = mm.check_unallocated_page(100, 0, 10);
652        assert!(matches!(result, Err(MemoryError::SegmentationFault { .. })));
653
654        let last_page = mm.last_page().unwrap();
655        let result = mm.check_unallocated_page(last_page, 0, 10);
656        assert!(result.is_ok());
657    }
658
659    #[test]
660    fn test_should_compute_padding() {
661        assert_eq!(align_up::<DataWithAlignment>(0), 0);
662        assert_eq!(align_up::<DataWithAlignment>(1), 32);
663        assert_eq!(align_up::<DataWithAlignment>(2), 32);
664        assert_eq!(align_up::<DataWithAlignment>(3), 32);
665        assert_eq!(align_up::<DataWithAlignment>(31), 32);
666        assert_eq!(align_up::<DataWithAlignment>(32), 32);
667        assert_eq!(align_up::<DataWithAlignment>(48), 64);
668        assert_eq!(align_up::<DataWithAlignment>(147), 160);
669    }
670
671    #[derive(Debug, Clone, PartialEq)]
672    struct FixedSizeData {
673        a: u16,
674        b: u32,
675    }
676
677    impl Encode for FixedSizeData {
678        const SIZE: DataSize = DataSize::Fixed(6);
679        const ALIGNMENT: PageOffset = 6;
680
681        fn encode(&'_ self) -> Cow<'_, [u8]> {
682            let mut buf = vec![0u8; self.size() as usize];
683            buf[0..2].copy_from_slice(&self.a.to_le_bytes());
684            buf[2..6].copy_from_slice(&self.b.to_le_bytes());
685            Cow::Owned(buf)
686        }
687
688        fn decode(data: Cow<[u8]>) -> MemoryResult<Self>
689        where
690            Self: Sized,
691        {
692            let a = u16::from_le_bytes([data[0], data[1]]);
693            let b = u32::from_le_bytes([data[2], data[3], data[4], data[5]]);
694            Ok(FixedSizeData { a, b })
695        }
696
697        fn size(&self) -> MSize {
698            6
699        }
700    }
701
702    #[derive(Debug, Clone, PartialEq)]
703    struct DataWithAlignment {
704        a: u16,
705        b: u32,
706    }
707
708    impl Encode for DataWithAlignment {
709        const SIZE: DataSize = DataSize::Dynamic;
710        const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
711
712        fn encode(&'_ self) -> Cow<'_, [u8]> {
713            let mut buf = vec![0u8; self.size() as usize];
714            buf[0..2].copy_from_slice(&self.a.to_le_bytes());
715            buf[2..6].copy_from_slice(&self.b.to_le_bytes());
716            Cow::Owned(buf)
717        }
718
719        fn decode(data: Cow<[u8]>) -> MemoryResult<Self>
720        where
721            Self: Sized,
722        {
723            let a = u16::from_le_bytes([data[0], data[1]]);
724            let b = u32::from_le_bytes([data[2], data[3], data[4], data[5]]);
725            Ok(DataWithAlignment { a, b })
726        }
727
728        fn size(&self) -> MSize {
729            6
730        }
731    }
732
733    /// Dynamic 6-byte value whose alignment (10) does not divide the page
734    /// size, so its padded footprint can cross the page end.
735    #[derive(Debug, Clone, PartialEq)]
736    struct TenAlignedData([u8; 6]);
737
738    impl Encode for TenAlignedData {
739        const SIZE: DataSize = DataSize::Dynamic;
740        const ALIGNMENT: PageOffset = 10;
741
742        fn encode(&'_ self) -> Cow<'_, [u8]> {
743            Cow::Owned(self.0.to_vec())
744        }
745
746        fn decode(data: Cow<[u8]>) -> MemoryResult<Self>
747        where
748            Self: Sized,
749        {
750            Ok(Self(data[0..6].try_into()?))
751        }
752
753        fn size(&self) -> MSize {
754            6
755        }
756    }
757}