//! Extra streaming decompression functionality. //! //! As of now this is mainly intended for use to build a higher-level wrapper. #[cfg(feature = "with-alloc")] usecrate::alloc::boxed::Box; use core::{cmp, mem};
/// Tag that determines reset policy of [InflateState](struct.InflateState.html) pubtrait ResetPolicy { /// Performs reset fn reset(&self, state: &mut InflateState);
}
/// Resets state, without performing expensive ops (e.g. zeroing buffer) /// /// Note that not zeroing buffer can lead to security issues when dealing with untrusted input. pubstruct MinReset;
/// A struct that compbines a decompressor with extra data for streaming decompression. /// #[derive(Clone)] pubstruct InflateState { /// Inner decompressor struct
decomp: DecompressorOxide,
/// Buffer of input bytes for matches. /// TODO: Could probably do this a bit cleaner with some /// Cursor-like class. /// We may also look into whether we need to keep a buffer here, or just one in the /// decompressor struct.
dict: [u8; TINFL_LZ_DICT_SIZE], /// Where in the buffer are we currently at?
dict_ofs: usize, /// How many bytes of data to be flushed is there currently in the buffer?
dict_avail: usize,
first_call: bool,
has_flushed: bool,
/// Whether the input data is wrapped in a zlib header and checksum. /// TODO: This should be stored in the decompressor.
data_format: DataFormat,
last_status: TINFLStatus,
}
impl Default for InflateState { fn default() -> Self {
InflateState {
decomp: DecompressorOxide::default(),
dict: [0; TINFL_LZ_DICT_SIZE],
dict_ofs: 0,
dict_avail: 0,
first_call: true,
has_flushed: false,
data_format: DataFormat::Raw,
last_status: TINFLStatus::NeedsMoreInput,
}
}
} impl InflateState { /// Create a new state. /// /// Note that this struct is quite large due to internal buffers, and as such storing it on /// the stack is not recommended. /// /// # Parameters /// `data_format`: Determines whether the compressed data is assumed to wrapped with zlib /// metadata. pubfn new(data_format: DataFormat) -> InflateState {
InflateState {
data_format,
..Default::default()
}
}
/// Create a new state on the heap. /// /// # Parameters /// `data_format`: Determines whether the compressed data is assumed to wrapped with zlib /// metadata. #[cfg(feature = "with-alloc")] pubfn new_boxed(data_format: DataFormat) -> Box<InflateState> { letmut b: Box<InflateState> = Box::default();
b.data_format = data_format;
b
}
/// Return the status of the last call to `inflate` with this `InflateState`. pubconstfn last_status(&self) -> TINFLStatus { self.last_status
}
/// Create a new state using miniz/zlib style window bits parameter. /// /// The decompressor does not support different window sizes. As such, /// any positive (>0) value will set the zlib header flag, while a negative one /// will not. #[cfg(feature = "with-alloc")] pubfn new_boxed_with_window_bits(window_bits: i32) -> Box<InflateState> { letmut b: Box<InflateState> = Box::default();
b.data_format = DataFormat::from_window_bits(window_bits);
b
}
#[inline] /// Reset the decompressor without re-allocating memory, using the given /// data format. pubfn reset(&mutself, data_format: DataFormat) { self.reset_as(FullReset(data_format));
}
#[inline] /// Resets the state according to specified policy. pubfn reset_as<T: ResetPolicy>(&mutself, policy: T) {
policy.reset(self)
}
}
/// Try to decompress from `input` to `output` with the given [`InflateState`] /// /// # `flush` /// /// Generally, the various [`MZFlush`] flags have meaning only on the compression side. They can be /// supplied here, but the only one that has any semantic meaning is [`MZFlush::Finish`], which is a /// signal that the stream is expected to finish, and failing to do so is an error. It isn't /// necessary to specify it when the stream ends; you'll still get returned a /// [`MZStatus::StreamEnd`] anyway. Other values either have no effect or cause errors. It's /// likely that you'll almost always just want to use [`MZFlush::None`]. /// /// # Errors /// /// Returns [`MZError::Buf`] if the size of the `output` slice is empty or no progress was made due /// to lack of expected input data, or if called with [`MZFlush::Finish`] and input wasn't all /// consumed. /// /// Returns [`MZError::Data`] if this or a a previous call failed with an error return from /// [`TINFLStatus`]; probably indicates corrupted data. /// /// Returns [`MZError::Stream`] when called with [`MZFlush::Full`] (meaningless on /// decompression), or when called without [`MZFlush::Finish`] after an earlier call with /// [`MZFlush::Finish`] has been made. pubfn inflate(
state: &mut InflateState,
input: &[u8],
output: &mut [u8],
flush: MZFlush,
) -> StreamResult { letmut bytes_consumed = 0; letmut bytes_written = 0; letmut next_in = input; letmut next_out = output;
if flush == MZFlush::Full { return StreamResult::error(MZError::Stream);
}
if (flush == MZFlush::Finish) && first_call {
decomp_flags |= inflate_flags::TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
// The caller is indicating that they want to finish the compression and this is the first call with the current stream // so we can simply write directly to the output buffer. // If there is not enough space for all of the decompressed data we will end up with a failure regardless. let status = decompress(&mut state.decomp, next_in, next_out, 0, decomp_flags); let in_bytes = status.1; let out_bytes = status.2; let status = status.0;
// Finish was requested but we didn't end on an end block. if status == TINFLStatus::FailedCannotMakeProgress { return Err(MZError::Buf);
} // The stream was corrupted, and decompression failed. elseif (status as i32) < 0 { return Err(MZError::Data);
}
// The decompressor has flushed all it's data and is waiting for more input, but // there was no more input provided. if (status == TINFLStatus::NeedsMoreInput) && orig_in_len == 0 { return Err(MZError::Buf);
}
if flush == MZFlush::Finish { if status == TINFLStatus::Done { // There is not enough space in the output buffer to flush the remaining // decompressed data in the internal buffer. returnif state.dict_avail != 0 {
Err(MZError::Buf)
} else {
Ok(MZStatus::StreamEnd)
}; // No more space in the output buffer, but we're not done.
} elseif next_out.is_empty() { return Err(MZError::Buf);
}
} else { // We're not expected to finish, so it's fine if we can't flush everything yet. let empty_buf = next_in.is_empty() || next_out.is_empty(); if (status == TINFLStatus::Done) || empty_buf || (state.dict_avail != 0) { returnif (status == TINFLStatus::Done) && (state.dict_avail == 0) { // No more data left, we're done.
Ok(MZStatus::StreamEnd)
} else { // Ok for now, still waiting for more input data or output space.
Ok(MZStatus::Ok)
};
}
}
}
}
#[cfg(all(test, feature = "with-alloc"))] mod test { usesuper::{inflate, InflateState}; usecrate::{DataFormat, MZFlush, MZStatus}; use alloc::vec;
#[test] fn test_state() { let encoded = [ 120u8, 156, 243, 72, 205, 201, 201, 215, 81, 168, 202, 201, 76, 82, 4, 0, 27, 101, 4, 19,
]; letmut out = vec![0; 50]; letmut state = InflateState::new_boxed(DataFormat::Zlib); let res = inflate(&mut state, &encoded, &mut out, MZFlush::Finish); let status = res.status.expect("Failed to decompress!");
assert_eq!(status, MZStatus::StreamEnd);
assert_eq!(out[..res.bytes_written as usize], b"Hello, zlib!"[..]);
assert_eq!(res.bytes_consumed, encoded.len());
state.reset_as(super::ZeroReset);
out.iter_mut().map(|x| *x = 0).count(); let res = inflate(&mut state, &encoded, &mut out, MZFlush::Finish); let status = res.status.expect("Failed to decompress!");
assert_eq!(status, MZStatus::StreamEnd);
assert_eq!(out[..res.bytes_written as usize], b"Hello, zlib!"[..]);
assert_eq!(res.bytes_consumed, encoded.len());
state.reset_as(super::MinReset);
out.iter_mut().map(|x| *x = 0).count(); let res = inflate(&mut state, &encoded, &mut out, MZFlush::Finish); let status = res.status.expect("Failed to decompress!");
assert_eq!(status, MZStatus::StreamEnd);
assert_eq!(out[..res.bytes_written as usize], b"Hello, zlib!"[..]);
assert_eq!(res.bytes_consumed, encoded.len());
assert_eq!(state.decompressor().adler32(), Some(459605011));
// Test state when not computing adler.
state = InflateState::new_boxed(DataFormat::ZLibIgnoreChecksum);
out.iter_mut().map(|x| *x = 0).count(); let res = inflate(&mut state, &encoded, &mut out, MZFlush::Finish); let status = res.status.expect("Failed to decompress!");
assert_eq!(status, MZStatus::StreamEnd);
assert_eq!(out[..res.bytes_written as usize], b"Hello, zlib!"[..]);
assert_eq!(res.bytes_consumed, encoded.len()); // Not computed, so should be Some(1)
assert_eq!(state.decompressor().adler32(), Some(1)); // Should still have the checksum read from the header file.
assert_eq!(state.decompressor().adler32_header(), Some(459605011))
}
// Feed input bytes one at a time to the decompressor letmut out = vec![0; 50]; letmut state = InflateState::new_boxed(DataFormat::Zlib); letmut part_in = 0; letmut part_out = 0; for i in1..=encoded.len() { let res = inflate(
&mut state,
&encoded[part_in..i],
&mut out[part_out..],
MZFlush::None,
); let status = res.status.expect("Failed to decompress!"); if i == encoded.len() {
assert_eq!(status, MZStatus::StreamEnd);
} else {
assert_eq!(status, MZStatus::Ok);
}
part_out += res.bytes_written as usize;
part_in += res.bytes_consumed;
}
assert_eq!(out[..part_out as usize], b"Hello, zlib!"[..]);
assert_eq!(part_in, encoded.len());
assert_eq!(state.decompressor().adler32(), Some(459605011));
}
// Inflate part of a stream and clone the inflate state. // Discard the original state and resume the stream from the clone. #[test] fn test_rewind_and_resume() { let encoded = [ 120u8, 156, 243, 72, 205, 201, 201, 215, 81, 168, 202, 201, 76, 82, 4, 0, 27, 101, 4, 19,
]; let decoded = b"Hello, zlib!";
// Feed partial input bytes to the decompressor letmut out = vec![0; 50]; letmut state = InflateState::new_boxed(DataFormat::Zlib); let res1 = inflate(&mut state, &encoded[..10], &an style='color:red'>mut out, MZFlush::None); let status = res1.status.expect("Failed to decompress!");
assert_eq!(status, MZStatus::Ok);
// Clone the state and discard the original letmut resume = state.clone();
drop(state);
// Resume the stream using the cloned state let res2 = inflate(
&mut resume,
&encoded[res1.bytes_consumed..],
&mut out[res1.bytes_written..],
MZFlush::Finish,
); let status = res2.status.expect("Failed to decompress!");
assert_eq!(status, MZStatus::StreamEnd);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.