use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionMode {
Binary = 0,
ASCII = 1,
}
impl CompressionMode {
pub fn from_u8(value: u8) -> Result<Self> {
match value {
0 => Ok(CompressionMode::Binary),
1 => Ok(CompressionMode::ASCII),
_ => Err(PkLibError::InvalidCompressionMode(value)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DictionarySize {
Size1K = 1024,
Size2K = 2048,
Size4K = 4096,
}
impl DictionarySize {
pub fn bits(&self) -> u8 {
match self {
DictionarySize::Size1K => 4, DictionarySize::Size2K => 5, DictionarySize::Size4K => 6, }
}
pub fn mask(&self) -> u32 {
(*self as u32) - 1
}
pub fn from_bits(bits: u8) -> Result<Self> {
match bits {
4 => Ok(DictionarySize::Size1K),
5 => Ok(DictionarySize::Size2K),
6 => Ok(DictionarySize::Size4K),
_ => Err(PkLibError::InvalidDictionaryBits(bits)),
}
}
pub fn from_bytes(bytes: u32) -> Result<Self> {
match bytes {
1024 => Ok(DictionarySize::Size1K),
2048 => Ok(DictionarySize::Size2K),
4096 => Ok(DictionarySize::Size4K),
_ => Err(PkLibError::InvalidDictionarySize(bytes)),
}
}
}
#[derive(Debug, Error)]
pub enum PkLibError {
#[error("Invalid compression mode: {0}")]
InvalidCompressionMode(u8),
#[error("Invalid dictionary bits: {0} (expected 4, 5, or 6)")]
InvalidDictionaryBits(u8),
#[error("Invalid dictionary size: {0} (expected 1024, 2048, or 4096)")]
InvalidDictionarySize(u32),
#[error("Invalid compressed data format")]
InvalidFormat,
#[error("Unexpected end of input")]
UnexpectedEof,
#[error("Output buffer too small")]
BufferTooSmall,
#[error("Invalid length encoding: {0}")]
InvalidLength(u32),
#[error("Invalid distance encoding: {0}")]
InvalidDistance(u32),
#[error("Invalid data: {0}")]
InvalidData(String),
#[error("Decompression error: {0}")]
DecompressionError(String),
#[error("CRC32 checksum mismatch: expected {expected:08X}, got {actual:08X}")]
CrcMismatch {
expected: u32,
actual: u32,
},
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
}
pub type Result<T> = std::result::Result<T, PkLibError>;
pub const MAX_REP_LENGTH: u32 = 0x204;
pub const MAX_WINDOW_SIZE: usize = 0x3000;
pub const WORK_BUFF_SIZE: usize = 0x2204;
pub const OUT_BUFF_SIZE: usize = 0x802;
pub const IN_BUFF_SIZE: usize = 0x800;
pub const HASH_TABLE_SIZE: usize = 0x900;
pub const MIN_MATCH_LENGTH: usize = 3;
pub const PKLIB_SIGNATURE: u32 = 0x00088B1F;
#[derive(Debug, Clone, Copy)]
pub struct CompressionHeader {
pub mode: CompressionMode,
pub dict_size: DictionarySize,
pub uncompressed_size: Option<u32>,
pub crc32: Option<u32>,
}
#[derive(Debug, Default, Clone)]
pub struct CompressionStats {
pub literal_count: usize,
pub match_count: usize,
pub bytes_processed: usize,
pub longest_match: usize,
pub input_bytes: u64,
pub output_bytes: u64,
pub compression_ratio: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compression_mode() {
assert_eq!(
CompressionMode::from_u8(0).unwrap(),
CompressionMode::Binary
);
assert_eq!(CompressionMode::from_u8(1).unwrap(), CompressionMode::ASCII);
assert!(CompressionMode::from_u8(2).is_err());
}
#[test]
fn test_dictionary_size() {
assert_eq!(DictionarySize::Size1K.bits(), 4);
assert_eq!(DictionarySize::Size2K.bits(), 5);
assert_eq!(DictionarySize::Size4K.bits(), 6);
assert_eq!(DictionarySize::Size1K.mask(), 0x3FF);
assert_eq!(DictionarySize::Size2K.mask(), 0x7FF);
assert_eq!(DictionarySize::Size4K.mask(), 0xFFF);
assert_eq!(
DictionarySize::from_bits(4).unwrap(),
DictionarySize::Size1K
);
assert_eq!(
DictionarySize::from_bits(5).unwrap(),
DictionarySize::Size2K
);
assert_eq!(
DictionarySize::from_bits(6).unwrap(),
DictionarySize::Size4K
);
assert!(DictionarySize::from_bits(7).is_err());
assert_eq!(
DictionarySize::from_bytes(1024).unwrap(),
DictionarySize::Size1K
);
assert_eq!(
DictionarySize::from_bytes(2048).unwrap(),
DictionarySize::Size2K
);
assert_eq!(
DictionarySize::from_bytes(4096).unwrap(),
DictionarySize::Size4K
);
assert!(DictionarySize::from_bytes(512).is_err());
}
#[test]
fn test_constants() {
assert_eq!(MAX_REP_LENGTH, 516);
assert_eq!(MAX_WINDOW_SIZE, 0x3000);
assert_eq!(WORK_BUFF_SIZE, 0x2204);
assert_eq!(OUT_BUFF_SIZE, 0x802);
assert_eq!(IN_BUFF_SIZE, 0x800);
assert_eq!(HASH_TABLE_SIZE, 0x900);
}
}