use crate::error::Error;
use crate::reader::Reader;
pub const MAGIC: u32 = 0xFD2F_B528;
pub const MAGIC_SKIPPABLE_MIN: u32 = 0x184D_2A50;
pub const MAGIC_SKIPPABLE_MAX: u32 = 0x184D_2A5F;
pub const BLOCKSIZE_MAX: u32 = 128 * 1024;
pub const DEFAULT_WINDOW_MAX: u64 = 128 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameHeader {
pub window_size: u64,
pub dict_id: Option<u32>,
pub content_size: Option<u64>,
pub checksum: bool,
pub single_segment: bool,
}
impl FrameHeader {
pub fn block_size_max(self) -> u32 {
let w = self.window_size.min(u64::from(BLOCKSIZE_MAX));
w as u32
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameKind {
Zstd(FrameHeader),
Skippable {
magic: u32,
user_data_size: u32,
},
}
pub(crate) fn is_skippable_magic(m: u32) -> bool {
(MAGIC_SKIPPABLE_MIN..=MAGIC_SKIPPABLE_MAX).contains(&m)
}
pub fn get_frame_header(src: &[u8]) -> Result<FrameKind, Error> {
let mut r = Reader::new(src);
parse_kind(&mut r)
}
pub(crate) fn parse_kind(r: &mut Reader<'_>) -> Result<FrameKind, Error> {
let magic = r.u32_le()?;
if magic == MAGIC {
Ok(FrameKind::Zstd(parse_zstd_header(r)?))
} else if is_skippable_magic(magic) {
let user_data_size = r.u32_le()?;
Ok(FrameKind::Skippable {
magic,
user_data_size,
})
} else {
Err(Error::BadMagic)
}
}
pub(crate) fn parse_zstd_header(r: &mut Reader<'_>) -> Result<FrameHeader, Error> {
let desc = r.u8()?;
let fcs_flag = desc >> 6;
let single_segment = (desc & 0x20) != 0;
let unused = (desc & 0x10) != 0;
let reserved = (desc & 0x08) != 0;
let checksum = (desc & 0x04) != 0;
let dict_flag = desc & 0x03;
if reserved {
return Err(Error::ReservedBitSet);
}
if unused {
return Err(Error::UnusedBitSet);
}
let window_desc = if single_segment { None } else { Some(r.u8()?) };
let dict_id = match dict_flag {
0 => None,
1 => Some(u32::from(r.u8()?)),
2 => Some(u32::from(r.u16_le()?)),
3 => Some(r.u32_le()?),
_ => return Err(Error::BadMagic),
};
let content_size = match fcs_flag {
0 if single_segment => Some(u64::from(r.u8()?)),
0 => None,
1 => Some(u64::from(r.u16_le()?) + 256),
2 => Some(u64::from(r.u32_le()?)),
3 => Some(r.u64_le()?),
_ => return Err(Error::BadMagic),
};
let window_size = if single_segment {
content_size.unwrap_or(0)
} else {
window_size_from_desc(window_desc.unwrap_or(0))?
};
Ok(FrameHeader {
window_size,
dict_id,
content_size,
checksum,
single_segment,
})
}
fn window_size_from_desc(desc: u8) -> Result<u64, Error> {
let exponent = u32::from(desc >> 3);
let mantissa = u32::from(desc & 7);
let window_log = 10u32.saturating_add(exponent);
if window_log >= 64 {
return Err(Error::WindowTooLarge);
}
let base = 1u64 << window_log;
let extra = u64::from(mantissa) << (window_log.saturating_sub(3));
Ok(base.saturating_add(extra))
}
#[cfg(test)]
mod tests {
use super::*;
const A_NC: &[u8] = &[0x28, 0xB5, 0x2F, 0xFD, 0x20, 0x01, 0x09, 0x00, 0x00, 0x61];
#[test]
fn parses_c_a_no_check() {
match get_frame_header(A_NC).unwrap() {
FrameKind::Zstd(h) => {
assert!(h.single_segment);
assert!(!h.checksum);
assert_eq!(h.content_size, Some(1));
assert_eq!(h.window_size, 1);
assert_eq!(h.dict_id, None);
assert_eq!(h.block_size_max(), 1);
}
other => panic!("unexpected {other:?}"),
}
}
#[test]
fn skippable_header() {
let src = [
0x50, 0x2A, 0x4D, 0x18, 0x04, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF,
];
match get_frame_header(&src).unwrap() {
FrameKind::Skippable {
magic,
user_data_size,
} => {
assert_eq!(magic, 0x184D_2A50);
assert_eq!(user_data_size, 4);
}
other => panic!("unexpected {other:?}"),
}
}
#[test]
fn bad_magic() {
assert_eq!(
get_frame_header(&[0, 1, 2, 3]).unwrap_err(),
Error::BadMagic
);
}
}