use crate::format::frame::{
FILE_HEADER_LEN, FILE_MAGIC, FileHeader, FormatCheck, FormatError,
decode_header as decode_frame_header, encode_header as encode_frame_header, read_u16, read_u64,
};
use crate::format::{FormatFamily, FormatRegistry, RegistryError};
use super::LogSeq;
pub const WAL_HEADER_LEN: usize = FILE_HEADER_LEN + 8;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct WalHeader {
pub first_seq: LogSeq,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WalHeaderError {
Missing,
Truncated {
needed: usize,
available: usize,
},
WrongMagic {
expected: [u8; 8],
actual: [u8; 8],
},
WrongFamily {
expected: u16,
actual: u16,
},
UnsupportedVersion {
family: u16,
version: u16,
minimum: u16,
maximum: u16,
},
InvalidHeaderLength {
expected: u64,
actual: u64,
},
NonZeroFileLength {
actual: u64,
},
InvalidSharedHeader {
check: FormatCheck,
},
}
impl std::fmt::Display for WalHeaderError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Missing => formatter.write_str("WAL file header is missing"),
Self::Truncated { needed, available } => {
write!(
formatter,
"WAL file header needs {needed} bytes, got {available}"
)
}
Self::WrongMagic { expected, actual } => {
write!(formatter, "WAL magic expected {expected:?}, got {actual:?}")
}
Self::WrongFamily { expected, actual } => {
write!(formatter, "WAL family expected {expected}, got {actual}")
}
Self::UnsupportedVersion {
family,
version,
minimum,
maximum,
} => write!(
formatter,
"WAL family {family} version {version} is outside accepted range {minimum}..={maximum}"
),
Self::InvalidHeaderLength { expected, actual } => {
write!(
formatter,
"WAL header length expected {expected}, got {actual}"
)
}
Self::NonZeroFileLength { actual } => {
write!(formatter, "WAL file length must be zero, got {actual}")
}
Self::InvalidSharedHeader { check } => {
write!(formatter, "WAL shared header failed {check:?}")
}
}
}
}
impl std::error::Error for WalHeaderError {}
pub fn encode_header(first_seq: LogSeq) -> Result<Vec<u8>, RegistryError> {
let family = FormatFamily::Wal;
let spec = FormatRegistry::families()
.iter()
.find(|candidate| candidate.family == family)
.ok_or(RegistryError::UnknownFamily(family.id()))?;
let mut encoded = encode_frame_header(FileHeader {
magic: FILE_MAGIC,
family: family.id(),
version: spec.current_version,
flags: 0,
header_length: WAL_HEADER_LEN as u64,
file_length: 0,
});
encoded.extend_from_slice(&first_seq.get().to_le_bytes());
Ok(encoded)
}
pub fn decode_header(bytes: &[u8]) -> Result<WalHeader, WalHeaderError> {
if bytes.is_empty() {
return Err(WalHeaderError::Missing);
}
if bytes.len() < WAL_HEADER_LEN {
return Err(WalHeaderError::Truncated {
needed: WAL_HEADER_LEN,
available: bytes.len(),
});
}
let actual_magic = bytes
.get(..8)
.and_then(|value| <[u8; 8]>::try_from(value).ok())
.ok_or(WalHeaderError::Truncated {
needed: WAL_HEADER_LEN,
available: bytes.len(),
})?;
let family = read_u16("WAL", bytes, 8).map_err(invalid_shared_header)?;
let version = read_u16("WAL", bytes, 10).map_err(invalid_shared_header)?;
let header_length = read_u64("WAL", bytes, 16).map_err(invalid_shared_header)?;
let common =
decode_frame_header("WAL", FormatFamily::Wal, bytes).map_err(|error| {
match error.check() {
FormatCheck::Magic => WalHeaderError::WrongMagic {
expected: FILE_MAGIC,
actual: actual_magic,
},
FormatCheck::Family => WalHeaderError::WrongFamily {
expected: FormatFamily::Wal.id(),
actual: family,
},
FormatCheck::Version => match FormatRegistry::require(family, version) {
Err(RegistryError::UnsupportedVersion {
family,
version,
minimum,
maximum,
}) => WalHeaderError::UnsupportedVersion {
family,
version,
minimum,
maximum,
},
Ok(_) | Err(_) => WalHeaderError::InvalidSharedHeader {
check: FormatCheck::Version,
},
},
FormatCheck::HeaderLength => WalHeaderError::InvalidHeaderLength {
expected: WAL_HEADER_LEN as u64,
actual: header_length,
},
check => WalHeaderError::InvalidSharedHeader { check },
}
})?;
if common.header_length != WAL_HEADER_LEN as u64 {
return Err(WalHeaderError::InvalidHeaderLength {
expected: WAL_HEADER_LEN as u64,
actual: common.header_length,
});
}
if common.file_length != 0 {
return Err(WalHeaderError::NonZeroFileLength {
actual: common.file_length,
});
}
let first_seq = read_u64("WAL", bytes, FILE_HEADER_LEN).map_err(invalid_shared_header)?;
Ok(WalHeader {
first_seq: LogSeq::new(first_seq),
})
}
fn invalid_shared_header(error: FormatError) -> WalHeaderError {
WalHeaderError::InvalidSharedHeader {
check: error.check(),
}
}