Skip to content

Commit 5a2361b

Browse files
committed
in gix-features, use libz-rs-sys directly, skipping flate2
1 parent 768164a commit 5a2361b

File tree

8 files changed

+239
-23
lines changed

8 files changed

+239
-23
lines changed

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-features/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ io-pipe = ["dep:bytes"]
5555
crc32 = ["dep:crc32fast"]
5656

5757
## Enable the usage of zlib-related utilities to compress or decompress data.
58-
## This enables the `flate2` crate, and always uses the high-performance `zlib-rs` backend.
58+
## This enables and uses the high-performance `zlib-rs` backend.
5959
## Note that the various past features for selecting zlib backends are now deprecated and do nothing.
60-
zlib = ["dep:flate2", "dep:thiserror"]
60+
zlib = ["dep:libz-rs-sys", "dep:thiserror"]
6161
## Deprecated: gix always uses zlib-rs.
6262
zlib-ng = ["zlib"]
6363
## Deprecated: gix always uses zlib-rs now. As of zlib-rs 0.5.0 (used by flate2
@@ -121,7 +121,7 @@ bytesize = { version = "2.0.1", optional = true }
121121
bytes = { version = "1.0.0", optional = true }
122122

123123
# zlib module
124-
flate2 = { version = "1.1.1", optional = true, default-features = false, features = ["zlib-rs"] }
124+
libz-rs-sys = { version = "0.5.2", optional = true }
125125
thiserror = { version = "2.0.0", optional = true }
126126

127127
once_cell = { version = "1.21.3", optional = true }

gix-features/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
doc = ::document_features::document_features!()
1313
)]
1414
#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))]
15-
#![deny(missing_docs, rust_2018_idioms, unsafe_code)]
15+
#![deny(rust_2018_idioms)]
1616

1717
///
1818
pub mod cache;

gix-features/src/zlib/mod.rs

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,111 @@
1-
pub use flate2::{Decompress, Status};
1+
use std::fmt::Display;
2+
3+
pub struct Decompress(Box<libz_rs_sys::z_stream>);
4+
5+
unsafe impl Sync for Decompress {}
6+
unsafe impl Send for Decompress {}
7+
8+
impl Decompress {
9+
pub fn total_in(&self) -> u64 {
10+
self.0.total_in
11+
}
12+
13+
pub fn total_out(&self) -> u64 {
14+
self.0.total_out
15+
}
16+
17+
pub fn new(_zlib_header: bool) -> Self {
18+
let mut this = Box::new(libz_rs_sys::z_stream::default());
19+
20+
unsafe {
21+
libz_rs_sys::inflateInit_(
22+
&mut *this,
23+
libz_rs_sys::zlibVersion(),
24+
core::mem::size_of::<libz_rs_sys::z_stream>() as core::ffi::c_int,
25+
);
26+
}
27+
28+
Self(this)
29+
}
30+
31+
pub fn reset(&mut self, _: bool) {
32+
unsafe { libz_rs_sys::inflateReset(&mut *self.0) };
33+
}
34+
35+
pub fn decompress(
36+
&mut self,
37+
input: &[u8],
38+
output: &mut [u8],
39+
flush: FlushDecompress,
40+
) -> Result<Status, DecompressError> {
41+
self.0.avail_in = input.len() as _;
42+
self.0.avail_out = output.len() as _;
43+
44+
self.0.next_in = input.as_ptr();
45+
self.0.next_out = output.as_mut_ptr();
46+
47+
match unsafe { libz_rs_sys::inflate(&mut *self.0, flush as _) } {
48+
libz_rs_sys::Z_OK => Ok(Status::Ok),
49+
libz_rs_sys::Z_BUF_ERROR => Ok(Status::BufError),
50+
libz_rs_sys::Z_STREAM_END => Ok(Status::StreamEnd),
51+
52+
libz_rs_sys::Z_STREAM_ERROR => Err(DecompressError("stream error")),
53+
libz_rs_sys::Z_DATA_ERROR => Err(DecompressError("data error")),
54+
libz_rs_sys::Z_MEM_ERROR => Err(DecompressError("insufficient memory")),
55+
libz_rs_sys::Z_NEED_DICT => Err(DecompressError("need dictionary")),
56+
c => panic!("unknown return code: {}", c),
57+
}
58+
}
59+
}
60+
61+
impl Drop for Decompress {
62+
fn drop(&mut self) {
63+
unsafe { libz_rs_sys::inflateEnd(&mut *self.0) };
64+
}
65+
}
66+
67+
#[derive(Debug, thiserror::Error)]
68+
pub struct DecompressError(&'static str);
69+
70+
impl Display for DecompressError {
71+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72+
f.write_str(self.0)
73+
}
74+
}
75+
76+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77+
pub enum Status {
78+
Ok,
79+
BufError,
80+
StreamEnd,
81+
}
82+
83+
/// Values which indicate the form of flushing to be used when
84+
/// decompressing in-memory data.
85+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
86+
#[non_exhaustive]
87+
#[allow(clippy::unnecessary_cast)]
88+
pub enum FlushDecompress {
89+
/// A typical parameter for passing to compression/decompression functions,
90+
/// this indicates that the underlying stream to decide how much data to
91+
/// accumulate before producing output in order to maximize compression.
92+
None = libz_rs_sys::Z_NO_FLUSH as isize,
93+
94+
/// All pending output is flushed to the output buffer and the output is
95+
/// aligned on a byte boundary so that the decompressor can get all input
96+
/// data available so far.
97+
///
98+
/// Flushing may degrade compression for some compression algorithms and so
99+
/// it should only be used when necessary. This will complete the current
100+
/// deflate block and follow it with an empty stored block.
101+
Sync = libz_rs_sys::Z_SYNC_FLUSH as isize,
102+
103+
/// Pending input is processed and pending output is flushed.
104+
///
105+
/// The return value may indicate that the stream is not yet done and more
106+
/// data has yet to be processed.
107+
Finish = libz_rs_sys::Z_FINISH as isize,
108+
}
2109

3110
/// non-streaming interfaces for decompression
4111
pub mod inflate {
@@ -8,10 +115,10 @@ pub mod inflate {
8115
pub enum Error {
9116
#[error("Could not write all bytes when decompressing content")]
10117
WriteInflated(#[from] std::io::Error),
11-
#[error("Could not decode zip stream, status was '{0:?}'")]
12-
Inflate(#[from] flate2::DecompressError),
118+
#[error("Could not decode zip stream, status was '{0}'")]
119+
Inflate(#[from] super::DecompressError),
13120
#[error("The zlib status indicated an error, status was '{0:?}'")]
14-
Status(flate2::Status),
121+
Status(super::Status),
15122
}
16123
}
17124

@@ -31,10 +138,10 @@ impl Default for Inflate {
31138

32139
impl Inflate {
33140
/// Run the decompressor exactly once. Cannot be run multiple times
34-
pub fn once(&mut self, input: &[u8], out: &mut [u8]) -> Result<(flate2::Status, usize, usize), inflate::Error> {
141+
pub fn once(&mut self, input: &[u8], out: &mut [u8]) -> Result<(Status, usize, usize), inflate::Error> {
35142
let before_in = self.state.total_in();
36143
let before_out = self.state.total_out();
37-
let status = self.state.decompress(input, out, flate2::FlushDecompress::None)?;
144+
let status = self.state.decompress(input, out, FlushDecompress::None)?;
38145
Ok((
39146
status,
40147
(self.state.total_in() - before_in) as usize,

gix-features/src/zlib/stream/deflate/mod.rs

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use flate2::Compress;
1+
use crate::zlib::Status;
22

33
const BUF_SIZE: usize = 4096 * 8;
44

@@ -24,15 +24,124 @@ where
2424
}
2525
}
2626

27+
pub struct Compress(Box<libz_rs_sys::z_stream>);
28+
29+
unsafe impl Sync for Compress {}
30+
unsafe impl Send for Compress {}
31+
32+
impl Compress {
33+
pub fn total_in(&self) -> u64 {
34+
self.0.total_in
35+
}
36+
37+
pub fn total_out(&self) -> u64 {
38+
self.0.total_out
39+
}
40+
41+
pub fn new() -> Self {
42+
let mut this = Box::new(libz_rs_sys::z_stream::default());
43+
44+
unsafe {
45+
libz_rs_sys::deflateInit_(
46+
&mut *this,
47+
libz_rs_sys::Z_BEST_SPEED,
48+
libz_rs_sys::zlibVersion(),
49+
core::mem::size_of::<libz_rs_sys::z_stream>() as core::ffi::c_int,
50+
);
51+
}
52+
53+
Self(this)
54+
}
55+
56+
pub fn reset(&mut self) {
57+
unsafe { libz_rs_sys::deflateReset(&mut *self.0) };
58+
}
59+
60+
pub fn compress(&mut self, input: &[u8], output: &mut [u8], flush: FlushCompress) -> Result<Status, CompressError> {
61+
self.0.avail_in = input.len() as _;
62+
self.0.avail_out = output.len() as _;
63+
64+
self.0.next_in = input.as_ptr();
65+
self.0.next_out = output.as_mut_ptr();
66+
67+
match unsafe { libz_rs_sys::deflate(&mut *self.0, flush as _) } {
68+
libz_rs_sys::Z_OK => Ok(Status::Ok),
69+
libz_rs_sys::Z_BUF_ERROR => Ok(Status::BufError),
70+
libz_rs_sys::Z_STREAM_END => Ok(Status::StreamEnd),
71+
72+
libz_rs_sys::Z_STREAM_ERROR => Err(CompressError("stream error")),
73+
_ => todo!(),
74+
}
75+
}
76+
}
77+
78+
impl Drop for Compress {
79+
fn drop(&mut self) {
80+
unsafe { libz_rs_sys::deflateEnd(&mut *self.0) };
81+
}
82+
}
83+
84+
#[derive(Debug, thiserror::Error)]
85+
pub struct CompressError(&'static str);
86+
87+
impl std::fmt::Display for CompressError {
88+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89+
f.write_str(self.0)
90+
}
91+
}
92+
93+
/// Values which indicate the form of flushing to be used when compressing
94+
/// in-memory data.
95+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
96+
#[non_exhaustive]
97+
#[allow(clippy::unnecessary_cast)]
98+
pub enum FlushCompress {
99+
/// A typical parameter for passing to compression/decompression functions,
100+
/// this indicates that the underlying stream to decide how much data to
101+
/// accumulate before producing output in order to maximize compression.
102+
None = libz_rs_sys::Z_NO_FLUSH as isize,
103+
104+
/// All pending output is flushed to the output buffer, but the output is
105+
/// not aligned to a byte boundary.
106+
///
107+
/// All input data so far will be available to the decompressor (as with
108+
/// `Flush::Sync`). This completes the current deflate block and follows it
109+
/// with an empty fixed codes block that is 10 bits long, and it assures
110+
/// that enough bytes are output in order for the decompressor to finish the
111+
/// block before the empty fixed code block.
112+
Partial = libz_rs_sys::Z_PARTIAL_FLUSH as isize,
113+
114+
/// All pending output is flushed to the output buffer and the output is
115+
/// aligned on a byte boundary so that the decompressor can get all input
116+
/// data available so far.
117+
///
118+
/// Flushing may degrade compression for some compression algorithms and so
119+
/// it should only be used when necessary. This will complete the current
120+
/// deflate block and follow it with an empty stored block.
121+
Sync = libz_rs_sys::Z_SYNC_FLUSH as isize,
122+
123+
/// All output is flushed as with `Flush::Sync` and the compression state is
124+
/// reset so decompression can restart from this point if previous
125+
/// compressed data has been damaged or if random access is desired.
126+
///
127+
/// Using this option too often can seriously degrade compression.
128+
Full = libz_rs_sys::Z_FULL_FLUSH as isize,
129+
130+
/// Pending input is processed and pending output is flushed.
131+
///
132+
/// The return value may indicate that the stream is not yet done and more
133+
/// data has yet to be processed.
134+
Finish = libz_rs_sys::Z_FINISH as isize,
135+
}
136+
27137
mod impls {
28138
use std::io;
29139

30-
use flate2::{Compress, Compression, FlushCompress, Status};
31-
32-
use crate::zlib::stream::deflate;
140+
use crate::zlib::stream::deflate::{self, Compress, FlushCompress};
141+
use crate::zlib::Status;
33142

34143
pub(crate) fn new_compress() -> Compress {
35-
Compress::new(Compression::fast(), true)
144+
Compress::new()
36145
}
37146

38147
impl<W> deflate::Write<W>

gix-features/src/zlib/stream/deflate/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ mod deflate_stream {
55
};
66

77
use bstr::ByteSlice;
8-
use flate2::Decompress;
98

109
use crate::zlib::stream::deflate;
10+
use crate::zlib::Decompress;
1111

1212
/// Provide streaming decompression using the `std::io::Read` trait.
1313
/// If `std::io::BufReader` is used, an allocation for the input buffer will be performed.

gix-features/src/zlib/stream/inflate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{io, io::BufRead};
22

3-
use flate2::{Decompress, FlushDecompress, Status};
3+
use crate::zlib::{Decompress, FlushDecompress, Status};
44

55
/// Read bytes from `rd` and decompress them using `state` into a pre-allocated fitting buffer `dst`, returning the amount of bytes written.
66
pub fn read(rd: &mut impl BufRead, state: &mut Decompress, mut dst: &mut [u8]) -> io::Result<usize> {

gix/tests/gix/revision/spec/from_bytes/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ fn bad_objects_are_valid_until_they_are_actually_read_from_the_odb() {
127127
);
128128
assert_eq!(
129129
&format!("{:?}", parse_spec("cafea^{object}", &repo).unwrap_err())[..80],
130-
r#"FindObject(Find(Loose(DecompressFile { source: Inflate(DecompressError(General {"#
130+
r#"FindObject(Find(Loose(DecompressFile { source: Inflate(DecompressError("data err"#
131131
);
132132
}
133133
}

0 commit comments

Comments
 (0)