From ef24de24a82fe681581cc130f342363c47c0969a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 7 Jun 2024 07:48:48 +0200 Subject: Merging upstream version 1.75.0+dfsg1. Signed-off-by: Daniel Baumann --- vendor/gix-features-0.35.0/src/zlib/mod.rs | 52 ++++++++++ .../src/zlib/stream/deflate/mod.rs | 113 +++++++++++++++++++++ .../src/zlib/stream/deflate/tests.rs | 101 ++++++++++++++++++ .../gix-features-0.35.0/src/zlib/stream/inflate.rs | 40 ++++++++ vendor/gix-features-0.35.0/src/zlib/stream/mod.rs | 4 + 5 files changed, 310 insertions(+) create mode 100644 vendor/gix-features-0.35.0/src/zlib/mod.rs create mode 100644 vendor/gix-features-0.35.0/src/zlib/stream/deflate/mod.rs create mode 100644 vendor/gix-features-0.35.0/src/zlib/stream/deflate/tests.rs create mode 100644 vendor/gix-features-0.35.0/src/zlib/stream/inflate.rs create mode 100644 vendor/gix-features-0.35.0/src/zlib/stream/mod.rs (limited to 'vendor/gix-features-0.35.0/src/zlib') diff --git a/vendor/gix-features-0.35.0/src/zlib/mod.rs b/vendor/gix-features-0.35.0/src/zlib/mod.rs new file mode 100644 index 000000000..f55660075 --- /dev/null +++ b/vendor/gix-features-0.35.0/src/zlib/mod.rs @@ -0,0 +1,52 @@ +pub use flate2::{Decompress, Status}; + +/// non-streaming interfaces for decompression +pub mod inflate { + /// The error returned by various [Inflate methods][super::Inflate] + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("Could not write all bytes when decompressing content")] + WriteInflated(#[from] std::io::Error), + #[error("Could not decode zip stream, status was '{0:?}'")] + Inflate(#[from] flate2::DecompressError), + #[error("The zlib status indicated an error, status was '{0:?}'")] + Status(flate2::Status), + } +} + +/// Decompress a few bytes of a zlib stream without allocation +pub struct Inflate { + /// The actual decompressor doing all the work. + pub state: Decompress, +} + +impl Default for Inflate { + fn default() -> Self { + Inflate { + state: Decompress::new(true), + } + } +} + +impl Inflate { + /// Run the decompressor exactly once. Cannot be run multiple times + pub fn once(&mut self, input: &[u8], out: &mut [u8]) -> Result<(flate2::Status, usize, usize), inflate::Error> { + let before_in = self.state.total_in(); + let before_out = self.state.total_out(); + let status = self.state.decompress(input, out, flate2::FlushDecompress::None)?; + Ok(( + status, + (self.state.total_in() - before_in) as usize, + (self.state.total_out() - before_out) as usize, + )) + } + + /// Ready this instance for decoding another data stream. + pub fn reset(&mut self) { + self.state.reset(true); + } +} + +/// +pub mod stream; diff --git a/vendor/gix-features-0.35.0/src/zlib/stream/deflate/mod.rs b/vendor/gix-features-0.35.0/src/zlib/stream/deflate/mod.rs new file mode 100644 index 000000000..567e8fece --- /dev/null +++ b/vendor/gix-features-0.35.0/src/zlib/stream/deflate/mod.rs @@ -0,0 +1,113 @@ +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 { + compressor: Compress, + inner: W, + buf: [u8; BUF_SIZE], +} + +impl Clone for Write +where + W: Clone, +{ + fn clone(&self) -> Self { + Write { + compressor: impls::new_compress(), + inner: self.inner.clone(), + buf: self.buf, + } + } +} + +mod impls { + use std::io; + + use flate2::{Compress, Compression, FlushCompress, Status}; + + use crate::zlib::stream::deflate; + + pub(crate) fn new_compress() -> Compress { + Compress::new(Compression::fast(), true) + } + + impl deflate::Write + where + W: io::Write, + { + /// Create a new instance writing compressed bytes to `inner`. + pub fn new(inner: W) -> deflate::Write { + deflate::Write { + compressor: new_compress(), + 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 { + 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 io::Write for deflate::Write { + fn write(&mut self, buf: &[u8]) -> io::Result { + 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-0.35.0/src/zlib/stream/deflate/tests.rs b/vendor/gix-features-0.35.0/src/zlib/stream/deflate/tests.rs new file mode 100644 index 000000000..7c5865e0b --- /dev/null +++ b/vendor/gix-features-0.35.0/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 { + inner: R, + decompressor: Decompress, + } + + impl InflateReader + where + R: io::BufRead, + { + pub fn from_read(read: R) -> InflateReader { + InflateReader { + decompressor: Decompress::new(true), + inner: read, + } + } + } + + impl io::Read for InflateReader + where + R: io::BufRead, + { + fn read(&mut self, into: &mut [u8]) -> io::Result { + crate::zlib::stream::inflate::read(&mut self.inner, &mut self.decompressor, into) + } + } + + #[test] + fn small_file_decompress() -> Result<(), Box> { + 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::, _>>()?; + 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> { + 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, expected: &[u8]) -> Result<(), Box> { + 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> { + let mut w = deflate::Write::new(Vec::new()); + let bytes = include_bytes!( + "../../../../../gix-odb/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> { + let mut w = deflate::Write::new(Vec::new()); + let bytes = include_bytes!( + "../../../../../gix-odb/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-0.35.0/src/zlib/stream/inflate.rs b/vendor/gix-features-0.35.0/src/zlib/stream/inflate.rs new file mode 100644 index 000000000..11dc92800 --- /dev/null +++ b/vendor/gix-features-0.35.0/src/zlib/stream/inflate.rs @@ -0,0 +1,40 @@ +use std::{io, io::BufRead}; + +use flate2::{Decompress, FlushDecompress, Status}; + +/// 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 { + 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 | 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 | 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 | 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-0.35.0/src/zlib/stream/mod.rs b/vendor/gix-features-0.35.0/src/zlib/stream/mod.rs new file mode 100644 index 000000000..7fb239d36 --- /dev/null +++ b/vendor/gix-features-0.35.0/src/zlib/stream/mod.rs @@ -0,0 +1,4 @@ +/// +pub mod deflate; +/// +pub mod inflate; -- cgit v1.2.3