Skip to content

Commit

Permalink
fix bug in the quick algorithm (Z_BEST_SPEED) where a block was close…
Browse files Browse the repository at this point in the history
…d where it should not be
  • Loading branch information
folkertdev committed Aug 27, 2024
1 parent 85bc778 commit 5483e80
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 5 deletions.
102 changes: 102 additions & 0 deletions test-libz-rs-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1344,3 +1344,105 @@ mod coverage {
});
}
}

#[test]
fn deflate_chunked_input_all_levels() {
use std::io::{Read, Write};
use std::mem::MaybeUninit;

assert_eq_rs_ng!({
// translated from zpipe.c
fn def<R: Read, W: Write, const CHUNK: usize>(
source: &mut R,
dest: &mut W,
level: i32,
) -> i32 {
let mut strm = MaybeUninit::zeroed();

let mut in_buf = [0u8; CHUNK];
let mut out_buf = [0u8; CHUNK];

let mut ret;

ret = unsafe {
deflateInit_(
strm.as_mut_ptr(),
level,
zlibVersion(),
core::mem::size_of::<z_stream>() as _,
)
};
if ret != Z_OK {
return ret;
}

let strm = unsafe { strm.assume_init_mut() };

loop {
strm.avail_in = match source.read(&mut in_buf) {
Ok(0) => 0,
Ok(n) => n as u32,
Err(_) => {
unsafe { deflateEnd(strm) };
return Z_ERRNO;
}
};
strm.next_in = in_buf.as_mut_ptr();

let flush = if strm.avail_in == 0 {
Z_FINISH
} else {
Z_NO_FLUSH
};

loop {
strm.avail_out = CHUNK as u32;
strm.next_out = out_buf.as_mut_ptr();

ret = unsafe { deflate(strm, flush) };
assert_ne!(ret, Z_STREAM_ERROR);

let have = CHUNK - strm.avail_out as usize;
if dest.write_all(&out_buf[..have]).is_err() {
unsafe { deflateEnd(strm) };
return Z_ERRNO;
}

if strm.avail_out != 0 {
break;
}
}

if flush == Z_FINISH {
break;
}
}

assert_eq!(ret, Z_STREAM_END);

unsafe { deflateEnd(strm) };

Z_OK
}

fn run<const CHUNK: usize>(level: i32) -> Vec<u8> {
let input: Vec<_> = (0..4096).map(|x| x as u8).collect();
let mut output = Vec::new();

def::<_, _, CHUNK>(&mut input.as_slice(), &mut output, level);

output
}

let mut outputs = Vec::new();

for level in [-1, 1, 2, 3, 4, 5, 6, 7, 8, 9] {
outputs.push((level, 4, run::<{ 4 }>(level)));
outputs.push((level, 1 << 10, run::<{ 1 << 10 }>(level)));
outputs.push((level, 1 << 12, run::<{ 1 << 12 }>(level)));
outputs.push((level, 1 << 14, run::<{ 1 << 14 }>(level)));
}

outputs
});
}
2 changes: 1 addition & 1 deletion zlib-rs/src/deflate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,7 @@ impl<'a> BitWriter<'a> {

#[cfg(feature = "ZLIB_DEBUG")]
if let Some(c) = char::from_u32(c as u32) {
if c.is_ascii() && !c.is_whitespace() {
if isgraph(c as u8) {
trace!(" '{}' ", c);
}
}
Expand Down
8 changes: 4 additions & 4 deletions zlib-rs/src/deflate/algorithm/quick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ pub fn deflate_quick(stream: &mut DeflateStream, flush: DeflateFlush) -> BlockSt
}

macro_rules! quick_end_block {
() => {
($last:expr) => {
if state.block_open > 0 {
state
.bit_writer
.emit_end_block_and_align(&StaticTreeDesc::L.static_tree, last);
.emit_end_block_and_align(&StaticTreeDesc::L.static_tree, $last);
state.block_open = 0;
state.block_start = state.strstart as isize;
flush_pending(stream);
Expand All @@ -46,7 +46,7 @@ pub fn deflate_quick(stream: &mut DeflateStream, flush: DeflateFlush) -> BlockSt

if last && state.block_open != 2 {
/* Emit end of previous block */
quick_end_block!();
quick_end_block!(false);
/* Emit start of last block */
quick_start_block!();
} else if state.block_open == 0 && state.lookahead > 0 {
Expand Down Expand Up @@ -135,7 +135,7 @@ pub fn deflate_quick(stream: &mut DeflateStream, flush: DeflateFlush) -> BlockSt

state.insert = Ord::min(state.strstart, STD_MIN_MATCH - 1);

quick_end_block!();
quick_end_block!(last);

if last {
BlockState::FinishDone
Expand Down

0 comments on commit 5483e80

Please sign in to comment.