diff --git a/Cargo.lock b/Cargo.lock index cba42ea32ff..aeae7cc9119 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1691,12 +1691,12 @@ dependencies = [ "gix-trace", "gix-utils", "libc", - "libz-rs-sys", "once_cell", "parking_lot", "prodash", "thiserror 2.0.17", "walkdir", + "zlib-rs", ] [[package]] @@ -5874,6 +5874,6 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" +checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" diff --git a/gix-features/Cargo.toml b/gix-features/Cargo.toml index 672529edabc..396a06a929c 100644 --- a/gix-features/Cargo.toml +++ b/gix-features/Cargo.toml @@ -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 @@ -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. diff --git a/gix-features/src/zlib/mod.rs b/gix-features/src/zlib/mod.rs index 810715f3b15..448b8f1aaab 100644 --- a/gix-features/src/zlib/mod.rs +++ b/gix-features/src/zlib/mod.rs @@ -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 { @@ -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::() 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. @@ -50,31 +38,21 @@ impl Decompress { output: &mut [u8], flush: FlushDecompress, ) -> Result { - 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)] @@ -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 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()`]. @@ -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 @@ -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 diff --git a/gix-features/src/zlib/stream/deflate/mod.rs b/gix-features/src/zlib/stream/deflate/mod.rs index 9945288977a..f5d291d8041 100644 --- a/gix-features/src/zlib/stream/deflate/mod.rs +++ b/gix-features/src/zlib/stream/deflate/mod.rs @@ -1,5 +1,5 @@ use crate::zlib::Status; -use std::ffi::c_int; +use zlib_rs::DeflateError; const BUF_SIZE: usize = 4096 * 8; @@ -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 { @@ -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::() 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 { - 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 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 @@ -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. @@ -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 @@ -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 {