use crate::hfile::block_type::{HFileBlockType, MAGIC_LENGTH};
use crate::hfile::compression::CompressionCodec;
use crate::hfile::error::{HFileError, Result};
use crate::hfile::proto::TrailerProto;
use prost::Message;
pub const TRAILER_SIZE: usize = 4096;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct HFileTrailer {
pub major_version: u32,
pub minor_version: u32,
pub file_info_offset: u64,
pub load_on_open_data_offset: u64,
pub uncompressed_data_index_size: u64,
pub total_uncompressed_bytes: u64,
pub data_index_count: u32,
pub meta_index_count: u32,
pub entry_count: u64,
pub num_data_index_levels: u32,
pub first_data_block_offset: u64,
pub last_data_block_offset: u64,
pub comparator_class_name: String,
pub compression_codec: CompressionCodec,
}
impl HFileTrailer {
pub fn read(file_bytes: &[u8]) -> Result<Self> {
let file_size = file_bytes.len();
if file_size < TRAILER_SIZE {
return Err(HFileError::InvalidFormat(format!(
"File too small for HFile trailer: {file_size} bytes, need at least {TRAILER_SIZE}"
)));
}
let trailer_start = file_size - TRAILER_SIZE;
let trailer_bytes = &file_bytes[trailer_start..];
HFileBlockType::Trailer.check_magic(trailer_bytes)?;
let version_bytes = &trailer_bytes[TRAILER_SIZE - 4..];
let minor_version = version_bytes[0] as u32;
let major_version = ((version_bytes[1] as u32) << 16)
| ((version_bytes[2] as u32) << 8)
| (version_bytes[3] as u32);
if major_version != 3 {
return Err(HFileError::UnsupportedVersion {
major: major_version,
minor: minor_version,
});
}
let proto_start = MAGIC_LENGTH;
let proto_end = TRAILER_SIZE - 4;
let proto_bytes = &trailer_bytes[proto_start..proto_end];
let proto = Self::parse_length_delimited_proto(proto_bytes)?;
let compression_codec = proto
.compression_codec
.map(CompressionCodec::from_id)
.transpose()?
.unwrap_or_default();
Ok(Self {
major_version,
minor_version,
file_info_offset: proto.file_info_offset.unwrap_or(0),
load_on_open_data_offset: proto.load_on_open_data_offset.unwrap_or(0),
uncompressed_data_index_size: proto.uncompressed_data_index_size.unwrap_or(0),
total_uncompressed_bytes: proto.total_uncompressed_bytes.unwrap_or(0),
data_index_count: proto.data_index_count.unwrap_or(0),
meta_index_count: proto.meta_index_count.unwrap_or(0),
entry_count: proto.entry_count.unwrap_or(0),
num_data_index_levels: proto.num_data_index_levels.unwrap_or(1),
first_data_block_offset: proto.first_data_block_offset.unwrap_or(0),
last_data_block_offset: proto.last_data_block_offset.unwrap_or(0),
comparator_class_name: proto.comparator_class_name.unwrap_or_default(),
compression_codec,
})
}
fn parse_length_delimited_proto(bytes: &[u8]) -> Result<TrailerProto> {
let (length, consumed) = read_varint(bytes);
let proto_bytes = &bytes[consumed..consumed + length as usize];
TrailerProto::decode(proto_bytes).map_err(HFileError::from)
}
}
fn read_varint(bytes: &[u8]) -> (u64, usize) {
let mut result: u64 = 0;
let mut shift = 0;
let mut pos = 0;
while pos < bytes.len() {
let b = bytes[pos] as u64;
pos += 1;
result |= (b & 0x7F) << shift;
if b & 0x80 == 0 {
break;
}
shift += 7;
}
(result, pos)
}