Skip to content
Merged
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
6 changes: 3 additions & 3 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 = { version = "0.5.4", optional = true }
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
79 changes: 34 additions & 45 deletions gix-features/src/zlib/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use std::ffi::c_int;
use zlib_rs::InflateError;

/// 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 {}
pub struct Decompress(zlib_rs::Inflate);

impl Default for Decompress {
fn default() -> Self {
Expand All @@ -15,32 +12,23 @@ impl Default for Decompress {
impl Decompress {
/// The amount of bytes consumed from the input so far.
pub fn total_in(&self) -> u64 {
self.0.total_in as _
self.0.total_in()
}

/// 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 _
self.0.total_out()
}

/// 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)
let inner = zlib_rs::Inflate::new(true, zlib_rs::MAX_WBITS as u8);
Self(inner)
}

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

/// Decompress `input` and write all decompressed bytes into `output`, with `flush` defining some details about this.
Expand All @@ -50,31 +38,21 @@ impl Decompress {
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 }),
let inflate_flush = match flush {
FlushDecompress::None => zlib_rs::InflateFlush::NoFlush,
FlushDecompress::Sync => zlib_rs::InflateFlush::SyncFlush,
FlushDecompress::Finish => zlib_rs::InflateFlush::Finish,
};

let status = self.0.decompress(input, output, inflate_flush)?;
match status {
zlib_rs::Status::Ok => Ok(Status::Ok),
zlib_rs::Status::BufError => Ok(Status::BufError),
zlib_rs::Status::StreamEnd => Ok(Status::StreamEnd),
}
}
}

impl Drop for Decompress {
fn drop(&mut self) {
unsafe { libz_rs_sys::inflateEnd(&mut self.0) };
}
}

/// The error produced by [`Decompress::decompress()`].
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
Expand All @@ -85,8 +63,19 @@ pub enum DecompressError {
InsufficientMemory,
#[error("Invalid input data")]
DataError,
#[error("An unknown error occurred: {err}")]
Unknown { err: c_int },
#[error("Decompressing this input requires a dictionary")]
NeedDict,
}

impl From<zlib_rs::InflateError> for DecompressError {
fn from(value: InflateError) -> Self {
match value {
InflateError::NeedDict { .. } => DecompressError::NeedDict,
InflateError::StreamError => DecompressError::StreamError,
InflateError::DataError => DecompressError::DataError,
InflateError::MemError => DecompressError::InsufficientMemory,
}
}
}

/// The status returned by [`Decompress::decompress()`].
Expand All @@ -110,7 +99,7 @@ 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,
None = 0,

/// 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
Expand All @@ -119,13 +108,13 @@ pub enum FlushDecompress {
/// 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,
Sync = 2,

/// 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,
Finish = 4,
}

/// non-streaming interfaces for decompression
Expand Down
84 changes: 36 additions & 48 deletions gix-features/src/zlib/stream/deflate/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::zlib::Status;
use std::ffi::c_int;
use zlib_rs::DeflateError;

const BUF_SIZE: usize = 4096 * 8;

Expand All @@ -26,10 +26,7 @@ where
}

/// Hold all state needed for compressing data.
pub struct Compress(libz_rs_sys::z_stream);

unsafe impl Sync for Compress {}
unsafe impl Send for Compress {}
pub struct Compress(zlib_rs::Deflate);

impl Default for Compress {
fn default() -> Self {
Expand All @@ -40,72 +37,63 @@ impl Default for Compress {
impl Compress {
/// The number of bytes that were read from the input.
pub fn total_in(&self) -> u64 {
self.0.total_in as _
self.0.total_in()
}

/// The number of compressed bytes that were written to the output.
pub fn total_out(&self) -> u64 {
self.0.total_out as _
self.0.total_out()
}

/// Create a new instance - this allocates so should be done with care.
pub fn new() -> Self {
let mut this = libz_rs_sys::z_stream::default();

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

Self(this)
let inner = zlib_rs::Deflate::new(zlib_rs::c_api::Z_BEST_SPEED, true, zlib_rs::MAX_WBITS as u8);
Self(inner)
}

/// Prepare the instance for a new stream.
pub fn reset(&mut self) {
unsafe { libz_rs_sys::deflateReset(&mut self.0) };
self.0.reset();
}

/// Compress `input` and write compressed bytes to `output`, with `flush` controlling additional characteristics.
pub fn compress(&mut self, input: &[u8], output: &mut [u8], flush: FlushCompress) -> Result<Status, CompressError> {
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::deflate(&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(CompressError::StreamError),
libz_rs_sys::Z_MEM_ERROR => Err(CompressError::InsufficientMemory),
err => Err(CompressError::Unknown { err }),
let flush = match flush {
FlushCompress::None => zlib_rs::DeflateFlush::NoFlush,
FlushCompress::Partial => zlib_rs::DeflateFlush::PartialFlush,
FlushCompress::Sync => zlib_rs::DeflateFlush::SyncFlush,
FlushCompress::Full => zlib_rs::DeflateFlush::FullFlush,
FlushCompress::Finish => zlib_rs::DeflateFlush::Finish,
};
let status = self.0.compress(input, output, flush)?;
match status {
zlib_rs::Status::Ok => Ok(Status::Ok),
zlib_rs::Status::BufError => Ok(Status::BufError),
zlib_rs::Status::StreamEnd => Ok(Status::StreamEnd),
}
}
}

impl Drop for Compress {
fn drop(&mut self) {
unsafe { libz_rs_sys::deflateEnd(&mut self.0) };
}
}

/// The error produced by [`Compress::compress()`].
#[derive(Debug, thiserror::Error)]
#[error("{msg}")]
#[allow(missing_docs)]
pub enum CompressError {
#[error("stream error")]
StreamError,
#[error("The input is not a valid deflate stream.")]
DataError,
#[error("Not enough memory")]
InsufficientMemory,
#[error("An unknown error occurred: {err}")]
Unknown { err: c_int },
}

impl From<zlib_rs::DeflateError> for CompressError {
fn from(value: zlib_rs::DeflateError) -> Self {
match value {
DeflateError::StreamError => CompressError::StreamError,
DeflateError::DataError => CompressError::DataError,
DeflateError::MemError => CompressError::InsufficientMemory,
}
}
}

/// Values which indicate the form of flushing to be used when compressing
Expand All @@ -117,7 +105,7 @@ pub enum FlushCompress {
/// 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,
None = 0,

/// All pending output is flushed to the output buffer, but the output is
/// not aligned to a byte boundary.
Expand All @@ -127,7 +115,7 @@ pub enum FlushCompress {
/// with an empty fixed codes block that is 10 bits long, and it assures
/// that enough bytes are output in order for the decompressor to finish the
/// block before the empty fixed code block.
Partial = libz_rs_sys::Z_PARTIAL_FLUSH as isize,
Partial = 1,

/// 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
Expand All @@ -136,20 +124,20 @@ pub enum FlushCompress {
/// 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,
Sync = 2,

/// All output is flushed as with `Flush::Sync` and the compression state is
/// reset so decompression can restart from this point if previous
/// compressed data has been damaged or if random access is desired.
///
/// Using this option too often can seriously degrade compression.
Full = libz_rs_sys::Z_FULL_FLUSH as isize,
Full = 3,

/// 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,
Finish = 4,
}

mod impls {
Expand Down
Loading