use hex::FromHexError;
use std::fmt::{Debug, Display, Formatter};
#[derive(Debug)]
pub enum OutOfRangeError {
CompressionMethod(String),
CompressionInfo(String),
CompressionLevel(String),
}
#[repr(C)]
pub struct ZlibHeader {
pub cmf: u8,
pub flg: u8,
}
impl ZlibHeader {
pub fn new(cm: u8, cinfo: u8, fdict: bool, flevel: u8) -> Result<Self, OutOfRangeError> {
let mut header = Self { cmf: 0, flg: 0 };
header.set_cm(cm)?;
header.set_cinfo(cinfo)?;
header.set_fdict(fdict);
header.set_flevel(flevel)?;
header.set_fcheck::<false>();
Ok(header)
}
pub fn from_hex(input: &str) -> Result<Self, FromHexError> {
if input.len() != 4 {
return Err(FromHexError::InvalidStringLength);
}
let bytes = hex::decode(input)?;
let header = Self {
cmf: bytes[0],
flg: bytes[1],
};
Ok(header)
}
pub fn get_cm(&self) -> u8 {
self.cmf & 0b0000_1111
}
pub fn set_cm(&mut self, cm: u8) -> Result<(), OutOfRangeError> {
if cm > 15 {
let msg = format!("cm was {}, but must be between 0 and 15", cm);
return Err(OutOfRangeError::CompressionInfo(msg));
}
self.cmf = (self.cmf & 0b1111_0000) | cm;
Ok(())
}
pub fn get_cm_str(&self) -> &str {
match self.get_cm() {
8 => "DEFLATE",
_ => "UNDEFINED",
}
}
pub fn get_cinfo(&self) -> u8 {
self.cmf >> 4
}
pub fn set_cinfo(&mut self, cinfo: u8) -> Result<(), OutOfRangeError> {
if cinfo > 15 {
let msg = format!("cinfo was {}, but must be between 0 and 15", cinfo);
return Err(OutOfRangeError::CompressionInfo(msg));
}
self.cmf = (self.cmf & 0b0000_1111) | cinfo << 4;
Ok(())
}
pub fn get_window_size(&self) -> u32 {
2u32.pow(self.get_cinfo() as u32 + 8)
}
pub fn get_fcheck(&self) -> u8 {
self.flg & 0b0001_1111
}
pub fn set_fcheck<const CLEAN: bool>(&mut self) {
let clean_flg = match CLEAN {
false => self.flg,
true => self.flg & 0b1110_0000,
};
let clean_header: u16 = ((self.cmf as u16) << 8) + clean_flg as u16;
let fcheck = 31 - clean_header % 31;
self.flg = clean_flg | fcheck as u8;
}
pub fn is_valid(&self) -> bool {
(self.cmf as usize * 256 + self.flg as usize) % 31 == 0
}
pub fn is_valid_strict(&self) -> bool {
let is_valid = self.is_valid();
let is_deflate = self.get_cm() == 8;
let cinfo = self.get_cinfo();
let is_valid_cinfo = cinfo <= 7;
is_valid && is_deflate && is_valid_cinfo
}
pub fn get_fdict(&self) -> bool {
(self.flg >> 5) & 1 == 1
}
pub fn set_fdict(&mut self, fdict: bool) {
let mask: u8 = 0b1101_1111;
self.flg = (self.flg & mask) | if fdict { !mask } else { 0 };
}
pub fn get_flevel(&self) -> u8 {
self.flg >> 6
}
pub fn set_flevel(&mut self, flevel: u8) -> Result<(), OutOfRangeError> {
if flevel > 3 {
let msg = format!("flevel was {}, but must be between 0 and 3", flevel);
return Err(OutOfRangeError::CompressionInfo(msg));
}
self.flg = (self.flg & 0b0011_1111) | (flevel << 6);
Ok(())
}
pub fn get_flevel_str(&self) -> &str {
match self.get_flevel() {
0 => "fastest",
1 => "fast",
2 => "default",
3 => "best",
_ => unreachable!("only has 2 bits"),
}
}
}
impl PartialEq<[u8; 2]> for ZlibHeader {
fn eq(&self, slice: &[u8; 2]) -> bool {
self.cmf == slice[0] && self.flg == slice[1]
}
}
impl PartialEq<ZlibHeader> for [u8; 2] {
fn eq(&self, header: &ZlibHeader) -> bool {
self[0] == header.cmf && self[1] == header.flg
}
}
impl PartialEq<&[u8]> for ZlibHeader {
fn eq(&self, slice: &&[u8]) -> bool {
if slice.len() != 2 {
panic!("u8 slice must be exactly 2 Bytes when comparing ZlibHeader");
}
self.cmf == slice[0] && self.flg == slice[1]
}
}
impl PartialEq<ZlibHeader> for &[u8] {
fn eq(&self, header: &ZlibHeader) -> bool {
if self.len() != 2 {
panic!("u8 slice must be exactly 2 Bytes when comparing ZlibHeader");
}
self[0] == header.cmf && self[1] == header.flg
}
}
impl From<[u8; 2]> for ZlibHeader {
fn from(bytes: [u8; 2]) -> Self {
Self {
cmf: bytes[0],
flg: bytes[1],
}
}
}
impl From<ZlibHeader> for [u8; 2] {
fn from(header: ZlibHeader) -> Self {
[header.cmf, header.flg]
}
}
impl Default for ZlibHeader {
fn default() -> Self {
Self {
cmf: 0x78,
flg: 0x9C,
}
}
}
impl Display for ZlibHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!("{:02X}{:02X}", self.cmf, self.flg))
}
}
impl Debug for ZlibHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let cm = self.get_cm_str();
let window_size = self.get_window_size();
let flevel = self.get_flevel_str();
let fdict = self.get_fdict();
let validity = if self.is_valid() { "valid" } else { "invalid" };
let str = &format!(
"ZlibHeader {{ {} | {} Bytes | {} | Dictionary: {} | {} }}",
cm, window_size, flevel, fdict, validity
);
f.write_str(str)
}
}