use std::convert::TryInto;
use std::mem;
use crate::storage::compression::CompressionAlgorithm;
use crate::storage::format::{FormatError, SECTION_ENTRY_SIZE};
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionType {
Metadata = 0x00,
SSTable = 0x01,
VectorIndex = 0x02,
ColumnarSegment = 0x03,
LargeValue = 0x04,
Intent = 0x05,
Lock = 0x06,
RaftLog = 0x07,
}
impl TryFrom<u8> for SectionType {
type Error = FormatError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::Metadata),
0x01 => Ok(Self::SSTable),
0x02 => Ok(Self::VectorIndex),
0x03 => Ok(Self::ColumnarSegment),
0x04 => Ok(Self::LargeValue),
0x05 => Ok(Self::Intent),
0x06 => Ok(Self::Lock),
0x07 => Ok(Self::RaftLog),
_ => Err(FormatError::IncompleteWrite),
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SectionEntry {
pub section_type: SectionType,
pub compression: CompressionAlgorithm,
pub _padding: [u8; 2],
pub section_id: u32,
pub offset: u64,
pub compressed_length: u64,
pub uncompressed_length: u64,
pub checksum: u32,
pub _padding2: [u8; 4],
}
const _: () = assert!(mem::size_of::<SectionEntry>() == SECTION_ENTRY_SIZE);
impl SectionEntry {
pub const SIZE: usize = SECTION_ENTRY_SIZE;
#[allow(clippy::too_many_arguments)]
pub fn new(
section_type: SectionType,
compression: CompressionAlgorithm,
section_id: u32,
offset: u64,
compressed_length: u64,
uncompressed_length: u64,
checksum: u32,
) -> Self {
Self {
section_type,
compression,
_padding: [0u8; 2],
section_id,
offset,
compressed_length,
uncompressed_length,
checksum,
_padding2: [0u8; 4],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SectionIndex {
pub count: u32,
pub entries: Vec<SectionEntry>,
}
impl Default for SectionIndex {
fn default() -> Self {
Self::new()
}
}
impl SectionIndex {
pub const ENTRY_SIZE: usize = SECTION_ENTRY_SIZE;
pub fn new() -> Self {
Self {
count: 0,
entries: Vec::new(),
}
}
pub fn add_entry(&mut self, entry: SectionEntry) {
self.entries.push(entry);
self.count = self.entries.len() as u32;
}
pub fn filter_by_type(&self, section_type: SectionType) -> Vec<&SectionEntry> {
self.entries
.iter()
.filter(|entry| entry.section_type == section_type)
.collect()
}
pub fn find_by_id(&self, section_id: u32) -> Option<&SectionEntry> {
self.entries
.iter()
.find(|entry| entry.section_id == section_id)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, FormatError> {
if bytes.len() < 4 {
return Err(FormatError::IncompleteWrite);
}
let count = u32::from_le_bytes(
bytes[0..4]
.try_into()
.expect("fixed slice length for count"),
);
let expected_size = 4 + count as usize * SECTION_ENTRY_SIZE;
if bytes.len() < expected_size {
return Err(FormatError::IncompleteWrite);
}
let mut entries = Vec::with_capacity(count as usize);
let mut cursor = 4;
for _ in 0..count {
let section_type = SectionType::try_from(bytes[cursor])?;
let compression = parse_compression(bytes[cursor + 1])?;
let padding = [bytes[cursor + 2], bytes[cursor + 3]];
if padding != [0, 0] {
return Err(FormatError::IncompleteWrite);
}
let section_id = u32::from_le_bytes(
bytes[cursor + 4..cursor + 8]
.try_into()
.expect("fixed slice length for section_id"),
);
let offset = u64::from_le_bytes(
bytes[cursor + 8..cursor + 16]
.try_into()
.expect("fixed slice length for offset"),
);
let compressed_length = u64::from_le_bytes(
bytes[cursor + 16..cursor + 24]
.try_into()
.expect("fixed slice length for compressed_length"),
);
let uncompressed_length = u64::from_le_bytes(
bytes[cursor + 24..cursor + 32]
.try_into()
.expect("fixed slice length for uncompressed_length"),
);
let checksum = u32::from_le_bytes(
bytes[cursor + 32..cursor + 36]
.try_into()
.expect("fixed slice length for checksum"),
);
let padding2: [u8; 4] = bytes[cursor + 36..cursor + 40]
.try_into()
.expect("fixed slice length for padding2");
if padding2 != [0; 4] {
return Err(FormatError::IncompleteWrite);
}
entries.push(SectionEntry {
section_type,
compression,
_padding: [0u8; 2],
section_id,
offset,
compressed_length,
uncompressed_length,
checksum,
_padding2: [0u8; 4],
});
cursor += SECTION_ENTRY_SIZE;
}
Ok(Self { count, entries })
}
pub fn to_bytes(&self) -> Vec<u8> {
let count = self.entries.len() as u32;
let mut bytes = Vec::with_capacity(self.serialized_size());
bytes.extend_from_slice(&count.to_le_bytes());
for entry in &self.entries {
bytes.push(entry.section_type as u8);
bytes.push(entry.compression as u8);
bytes.extend_from_slice(&[0u8; 2]);
bytes.extend_from_slice(&entry.section_id.to_le_bytes());
bytes.extend_from_slice(&entry.offset.to_le_bytes());
bytes.extend_from_slice(&entry.compressed_length.to_le_bytes());
bytes.extend_from_slice(&entry.uncompressed_length.to_le_bytes());
bytes.extend_from_slice(&entry.checksum.to_le_bytes());
bytes.extend_from_slice(&[0u8; 4]);
}
bytes
}
pub fn serialized_size(&self) -> usize {
4 + self.entries.len() * SECTION_ENTRY_SIZE
}
}
fn parse_compression(byte: u8) -> Result<CompressionAlgorithm, FormatError> {
match byte {
0 => Ok(CompressionAlgorithm::None),
1 => Ok(CompressionAlgorithm::Snappy),
2 => Ok(CompressionAlgorithm::Zstd),
3 => Ok(CompressionAlgorithm::Lz4),
other => Err(FormatError::UnsupportedCompression { algorithm: other }),
}
}