From 9417e52db73669e41c255e1051830aaa8b649b4d Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 29 Nov 2025 20:26:23 +0100 Subject: [PATCH 1/3] Use a single allocation for inflate --- test-libz-rs-sys/src/inflate.rs | 11 -- zlib-rs/src/inflate.rs | 257 ++++++++++++++------------------ zlib-rs/src/inflate/infback.rs | 25 +++- zlib-rs/src/inflate/window.rs | 17 ++- 4 files changed, 134 insertions(+), 176 deletions(-) diff --git a/test-libz-rs-sys/src/inflate.rs b/test-libz-rs-sys/src/inflate.rs index aca8ee3a..60e34699 100644 --- a/test-libz-rs-sys/src/inflate.rs +++ b/test-libz-rs-sys/src/inflate.rs @@ -352,11 +352,6 @@ fn inf(input: &[u8], _what: &str, step: usize, win: i32, len: usize, err: c_int) println!("{ret:?}"); assert_eq!(ret, Z_DATA_ERROR); - mem_limit(&mut stream, 1); - let ret = unsafe { inflateSetDictionary(&mut stream, input.as_ptr(), 0) }; - assert_eq!(ret, Z_MEM_ERROR); - mem_limit(&mut stream, 0); - unsafe { set_mode_dict(&mut stream) } let ret = unsafe { inflateSetDictionary(&mut stream, out.as_ptr(), 0) }; assert_eq!(ret, Z_OK); @@ -472,12 +467,6 @@ fn cover_wrap() { strm.next_in = input.as_mut_ptr().cast(); strm.avail_out = 1; strm.next_out = (&mut ret) as *mut _ as *mut u8; - mem_limit(&mut strm, 1); - ret = unsafe { inflate(&mut strm, InflateFlush::NoFlush as _) }; - assert_eq!(ret, Z_MEM_ERROR); - ret = unsafe { inflate(&mut strm, InflateFlush::NoFlush as _) }; - assert_eq!(ret, Z_MEM_ERROR); - mem_limit(&mut strm, 0); let dict = [0u8; 257]; ret = unsafe { inflateSetDictionary(&mut strm, dict.as_ptr(), 257) }; diff --git a/zlib-rs/src/inflate.rs b/zlib-rs/src/inflate.rs index 8e24d21a..6291836a 100644 --- a/zlib-rs/src/inflate.rs +++ b/zlib-rs/src/inflate.rs @@ -429,6 +429,9 @@ pub(crate) struct State<'a> { lens: [u16; 320], /// work area for code table building work: [u16; 288], + + allocation_start: *mut u8, + total_allocation_size: usize, } impl<'a> State<'a> { @@ -484,6 +487,9 @@ impl<'a> State<'a> { codes_codes: [Code::default(); crate::ENOUGH_LENS], len_codes: [Code::default(); crate::ENOUGH_LENS], dist_codes: [Code::default(); crate::ENOUGH_DISTS], + + allocation_start: core::ptr::null_mut(), + total_allocation_size: 0, } } @@ -2147,6 +2153,43 @@ pub fn prime(stream: &mut InflateStream, bits: i32, value: i32) -> ReturnCode { ReturnCode::Ok } +struct InflateAllocOffsets { + total_size: usize, + state_pos: usize, + window_pos: usize, +} + +impl InflateAllocOffsets { + fn new() -> Self { + use core::mem::size_of; + + // 64B padding for SIMD operations + const WINDOW_PAD_SIZE: usize = 64; + + let mut curr_size = 0usize; + + /* Define sizes */ + let window_size = (1 << MAX_WBITS) + WINDOW_PAD_SIZE; + let state_size = size_of::(); + + /* Calculate relative buffer positions and paddings */ + let window_pos = curr_size.next_multiple_of(WINDOW_PAD_SIZE); + curr_size += window_pos + window_size; + + let state_pos = curr_size.next_multiple_of(64); + curr_size += state_pos + state_size; + + /* Add 64-1 or 4096-1 to allow window alignment, and round size of buffer up to multiple of 64 */ + let total_size = (curr_size + (WINDOW_PAD_SIZE - 1)).next_multiple_of(64); + + Self { + total_size, + state_pos, + window_pos, + } + } +} + #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub struct InflateConfig { pub window_bits: i32, @@ -2192,31 +2235,38 @@ pub fn init(stream: &mut z_stream, config: InflateConfig) -> ReturnCode { opaque: stream.opaque, _marker: PhantomData, }; + let allocs = InflateAllocOffsets::new(); - // allocated here to have the same order as zlib - let Some(state_allocation) = alloc.allocate_raw::() else { + let Some(allocation_start) = alloc.allocate_slice_raw::(allocs.total_size) else { return ReturnCode::MemError; }; - // FIXME: write is stable for NonNull since 1.80.0 - unsafe { state_allocation.as_ptr().write(state) }; - stream.state = state_allocation.as_ptr() as *mut internal_state; + 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) }; + + let window_allocation = unsafe { buf.add(allocs.window_pos) }; + let window = unsafe { Window::from_raw_parts(window_allocation, (1 << MAX_WBITS) + 64) }; + state.window = window; + + let state_allocation = unsafe { buf.add(allocs.state_pos).cast::() }; + unsafe { state_allocation.write(state) }; + stream.state = state_allocation.cast::(); // SAFETY: we've correctly initialized the stream to be an InflateStream - let ret = if let Some(stream) = unsafe { InflateStream::from_stream_mut(stream) } { - reset_with_config(stream, config) + if let 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); + + if ret != ReturnCode::Ok { + end(stream); + } + + ret } else { ReturnCode::StreamError - }; - - if ret != ReturnCode::Ok { - let ptr = stream.state; - stream.state = core::ptr::null_mut(); - // SAFETY: we assume deallocation does not cause UB - unsafe { alloc.deallocate(ptr, 1) }; } - - ret } pub fn reset_with_config(stream: &mut InflateStream, config: InflateConfig) -> ReturnCode { @@ -2245,16 +2295,6 @@ pub fn reset_with_config(stream: &mut InflateStream, config: InflateConfig) -> R return ReturnCode::StreamError; } - if stream.state.window.size() != 0 && stream.state.wbits as i32 != window_bits { - let mut window = Window::empty(); - core::mem::swap(&mut window, &mut stream.state.window); - - let (ptr, len) = window.into_raw_parts(); - assert_ne!(len, 0); - // SAFETY: window is discarded after this deallocation. - unsafe { stream.alloc.deallocate(ptr, len) }; - } - stream.state.wrap = wrap as u8; stream.state.wbits = window_bits as _; @@ -2333,7 +2373,7 @@ pub unsafe fn inflate(stream: &mut InflateStream, flush: InflateFlush) -> Return state.in_available = stream.avail_in as _; state.out_available = stream.avail_out as _; - let mut err = state.dispatch(); + 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()); @@ -2366,27 +2406,13 @@ pub unsafe fn inflate(stream: &mut InflateStream, flush: InflateFlush) -> Return let update_checksum = state.wrap & 4 != 0; if must_update_window { - 'blk: { - // initialize the window if needed - if state.window.size() == 0 { - match Window::new_in(&stream.alloc, state.wbits as usize) { - Some(window) => state.window = window, - None => { - state.mode = Mode::Mem; - err = ReturnCode::MemError; - break 'blk; - } - } - } - - state.window.extend( - &state.writer.filled()[..out_written], - state.gzip_flags, - update_checksum, - &mut state.checksum, - &mut state.crc_fold, - ); - } + state.window.extend( + &state.writer.filled()[..out_written], + state.gzip_flags, + update_checksum, + &mut state.checksum, + &mut state.crc_fold, + ); } if let Some(msg) = state.error_message { @@ -2497,83 +2523,39 @@ pub unsafe fn copy<'a>( // 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); - } + 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); - // allocated here to have the same order as zlib - let Some(state_allocation) = source.alloc.allocate_raw::() else { + let Some(allocation_start) = source.alloc.allocate_slice_raw::(allocs.total_size) else { return ReturnCode::MemError; }; - let state = &source.state; - - // SAFETY: an initialized Writer is a valid MaybeUninit. - let writer: MaybeUninit = - unsafe { core::ptr::read(&state.writer as *const _ as *const MaybeUninit) }; - - let mut copy = State { - mode: state.mode, - flags: state.flags, - wrap: state.wrap, - len_table: state.len_table, - dist_table: state.dist_table, - wbits: state.wbits, - window: Window::empty(), - head: None, - ncode: state.ncode, - nlen: state.nlen, - ndist: state.ndist, - have: state.have, - next: state.next, - bit_reader: state.bit_reader, - writer: Writer::new(&mut []), - total: state.total, - length: state.length, - offset: state.offset, - extra: state.extra, - back: state.back, - was: state.was, - chunksize: state.chunksize, - in_available: state.in_available, - out_available: state.out_available, - lens: state.lens, - work: state.work, - error_message: state.error_message, - flush: state.flush, - checksum: state.checksum, - crc_fold: state.crc_fold, - dmax: state.dmax, - gzip_flags: state.gzip_flags, - codes_codes: state.codes_codes, - len_codes: state.len_codes, - dist_codes: state.dist_codes, + 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) }; + + let window_allocation = unsafe { buf.add(allocs.window_pos) }; + let window = unsafe { + source + .state + .window + .clone_to(window_allocation, (1 << MAX_WBITS) + 64) }; - if !state.window.is_empty() { - let Some(window) = state.window.clone_in(&source.alloc) else { - // SAFETY: state_allocation is not used again. - unsafe { source.alloc.deallocate(state_allocation.as_ptr(), 1) }; - return ReturnCode::MemError; - }; + let copy = unsafe { buf.add(allocs.state_pos).cast::() }; + unsafe { core::ptr::copy_nonoverlapping(source.state, copy, 1) }; - copy.window = window; - } + let field_ptr = unsafe { core::ptr::addr_of_mut!((*copy).window) }; + unsafe { core::ptr::write(field_ptr, window) }; - // write the cloned state into state_ptr - unsafe { state_allocation.as_ptr().write(copy) }; // FIXME: write is stable for NonNull since 1.80.0 + let field_ptr = unsafe { core::ptr::addr_of_mut!((*copy).allocation_start) }; + unsafe { core::ptr::write(field_ptr, allocation_start.as_ptr()) }; - // insert the state_ptr into `dest` let field_ptr = unsafe { core::ptr::addr_of_mut!((*dest.as_mut_ptr()).state) }; - unsafe { core::ptr::write(field_ptr as *mut *mut State, state_allocation.as_ptr()) }; - - // update the writer; it cannot be cloned so we need to use some shennanigans - let field_ptr = unsafe { core::ptr::addr_of_mut!((*dest.as_mut_ptr()).state.writer) }; - unsafe { core::ptr::copy(writer.as_ptr(), field_ptr, 1) }; - - // update the gzhead field (it contains a mutable reference so we need to be careful - let field_ptr = unsafe { core::ptr::addr_of_mut!((*dest.as_mut_ptr()).state.head) }; - unsafe { core::ptr::copy(&source.state.head, field_ptr, 1) }; + unsafe { core::ptr::write(field_ptr as *mut *mut State, copy) }; ReturnCode::Ok } @@ -2614,30 +2596,13 @@ pub fn set_dictionary(stream: &mut InflateStream, dictionary: &[u8]) -> ReturnCo } } - let err = 'blk: { - // initialize the window if needed - if stream.state.window.size() == 0 { - match Window::new_in(&stream.alloc, stream.state.wbits as usize) { - None => break 'blk ReturnCode::MemError, - Some(window) => stream.state.window = window, - } - } - - stream.state.window.extend( - dictionary, - stream.state.gzip_flags, - false, - &mut stream.state.checksum, - &mut stream.state.crc_fold, - ); - - ReturnCode::Ok - }; - - if err != ReturnCode::Ok { - stream.state.mode = Mode::Mem; - return ReturnCode::MemError; - } + stream.state.window.extend( + dictionary, + stream.state.gzip_flags, + false, + &mut stream.state.checksum, + &mut stream.state.crc_fold, + ); stream.state.flags.update(Flags::HAVE_DICT, true); @@ -2646,22 +2611,16 @@ pub fn set_dictionary(stream: &mut InflateStream, dictionary: &[u8]) -> ReturnCo pub fn end<'a>(stream: &'a mut InflateStream<'_>) -> &'a mut z_stream { let alloc = stream.alloc; + let allocation_start = stream.state.allocation_start; + let total_allocation_size = stream.state.total_allocation_size; let mut window = Window::empty(); core::mem::swap(&mut window, &mut stream.state.window); - // safety: window is not used again - if !window.is_empty() { - let (ptr, len) = window.into_raw_parts(); - unsafe { alloc.deallocate(ptr, len) }; - } - let stream = stream.as_z_stream_mut(); + let _ = core::mem::replace(&mut stream.state, core::ptr::null_mut()); - let state_ptr = core::mem::replace(&mut stream.state, core::ptr::null_mut()); - - // safety: state_ptr is not used again - unsafe { alloc.deallocate(state_ptr as *mut State, 1) }; + unsafe { alloc.deallocate(allocation_start, total_allocation_size) }; stream } diff --git a/zlib-rs/src/inflate/infback.rs b/zlib-rs/src/inflate/infback.rs index a224a432..a26da558 100644 --- a/zlib-rs/src/inflate/infback.rs +++ b/zlib-rs/src/inflate/infback.rs @@ -6,8 +6,8 @@ use crate::c_api::{in_func, internal_state, out_func}; use crate::inflate::bitreader::BitReader; use crate::inflate::inftrees::{inflate_table, CodeType, InflateTable}; use crate::inflate::{ - Codes, Flags, InflateConfig, InflateStream, Mode, State, Table, Window, INFLATE_FAST_MIN_HAVE, - INFLATE_FAST_MIN_LEFT, INFLATE_STRICT, MAX_BITS, MAX_DIST_EXTRA_BITS, + Codes, Flags, InflateAllocOffsets, InflateConfig, InflateStream, Mode, State, Table, Window, + INFLATE_FAST_MIN_HAVE, INFLATE_FAST_MIN_LEFT, INFLATE_STRICT, MAX_BITS, MAX_DIST_EXTRA_BITS, }; use crate::{c_api::z_stream, inflate::writer::Writer, ReturnCode}; @@ -56,19 +56,28 @@ pub fn back_init(stream: &mut z_stream, config: InflateConfig, window: Window) - opaque: stream.opaque, _marker: PhantomData, }; + let allocs = InflateAllocOffsets::new(); - // allocated here to have the same order as zlib - let Some(state_allocation) = alloc.allocate_raw::() else { + let Some(allocation_start) = alloc.allocate_slice_raw::(allocs.total_size) else { return ReturnCode::MemError; }; - // FIXME: write is stable for NonNull since 1.80.0 - unsafe { state_allocation.as_ptr().write(state) }; - stream.state = state_allocation.as_ptr() as *mut internal_state; + 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) }; + + // NOTE: the window part of the allocation is ignored in this case. + state.window = window; + + let state_allocation = unsafe { buf.add(allocs.state_pos).cast::() }; + unsafe { state_allocation.write(state) }; + stream.state = state_allocation.cast::(); // SAFETY: we've correctly initialized the stream to be an InflateStream let ret = if let Some(stream) = unsafe { InflateStream::from_stream_mut(stream) } { - stream.state.window = window; + stream.state.allocation_start = allocation_start.as_ptr(); + stream.state.total_allocation_size = allocs.total_size; + stream.state.wbits = config.window_bits as u8; stream.state.flags.update(Flags::SANE, true); ReturnCode::Ok diff --git a/zlib-rs/src/inflate/window.rs b/zlib-rs/src/inflate/window.rs index 2d8f8047..496508c4 100644 --- a/zlib-rs/src/inflate/window.rs +++ b/zlib-rs/src/inflate/window.rs @@ -1,6 +1,5 @@ use crate::{ adler32::{adler32, adler32_fold_copy}, - allocate::Allocator, crc32::Crc32Fold, weak_slice::WeakSliceMut, }; @@ -164,7 +163,8 @@ impl<'a> Window<'a> { } } - pub fn new_in(alloc: &Allocator<'a>, window_bits: usize) -> Option { + #[cfg(test)] + pub fn new_in(alloc: &crate::inflate::Allocator<'a>, window_bits: usize) -> Option { let len = (1 << window_bits) + Self::padding(); let ptr = alloc.allocate_zeroed_buffer(len)?; @@ -175,15 +175,16 @@ impl<'a> Window<'a> { }) } - pub fn clone_in(&self, alloc: &Allocator<'a>) -> Option { - let len = self.buf.len(); - let ptr = alloc.allocate_zeroed_buffer(len)?; + pub unsafe fn clone_to(&self, ptr: *mut u8, len: usize) -> Self { + debug_assert_eq!(self.buf.len(), len); - Some(Self { - buf: unsafe { WeakSliceMut::from_raw_parts_mut(ptr.as_ptr(), len) }, + unsafe { core::ptr::copy_nonoverlapping(self.buf.as_ptr(), ptr, len) }; + + Self { + buf: unsafe { WeakSliceMut::from_raw_parts_mut(ptr, len) }, have: self.have, next: self.next, - }) + } } // padding required so that SIMD operations going out-of-bounds are not a problem From 3f67f89e8330fbda2a05dcfe5eb75a5d99332378 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 1 Dec 2025 10:41:17 +0100 Subject: [PATCH 2/3] do not partially deallocate the state --- zlib-rs/src/inflate/infback.rs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/zlib-rs/src/inflate/infback.rs b/zlib-rs/src/inflate/infback.rs index a26da558..f71f68d4 100644 --- a/zlib-rs/src/inflate/infback.rs +++ b/zlib-rs/src/inflate/infback.rs @@ -74,25 +74,17 @@ pub fn back_init(stream: &mut z_stream, config: InflateConfig, window: Window) - stream.state = state_allocation.cast::(); // SAFETY: we've correctly initialized the stream to be an InflateStream - let ret = if let Some(stream) = unsafe { InflateStream::from_stream_mut(stream) } { - stream.state.allocation_start = allocation_start.as_ptr(); - stream.state.total_allocation_size = allocs.total_size; - - stream.state.wbits = config.window_bits as u8; - stream.state.flags.update(Flags::SANE, true); - ReturnCode::Ok - } else { - ReturnCode::StreamError + let Some(stream) = (unsafe { InflateStream::from_stream_mut(stream) }) else { + return ReturnCode::StreamError; }; - if ret != ReturnCode::Ok { - let ptr = stream.state; - stream.state = core::ptr::null_mut(); - // SAFETY: we assume deallocation does not cause UB - unsafe { alloc.deallocate(ptr, 1) }; - } + stream.state.allocation_start = allocation_start.as_ptr(); + stream.state.total_allocation_size = allocs.total_size; - ret + stream.state.wbits = config.window_bits as u8; + stream.state.flags.update(Flags::SANE, true); + + ReturnCode::Ok } pub unsafe fn back( From 506e482826cd0315bb84467933c91972da395dc2 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 1 Dec 2025 11:19:50 +0100 Subject: [PATCH 3/3] add copy continue test --- test-libz-rs-sys/src/inflate.rs | 119 +++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/test-libz-rs-sys/src/inflate.rs b/test-libz-rs-sys/src/inflate.rs index 60e34699..6aaba96f 100644 --- a/test-libz-rs-sys/src/inflate.rs +++ b/test-libz-rs-sys/src/inflate.rs @@ -349,7 +349,6 @@ fn inf(input: &[u8], _what: &str, step: usize, win: i32, len: usize, err: c_int) if matches!(ret, Z_NEED_DICT) { let ret = unsafe { inflateSetDictionary(&mut stream, input.as_ptr(), 1) }; - println!("{ret:?}"); assert_eq!(ret, Z_DATA_ERROR); unsafe { set_mode_dict(&mut stream) } @@ -2750,3 +2749,121 @@ fn done_state_returns_stream_end() { assert_eq!(unsafe { inflate(stream, Z_FINISH) }, Z_STREAM_END); }); } + +#[test] +fn inflate_copy_after_half_input() { + let input = include_bytes!("test-data/compression-corpus/The fastest WASM zlib.md.gzip-9.gz"); + + let _ = assert_eq_rs_ng!({ + let mut stream = MaybeUninit::::zeroed(); + let ret = unsafe { + inflateInit2_( + stream.as_mut_ptr(), + 16 + 15, + zlibVersion(), + core::mem::size_of::() as i32, + ) + }; + let stream = stream.assume_init_mut(); + assert_eq!(ret, Z_OK); + + let mut out = vec![0u8; 16 * 1024]; + + // First, decompress only the first half of the compressed input. + let half = input.len() / 2; + + stream.next_in = input.as_ptr() as *mut _; + stream.avail_in = half as _; + stream.next_out = out.as_mut_ptr(); + stream.avail_out = out.len() as _; + + loop { + let ret = unsafe { inflate(stream, InflateFlush::NoFlush as _) }; + + assert!( + matches!(ret, Z_OK | Z_BUF_ERROR | Z_STREAM_END), + "unexpected inflate return: {}", + ret + ); + + if matches!(ret, Z_STREAM_END) { + unreachable!("we only provide half of the input") + } + + if stream.avail_in == 0 { + break; + } + + if stream.avail_out == 0 { + unreachable!("there is enough output space") + } + } + + // At this point, we’ve decompressed part (or possibly all) of the stream. + let prefix_len = stream.total_out as usize; + + // Make a copy of the inflate state *after* half the input has been processed. + let mut copy = MaybeUninit::::zeroed(); + let ret = unsafe { inflateCopy(copy.as_mut_ptr(), stream) }; + let copy = copy.assume_init_mut(); + assert_eq!(ret, Z_OK); + + // Prepare a separate output buffer for the copy, and copy the already-produced prefix + let mut out_copy = vec![0u8; 16 * 1024]; + out_copy[..prefix_len].copy_from_slice(&out[..prefix_len]); + + // The original stream already has next_out pointing at out[prefix_len]. + // For the copy, we need to point it at the corresponding location in out_copy. + copy.next_out = unsafe { out_copy.as_mut_ptr().add(prefix_len) }; + copy.avail_out = (out_copy.len() - prefix_len) as _; + + // Now feed the *remainder* of the compressed input to both streams. + let remaining = input.len() - half; + if remaining > 0 { + stream.next_in = unsafe { input.as_ptr().add(half) as *mut _ }; + stream.avail_in = remaining as _; + + copy.next_in = stream.next_in; + copy.avail_in = stream.avail_in; + } + + // Decompress the remainder in lockstep on both the original and the copy. + loop { + let ret1 = unsafe { inflate(stream, InflateFlush::NoFlush as _) }; + let ret2 = unsafe { inflate(copy, InflateFlush::NoFlush as _) }; + + // Both streams should behave identically + assert_eq!(ret1, ret2); + assert!( + matches!(ret1, Z_OK | Z_BUF_ERROR | Z_STREAM_END), + "unexpected inflate return: {ret}", + ); + + // Their accounting should remain in sync at all times + assert_eq!(stream.total_out, copy.total_out); + assert_eq!(stream.total_in, copy.total_in); + + if matches!(ret1, Z_STREAM_END) { + break; + } + + // If we somehow run out of input or output space, bail + if stream.avail_in == 0 && copy.avail_in == 0 { + break; + } + if stream.avail_out == 0 || copy.avail_out == 0 { + break; + } + } + + assert_eq!(unsafe { inflateEnd(stream) }, Z_OK); + assert_eq!(unsafe { inflateEnd(copy) }, Z_OK); + + let total = stream.total_out as usize; + assert_eq!(total, copy.total_out as usize); + + assert_eq!(&out[..total], &out_copy[..total]); + + out[..total].to_vec() + }); +}