use std::cmp;
use miniz_oxide::inflate::{
core::{
decompress,
inflate_flags::{TINFL_FLAG_HAS_MORE_INPUT, TINFL_FLAG_IGNORE_ADLER32},
DecompressorOxide,
},
TINFLStatus,
};
use tracing::trace;
use crate::{fsm::entry::HasMoreInput, parse::Method, Error};
use super::{DecompressOutcome, Decompressor};
pub(crate) struct DeflateDec {
internal_buffer: Vec<u8>,
out_pos: usize,
remain_in_internal_buffer: usize,
state: DecompressorOxide,
}
impl Default for DeflateDec {
fn default() -> Self {
Self {
internal_buffer: vec![0u8; Self::INTERNAL_BUFFER_LENGTH],
out_pos: 0,
state: DecompressorOxide::new(),
remain_in_internal_buffer: 0,
}
}
}
impl Decompressor for DeflateDec {
fn decompress(
&mut self,
in_buf: &[u8],
out: &mut [u8],
has_more_input: HasMoreInput,
) -> Result<DecompressOutcome, Error> {
tracing::trace!(
in_buf_len = in_buf.len(),
out_len = out.len(),
remain_in_internal_buffer = self.remain_in_internal_buffer,
out_pos = self.out_pos,
"decompress",
);
let mut outcome: DecompressOutcome = Default::default();
self.copy_to_out(out, &mut outcome);
if outcome.bytes_written > 0 {
tracing::trace!(
"returning {} bytes from internal buffer",
outcome.bytes_written
);
return Ok(outcome);
}
let mut flags = TINFL_FLAG_IGNORE_ADLER32;
if matches!(has_more_input, HasMoreInput::Yes) {
flags |= TINFL_FLAG_HAS_MORE_INPUT;
}
let (status, bytes_read, bytes_written) = decompress(
&mut self.state,
in_buf,
&mut self.internal_buffer,
self.out_pos,
flags,
);
trace!(%bytes_read, %bytes_written, ?status, "decompress returned");
outcome.bytes_read += bytes_read;
self.remain_in_internal_buffer += bytes_written;
match status {
TINFLStatus::FailedCannotMakeProgress => {
return Err(Error::Decompression { method: Method::Deflate, msg: "Failed to make progress: more input data was expected, but the caller indicated there was no more data, so the input stream is likely truncated".to_string() })
}
TINFLStatus::BadParam => {
return Err(Error::Decompression { method: Method::Deflate, msg: "The output buffer is an invalid size; consider the flags parameter".to_string() })
}
TINFLStatus::Adler32Mismatch => {
return Err(Error::Decompression { method: Method::Deflate, msg: "The decompression went fine, but the adler32 checksum did not match the one provided in the header.".to_string() })
}
TINFLStatus::Failed => {
return Err(Error::Decompression { method: Method::Deflate, msg: "Failed to decompress due to invalid data.".to_string() })
},
TINFLStatus::Done => {
},
TINFLStatus::NeedsMoreInput => {
},
TINFLStatus::HasMoreOutput => {
},
}
trace!("calling copy_to_out");
self.copy_to_out(out, &mut outcome);
Ok(outcome)
}
}
impl DeflateDec {
const INTERNAL_BUFFER_LENGTH: usize = 64 * 1024;
fn copy_to_out(&mut self, mut out: &mut [u8], outcome: &mut DecompressOutcome) {
while !out.is_empty() && self.remain_in_internal_buffer > 0 {
let copy_len = cmp::min(self.remain_in_internal_buffer, out.len());
let copy_len = cmp::min(copy_len, self.internal_buffer.len() - self.out_pos);
trace!("copying {} bytes from internal buffer to out_buf", copy_len);
out[..copy_len].copy_from_slice(&self.internal_buffer[self.out_pos..][..copy_len]);
self.out_pos += copy_len;
outcome.bytes_written += copy_len;
self.remain_in_internal_buffer -= copy_len;
out = &mut out[copy_len..];
if self.out_pos == self.internal_buffer.len() {
self.out_pos = 0;
}
}
}
}