use crate::decompress::WINDOW_LOG_MAX;
use crate::error::Error;
pub(crate) struct FrameHeader {
pub content_size: Option<u64>,
pub window_size: u64,
pub dict_id: u32,
pub has_checksum: bool,
pub header_len: usize,
}
pub(crate) fn parse(src: &[u8], window_log_max: u32) -> Result<FrameHeader, Error> {
let max_log = u64::from(window_log_max.min(WINDOW_LOG_MAX));
let descriptor = *src.first().ok_or(Error::SrcSizeWrong)?;
let dict_id_flag = (descriptor & 3) as usize;
let has_checksum = descriptor & 0x04 != 0;
if descriptor & 0x08 != 0 {
return Err(Error::FrameHeaderInvalid("reserved descriptor bit set"));
}
let single_segment = descriptor & 0x20 != 0;
let fcs_flag = (descriptor >> 6) as usize;
let mut pos = 1usize;
let mut window_size = 0u64;
if !single_segment {
let wd = u64::from(*src.get(pos).ok_or(Error::SrcSizeWrong)?);
pos += 1;
let exponent = wd >> 3;
let mantissa = wd & 7;
if 10 + exponent > max_log {
return Err(Error::WindowTooLarge);
}
let base = 1u64 << (10 + exponent);
window_size = base + (base >> 3) * mantissa;
}
let dict_id_len = [0usize, 1, 2, 4][dict_id_flag];
let dict_id = read_le(src, &mut pos, dict_id_len)? as u32;
let fcs_len = if single_segment {
[1usize, 2, 4, 8][fcs_flag]
} else {
[0usize, 2, 4, 8][fcs_flag]
};
let content_size = if fcs_len == 0 {
None
} else {
let raw = read_le(src, &mut pos, fcs_len)?;
Some(if fcs_len == 2 { raw + 256 } else { raw })
};
if single_segment {
window_size = content_size.expect("single-segment frames always carry a content size");
if window_size > 1u64 << max_log {
return Err(Error::WindowTooLarge);
}
}
Ok(FrameHeader {
content_size,
window_size,
dict_id,
has_checksum,
header_len: pos,
})
}
fn read_le(src: &[u8], pos: &mut usize, n: usize) -> Result<u64, Error> {
let bytes = src.get(*pos..*pos + n).ok_or(Error::SrcSizeWrong)?;
*pos += n;
let mut v = 0u64;
for (i, &b) in bytes.iter().enumerate() {
v |= u64::from(b) << (8 * i);
}
Ok(v)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_segment_with_one_byte_fcs() {
let h = parse(&[0x20, 0x05], WINDOW_LOG_MAX).unwrap();
assert_eq!(h.content_size, Some(5));
assert_eq!(h.window_size, 5);
assert_eq!(h.dict_id, 0);
assert!(!h.has_checksum);
assert_eq!(h.header_len, 2);
}
#[test]
fn windowed_with_checksum() {
let h = parse(&[0x04, 0x08], WINDOW_LOG_MAX).unwrap();
assert_eq!(h.content_size, None);
assert_eq!(h.window_size, 2048);
assert!(h.has_checksum);
assert_eq!(h.header_len, 2);
}
#[test]
fn two_byte_fcs_is_biased() {
let h = parse(&[0x60, 0x00, 0x01], WINDOW_LOG_MAX).unwrap();
assert_eq!(h.content_size, Some(256 + 256));
}
#[test]
fn rejects_reserved_bit() {
assert!(matches!(
parse(&[0x08, 0x00], WINDOW_LOG_MAX),
Err(Error::FrameHeaderInvalid(_))
));
}
#[test]
fn window_log_max_bounds_the_window_descriptor() {
assert!(parse(&[0x00, 0x90], 28).is_ok());
assert!(matches!(
parse(&[0x00, 0x90], 27),
Err(Error::WindowTooLarge)
));
assert!(matches!(
parse(&[0x00, 0xF8], 60),
Err(Error::WindowTooLarge)
));
}
}