Skip to content

Commit e08b9b5

Browse files
committed
refactor
- avoid all unsafe by avoiding the C-api completely
1 parent a75c852 commit e08b9b5

File tree

4 files changed

+53
-104
lines changed

4 files changed

+53
-104
lines changed

Cargo.lock

Lines changed: 2 additions & 2 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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ bytesize = { version = "2.3.1", optional = true }
108108
bytes = { version = "1.0.0", optional = true }
109109

110110
# zlib module
111-
zlib-rs = { version = "0.5.2", optional = true }
111+
zlib-rs = { version = "0.5.4", optional = true }
112112
thiserror = { version = "2.0.17", optional = true }
113113

114114
# Note: once_cell is kept for OnceCell type because std::sync::OnceLock::get_or_try_init() is not yet stable.

gix-features/src/zlib/mod.rs

Lines changed: 25 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
use std::ffi::c_int;
1+
use zlib_rs::InflateError;
22

33
/// A type to hold all state needed for decompressing a ZLIB encoded stream.
4-
pub struct Decompress(zlib_rs::c_api::z_stream);
5-
6-
// SAFETY: The z_stream contains raw pointers but they are only used through safe zlib-rs APIs
7-
// and the state is fully owned by this struct, making it safe to send across threads.
8-
unsafe impl Send for Decompress {}
9-
unsafe impl Sync for Decompress {}
4+
pub struct Decompress(zlib_rs::Inflate);
105

116
impl Default for Decompress {
127
fn default() -> Self {
@@ -17,28 +12,23 @@ impl Default for Decompress {
1712
impl Decompress {
1813
/// The amount of bytes consumed from the input so far.
1914
pub fn total_in(&self) -> u64 {
20-
self.0.total_in as _
15+
self.0.total_in()
2116
}
2217

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

2823
/// Create a new instance. Note that it allocates in various ways and thus should be re-used.
2924
pub fn new() -> Self {
30-
let mut stream = zlib_rs::c_api::z_stream::default();
31-
let config = zlib_rs::inflate::InflateConfig::default();
32-
zlib_rs::inflate::init(&mut stream, config);
33-
Self(stream)
25+
let inner = zlib_rs::Inflate::new(true, zlib_rs::MAX_WBITS as u8);
26+
Self(inner)
3427
}
3528

3629
/// Reset the state to allow handling a new stream.
3730
pub fn reset(&mut self) {
38-
// SAFETY: Converting from z_stream to InflateStream is safe here as we maintain ownership
39-
if let Some(stream) = unsafe { zlib_rs::inflate::InflateStream::from_stream_mut(&mut self.0) } {
40-
zlib_rs::inflate::reset(stream);
41-
}
31+
self.0.reset(true);
4232
}
4333

4434
/// Decompress `input` and write all decompressed bytes into `output`, with `flush` defining some details about this.
@@ -48,43 +38,17 @@ impl Decompress {
4838
output: &mut [u8],
4939
flush: FlushDecompress,
5040
) -> Result<Status, DecompressError> {
51-
self.0.avail_in = input.len() as _;
52-
self.0.avail_out = output.len() as _;
53-
54-
self.0.next_in = input.as_ptr();
55-
self.0.next_out = output.as_mut_ptr();
56-
57-
// SAFETY: We're converting from the C-compatible z_stream to the typed InflateStream.
58-
// This is safe because we initialized the stream with `inflate::init` and maintain ownership.
59-
let stream = unsafe { zlib_rs::inflate::InflateStream::from_stream_mut(&mut self.0) }
60-
.ok_or(DecompressError::StreamError)?;
61-
6241
let inflate_flush = match flush {
6342
FlushDecompress::None => zlib_rs::InflateFlush::NoFlush,
6443
FlushDecompress::Sync => zlib_rs::InflateFlush::SyncFlush,
6544
FlushDecompress::Finish => zlib_rs::InflateFlush::Finish,
6645
};
67-
68-
// SAFETY: The zlib_rs::inflate::inflate function is marked unsafe because it operates on
69-
// raw pointers internally. However, we've properly set up the stream with valid input/output
70-
// buffers and the actual decompression is done in safe Rust code within zlib-rs.
71-
match unsafe { zlib_rs::inflate::inflate(stream, inflate_flush) } {
72-
zlib_rs::ReturnCode::Ok => Ok(Status::Ok),
73-
zlib_rs::ReturnCode::BufError => Ok(Status::BufError),
74-
zlib_rs::ReturnCode::StreamEnd => Ok(Status::StreamEnd),
75-
zlib_rs::ReturnCode::StreamError => Err(DecompressError::StreamError),
76-
zlib_rs::ReturnCode::DataError => Err(DecompressError::DataError),
77-
zlib_rs::ReturnCode::MemError => Err(DecompressError::InsufficientMemory),
78-
err => Err(DecompressError::Unknown { err: err as c_int }),
79-
}
80-
}
81-
}
8246

83-
impl Drop for Decompress {
84-
fn drop(&mut self) {
85-
// SAFETY: Converting from z_stream to InflateStream is safe here as we maintain ownership
86-
if let Some(stream) = unsafe { zlib_rs::inflate::InflateStream::from_stream_mut(&mut self.0) } {
87-
zlib_rs::inflate::end(stream);
47+
let status = self.0.decompress(input, output, inflate_flush)?;
48+
match status {
49+
zlib_rs::Status::Ok => Ok(Status::Ok),
50+
zlib_rs::Status::BufError => Ok(Status::BufError),
51+
zlib_rs::Status::StreamEnd => Ok(Status::StreamEnd),
8852
}
8953
}
9054
}
@@ -99,8 +63,19 @@ pub enum DecompressError {
9963
InsufficientMemory,
10064
#[error("Invalid input data")]
10165
DataError,
102-
#[error("An unknown error occurred: {err}")]
103-
Unknown { err: c_int },
66+
#[error("Decompressing this input requires a dictionary")]
67+
NeedDict,
68+
}
69+
70+
impl From<zlib_rs::InflateError> for DecompressError {
71+
fn from(value: InflateError) -> Self {
72+
match value {
73+
InflateError::NeedDict { .. } => DecompressError::NeedDict,
74+
InflateError::StreamError => DecompressError::StreamError,
75+
InflateError::DataError => DecompressError::DataError,
76+
InflateError::MemError => DecompressError::InsufficientMemory,
77+
}
78+
}
10479
}
10580

10681
/// The status returned by [`Decompress::decompress()`].

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

Lines changed: 25 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::zlib::Status;
2-
use std::ffi::c_int;
2+
use zlib_rs::DeflateError;
33

44
const BUF_SIZE: usize = 4096 * 8;
55

@@ -26,12 +26,7 @@ where
2626
}
2727

2828
/// Hold all state needed for compressing data.
29-
pub struct Compress(zlib_rs::c_api::z_stream);
30-
31-
// SAFETY: The z_stream contains raw pointers but they are only used through safe zlib-rs APIs
32-
// and the state is fully owned by this struct, making it safe to send across threads.
33-
unsafe impl Send for Compress {}
34-
unsafe impl Sync for Compress {}
29+
pub struct Compress(zlib_rs::Deflate);
3530

3631
impl Default for Compress {
3732
fn default() -> Self {
@@ -42,72 +37,41 @@ impl Default for Compress {
4237
impl Compress {
4338
/// The number of bytes that were read from the input.
4439
pub fn total_in(&self) -> u64 {
45-
self.0.total_in as _
40+
self.0.total_in()
4641
}
4742

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

5348
/// Create a new instance - this allocates so should be done with care.
5449
pub fn new() -> Self {
55-
let mut stream = zlib_rs::c_api::z_stream::default();
56-
let config = zlib_rs::deflate::DeflateConfig {
57-
level: 1, // Z_BEST_SPEED
58-
..Default::default()
59-
};
60-
zlib_rs::deflate::init(&mut stream, config);
61-
Self(stream)
50+
let inner = zlib_rs::Deflate::new(zlib_rs::c_api::Z_BEST_SPEED, true, zlib_rs::MAX_WBITS as u8);
51+
Self(inner)
6252
}
6353

6454
/// Prepare the instance for a new stream.
6555
pub fn reset(&mut self) {
66-
// SAFETY: Converting from z_stream to DeflateStream is safe here as we maintain ownership
67-
if let Some(stream) = unsafe { zlib_rs::deflate::DeflateStream::from_stream_mut(&mut self.0) } {
68-
zlib_rs::deflate::reset(stream);
69-
}
56+
self.0.reset();
7057
}
7158

7259
/// Compress `input` and write compressed bytes to `output`, with `flush` controlling additional characteristics.
7360
pub fn compress(&mut self, input: &[u8], output: &mut [u8], flush: FlushCompress) -> Result<Status, CompressError> {
74-
self.0.avail_in = input.len() as _;
75-
self.0.avail_out = output.len() as _;
76-
77-
self.0.next_in = input.as_ptr();
78-
self.0.next_out = output.as_mut_ptr();
79-
80-
// SAFETY: We're converting from the C-compatible z_stream to the typed DeflateStream.
81-
// This is safe because we initialized the stream with `deflate::init` and maintain ownership.
82-
let stream = unsafe { zlib_rs::deflate::DeflateStream::from_stream_mut(&mut self.0) }
83-
.ok_or(CompressError::StreamError)?;
84-
85-
let deflate_flush = match flush {
61+
let flush = match flush {
8662
FlushCompress::None => zlib_rs::DeflateFlush::NoFlush,
8763
FlushCompress::Partial => zlib_rs::DeflateFlush::PartialFlush,
8864
FlushCompress::Sync => zlib_rs::DeflateFlush::SyncFlush,
8965
FlushCompress::Full => zlib_rs::DeflateFlush::FullFlush,
9066
FlushCompress::Finish => zlib_rs::DeflateFlush::Finish,
9167
};
92-
68+
let status = self.0.compress(input, output, flush)?;
9369
// Note: zlib_rs::deflate::deflate is a safe function (unlike inflate which is unsafe)
9470
// because deflate doesn't have the same safety concerns as inflate regarding output buffer handling
95-
match zlib_rs::deflate::deflate(stream, deflate_flush) {
96-
zlib_rs::ReturnCode::Ok => Ok(Status::Ok),
97-
zlib_rs::ReturnCode::BufError => Ok(Status::BufError),
98-
zlib_rs::ReturnCode::StreamEnd => Ok(Status::StreamEnd),
99-
zlib_rs::ReturnCode::StreamError => Err(CompressError::StreamError),
100-
zlib_rs::ReturnCode::MemError => Err(CompressError::InsufficientMemory),
101-
err => Err(CompressError::Unknown { err: err as c_int }),
102-
}
103-
}
104-
}
105-
106-
impl Drop for Compress {
107-
fn drop(&mut self) {
108-
// SAFETY: Converting from z_stream to DeflateStream is safe here as we maintain ownership
109-
if let Some(stream) = unsafe { zlib_rs::deflate::DeflateStream::from_stream_mut(&mut self.0) } {
110-
let _ = zlib_rs::deflate::end(stream);
71+
match status {
72+
zlib_rs::Status::Ok => Ok(Status::Ok),
73+
zlib_rs::Status::BufError => Ok(Status::BufError),
74+
zlib_rs::Status::StreamEnd => Ok(Status::StreamEnd),
11175
}
11276
}
11377
}
@@ -119,10 +83,20 @@ impl Drop for Compress {
11983
pub enum CompressError {
12084
#[error("stream error")]
12185
StreamError,
86+
#[error("The input is not a valid deflate stream.")]
87+
DataError,
12288
#[error("Not enough memory")]
12389
InsufficientMemory,
124-
#[error("An unknown error occurred: {err}")]
125-
Unknown { err: c_int },
90+
}
91+
92+
impl From<zlib_rs::DeflateError> for CompressError {
93+
fn from(value: zlib_rs::DeflateError) -> Self {
94+
match value {
95+
DeflateError::StreamError => CompressError::StreamError,
96+
DeflateError::DataError => CompressError::DataError,
97+
DeflateError::MemError => CompressError::InsufficientMemory,
98+
}
99+
}
126100
}
127101

128102
/// Values which indicate the form of flushing to be used when compressing

0 commit comments

Comments
 (0)