diff --git a/crates/async-compression/Cargo.toml b/crates/async-compression/Cargo.toml index 8671e5a1..d30004f2 100644 --- a/crates/async-compression/Cargo.toml +++ b/crates/async-compression/Cargo.toml @@ -139,6 +139,10 @@ required-features = ["zlib", "tokio"] name = "zstd_gzip" required-features = ["zstd", "gzip", "tokio"] +[[test]] +name = "empty_stream" +required-features = ["zstd", "tokio"] + [[example]] name = "lzma_filters" required-features = ["xz", "tokio"] diff --git a/crates/async-compression/src/generic/bufread/decoder.rs b/crates/async-compression/src/generic/bufread/decoder.rs index 9cad7380..0033f021 100644 --- a/crates/async-compression/src/generic/bufread/decoder.rs +++ b/crates/async-compression/src/generic/bufread/decoder.rs @@ -17,6 +17,7 @@ enum State { pub struct Decoder { state: State, multiple_members: bool, + received_data: bool, } impl Default for Decoder { @@ -24,6 +25,7 @@ impl Default for Decoder { Self { state: State::Decoding, multiple_members: false, + received_data: false, } } } @@ -48,8 +50,16 @@ impl Decoder { // reader has returned EOF. self.multiple_members = false; - State::Flushing + // Empty stream (no data received) - return empty output + if !self.received_data { + State::Done + } else { + State::Flushing + } } else { + if !input.unwritten().is_empty() { + self.received_data = true; + } match decoder.decode(input, output) { Ok(true) => State::Flushing, // ignore the first error, occurs when input is empty diff --git a/crates/async-compression/tests/empty_stream.rs b/crates/async-compression/tests/empty_stream.rs new file mode 100644 index 00000000..44dc7eba --- /dev/null +++ b/crates/async-compression/tests/empty_stream.rs @@ -0,0 +1,19 @@ +//! Test that bufread decoders handle empty input streams (immediate EOF). + +#[macro_use] +mod utils; + +#[tokio::test] +async fn zstd_empty_stream() { + use async_compression::tokio::bufread::ZstdDecoder; + use std::io::Cursor; + use tokio::io::AsyncReadExt; + + let empty: &[u8] = &[]; + let mut decoder = ZstdDecoder::new(Cursor::new(empty)); + let mut output = Vec::new(); + let result = decoder.read_to_end(&mut output).await; + // Empty input should return Ok(0), not error with "zstd stream did not finish" + assert!(result.is_ok(), "empty stream failed: {:?}", result); + assert!(output.is_empty()); +}