feat(compress): move stream orchestration into Rust
The high-level single-thread stream path previously combined stable-input rewind, buffer fill, direct-versus-buffered output, pending flush, end-of-frame reset, and progress hints in C. The multithreaded path likewise owned input range selection, rsync and end-directive adjustments, job creation decisions, and outer flush return policy in C. Add explicit C/Rust projections and callbacks. Rust now drives the single-thread state machine and the MT scheduling and flush decision sequence, while C retains CCtx-sensitive block callbacks, reusable buffers, job descriptors, synchronization, and worker lifecycle. Focused tests cover stable and buffered stream behavior, errors, pending output, frame completion, and MT scheduler branches. Sequence-store and dictionary boundaries remain unchanged. Test Plan: - Rust lib and CLI checks, tests, formatting, and clippy passed. - Rust legacy feature matrix passed: 556 tests. - Native lib and CLI builds passed; CLI tests passed: 41 tests. - Native test-zstd, fuzzer, zstream, and decode-corpus gates passed.
This commit is contained in:
+832
-3
@@ -83,7 +83,6 @@ unsafe extern "C" {
|
||||
const ZSTD_FAST: c_int = 1;
|
||||
const ZSTD_DFAST: c_int = 2;
|
||||
const ZSTD_REP_NUM: usize = 3;
|
||||
#[cfg(test)]
|
||||
const ZSTD_BM_BUFFERED: c_int = 0;
|
||||
const ZSTD_BM_STABLE: c_int = 1;
|
||||
const ZSTD_SF_NO_BLOCK_DELIMITERS: c_int = 0;
|
||||
@@ -106,8 +105,11 @@ const ZSTD_CURRENT_MAX: usize = if size_of::<usize>() == 8 {
|
||||
2000usize << 20
|
||||
};
|
||||
const ZSTD_CHUNKSIZE_MAX: usize = u32::MAX as usize - ZSTD_CURRENT_MAX;
|
||||
#[cfg(not(test))]
|
||||
const ZSTD_E_END: c_int = 2;
|
||||
const ZSTD_E_CONTINUE: c_int = 0;
|
||||
const ZSTD_E_FLUSH: c_int = 1;
|
||||
const ZSTD_CSTREAM_STAGE_LOAD: c_int = 1;
|
||||
const ZSTD_CSTREAM_STAGE_FLUSH: c_int = 2;
|
||||
|
||||
type FrameChunkPrepareFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize);
|
||||
type FrameChunkCompressFn =
|
||||
@@ -519,6 +521,436 @@ pub unsafe extern "C" fn ZSTD_rust_compressContinue(
|
||||
}
|
||||
}
|
||||
|
||||
type CompressStreamBlockFn =
|
||||
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize) -> usize;
|
||||
type CompressStreamResetFn = unsafe extern "C" fn(*mut c_void) -> usize;
|
||||
|
||||
/// Explicit projection of the single-threaded `ZSTD_compressStream_generic`
|
||||
/// state machine.
|
||||
///
|
||||
/// Rust owns buffering, output-drain policy, directive handling, and stream
|
||||
/// stage transitions. The opaque callback context stays in C; callbacks
|
||||
/// retain access to `ZSTD_CCtx`, frame construction, and session reset.
|
||||
#[repr(C)]
|
||||
pub struct ZSTD_rust_compressStreamState {
|
||||
callback_context: *mut c_void,
|
||||
in_buffer_mode: c_int,
|
||||
out_buffer_mode: c_int,
|
||||
stream_stage: *mut c_int,
|
||||
block_size_max: usize,
|
||||
stable_in_not_consumed: *mut usize,
|
||||
in_buff: *mut c_void,
|
||||
in_buff_size: usize,
|
||||
in_to_compress: *mut usize,
|
||||
in_buff_pos: *mut usize,
|
||||
in_buff_target: *mut usize,
|
||||
out_buff: *mut c_void,
|
||||
out_buff_size: usize,
|
||||
out_buff_content_size: *mut usize,
|
||||
out_buff_flushed_size: *mut usize,
|
||||
frame_ended: *mut c_uint,
|
||||
compress_continue: CompressStreamBlockFn,
|
||||
compress_end: CompressStreamBlockFn,
|
||||
reset_session: CompressStreamResetFn,
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
assert!(offset_of!(ZSTD_rust_compressStreamState, callback_context) == 0);
|
||||
assert!(offset_of!(ZSTD_rust_compressStreamState, in_buffer_mode) == size_of::<usize>());
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, out_buffer_mode)
|
||||
== size_of::<usize>() + size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, stream_stage)
|
||||
== size_of::<usize>() + 2 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, block_size_max)
|
||||
== 2 * size_of::<usize>() + 2 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, stable_in_not_consumed)
|
||||
== 3 * size_of::<usize>() + 2 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, in_buff)
|
||||
== 4 * size_of::<usize>() + 2 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, in_buff_size)
|
||||
== 5 * size_of::<usize>() + 2 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, in_to_compress)
|
||||
== 5 * size_of::<usize>() + 2 * size_of::<c_int>() + size_of::<usize>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, in_buff_pos)
|
||||
== 6 * size_of::<usize>() + 2 * size_of::<c_int>() + size_of::<usize>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, in_buff_target)
|
||||
== 7 * size_of::<usize>() + 2 * size_of::<c_int>() + size_of::<usize>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, out_buff)
|
||||
== 9 * size_of::<usize>() + 2 * size_of::<c_int>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, out_buff_size)
|
||||
== 9 * size_of::<usize>() + 2 * size_of::<c_int>() + size_of::<usize>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, out_buff_content_size)
|
||||
== 9 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, out_buff_flushed_size)
|
||||
== 10 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, frame_ended)
|
||||
== 11 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, compress_continue)
|
||||
== 12 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, compress_end)
|
||||
== 13 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
||||
);
|
||||
assert!(
|
||||
offset_of!(ZSTD_rust_compressStreamState, reset_session)
|
||||
== 14 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
||||
);
|
||||
assert!(
|
||||
size_of::<ZSTD_rust_compressStreamState>()
|
||||
== 15 * size_of::<usize>() + 2 * size_of::<c_int>() + 2 * size_of::<usize>()
|
||||
);
|
||||
};
|
||||
|
||||
#[inline]
|
||||
unsafe fn stream_limit_copy(
|
||||
dst: *mut c_void,
|
||||
dst_capacity: usize,
|
||||
src: *const c_void,
|
||||
src_size: usize,
|
||||
) -> usize {
|
||||
let length = dst_capacity.min(src_size);
|
||||
if length != 0 {
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(src.cast::<u8>(), dst.cast::<u8>(), length);
|
||||
}
|
||||
}
|
||||
length
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn compress_stream_generic_body_with(
|
||||
state: &ZSTD_rust_compressStreamState,
|
||||
output: &mut ZSTD_outBuffer,
|
||||
input: &mut ZSTD_inBuffer,
|
||||
flush_mode: c_int,
|
||||
) -> usize {
|
||||
debug_assert!((ZSTD_E_CONTINUE..=ZSTD_E_END).contains(&flush_mode));
|
||||
debug_assert!(input.pos <= input.size);
|
||||
debug_assert!(output.pos <= output.size);
|
||||
debug_assert!(!input.src.is_null() || input.size == 0);
|
||||
debug_assert!(!output.dst.is_null() || output.size == 0);
|
||||
|
||||
if state.stream_stage.is_null()
|
||||
|| state.stable_in_not_consumed.is_null()
|
||||
|| state.in_to_compress.is_null()
|
||||
|| state.in_buff_pos.is_null()
|
||||
|| state.in_buff_target.is_null()
|
||||
|| state.out_buff_content_size.is_null()
|
||||
|| state.out_buff_flushed_size.is_null()
|
||||
|| state.frame_ended.is_null()
|
||||
{
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
|
||||
if state.in_buffer_mode == ZSTD_BM_STABLE {
|
||||
let stable = unsafe { *state.stable_in_not_consumed };
|
||||
debug_assert!(input.pos >= stable);
|
||||
input.pos = input.pos.wrapping_sub(stable);
|
||||
*unsafe { state.stable_in_not_consumed.as_mut().unwrap_unchecked() } = 0;
|
||||
}
|
||||
|
||||
debug_assert!(
|
||||
state.in_buffer_mode == ZSTD_BM_BUFFERED || state.in_buffer_mode == ZSTD_BM_STABLE
|
||||
);
|
||||
if state.in_buffer_mode == ZSTD_BM_BUFFERED {
|
||||
debug_assert!(!state.in_buff.is_null());
|
||||
debug_assert!(state.in_buff_size != 0);
|
||||
}
|
||||
if state.out_buffer_mode == ZSTD_BM_BUFFERED {
|
||||
debug_assert!(!state.out_buff.is_null());
|
||||
debug_assert!(state.out_buff_size != 0);
|
||||
}
|
||||
|
||||
let mut some_more_work = true;
|
||||
while some_more_work {
|
||||
let stage = unsafe { *state.stream_stage };
|
||||
match stage {
|
||||
ZSTD_CSTREAM_STAGE_INIT => return ERROR(ZstdErrorCode::InitMissing),
|
||||
ZSTD_CSTREAM_STAGE_LOAD => {
|
||||
let input_remaining = input.size - input.pos;
|
||||
let output_remaining = output.size - output.pos;
|
||||
let bound = crate::zstd_compress_api::ZSTD_compressBound(input_remaining);
|
||||
if flush_mode == ZSTD_E_END
|
||||
&& (output_remaining >= bound || state.out_buffer_mode == ZSTD_BM_STABLE)
|
||||
&& unsafe { *state.in_buff_pos } == 0
|
||||
{
|
||||
let dst = if output.dst.is_null() {
|
||||
ptr::null_mut()
|
||||
} else {
|
||||
unsafe { output.dst.cast::<u8>().add(output.pos).cast() }
|
||||
};
|
||||
let src = if input.src.is_null() {
|
||||
ptr::null()
|
||||
} else {
|
||||
unsafe { input.src.cast::<u8>().add(input.pos).cast() }
|
||||
};
|
||||
let c_size = unsafe {
|
||||
(state.compress_end)(
|
||||
state.callback_context,
|
||||
dst,
|
||||
output_remaining,
|
||||
src,
|
||||
input_remaining,
|
||||
)
|
||||
};
|
||||
if ERR_isError(c_size) {
|
||||
return c_size;
|
||||
}
|
||||
input.pos = input.size;
|
||||
output.pos += c_size;
|
||||
unsafe { *state.frame_ended = 1 };
|
||||
unsafe { (state.reset_session)(state.callback_context) };
|
||||
some_more_work = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if state.in_buffer_mode == ZSTD_BM_BUFFERED {
|
||||
let to_load =
|
||||
unsafe { (*state.in_buff_target).wrapping_sub(*state.in_buff_pos) };
|
||||
let src = if input.src.is_null() {
|
||||
ptr::null()
|
||||
} else {
|
||||
unsafe { input.src.cast::<u8>().add(input.pos).cast() }
|
||||
};
|
||||
let dst = if state.in_buff.is_null() {
|
||||
ptr::null_mut()
|
||||
} else {
|
||||
unsafe { state.in_buff.cast::<u8>().add(*state.in_buff_pos).cast() }
|
||||
};
|
||||
let loaded = unsafe { stream_limit_copy(dst, to_load, src, input_remaining) };
|
||||
input.pos += loaded;
|
||||
unsafe { *state.in_buff_pos += loaded };
|
||||
|
||||
if flush_mode == ZSTD_E_CONTINUE
|
||||
&& unsafe { *state.in_buff_pos < *state.in_buff_target }
|
||||
{
|
||||
some_more_work = false;
|
||||
continue;
|
||||
}
|
||||
if flush_mode == ZSTD_E_FLUSH
|
||||
&& unsafe { *state.in_buff_pos == *state.in_to_compress }
|
||||
{
|
||||
some_more_work = false;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
debug_assert_eq!(state.in_buffer_mode, ZSTD_BM_STABLE);
|
||||
let input_remaining = input.size - input.pos;
|
||||
if flush_mode == ZSTD_E_CONTINUE && input_remaining < state.block_size_max {
|
||||
unsafe { *state.stable_in_not_consumed = input_remaining };
|
||||
input.pos = input.size;
|
||||
some_more_work = false;
|
||||
continue;
|
||||
}
|
||||
if flush_mode == ZSTD_E_FLUSH && input.pos == input.size {
|
||||
some_more_work = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let input_buffered = state.in_buffer_mode == ZSTD_BM_BUFFERED;
|
||||
let mut output_size = output.size - output.pos;
|
||||
let input_size = if input_buffered {
|
||||
unsafe { (*state.in_buff_pos).wrapping_sub(*state.in_to_compress) }
|
||||
} else {
|
||||
(input.size - input.pos).min(state.block_size_max)
|
||||
};
|
||||
let output_dst = if output_size
|
||||
>= crate::zstd_compress_api::ZSTD_compressBound(input_size)
|
||||
|| state.out_buffer_mode == ZSTD_BM_STABLE
|
||||
{
|
||||
if output.dst.is_null() {
|
||||
ptr::null_mut()
|
||||
} else {
|
||||
unsafe { output.dst.cast::<u8>().add(output.pos).cast() }
|
||||
}
|
||||
} else {
|
||||
output_size = state.out_buff_size;
|
||||
state.out_buff
|
||||
};
|
||||
|
||||
let last_block = flush_mode == ZSTD_E_END
|
||||
&& if input_buffered {
|
||||
input.pos == input.size
|
||||
} else {
|
||||
input.pos + input_size == input.size
|
||||
};
|
||||
let source = if input_buffered {
|
||||
if state.in_buff.is_null() {
|
||||
ptr::null()
|
||||
} else {
|
||||
unsafe { state.in_buff.cast::<u8>().add(*state.in_to_compress).cast() }
|
||||
}
|
||||
} else if input.src.is_null() {
|
||||
ptr::null()
|
||||
} else {
|
||||
unsafe { input.src.cast::<u8>().add(input.pos).cast() }
|
||||
};
|
||||
let c_size = unsafe {
|
||||
if last_block {
|
||||
(state.compress_end)(
|
||||
state.callback_context,
|
||||
output_dst,
|
||||
output_size,
|
||||
source,
|
||||
input_size,
|
||||
)
|
||||
} else {
|
||||
(state.compress_continue)(
|
||||
state.callback_context,
|
||||
output_dst,
|
||||
output_size,
|
||||
source,
|
||||
input_size,
|
||||
)
|
||||
}
|
||||
};
|
||||
if !input_buffered {
|
||||
/* Match C: stable input is consumed before forwarding a
|
||||
* compression error. */
|
||||
input.pos += input_size;
|
||||
}
|
||||
if ERR_isError(c_size) {
|
||||
return c_size;
|
||||
}
|
||||
|
||||
unsafe { *state.frame_ended = c_uint::from(last_block) };
|
||||
if input_buffered {
|
||||
unsafe {
|
||||
*state.in_buff_target =
|
||||
(*state.in_buff_pos).wrapping_add(state.block_size_max);
|
||||
if *state.in_buff_target > state.in_buff_size {
|
||||
*state.in_buff_pos = 0;
|
||||
*state.in_buff_target = state.block_size_max;
|
||||
}
|
||||
*state.in_to_compress = *state.in_buff_pos;
|
||||
}
|
||||
}
|
||||
|
||||
let current_output = if output.dst.is_null() {
|
||||
ptr::null_mut()
|
||||
} else {
|
||||
unsafe { output.dst.cast::<u8>().add(output.pos).cast() }
|
||||
};
|
||||
if output_dst == current_output {
|
||||
output.pos += c_size;
|
||||
if unsafe { *state.frame_ended } != 0 {
|
||||
unsafe { (state.reset_session)(state.callback_context) };
|
||||
some_more_work = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*state.out_buff_content_size = c_size;
|
||||
*state.out_buff_flushed_size = 0;
|
||||
*state.stream_stage = ZSTD_CSTREAM_STAGE_FLUSH;
|
||||
}
|
||||
}
|
||||
ZSTD_CSTREAM_STAGE_FLUSH => {
|
||||
debug_assert_eq!(state.out_buffer_mode, ZSTD_BM_BUFFERED);
|
||||
let to_flush = unsafe {
|
||||
(*state.out_buff_content_size).wrapping_sub(*state.out_buff_flushed_size)
|
||||
};
|
||||
let src = if state.out_buff.is_null() {
|
||||
ptr::null()
|
||||
} else {
|
||||
unsafe {
|
||||
state
|
||||
.out_buff
|
||||
.cast::<u8>()
|
||||
.add(*state.out_buff_flushed_size)
|
||||
.cast()
|
||||
}
|
||||
};
|
||||
let dst = if output.dst.is_null() {
|
||||
ptr::null_mut()
|
||||
} else {
|
||||
unsafe { output.dst.cast::<u8>().add(output.pos).cast() }
|
||||
};
|
||||
let flushed =
|
||||
unsafe { stream_limit_copy(dst, output.size - output.pos, src, to_flush) };
|
||||
output.pos += flushed;
|
||||
unsafe { *state.out_buff_flushed_size += flushed };
|
||||
if to_flush != flushed {
|
||||
some_more_work = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*state.out_buff_content_size = 0;
|
||||
*state.out_buff_flushed_size = 0;
|
||||
}
|
||||
if unsafe { *state.frame_ended } != 0 {
|
||||
unsafe { (state.reset_session)(state.callback_context) };
|
||||
some_more_work = false;
|
||||
continue;
|
||||
}
|
||||
unsafe { *state.stream_stage = ZSTD_CSTREAM_STAGE_LOAD };
|
||||
}
|
||||
_ => {
|
||||
debug_assert!(false, "invalid C stream stage");
|
||||
return ERROR(ZstdErrorCode::StageWrong);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if unsafe { *state.frame_ended } != 0 {
|
||||
return 0;
|
||||
}
|
||||
next_input_size_hint(
|
||||
state.in_buffer_mode,
|
||||
state.block_size_max,
|
||||
unsafe { *state.stable_in_not_consumed },
|
||||
unsafe { *state.in_buff_target },
|
||||
unsafe { *state.in_buff_pos },
|
||||
)
|
||||
}
|
||||
|
||||
/// Drive the single-threaded stream state machine through a C-owned context.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_compressStreamGeneric(
|
||||
state: *const ZSTD_rust_compressStreamState,
|
||||
output: *mut ZSTD_outBuffer,
|
||||
input: *mut ZSTD_inBuffer,
|
||||
flush_mode: c_int,
|
||||
) -> usize {
|
||||
if state.is_null() || output.is_null() || input.is_null() {
|
||||
return ERROR(ZstdErrorCode::Generic);
|
||||
}
|
||||
unsafe { compress_stream_generic_body_with(&*state, &mut *output, &mut *input, flush_mode) }
|
||||
}
|
||||
|
||||
/// Explicit projection of the state used by `ZSTD_compressSequences_internal`.
|
||||
///
|
||||
/// The C context and its function-pointer-bearing parameter structure remain
|
||||
@@ -1653,7 +2085,6 @@ pub struct ZSTD_inBuffer {
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[repr(C)]
|
||||
pub struct ZSTD_outBuffer {
|
||||
dst: *mut c_void,
|
||||
@@ -3901,6 +4332,404 @@ mod tests {
|
||||
assert_eq!(context.block_calls, 1);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CompressStreamTestContext {
|
||||
continue_calls: usize,
|
||||
end_calls: usize,
|
||||
reset_calls: usize,
|
||||
continue_result: usize,
|
||||
end_result: usize,
|
||||
reset_result: usize,
|
||||
}
|
||||
|
||||
unsafe fn compress_stream_test_context(
|
||||
context: *mut c_void,
|
||||
) -> &'static mut CompressStreamTestContext {
|
||||
unsafe { &mut *context.cast::<CompressStreamTestContext>() }
|
||||
}
|
||||
|
||||
unsafe extern "C" fn compress_stream_test_block(
|
||||
context: *mut c_void,
|
||||
dst: *mut c_void,
|
||||
dst_capacity: usize,
|
||||
_src: *const c_void,
|
||||
_src_size: usize,
|
||||
) -> usize {
|
||||
let context = unsafe { compress_stream_test_context(context) };
|
||||
context.continue_calls += 1;
|
||||
if !ERR_isError(context.continue_result) && context.continue_result != 0 {
|
||||
assert!(context.continue_result <= dst_capacity);
|
||||
unsafe { ptr::write_bytes(dst.cast::<u8>(), 0x5a, context.continue_result) };
|
||||
}
|
||||
context.continue_result
|
||||
}
|
||||
|
||||
unsafe extern "C" fn compress_stream_test_end(
|
||||
context: *mut c_void,
|
||||
dst: *mut c_void,
|
||||
dst_capacity: usize,
|
||||
_src: *const c_void,
|
||||
_src_size: usize,
|
||||
) -> usize {
|
||||
let context = unsafe { compress_stream_test_context(context) };
|
||||
context.end_calls += 1;
|
||||
if !ERR_isError(context.end_result) && context.end_result != 0 {
|
||||
assert!(context.end_result <= dst_capacity);
|
||||
unsafe { ptr::write_bytes(dst.cast::<u8>(), 0x3c, context.end_result) };
|
||||
}
|
||||
context.end_result
|
||||
}
|
||||
|
||||
unsafe extern "C" fn compress_stream_test_reset(context: *mut c_void) -> usize {
|
||||
let context = unsafe { compress_stream_test_context(context) };
|
||||
context.reset_calls += 1;
|
||||
context.reset_result
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn compress_stream_test_state(
|
||||
context: &mut CompressStreamTestContext,
|
||||
in_buffer_mode: c_int,
|
||||
out_buffer_mode: c_int,
|
||||
stage: &mut c_int,
|
||||
block_size_max: usize,
|
||||
stable_in_not_consumed: &mut usize,
|
||||
in_buff: *mut c_void,
|
||||
in_buff_size: usize,
|
||||
in_to_compress: &mut usize,
|
||||
in_buff_pos: &mut usize,
|
||||
in_buff_target: &mut usize,
|
||||
out_buff: *mut c_void,
|
||||
out_buff_size: usize,
|
||||
out_buff_content_size: &mut usize,
|
||||
out_buff_flushed_size: &mut usize,
|
||||
frame_ended: &mut c_uint,
|
||||
) -> ZSTD_rust_compressStreamState {
|
||||
ZSTD_rust_compressStreamState {
|
||||
callback_context: (context as *mut CompressStreamTestContext).cast(),
|
||||
in_buffer_mode,
|
||||
out_buffer_mode,
|
||||
stream_stage: stage,
|
||||
block_size_max,
|
||||
stable_in_not_consumed,
|
||||
in_buff,
|
||||
in_buff_size,
|
||||
in_to_compress,
|
||||
in_buff_pos,
|
||||
in_buff_target,
|
||||
out_buff,
|
||||
out_buff_size,
|
||||
out_buff_content_size,
|
||||
out_buff_flushed_size,
|
||||
frame_ended,
|
||||
compress_continue: compress_stream_test_block,
|
||||
compress_end: compress_stream_test_end,
|
||||
reset_session: compress_stream_test_reset,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compress_stream_stable_short_input_is_deferred_with_a_progress_hint() {
|
||||
let mut context = CompressStreamTestContext::default();
|
||||
let mut stage = ZSTD_CSTREAM_STAGE_LOAD;
|
||||
let mut stable_in_not_consumed = 0;
|
||||
let mut in_to_compress = 0;
|
||||
let mut in_buff_pos = 0;
|
||||
let mut in_buff_target = 0;
|
||||
let mut out_buff_content_size = 0;
|
||||
let mut out_buff_flushed_size = 0;
|
||||
let mut frame_ended = 0;
|
||||
let state = compress_stream_test_state(
|
||||
&mut context,
|
||||
ZSTD_BM_STABLE,
|
||||
ZSTD_BM_STABLE,
|
||||
&mut stage,
|
||||
8,
|
||||
&mut stable_in_not_consumed,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
&mut in_to_compress,
|
||||
&mut in_buff_pos,
|
||||
&mut in_buff_target,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
&mut out_buff_content_size,
|
||||
&mut out_buff_flushed_size,
|
||||
&mut frame_ended,
|
||||
);
|
||||
let source = [1u8, 2, 3];
|
||||
let mut output = [0xa5u8; 8];
|
||||
let mut input = ZSTD_inBuffer {
|
||||
src: source.as_ptr().cast(),
|
||||
size: source.len(),
|
||||
pos: 0,
|
||||
};
|
||||
let mut output_buffer = ZSTD_outBuffer {
|
||||
dst: output.as_mut_ptr().cast(),
|
||||
size: output.len(),
|
||||
pos: 0,
|
||||
};
|
||||
|
||||
let result = unsafe {
|
||||
ZSTD_rust_compressStreamGeneric(&state, &mut output_buffer, &mut input, ZSTD_E_CONTINUE)
|
||||
};
|
||||
|
||||
assert_eq!(result, 5);
|
||||
assert_eq!(input.pos, source.len());
|
||||
assert_eq!(stable_in_not_consumed, source.len());
|
||||
assert_eq!(output_buffer.pos, 0);
|
||||
assert_eq!(context.continue_calls, 0);
|
||||
assert_eq!(context.end_calls, 0);
|
||||
assert_eq!(frame_ended, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compress_stream_buffered_output_preserves_pending_flush_and_drains_it() {
|
||||
let mut context = CompressStreamTestContext {
|
||||
continue_result: 3,
|
||||
..CompressStreamTestContext::default()
|
||||
};
|
||||
let mut stage = ZSTD_CSTREAM_STAGE_LOAD;
|
||||
let mut stable_in_not_consumed = 0;
|
||||
let mut in_to_compress = 0;
|
||||
let mut in_buff_pos = 0;
|
||||
let mut in_buff_target = 4;
|
||||
let mut out_buff_content_size = 0;
|
||||
let mut out_buff_flushed_size = 0;
|
||||
let mut frame_ended = 0;
|
||||
let mut input_buffer = [0u8; 8];
|
||||
let mut internal_output = [0xa5u8; 8];
|
||||
let state = compress_stream_test_state(
|
||||
&mut context,
|
||||
ZSTD_BM_BUFFERED,
|
||||
ZSTD_BM_BUFFERED,
|
||||
&mut stage,
|
||||
4,
|
||||
&mut stable_in_not_consumed,
|
||||
input_buffer.as_mut_ptr().cast(),
|
||||
input_buffer.len(),
|
||||
&mut in_to_compress,
|
||||
&mut in_buff_pos,
|
||||
&mut in_buff_target,
|
||||
internal_output.as_mut_ptr().cast(),
|
||||
internal_output.len(),
|
||||
&mut out_buff_content_size,
|
||||
&mut out_buff_flushed_size,
|
||||
&mut frame_ended,
|
||||
);
|
||||
let source = [1u8, 2, 3, 4];
|
||||
let mut output = [0xa5u8; 2];
|
||||
let mut input = ZSTD_inBuffer {
|
||||
src: source.as_ptr().cast(),
|
||||
size: source.len(),
|
||||
pos: 0,
|
||||
};
|
||||
let mut output_buffer = ZSTD_outBuffer {
|
||||
dst: output.as_mut_ptr().cast(),
|
||||
size: output.len(),
|
||||
pos: 0,
|
||||
};
|
||||
|
||||
let result = unsafe {
|
||||
ZSTD_rust_compressStreamGeneric(&state, &mut output_buffer, &mut input, ZSTD_E_CONTINUE)
|
||||
};
|
||||
|
||||
assert_eq!(result, 4);
|
||||
assert_eq!(input.pos, source.len());
|
||||
assert_eq!(output_buffer.pos, output.len());
|
||||
assert_eq!(output, [0x5a; 2]);
|
||||
assert_eq!(out_buff_content_size, 3);
|
||||
assert_eq!(out_buff_flushed_size, 2);
|
||||
assert_eq!(stage, ZSTD_CSTREAM_STAGE_FLUSH);
|
||||
assert_eq!(context.continue_calls, 1);
|
||||
|
||||
let mut next_output = [0xa5u8; 2];
|
||||
output_buffer = ZSTD_outBuffer {
|
||||
dst: next_output.as_mut_ptr().cast(),
|
||||
size: next_output.len(),
|
||||
pos: 0,
|
||||
};
|
||||
let result = unsafe {
|
||||
ZSTD_rust_compressStreamGeneric(&state, &mut output_buffer, &mut input, ZSTD_E_FLUSH)
|
||||
};
|
||||
|
||||
assert_eq!(result, 4);
|
||||
assert_eq!(next_output[0], 0x5a);
|
||||
assert_eq!(output_buffer.pos, 1);
|
||||
assert_eq!((out_buff_content_size, out_buff_flushed_size), (0, 0));
|
||||
assert_eq!(stage, ZSTD_CSTREAM_STAGE_LOAD);
|
||||
assert_eq!(context.continue_calls, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compress_stream_end_shortcut_resets_after_direct_completion() {
|
||||
let mut context = CompressStreamTestContext {
|
||||
end_result: 5,
|
||||
..CompressStreamTestContext::default()
|
||||
};
|
||||
let mut stage = ZSTD_CSTREAM_STAGE_LOAD;
|
||||
let mut stable_in_not_consumed = 0;
|
||||
let mut in_to_compress = 0;
|
||||
let mut in_buff_pos = 0;
|
||||
let mut in_buff_target = 4;
|
||||
let mut out_buff_content_size = 0;
|
||||
let mut out_buff_flushed_size = 0;
|
||||
let mut frame_ended = 0;
|
||||
let mut input_buffer = [0u8; 8];
|
||||
let mut internal_output = [0u8; 8];
|
||||
let state = compress_stream_test_state(
|
||||
&mut context,
|
||||
ZSTD_BM_BUFFERED,
|
||||
ZSTD_BM_STABLE,
|
||||
&mut stage,
|
||||
4,
|
||||
&mut stable_in_not_consumed,
|
||||
input_buffer.as_mut_ptr().cast(),
|
||||
input_buffer.len(),
|
||||
&mut in_to_compress,
|
||||
&mut in_buff_pos,
|
||||
&mut in_buff_target,
|
||||
internal_output.as_mut_ptr().cast(),
|
||||
internal_output.len(),
|
||||
&mut out_buff_content_size,
|
||||
&mut out_buff_flushed_size,
|
||||
&mut frame_ended,
|
||||
);
|
||||
let source = [7u8, 8, 9];
|
||||
let mut output = [0xa5u8; 8];
|
||||
let mut input = ZSTD_inBuffer {
|
||||
src: source.as_ptr().cast(),
|
||||
size: source.len(),
|
||||
pos: 0,
|
||||
};
|
||||
let mut output_buffer = ZSTD_outBuffer {
|
||||
dst: output.as_mut_ptr().cast(),
|
||||
size: output.len(),
|
||||
pos: 0,
|
||||
};
|
||||
|
||||
let result = unsafe {
|
||||
ZSTD_rust_compressStreamGeneric(&state, &mut output_buffer, &mut input, ZSTD_E_END)
|
||||
};
|
||||
|
||||
assert_eq!(result, 0);
|
||||
assert_eq!(input.pos, source.len());
|
||||
assert_eq!(output_buffer.pos, 5);
|
||||
assert_eq!(&output[..5], &[0x3c; 5]);
|
||||
assert_eq!(frame_ended, 1);
|
||||
assert_eq!(context.end_calls, 1);
|
||||
assert_eq!(context.continue_calls, 0);
|
||||
assert_eq!(context.reset_calls, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compress_stream_stable_block_error_consumes_input_before_returning() {
|
||||
let mut context = CompressStreamTestContext {
|
||||
continue_result: ERROR(ZstdErrorCode::DstSizeTooSmall),
|
||||
..CompressStreamTestContext::default()
|
||||
};
|
||||
let mut stage = ZSTD_CSTREAM_STAGE_LOAD;
|
||||
let mut stable_in_not_consumed = 0;
|
||||
let mut in_to_compress = 0;
|
||||
let mut in_buff_pos = 0;
|
||||
let mut in_buff_target = 0;
|
||||
let mut out_buff_content_size = 0;
|
||||
let mut out_buff_flushed_size = 0;
|
||||
let mut frame_ended = 0;
|
||||
let state = compress_stream_test_state(
|
||||
&mut context,
|
||||
ZSTD_BM_STABLE,
|
||||
ZSTD_BM_STABLE,
|
||||
&mut stage,
|
||||
4,
|
||||
&mut stable_in_not_consumed,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
&mut in_to_compress,
|
||||
&mut in_buff_pos,
|
||||
&mut in_buff_target,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
&mut out_buff_content_size,
|
||||
&mut out_buff_flushed_size,
|
||||
&mut frame_ended,
|
||||
);
|
||||
let source = [0x11u8; 4];
|
||||
let mut output = [0xa5u8; 8];
|
||||
let mut input = ZSTD_inBuffer {
|
||||
src: source.as_ptr().cast(),
|
||||
size: source.len(),
|
||||
pos: 0,
|
||||
};
|
||||
let mut output_buffer = ZSTD_outBuffer {
|
||||
dst: output.as_mut_ptr().cast(),
|
||||
size: output.len(),
|
||||
pos: 0,
|
||||
};
|
||||
|
||||
let result = unsafe {
|
||||
ZSTD_rust_compressStreamGeneric(&state, &mut output_buffer, &mut input, ZSTD_E_CONTINUE)
|
||||
};
|
||||
|
||||
assert_eq!(result, ERROR(ZstdErrorCode::DstSizeTooSmall));
|
||||
assert_eq!(input.pos, source.len());
|
||||
assert_eq!(output_buffer.pos, 0);
|
||||
assert_eq!(context.continue_calls, 1);
|
||||
assert_eq!(context.end_calls, 0);
|
||||
assert_eq!(context.reset_calls, 0);
|
||||
assert_eq!(frame_ended, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compress_stream_rejects_missing_initialization_before_callbacks() {
|
||||
let mut context = CompressStreamTestContext::default();
|
||||
let mut stage = ZSTD_CSTREAM_STAGE_INIT;
|
||||
let mut stable_in_not_consumed = 0;
|
||||
let mut in_to_compress = 0;
|
||||
let mut in_buff_pos = 0;
|
||||
let mut in_buff_target = 0;
|
||||
let mut out_buff_content_size = 0;
|
||||
let mut out_buff_flushed_size = 0;
|
||||
let mut frame_ended = 0;
|
||||
let state = compress_stream_test_state(
|
||||
&mut context,
|
||||
ZSTD_BM_STABLE,
|
||||
ZSTD_BM_STABLE,
|
||||
&mut stage,
|
||||
4,
|
||||
&mut stable_in_not_consumed,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
&mut in_to_compress,
|
||||
&mut in_buff_pos,
|
||||
&mut in_buff_target,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
&mut out_buff_content_size,
|
||||
&mut out_buff_flushed_size,
|
||||
&mut frame_ended,
|
||||
);
|
||||
let mut input = ZSTD_inBuffer {
|
||||
src: ptr::null(),
|
||||
size: 0,
|
||||
pos: 0,
|
||||
};
|
||||
let mut output = ZSTD_outBuffer {
|
||||
dst: ptr::null_mut(),
|
||||
size: 0,
|
||||
pos: 0,
|
||||
};
|
||||
|
||||
let result = unsafe {
|
||||
ZSTD_rust_compressStreamGeneric(&state, &mut output, &mut input, ZSTD_E_FLUSH)
|
||||
};
|
||||
|
||||
assert_eq!(result, ERROR(ZstdErrorCode::InitMissing));
|
||||
assert_eq!(context.continue_calls, 0);
|
||||
assert_eq!(context.end_calls, 0);
|
||||
assert_eq!(context.reset_calls, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_strategy_follows_source_size_tiers() {
|
||||
assert_eq!(ZSTD_rust_compressCCtxStrategy(0, 1), ZSTD_FAST);
|
||||
|
||||
Reference in New Issue
Block a user