Skip to content

Commit d1d2730

Browse files
committed
implement inflateBackEnd
1 parent bc0c9fd commit d1d2730

File tree

10 files changed

+1711
-41
lines changed

10 files changed

+1711
-41
lines changed

fuzz/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,9 @@ name = "checksum"
8787
path = "fuzz_targets/checksum.rs"
8888
test = false
8989
doc = false
90+
91+
[[bin]]
92+
name = "infback"
93+
path = "fuzz_targets/infback.rs"
94+
test = false
95+
doc = false

fuzz/fuzz_targets/infback.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#![no_main]
2+
use core::ffi::{c_int, c_uchar, c_uint, c_void};
3+
use core::mem::MaybeUninit;
4+
5+
use libfuzzer_sys::{fuzz_target, Corpus};
6+
7+
fuzz_target!(|input: &[u8]| -> Corpus { entry(input) });
8+
9+
fn entry(input: &[u8]) -> Corpus {
10+
if input.is_empty() {
11+
return Corpus::Reject;
12+
}
13+
14+
differential_inflate_back::<1>(input);
15+
differential_inflate_back::<512>(input);
16+
17+
Corpus::Keep
18+
}
19+
20+
fn differential_inflate_back<const CHUNK: usize>(input: &[u8]) {
21+
// Per the documentation, only window_bits 15 is supported.
22+
let window_bits = 15;
23+
24+
let ng_out = run_inflate_back_ng::<CHUNK>(input, window_bits);
25+
let rs_out = run_inflate_back_rs::<CHUNK>(input, window_bits);
26+
27+
if let (Ok(ng_out), Ok(rs_out)) = (&ng_out, &rs_out) {
28+
assert_eq!(ng_out.len(), rs_out.len());
29+
30+
for (i, (a, b)) in ng_out.iter().zip(rs_out).enumerate() {
31+
if a != b {
32+
println!("{:?}", &ng_out[i..]);
33+
println!("{:?}", &rs_out[i..]);
34+
}
35+
36+
assert_eq!(a, b, "failed at position {} of {}", i, ng_out.len());
37+
}
38+
}
39+
40+
assert_eq!(
41+
ng_out, rs_out,
42+
"inflateBack mismatch for window_bits = {window_bits}, CHUNK = {CHUNK}",
43+
);
44+
}
45+
46+
/// Shared input context for the inflateBack `in` callback.
47+
struct InputCtx<'a> {
48+
data: &'a [u8],
49+
pos: usize,
50+
}
51+
52+
/// Shared output context for the inflateBack `out` callback.
53+
struct OutputCtx {
54+
buf: Vec<u8>,
55+
}
56+
57+
/// `in` callback: supplies more compressed data to inflateBack.
58+
unsafe extern "C" fn pull_cb<const CHUNK: usize>(
59+
desc: *mut c_void,
60+
buf: *mut *const c_uchar,
61+
) -> c_uint {
62+
let Some(ctx) = desc.cast::<InputCtx>().as_mut() else {
63+
return 0;
64+
};
65+
66+
if ctx.pos >= ctx.data.len() {
67+
// No more data
68+
*buf = core::ptr::null();
69+
return 0;
70+
}
71+
72+
// Feed one byte at a time (stress the state machine a bit)
73+
let remaining = ctx.data.len() - ctx.pos;
74+
let chunk = Ord::min(CHUNK, remaining);
75+
76+
let ptr = ctx.data[ctx.pos..].as_ptr();
77+
*buf = ptr;
78+
79+
ctx.pos += chunk;
80+
chunk as c_uint
81+
}
82+
83+
/// `out` callback: collects decompressed bytes from inflateBack.
84+
///
85+
/// C signature:
86+
/// int out_func(void *desc, unsigned char *buf, unsigned len);
87+
unsafe extern "C" fn push_cb(desc: *mut c_void, buf: *mut c_uchar, len: c_uint) -> c_int {
88+
let Some(ctx) = desc.cast::<OutputCtx>().as_mut() else {
89+
// Signal error; inflateBack will return Z_BUF_ERROR.
90+
return 1;
91+
};
92+
93+
let slice = core::slice::from_raw_parts(buf as *const u8, len as usize);
94+
ctx.buf.extend_from_slice(slice);
95+
96+
// 0 means "ok, continue"
97+
0
98+
}
99+
100+
fn run_inflate_back_ng<const CHUNK: usize>(
101+
input: &[u8],
102+
window_bits: c_int,
103+
) -> Result<Vec<u8>, c_int> {
104+
let mut strm = MaybeUninit::zeroed();
105+
let mut window = vec![0xAA; 1 << window_bits];
106+
107+
let mut in_ctx = InputCtx {
108+
data: input,
109+
pos: 0,
110+
};
111+
let mut out_ctx = OutputCtx { buf: Vec::new() };
112+
113+
let in_desc: *mut c_void = &mut in_ctx as *mut _ as *mut c_void;
114+
let out_desc: *mut c_void = &mut out_ctx as *mut _ as *mut c_void;
115+
116+
unsafe {
117+
let ret = libz_ng_sys::inflateBackInit_(
118+
strm.as_mut_ptr(),
119+
window_bits,
120+
window.as_mut_ptr(),
121+
libz_ng_sys::zlibVersion(),
122+
core::mem::size_of::<libz_ng_sys::z_stream>() as c_int,
123+
);
124+
125+
if ret != libz_ng_sys::Z_OK {
126+
return Err(ret);
127+
}
128+
129+
let ret = libz_ng_sys::inflateBack(
130+
strm.as_mut_ptr(),
131+
pull_cb::<CHUNK>,
132+
in_desc,
133+
push_cb,
134+
out_desc,
135+
);
136+
137+
let _ = libz_ng_sys::inflateBackEnd(strm.as_mut_ptr());
138+
139+
match ret {
140+
libz_ng_sys::Z_STREAM_END => Ok(out_ctx.buf),
141+
_ => Err(ret),
142+
}
143+
}
144+
}
145+
146+
fn run_inflate_back_rs<const CHUNK: usize>(
147+
input: &[u8],
148+
window_bits: c_int,
149+
) -> Result<Vec<u8>, c_int> {
150+
let mut strm = MaybeUninit::zeroed();
151+
let mut window = vec![0xAA; 1 << window_bits];
152+
153+
let mut in_ctx = InputCtx {
154+
data: input,
155+
pos: 0,
156+
};
157+
let mut out_ctx = OutputCtx { buf: Vec::new() };
158+
159+
let in_desc: *mut c_void = &mut in_ctx as *mut _ as *mut c_void;
160+
let out_desc: *mut c_void = &mut out_ctx as *mut _ as *mut c_void;
161+
162+
unsafe {
163+
let ret = libz_rs_sys::inflateBackInit_(
164+
strm.as_mut_ptr(),
165+
window_bits,
166+
window.as_mut_ptr(),
167+
libz_rs_sys::zlibVersion(),
168+
core::mem::size_of::<libz_rs_sys::z_stream>() as c_int,
169+
);
170+
171+
if ret != libz_rs_sys::Z_OK {
172+
return Err(ret);
173+
}
174+
175+
let ret = libz_rs_sys::inflateBack(
176+
strm.as_mut_ptr(),
177+
Some(pull_cb::<CHUNK>),
178+
in_desc,
179+
Some(push_cb),
180+
out_desc,
181+
);
182+
183+
let _ = libz_rs_sys::inflateBackEnd(strm.as_mut_ptr());
184+
185+
match ret {
186+
libz_rs_sys::Z_STREAM_END => Ok(out_ctx.buf),
187+
_ => Err(ret),
188+
}
189+
}
190+
}

libz-rs-sys/src/lib.rs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -564,13 +564,29 @@ pub unsafe extern "C-unwind" fn inflateEnd(strm: *mut z_stream) -> i32 {
564564
/// - `opaque`
565565
#[cfg_attr(feature = "export-symbols", export_name = prefix!(inflateBackInit_))]
566566
pub unsafe extern "C-unwind" fn inflateBackInit_(
567-
_strm: z_streamp,
568-
_windowBits: c_int,
569-
_window: *mut c_uchar,
570-
_version: *const c_char,
571-
_stream_size: c_int,
567+
strm: z_streamp,
568+
windowBits: c_int,
569+
window: *mut c_uchar,
570+
version: *const c_char,
571+
stream_size: c_int,
572572
) -> c_int {
573-
todo!("inflateBack is not implemented yet")
573+
if !is_version_compatible(version, stream_size) {
574+
return ReturnCode::VersionError as _;
575+
}
576+
577+
let Some(strm) = (unsafe { strm.as_mut() }) else {
578+
return ReturnCode::StreamError as _;
579+
};
580+
581+
let config = InflateConfig {
582+
window_bits: windowBits,
583+
};
584+
585+
// NOTE: normally we allocate a window with some additional padding. That doesn't happen here,
586+
// so the `infback` function uses `Window::buffer_size` instead of `Window::size`.
587+
let window = unsafe { zlib_rs::inflate::Window::from_raw_parts(window, 1usize << windowBits) };
588+
589+
zlib_rs::inflate::back_init(strm, config, window) as _
574590
}
575591

576592
/// Decompresses as much data as possible, and stops when the input buffer becomes empty or the output buffer becomes full.
@@ -584,13 +600,25 @@ pub unsafe extern "C-unwind" fn inflateBackInit_(
584600
/// - `strm` satisfies the requirements of `&mut *strm` and was initialized with [`inflateBackInit_`]
585601
#[cfg_attr(feature = "export-symbols", export_name = prefix!(inflateBack))]
586602
pub unsafe extern "C-unwind" fn inflateBack(
587-
_strm: z_streamp,
588-
_in: in_func,
589-
_in_desc: *mut c_void,
590-
_out: out_func,
591-
_out_desc: *mut c_void,
603+
strm: z_streamp,
604+
in_: Option<in_func>,
605+
in_desc: *mut c_void,
606+
out: Option<out_func>,
607+
out_desc: *mut c_void,
592608
) -> c_int {
593-
todo!("inflateBack is not implemented yet")
609+
let Some(strm) = (unsafe { InflateStream::from_stream_mut(strm) }) else {
610+
return ReturnCode::StreamError as _;
611+
};
612+
613+
let Some(in_) = in_ else {
614+
return ReturnCode::StreamError as _;
615+
};
616+
617+
let Some(out) = out else {
618+
return ReturnCode::StreamError as _;
619+
};
620+
621+
zlib_rs::inflate::back(strm, in_, in_desc, out, out_desc) as _
594622
}
595623

596624
/// Deallocates all dynamically allocated data structures for this stream.
@@ -610,8 +638,14 @@ pub unsafe extern "C-unwind" fn inflateBack(
610638
/// - `strm` is `NULL`
611639
/// - `strm` satisfies the requirements of `&mut *strm` and was initialized with [`inflateBackInit_`]
612640
#[cfg_attr(feature = "export-symbols", export_name = prefix!(inflateBackEnd))]
613-
pub unsafe extern "C-unwind" fn inflateBackEnd(_strm: z_streamp) -> c_int {
614-
todo!("inflateBack is not implemented yet")
641+
pub unsafe extern "C-unwind" fn inflateBackEnd(strm: z_streamp) -> c_int {
642+
let Some(stream) = (unsafe { InflateStream::from_stream_mut(strm) }) else {
643+
return ReturnCode::StreamError as _;
644+
};
645+
646+
zlib_rs::inflate::back_end(stream);
647+
648+
ReturnCode::Ok as _
615649
}
616650

617651
/// Sets the destination stream as a complete copy of the source stream.

0 commit comments

Comments
 (0)