use super::error::WalError;
use super::Lsn;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RankRegime {
Owned = 0,
Overlay = 1,
}
impl RankRegime {
pub fn from_u8(b: u8) -> Self {
match b {
1 => RankRegime::Overlay,
_ => RankRegime::Owned,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct WalHeader {
pub magic: [u8; 8],
pub version: u32,
pub checkpoint_lsn: Lsn,
pub commit_seq_floor: u64,
pub rank_regime: u8,
pub reserved: [u8; 35],
}
impl WalHeader {
pub const MAGIC: [u8; 8] = *b"PARTWAL\0";
pub const MAGIC_OVERLAY: [u8; 8] = *b"PARTWALO";
pub const VERSION: u32 = 2;
pub const MIN_SUPPORTED_VERSION: u32 = 1;
pub const SIZE: usize = 64;
pub fn new() -> Self {
WalHeader {
magic: Self::MAGIC,
version: Self::VERSION,
checkpoint_lsn: 0,
commit_seq_floor: 0,
rank_regime: RankRegime::Owned as u8,
reserved: [0; 35],
}
}
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
let mut buf = [0u8; Self::SIZE];
buf[0..8].copy_from_slice(&self.magic);
buf[8..12].copy_from_slice(&self.version.to_le_bytes());
buf[12..20].copy_from_slice(&self.checkpoint_lsn.to_le_bytes());
buf[20..28].copy_from_slice(&self.commit_seq_floor.to_le_bytes());
buf[28] = self.rank_regime;
buf[29..64].copy_from_slice(&self.reserved);
buf
}
pub fn from_bytes(buf: &[u8; Self::SIZE]) -> Result<Self, WalError> {
let magic: [u8; 8] = buf[0..8].try_into().unwrap();
if magic != Self::MAGIC && magic != Self::MAGIC_OVERLAY {
return Err(WalError::CorruptedRecord("Invalid WAL magic number".into()));
}
let version = u32::from_le_bytes(buf[8..12].try_into().unwrap());
if version < Self::MIN_SUPPORTED_VERSION || version > Self::VERSION {
return Err(WalError::CorruptedRecord(format!(
"Unsupported WAL version: {} (supported range {}..={})",
version,
Self::MIN_SUPPORTED_VERSION,
Self::VERSION
)));
}
let checkpoint_lsn = u64::from_le_bytes(buf[12..20].try_into().unwrap());
let commit_seq_floor = u64::from_le_bytes(buf[20..28].try_into().unwrap());
let rank_regime = buf[28];
let reserved: [u8; 35] = buf[29..64].try_into().unwrap();
Ok(WalHeader {
magic,
version,
checkpoint_lsn,
commit_seq_floor,
rank_regime,
reserved,
})
}
pub fn regime(&self) -> RankRegime {
RankRegime::from_u8(self.rank_regime)
}
}
impl Default for WalHeader {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod dg0_header_tests {
use super::*;
#[test]
fn header_roundtrips_floor_and_regime() {
let mut h = WalHeader::new();
h.commit_seq_floor = 0x0102_0304_0506_0708;
h.rank_regime = RankRegime::Overlay as u8;
h.checkpoint_lsn = 42;
let bytes = h.to_bytes();
let h2 = WalHeader::from_bytes(&bytes).expect("roundtrip");
assert_eq!(h2.commit_seq_floor, 0x0102_0304_0506_0708);
assert_eq!(h2.regime(), RankRegime::Overlay);
assert_eq!(h2.checkpoint_lsn, 42);
assert_eq!(h2.version, WalHeader::VERSION);
}
#[test]
fn old_zeroed_reserved_reads_as_owned_no_floor() {
let mut buf = WalHeader::new().to_bytes();
for b in buf[20..64].iter_mut() {
*b = 0; }
let h = WalHeader::from_bytes(&buf).expect("old header reads");
assert_eq!(h.commit_seq_floor, 0);
assert_eq!(h.regime(), RankRegime::Owned);
}
#[test]
fn unknown_regime_byte_is_owned_failsafe() {
let mut buf = WalHeader::new().to_bytes();
buf[28] = 0xFF;
let h = WalHeader::from_bytes(&buf).expect("reads");
assert_eq!(h.regime(), RankRegime::Owned);
}
#[test]
fn dual_magic_accept_set() {
let std = WalHeader::new().to_bytes();
assert!(
WalHeader::from_bytes(&std).is_ok(),
"standard MAGIC must still parse (additive)"
);
let mut over = WalHeader::new();
over.magic = WalHeader::MAGIC_OVERLAY;
over.rank_regime = RankRegime::Overlay as u8;
let bytes = over.to_bytes();
let h = WalHeader::from_bytes(&bytes).expect("MAGIC_OVERLAY must parse in a new binary");
assert_eq!(h.magic, WalHeader::MAGIC_OVERLAY);
assert_eq!(h.regime(), RankRegime::Overlay);
let mut bad = WalHeader::new().to_bytes();
bad[0..8].copy_from_slice(b"NOTAWAL!");
assert!(
WalHeader::from_bytes(&bad).is_err(),
"unknown magic must still fail-close"
);
}
}