use haagenti_core::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockType {
Raw,
Rle,
Compressed,
Reserved,
}
impl BlockType {
pub fn from_field(field: u8) -> Result<Self> {
match field {
0 => Ok(BlockType::Raw),
1 => Ok(BlockType::Rle),
2 => Ok(BlockType::Compressed),
3 => Err(Error::corrupted("Reserved block type")),
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlockHeader {
pub last_block: bool,
pub block_type: BlockType,
pub block_size: usize,
}
impl BlockHeader {
pub const SIZE: usize = 3;
pub const MAX_BLOCK_SIZE: usize = (1 << 17) - 1;
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < Self::SIZE {
return Err(Error::corrupted(format!(
"Block header too short: {} bytes, need {}",
data.len(),
Self::SIZE
)));
}
let header = data[0] as u32 | ((data[1] as u32) << 8) | ((data[2] as u32) << 16);
let last_block = (header & 0x01) != 0;
let block_type_field = ((header >> 1) & 0x03) as u8;
let block_size = (header >> 3) as usize;
let block_type = BlockType::from_field(block_type_field)?;
if block_size > Self::MAX_BLOCK_SIZE {
return Err(Error::corrupted(format!(
"Block size {} exceeds maximum {}",
block_size,
Self::MAX_BLOCK_SIZE
)));
}
Ok(Self {
last_block,
block_type,
block_size,
})
}
pub fn compressed_size(&self) -> usize {
match self.block_type {
BlockType::Raw => self.block_size,
BlockType::Rle => 1,
BlockType::Compressed => self.block_size,
BlockType::Reserved => 0,
}
}
pub fn decompressed_size(&self) -> usize {
self.block_size
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_block_type_parsing() {
assert_eq!(BlockType::from_field(0).unwrap(), BlockType::Raw);
assert_eq!(BlockType::from_field(1).unwrap(), BlockType::Rle);
assert_eq!(BlockType::from_field(2).unwrap(), BlockType::Compressed);
assert!(BlockType::from_field(3).is_err());
}
#[test]
fn test_raw_block_header() {
let data = [0x20, 0x03, 0x00];
let header = BlockHeader::parse(&data).unwrap();
assert!(!header.last_block);
assert_eq!(header.block_type, BlockType::Raw);
assert_eq!(header.block_size, 100);
assert_eq!(header.compressed_size(), 100);
assert_eq!(header.decompressed_size(), 100);
}
#[test]
fn test_rle_block_header() {
let data = [0x43, 0x1F, 0x00];
let header = BlockHeader::parse(&data).unwrap();
assert!(header.last_block);
assert_eq!(header.block_type, BlockType::Rle);
assert_eq!(header.block_size, 1000);
assert_eq!(header.compressed_size(), 1); assert_eq!(header.decompressed_size(), 1000);
}
#[test]
fn test_compressed_block_header() {
let data = [0x84, 0x1A, 0x06];
let header = BlockHeader::parse(&data).unwrap();
assert!(!header.last_block);
assert_eq!(header.block_type, BlockType::Compressed);
assert_eq!(header.block_size, 50000);
assert_eq!(header.compressed_size(), 50000);
}
#[test]
fn test_last_block_flag() {
let data = [0x21, 0x03, 0x00];
let header = BlockHeader::parse(&data).unwrap();
assert!(header.last_block);
assert_eq!(header.block_type, BlockType::Raw);
assert_eq!(header.block_size, 100);
}
#[test]
fn test_max_block_size() {
let data = [0xF8, 0xFF, 0x0F];
let header = BlockHeader::parse(&data).unwrap();
assert_eq!(header.block_size, 131071);
assert_eq!(header.block_size, BlockHeader::MAX_BLOCK_SIZE);
}
#[test]
fn test_block_size_too_large() {
let data = [0x00, 0x00, 0x10];
let result = BlockHeader::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_reserved_block_type_error() {
let data = [0x06, 0x00, 0x00];
let result = BlockHeader::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_header_too_short() {
let result = BlockHeader::parse(&[0x00, 0x00]);
assert!(result.is_err());
}
#[test]
fn test_zero_size_block() {
let data = [0x00, 0x00, 0x00];
let header = BlockHeader::parse(&data).unwrap();
assert_eq!(header.block_size, 0);
assert_eq!(header.compressed_size(), 0);
}
}