#![forbid(unsafe_code)]
use core::num::NonZeroU64;
pub const PAGE_SIZE: usize = 4096;
pub const PAGE_TRAILER_SIZE: usize = 4;
pub const ENCRYPTION_OVERHEAD: usize = 24 + 16;
#[must_use]
pub const fn physical_page_stride(feature_flags: u32) -> usize {
if feature_flags & (1u32 << 1) != 0 {
PAGE_SIZE + ENCRYPTION_OVERHEAD
} else {
PAGE_SIZE
}
}
#[must_use]
pub fn physical_offset_for(raw_id: u64, feature_flags: u32) -> u64 {
if raw_id == 0 {
return 0;
}
let stride = physical_page_stride(feature_flags) as u64;
let after_header = PAGE_SIZE as u64;
let Some(rel) = raw_id.checked_sub(1).and_then(|n| n.checked_mul(stride)) else {
return u64::MAX;
};
after_header.saturating_add(rel)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PageId(NonZeroU64);
impl PageId {
#[inline]
#[must_use]
pub const fn new(raw: u64) -> Option<Self> {
match NonZeroU64::new(raw) {
Some(nz) => Some(Self(nz)),
None => None,
}
}
#[must_use]
pub const fn from_nonzero(nz: NonZeroU64) -> Self {
Self(nz)
}
#[inline]
#[must_use]
pub const fn get(self) -> u64 {
self.0.get()
}
#[must_use]
pub fn byte_offset(self, feature_flags: u32) -> u64 {
physical_offset_for(self.0.get(), feature_flags)
}
}
#[derive(Debug, Clone)]
pub struct Page {
bytes: Box<[u8; PAGE_SIZE]>,
}
impl Page {
#[must_use]
pub fn zeroed() -> Self {
Self {
bytes: Box::new([0u8; PAGE_SIZE]),
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8; PAGE_SIZE] {
&self.bytes
}
pub fn as_bytes_mut(&mut self) -> &mut [u8; PAGE_SIZE] {
&mut self.bytes
}
pub fn zero(&mut self) {
self.bytes.fill(0);
}
}
impl Default for Page {
fn default() -> Self {
Self::zeroed()
}
}
#[cfg(test)]
mod tests {
use super::{Page, PageId, ENCRYPTION_OVERHEAD, PAGE_SIZE};
#[test]
fn page_id_rejects_zero() {
assert!(PageId::new(0).is_none());
assert_eq!(PageId::new(1).map(PageId::get), Some(1));
}
#[test]
fn page_id_byte_offset() {
let id = PageId::new(3).expect("non-zero");
assert_eq!(id.byte_offset(0), 3 * PAGE_SIZE as u64);
let enc_flags = 1u32 << 1;
assert_eq!(
id.byte_offset(enc_flags),
super::physical_offset_for(3, enc_flags)
);
assert_eq!(
id.byte_offset(enc_flags),
PAGE_SIZE as u64 + 2 * (PAGE_SIZE + ENCRYPTION_OVERHEAD) as u64
);
}
#[test]
fn page_zeroed_and_zero() {
let mut p = Page::zeroed();
assert!(p.as_bytes().iter().all(|&b| b == 0));
p.as_bytes_mut()[0] = 0xAB;
p.zero();
assert!(p.as_bytes().iter().all(|&b| b == 0));
}
#[test]
fn physical_page_stride_picks_4096_or_4136() {
assert_eq!(super::physical_page_stride(0), super::PAGE_SIZE);
assert_eq!(super::physical_page_stride(0b01), super::PAGE_SIZE);
assert_eq!(
super::physical_page_stride(0b10),
super::PAGE_SIZE + ENCRYPTION_OVERHEAD
);
assert_eq!(
super::physical_page_stride(0b11),
super::PAGE_SIZE + ENCRYPTION_OVERHEAD
);
}
#[test]
fn physical_offset_for_page_zero_is_zero() {
assert_eq!(super::physical_offset_for(0, 0), 0);
assert_eq!(super::physical_offset_for(0, 0b10), 0);
}
#[test]
fn physical_offset_for_unencrypted_matches_legacy() {
for raw in 1..32u64 {
assert_eq!(
super::physical_offset_for(raw, 0),
raw * super::PAGE_SIZE as u64
);
}
}
#[test]
fn physical_offset_for_encrypted_uses_4136_stride() {
assert_eq!(super::physical_offset_for(1, 0b10), super::PAGE_SIZE as u64);
assert_eq!(
super::physical_offset_for(2, 0b10),
(super::PAGE_SIZE + super::PAGE_SIZE + super::ENCRYPTION_OVERHEAD) as u64
);
assert_eq!(
super::physical_offset_for(3, 0b10),
(super::PAGE_SIZE + 2 * (super::PAGE_SIZE + super::ENCRYPTION_OVERHEAD)) as u64
);
}
}