pub(crate) const MAGIC_TAG: &[u8; 4] = b"MPPG";
pub(crate) const VERSION_MAJOR: u8 = 0;
pub(crate) const VERSION_MINOR: u8 = 2;
pub(crate) const VERSION_PATCH: u8 = 0;
pub(crate) const VERSION_BUILD: u8 = 0;
pub(crate) const MAGIC: u64 = u64::from_le_bytes([
b'M',
b'P',
b'P',
b'G',
VERSION_MAJOR,
VERSION_MINOR,
VERSION_PATCH,
VERSION_BUILD,
]);
pub(crate) const FIRST_DATA_PAGE: u64 = 3;
pub(crate) const PAGE0_DIR_OFFSET: usize = 20;
pub(crate) const DIR_BLOCK_REF_SIZE: usize = 17;
pub(crate) const DIR_ENTRY_SIZE: usize = 30;
pub(crate) fn max_dir_blocks(page_size: usize) -> usize {
page_size.saturating_sub(PAGE0_DIR_OFFSET + 4 + 4) / DIR_BLOCK_REF_SIZE
}
pub(crate) fn dir_entries_per_page(page_size: usize) -> usize {
page_size.saturating_sub(8 + 4) / DIR_ENTRY_SIZE
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum MetaSelector {
A,
B,
}
impl MetaSelector {
pub(crate) fn page_id(self) -> u64 {
match self {
MetaSelector::A => 1,
MetaSelector::B => 2,
}
}
pub(crate) fn other(self) -> Self {
match self {
MetaSelector::A => MetaSelector::B,
MetaSelector::B => MetaSelector::A,
}
}
fn from_byte(b: u8) -> Option<Self> {
match b {
0 => Some(MetaSelector::A),
1 => Some(MetaSelector::B),
_ => None,
}
}
fn to_byte(self) -> u8 {
match self {
MetaSelector::A => 0,
MetaSelector::B => 1,
}
}
}
pub(crate) struct Superblock {
pub magic: u64,
pub page_size_log2: u32,
pub active_meta: MetaSelector,
pub meta_checksum: u32,
}
pub(crate) struct MetaPage {
pub total_pages: u64,
pub generation: u64,
pub free_count: u64,
pub bitmap: Vec<u8>,
}
impl Superblock {
pub(crate) fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 20 {
return None;
}
let magic = u64::from_le_bytes(data[0..8].try_into().ok()?);
let page_size_log2 = u32::from_le_bytes(data[8..12].try_into().ok()?);
let active_meta = MetaSelector::from_byte(data[12])?;
let meta_checksum = u32::from_le_bytes(data[16..20].try_into().ok()?);
Some(Superblock {
magic,
page_size_log2,
active_meta,
meta_checksum,
})
}
pub(crate) fn write_to(&self, buf: &mut [u8]) {
buf[0..8].copy_from_slice(&self.magic.to_le_bytes());
buf[8..12].copy_from_slice(&self.page_size_log2.to_le_bytes());
buf[12] = self.active_meta.to_byte();
buf[13..16].fill(0);
buf[16..20].copy_from_slice(&self.meta_checksum.to_le_bytes());
}
pub(crate) fn is_valid(&self) -> bool {
let b = self.magic.to_le_bytes();
&b[0..4] == MAGIC_TAG && b[4] == VERSION_MAJOR && b[5] <= VERSION_MINOR
}
}
impl MetaPage {
pub(crate) fn from_bytes(data: &[u8]) -> Option<Self> {
let page_size = data.len();
if page_size < 28 {
return None;
}
let stored_csum = u32::from_le_bytes(data[page_size - 4..].try_into().ok()?);
let computed_csum = crc32fast::hash(&data[..page_size - 4]);
if stored_csum != computed_csum {
return None;
}
let total_pages = u64::from_le_bytes(data[0..8].try_into().ok()?);
let generation = u64::from_le_bytes(data[8..16].try_into().ok()?);
let free_count = u64::from_le_bytes(data[16..24].try_into().ok()?);
let bitmap_len = (total_pages as usize).div_ceil(8);
let bitmap_end = 24 + bitmap_len;
if bitmap_end > page_size - 4 {
return None;
}
let bitmap = data[24..bitmap_end].to_vec();
Some(MetaPage {
total_pages,
generation,
free_count,
bitmap,
})
}
pub(crate) fn write_to(&self, buf: &mut [u8]) {
let page_size = buf.len();
buf[0..8].copy_from_slice(&self.total_pages.to_le_bytes());
buf[8..16].copy_from_slice(&self.generation.to_le_bytes());
buf[16..24].copy_from_slice(&self.free_count.to_le_bytes());
let bitmap_len = self.bitmap.len();
buf[24..24 + bitmap_len].copy_from_slice(&self.bitmap);
buf[24 + bitmap_len..page_size - 4].fill(0);
let csum = crc32fast::hash(&buf[..page_size - 4]);
buf[page_size - 4..].copy_from_slice(&csum.to_le_bytes());
}
pub(crate) fn page_checksum(data: &[u8]) -> u32 {
crc32fast::hash(data)
}
pub(crate) fn alloc_page(&mut self) -> Option<u64> {
for (byte_idx, byte) in self.bitmap.iter_mut().enumerate() {
if *byte != 0xff {
let bit = byte.trailing_ones();
let page_id = byte_idx as u64 * 8 + bit as u64;
if page_id >= self.total_pages {
break;
}
*byte |= 1 << bit;
self.free_count = self.free_count.saturating_sub(1);
return Some(page_id);
}
}
None
}
pub(crate) fn free_page(&mut self, id: u64) -> bool {
if id >= self.total_pages {
return false;
}
let byte_idx = (id / 8) as usize;
let bit = (id % 8) as u8;
if self.bitmap[byte_idx] & (1 << bit) == 0 {
return false; }
self.bitmap[byte_idx] &= !(1 << bit);
self.free_count += 1;
true
}
pub(crate) fn new_for_capacity(total_pages: u64) -> Self {
let bitmap_len = (total_pages as usize).div_ceil(8);
let mut bitmap = vec![0u8; bitmap_len];
for reserved in 0..FIRST_DATA_PAGE.min(total_pages) {
bitmap[(reserved / 8) as usize] |= 1 << (reserved % 8);
}
let free_count = total_pages.saturating_sub(FIRST_DATA_PAGE);
MetaPage {
total_pages,
generation: 0,
free_count,
bitmap,
}
}
pub(crate) fn grow_to(&mut self, new_total_pages: u64) {
let new_bitmap_len = (new_total_pages as usize).div_ceil(8);
self.bitmap.resize(new_bitmap_len, 0);
let added = new_total_pages.saturating_sub(self.total_pages);
self.free_count += added;
self.total_pages = new_total_pages;
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct DirBlockRef {
pub page_a: u64,
pub page_b: u64,
pub active: MetaSelector,
}
impl DirBlockRef {
pub(crate) fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < DIR_BLOCK_REF_SIZE {
return None;
}
let page_a = u64::from_le_bytes(data[0..8].try_into().ok()?);
let page_b = u64::from_le_bytes(data[8..16].try_into().ok()?);
let active = MetaSelector::from_byte(data[16])?;
Some(DirBlockRef {
page_a,
page_b,
active,
})
}
pub(crate) fn write_to(&self, buf: &mut [u8]) {
buf[0..8].copy_from_slice(&self.page_a.to_le_bytes());
buf[8..16].copy_from_slice(&self.page_b.to_le_bytes());
buf[16] = self.active.to_byte();
}
}
pub(crate) fn read_dir_blocks(page0: &[u8]) -> Result<Vec<DirBlockRef>, ()> {
let page_size = page0.len();
if page_size < PAGE0_DIR_OFFSET + 8 {
return Ok(vec![]);
}
let count = u32::from_le_bytes(
page0[PAGE0_DIR_OFFSET..PAGE0_DIR_OFFSET + 4]
.try_into()
.map_err(|_| ())?,
) as usize;
if count == 0 {
return Ok(vec![]);
}
if page_size < PAGE0_DIR_OFFSET + 4 + 4 {
return Err(());
}
let stored_csum = u32::from_le_bytes(page0[page_size - 4..].try_into().map_err(|_| ())?);
let computed_csum = crc32fast::hash(&page0[PAGE0_DIR_OFFSET..page_size - 4]);
if stored_csum != computed_csum {
return Err(());
}
if count > max_dir_blocks(page_size) {
return Err(());
}
let entries_start = PAGE0_DIR_OFFSET + 4;
let mut blocks = Vec::with_capacity(count);
for i in 0..count {
let off = entries_start + i * DIR_BLOCK_REF_SIZE;
let block = DirBlockRef::from_bytes(&page0[off..off + DIR_BLOCK_REF_SIZE]).ok_or(())?;
blocks.push(block);
}
Ok(blocks)
}
pub(crate) fn write_dir_blocks(dir_blocks: &[DirBlockRef], page0: &mut [u8]) {
let page_size = page0.len();
let count = dir_blocks.len() as u32;
page0[PAGE0_DIR_OFFSET..PAGE0_DIR_OFFSET + 4].copy_from_slice(&count.to_le_bytes());
let entries_start = PAGE0_DIR_OFFSET + 4;
for (i, block) in dir_blocks.iter().enumerate() {
let off = entries_start + i * DIR_BLOCK_REF_SIZE;
block.write_to(&mut page0[off..off + DIR_BLOCK_REF_SIZE]);
}
let entries_end = entries_start + dir_blocks.len() * DIR_BLOCK_REF_SIZE;
page0[entries_end..page_size - 4].fill(0);
let csum = crc32fast::hash(&page0[PAGE0_DIR_OFFSET..page_size - 4]);
page0[page_size - 4..].copy_from_slice(&csum.to_le_bytes());
}
#[derive(Clone, Debug, Default)]
pub(crate) struct DirEntry {
pub in_use: bool,
pub page_a: u64,
pub page_b: u64,
pub active_slot: u8,
pub generation: u64,
pub checksum: u32,
}
impl DirEntry {
pub(crate) fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < DIR_ENTRY_SIZE {
return None;
}
let in_use = data[0] != 0;
let page_a = u64::from_le_bytes(data[1..9].try_into().ok()?);
let page_b = u64::from_le_bytes(data[9..17].try_into().ok()?);
let active_slot = data[17];
let generation = u64::from_le_bytes(data[18..26].try_into().ok()?);
let checksum = u32::from_le_bytes(data[26..30].try_into().ok()?);
Some(DirEntry {
in_use,
page_a,
page_b,
active_slot,
generation,
checksum,
})
}
pub(crate) fn write_to(&self, buf: &mut [u8]) {
buf[0] = self.in_use as u8;
buf[1..9].copy_from_slice(&self.page_a.to_le_bytes());
buf[9..17].copy_from_slice(&self.page_b.to_le_bytes());
buf[17] = self.active_slot;
buf[18..26].copy_from_slice(&self.generation.to_le_bytes());
buf[26..30].copy_from_slice(&self.checksum.to_le_bytes());
}
}
pub(crate) struct DirPage {
pub capacity: u32,
pub entries: Vec<DirEntry>,
}
impl DirPage {
pub(crate) fn new_empty(page_size: usize) -> Self {
let capacity = dir_entries_per_page(page_size) as u32;
let entries = (0..capacity as usize)
.map(|_| DirEntry::default())
.collect();
DirPage { capacity, entries }
}
pub(crate) fn from_bytes(data: &[u8]) -> Option<Self> {
let page_size = data.len();
if page_size < 12 {
return None;
}
let stored_csum = u32::from_le_bytes(data[page_size - 4..].try_into().ok()?);
let computed_csum = crc32fast::hash(&data[..page_size - 4]);
if stored_csum != computed_csum {
return None;
}
let capacity = u32::from_le_bytes(data[0..4].try_into().ok()?) as usize;
let entries_end = 8 + capacity * DIR_ENTRY_SIZE;
if entries_end > page_size - 4 {
return None;
}
let mut entries = Vec::with_capacity(capacity);
for i in 0..capacity {
let off = 8 + i * DIR_ENTRY_SIZE;
entries.push(DirEntry::from_bytes(&data[off..off + DIR_ENTRY_SIZE])?);
}
Some(DirPage {
capacity: capacity as u32,
entries,
})
}
pub(crate) fn write_to(&self, buf: &mut [u8]) {
let page_size = buf.len();
buf[0..4].copy_from_slice(&self.capacity.to_le_bytes());
buf[4..8].fill(0);
for (i, entry) in self.entries.iter().enumerate() {
let off = 8 + i * DIR_ENTRY_SIZE;
entry.write_to(&mut buf[off..off + DIR_ENTRY_SIZE]);
}
let entries_end = 8 + self.entries.len() * DIR_ENTRY_SIZE;
buf[entries_end..page_size - 4].fill(0);
let csum = crc32fast::hash(&buf[..page_size - 4]);
buf[page_size - 4..].copy_from_slice(&csum.to_le_bytes());
}
}