summaryrefslogtreecommitdiffstats
path: root/vendor/gix-features/src/zlib/stream
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-features/src/zlib/stream
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz
rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-features/src/zlib/stream')
-rw-r--r--vendor/gix-features/src/zlib/stream/deflate/mod.rs96
-rw-r--r--vendor/gix-features/src/zlib/stream/deflate/tests.rs101
-rw-r--r--vendor/gix-features/src/zlib/stream/inflate.rs57
-rw-r--r--vendor/gix-features/src/zlib/stream/mod.rs4
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;