use super::Checksum;
use crate::checksum::ChecksummedWriter;
use crate::coding::{Decode, Encode};
use crate::file::MAGIC_BYTES;
use crate::io::{ReadBytesExt, WriteBytesExt};
use crate::table::block::BlockType;
#[cfg(not(feature = "std"))]
use crate::io::{Read, Write};
#[cfg(feature = "std")]
use std::io::{Read, Write};
struct ChecksummedReader<R: Read> {
inner: R,
hasher: xxhash_rust::xxh3::Xxh3Default,
}
impl<R: Read> ChecksummedReader<R> {
pub fn new(reader: R) -> Self {
Self {
inner: reader,
hasher: xxhash_rust::xxh3::Xxh3Default::new(),
}
}
pub fn checksum(&self) -> Checksum {
Checksum::from_raw(self.hasher.digest128())
}
pub fn into_inner(self) -> R {
self.inner
}
}
#[cfg(feature = "std")]
impl<R: Read> std::io::Read for ChecksummedReader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = self.inner.read(buf)?;
#[expect(clippy::indexing_slicing)]
self.hasher.update(&buf[..n]);
Ok(n)
}
}
#[cfg(not(feature = "std"))]
impl<R: Read> crate::io::Read for ChecksummedReader<R> {
fn read(&mut self, buf: &mut [u8]) -> crate::io::Result<usize> {
let n = self.inner.read(buf)?;
#[expect(clippy::indexing_slicing)]
self.hasher.update(&buf[..n]);
Ok(n)
}
}
pub mod block_flags {
pub const KV_CHECKSUM_FOOTER: u8 = 1 << 0;
pub const ECC_PARITY: u8 = 1 << 1;
pub const COMPRESSED: u8 = 1 << 2;
pub const ENCRYPTED: u8 = 1 << 3;
pub const KNOWN: u8 = KV_CHECKSUM_FOOTER | ECC_PARITY | COMPRESSED | ENCRYPTED;
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Header {
pub block_type: BlockType,
pub block_flags: u8,
pub checksum: Checksum,
pub data_length: u32,
pub uncompressed_length: u32,
}
impl Header {
pub const MIN_LEN: usize = MAGIC_BYTES.len()
+ 1
+ core::mem::size_of::<Checksum>()
+ core::mem::size_of::<u32>()
+ core::mem::size_of::<u32>()
+ core::mem::size_of::<u32>();
pub const MAX_LEN: usize = Self::MIN_LEN + 1;
#[must_use]
pub const fn has_block_flags(block_type: BlockType) -> bool {
matches!(
block_type,
BlockType::Meta | BlockType::Manifest | BlockType::ManifestFooter
)
}
#[must_use]
pub const fn header_len(block_type: BlockType) -> usize {
if Self::has_block_flags(block_type) {
Self::MIN_LEN + 1
} else {
Self::MIN_LEN
}
}
#[must_use]
pub fn on_disk_size(&self) -> u32 {
#[expect(
clippy::cast_possible_truncation,
reason = "Header::header_len() is a small const"
)]
let header = Self::header_len(self.block_type) as u32;
let parity = if self.block_flags & block_flags::ECC_PARITY != 0 {
super::expected_parity_len(self.data_length, super::EccParams::RS_4_2)
} else {
0
};
header
.saturating_add(self.data_length)
.saturating_add(parity)
}
#[must_use]
pub fn on_disk_size_with(&self, ecc: Option<super::EccParams>) -> u32 {
#[expect(
clippy::cast_possible_truncation,
reason = "Header::header_len() is a small const"
)]
let header = Self::header_len(self.block_type) as u32;
let parity = ecc.map_or(0, |p| super::expected_parity_len(self.data_length, p));
header
.saturating_add(self.data_length)
.saturating_add(parity)
}
}
impl Encode for Header {
fn encode_into<W: Write>(&self, mut writer: &mut W) -> Result<(), crate::Error> {
use crate::io::LE;
let checksum = {
let mut writer = ChecksummedWriter::new(&mut writer);
writer.write_all(&MAGIC_BYTES)?;
writer.write_u8(self.block_type.into())?;
if Self::has_block_flags(self.block_type) {
writer.write_u8(self.block_flags)?;
}
writer.write_u128::<LE>(self.checksum.into_u128())?;
writer.write_u32::<LE>(self.data_length)?;
writer.write_u32::<LE>(self.uncompressed_length)?;
writer.checksum()
};
#[expect(
clippy::cast_possible_truncation,
reason = "we purposefully only use the lower 4 bytes as checksum"
)]
writer.write_u32::<LE>(checksum.into_u128() as u32)?;
Ok(())
}
}
impl Decode for Header {
fn decode_from<R: Read>(reader: &mut R) -> Result<Self, crate::Error> {
use crate::io::LE;
let mut protected_reader = ChecksummedReader::new(reader);
let mut magic = [0u8; MAGIC_BYTES.len()];
protected_reader.read_exact(&mut magic)?;
if magic != MAGIC_BYTES {
return Err(crate::Error::InvalidHeader("Block"));
}
let block_type = protected_reader.read_u8()?;
let block_type = BlockType::try_from(block_type)?;
let block_flags = if Self::has_block_flags(block_type) {
let flags = protected_reader.read_u8()?;
if flags & !block_flags::KNOWN != 0 {
return Err(crate::Error::InvalidTag(("block_flags", flags)));
}
flags
} else {
0
};
let checksum = protected_reader.read_u128::<LE>()?;
let data_length = protected_reader.read_u32::<LE>()?;
let uncompressed_length = protected_reader.read_u32::<LE>()?;
#[expect(
clippy::cast_possible_truncation,
reason = "we purposefully only use the lower 4 bytes as checksum"
)]
let got_checksum = protected_reader.checksum().into_u128() as u32;
let got_checksum = Checksum::from_raw(u128::from(got_checksum));
let reader = protected_reader.into_inner();
let header_checksum: u128 = reader.read_u32::<LE>()?.into();
let header_checksum = Checksum::from_raw(header_checksum);
if header_checksum != got_checksum {
return Err(crate::Error::ChecksumMismatch {
got: got_checksum,
expected: header_checksum,
});
}
Ok(Self {
block_type,
block_flags,
checksum: Checksum::from_raw(checksum),
data_length,
uncompressed_length,
})
}
}
#[cfg(test)]
impl Header {
pub(crate) fn test_dummy(block_type: BlockType) -> Self {
Self {
block_type,
block_flags: 0,
checksum: Checksum::from_raw(0),
data_length: 0,
uncompressed_length: 0,
}
}
}
#[cfg(test)]
mod tests;