use crate::deflate::{Deflater, deflate};
use crate::inflate::{Inflater, inflate};
use oxiarc_core::error::{OxiArcError, Result};
pub const MAX_DICTIONARY_SIZE: usize = 32768;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ZlibLevel {
Fastest = 0,
Fast = 1,
Default = 2,
Maximum = 3,
}
impl ZlibLevel {
fn from_level(level: u8) -> Self {
match level {
0..=2 => Self::Fastest,
3..=5 => Self::Fast,
6 => Self::Default,
7..=9 => Self::Maximum,
_ => Self::Default,
}
}
}
#[derive(Clone, Debug)]
pub struct Adler32 {
a: u32,
b: u32,
}
const ADLER_MOD: u32 = 65521;
const NMAX: usize = 5552;
impl Adler32 {
pub fn new() -> Self {
Self { a: 1, b: 0 }
}
pub fn update(&mut self, data: &[u8]) {
let mut a = self.a;
let mut b = self.b;
let mut remaining = data;
while remaining.len() >= NMAX {
let (chunk, rest) = remaining.split_at(NMAX);
remaining = rest;
for &byte in chunk {
a += byte as u32;
b += a;
}
a %= ADLER_MOD;
b %= ADLER_MOD;
}
for &byte in remaining {
a += byte as u32;
b += a;
}
self.a = a % ADLER_MOD;
self.b = b % ADLER_MOD;
}
pub fn finish(&self) -> u32 {
(self.b << 16) | self.a
}
pub fn checksum(data: &[u8]) -> u32 {
let mut adler = Self::new();
adler.update(data);
adler.finish()
}
}
impl Default for Adler32 {
fn default() -> Self {
Self::new()
}
}
pub fn zlib_compress(input: &[u8], level: u8) -> Result<Vec<u8>> {
let level = level.min(9);
let compressed = deflate(input, level)?;
let mut output = Vec::with_capacity(6 + compressed.len());
let cmf: u8 = 0x78;
let flevel = ZlibLevel::from_level(level) as u8;
let fdict = 0u8; let fcheck = {
let base = (cmf as u16) * 256 + ((flevel << 6) | (fdict << 5)) as u16;
let remainder = base % 31;
if remainder == 0 {
0
} else {
(31 - remainder) as u8
}
};
let flg = (flevel << 6) | (fdict << 5) | fcheck;
output.push(cmf);
output.push(flg);
output.extend_from_slice(&compressed);
let checksum = Adler32::checksum(input);
output.push((checksum >> 24) as u8);
output.push((checksum >> 16) as u8);
output.push((checksum >> 8) as u8);
output.push(checksum as u8);
Ok(output)
}
pub fn zlib_compress_with_dict(input: &[u8], level: u8, dictionary: &[u8]) -> Result<Vec<u8>> {
let level = level.min(9);
let mut deflater = Deflater::with_dictionary(level, dictionary);
let compressed = deflater.compress_to_vec(input)?;
let dict_checksum = deflater
.dictionary_checksum()
.unwrap_or_else(|| Adler32::checksum(dictionary));
let mut output = Vec::with_capacity(10 + compressed.len());
let cmf: u8 = 0x78;
let flevel = ZlibLevel::from_level(level) as u8;
let fdict = 1u8; let fcheck = {
let base = (cmf as u16) * 256 + ((flevel << 6) | (fdict << 5)) as u16;
let remainder = base % 31;
if remainder == 0 {
0
} else {
(31 - remainder) as u8
}
};
let flg = (flevel << 6) | (fdict << 5) | fcheck;
output.push(cmf);
output.push(flg);
output.push((dict_checksum >> 24) as u8);
output.push((dict_checksum >> 16) as u8);
output.push((dict_checksum >> 8) as u8);
output.push(dict_checksum as u8);
output.extend_from_slice(&compressed);
let checksum = Adler32::checksum(input);
output.push((checksum >> 24) as u8);
output.push((checksum >> 16) as u8);
output.push((checksum >> 8) as u8);
output.push(checksum as u8);
Ok(output)
}
pub fn zlib_decompress(input: &[u8]) -> Result<Vec<u8>> {
if input.len() < 6 {
return Err(OxiArcError::invalid_header("zlib data too short"));
}
let cmf = input[0];
let flg = input[1];
let cm = cmf & 0x0F;
if cm != 8 {
return Err(OxiArcError::invalid_header(
"unsupported compression method",
));
}
let cinfo = cmf >> 4;
if cinfo > 7 {
return Err(OxiArcError::invalid_header("invalid window size"));
}
let check = (cmf as u16) * 256 + (flg as u16);
if check % 31 != 0 {
return Err(OxiArcError::invalid_header("zlib header check failed"));
}
let fdict = (flg >> 5) & 1;
if fdict != 0 {
return Err(OxiArcError::unsupported_method(
"preset dictionary required - use zlib_decompress_with_dict",
));
}
let deflate_data = &input[2..input.len() - 4];
let decompressed = inflate(deflate_data)?;
let stored_checksum = u32::from_be_bytes([
input[input.len() - 4],
input[input.len() - 3],
input[input.len() - 2],
input[input.len() - 1],
]);
let computed_checksum = Adler32::checksum(&decompressed);
if stored_checksum != computed_checksum {
return Err(OxiArcError::crc_mismatch(
computed_checksum,
stored_checksum,
));
}
Ok(decompressed)
}
pub fn zlib_decompress_with_dict(input: &[u8], dictionary: &[u8]) -> Result<Vec<u8>> {
if input.len() < 10 {
return Err(OxiArcError::invalid_header(
"zlib data with dictionary too short",
));
}
let cmf = input[0];
let flg = input[1];
let cm = cmf & 0x0F;
if cm != 8 {
return Err(OxiArcError::invalid_header(
"unsupported compression method",
));
}
let cinfo = cmf >> 4;
if cinfo > 7 {
return Err(OxiArcError::invalid_header("invalid window size"));
}
let check = (cmf as u16) * 256 + (flg as u16);
if check % 31 != 0 {
return Err(OxiArcError::invalid_header("zlib header check failed"));
}
let fdict = (flg >> 5) & 1;
let deflate_start = if fdict != 0 {
let stored_dict_checksum = u32::from_be_bytes([input[2], input[3], input[4], input[5]]);
let computed_dict_checksum = Adler32::checksum(dictionary);
if stored_dict_checksum != computed_dict_checksum {
return Err(OxiArcError::crc_mismatch(
computed_dict_checksum,
stored_dict_checksum,
));
}
6 } else {
2 };
let deflate_data = &input[deflate_start..input.len() - 4];
let mut inflater = Inflater::with_dictionary(dictionary);
let mut cursor = std::io::Cursor::new(deflate_data);
let decompressed = inflater.inflate_reader(&mut cursor)?;
let stored_checksum = u32::from_be_bytes([
input[input.len() - 4],
input[input.len() - 3],
input[input.len() - 2],
input[input.len() - 1],
]);
let computed_checksum = Adler32::checksum(&decompressed);
if stored_checksum != computed_checksum {
return Err(OxiArcError::crc_mismatch(
computed_checksum,
stored_checksum,
));
}
Ok(decompressed)
}
pub fn zlib_requires_dictionary(input: &[u8]) -> Option<u32> {
if input.len() < 6 {
return None;
}
let flg = input[1];
let fdict = (flg >> 5) & 1;
if fdict != 0 && input.len() >= 6 {
Some(u32::from_be_bytes([input[2], input[3], input[4], input[5]]))
} else {
None
}
}
#[derive(Debug)]
pub struct ZlibCompressor {
level: u8,
buffer: Vec<u8>,
finished: bool,
}
impl ZlibCompressor {
pub fn new(level: u8) -> Self {
Self {
level: level.min(9),
buffer: Vec::new(),
finished: false,
}
}
pub fn write(&mut self, data: &[u8]) {
self.buffer.extend_from_slice(data);
}
pub fn finish(&mut self) -> Result<Vec<u8>> {
if self.finished {
return Ok(Vec::new());
}
self.finished = true;
zlib_compress(&self.buffer, self.level)
}
pub fn reset(&mut self) {
self.buffer.clear();
self.finished = false;
}
}
#[derive(Debug)]
pub struct ZlibDecompressor {
buffer: Vec<u8>,
finished: bool,
}
impl ZlibDecompressor {
pub fn new() -> Self {
Self {
buffer: Vec::new(),
finished: false,
}
}
pub fn write(&mut self, data: &[u8]) {
self.buffer.extend_from_slice(data);
}
pub fn finish(&mut self) -> Result<Vec<u8>> {
if self.finished {
return Ok(Vec::new());
}
self.finished = true;
zlib_decompress(&self.buffer)
}
pub fn reset(&mut self) {
self.buffer.clear();
self.finished = false;
}
}
impl Default for ZlibDecompressor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adler32_empty() {
let checksum = Adler32::checksum(&[]);
assert_eq!(checksum, 1);
}
#[test]
fn test_adler32_hello() {
let checksum = Adler32::checksum(b"Hello");
assert_eq!(checksum, 0x058C01F5);
}
#[test]
fn test_adler32_incremental() {
let data = b"Hello, World!";
let one_shot = Adler32::checksum(data);
let mut adler = Adler32::new();
adler.update(&data[..6]);
adler.update(&data[6..]);
let incremental = adler.finish();
assert_eq!(one_shot, incremental);
}
#[test]
fn test_adler32_large() {
let data = vec![0x42u8; 10000];
let mut adler = Adler32::new();
adler.update(&data);
let checksum = adler.finish();
assert_ne!(checksum, 0);
}
#[test]
fn test_zlib_header() {
let compressed = zlib_compress(b"test", 6).expect("compress failed");
assert_eq!(compressed[0], 0x78);
let cmf = compressed[0] as u16;
let flg = compressed[1] as u16;
assert_eq!((cmf * 256 + flg) % 31, 0);
}
#[test]
fn test_zlib_roundtrip_simple() {
let data = b"Hello, World!";
let compressed = zlib_compress(data, 6).expect("compress failed");
let decompressed = zlib_decompress(&compressed).expect("decompress failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_zlib_roundtrip_repeated() {
let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let compressed = zlib_compress(data, 6).expect("compress failed");
assert!(compressed.len() < data.len());
let decompressed = zlib_decompress(&compressed).expect("decompress failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_zlib_roundtrip_empty() {
let data: &[u8] = b"";
let compressed = zlib_compress(data, 6).expect("compress failed");
let decompressed = zlib_decompress(&compressed).expect("decompress failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_zlib_roundtrip_large() {
let data: Vec<u8> = (0..10000).map(|i| (i % 256) as u8).collect();
let compressed = zlib_compress(&data, 6).expect("compress failed");
let decompressed = zlib_decompress(&compressed).expect("decompress failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_zlib_levels() {
let data = b"Hello, World! Hello, World! Hello, World!";
for level in 1..=9 {
let compressed = zlib_compress(data, level)
.unwrap_or_else(|_| panic!("level {} compress failed", level));
let decompressed = zlib_decompress(&compressed)
.unwrap_or_else(|_| panic!("level {} decompress failed", level));
assert_eq!(&decompressed[..], &data[..]);
}
}
#[test]
fn test_zlib_level_0() {
let data = b"Hello, World!";
let compressed = zlib_compress(data, 0).expect("level 0 compress failed");
let decompressed = zlib_decompress(&compressed).expect("level 0 decompress failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_zlib_checksum_verification() {
let data = b"Test data for checksum";
let mut compressed = zlib_compress(data, 6).expect("compress failed");
let len = compressed.len();
compressed[len - 1] ^= 0xFF;
let result = zlib_decompress(&compressed);
assert!(result.is_err());
}
#[test]
fn test_zlib_invalid_header() {
let bad_data = [0x08, 0x1D, 0x00, 0x00, 0x00, 0x01]; let result = zlib_decompress(&bad_data);
assert!(result.is_err());
}
#[test]
fn test_zlib_too_short() {
let short_data = [0x78, 0x9C];
let result = zlib_decompress(&short_data);
assert!(result.is_err());
}
#[test]
fn test_compressor_streaming() {
let mut compressor = ZlibCompressor::new(6);
compressor.write(b"Hello, ");
compressor.write(b"World!");
let compressed = compressor.finish().expect("compress failed");
let decompressed = zlib_decompress(&compressed).expect("decompress failed");
assert_eq!(decompressed, b"Hello, World!");
}
#[test]
fn test_decompressor_streaming() {
let compressed = zlib_compress(b"Hello, World!", 6).expect("compress failed");
let mut decompressor = ZlibDecompressor::new();
decompressor.write(&compressed[..5]);
decompressor.write(&compressed[5..]);
let decompressed = decompressor.finish().expect("decompress failed");
assert_eq!(decompressed, b"Hello, World!");
}
#[test]
fn test_zlib_dictionary_roundtrip() {
let dictionary = b"Hello World common patterns repeating text";
let data = b"Hello World Hello World repeating text patterns";
let compressed =
zlib_compress_with_dict(data, 6, dictionary).expect("dictionary compress failed");
let decompressed = zlib_decompress_with_dict(&compressed, dictionary)
.expect("dictionary decompress failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_zlib_dictionary_header() {
let dictionary = b"test dictionary";
let data = b"test data";
let compressed =
zlib_compress_with_dict(data, 6, dictionary).expect("dictionary compress failed");
assert_eq!(compressed[0], 0x78);
let flg = compressed[1];
let fdict = (flg >> 5) & 1;
assert_eq!(fdict, 1, "FDICT flag should be set");
let cmf = compressed[0] as u16;
let flg = compressed[1] as u16;
assert_eq!((cmf * 256 + flg) % 31, 0, "Header check should pass");
let dict_checksum =
u32::from_be_bytes([compressed[2], compressed[3], compressed[4], compressed[5]]);
let expected_checksum = Adler32::checksum(dictionary);
assert_eq!(
dict_checksum, expected_checksum,
"Dictionary checksum mismatch"
);
}
#[test]
fn test_zlib_requires_dictionary() {
let dictionary = b"test dictionary";
let data = b"test data";
let compressed_no_dict = zlib_compress(data, 6).expect("compress failed");
assert!(
zlib_requires_dictionary(&compressed_no_dict).is_none(),
"Should not require dictionary"
);
let compressed_with_dict =
zlib_compress_with_dict(data, 6, dictionary).expect("dictionary compress failed");
let required_checksum = zlib_requires_dictionary(&compressed_with_dict);
assert!(required_checksum.is_some(), "Should require dictionary");
assert_eq!(
required_checksum.unwrap_or(0),
Adler32::checksum(dictionary),
"Dictionary checksum mismatch"
);
}
#[test]
fn test_zlib_dictionary_wrong_dict_error() {
let dictionary = b"correct dictionary";
let wrong_dictionary = b"wrong dictionary data";
let data = b"test data for compression";
let compressed =
zlib_compress_with_dict(data, 6, dictionary).expect("dictionary compress failed");
let result = zlib_decompress_with_dict(&compressed, wrong_dictionary);
assert!(result.is_err(), "Should fail with wrong dictionary");
}
#[test]
fn test_zlib_dictionary_no_dict_error() {
let dictionary = b"test dictionary";
let data = b"test data";
let compressed =
zlib_compress_with_dict(data, 6, dictionary).expect("dictionary compress failed");
let result = zlib_decompress(&compressed);
assert!(result.is_err(), "Should fail without dictionary");
}
#[test]
fn test_zlib_dictionary_better_compression() {
let dictionary = b"AAABBBCCCDDDEEE";
let data = b"AAABBBCCCDDDEEEAAABBBCCCDDDEEEAAABBBCCCDDDEEE";
let compressed_no_dict = zlib_compress(data, 6).expect("compress failed");
let compressed_with_dict =
zlib_compress_with_dict(data, 6, dictionary).expect("dictionary compress failed");
let decompressed = zlib_decompress_with_dict(&compressed_with_dict, dictionary)
.expect("decompress failed");
assert_eq!(decompressed, data);
let decompressed_no_dict = zlib_decompress(&compressed_no_dict).expect("decompress failed");
assert_eq!(decompressed_no_dict, data);
}
#[test]
fn test_zlib_dictionary_empty_data() {
let dictionary = b"test dictionary";
let data: &[u8] = b"";
let compressed =
zlib_compress_with_dict(data, 6, dictionary).expect("dictionary compress failed");
let decompressed = zlib_decompress_with_dict(&compressed, dictionary)
.expect("dictionary decompress failed");
assert!(decompressed.is_empty());
}
#[test]
fn test_zlib_dictionary_large_dict() {
let dictionary: Vec<u8> = (0..30000).map(|i| (i % 256) as u8).collect();
let data = b"Some test data that may use parts of the large dictionary";
let compressed =
zlib_compress_with_dict(data, 6, &dictionary).expect("dictionary compress failed");
let decompressed = zlib_decompress_with_dict(&compressed, &dictionary)
.expect("dictionary decompress failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_zlib_dictionary_levels() {
let dictionary = b"Hello World common patterns repeating text";
let data = b"Hello World Hello World repeating text patterns";
for level in 0..=9 {
let compressed = zlib_compress_with_dict(data, level, dictionary)
.unwrap_or_else(|_| panic!("level {} dictionary compress failed", level));
let decompressed = zlib_decompress_with_dict(&compressed, dictionary)
.unwrap_or_else(|_| panic!("level {} dictionary decompress failed", level));
assert_eq!(
&decompressed[..],
&data[..],
"Level {} roundtrip failed",
level
);
}
}
#[test]
fn test_raw_deflate_with_dictionary() {
use crate::deflate::Deflater;
use crate::inflate::Inflater;
let dictionary = b"AAABBBCCCDDDEEE";
let data = b"AAABBBCCCDDDEEEAAABBBCCC";
for level in [0u8, 1, 6] {
let mut deflater = Deflater::with_dictionary(level, dictionary);
let compressed = deflater
.compress_to_vec(data)
.unwrap_or_else(|_| panic!("level {} deflate failed", level));
let mut inflater = Inflater::with_dictionary(dictionary);
let mut cursor = std::io::Cursor::new(&compressed);
let decompressed = inflater
.inflate_reader(&mut cursor)
.unwrap_or_else(|e| panic!("level {} inflate failed: {:?}", level, e));
assert_eq!(
&decompressed[..],
&data[..],
"Level {} raw deflate roundtrip failed",
level
);
}
}
#[test]
fn test_deflate_dict_simple() {
use crate::deflate::Deflater;
use crate::inflate::Inflater;
let dictionary = b"Hello World";
let data = b"Hello World";
let mut deflater = Deflater::with_dictionary(6, dictionary);
let compressed = deflater.compress_to_vec(data).expect("deflate failed");
let mut inflater = Inflater::with_dictionary(dictionary);
let mut cursor = std::io::Cursor::new(&compressed);
let decompressed = inflater
.inflate_reader(&mut cursor)
.expect("inflate failed");
assert_eq!(&decompressed[..], &data[..]);
}
}