javm_cap/data.rs
1//! `DataCap` — Data cap.
2//!
3//! Two storage forms:
4//! - `Inline` — bytes in one `Vec<u8>`. Used for "small" Data
5//! (typically a single page).
6//! - `Paged` — page-merkleized; each page is owned by the DataCap
7//! via a reference-counted [`PageRef`](crate::page::PageRef) so
8//! multiple DataCap clones can share page bytes between CoW
9//! operations.
10//!
11//! ## Page-aligned invariant
12//!
13//! `DataCap` content storage is always a multiple of [`PAGE_SIZE`]
14//! bytes. There is no separate logical-size field — `content.len()`
15//! is the size, always 4 KiB-multiple. This lets the kernel map the
16//! cap's pages directly into a ring-3 page table without an
17//! intermediate per-call copy (see the v3 spec, §2 "Memory model").
18//!
19//! Callers that want to pass shorter payloads (variable-length args)
20//! pad up to the next page boundary at mint time; the meaningful
21//! bytes are interpreted by the receiver (length-prefix encoding or
22//! zero-terminator scanning).
23
24use core::alloc::Layout;
25
26use alloc::alloc::alloc_zeroed;
27use alloc::vec::Vec;
28
29use super::page::PageSlot;
30
31/// Cap-level page size. Mirrors the architecture's 4 KiB page (must
32/// match `nub_arch_x86::paging::PAGE_SIZE` for direct PT mapping to
33/// work).
34pub const PAGE_SIZE: usize = 4096;
35
36#[derive(Clone, Debug, ssz_derive::HashTreeRoot)]
37pub struct DataCap {
38 pub content: DataContent,
39}
40
41#[derive(Debug, ssz_derive::HashTreeRoot)]
42pub enum DataContent {
43 /// Bytes in a single slab. `bytes.len()` must be a multiple of
44 /// [`PAGE_SIZE`] (zero-padded by the constructor).
45 #[ssz(selector = 0)]
46 Inline(Vec<u8>),
47 /// Page-merkleized form. Each page is owned (via refcounted
48 /// PageRef) so DataCap clones can share unmodified pages.
49 #[ssz(selector = 1)]
50 Paged {
51 /// Logical page size (typically 4 KiB). Every page slab
52 /// has exactly this many bytes.
53 page_size: u32,
54 /// Dense slot table indexed by page index.
55 pages: Vec<PageSlot>,
56 },
57}
58
59// Manual Clone: the derived impl uses `Vec::clone`, which goes through
60// `Global::alloc` with the source Vec's *layout* — but `Vec::clone`
61// uses the default 1-byte alignment for `[u8]`. That violates DataCap's
62// page-alignment invariant: the kernel maps Inline content directly
63// into a ring-3 PT and panics at `assert!(phys.is_multiple_of(PAGE_SIZE))`
64// when the cloned buffer happens to land on an unaligned page.
65//
66// Re-allocate through `alloc_page_aligned_zeroed` and memcpy the bytes
67// to preserve the invariant across clones.
68impl Clone for DataContent {
69 fn clone(&self) -> Self {
70 match self {
71 DataContent::Inline(bytes) => {
72 let mut buf = alloc_page_aligned_zeroed(bytes.len());
73 buf[..bytes.len()].copy_from_slice(bytes);
74 DataContent::Inline(buf)
75 }
76 DataContent::Paged { page_size, pages } => DataContent::Paged {
77 page_size: *page_size,
78 pages: pages.clone(),
79 },
80 }
81 }
82}
83
84impl DataCap {
85 /// Total content size in bytes. Always a multiple of
86 /// [`PAGE_SIZE`].
87 pub fn content_len(&self) -> u64 {
88 match &self.content {
89 DataContent::Inline(bytes) => bytes.len() as u64,
90 DataContent::Paged { page_size, pages } => {
91 (*page_size as u64).saturating_mul(pages.len() as u64)
92 }
93 }
94 }
95}
96
97/// Allocate a zero-filled `Vec<u8>` of `len` bytes (rounded up to
98/// the next page boundary) with `PAGE_SIZE`-aligned backing storage.
99///
100/// The resulting `Vec` has `len == capacity == padded_len`; all bytes
101/// are zero. Page alignment of the underlying allocation is what lets
102/// the kernel later map the buffer directly into a ring-3 PT.
103///
104/// Panics if the allocator returns null (out-of-memory) or if
105/// constructing the `Layout` overflows.
106pub fn alloc_page_aligned_zeroed(len: usize) -> Vec<u8> {
107 let padded = len.next_multiple_of(PAGE_SIZE).max(PAGE_SIZE);
108 let layout =
109 Layout::from_size_align(padded, PAGE_SIZE).expect("DataCap page-aligned layout overflow");
110 // SAFETY: `padded > 0` so `Layout` is non-zero; the std global
111 // allocator is what `Vec` itself uses, so the resulting buffer is
112 // compatible with `Vec::from_raw_parts`.
113 let ptr = unsafe { alloc_zeroed(layout) };
114 if ptr.is_null() {
115 alloc::alloc::handle_alloc_error(layout);
116 }
117 // SAFETY: `alloc_zeroed` returned a non-null pointer to `padded`
118 // zeroed bytes aligned to PAGE_SIZE. The capacity we pass to
119 // `from_raw_parts` matches the allocation; the length (== capacity)
120 // reflects that all bytes are initialised (to zero).
121 unsafe { Vec::from_raw_parts(ptr, padded, padded) }
122}