use haagenti_core::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameDescriptor {
raw: u8,
}
impl FrameDescriptor {
pub fn new(byte: u8) -> Result<Self> {
if byte & 0x08 != 0 {
return Err(Error::corrupted(
"Reserved bit in frame descriptor must be 0",
));
}
Ok(Self { raw: byte })
}
#[inline]
pub fn frame_content_size_flag(&self) -> u8 {
(self.raw >> 6) & 0x03
}
pub fn frame_content_size_bytes(&self) -> usize {
match self.frame_content_size_flag() {
0 => {
if self.single_segment_flag() {
1 } else {
0 }
}
1 => 2,
2 => 4,
3 => 8,
_ => unreachable!(),
}
}
#[inline]
pub fn single_segment_flag(&self) -> bool {
(self.raw & 0x20) != 0
}
#[inline]
pub fn content_checksum_flag(&self) -> bool {
(self.raw & 0x04) != 0
}
#[inline]
pub fn dictionary_id_flag(&self) -> u8 {
self.raw & 0x03
}
pub fn dictionary_id_bytes(&self) -> usize {
match self.dictionary_id_flag() {
0 => 0,
1 => 1,
2 => 2,
3 => 4,
_ => unreachable!(),
}
}
#[inline]
pub fn has_window_descriptor(&self) -> bool {
!self.single_segment_flag()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FrameHeader {
pub descriptor: FrameDescriptor,
pub window_size: usize,
pub dictionary_id: u32,
pub frame_content_size: Option<u64>,
pub has_checksum: bool,
pub header_size: usize,
}
impl FrameHeader {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.is_empty() {
return Err(Error::corrupted("Empty frame header"));
}
let descriptor = FrameDescriptor::new(data[0])?;
let mut offset = 1;
let window_size = if descriptor.has_window_descriptor() {
if data.len() < offset + 1 {
return Err(Error::corrupted(
"Frame header truncated at window descriptor",
));
}
let window_byte = data[offset];
offset += 1;
Self::decode_window_size(window_byte)?
} else {
0
};
let dict_bytes = descriptor.dictionary_id_bytes();
let dictionary_id = if dict_bytes > 0 {
if data.len() < offset + dict_bytes {
return Err(Error::corrupted("Frame header truncated at dictionary ID"));
}
let dict_id = Self::read_le_uint(&data[offset..], dict_bytes)?;
offset += dict_bytes;
dict_id as u32
} else {
0
};
let fcs_bytes = descriptor.frame_content_size_bytes();
let frame_content_size = if fcs_bytes > 0 {
if data.len() < offset + fcs_bytes {
return Err(Error::corrupted(
"Frame header truncated at frame content size",
));
}
let mut fcs = Self::read_le_uint(&data[offset..], fcs_bytes)?;
if fcs_bytes == 2 {
fcs += 256;
}
offset += fcs_bytes;
Some(fcs)
} else {
None
};
let final_window_size = if descriptor.single_segment_flag() {
frame_content_size.unwrap_or(0) as usize
} else {
window_size
};
Ok(Self {
descriptor,
window_size: final_window_size,
dictionary_id,
frame_content_size,
has_checksum: descriptor.content_checksum_flag(),
header_size: 4 + offset, })
}
fn decode_window_size(byte: u8) -> Result<usize> {
let exponent = (byte >> 3) as u32;
let mantissa = (byte & 0x07) as usize;
if exponent > 41 {
return Err(Error::corrupted(format!(
"Window size exponent {} exceeds maximum",
exponent
)));
}
let window_base = 1usize << (10 + exponent);
let window_add = (window_base >> 3) * mantissa;
let window_size = window_base + window_add;
if window_size > super::MAX_WINDOW_SIZE {
return Err(Error::corrupted(format!(
"Window size {} exceeds maximum {}",
window_size,
super::MAX_WINDOW_SIZE
)));
}
Ok(window_size)
}
fn read_le_uint(data: &[u8], size: usize) -> Result<u64> {
if data.len() < size {
return Err(Error::corrupted("Insufficient data for integer"));
}
let mut result = 0u64;
for (i, &byte) in data.iter().enumerate().take(size) {
result |= (byte as u64) << (8 * i);
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_descriptor_flags() {
let desc = FrameDescriptor::new(0b11100111).unwrap();
assert_eq!(desc.frame_content_size_flag(), 3);
assert!(desc.single_segment_flag());
assert!(desc.content_checksum_flag());
assert_eq!(desc.dictionary_id_flag(), 3);
}
#[test]
fn test_frame_descriptor_reserved_bit_error() {
let result = FrameDescriptor::new(0b00001000);
assert!(result.is_err());
}
#[test]
fn test_frame_descriptor_fcs_bytes() {
let desc = FrameDescriptor::new(0b00000000).unwrap();
assert_eq!(desc.frame_content_size_bytes(), 0);
let desc = FrameDescriptor::new(0b00100000).unwrap();
assert_eq!(desc.frame_content_size_bytes(), 1);
let desc = FrameDescriptor::new(0b01000000).unwrap();
assert_eq!(desc.frame_content_size_bytes(), 2);
let desc = FrameDescriptor::new(0b10000000).unwrap();
assert_eq!(desc.frame_content_size_bytes(), 4);
let desc = FrameDescriptor::new(0b11000000).unwrap();
assert_eq!(desc.frame_content_size_bytes(), 8);
}
#[test]
fn test_frame_descriptor_dict_bytes() {
let desc = FrameDescriptor::new(0b00000000).unwrap();
assert_eq!(desc.dictionary_id_bytes(), 0);
let desc = FrameDescriptor::new(0b00000001).unwrap();
assert_eq!(desc.dictionary_id_bytes(), 1);
let desc = FrameDescriptor::new(0b00000010).unwrap();
assert_eq!(desc.dictionary_id_bytes(), 2);
let desc = FrameDescriptor::new(0b00000011).unwrap();
assert_eq!(desc.dictionary_id_bytes(), 4);
}
#[test]
fn test_window_descriptor_has() {
let desc = FrameDescriptor::new(0b00000000).unwrap();
assert!(desc.has_window_descriptor());
let desc = FrameDescriptor::new(0b00100000).unwrap();
assert!(!desc.has_window_descriptor());
}
#[test]
fn test_frame_header_minimal() {
let data = [0x20, 0x00];
let header = FrameHeader::parse(&data).unwrap();
assert!(header.descriptor.single_segment_flag());
assert_eq!(header.frame_content_size, Some(0));
assert_eq!(header.dictionary_id, 0);
assert!(!header.has_checksum);
assert_eq!(header.header_size, 4 + 2); }
#[test]
fn test_frame_header_with_window() {
let data = [0x00, 0x00];
let header = FrameHeader::parse(&data).unwrap();
assert_eq!(header.window_size, 1024);
assert_eq!(header.frame_content_size, None);
assert_eq!(header.header_size, 4 + 2);
}
#[test]
fn test_frame_header_with_dictionary() {
let data = [0x23, 0x78, 0x56, 0x34, 0x12, 0x10];
let header = FrameHeader::parse(&data).unwrap();
assert_eq!(header.dictionary_id, 0x12345678);
assert_eq!(header.frame_content_size, Some(0x10));
}
#[test]
fn test_frame_header_with_checksum() {
let data = [0x24, 0x00]; let header = FrameHeader::parse(&data).unwrap();
assert!(header.has_checksum);
}
#[test]
fn test_frame_header_2byte_fcs() {
let data = [0x60, 0x00, 0x01];
let header = FrameHeader::parse(&data).unwrap();
assert_eq!(header.frame_content_size, Some(256 + 256));
}
#[test]
fn test_frame_header_8byte_fcs() {
let data = [
0xE0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ];
let header = FrameHeader::parse(&data).unwrap();
assert_eq!(header.frame_content_size, Some(0x0807060504030201));
}
#[test]
fn test_window_size_decoding() {
assert_eq!(FrameHeader::decode_window_size(0x00).unwrap(), 1024);
assert_eq!(FrameHeader::decode_window_size(0x07).unwrap(), 1024 + 896);
assert_eq!(FrameHeader::decode_window_size(0x50).unwrap(), 1024 * 1024);
assert_eq!(
FrameHeader::decode_window_size(0x88).unwrap(),
128 * 1024 * 1024
);
}
#[test]
fn test_window_size_too_large() {
let result = FrameHeader::decode_window_size(0x90);
assert!(result.is_err());
}
#[test]
fn test_empty_header_error() {
let result = FrameHeader::parse(&[]);
assert!(result.is_err());
}
#[test]
fn test_truncated_header_error() {
let result = FrameHeader::parse(&[0x00]);
assert!(result.is_err());
}
}