pub(crate) mod binary_index;
pub mod decoder;
mod encoder;
pub mod hash_index;
mod header;
mod offset;
mod trailer;
mod r#type;
pub(crate) use decoder::{Decodable, Decoder, ParsedItem};
pub(crate) use encoder::{Encodable, Encoder};
pub use header::Header;
pub use offset::BlockOffset;
pub use r#type::BlockType;
pub(crate) use trailer::{Trailer, TRAILER_START_MARKER};
use crate::{
coding::{Decode, Encode},
table::BlockHandle,
Checksum, CompressionType, Slice,
};
use std::fs::File;
#[derive(Clone)]
pub struct Block {
pub header: Header,
pub data: Slice,
}
impl Block {
#[must_use]
pub fn size(&self) -> usize {
self.data.len()
}
pub fn write_into<W: std::io::Write>(
mut writer: &mut W,
data: &[u8],
block_type: BlockType,
compression: CompressionType,
) -> crate::Result<Header> {
let mut header = Header {
block_type,
checksum: Checksum::from_raw(0), data_length: 0,
#[expect(clippy::cast_possible_truncation, reason = "blocks are limited to u32")]
uncompressed_length: data.len() as u32,
};
let data = match compression {
CompressionType::None => data,
#[cfg(feature = "lz4")]
CompressionType::Lz4 => &lz4_flex::compress(data),
};
#[expect(clippy::cast_possible_truncation, reason = "blocks are limited to u32")]
{
header.data_length = data.len() as u32;
header.checksum = Checksum::from_raw(crate::hash::hash128(data));
}
header.encode_into(&mut writer)?;
writer.write_all(data)?;
log::trace!(
"Writing block with size {}B (compressed: {}B) (excluding header of {}B)",
header.uncompressed_length,
header.data_length,
Header::serialized_len(),
);
Ok(header)
}
pub fn from_reader<R: std::io::Read>(
reader: &mut R,
compression: CompressionType,
) -> crate::Result<Self> {
let header = Header::decode_from(reader)?;
let raw_data = Slice::from_reader(reader, header.data_length as usize)?;
let checksum = Checksum::from_raw(crate::hash::hash128(&raw_data));
checksum.check(header.checksum).inspect_err(|_| {
log::error!(
"Checksum mismatch for <bufreader>, got={}, expected={}",
checksum,
header.checksum,
);
})?;
let data = match compression {
CompressionType::None => raw_data,
#[cfg(feature = "lz4")]
CompressionType::Lz4 => {
#[warn(unsafe_code)]
let mut builder =
unsafe { Slice::builder_unzeroed(header.uncompressed_length as usize) };
lz4_flex::decompress_into(&raw_data, &mut builder)
.map_err(|_| crate::Error::Decompress(compression))?;
builder.freeze().into()
}
};
debug_assert_eq!(header.uncompressed_length, {
#[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")]
{
data.len() as u32
}
});
Ok(Self { header, data })
}
pub fn from_file(
file: &File,
handle: BlockHandle,
compression: CompressionType,
) -> crate::Result<Self> {
let buf = crate::file::read_exact(file, *handle.offset(), handle.size() as usize)?;
let header = Header::decode_from(&mut &buf[..])?;
#[expect(clippy::indexing_slicing)]
let checksum = Checksum::from_raw(crate::hash::hash128(&buf[Header::serialized_len()..]));
checksum.check(header.checksum).inspect_err(|_| {
log::error!(
"Checksum mismatch for block {handle:?}, got={}, expected={}",
checksum,
header.checksum,
);
})?;
let buf = match compression {
CompressionType::None => {
let value = buf.slice(Header::serialized_len()..);
#[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")]
{
debug_assert_eq!(header.uncompressed_length, value.len() as u32);
}
value
}
#[cfg(feature = "lz4")]
CompressionType::Lz4 => {
#[expect(clippy::indexing_slicing)]
let raw_data = &buf[Header::serialized_len()..];
#[warn(unsafe_code)]
let mut builder =
unsafe { Slice::builder_unzeroed(header.uncompressed_length as usize) };
lz4_flex::decompress_into(raw_data, &mut builder)
.map_err(|_| crate::Error::Decompress(compression))?;
builder.freeze().into()
}
};
Ok(Self { header, data: buf })
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
#[test]
fn block_roundtrip_uncompressed() -> crate::Result<()> {
let mut writer = vec![];
Block::write_into(
&mut writer,
b"abcdefabcdefabcdef",
BlockType::Data,
CompressionType::None,
)?;
{
let mut reader = &writer[..];
let block = Block::from_reader(&mut reader, CompressionType::None)?;
assert_eq!(b"abcdefabcdefabcdef", &*block.data);
}
Ok(())
}
#[test]
#[cfg(feature = "lz4")]
fn block_roundtrip_lz4() -> crate::Result<()> {
let mut writer = vec![];
Block::write_into(
&mut writer,
b"abcdefabcdefabcdef",
BlockType::Data,
CompressionType::Lz4,
)?;
{
let mut reader = &writer[..];
let block = Block::from_reader(&mut reader, CompressionType::Lz4)?;
assert_eq!(b"abcdefabcdefabcdef", &*block.data);
}
Ok(())
}
}