Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions gix-features/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ crc32 = ["dep:crc32fast"]

## Enable the usage of zlib-related utilities to compress or decompress data.
## This enables and uses the high-performance `zlib-rs` backend.
zlib = ["dep:libz-rs-sys", "dep:thiserror"]
zlib = ["dep:zlib-rs", "dep:thiserror"]

#! ### Other

Expand Down Expand Up @@ -108,7 +108,7 @@ bytesize = { version = "2.3.1", optional = true }
bytes = { version = "1.0.0", optional = true }

# zlib module
libz-rs-sys = { version = "0.5.2", optional = true }
zlib-rs = { git = "https://github.com/trifectatechfoundation/zlib-rs.git", rev = "bc0c9fd8b29a5ba64869717f47cb241008621f30", optional = true, default-features = false, features = ["std", "rust-allocator"] }
thiserror = { version = "2.0.17", optional = true }

# Note: once_cell is kept for OnceCell type because std::sync::OnceLock::get_or_try_init() is not yet stable.
Expand Down
170 changes: 50 additions & 120 deletions gix-features/src/zlib/mod.rs
Original file line number Diff line number Diff line change
@@ -1,79 +1,8 @@
use std::ffi::c_int;

/// A type to hold all state needed for decompressing a ZLIB encoded stream.
pub struct Decompress(libz_rs_sys::z_stream);

unsafe impl Sync for Decompress {}
unsafe impl Send for Decompress {}

impl Default for Decompress {
fn default() -> Self {
Self::new()
}
}

impl Decompress {
/// The amount of bytes consumed from the input so far.
pub fn total_in(&self) -> u64 {
self.0.total_in as _
}

/// The amount of decompressed bytes that have been written to the output thus far.
pub fn total_out(&self) -> u64 {
self.0.total_out as _
}

/// Create a new instance. Note that it allocates in various ways and thus should be re-used.
pub fn new() -> Self {
let mut this = libz_rs_sys::z_stream::default();

unsafe {
libz_rs_sys::inflateInit_(
&mut this,
libz_rs_sys::zlibVersion(),
core::mem::size_of::<libz_rs_sys::z_stream>() as core::ffi::c_int,
);
}

Self(this)
}

/// Reset the state to allow handling a new stream.
pub fn reset(&mut self) {
unsafe { libz_rs_sys::inflateReset(&mut self.0) };
}

/// Decompress `input` and write all decompressed bytes into `output`, with `flush` defining some details about this.
pub fn decompress(
&mut self,
input: &[u8],
output: &mut [u8],
flush: FlushDecompress,
) -> Result<Status, DecompressError> {
self.0.avail_in = input.len() as _;
self.0.avail_out = output.len() as _;

self.0.next_in = input.as_ptr();
self.0.next_out = output.as_mut_ptr();

match unsafe { libz_rs_sys::inflate(&mut self.0, flush as _) } {
libz_rs_sys::Z_OK => Ok(Status::Ok),
libz_rs_sys::Z_BUF_ERROR => Ok(Status::BufError),
libz_rs_sys::Z_STREAM_END => Ok(Status::StreamEnd),

libz_rs_sys::Z_STREAM_ERROR => Err(DecompressError::StreamError),
libz_rs_sys::Z_DATA_ERROR => Err(DecompressError::DataError),
libz_rs_sys::Z_MEM_ERROR => Err(DecompressError::InsufficientMemory),
err => Err(DecompressError::Unknown { err }),
}
}
}

impl Drop for Decompress {
fn drop(&mut self) {
unsafe { libz_rs_sys::inflateEnd(&mut self.0) };
}
}
pub use zlib_rs::Inflate as Decompress;
pub use zlib_rs::InflateFlush as FlushDecompress;
pub use zlib_rs::Status;

/// The error produced by [`Decompress::decompress()`].
#[derive(Debug, thiserror::Error)]
Expand All @@ -89,43 +18,15 @@ pub enum DecompressError {
Unknown { err: c_int },
}

/// The status returned by [`Decompress::decompress()`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The decompress operation went well. Not to be confused with `StreamEnd`, so one can continue
/// the decompression.
Ok,
/// An error occurred when decompression.
BufError,
/// The stream was fully decompressed.
StreamEnd,
}

/// Values which indicate the form of flushing to be used when
/// decompressing in-memory data.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
#[allow(clippy::unnecessary_cast)]
pub enum FlushDecompress {
/// A typical parameter for passing to compression/decompression functions,
/// this indicates that the underlying stream to decide how much data to
/// accumulate before producing output in order to maximize compression.
None = libz_rs_sys::Z_NO_FLUSH as isize,

/// All pending output is flushed to the output buffer and the output is
/// aligned on a byte boundary so that the decompressor can get all input
/// data available so far.
///
/// Flushing may degrade compression for some compression algorithms and so
/// it should only be used when necessary. This will complete the current
/// deflate block and follow it with an empty stored block.
Sync = libz_rs_sys::Z_SYNC_FLUSH as isize,

/// Pending input is processed and pending output is flushed.
///
/// The return value may indicate that the stream is not yet done and more
/// data has yet to be processed.
Finish = libz_rs_sys::Z_FINISH as isize,
impl From<zlib_rs::InflateError> for DecompressError {
fn from(value: zlib_rs::InflateError) -> Self {
match value {
zlib_rs::InflateError::NeedDict { .. } => Self::Unknown { err: 2 },
zlib_rs::InflateError::StreamError => Self::StreamError,
zlib_rs::InflateError::DataError => Self::DataError,
zlib_rs::InflateError::MemError => Self::InsufficientMemory,
}
}
}

/// non-streaming interfaces for decompression
Expand All @@ -144,28 +45,57 @@ pub mod inflate {
}

/// Decompress a few bytes of a zlib stream without allocation
#[derive(Default)]
pub struct Inflate {
/// The actual decompressor doing all the work.
pub state: Decompress,
pub state: zlib_rs::Inflate,
}

impl Default for Inflate {
fn default() -> Self {
Self {
state: zlib_rs::Inflate::new(true, 15),
}
}
}

impl Inflate {
/// The amount of bytes consumed from the input so far.
pub fn total_in(&self) -> u64 {
self.state.total_in()
}

/// The amount of decompressed bytes that have been written to the output thus far.
pub fn total_out(&self) -> u64 {
self.state.total_out()
}

/// Decompress `input` and write all decompressed bytes into `output`, with `flush` defining some details about this.
pub fn decompress(
&mut self,
input: &[u8],
output: &mut [u8],
flush: zlib_rs::InflateFlush,
) -> Result<Status, zlib_rs::InflateError> {
self.state.decompress(input, output, flush)
}

/// Run the decompressor exactly once. Cannot be run multiple times
pub fn once(&mut self, input: &[u8], out: &mut [u8]) -> Result<(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, FlushDecompress::None)?;
Ok((
status,
(self.state.total_in() - before_in) as usize,
(self.state.total_out() - before_out) as usize,
))
match self.state.decompress(input, out, FlushDecompress::NoFlush) {
Ok(status) => Ok((
status,
(self.state.total_in() - before_in) as usize,
(self.state.total_out() - before_out) as usize,
)),
Err(e) => Err(inflate::Error::Inflate(e.into())),
}
}

/// Ready this instance for decoding another data stream.
pub fn reset(&mut self) {
self.state.reset();
self.state.reset(true);
}
}

Expand Down
Loading
Loading