use alloc::vec::Vec;
use compcol::zlib::Zlib;
use crate::error::{Error, Result};
pub fn deflate(data: &[u8]) -> Result<Vec<u8>> {
Ok(compcol::vec::compress_to_vec::<Zlib>(data)?)
}
pub fn inflate(data: &[u8]) -> Result<Vec<u8>> {
Ok(compcol::vec::decompress_to_vec::<Zlib>(data)?)
}
pub fn inflate_capped(data: &[u8], max: usize) -> Result<Vec<u8>> {
inflate_counted(data, max).map(|(out, _consumed)| out)
}
pub fn inflate_exact(data: &[u8], max: usize) -> Result<(Vec<u8>, usize)> {
let (out, over) = inflate_counted(data, max)?;
const MAX_OVERREAD: usize = 64;
let floor = over.saturating_sub(MAX_OVERREAD);
let mut exact = over;
let mut len = over;
while len > floor && len > 0 {
let cand = len - 1;
match inflate_counted(&data[..cand], max) {
Ok((o, _)) if o == out => {
exact = cand;
len = cand;
}
_ => break,
}
}
Ok((out, exact))
}
pub fn inflate_counted(data: &[u8], max: usize) -> Result<(Vec<u8>, usize)> {
use compcol::zlib::Decoder as ZlibDecoder;
use compcol::{Decoder, Status};
let mut dec = ZlibDecoder::new();
let mut out = Vec::new();
let mut scratch = [0u8; 16 * 1024];
let mut consumed = 0usize;
loop {
let (progress, status) = dec
.decode(&data[consumed..], &mut scratch)
.map_err(map_err)?;
out.extend_from_slice(&scratch[..progress.written]);
consumed += progress.consumed;
if out.len() > max {
return Err(Error::Compression("inflate exceeded declared size".into()));
}
match status {
Status::StreamEnd => return Ok((out, consumed)),
Status::InputEmpty => break,
Status::OutputFull => {
if progress.written == 0 && progress.consumed == 0 {
break;
}
}
}
}
loop {
let (progress, status) = dec.finish(&mut scratch).map_err(map_err)?;
out.extend_from_slice(&scratch[..progress.written]);
if out.len() > max {
return Err(Error::Compression("inflate exceeded declared size".into()));
}
match status {
Status::StreamEnd => return Ok((out, consumed)),
_ if progress.written == 0 => {
return Err(Error::Compression("truncated zlib stream".into()));
}
_ => continue,
}
}
}
fn map_err(e: compcol::Error) -> Error {
use alloc::format;
Error::Compression(format!("{e:?}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip() {
let msg = b"the quick brown fox jumps over the lazy dog";
let z = deflate(msg).unwrap();
assert_eq!(inflate(&z).unwrap(), msg);
assert_eq!(inflate_capped(&z, msg.len()).unwrap(), msg);
}
#[test]
fn cap_is_enforced() {
let z = deflate(&[7u8; 4096]).unwrap();
assert!(inflate_capped(&z, 16).is_err());
}
#[test]
fn exact_cap_with_trailing_bytes() {
for payload in [&b""[..], &b"hello\n"[..], &[9u8; 5000][..]] {
let mut z = deflate(payload).unwrap();
z.extend_from_slice(b"TRAILING-PACK-BYTES-AND-CHECKSUM");
let got = inflate_capped(&z, payload.len()).unwrap();
assert_eq!(got, payload, "exact-cap decode for len {}", payload.len());
}
}
#[test]
fn over_cap_by_one_is_rejected() {
let z = deflate(b"hello\n").unwrap();
assert!(inflate_capped(&z, 5).is_err()); }
#[test]
fn large_output_drains_correctly() {
let payload: Vec<u8> = (0..200_000u32).map(|i| (i % 251) as u8).collect();
let mut z = deflate(&payload).unwrap();
z.extend_from_slice(b"TRAILER");
assert_eq!(inflate_capped(&z, payload.len()).unwrap(), payload);
}
#[test]
fn inflate_exact_reports_true_length() {
for payload in [&b""[..], &b"hi\n"[..], &[5u8; 1000][..], &[0u8; 70_000][..]] {
let stream = deflate(payload).unwrap();
let exact = stream.len();
let (out, n) = inflate_exact(&stream, payload.len()).unwrap();
assert_eq!(out, payload);
assert_eq!(n, exact, "no-trailer exact for len {}", payload.len());
let mut with_trailer = stream.clone();
with_trailer.extend_from_slice(b"NEXTOBJECTHEADER....");
let (out2, n2) = inflate_exact(&with_trailer, payload.len()).unwrap();
assert_eq!(out2, payload);
assert_eq!(n2, exact, "with-trailer exact for len {}", payload.len());
}
}
}