diff --git a/Cargo.toml b/Cargo.toml index 2a5a3fcd..030c76db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "flate2" authors = ["Alex Crichton ", "Josh Triplett "] -version = "1.1.5" +version = "1.1.6" edition = "2018" license = "MIT OR Apache-2.0" readme = "README.md" @@ -22,7 +22,7 @@ exclude = [".*"] libz-sys = { version = "1.1.20", optional = true, default-features = false } libz-ng-sys = { version = "1.1.16", optional = true } # this matches the default features, but we don't want to depend on the default features staying the same -libz-rs-sys = { version = "0.5.1", optional = true, default-features = false, features = ["std", "rust-allocator"] } +zlib-rs = { version = "0.5.3", optional = true, default-features = false, features = ["std", "rust-allocator"] } cloudflare-zlib-sys = { version = "0.3.6", optional = true } miniz_oxide = { version = "0.8.5", optional = true, default-features = false, features = ["with-alloc", "simd"] } crc32fast = "1.2.0" @@ -43,7 +43,7 @@ zlib = ["any_zlib", "libz-sys"] zlib-default = ["any_zlib", "libz-sys/default"] zlib-ng-compat = ["zlib", "libz-sys/zlib-ng"] zlib-ng = ["any_zlib", "libz-ng-sys"] -zlib-rs = ["any_zlib", "libz-rs-sys"] +zlib-rs = ["any_impl", "dep:zlib-rs"] cloudflare_zlib = ["any_zlib", "cloudflare-zlib-sys"] rust_backend = ["miniz_oxide", "any_impl"] miniz-sys = ["rust_backend"] # For backwards compatibility diff --git a/src/ffi/c.rs b/src/ffi/c.rs index e3fb171e..0e541861 100644 --- a/src/ffi/c.rs +++ b/src/ffi/c.rs @@ -9,7 +9,7 @@ use super::*; use crate::mem; #[derive(Clone, Default)] -pub struct ErrorMessage(Option<&'static str>); +pub struct ErrorMessage(pub(crate) Option<&'static str>); impl ErrorMessage { pub fn get(&self) -> Option<&str> { @@ -55,34 +55,27 @@ impl Default for StreamWrapper { // zlib-ng feature = "zlib-ng", // libz-sys - all(not(feature = "cloudflare_zlib"), not(feature = "zlib-ng"), not(feature = "zlib-rs")) + all(not(feature = "cloudflare_zlib"), not(feature = "zlib-ng")) ))] zalloc: allocator::zalloc, #[cfg(any( // zlib-ng feature = "zlib-ng", // libz-sys - all(not(feature = "cloudflare_zlib"), not(feature = "zlib-ng"), not(feature = "zlib-rs")) + all(not(feature = "cloudflare_zlib"), not(feature = "zlib-ng")) ))] zfree: allocator::zfree, #[cfg( // cloudflare-zlib - all(feature = "cloudflare_zlib", not(feature = "zlib-rs"), not(feature = "zlib-ng")), + all(feature = "cloudflare_zlib", not(feature = "zlib-ng")), )] zalloc: Some(allocator::zalloc), #[cfg( // cloudflare-zlib - all(feature = "cloudflare_zlib", not(feature = "zlib-rs"), not(feature = "zlib-ng")), + all(feature = "cloudflare_zlib", not(feature = "zlib-ng")), )] zfree: Some(allocator::zfree), - - // for zlib-rs, it is most efficient to have it provide the allocator. - // The libz-rs-sys dependency is configured to use the rust system allocator - #[cfg(all(feature = "zlib-rs", not(feature = "zlib-ng")))] - zalloc: None, - #[cfg(all(feature = "zlib-rs", not(feature = "zlib-ng")))] - zfree: None, })), } } @@ -102,9 +95,9 @@ impl Drop for StreamWrapper { // zlib-ng feature = "zlib-ng", // cloudflare-zlib - all(feature = "cloudflare_zlib", not(feature = "zlib-rs"), not(feature = "zlib-ng")), + all(feature = "cloudflare_zlib", not(feature = "zlib-ng")), // libz-sys - all(not(feature = "cloudflare_zlib"), not(feature = "zlib-ng"), not(feature = "zlib-rs")), + all(not(feature = "cloudflare_zlib"), not(feature = "zlib-ng")), ))] mod allocator { use super::*; @@ -462,18 +455,15 @@ mod c_backend { #[cfg(feature = "zlib-ng")] use libz_ng_sys as libz; - #[cfg(all(feature = "zlib-rs", not(feature = "zlib-ng")))] - use libz_rs_sys as libz; - #[cfg( // cloudflare-zlib - all(feature = "cloudflare_zlib", not(feature = "zlib-rs"), not(feature = "zlib-ng")), + all(feature = "cloudflare_zlib", not(feature = "zlib-ng")), )] use cloudflare_zlib_sys as libz; #[cfg( // libz-sys - all(not(feature = "cloudflare_zlib"), not(feature = "zlib-ng"), not(feature = "zlib-rs")), + all(not(feature = "cloudflare_zlib"), not(feature = "zlib-ng")), )] use libz_sys as libz; diff --git a/src/ffi/rust.rs b/src/ffi/miniz_oxide.rs similarity index 95% rename from src/ffi/rust.rs rename to src/ffi/miniz_oxide.rs index 5b3c1937..4dc7c478 100644 --- a/src/ffi/rust.rs +++ b/src/ffi/miniz_oxide.rs @@ -3,9 +3,9 @@ use std::convert::TryInto; use std::fmt; -use miniz_oxide::deflate::core::CompressorOxide; -use miniz_oxide::inflate::stream::InflateState; -pub use miniz_oxide::*; +use ::miniz_oxide::deflate::core::CompressorOxide; +use ::miniz_oxide::inflate::stream::InflateState; +pub use ::miniz_oxide::*; pub const MZ_NO_FLUSH: isize = MZFlush::None as isize; pub const MZ_PARTIAL_FLUSH: isize = MZFlush::Partial as isize; @@ -145,7 +145,8 @@ impl From for MZFlush { impl DeflateBackend for Deflate { fn make(level: Compression, zlib_header: bool, _window_bits: u8) -> Self { - // Check in case the integer value changes at some point. + // Check in case the integer value changes at some point. Unlike the other zlib + // implementations, miniz_oxide actually has a compression level 10. debug_assert!(level.level() <= 10); let mut inner: Box = Box::default(); diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 856bc651..6b3e0d14 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -65,10 +65,15 @@ mod c; #[cfg(feature = "any_zlib")] pub use self::c::*; +#[cfg(all(not(feature = "any_zlib"), feature = "zlib-rs"))] +mod zlib_rs; +#[cfg(all(not(feature = "any_zlib"), feature = "zlib-rs"))] +pub use self::zlib_rs::*; + #[cfg(all(not(feature = "any_zlib"), feature = "miniz_oxide"))] -mod rust; +mod miniz_oxide; #[cfg(all(not(feature = "any_zlib"), feature = "miniz_oxide"))] -pub use self::rust::*; +pub use self::miniz_oxide::*; impl std::fmt::Debug for ErrorMessage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/src/ffi/zlib_rs.rs b/src/ffi/zlib_rs.rs new file mode 100644 index 00000000..ddb9339d --- /dev/null +++ b/src/ffi/zlib_rs.rs @@ -0,0 +1,181 @@ +//! Implementation for `zlib_rs` rust backend. +//! +//! Every backend must provide two types: +//! +//! - `Deflate` for compression, implements the `Backend` and `DeflateBackend` trait +//! - `Inflate` for decompression, implements the `Backend` and `InflateBackend` trait +//! +//! Additionally the backend provides a number of constants, and a `ErrorMessage` type. +//! +//! ## Allocation +//! +//! The (de)compression state is not boxed. The C implementations require that the z_stream is +//! pinned in memory (has a fixed address), because their z_stream is self-referential. The most +//! convenient way in rust to guarantee a stable address is to `Box` the data, but it does add an +//! additional allocation. +//! +//! With zlib_rs the state is not self-referential and hence no boxing is needed. The `new` methods +//! internally do allocate space for the (de)compression state. + +use std::fmt; + +use ::zlib_rs::{DeflateFlush, InflateError, InflateFlush}; + +pub const MZ_NO_FLUSH: isize = DeflateFlush::NoFlush as isize; +pub const MZ_PARTIAL_FLUSH: isize = DeflateFlush::PartialFlush as isize; +pub const MZ_SYNC_FLUSH: isize = DeflateFlush::SyncFlush as isize; +pub const MZ_FULL_FLUSH: isize = DeflateFlush::FullFlush as isize; +pub const MZ_FINISH: isize = DeflateFlush::Finish as isize; + +pub const MZ_DEFAULT_WINDOW_BITS: core::ffi::c_int = 15; + +use super::*; + +impl From<::zlib_rs::Status> for crate::mem::Status { + fn from(value: ::zlib_rs::Status) -> Self { + match value { + ::zlib_rs::Status::Ok => crate::mem::Status::Ok, + ::zlib_rs::Status::BufError => crate::mem::Status::BufError, + ::zlib_rs::Status::StreamEnd => crate::mem::Status::StreamEnd, + } + } +} + +#[derive(Clone, Default)] +pub struct ErrorMessage(Option<&'static str>); + +impl ErrorMessage { + pub fn get(&self) -> Option<&str> { + self.0 + } +} + +pub struct Inflate { + pub(crate) inner: ::zlib_rs::Inflate, +} + +impl fmt::Debug for Inflate { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + f, + "zlib_rs inflate internal state. total_in: {}, total_out: {}", + self.inner.total_in(), + self.inner.total_out(), + ) + } +} + +impl From for DeflateFlush { + fn from(value: FlushDecompress) -> Self { + match value { + FlushDecompress::None => Self::NoFlush, + FlushDecompress::Sync => Self::SyncFlush, + FlushDecompress::Finish => Self::Finish, + } + } +} + +impl InflateBackend for Inflate { + fn make(zlib_header: bool, window_bits: u8) -> Self { + Inflate { + inner: ::zlib_rs::Inflate::new(zlib_header, window_bits), + } + } + + fn decompress( + &mut self, + input: &[u8], + output: &mut [u8], + flush: FlushDecompress, + ) -> Result { + let flush = match flush { + FlushDecompress::None => InflateFlush::NoFlush, + FlushDecompress::Sync => InflateFlush::SyncFlush, + FlushDecompress::Finish => InflateFlush::Finish, + }; + + match self.inner.decompress(input, output, flush) { + Ok(status) => Ok(status.into()), + Err(InflateError::NeedDict { dict_id }) => crate::mem::decompress_need_dict(dict_id), + Err(e) => crate::mem::decompress_failed(ErrorMessage(Some(e.as_str()))), + } + } + + fn reset(&mut self, zlib_header: bool) { + self.inner.reset(zlib_header); + } +} + +impl Backend for Inflate { + #[inline] + fn total_in(&self) -> u64 { + self.inner.total_in() + } + + #[inline] + fn total_out(&self) -> u64 { + self.inner.total_out() + } +} + +pub struct Deflate { + pub(crate) inner: ::zlib_rs::Deflate, +} + +impl fmt::Debug for Deflate { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + f, + "zlib_rs deflate internal state. total_in: {}, total_out: {}", + self.inner.total_in(), + self.inner.total_out(), + ) + } +} + +impl DeflateBackend for Deflate { + fn make(level: Compression, zlib_header: bool, window_bits: u8) -> Self { + // Check in case the integer value changes at some point. + debug_assert!(level.level() <= 9); + + Deflate { + inner: ::zlib_rs::Deflate::new(level.level() as i32, zlib_header, window_bits), + } + } + + fn compress( + &mut self, + input: &[u8], + output: &mut [u8], + flush: FlushCompress, + ) -> Result { + let flush = match flush { + FlushCompress::None => DeflateFlush::NoFlush, + FlushCompress::Partial => DeflateFlush::PartialFlush, + FlushCompress::Sync => DeflateFlush::SyncFlush, + FlushCompress::Full => DeflateFlush::FullFlush, + FlushCompress::Finish => DeflateFlush::Finish, + }; + + match self.inner.compress(input, output, flush) { + Ok(status) => Ok(status.into()), + Err(e) => crate::mem::compress_failed(ErrorMessage(Some(e.as_str()))), + } + } + + fn reset(&mut self) { + self.inner.reset(); + } +} + +impl Backend for Deflate { + #[inline] + fn total_in(&self) -> u64 { + self.inner.total_in() + } + + #[inline] + fn total_out(&self) -> u64 { + self.inner.total_out() + } +} diff --git a/src/mem.rs b/src/mem.rs index 8121d98a..903e46b5 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -662,9 +662,8 @@ mod tests { let mut d = Decompress::new(false); // decompressed whole deflate stream - assert!(d - .decompress_vec(&data[10..], &mut decoded, FlushDecompress::Finish) - .is_ok()); + d.decompress_vec(&data[10..], &mut decoded, FlushDecompress::Finish) + .unwrap(); // decompress data that has nothing to do with the deflate stream (this // used to panic)