diff options
Diffstat (limited to 'vendor/gix-features/src/zlib/stream')
-rw-r--r-- | vendor/gix-features/src/zlib/stream/deflate/mod.rs | 96 | ||||
-rw-r--r-- | vendor/gix-features/src/zlib/stream/deflate/tests.rs | 101 | ||||
-rw-r--r-- | vendor/gix-features/src/zlib/stream/inflate.rs | 57 | ||||
-rw-r--r-- | vendor/gix-features/src/zlib/stream/mod.rs | 4 |
4 files changed, 258 insertions, 0 deletions
diff --git a/vendor/gix-features/src/zlib/stream/deflate/mod.rs b/vendor/gix-features/src/zlib/stream/deflate/mod.rs new file mode 100644 index 000000000..55f575ea4 --- /dev/null +++ b/vendor/gix-features/src/zlib/stream/deflate/mod.rs @@ -0,0 +1,96 @@ +use flate2::Compress; + +const BUF_SIZE: usize = 4096 * 8; + +/// A utility to zlib compress anything that is written via its [Write][std::io::Write] implementation. +/// +/// Be sure to call `flush()` when done to finalize the deflate stream. +pub struct Write<W> { + compressor: Compress, + inner: W, + buf: [u8; BUF_SIZE], +} + +mod impls { + use std::io; + + use flate2::{Compress, Compression, FlushCompress, Status}; + + use crate::zlib::stream::deflate; + + impl<W> deflate::Write<W> + where + W: io::Write, + { + /// Create a new instance writing compressed bytes to `inner`. + pub fn new(inner: W) -> deflate::Write<W> { + deflate::Write { + compressor: Compress::new(Compression::fast(), true), + inner, + buf: [0; deflate::BUF_SIZE], + } + } + + /// Reset the compressor, starting a new compression stream. + /// + /// That way multiple streams can be written to the same inner writer. + pub fn reset(&mut self) { + self.compressor.reset(); + } + + /// Consume `self` and return the inner writer. + pub fn into_inner(self) -> W { + self.inner + } + + fn write_inner(&mut self, mut buf: &[u8], flush: FlushCompress) -> io::Result<usize> { + let total_in_when_start = self.compressor.total_in(); + loop { + let last_total_in = self.compressor.total_in(); + let last_total_out = self.compressor.total_out(); + + let status = self + .compressor + .compress(buf, &mut self.buf, flush) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; + + let written = self.compressor.total_out() - last_total_out; + if written > 0 { + self.inner.write_all(&self.buf[..written as usize])?; + } + + match status { + Status::StreamEnd => return Ok((self.compressor.total_in() - total_in_when_start) as usize), + Status::Ok | Status::BufError => { + let consumed = self.compressor.total_in() - last_total_in; + buf = &buf[consumed as usize..]; + + // output buffer still makes progress + if self.compressor.total_out() > last_total_out { + continue; + } + // input still makes progress + if self.compressor.total_in() > last_total_in { + continue; + } + // input also makes no progress anymore, need more so leave with what we have + return Ok((self.compressor.total_in() - total_in_when_start) as usize); + } + } + } + } + } + + impl<W: io::Write> io::Write for deflate::Write<W> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.write_inner(buf, FlushCompress::None) + } + + fn flush(&mut self) -> io::Result<()> { + self.write_inner(&[], FlushCompress::Finish).map(|_| ()) + } + } +} + +#[cfg(test)] +mod tests; diff --git a/vendor/gix-features/src/zlib/stream/deflate/tests.rs b/vendor/gix-features/src/zlib/stream/deflate/tests.rs new file mode 100644 index 000000000..ba0dd2a2c --- /dev/null +++ b/vendor/gix-features/src/zlib/stream/deflate/tests.rs @@ -0,0 +1,101 @@ +mod deflate_stream { + use std::{ + io, + io::{Read, Write}, + }; + + use bstr::ByteSlice; + use flate2::Decompress; + + use crate::zlib::stream::deflate; + + /// Provide streaming decompression using the `std::io::Read` trait. + /// If `std::io::BufReader` is used, an allocation for the input buffer will be performed. + struct InflateReader<R> { + inner: R, + decompressor: Decompress, + } + + impl<R> InflateReader<R> + where + R: io::BufRead, + { + pub fn from_read(read: R) -> InflateReader<R> { + InflateReader { + decompressor: Decompress::new(true), + inner: read, + } + } + } + + impl<R> io::Read for InflateReader<R> + where + R: io::BufRead, + { + fn read(&mut self, into: &mut [u8]) -> io::Result<usize> { + crate::zlib::stream::inflate::read(&mut self.inner, &mut self.decompressor, into) + } + } + + #[test] + fn small_file_decompress() -> Result<(), Box<dyn std::error::Error>> { + fn fixture_path(path: &str) -> std::path::PathBuf { + std::path::PathBuf::from("tests/fixtures").join(path) + } + let r = InflateReader::from_read(io::BufReader::new(std::fs::File::open(fixture_path( + "objects/37/d4e6c5c48ba0d245164c4e10d5f41140cab980", + ))?)); + let mut bytes = r.bytes(); + let content = bytes.by_ref().take(16).collect::<Result<Vec<_>, _>>()?; + assert_eq!(content.as_slice().as_bstr(), b"blob 9\0hi there\n".as_bstr()); + assert!(bytes.next().is_none()); + Ok(()) + } + + #[test] + fn all_at_once() -> Result<(), Box<dyn std::error::Error>> { + let mut w = deflate::Write::new(Vec::new()); + assert_eq!(w.write(b"hello")?, 5); + w.flush()?; + + let out = w.inner; + assert!(out.len() == 12 || out.len() == 13); + + assert_deflate_buffer(out, b"hello") + } + + fn assert_deflate_buffer(out: Vec<u8>, expected: &[u8]) -> Result<(), Box<dyn std::error::Error>> { + let mut actual = Vec::new(); + InflateReader::from_read(out.as_slice()).read_to_end(&mut actual)?; + assert_eq!(actual, expected); + Ok(()) + } + + #[test] + fn big_file_small_writes() -> Result<(), Box<dyn std::error::Error>> { + let mut w = deflate::Write::new(Vec::new()); + let bytes = include_bytes!( + "../../../../tests/fixtures/objects/pack/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack" + ); + for chunk in bytes.chunks(2) { + assert_eq!(w.write(chunk)?, chunk.len()); + } + w.flush()?; + + assert_deflate_buffer(w.inner, bytes) + } + + #[test] + fn big_file_a_few_big_writes() -> Result<(), Box<dyn std::error::Error>> { + let mut w = deflate::Write::new(Vec::new()); + let bytes = include_bytes!( + "../../../../tests/fixtures/objects/pack/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack" + ); + for chunk in bytes.chunks(4096 * 9) { + assert_eq!(w.write(chunk)?, chunk.len()); + } + w.flush()?; + + assert_deflate_buffer(w.inner, bytes) + } +} diff --git a/vendor/gix-features/src/zlib/stream/inflate.rs b/vendor/gix-features/src/zlib/stream/inflate.rs new file mode 100644 index 000000000..007ecedc6 --- /dev/null +++ b/vendor/gix-features/src/zlib/stream/inflate.rs @@ -0,0 +1,57 @@ +use std::{io, io::BufRead}; + +use flate2::{Decompress, FlushDecompress, Status}; + +/// The boxed variant is faster for what we do (moving the decompressor in and out a lot) +pub struct ReadBoxed<R> { + /// The reader from which bytes should be decompressed. + pub inner: R, + /// The decompressor doing all the work. + pub decompressor: Box<Decompress>, +} + +impl<R> io::Read for ReadBoxed<R> +where + R: BufRead, +{ + fn read(&mut self, into: &mut [u8]) -> io::Result<usize> { + read(&mut self.inner, &mut self.decompressor, into) + } +} + +/// Read bytes from `rd` and decompress them using `state` into a pre-allocated fitting buffer `dst`, returning the amount of bytes written. +pub fn read(rd: &mut impl BufRead, state: &mut Decompress, mut dst: &mut [u8]) -> io::Result<usize> { + let mut total_written = 0; + loop { + let (written, consumed, ret, eof); + { + let input = rd.fill_buf()?; + eof = input.is_empty(); + let before_out = state.total_out(); + let before_in = state.total_in(); + let flush = if eof { + FlushDecompress::Finish + } else { + FlushDecompress::None + }; + ret = state.decompress(input, dst, flush); + written = (state.total_out() - before_out) as usize; + total_written += written; + dst = &mut dst[written..]; + consumed = (state.total_in() - before_in) as usize; + } + rd.consume(consumed); + + match ret { + // The stream has officially ended, nothing more to do here. + Ok(Status::StreamEnd) => return Ok(total_written), + // Either input our output are depleted even though the stream is not depleted yet. + Ok(Status::Ok) | Ok(Status::BufError) if eof || dst.is_empty() => return Ok(total_written), + // Some progress was made in both the input and the output, it must continue to reach the end. + Ok(Status::Ok) | Ok(Status::BufError) if consumed != 0 || written != 0 => continue, + // A strange state, where zlib makes no progress but isn't done either. Call it out. + Ok(Status::Ok) | Ok(Status::BufError) => unreachable!("Definitely a bug somewhere"), + Err(..) => return Err(io::Error::new(io::ErrorKind::InvalidInput, "corrupt deflate stream")), + } + } +} diff --git a/vendor/gix-features/src/zlib/stream/mod.rs b/vendor/gix-features/src/zlib/stream/mod.rs new file mode 100644 index 000000000..7fb239d36 --- /dev/null +++ b/vendor/gix-features/src/zlib/stream/mod.rs @@ -0,0 +1,4 @@ +/// +pub mod deflate; +/// +pub mod inflate; |