// SAFETY: This struct must have the same layout as [`z_stream`], so that casts and transmutations // between the two can work without UB. #[repr(C)] pubstruct InflateStream<'a> { pub(crate) next_in: *mutcrate::c_api::Bytef, pub(crate) avail_in: crate::c_api::uInt, pub(crate) total_in: crate::c_api::z_size, pub(crate) next_out: *mutcrate::c_api::Bytef, pub(crate) avail_out: crate::c_api::uInt, pub(crate) total_out: crate::c_api::z_size, pub(crate) msg: *mut c_char, pub(crate) state: &'a mut State<'a>, pub(crate) alloc: Allocator<'a>, pub(crate) data_type: c_int, pub(crate) adler: crate::c_api::z_checksum, pub(crate) reserved: crate::c_api::uLong,
}
unsafeimpl Sync for InflateStream<'_> {} unsafeimpl Send for InflateStream<'_> {}
impl<'a> InflateStream<'a> { // z_stream and DeflateStream must have the same layout. Do our best to check if this is true. // (imperfect check, but should catch most mistakes.) const _S: () = assert!(core::mem::size_of::<z_stream>() == core::mem::size_of::<Self>()); const _A: () = assert!(core::mem::align_of::<z_stream>() == core::mem::align_of::<Self>());
/// # Safety /// /// Behavior is undefined if any of the following conditions are violated: /// /// - `strm` satisfies the conditions of [`pointer::as_ref`] /// - if not `NULL`, `strm` as initialized using [`init`] or similar /// /// [`pointer::as_ref`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_ref #[inline(always)] pubunsafefn from_stream_ref(strm: *const z_stream) -> Option<&'a Self> {
{ // Safety: ptr points to a valid value of type z_stream (if non-null) let stream = unsafe { strm.as_ref() }?;
if stream.zalloc.is_none() || stream.zfree.is_none() { return None;
}
if stream.state.is_null() { return None;
}
}
// Safety: InflateStream has an equivalent layout as z_stream unsafe { strm.cast::<InflateStream>().as_ref() }
}
/// # Safety /// /// Behavior is undefined if any of the following conditions are violated: /// /// - `strm` satisfies the conditions of [`pointer::as_mut`] /// - if not `NULL`, `strm` as initialized using [`init`] or similar /// /// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut #[inline(always)] pubunsafefn from_stream_mut(strm: *mut z_stream) -> Option<&'a mut Self> {
{ // Safety: ptr points to a valid value of type z_stream (if non-null) let stream = unsafe { strm.as_ref() }?;
if stream.zalloc.is_none() || stream.zfree.is_none() { return None;
}
if stream.state.is_null() { return None;
}
}
// Safety: InflateStream has an equivalent layout as z_stream unsafe { strm.cast::<InflateStream>().as_mut() }
}
fn as_z_stream_mut(&mutself) -> &mut z_stream { // safety: a valid &mut InflateStream is also a valid &mut z_stream unsafe { &mut *(selfas *mut _ as *mut z_stream) }
}
let ret = crate::inflate::init(&mut inner, config);
assert_eq!(ret, ReturnCode::Ok);
unsafe { core::mem::transmute(inner) }
}
}
const MAX_BITS: u8 = 15; // maximum number of bits in a code const MAX_DIST_EXTRA_BITS: u8 = 13; // maximum number of extra distance bits
/// Decompresses `input` into the provided `output` buffer. /// /// Returns a subslice of `output` containing the decompressed bytes and a /// [`ReturnCode`] indicating the result of the operation. Returns [`ReturnCode::BufError`] if /// there is insufficient output space. /// /// # Example /// /// ``` /// # use zlib_rs::*; /// # fn foo(compressed: &[u8]) { /// let mut buffer = [0u8; 1024]; /// let (decompressed, rc) = decompress_slice(&mut buffer, compressed, InflateConfig::default()); /// assert_eq!(rc, ReturnCode::Ok); /// # } /// ``` pubfn decompress_slice<'a>(
output: &'a mut [u8],
input: &[u8],
config: InflateConfig,
) -> (&'a mut [u8], ReturnCode) { // SAFETY: [u8] is also a valid [MaybeUninit<u8>] let output_uninit = unsafe {
core::slice::from_raw_parts_mut(output.as_mut_ptr() as *mut MaybeUninit<u8>, output.len())
};
uncompress(output_uninit, input, config)
}
/// Inflates `source` into `dest`, and writes the final inflated size into `dest_len`. pubfn uncompress<'a>(
output: &'a mut [MaybeUninit<u8>],
input: &[u8],
config: InflateConfig,
) -> (&'a mut [u8], ReturnCode) { let (_consumed, output, ret) = uncompress2(output, input, config);
(output, ret)
}
let err = loop { if stream.avail_out == 0 {
stream.avail_out = Ord::min(left, u32::MAX as u64) as u32;
left -= stream.avail_out as u64;
}
if stream.avail_in == 0 {
stream.avail_in = Ord::min(len, u32::MAX as u64) as u32;
len -= stream.avail_in as u64;
}
let err = unsafe { inflate(stream, InflateFlush::NoFlush) };
if err != ReturnCode::Ok { break err;
}
};
let consumed = len + u64::from(stream.avail_in); if !output.is_empty() {
dest_len_ptr = stream.total_out;
} elseif stream.total_out != 0 && err == ReturnCode::BufError {
left = 1;
}
let avail_out = stream.avail_out;
end(stream);
let ret = match err {
ReturnCode::StreamEnd => ReturnCode::Ok,
ReturnCode::NeedDict => ReturnCode::DataError,
ReturnCode::BufError if (left + avail_out as u64) != 0 => ReturnCode::DataError,
_ => err,
};
// SAFETY: we have now initialized these bytes let output_slice = unsafe {
core::slice::from_raw_parts_mut(output.as_mut_ptr() as *mut u8, dest_len_ptr as usize)
};
/// log base 2 of requested window size
wbits: u8,
/// bitflag /// /// - bit 0 true if zlib /// - bit 1 true if gzip /// - bit 2 true to validate check value
wrap: u8,
flush: InflateFlush,
// allocated window if needed (capacity == 0 if unused)
window: Window<'a>,
// /// number of code length code lengths
ncode: usize, /// number of length code lengths
nlen: usize, /// number of distance code lengths
ndist: usize, /// number of code lengths in lens[]
have: usize, /// next available space in codes[]
next: usize, // represented as an index, don't want a self-referential structure here
// IO
bit_reader: BitReader<'a>,
writer: Writer<'a>,
total: usize,
/// length of a block to copy
length: usize, /// distance back to copy the string from
offset: usize,
/// extra bits needed
extra: usize,
/// bits back of last unprocessed length/lit
back: usize,
/// initial length of match
was: usize,
/// size of memory copying chunk
chunksize: usize,
in_available: usize,
out_available: usize,
gzip_flags: i32,
checksum: u32,
crc_fold: Crc32Fold,
error_message: Option<&'static str>,
/// place to store gzip header if needed
head: Option<&'a mut gz_header>,
dmax: usize,
/// table for length/literal codes
len_table: Table,
impl State<'_> { // This logic is split into its own function for two reasons // // - We get to load state to the stack; doing this in all cases is expensive, but doing it just // for Len and related states is very helpful. // - The `-Cllvm-args=-enable-dfa-jump-thread` llvm arg is able to optimize this function, but // not the entirity of `dispatch`. We get a massive boost from that pass. // // It unfortunately does duplicate the code for some of the states; deduplicating it by having // more of the states call this function is slower. fn len_and_friends(&mutself) -> ControlFlow<ReturnCode, ()> { let avail_in = self.bit_reader.bytes_remaining(); let avail_out = self.writer.remaining();
if avail_in >= INFLATE_FAST_MIN_HAVE && avail_out >= INFLATE_FAST_MIN_LEFT { // SAFETY: INFLATE_FAST_MIN_HAVE is enough bytes remaining to satisfy the precondition. unsafe { inflate_fast_help(self, 0) }; matchself.mode {
Mode::Len => {}
_ => return ControlFlow::Continue(()),
}
}
loop {
mode = 'top: { match mode {
Mode::Len => { let avail_in = bit_reader.bytes_remaining(); let avail_out = writer.remaining();
// INFLATE_FAST_MIN_LEFT is important. It makes sure there is at least 32 bytes of free // space available. This means for many SIMD operations we don't need to process a // remainder; we just copy blindly, and a later operation will overwrite the extra copied // bytes if avail_in >= INFLATE_FAST_MIN_HAVE && avail_out >= INFLATE_FAST_MIN_LEFT {
restore!(); // SAFETY: INFLATE_FAST_MIN_HAVE >= 15. // Note that the restore macro does not do anything that would // reduce the number of bytes available. unsafe { inflate_fast_help(self, 0) }; return ControlFlow::Continue(());
}
self.back = 0;
// get a literal, length, or end-of-block code letmut here; loop { let bits = bit_reader.bits(self.len_table.bits);
here = len_table[bits as usize];
if here.bits <= bit_reader.bits_in_buffer() { break;
}
if here.op != 0 && here.op & 0xf0 == 0 { let last = here; loop { let bits = bit_reader.bits((last.bits + last.op) as usize) as u16;
here = len_table[(last.val + (bits >> last.bits)) as usize]; if last.bits + here.bits <= bit_reader.bits_in_buffer() { break;
}
bit_reader.drop_bits(last.bits); self.back += last.bits as usize;
}
bit_reader.drop_bits(here.bits); self.back += here.bits as usize; self.length = here.val as usize;
if here.op == 0 { break'top Mode::Lit;
} elseif here.op & 32 != 0 { // end of block
// eprintln!("inflate: end of block");
self.back = usize::MAX;
mode = Mode::Type;
restore!(); return ControlFlow::Continue(());
} elseif here.op & 64 != 0 {
mode = Mode::Bad;
{
restore!(); let this = &mut *self; let msg: &'static str = "invalid literal/length code\0"; #[cfg(all(feature = "std", test))]
dbg!(msg);
this.error_message = Some(msg); return ControlFlow::Break(ReturnCode::DataError);
}
} else { // length code self.extra = (here.op & MAX_BITS) as usize; break'top Mode::LenExt;
}
}
Mode::Lit => { // NOTE: this branch must be kept in sync with its counterpart in `dispatch` if writer.is_full() {
restore!(); #[cfg(all(test, feature = "std"))]
eprintln!("Ok: writer is full ({} bytes)", self.writer.capacity()); return ControlFlow::Break(ReturnCode::Ok);
}
writer.push(self.length as u8);
break'top Mode::Len;
}
Mode::LenExt => { // NOTE: this branch must be kept in sync with its counterpart in `dispatch` let extra = self.extra;
// get extra bits, if any if extra != 0 { match bit_reader.need_bits(extra) {
Err(return_code) => {
restore!(); return ControlFlow::Break(return_code);
}
Ok(v) => v,
}; self.length += bit_reader.bits(extra) as usize;
bit_reader.drop_bits(extra as u8); self.back += extra;
}
// eprintln!("inflate: length {}", state.length);
self.was = self.length;
break'top Mode::Dist;
}
Mode::Dist => { // NOTE: this branch must be kept in sync with its counterpart in `dispatch`
// get distance code letmut here; loop { let bits = bit_reader.bits(self.dist_table.bits) as usize;
here = dist_table[bits]; if here.bits <= bit_reader.bits_in_buffer() { break;
}
break'top Mode::Match;
}
Mode::Match => { // NOTE: this branch must be kept in sync with its counterpart in `dispatch` if writer.is_full() {
restore!(); #[cfg(all(feature = "std", test))]
eprintln!( "BufError: writer is full ({} bytes)", self.writer.capacity()
); return ControlFlow::Break(ReturnCode::Ok);
}
let left = writer.remaining(); let copy = writer.len();
let copy = ifself.offset > copy { // copy from window to output
letmut copy = self.offset - copy;
if copy > self.window.have() { ifself.flags.contains(Flags::SANE) {
restore!(); self.mode = Mode::Bad; return ControlFlow::Break( self.bad("invalid distance too far back\0"),
);
}
// TODO INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
panic!("INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR")
}
let wnext = self.window.next(); let wsize = self.window.size();
let from = if copy > wnext {
copy -= wnext;
wsize - copy
} else {
wnext - copy
};
let b0 = self.bit_reader.bits(8) as u8; let b1 = (self.bit_reader.hold() >> 8) as u8; self.checksum = crc32(crate::CRC32_INITIAL_VALUE, &[b0, b1]); self.bit_reader.init_bits();
// self.length (and head.extra_len) represent the length of the extra field self.length = self.bit_reader.hold() as usize; iflet Some(head) = self.head.as_mut() {
head.extra_len = self.length as u32;
}
break'blk Mode::Extra;
}
Mode::Extra => { if (self.gzip_flags & 0x0400) != 0 { // self.length is the number of remaining `extra` bytes. But they may not all be available let extra_available =
Ord::min(self.length, self.bit_reader.bytes_remaining());
if extra_available > 0 { iflet Some(head) = self.head.as_mut() { if !head.extra.is_null() { // at `head.extra`, the caller has reserved `head.extra_max` bytes. // in the deflated byte stream, we've found a gzip header with // `head.extra_len` bytes of data. We must be careful because // `head.extra_len` may be larger than `head.extra_max`.
// how many bytes we've already written into `head.extra` let written_so_far = head.extra_len as usize - self.length;
// min of number of bytes available at dst and at src let count = Ord::min(
(head.extra_max as usize)
.saturating_sub(written_so_far),
extra_available,
);
// SAFETY: location where we'll write: this saturates at the // `head.extra.add(head.extra.max)` to prevent UB let next_write_offset =
Ord::min(written_so_far, head.extra_max as usize);
unsafe { // SAFETY: count is effectively bounded by head.extra_max // and bit_reader.bytes_remaining(), so the count won't // go out of bounds.
core::ptr::copy_nonoverlapping( self.bit_reader.as_mut_ptr(),
head.extra.add(next_write_offset),
count,
);
}
}
}
// the name string will always be null-terminated, but might be longer than we have // space for in the header struct. Nonetheless, we read the whole thing. let slice = self.bit_reader.as_slice(); let null_terminator_index = slice.iter().position(|c| *c == 0);
// we include the null terminator if it exists let name_slice = match null_terminator_index {
Some(i) => &slice[..=i],
None => slice,
};
// if the header has space, store as much as possible in there iflet Some(head) = self.head.as_mut() { if !head.name.is_null() { let remaining_name_bytes = (head.name_max as usize)
.checked_sub(self.length)
.expect("name out of bounds"); let copy = Ord::min(name_slice.len(), remaining_name_bytes);
unsafe { // SAFETY: copy is effectively bound by the name length and // head.name_max, so this won't go out of bounds.
core::ptr::copy_nonoverlapping(
name_slice.as_ptr(),
head.name.add(self.length),
copy,
)
};
// the comment string will always be null-terminated, but might be longer than we have // space for in the header struct. Nonetheless, we read the whole thing. let slice = self.bit_reader.as_slice(); let null_terminator_index = slice.iter().position(|c| *c == 0);
// we include the null terminator if it exists let comment_slice = match null_terminator_index {
Some(i) => &slice[..=i],
None => slice,
};
// if the header has space, store as much as possible in there iflet Some(head) = self.head.as_mut() { if !head.comment.is_null() { let remaining_comm_bytes = (head.comm_max as usize)
.checked_sub(self.length)
.expect("comm out of bounds"); let copy = Ord::min(comment_slice.len(), remaining_comm_bytes);
unsafe { // SAFETY: copy is effectively bound by the comment length and // head.comm_max, so this won't go out of bounds.
core::ptr::copy_nonoverlapping(
comment_slice.as_ptr(),
head.comment.add(self.length),
copy,
)
};
mode = Mode::Bad; break'label self.bad("invalid block type\0");
}
_ => { // LLVM will optimize this branch away
unreachable!("BitReader::bits(2) only yields a value of two bits, so this match is already exhaustive")
}
}
}
Mode::Stored => { self.bit_reader.next_byte_boundary();
need_bits!(self, 32);
let hold = self.bit_reader.bits(32) as u32;
// eprintln!("hold {hold:#x}");
if hold as u16 != !((hold >> 16) as u16) {
mode = Mode::Bad; break'label self.bad("invalid stored block lengths\0");
}
self.length = hold as usize & 0xFFFF; // eprintln!("inflate: stored length {}", state.length);
break'blk Mode::Length;
}
Mode::Len_ => { break'blk Mode::Len;
}
Mode::Len => { self.mode = mode; let val = self.len_and_friends();
mode = self.mode; match val {
ControlFlow::Break(return_code) => break'label return_code,
ControlFlow::Continue(()) => continue'label,
}
}
Mode::LenExt => { // NOTE: this branch must be kept in sync with its counterpart in `len_and_friends` let extra = self.extra;
// get extra bits, if any if extra != 0 {
need_bits!(self, extra); self.length += self.bit_reader.bits(extra) as usize; self.bit_reader.drop_bits(extra as u8); self.back += extra;
}
// eprintln!("inflate: length {}", state.length);
self.was = self.length;
break'blk Mode::Dist;
}
Mode::Lit => { // NOTE: this branch must be kept in sync with its counterpart in `len_and_friends` ifself.writer.is_full() { #[cfg(all(test, feature = "std"))]
eprintln!("Ok: writer is full ({} bytes)", self.writer.capacity()); break'label self.inflate_leave(ReturnCode::Ok);
}
self.writer.push(self.length as u8);
break'blk Mode::Len;
}
Mode::Dist => { // NOTE: this branch must be kept in sync with its counterpart in `len_and_friends`
// get distance code letmut here; loop { let bits = self.bit_reader.bits(self.dist_table.bits) as usize;
here = self.dist_table_get(bits); if here.bits <= self.bit_reader.bits_in_buffer() { break;
}
pull_byte!(self);
}
if here.op & 0xf0 == 0 { let last = here;
loop { let bits = self.bit_reader.bits((last.bits + last.op) as usize);
here = self.dist_table_get(
last.val as usize + ((bits as usize) >> last.bits),
);
if last.bits + here.bits <= self.bit_reader.bits_in_buffer() { break;
}
pull_byte!(self);
}
self.bit_reader.drop_bits(last.bits); self.back += last.bits as usize;
}
break'blk Mode::DistExt;
}
Mode::DistExt => { // NOTE: this branch must be kept in sync with its counterpart in `len_and_friends` let extra = self.extra;
if extra > 0 {
need_bits!(self, extra); self.offset += self.bit_reader.bits(extra) as usize; self.bit_reader.drop_bits(extra as u8); self.back += extra;
}
if INFLATE_STRICT && self.offset > self.dmax {
mode = Mode::Bad; break'label self.bad("invalid distance code too far back\0");
}
break'blk Mode::CodeLens;
}
Mode::CodeLens => { whileself.have < self.nlen + self.ndist { let here = loop { let bits = self.bit_reader.bits(self.len_table.bits); let here = self.len_table_get(bits as usize); if here.bits <= self.bit_reader.bits_in_buffer() { break here;
}
// NOTE: it is crucial for the internal bookkeeping that this is the only route for actually // leaving the inflate function call chain fn inflate_leave(&mutself, return_code: ReturnCode) -> ReturnCode { // actual logic is in `inflate` itself
return_code
}
/// Stored in the `z_stream.data_type` field fn decoding_state(&self) -> i32 { let bit_reader_bits = self.bit_reader.bits_in_buffer() as i32;
debug_assert!(bit_reader_bits < 64);
let last = ifself.flags.contains(Flags::IS_LAST_BLOCK) { 64
} else { 0
};
/// # Safety /// /// `state.bit_reader` must have at least 15 bytes available to read, as /// indicated by `state.bit_reader.bytes_remaining() >= 15` unsafefn inflate_fast_help(state: &mut State, start: usize) { #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] ifcrate::cpu_features::is_enabled_avx2_and_bmi2() { // SAFETY: we've verified the target features and the caller ensured enough bytes_remaining returnunsafe { inflate_fast_help_avx2(state, start) };
}
/// # Safety /// /// `state.bit_reader` must have at least 15 bytes available to read, as /// indicated by `state.bit_reader.bytes_remaining() >= 15` #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] #[target_feature(enable = "avx2")] #[target_feature(enable = "bmi2")] #[target_feature(enable = "bmi1")] unsafefn inflate_fast_help_avx2(state: &mut State, start: usize) { // SAFETY: `bytes_remaining` checked by our caller unsafe { inflate_fast_help_impl::<{ CpuFeatures::AVX2 }>(state, start) };
}
/// # Safety /// /// `state.bit_reader` must have at least 15 bytes available to read, as /// indicated by `state.bit_reader.bytes_remaining() >= 15` unsafefn inflate_fast_help_vanilla(state: &mut State, start: usize) { // SAFETY: `bytes_remaining` checked by our caller unsafe { inflate_fast_help_impl::<{ CpuFeatures::NONE }>(state, start) };
}
/// # Safety /// /// `state.bit_reader` must have at least 15 bytes available to read, as /// indicated by `state.bit_reader.bytes_remaining() >= 15` #[inline(always)] unsafefn inflate_fast_help_impl<const FEATURES: usize>(state: &mut State, _start: usize) { letmut bit_reader = BitReader::new(&[]);
core::mem::swap(&mut bit_reader, &mut state.bit_reader);
debug_assert!(bit_reader.bytes_remaining() >= 15);
let lcode = state.len_table_ref(); let dcode = state.dist_table_ref();
// IDEA: use const generics for the bits here? let lmask = (1u64 << state.len_table.bits) - 1; let dmask = (1u64 << state.dist_table.bits) - 1;
// TODO verify if this is relevant for us let extra_safe = false;
let window_size = state.window.size();
letmut bad = None;
if bit_reader.bits_in_buffer() < 10 {
debug_assert!(bit_reader.bytes_remaining() >= 15); // Safety: Caller ensured that bit_reader has >= 15 bytes available; refill only needs 8. unsafe { bit_reader.refill() };
} // We had at least 15 bytes in the slice, plus whatever was in the buffer. After filling the // buffer from the slice, we now have at least 8 bytes remaining in the slice, plus a full buffer.
debug_assert!(
bit_reader.bytes_remaining() >= 8 && bit_reader.bytes_remaining_including_buffer() >= 15
);
'outer: loop { // This condition is ensured above for the first iteration of the `outer` loop. For // subsequent iterations, the loop continuation condition is // `bit_reader.bytes_remaining_including_buffer() > 15`. And because the buffer // contributes at most 7 bytes to the result of bit_reader.bytes_remaining_including_buffer(), // that means that the slice contains at least 8 bytes.
debug_assert!(
bit_reader.bytes_remaining() >= 8
&& bit_reader.bytes_remaining_including_buffer() >= 15
);
letmut here = { let bits = bit_reader.bits_in_buffer(); let hold = bit_reader.hold();
// Safety: As described in the comments for the debug_assert at the start of // the `outer` loop, it is guaranteed that `bit_reader.bytes_remaining() >= 8` here, // which satisfies the safety precondition for `refill`. And, because the total // number of bytes in `bit_reader`'s buffer plus its slice is at least 15, and // `refill` moves at most 7 bytes from the slice to the buffer, the slice will still // contain at least 8 bytes after this `refill` call. unsafe { bit_reader.refill() }; // After the refill, there will be at least 8 bytes left in the bit_reader's slice.
debug_assert!(bit_reader.bytes_remaining() >= 8);
// in most cases, the read can be interleaved with the logic // based on benchmarks this matters in practice. wild. if bits as usize >= state.len_table.bits {
lcode[(hold & lmask) as usize]
} else {
lcode[(bit_reader.hold() & lmask) as usize]
}
};
if here.op == 0 {
writer.push(here.val as u8);
bit_reader.drop_bits(here.bits);
here = lcode[(bit_reader.hold() & lmask) as usize];
if here.op == 0 {
writer.push(here.val as u8);
bit_reader.drop_bits(here.bits);
here = lcode[(bit_reader.hold() & lmask) as usize];
}
}
'dolen: loop {
bit_reader.drop_bits(here.bits); let op = here.op;
if op == 0 {
writer.push(here.val as u8);
} elseif op & 16 != 0 { let op = op & MAX_BITS; letmut len = here.val + bit_reader.bits(op as usize) as u16;
bit_reader.drop_bits(op);
here = dcode[(bit_reader.hold() & dmask) as usize];
// we have two fast-path loads: 10+10 + 15+5 = 40, // but we may need to refill here in the worst case if bit_reader.bits_in_buffer() < MAX_BITS + MAX_DIST_EXTRA_BITS {
debug_assert!(bit_reader.bytes_remaining() >= 8); // Safety: On the first iteration of the `dolen` loop, we can rely on the // invariant documented for the previous `refill` call above: after that // operation, `bit_reader.bytes_remining >= 8`, which satisfies the safety // precondition for this call. For subsequent iterations, this invariant // remains true because nothing else within the `dolen` loop consumes data // from the slice. unsafe { bit_reader.refill() };
}
'dodist: loop {
bit_reader.drop_bits(here.bits); let op = here.op;
if op & 16 != 0 { let op = op & MAX_BITS; let dist = here.val + bit_reader.bits(op as usize) as u16;
if INFLATE_STRICT && dist as usize > state.dmax {
bad = Some("invalid distance too far back\0");
state.mode = Mode::Bad; break'outer;
}
bit_reader.drop_bits(op);
// max distance in output let written = writer.len();
if dist as usize > written { // copy fropm the window if (dist as usize - written) > state.window.have() { if state.flags.contains(Flags::SANE) {
bad = Some("invalid distance too far back\0");
state.mode = Mode::Bad; break'outer;
}
if window_next == 0 { // This case is hit when the window has just wrapped around // by logic in `Window::extend`. It is special-cased because // apparently this is quite common. // // the match is at the end of the window, even though the next // position has now wrapped around.
from = window_size - op;
} elseif window_next >= op { // the standard case: a contiguous copy from the window, no wrapping
from = window_next - op;
} else { // This case is hit when the window has recently wrapped around // by logic in `Window::extend`. // // The match is (partially) at the end of the window
op -= window_next;
from = window_size - op;
if op < len as usize { // This case is hit when part of the match is at the end of the // window, and part of it has wrapped around to the start. Copy // the end section here, the start section will be copied below.
len -= op as u16;
writer.extend_from_window_with_features::<FEATURES>(
&state.window,
from..from + op,
);
from = 0;
op = window_next;
}
}
let copy = Ord::min(op, len as usize);
writer.extend_from_window_with_features::<FEATURES>(
&state.window,
from..from + copy,
);
if op < len as usize { // here we need some bytes from the output itself
writer.copy_match_with_features::<FEATURES>(
dist as usize,
len as usize - op,
);
}
} elseif extra_safe {
todo!()
} else {
writer.copy_match_with_features::<FEATURES>(dist as usize, len as usize)
}
} elseif (op & 64) == 0 { // 2nd level distance code
here = dcode[(here.val + bit_reader.bits(op as usize) as u16) as usize]; continue'dodist;
} else {
bad = Some("invalid distance code\0");
state.mode = Mode::Bad; break'outer;
}
break'dodist;
}
} elseif (op & 64) == 0 { // 2nd level length code
here = lcode[(here.val + bit_reader.bits(op as usize) as u16) as usize]; continue'dolen;
} elseif op & 32 != 0 { // end of block
state.mode = Mode::Type; break'outer;
} else {
bad = Some("invalid literal/length code\0");
state.mode = Mode::Bad; break'outer;
}
break'dolen;
}
// For normal `inflate`, include the bits in the bit_reader buffer in the count of available bytes. let remaining = bit_reader.bytes_remaining_including_buffer(); if remaining >= INFLATE_FAST_MIN_HAVE && writer.remaining() >= INFLATE_FAST_MIN_LEFT { continue;
}
break'outer;
}
// return unused bytes (on entry, bits < 8, so in won't go too far back)
bit_reader.return_unused_bytes();
impl InflateAllocOffsets { fn new() -> Self { use core::mem::size_of;
// 64B padding for SIMD operations. This allows unaligned operations (up to 512-bit) to run // off the end of the object without issue. const WINDOW_PAD_SIZE: usize = 64;
// 64B alignment of individual items in the alloc. // Note that changing this also requires changes in 'init' and 'copy'. const ALIGN_SIZE: usize = 64; letmut curr_size = 0usize;
/* Define sizes */ let state_size = size_of::<State>(); let window_size = (1 << MAX_WBITS) + WINDOW_PAD_SIZE;
/* Calculate relative buffer positions and paddings */ let state_pos = curr_size.next_multiple_of(ALIGN_SIZE);
curr_size = state_pos + state_size;
let window_pos = curr_size.next_multiple_of(ALIGN_SIZE);
curr_size = window_pos + window_size;
/* Add ALIGN_SIZE-1 to allow alignment (done in the 'init' and 'copy' functions), and round
* size of buffer up to next multiple of ALIGN_SIZE */ let total_size = (curr_size + (ALIGN_SIZE - 1)).next_multiple_of(ALIGN_SIZE);
Self {
total_size,
state_pos,
window_pos,
}
}
}
/// Configuration for decompresssion. /// /// Used with [`decompress_slice`]. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pubstruct InflateConfig { pub window_bits: i32,
}
/// Initialize the stream in an inflate state pubfn init(stream: &mut z_stream, config: InflateConfig) -> ReturnCode {
stream.msg = core::ptr::null_mut();
// for safety we must really make sure that alloc and free are consistent // this is a (slight) deviation from stock zlib. In this crate we pick the rust // allocator as the default, but `libz-rs-sys` configures the C allocator #[cfg(feature = "rust-allocator")] if stream.zalloc.is_none() || stream.zfree.is_none() {
stream.configure_default_rust_allocator()
}
#[cfg(feature = "c-allocator")] if stream.zalloc.is_none() || stream.zfree.is_none() {
stream.configure_default_c_allocator()
}
if stream.zalloc.is_none() || stream.zfree.is_none() { return ReturnCode::StreamError;
}
letmut state = State::new(&[], Writer::new(&mut []));
// TODO this can change depending on the used/supported SIMD instructions
state.chunksize = 32;
let alloc = Allocator {
zalloc: stream.zalloc.unwrap(),
zfree: stream.zfree.unwrap(),
opaque: stream.opaque,
_marker: PhantomData,
}; let allocs = InflateAllocOffsets::new();
let Some(allocation_start) = alloc.allocate_slice_raw::<u8>(allocs.total_size) else { return ReturnCode::MemError;
};
let address = allocation_start.as_ptr() as usize; let align_offset = address.next_multiple_of(64) - address; let buf = unsafe { allocation_start.as_ptr().add(align_offset) };
// SAFETY: we've correctly initialized the stream to be an InflateStream iflet Some(stream) = unsafe { InflateStream::from_stream_mut(stream) } {
stream.state.allocation_start = allocation_start.as_ptr();
stream.state.total_allocation_size = allocs.total_size; let ret = reset_with_config(stream, config);
unsafe {
state
.bit_reader
.update_slice(stream.next_in, stream.avail_in as usize)
}; // Safety: `stream.next_out` is non-null and points to at least `stream.avail_out` bytes.
state.writer = unsafe { Writer::new_uninit(stream.next_out.cast(), stream.avail_out asusize) };
state.in_available = stream.avail_in as _;
state.out_available = stream.avail_out as _;
let err = state.dispatch();
let in_read = state.bit_reader.as_ptr() as usize - stream.next_in as usize; let out_written = state.out_available - (state.writer.capacity() - state.writer.len());
stream.total_in += in_read as z_size;
state.total = state.total.wrapping_add(out_written);
stream.total_out = state.total as _;
stream.avail_in = state.bit_reader.bytes_remaining() as u32;
stream.next_in = state.bit_reader.as_ptr() as *mut u8;
stream.avail_out = (state.writer.capacity() - state.writer.len()) as u32;
stream.next_out = state.writer.next_out() as *mut u8;
fn syncsearch(mut got: usize, buf: &[u8]) -> (usize, usize) { let len = buf.len(); letmut next = 0;
while next < len && got < 4 { if buf[next] == if got < 2 { 0 } else { 0xff } {
got += 1;
} elseif buf[next] != 0 {
got = 0;
} else {
got = 4 - got;
}
next += 1;
}
(got, next)
}
pubfn sync(stream: &mut InflateStream) -> ReturnCode { let state = &mut stream.state;
if stream.avail_in == 0 && state.bit_reader.bits_in_buffer() < 8 { return ReturnCode::BufError;
} /* if first time, start search in bit buffer */ if !matches!(state.mode, Mode::Sync) {
state.mode = Mode::Sync;
let (buf, len) = state.bit_reader.start_sync_search();
(state.have, _) = syncsearch(0, &buf[..len]);
}
// search available input // SAFETY: user guarantees that pointer and length are valid. let slice = unsafe { core::slice::from_raw_parts(stream.next_in, stream.avail_in as usize) };
let len;
(state.have, len) = syncsearch(state.have, slice); // SAFETY: syncsearch() returns an index that is in-bounds of the slice.
stream.next_in = unsafe { stream.next_in.add(len) };
stream.avail_in -= len as u32;
stream.total_in += len as z_size;
/* return no joy or set up to restart inflate() on a new block */ if state.have != 4 { return ReturnCode::DataError;
}
if state.gzip_flags == -1 {
state.wrap = 0; /* if no header yet, treat as raw */
} else {
state.wrap &= !4; /* no point in computing a check value now */
}
let flags = state.gzip_flags; let total_in = stream.total_in; let total_out = stream.total_out;
// Safety: source and dest are both mutable references, so guaranteed not to overlap. // dest being a reference to maybe uninitialized memory makes a copy of 1 DeflateStream valid. unsafe { core::ptr::copy_nonoverlapping(source, dest.as_mut_ptr(), 1) };
// Allocate space. let allocs = InflateAllocOffsets::new();
debug_assert_eq!(allocs.total_size, source.state.total_allocation_size);
let Some(allocation_start) = source.alloc.allocate_slice_raw::<u8>(allocs.total_size) else { return ReturnCode::MemError;
};
let address = allocation_start.as_ptr() as usize; let align_offset = address.next_multiple_of(64) - address; let buf = unsafe { allocation_start.as_ptr().add(align_offset) };
/// # Safety /// /// The caller must guarantee: /// /// * If `head` is `Some`: /// - If `head.extra` is not NULL, it must be writable for at least `head.extra_max` bytes /// - if `head.name` is not NULL, it must be writable for at least `head.name_max` bytes /// - if `head.comment` is not NULL, it must be writable for at least `head.comm_max` bytes pubunsafefn get_header<'a>(
stream: &mut InflateStream<'a>,
head: Option<&'a mut gz_header>,
) -> ReturnCode { if (stream.state.wrap & 2) == 0 { return ReturnCode::StreamError;
}
/// # Safety /// /// The `dictionary` must have enough space for the dictionary. pubunsafefn get_dictionary(stream: &InflateStream<'_>, dictionary: *mut u8) -> usize { let whave = stream.state.window.have(); let wnext = stream.state.window.next();
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.