use std::io::Read;
use flate2::read::ZlibDecoder;
use flate2::{Decompress, FlushDecompress, Status};
use crate::error::{Error, Result};
pub fn decompress_into(data: &[u8], out: &mut Vec<u8>) -> Result<()> {
decompress_with_hint_into(data, None, out)
}
pub fn decompress_with_hint_into(
data: &[u8],
expected_len: Option<usize>,
out: &mut Vec<u8>,
) -> Result<()> {
let mut decoded = Vec::new();
if let Some(expected_len) = expected_len {
decoded.try_reserve_exact(expected_len).map_err(|err| {
Error::InvalidFormat(format!(
"deflate decompression output allocation failed: {err}"
))
})?;
}
let mut decoder = ZlibDecoder::new(data);
decoder
.read_to_end(&mut decoded)
.map_err(|e| Error::InvalidFormat(format!("deflate decompression failed: {e}")))?;
if decoder.total_in() != data.len() as u64 {
return Err(Error::InvalidFormat(
"deflate decompression left trailing input bytes".into(),
));
}
out.extend_from_slice(&decoded);
Ok(())
}
pub fn decompress_exact_into(data: &[u8], out: &mut [u8]) -> Result<()> {
let mut decoded = vec![0; out.len()];
let mut decoder = Decompress::new(true);
let status = decoder
.decompress(data, &mut decoded, FlushDecompress::Finish)
.map_err(|e| Error::InvalidFormat(format!("deflate decompression failed: {e}")))?;
if status != Status::StreamEnd {
if decoder.total_out() == out.len() as u64 {
return Err(Error::InvalidFormat(
"deflate decompression produced more bytes than expected".into(),
));
}
return Err(Error::InvalidFormat(
"deflate decompression ended before zlib stream end".into(),
));
}
if decoder.total_out() != out.len() as u64 {
return Err(Error::InvalidFormat(format!(
"deflate decompression output length mismatch: expected {}, got {}",
out.len(),
decoder.total_out()
)));
}
if decoder.total_in() != data.len() as u64 {
return Err(Error::InvalidFormat(
"deflate decompression left trailing input bytes".into(),
));
}
out.copy_from_slice(&decoded);
Ok(())
}
pub fn compress_into(data: &[u8], level: u32, out: &mut Vec<u8>) -> Result<()> {
use flate2::write::ZlibEncoder;
use flate2::Compression;
use std::io::Write;
let mut encoder = ZlibEncoder::new(out, Compression::new(level));
encoder
.write_all(data)
.map_err(|e| Error::InvalidFormat(format!("deflate compression failed: {e}")))?;
encoder
.finish()
.map_err(|e| Error::InvalidFormat(format!("deflate compression finish failed: {e}")))?;
Ok(())
}
pub fn filter_deflate_into(
data: &[u8],
level: u32,
reverse: bool,
out: &mut Vec<u8>,
) -> Result<()> {
if reverse {
decompress_into(data, out)
} else {
compress_into(data, level, out)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deflate_vec_decode_preserves_output_on_trailing_input() {
let mut encoded = Vec::new();
compress_into(b"payload", 6, &mut encoded).unwrap();
encoded.extend_from_slice(b"trailing");
let mut out = b"stale".to_vec();
let err = decompress_into(&encoded, &mut out).unwrap_err();
assert!(
err.to_string()
.contains("deflate decompression left trailing input bytes"),
"unexpected error: {err}"
);
assert_eq!(out, b"stale");
}
#[test]
fn deflate_hint_allocation_failure_returns_error() {
let mut out = b"stale".to_vec();
let err = decompress_with_hint_into(b"not zlib", Some(usize::MAX), &mut out).unwrap_err();
assert!(
err.to_string()
.contains("deflate decompression output allocation failed"),
"unexpected error: {err}"
);
assert_eq!(out, b"stale");
}
#[test]
fn deflate_exact_decode_preserves_output_on_size_error() {
let mut encoded = Vec::new();
compress_into(b"payload", 6, &mut encoded).unwrap();
let mut out = *b"stale!";
let err = decompress_exact_into(&encoded, &mut out).unwrap_err();
assert!(
err.to_string()
.contains("deflate decompression produced more bytes than expected"),
"unexpected error: {err}"
);
assert_eq!(&out, b"stale!");
}
}