diff --git a/assets/zeros64.zst b/assets/zeros64.zst new file mode 100644 index 00000000..37cb6753 Binary files /dev/null and b/assets/zeros64.zst differ diff --git a/src/stream/read/mod.rs b/src/stream/read/mod.rs index a3a947b0..8a82cc86 100644 --- a/src/stream/read/mod.rs +++ b/src/stream/read/mod.rs @@ -93,7 +93,9 @@ impl<'a, R: BufRead> Decoder<'a, R> { /// /// Calling `finish()` is not *required* after reading a stream - /// just use it if you need to get the `Read` back. - pub fn finish(self) -> R { + pub fn finish(mut self) -> R { + // Ensure the input buffers have been flushed by reading to a zero-length buffer. + let _ = self.reader.read(&mut [0; 0]); self.reader.into_inner() } diff --git a/tests/issue_251.rs b/tests/issue_251.rs new file mode 100644 index 00000000..37ad1588 --- /dev/null +++ b/tests/issue_251.rs @@ -0,0 +1,38 @@ +use std::io::Read; + +#[test] +fn test_issue_251() { + // This is 64 compressed zero bytes. + let compressed_data = include_bytes!("../assets/zeros64.zst"); + let decompressed_size = 64; + + // Construct a decompressor using `with_buffer`. This should be ok as + // `Cursor` is `BufRead`. + let reader = std::io::Cursor::new(compressed_data); + let mut decomp = zstd::Decoder::with_buffer(reader).unwrap(); + + // Count how many bytes we decompress. + let mut total = 0; + + // Decompress four bytes at a time (this is necessary to trigger underrun + // behaviour). + for _ in 0..(decompressed_size / 4) { + let mut buf = [0u8; 4]; + let count = decomp.read(&mut buf).unwrap(); + total += count; + } + + // Finish reading and get the buffer back. + let reader = decomp.finish(); + + // The cursor should now be at the end of the compressed data. + println!("We read {total}/{decompressed_size} decompressed bytes"); + println!( + "The underlying cursor is at position {} of {} compressed bytes", + reader.position(), + compressed_data.len() + ); + + assert_eq!(total, decompressed_size); + assert_eq!(reader.position() as usize, compressed_data.len()); +}