Keep ZSTD_getFrameProgression's multithreaded path and C-owned frame construction unchanged while moving the single-thread ingested-value calculation behind the existing scalar C-to-Rust boundary. The Rust helper accepts the consumed U64 value and buffered size_t value, explicitly converts the latter to u64, and uses wrapping addition to match C's unsigned arithmetic. Focused tests cover zero, ordinary, and overflowing inputs. Test Plan: - `cargo clippy --manifest-path rust/Cargo.toml --no-default-features --features compression` -- passed before and after formatting. - The same clippy command with `--benches` -- passed before and after formatting. - The same clippy command with `--tests` -- passed before and after formatting. - `cargo +nightly fmt --manifest-path rust/Cargo.toml` -- passed. - Focused Rust tests for `frame_progression_ingested` -- 3 passed. - `make -B -C lib -j2 lib` -- passed. - `make -C tests test-rust-lib-smoke` -- passed. - `tests/fuzzer -s4560 -t56 -i57 -v` -- passed. - `make -C tests -j2 test-zstream` -- passed: 84 named, 5,800, and 9,479 randomized cases. - `git diff --check` and `git diff --cached --check` -- passed. The zstream run retains the pre-existing warning at `tests/zstreamtest.c:1899` about an unterminated initializer string.
1953 lines
59 KiB
Rust
1953 lines
59 KiB
Rust
#![allow(non_camel_case_types)]
|
|
#![allow(non_snake_case)]
|
|
#![allow(clippy::missing_safety_doc)]
|
|
#![allow(clippy::too_many_arguments)]
|
|
|
|
//! First high-level compression slice.
|
|
//!
|
|
//! The layout-independent one-shot entry point in this module drives the
|
|
//! already migrated compression leaves. `ZSTD_compressCCtx` uses the same
|
|
//! path after a narrow C-owned context reset; the public context lifecycle
|
|
//! remains C-owned because it shares the configuration-dependent private
|
|
//! `ZSTD_CCtx_s` layout. `ZSTD_compress2` and the complete-input simple
|
|
//! `ZSTD_compressStream2(..., ZSTD_e_end)` path dispatch through Rust while
|
|
//! retaining the C implementation for advanced and partial-stream cases.
|
|
|
|
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
|
|
#[cfg(not(test))]
|
|
use crate::zstd_compress_api::ZSTD_compressBound;
|
|
use crate::zstd_compress_frame::{
|
|
write_raw_block, ZSTD_rust_writeFrameHeader, ZSTD_writeLastEmptyBlock,
|
|
};
|
|
use crate::zstd_compress_literals::min_gain;
|
|
use crate::zstd_compress_params::{
|
|
ZSTD_rust_params_adjustCParams, ZSTD_rust_params_maxNbSeq, ZSTD_rust_params_selectCParams,
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT, ZSTD_RUST_PS_DISABLE,
|
|
};
|
|
use crate::zstd_compress_sequences::SeqDef;
|
|
use crate::zstd_compress_stats::{SeqStore_t, ZSTD_compressedBlockState_t};
|
|
use crate::zstd_compress_superblock::ZSTD_rust_compressSuperBlock;
|
|
use std::ffi::c_void;
|
|
use std::mem::{size_of, MaybeUninit};
|
|
use std::os::raw::c_int;
|
|
use std::ptr;
|
|
|
|
#[cfg(not(test))]
|
|
unsafe extern "C" {
|
|
fn ZSTD_rust_resetCCtxForSimpleCompression(cctx: *mut c_void) -> usize;
|
|
fn ZSTD_rust_prepareCCtxForSimpleCompression(
|
|
cctx: *mut c_void,
|
|
src_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize;
|
|
fn ZSTD_rust_compressCCtxStrategy(src_size: usize, compression_level: c_int) -> c_int;
|
|
fn ZSTD_rust_resetCCtxForSimpleCompressionSession(cctx: *mut c_void) -> usize;
|
|
fn ZSTD_rust_markSimpleCompression2Complete(cctx: *mut c_void);
|
|
fn ZSTD_rust_simpleCompress2Level(cctx: *const c_void) -> c_int;
|
|
fn ZSTD_compress_usingDict(
|
|
cctx: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize;
|
|
fn ZSTD_rust_simpleCompressStream2Level(cctx: *const c_void) -> c_int;
|
|
fn ZSTD_compress2_c(
|
|
cctx: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize;
|
|
fn ZSTD_compressStream2_c(
|
|
cctx: *mut c_void,
|
|
output: *mut ZSTD_outBuffer,
|
|
input: *mut ZSTD_inBuffer,
|
|
end_op: c_int,
|
|
) -> usize;
|
|
}
|
|
|
|
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;
|
|
const ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS: c_int = 1;
|
|
const ZSTD_BLOCKSIZE_MAX: usize = 1 << 17;
|
|
const ZSTD_CONTENTSIZE_UNKNOWN: u64 = u64::MAX;
|
|
const ZSTD_TARGET_CBLOCK_BSS_COMPRESS: c_int = 0;
|
|
const ZSTD_BLOCK_HEADER_SIZE: usize = 3;
|
|
const ZSTD_ROWSIZE: usize = 16;
|
|
const ZSTD_WINDOW_START_INDEX: u32 = 2;
|
|
const ZSTD_DUBT_UNSORTED_MARK: u32 = 1;
|
|
const ZSTD_INDEXOVERFLOW_MARGIN: usize = 16usize << 20;
|
|
const ZSTD_SHORT_CACHE_TAG_BITS: u32 = 8;
|
|
const ZSTD_CURRENT_MAX: usize = if size_of::<usize>() == 8 {
|
|
3500usize << 20
|
|
} else {
|
|
2000usize << 20
|
|
};
|
|
const ZSTD_CHUNKSIZE_MAX: usize = u32::MAX as usize - ZSTD_CURRENT_MAX;
|
|
#[cfg(not(test))]
|
|
const ZSTD_E_END: c_int = 2;
|
|
|
|
#[repr(i32)]
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
enum TargetCBlockAction {
|
|
Raw = 0,
|
|
Rle = 1,
|
|
Compressed = 2,
|
|
Error = 3,
|
|
}
|
|
|
|
#[inline]
|
|
fn target_c_block_size_action(
|
|
bss: c_int,
|
|
is_first_block: c_int,
|
|
maybe_rle: c_int,
|
|
is_rle: c_int,
|
|
c_size: usize,
|
|
src_size: usize,
|
|
strategy: c_int,
|
|
) -> TargetCBlockAction {
|
|
if bss != ZSTD_TARGET_CBLOCK_BSS_COMPRESS {
|
|
return TargetCBlockAction::Raw;
|
|
}
|
|
if is_first_block == 0 && maybe_rle != 0 && is_rle != 0 {
|
|
return TargetCBlockAction::Rle;
|
|
}
|
|
if c_size == 0 || c_size == ERROR(ZstdErrorCode::DstSizeTooSmall) {
|
|
return TargetCBlockAction::Raw;
|
|
}
|
|
if ERR_isError(c_size) {
|
|
return TargetCBlockAction::Error;
|
|
}
|
|
|
|
let max_c_size = src_size.wrapping_sub(min_gain(src_size, strategy));
|
|
if c_size < max_c_size.wrapping_add(ZSTD_BLOCK_HEADER_SIZE) {
|
|
TargetCBlockAction::Compressed
|
|
} else {
|
|
TargetCBlockAction::Raw
|
|
}
|
|
}
|
|
|
|
/// Classify the target-sized block policy without crossing the C context ABI.
|
|
///
|
|
/// C calls this once before the superblock attempt to classify the RLE
|
|
/// precondition, then again with the superblock result and zero RLE flags.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_targetCBlockSizeAction(
|
|
bss: c_int,
|
|
is_first_block: c_int,
|
|
maybe_rle: c_int,
|
|
is_rle: c_int,
|
|
c_size: usize,
|
|
src_size: usize,
|
|
strategy: c_int,
|
|
) -> c_int {
|
|
target_c_block_size_action(
|
|
bss,
|
|
is_first_block,
|
|
maybe_rle,
|
|
is_rle,
|
|
c_size,
|
|
src_size,
|
|
strategy,
|
|
) as c_int
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct ZSTD_inBuffer {
|
|
src: *const c_void,
|
|
size: usize,
|
|
pos: usize,
|
|
}
|
|
|
|
#[cfg(not(test))]
|
|
#[repr(C)]
|
|
pub struct ZSTD_outBuffer {
|
|
dst: *mut c_void,
|
|
size: usize,
|
|
pos: usize,
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_inBufferForEndFlush(
|
|
in_buffer_mode: c_int,
|
|
expected_src: *const c_void,
|
|
expected_size: usize,
|
|
expected_pos: usize,
|
|
) -> ZSTD_inBuffer {
|
|
if in_buffer_mode == ZSTD_BM_STABLE {
|
|
ZSTD_inBuffer {
|
|
src: expected_src,
|
|
size: expected_size,
|
|
pos: expected_pos,
|
|
}
|
|
} else {
|
|
ZSTD_inBuffer {
|
|
src: ptr::null(),
|
|
size: 0,
|
|
pos: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn end_stream_remaining(
|
|
remaining_to_flush: usize,
|
|
frame_ended: c_int,
|
|
checksum_flag: c_int,
|
|
) -> usize {
|
|
if frame_ended != 0 {
|
|
return remaining_to_flush;
|
|
}
|
|
|
|
remaining_to_flush
|
|
.wrapping_add(ZSTD_BLOCK_HEADER_SIZE)
|
|
.wrapping_add((checksum_flag as usize).wrapping_mul(4))
|
|
}
|
|
|
|
/// Estimate single-threaded end-stream output without crossing C context state.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_endStreamRemaining(
|
|
remaining_to_flush: usize,
|
|
frame_ended: c_int,
|
|
checksum_flag: c_int,
|
|
) -> usize {
|
|
end_stream_remaining(remaining_to_flush, frame_ended, checksum_flag)
|
|
}
|
|
|
|
#[inline]
|
|
fn check_buffer_stability(
|
|
in_buffer_mode: c_int,
|
|
out_buffer_mode: c_int,
|
|
expected_in_src: *const c_void,
|
|
expected_in_pos: usize,
|
|
input_src: *const c_void,
|
|
input_pos: usize,
|
|
expected_out_buffer_size: usize,
|
|
output_size: usize,
|
|
output_pos: usize,
|
|
) -> usize {
|
|
if in_buffer_mode == ZSTD_BM_STABLE
|
|
&& (expected_in_src != input_src || expected_in_pos != input_pos)
|
|
{
|
|
return ERROR(ZstdErrorCode::StabilityConditionNotRespected);
|
|
}
|
|
|
|
if out_buffer_mode == ZSTD_BM_STABLE
|
|
&& expected_out_buffer_size != output_size.wrapping_sub(output_pos)
|
|
{
|
|
return ERROR(ZstdErrorCode::StabilityConditionNotRespected);
|
|
}
|
|
|
|
0
|
|
}
|
|
|
|
/// Validate the stable input/output buffer expectations without crossing the
|
|
/// private `ZSTD_CCtx` layout into Rust. Raw pointers are compared only for
|
|
/// identity and are never dereferenced.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_checkBufferStability(
|
|
in_buffer_mode: c_int,
|
|
out_buffer_mode: c_int,
|
|
expected_in_src: *const c_void,
|
|
expected_in_pos: usize,
|
|
input_src: *const c_void,
|
|
input_pos: usize,
|
|
expected_out_buffer_size: usize,
|
|
output_size: usize,
|
|
output_pos: usize,
|
|
) -> usize {
|
|
check_buffer_stability(
|
|
in_buffer_mode,
|
|
out_buffer_mode,
|
|
expected_in_src,
|
|
expected_in_pos,
|
|
input_src,
|
|
input_pos,
|
|
expected_out_buffer_size,
|
|
output_size,
|
|
output_pos,
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
fn select_sequence_copier(mode: c_int) -> c_int {
|
|
debug_assert!(
|
|
(ZSTD_SF_NO_BLOCK_DELIMITERS..=ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS).contains(&mode)
|
|
);
|
|
if mode == ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS {
|
|
ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS
|
|
} else {
|
|
debug_assert_eq!(mode, ZSTD_SF_NO_BLOCK_DELIMITERS);
|
|
ZSTD_SF_NO_BLOCK_DELIMITERS
|
|
}
|
|
}
|
|
|
|
/// Select the C-side sequence transfer policy without crossing private
|
|
/// function pointers through the Rust ABI. Invalid values retain the C
|
|
/// release fallback to the no-delimiter policy after debug validation.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_selectSequenceCopier(mode: c_int) -> c_int {
|
|
select_sequence_copier(mode)
|
|
}
|
|
|
|
/* HUF_WORKSPACE_SIZE + (MaxSeq + 2) * sizeof(unsigned), rounded up. The
|
|
* superblock leaf also accepts the larger pre-split workspace, so a fixed
|
|
* 16 KiB buffer is sufficient for this first non-splitting path on both
|
|
* supported pointer widths. */
|
|
const TMP_WORKSPACE_SIZE: usize = 16 << 10;
|
|
|
|
#[inline]
|
|
fn update_frame_progression(
|
|
consumed_src_size: &mut u64,
|
|
produced_c_size: &mut u64,
|
|
pledged_src_size_plus_one: u64,
|
|
src_size: usize,
|
|
c_size: usize,
|
|
frame_header_size: usize,
|
|
) -> bool {
|
|
*consumed_src_size = consumed_src_size.wrapping_add(src_size as u64);
|
|
*produced_c_size = produced_c_size.wrapping_add(c_size.wrapping_add(frame_header_size) as u64);
|
|
|
|
pledged_src_size_plus_one != 0 && consumed_src_size.wrapping_add(1) > pledged_src_size_plus_one
|
|
}
|
|
|
|
/// Update C-owned frame counters after successful compression.
|
|
///
|
|
/// A zero pledge means that the source size is unknown. C keeps the
|
|
/// diagnostic that accompanies a nonzero overrun result.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_updateFrameProgression(
|
|
consumed_src_size: *mut u64,
|
|
produced_c_size: *mut u64,
|
|
pledged_src_size_plus_one: u64,
|
|
src_size: usize,
|
|
c_size: usize,
|
|
frame_header_size: usize,
|
|
) -> c_int {
|
|
let consumed_src_size = unsafe { &mut *consumed_src_size };
|
|
let produced_c_size = unsafe { &mut *produced_c_size };
|
|
update_frame_progression(
|
|
consumed_src_size,
|
|
produced_c_size,
|
|
pledged_src_size_plus_one,
|
|
src_size,
|
|
c_size,
|
|
frame_header_size,
|
|
) as c_int
|
|
}
|
|
|
|
#[inline]
|
|
fn frame_progression_ingested(consumed_src_size: u64, buffered: usize) -> u64 {
|
|
// C's usual arithmetic conversions promote size_t to U64 before wrapping.
|
|
consumed_src_size.wrapping_add(buffered as u64)
|
|
}
|
|
|
|
/// Calculate single-threaded frame input progression from consumed and buffered input.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_frameProgressionIngested(
|
|
consumed_src_size: u64,
|
|
buffered: usize,
|
|
) -> u64 {
|
|
frame_progression_ingested(consumed_src_size, buffered)
|
|
}
|
|
|
|
#[inline]
|
|
fn next_input_size_hint(
|
|
in_buffer_mode: c_int,
|
|
block_size_max: usize,
|
|
stable_in_not_consumed: usize,
|
|
in_buff_target: usize,
|
|
in_buff_pos: usize,
|
|
) -> usize {
|
|
if in_buffer_mode == ZSTD_BM_STABLE {
|
|
return block_size_max.wrapping_sub(stable_in_not_consumed);
|
|
}
|
|
|
|
let hint_in_size = in_buff_target.wrapping_sub(in_buff_pos);
|
|
if hint_in_size == 0 {
|
|
block_size_max
|
|
} else {
|
|
hint_in_size
|
|
}
|
|
}
|
|
|
|
/// Return the next input size required by the C streaming state machine.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_nextInputSizeHint(
|
|
in_buffer_mode: c_int,
|
|
block_size_max: usize,
|
|
stable_in_not_consumed: usize,
|
|
in_buff_target: usize,
|
|
in_buff_pos: usize,
|
|
) -> usize {
|
|
next_input_size_hint(
|
|
in_buffer_mode,
|
|
block_size_max,
|
|
stable_in_not_consumed,
|
|
in_buff_target,
|
|
in_buff_pos,
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
fn mt_next_input_size_hint(target_section_size: usize, in_buff_filled: usize) -> usize {
|
|
let hint_in_size = target_section_size.wrapping_sub(in_buff_filled);
|
|
if hint_in_size == 0 {
|
|
target_section_size
|
|
} else {
|
|
hint_in_size
|
|
}
|
|
}
|
|
|
|
/// Return the next input size required by the C multithreaded streaming state.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTDMT_rust_nextInputSizeHint(
|
|
target_section_size: usize,
|
|
in_buff_filled: usize,
|
|
) -> usize {
|
|
mt_next_input_size_hint(target_section_size, in_buff_filled)
|
|
}
|
|
|
|
#[inline]
|
|
fn mt_sizeof_cctx(
|
|
mtctx_size: usize,
|
|
factory_size: usize,
|
|
buffer_pool_size: usize,
|
|
jobs_size: usize,
|
|
cctx_pool_size: usize,
|
|
seq_pool_size: usize,
|
|
cdict_size: usize,
|
|
round_buff_size: usize,
|
|
) -> usize {
|
|
mtctx_size
|
|
.wrapping_add(factory_size)
|
|
.wrapping_add(buffer_pool_size)
|
|
.wrapping_add(jobs_size)
|
|
.wrapping_add(cctx_pool_size)
|
|
.wrapping_add(seq_pool_size)
|
|
.wrapping_add(cdict_size)
|
|
.wrapping_add(round_buff_size)
|
|
}
|
|
|
|
/// Aggregate C-owned multithreaded context size components with C `size_t`
|
|
/// wrapping semantics.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTDMT_rust_sizeofCCtx(
|
|
mtctx_size: usize,
|
|
factory_size: usize,
|
|
buffer_pool_size: usize,
|
|
jobs_size: usize,
|
|
cctx_pool_size: usize,
|
|
seq_pool_size: usize,
|
|
cdict_size: usize,
|
|
round_buff_size: usize,
|
|
) -> usize {
|
|
mt_sizeof_cctx(
|
|
mtctx_size,
|
|
factory_size,
|
|
buffer_pool_size,
|
|
jobs_size,
|
|
cctx_pool_size,
|
|
seq_pool_size,
|
|
cdict_size,
|
|
round_buff_size,
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
fn bitmix(mut val: u64, len: u64) -> u64 {
|
|
val ^= val.rotate_right(49) ^ val.rotate_right(24);
|
|
val = val.wrapping_mul(0x9FB21C651E98DF25);
|
|
val ^= (val >> 35).wrapping_add(len);
|
|
val = val.wrapping_mul(0x9FB21C651E98DF25);
|
|
val ^ (val >> 28)
|
|
}
|
|
|
|
#[inline]
|
|
fn advance_hash_salt(hash_salt: u64, hash_salt_entropy: u64) -> u64 {
|
|
bitmix(hash_salt, 8) ^ bitmix(hash_salt_entropy, 4)
|
|
}
|
|
|
|
/// Advance the row-matchfinder salt without exposing C's private match state.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_advanceHashSalt(hash_salt: u64, hash_salt_entropy: u64) -> u64 {
|
|
advance_hash_salt(hash_salt, hash_salt_entropy)
|
|
}
|
|
|
|
#[inline]
|
|
fn index_too_close_to_max(next_src_base_offset: usize) -> bool {
|
|
next_src_base_offset > ZSTD_CURRENT_MAX - ZSTD_INDEXOVERFLOW_MARGIN
|
|
}
|
|
|
|
/// Return whether a scalar C window offset is within the overflow margin.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_indexTooCloseToMax(next_src_base_offset: usize) -> c_int {
|
|
index_too_close_to_max(next_src_base_offset) as c_int
|
|
}
|
|
|
|
#[inline]
|
|
fn dict_too_big(loaded_dict_size: usize) -> bool {
|
|
loaded_dict_size > ZSTD_CHUNKSIZE_MAX
|
|
}
|
|
|
|
/// Return whether a dictionary exceeds the maximum loadable chunk size.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_dictTooBig(loaded_dict_size: usize) -> c_int {
|
|
dict_too_big(loaded_dict_size) as c_int
|
|
}
|
|
|
|
#[inline]
|
|
fn sizeof_local_dict(dict_buffer_present: c_int, dict_size: usize, cdict_size: usize) -> usize {
|
|
let buffer_size = if dict_buffer_present != 0 {
|
|
dict_size
|
|
} else {
|
|
0
|
|
};
|
|
buffer_size.wrapping_add(cdict_size)
|
|
}
|
|
|
|
/// Add the C-owned local-dictionary sizes with C `size_t` wrapping semantics.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_sizeofLocalDict(
|
|
dict_buffer_present: c_int,
|
|
dict_size: usize,
|
|
cdict_size: usize,
|
|
) -> usize {
|
|
sizeof_local_dict(dict_buffer_present, dict_size, cdict_size)
|
|
}
|
|
|
|
#[inline]
|
|
fn sizeof_cdict(object_size: usize, workspace_size: usize) -> usize {
|
|
object_size.wrapping_add(workspace_size)
|
|
}
|
|
|
|
/// Aggregate C-owned dictionary size components with C `size_t` wrapping
|
|
/// semantics.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_sizeofCDict(objectSize: usize, workspaceSize: usize) -> usize {
|
|
sizeof_cdict(objectSize, workspaceSize)
|
|
}
|
|
|
|
#[inline]
|
|
fn sizeof_cctx(
|
|
object_size: usize,
|
|
workspace_size: usize,
|
|
local_dict_size: usize,
|
|
mtctx_size: usize,
|
|
) -> usize {
|
|
object_size
|
|
.wrapping_add(workspace_size)
|
|
.wrapping_add(local_dict_size)
|
|
.wrapping_add(mtctx_size)
|
|
}
|
|
|
|
/// Aggregate C-owned context size components with C `size_t` wrapping
|
|
/// semantics.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_sizeofCCtx(
|
|
object_size: usize,
|
|
workspace_size: usize,
|
|
local_dict_size: usize,
|
|
mtctx_size: usize,
|
|
) -> usize {
|
|
sizeof_cctx(object_size, workspace_size, local_dict_size, mtctx_size)
|
|
}
|
|
|
|
#[inline]
|
|
fn estimate_workspace_size(
|
|
cctx_space: usize,
|
|
tmp_work_space: usize,
|
|
block_state_space: usize,
|
|
ldm_space: usize,
|
|
ldm_seq_space: usize,
|
|
match_state_size: usize,
|
|
token_space: usize,
|
|
buffer_space: usize,
|
|
external_seq_space: usize,
|
|
) -> usize {
|
|
cctx_space
|
|
.wrapping_add(tmp_work_space)
|
|
.wrapping_add(block_state_space)
|
|
.wrapping_add(ldm_space)
|
|
.wrapping_add(ldm_seq_space)
|
|
.wrapping_add(match_state_size)
|
|
.wrapping_add(token_space)
|
|
.wrapping_add(buffer_space)
|
|
.wrapping_add(external_seq_space)
|
|
}
|
|
|
|
/// Aggregate workspace-size components with C `size_t` wrapping semantics.
|
|
#[no_mangle]
|
|
pub extern "C" fn ZSTD_rust_estimateWorkspaceSize(
|
|
cctx_space: usize,
|
|
tmp_work_space: usize,
|
|
block_state_space: usize,
|
|
ldm_space: usize,
|
|
ldm_seq_space: usize,
|
|
match_state_size: usize,
|
|
token_space: usize,
|
|
buffer_space: usize,
|
|
external_seq_space: usize,
|
|
) -> usize {
|
|
estimate_workspace_size(
|
|
cctx_space,
|
|
tmp_work_space,
|
|
block_state_space,
|
|
ldm_space,
|
|
ldm_seq_space,
|
|
match_state_size,
|
|
token_space,
|
|
buffer_space,
|
|
external_seq_space,
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
fn reduce_table_internal(table: &mut [u32], reducer_value: u32, preserve_mark: bool) {
|
|
debug_assert_eq!(table.len() % ZSTD_ROWSIZE, 0);
|
|
debug_assert!(table.len() < (1usize << 31));
|
|
|
|
/* Protect special index values < ZSTD_WINDOW_START_INDEX. */
|
|
let reducer_threshold = reducer_value.wrapping_add(ZSTD_WINDOW_START_INDEX);
|
|
let mut rows = table.chunks_exact_mut(ZSTD_ROWSIZE);
|
|
for row in &mut rows {
|
|
for cell in row {
|
|
let value = *cell;
|
|
*cell = if preserve_mark && value == ZSTD_DUBT_UNSORTED_MARK {
|
|
/* Keep the btlazy2 unsorted marker across table reduction. */
|
|
ZSTD_DUBT_UNSORTED_MARK
|
|
} else if value < reducer_threshold {
|
|
0
|
|
} else {
|
|
value.wrapping_sub(reducer_value)
|
|
};
|
|
}
|
|
}
|
|
debug_assert!(rows.into_remainder().is_empty());
|
|
}
|
|
|
|
/// Rust implementation of the C match-table reduction leaf.
|
|
///
|
|
/// The C wrappers select the ordinary or btlazy2 policy by passing a clear
|
|
/// zero/one `preserve_mark` value; `ZSTD_reduceIndex` remains C-owned.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_reduceTable(
|
|
table: *mut u32,
|
|
size: u32,
|
|
reducer_value: u32,
|
|
preserve_mark: c_int,
|
|
) {
|
|
debug_assert!(!table.is_null() || size == 0);
|
|
debug_assert_eq!(size % ZSTD_ROWSIZE as u32, 0);
|
|
debug_assert!(size < (1u32 << 31));
|
|
if size == 0 {
|
|
return;
|
|
}
|
|
|
|
let table = unsafe { std::slice::from_raw_parts_mut(table, size as usize) };
|
|
reduce_table_internal(table, reducer_value, preserve_mark != 0);
|
|
}
|
|
|
|
/// Copies a CDict match table into a CCtx, removing short-cache tags when the
|
|
/// C-owned compression parameters say the source table is tagged.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_copyCDictTableIntoCCtx(
|
|
dst: *mut u32,
|
|
src: *const u32,
|
|
table_size: usize,
|
|
tagged: c_int,
|
|
) {
|
|
if tagged != 0 {
|
|
for i in 0..table_size {
|
|
let index = unsafe { *src.add(i) } >> ZSTD_SHORT_CACHE_TAG_BITS;
|
|
unsafe { *dst.add(i) = index };
|
|
}
|
|
} else if table_size != 0 {
|
|
unsafe { ptr::copy_nonoverlapping(src, dst, table_size) };
|
|
}
|
|
}
|
|
|
|
/// Clear the previous block's repcodes before the next regular compression.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_invalidateRepCodes(rep: *mut u32) {
|
|
debug_assert!(!rep.is_null());
|
|
let rep = unsafe { std::slice::from_raw_parts_mut(rep, ZSTD_REP_NUM) };
|
|
rep.fill(0);
|
|
}
|
|
|
|
#[inline]
|
|
fn zeroed_state() -> ZSTD_compressedBlockState_t {
|
|
/* The state contains only integer arrays and enum fields. */
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() }
|
|
}
|
|
|
|
#[inline]
|
|
fn checked_table_size(log: u32) -> Option<usize> {
|
|
1usize.checked_shl(log)
|
|
}
|
|
|
|
#[inline]
|
|
fn ceil_log2(size: usize) -> u32 {
|
|
if size <= 1 {
|
|
0
|
|
} else {
|
|
usize::BITS - (size - 1).leading_zeros()
|
|
}
|
|
}
|
|
|
|
/// Compress one frame using the already migrated block leaves.
|
|
///
|
|
/// This path deliberately starts a fresh match table for each 128 KiB block.
|
|
/// That keeps the Rust-owned context independent from the still-private C
|
|
/// `ZSTD_MatchState_t` window while producing ordinary zstd blocks that any
|
|
/// decoder can consume.
|
|
unsafe fn compress_frame(
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize {
|
|
if dst.is_null() {
|
|
return ERROR(if dst_capacity == 0 {
|
|
ZstdErrorCode::DstSizeTooSmall
|
|
} else {
|
|
ZstdErrorCode::DstBufferNull
|
|
});
|
|
}
|
|
if src_size != 0 && src.is_null() {
|
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
|
}
|
|
if src_size as u64 == ZSTD_CONTENTSIZE_UNKNOWN {
|
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
|
}
|
|
|
|
let mut cparams = ZSTD_rust_params_selectCParams(
|
|
compression_level,
|
|
src_size as u64,
|
|
0,
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
|
);
|
|
cparams = ZSTD_rust_params_adjustCParams(
|
|
cparams,
|
|
src_size as u64,
|
|
0,
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
|
ZSTD_RUST_PS_DISABLE,
|
|
);
|
|
|
|
let header_size = unsafe {
|
|
ZSTD_rust_writeFrameHeader(
|
|
dst,
|
|
dst_capacity,
|
|
0, /* noDictIDFlag */
|
|
0, /* checksumFlag */
|
|
1, /* contentSizeFlag */
|
|
0, /* zstd frame */
|
|
cparams.windowLog,
|
|
src_size as u64,
|
|
0,
|
|
)
|
|
};
|
|
if ERR_isError(header_size) {
|
|
return header_size;
|
|
}
|
|
|
|
if src_size == 0 {
|
|
let empty_block = unsafe {
|
|
ZSTD_writeLastEmptyBlock(
|
|
dst.cast::<u8>().add(header_size).cast(),
|
|
dst_capacity - header_size,
|
|
)
|
|
};
|
|
return if ERR_isError(empty_block) {
|
|
empty_block
|
|
} else {
|
|
header_size + empty_block
|
|
};
|
|
}
|
|
|
|
let first_block_size = src_size.min(ZSTD_BLOCKSIZE_MAX);
|
|
/* A table larger than the block cannot contain a useful index for this
|
|
* independent-block path. This also keeps one-shot small inputs from
|
|
* allocating the table selected for a huge source-size hint. */
|
|
let matcher_hash_log = cparams.hashLog.min(ceil_log2(first_block_size).max(6));
|
|
let matcher_chain_log = cparams.chainLog.min(ceil_log2(first_block_size).max(6));
|
|
let hash_size = match checked_table_size(matcher_hash_log) {
|
|
Some(size) => size,
|
|
None => return ERROR(ZstdErrorCode::MemoryAllocation),
|
|
};
|
|
let chain_size = match checked_table_size(matcher_chain_log) {
|
|
Some(size) => size,
|
|
None => return ERROR(ZstdErrorCode::MemoryAllocation),
|
|
};
|
|
|
|
let max_nb_seq =
|
|
ZSTD_rust_params_maxNbSeq(ZSTD_BLOCKSIZE_MAX, cparams.minMatch, 0).saturating_add(1);
|
|
let disable_literal_compression =
|
|
c_int::from(cparams.strategy == ZSTD_FAST && cparams.targetLength > 0);
|
|
let mut sequences = vec![SeqDef::default(); max_nb_seq];
|
|
let mut literals = vec![0u8; ZSTD_BLOCKSIZE_MAX];
|
|
let mut ll_codes = vec![0u8; max_nb_seq];
|
|
let mut ml_codes = vec![0u8; max_nb_seq];
|
|
let mut of_codes = vec![0u8; max_nb_seq];
|
|
let mut hash_table = vec![0u32; hash_size];
|
|
let mut chain_table = vec![0u32; chain_size];
|
|
let mut workspace = vec![0u64; TMP_WORKSPACE_SIZE / size_of::<u64>()];
|
|
let mut prev_block = zeroed_state();
|
|
let mut next_block = zeroed_state();
|
|
prev_block.rep = [1, 4, 8];
|
|
next_block.rep = prev_block.rep;
|
|
|
|
let seq_store = &mut SeqStore_t {
|
|
sequencesStart: sequences.as_mut_ptr(),
|
|
sequences: sequences.as_mut_ptr(),
|
|
litStart: literals.as_mut_ptr(),
|
|
lit: literals.as_mut_ptr(),
|
|
llCode: ll_codes.as_mut_ptr(),
|
|
mlCode: ml_codes.as_mut_ptr(),
|
|
ofCode: of_codes.as_mut_ptr(),
|
|
maxNbSeq: max_nb_seq,
|
|
maxNbLit: ZSTD_BLOCKSIZE_MAX,
|
|
longLengthType: 0,
|
|
longLengthPos: 0,
|
|
};
|
|
|
|
let source = src.cast::<u8>();
|
|
let output = dst.cast::<u8>();
|
|
let mut input_offset = 0usize;
|
|
let mut output_offset = header_size;
|
|
while input_offset < src_size {
|
|
let block_size = (src_size - input_offset).min(ZSTD_BLOCKSIZE_MAX);
|
|
let block_src = unsafe { source.add(input_offset) };
|
|
let block_end = unsafe { block_src.add(block_size) };
|
|
|
|
seq_store.sequences = seq_store.sequencesStart;
|
|
seq_store.lit = seq_store.litStart;
|
|
seq_store.longLengthType = 0;
|
|
seq_store.longLengthPos = 0;
|
|
next_block.rep = prev_block.rep;
|
|
|
|
let mut reps = prev_block.rep;
|
|
let last_literals = if block_size < 8 {
|
|
block_size
|
|
} else if cparams.strategy == ZSTD_DFAST {
|
|
unsafe {
|
|
crate::zstd_double_fast::ZSTD_rust_compressBlock_doubleFast(
|
|
hash_table.as_mut_ptr(),
|
|
chain_table.as_mut_ptr(),
|
|
block_src,
|
|
0,
|
|
0,
|
|
matcher_hash_log,
|
|
matcher_chain_log,
|
|
cparams.minMatch,
|
|
cparams.windowLog,
|
|
(seq_store as *mut SeqStore_t).cast(),
|
|
reps.as_mut_ptr(),
|
|
block_src.cast(),
|
|
block_size,
|
|
)
|
|
}
|
|
} else {
|
|
unsafe {
|
|
crate::zstd_fast::ZSTD_rust_compressBlock_fast(
|
|
hash_table.as_mut_ptr(),
|
|
block_src,
|
|
0,
|
|
0,
|
|
matcher_hash_log,
|
|
cparams.minMatch,
|
|
cparams.targetLength,
|
|
cparams.windowLog,
|
|
(seq_store as *mut SeqStore_t).cast(),
|
|
reps.as_mut_ptr(),
|
|
block_src.cast(),
|
|
block_size,
|
|
)
|
|
}
|
|
};
|
|
|
|
if last_literals > block_size {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let last_literal_src = unsafe { block_end.sub(last_literals) };
|
|
if last_literals != 0 {
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(last_literal_src, seq_store.lit, last_literals);
|
|
seq_store.lit = seq_store.lit.add(last_literals);
|
|
}
|
|
}
|
|
next_block.rep = reps;
|
|
|
|
let last_block = u32::from(input_offset + block_size == src_size);
|
|
let remaining_capacity = dst_capacity.saturating_sub(output_offset);
|
|
let mut written = unsafe {
|
|
ZSTD_rust_compressSuperBlock(
|
|
(seq_store as *mut SeqStore_t).cast(),
|
|
(&prev_block as *const ZSTD_compressedBlockState_t).cast(),
|
|
(&mut next_block as *mut ZSTD_compressedBlockState_t).cast(),
|
|
cparams.strategy,
|
|
disable_literal_compression,
|
|
workspace.as_mut_ptr().cast(),
|
|
TMP_WORKSPACE_SIZE,
|
|
0, /* BMI2 is optional; portable Rust leaf path */
|
|
cparams.windowLog,
|
|
0, /* no target compressed block size */
|
|
output.add(output_offset).cast(),
|
|
remaining_capacity,
|
|
block_src.cast(),
|
|
block_size,
|
|
last_block,
|
|
)
|
|
};
|
|
if ERR_isError(written) {
|
|
return written;
|
|
}
|
|
if written == 0 {
|
|
written = unsafe {
|
|
write_raw_block(
|
|
output.add(output_offset),
|
|
remaining_capacity,
|
|
block_src,
|
|
block_size,
|
|
last_block,
|
|
)
|
|
};
|
|
if ERR_isError(written) {
|
|
return written;
|
|
}
|
|
}
|
|
if written > remaining_capacity {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
output_offset += written;
|
|
input_offset += block_size;
|
|
std::mem::swap(&mut prev_block, &mut next_block);
|
|
hash_table.fill(0);
|
|
chain_table.fill(0);
|
|
}
|
|
|
|
output_offset
|
|
}
|
|
|
|
/// Simple one-shot compression entry point.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_compress(
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize {
|
|
unsafe { compress_frame(dst, dst_capacity, src, src_size, compression_level) }
|
|
}
|
|
|
|
/// Simple explicit-context compression entry point.
|
|
///
|
|
/// The public contract deliberately ignores all advanced context parameters.
|
|
/// C performs the context reset because the private `ZSTD_CCtx_s` layout is
|
|
/// still configuration-dependent. Strategies not yet implemented by the
|
|
/// Rust frame compressor use the original C simple API before that reset.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_compressCCtx(
|
|
cctx: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize {
|
|
if cctx.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
#[cfg(not(test))]
|
|
{
|
|
let strategy = unsafe { ZSTD_rust_compressCCtxStrategy(src_size, compression_level) };
|
|
if strategy != ZSTD_FAST && strategy != ZSTD_DFAST {
|
|
return unsafe {
|
|
ZSTD_compress_usingDict(
|
|
cctx,
|
|
dst,
|
|
dst_capacity,
|
|
src,
|
|
src_size,
|
|
ptr::null(),
|
|
0,
|
|
compression_level,
|
|
)
|
|
};
|
|
}
|
|
let reset = unsafe { ZSTD_rust_resetCCtxForSimpleCompression(cctx) };
|
|
if ERR_isError(reset) {
|
|
return reset;
|
|
}
|
|
let prepare =
|
|
unsafe { ZSTD_rust_prepareCCtxForSimpleCompression(cctx, src_size, compression_level) };
|
|
if ERR_isError(prepare) {
|
|
return prepare;
|
|
}
|
|
}
|
|
unsafe { compress_frame(dst, dst_capacity, src, src_size, compression_level) }
|
|
}
|
|
|
|
/// Stateful compression entry point during the context migration.
|
|
///
|
|
/// A context with only the ordinary frame settings is reset by the C shim and
|
|
/// compressed through the Rust frame path. Contexts using dictionaries,
|
|
/// checksums, target-sized blocks, sequence collection, or other advanced
|
|
/// state still use the renamed C implementation until their state is moved.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_compress2(
|
|
cctx: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
if cctx.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
{
|
|
unsafe { compress_frame(dst, dst_capacity, src, src_size, 3) }
|
|
}
|
|
|
|
#[cfg(not(test))]
|
|
{
|
|
let level = unsafe { ZSTD_rust_simpleCompress2Level(cctx.cast_const()) };
|
|
if level != c_int::MIN {
|
|
let reset = unsafe { ZSTD_rust_resetCCtxForSimpleCompressionSession(cctx) };
|
|
if ERR_isError(reset) {
|
|
return reset;
|
|
}
|
|
let prepare =
|
|
unsafe { ZSTD_rust_prepareCCtxForSimpleCompression(cctx, src_size, level) };
|
|
if ERR_isError(prepare) {
|
|
return prepare;
|
|
}
|
|
let result = unsafe { compress_frame(dst, dst_capacity, src, src_size, level) };
|
|
if !ERR_isError(result) {
|
|
unsafe { ZSTD_rust_markSimpleCompression2Complete(cctx) };
|
|
}
|
|
return result;
|
|
}
|
|
unsafe { ZSTD_compress2_c(cctx, dst, dst_capacity, src, src_size) }
|
|
}
|
|
}
|
|
|
|
/// Compress a streaming call when the complete input and a full output bound
|
|
/// are already available for the ordinary context configuration.
|
|
///
|
|
/// The C implementation remains the fallback for partial-output streaming,
|
|
/// `ZSTD_e_continue`/`ZSTD_e_flush`, dictionaries, and every advanced context
|
|
/// configuration. The Rust path resets the C-owned session after emitting a
|
|
/// complete frame so the same context can immediately start another frame.
|
|
#[cfg(not(test))]
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_compressStream2(
|
|
cctx: *mut c_void,
|
|
output: *mut ZSTD_outBuffer,
|
|
input: *mut ZSTD_inBuffer,
|
|
end_op: c_int,
|
|
) -> usize {
|
|
if cctx.is_null() || output.is_null() || input.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let output_ref = unsafe { &mut *output };
|
|
let input_ref = unsafe { &mut *input };
|
|
if output_ref.pos > output_ref.size {
|
|
return ERROR(ZstdErrorCode::DstSizeTooSmall);
|
|
}
|
|
if input_ref.pos > input_ref.size {
|
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
|
}
|
|
|
|
if end_op == ZSTD_E_END {
|
|
let level = unsafe { ZSTD_rust_simpleCompressStream2Level(cctx.cast_const()) };
|
|
if level != c_int::MIN {
|
|
let src_size = input_ref.size - input_ref.pos;
|
|
let dst_capacity = output_ref.size - output_ref.pos;
|
|
let bound = ZSTD_compressBound(src_size);
|
|
if !ERR_isError(bound) && dst_capacity >= bound {
|
|
if dst_capacity != 0 && output_ref.dst.is_null() {
|
|
return ERROR(ZstdErrorCode::DstBufferNull);
|
|
}
|
|
if src_size != 0 && input_ref.src.is_null() {
|
|
return ERROR(ZstdErrorCode::SrcSizeWrong);
|
|
}
|
|
let dst = if output_ref.dst.is_null() {
|
|
ptr::null_mut()
|
|
} else {
|
|
unsafe { output_ref.dst.cast::<u8>().add(output_ref.pos).cast() }
|
|
};
|
|
let src = if input_ref.src.is_null() {
|
|
ptr::null()
|
|
} else {
|
|
unsafe { input_ref.src.cast::<u8>().add(input_ref.pos).cast() }
|
|
};
|
|
let result = unsafe { compress_frame(dst, dst_capacity, src, src_size, level) };
|
|
if !ERR_isError(result) {
|
|
output_ref.pos += result;
|
|
input_ref.pos = input_ref.size;
|
|
return unsafe { ZSTD_rust_resetCCtxForSimpleCompressionSession(cctx) };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe { ZSTD_compressStream2_c(cctx, output, input, end_op) }
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::io::Write;
|
|
use std::process::{Command, Stdio};
|
|
|
|
fn system_round_trip(compressed: &[u8]) -> Option<Vec<u8>> {
|
|
let mut child = Command::new("zstd")
|
|
.args(["-q", "-d", "-c"])
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.spawn()
|
|
.ok()?;
|
|
child.stdin.take()?.write_all(compressed).ok()?;
|
|
let output = child.wait_with_output().ok()?;
|
|
if !output.status.success() {
|
|
panic!(
|
|
"system zstd rejected Rust output: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
Some(output.stdout)
|
|
}
|
|
|
|
fn compress_input(input: &[u8], level: c_int) -> Vec<u8> {
|
|
let capacity = crate::zstd_compress_api::ZSTD_compressBound(input.len());
|
|
assert!(!ERR_isError(capacity));
|
|
let mut output = vec![0u8; capacity];
|
|
let written = unsafe {
|
|
ZSTD_compress(
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
input.as_ptr().cast(),
|
|
input.len(),
|
|
level,
|
|
)
|
|
};
|
|
assert!(!ERR_isError(written));
|
|
output.truncate(written);
|
|
output
|
|
}
|
|
|
|
#[test]
|
|
fn reduce_table_applies_threshold_and_wrapping_subtraction() {
|
|
let mut table = [0, 1, 2, 3, 4, 5, 6, u32::MAX, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
reduce_table_internal(&mut table, 3, false);
|
|
assert_eq!(&table[..8], &[0, 0, 0, 0, 0, 2, 3, u32::MAX - 3]);
|
|
|
|
let mut wrapped_threshold = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
|
|
reduce_table_internal(&mut wrapped_threshold, u32::MAX, false);
|
|
assert_eq!(wrapped_threshold[0], 0);
|
|
assert_eq!(wrapped_threshold[1], 2);
|
|
assert_eq!(wrapped_threshold[15], 16);
|
|
}
|
|
|
|
#[test]
|
|
fn reduce_table_preserves_only_the_btlazy2_mark() {
|
|
let mut ordinary = [0u32; ZSTD_ROWSIZE];
|
|
ordinary[..3].copy_from_slice(&[ZSTD_DUBT_UNSORTED_MARK, 5, 6]);
|
|
reduce_table_internal(&mut ordinary, 3, false);
|
|
assert_eq!(&ordinary[..3], &[0, 2, 3]);
|
|
|
|
let mut btlazy2 = [0u32; ZSTD_ROWSIZE];
|
|
btlazy2[..3].copy_from_slice(&[ZSTD_DUBT_UNSORTED_MARK, 5, 6]);
|
|
reduce_table_internal(&mut btlazy2, 3, true);
|
|
assert_eq!(&btlazy2[..3], &[ZSTD_DUBT_UNSORTED_MARK, 2, 3]);
|
|
}
|
|
|
|
#[test]
|
|
fn reduce_table_processes_every_cell_in_multiple_rows() {
|
|
let mut table = [0u32; ZSTD_ROWSIZE * 2];
|
|
table[0] = 2;
|
|
table[ZSTD_ROWSIZE - 1] = 8;
|
|
table[ZSTD_ROWSIZE] = 1;
|
|
table[ZSTD_ROWSIZE * 2 - 1] = u32::MAX;
|
|
|
|
reduce_table_internal(&mut table, 4, false);
|
|
|
|
assert_eq!(table[0], 0);
|
|
assert_eq!(table[ZSTD_ROWSIZE - 1], 4);
|
|
assert_eq!(table[ZSTD_ROWSIZE], 0);
|
|
assert_eq!(table[ZSTD_ROWSIZE * 2 - 1], u32::MAX - 4);
|
|
}
|
|
|
|
#[test]
|
|
fn copy_cdict_table_removes_short_cache_tags() {
|
|
let source = [0x1234_56ff, 0xdead_beef, 0x0000_0100, u32::MAX];
|
|
let mut destination = [0u32; 4];
|
|
|
|
unsafe {
|
|
ZSTD_rust_copyCDictTableIntoCCtx(
|
|
destination.as_mut_ptr(),
|
|
source.as_ptr(),
|
|
source.len(),
|
|
1,
|
|
);
|
|
}
|
|
|
|
assert_eq!(destination, [0x0012_3456, 0x00de_adbe, 1, 0x00ff_ffff]);
|
|
}
|
|
|
|
#[test]
|
|
fn copy_cdict_table_preserves_untagged_entries() {
|
|
let source = [0, 2, 0x1234_5678, u32::MAX];
|
|
let mut destination = [0xa5a5_a5a5; 4];
|
|
|
|
unsafe {
|
|
ZSTD_rust_copyCDictTableIntoCCtx(
|
|
destination.as_mut_ptr(),
|
|
source.as_ptr(),
|
|
source.len(),
|
|
0,
|
|
);
|
|
}
|
|
|
|
assert_eq!(destination, source);
|
|
}
|
|
|
|
#[test]
|
|
fn bitmix_and_hash_salt_match_the_c_arithmetic() {
|
|
assert_eq!(bitmix(0x0123_4567_89ab_cdef, 8), 0xd498_d855_4e8d_d8cb);
|
|
assert_eq!(
|
|
advance_hash_salt(0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210),
|
|
0xe5ee_f172_e5ff_3e57
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_advanceHashSalt(0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210),
|
|
0xe5ee_f172_e5ff_3e57
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn index_too_close_to_max_uses_a_strict_margin_boundary() {
|
|
let threshold = ZSTD_CURRENT_MAX - ZSTD_INDEXOVERFLOW_MARGIN;
|
|
assert!(!index_too_close_to_max(threshold));
|
|
assert!(index_too_close_to_max(threshold + 1));
|
|
assert_eq!(ZSTD_rust_indexTooCloseToMax(threshold), 0);
|
|
assert_eq!(ZSTD_rust_indexTooCloseToMax(threshold + 1), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn dict_too_big_uses_a_strict_chunk_size_boundary() {
|
|
assert!(!dict_too_big(0));
|
|
assert!(!dict_too_big(ZSTD_CHUNKSIZE_MAX));
|
|
assert!(dict_too_big(ZSTD_CHUNKSIZE_MAX + 1));
|
|
assert_eq!(ZSTD_rust_dictTooBig(ZSTD_CHUNKSIZE_MAX), 0);
|
|
assert_eq!(ZSTD_rust_dictTooBig(ZSTD_CHUNKSIZE_MAX + 1), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn target_block_policy_requires_compression_and_nonfirst_rle() {
|
|
assert_eq!(
|
|
target_c_block_size_action(0, 0, 1, 1, 0, 128, ZSTD_FAST),
|
|
TargetCBlockAction::Rle
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(0, 1, 1, 1, 0, 128, ZSTD_FAST),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(0, 0, 0, 1, 0, 128, ZSTD_FAST),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(0, 0, 1, 0, 0, 128, ZSTD_FAST),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_targetCBlockSizeAction(0, 0, 1, 1, 0, 128, ZSTD_FAST),
|
|
TargetCBlockAction::Rle as c_int
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn target_block_policy_falls_back_for_uncompressed_and_small_results() {
|
|
assert_eq!(
|
|
target_c_block_size_action(1, 0, 0, 0, 0, 128, ZSTD_FAST),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
0,
|
|
0,
|
|
0,
|
|
ERROR(ZstdErrorCode::DstSizeTooSmall),
|
|
128,
|
|
ZSTD_FAST,
|
|
),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
0,
|
|
0,
|
|
0,
|
|
ERROR(ZstdErrorCode::Generic),
|
|
128,
|
|
ZSTD_FAST,
|
|
),
|
|
TargetCBlockAction::Error
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(ZSTD_TARGET_CBLOCK_BSS_COMPRESS, 0, 0, 0, 0, 128, ZSTD_FAST,),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn target_block_policy_uses_a_strict_three_byte_header_boundary() {
|
|
let src_size = 128;
|
|
let max_c_size = src_size - min_gain(src_size, ZSTD_FAST);
|
|
let compressed = max_c_size + ZSTD_BLOCK_HEADER_SIZE - 1;
|
|
let raw = max_c_size + ZSTD_BLOCK_HEADER_SIZE;
|
|
|
|
assert_eq!(
|
|
target_c_block_size_action(
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
0,
|
|
0,
|
|
0,
|
|
compressed,
|
|
src_size,
|
|
ZSTD_FAST,
|
|
),
|
|
TargetCBlockAction::Compressed
|
|
);
|
|
assert_eq!(
|
|
target_c_block_size_action(
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
0,
|
|
0,
|
|
0,
|
|
raw,
|
|
src_size,
|
|
ZSTD_FAST,
|
|
),
|
|
TargetCBlockAction::Raw
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_targetCBlockSizeAction(
|
|
ZSTD_TARGET_CBLOCK_BSS_COMPRESS,
|
|
0,
|
|
0,
|
|
0,
|
|
compressed,
|
|
src_size,
|
|
ZSTD_FAST,
|
|
),
|
|
TargetCBlockAction::Compressed as c_int
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progress_unknown_pledge_updates_counters() {
|
|
let mut consumed = 7;
|
|
let mut produced = 11;
|
|
let result =
|
|
unsafe { ZSTD_rust_updateFrameProgression(&mut consumed, &mut produced, 0, 5, 13, 2) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(consumed, 12);
|
|
assert_eq!(produced, 26);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progress_exact_pledge_is_accepted() {
|
|
let mut consumed = 7;
|
|
let mut produced = 11;
|
|
let result =
|
|
unsafe { ZSTD_rust_updateFrameProgression(&mut consumed, &mut produced, 13, 5, 13, 2) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(consumed, 12);
|
|
assert_eq!(produced, 26);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progress_one_byte_overrun_is_reported() {
|
|
let mut consumed = 7;
|
|
let mut produced = 11;
|
|
let result =
|
|
unsafe { ZSTD_rust_updateFrameProgression(&mut consumed, &mut produced, 13, 6, 13, 2) };
|
|
|
|
assert_eq!(result, 1);
|
|
assert_eq!(consumed, 13);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progress_counters_update_before_overrun_result() {
|
|
let mut consumed = 100;
|
|
let mut produced = 200;
|
|
let result = unsafe {
|
|
ZSTD_rust_updateFrameProgression(&mut consumed, &mut produced, 106, 7, 17, 3)
|
|
};
|
|
|
|
assert_eq!(result, 1);
|
|
assert_eq!(consumed, 107);
|
|
assert_eq!(produced, 220);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progression_ingested_handles_zero() {
|
|
assert_eq!(frame_progression_ingested(0, 0), 0);
|
|
assert_eq!(ZSTD_rust_frameProgressionIngested(0, 0), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progression_ingested_adds_consumed_and_buffered_input() {
|
|
let consumed = 11_u64;
|
|
let buffered = 37_usize;
|
|
let expected = consumed + buffered as u64;
|
|
|
|
assert_eq!(frame_progression_ingested(consumed, buffered), expected);
|
|
assert_eq!(
|
|
ZSTD_rust_frameProgressionIngested(consumed, buffered),
|
|
expected
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_progression_ingested_wraps_u64() {
|
|
let consumed = u64::MAX - 3;
|
|
let buffered = 8_usize;
|
|
let expected = consumed.wrapping_add(buffered as u64);
|
|
|
|
assert_eq!(frame_progression_ingested(consumed, buffered), expected);
|
|
assert_eq!(
|
|
ZSTD_rust_frameProgressionIngested(consumed, buffered),
|
|
expected
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn next_input_size_hint_uses_remaining_stable_block_capacity() {
|
|
let hint = ZSTD_rust_nextInputSizeHint(ZSTD_BM_STABLE, 256, 37, 99, 12);
|
|
|
|
assert_eq!(next_input_size_hint(ZSTD_BM_STABLE, 256, 37, 99, 12), 219);
|
|
assert_eq!(hint, 219);
|
|
}
|
|
|
|
#[test]
|
|
fn next_input_size_hint_replaces_empty_buffered_hint_with_block_size() {
|
|
assert_eq!(
|
|
ZSTD_rust_nextInputSizeHint(ZSTD_BM_BUFFERED, 256, 37, 128, 128),
|
|
256
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn next_input_size_hint_returns_nonzero_buffered_hint() {
|
|
assert_eq!(
|
|
ZSTD_rust_nextInputSizeHint(ZSTD_BM_BUFFERED, 256, 37, 128, 32),
|
|
96
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_next_input_size_hint_handles_empty_input_buffer() {
|
|
assert_eq!(mt_next_input_size_hint(128, 0), 128);
|
|
assert_eq!(ZSTDMT_rust_nextInputSizeHint(128, 0), 128);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_next_input_size_hint_returns_remaining_capacity() {
|
|
assert_eq!(mt_next_input_size_hint(128, 37), 91);
|
|
assert_eq!(ZSTDMT_rust_nextInputSizeHint(128, 37), 91);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_next_input_size_hint_replaces_full_buffer_with_target_size() {
|
|
assert_eq!(ZSTDMT_rust_nextInputSizeHint(128, 128), 128);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_next_input_size_hint_preserves_zero_target_behavior() {
|
|
assert_eq!(ZSTDMT_rust_nextInputSizeHint(0, 0), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_next_input_size_hint_preserves_wrapped_overfill() {
|
|
assert_eq!(ZSTDMT_rust_nextInputSizeHint(3, 4), usize::MAX);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_sizeof_cctx_handles_zero_components() {
|
|
assert_eq!(mt_sizeof_cctx(0, 0, 0, 0, 0, 0, 0, 0), 0);
|
|
assert_eq!(ZSTDMT_rust_sizeofCCtx(0, 0, 0, 0, 0, 0, 0, 0), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_sizeof_cctx_adds_all_components_in_order() {
|
|
assert_eq!(mt_sizeof_cctx(1, 2, 3, 4, 5, 6, 7, 8), 36);
|
|
assert_eq!(ZSTDMT_rust_sizeofCCtx(1, 2, 3, 4, 5, 6, 7, 8), 36);
|
|
}
|
|
|
|
#[test]
|
|
fn mt_sizeof_cctx_wraps_like_c_size_t_addition() {
|
|
assert_eq!(mt_sizeof_cctx(usize::MAX, 1, 2, 3, 4, 5, 6, 7), 27);
|
|
assert_eq!(ZSTDMT_rust_sizeofCCtx(usize::MAX, 1, 2, 3, 4, 5, 6, 7), 27);
|
|
}
|
|
|
|
#[test]
|
|
fn in_buffer_for_end_flush_returns_stable_expected_buffer() {
|
|
let expected_src = b"input".as_ptr().cast::<c_void>();
|
|
let result = ZSTD_rust_inBufferForEndFlush(ZSTD_BM_STABLE, expected_src, 37, 11);
|
|
|
|
assert_eq!(result.src, expected_src);
|
|
assert_eq!(result.size, 37);
|
|
assert_eq!(result.pos, 11);
|
|
|
|
let null_result = ZSTD_rust_inBufferForEndFlush(ZSTD_BM_STABLE, ptr::null(), 37, 11);
|
|
|
|
assert!(null_result.src.is_null());
|
|
assert_eq!(null_result.size, 37);
|
|
assert_eq!(null_result.pos, 11);
|
|
}
|
|
|
|
#[test]
|
|
fn in_buffer_for_end_flush_clears_buffered_and_other_modes() {
|
|
let expected_src = b"input".as_ptr().cast::<c_void>();
|
|
|
|
for mode in [ZSTD_BM_BUFFERED, 42] {
|
|
let result = ZSTD_rust_inBufferForEndFlush(mode, expected_src, 37, 11);
|
|
|
|
assert!(result.src.is_null());
|
|
assert_eq!(result.size, 0);
|
|
assert_eq!(result.pos, 0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn end_stream_remaining_ignores_estimate_components_after_frame_end() {
|
|
assert_eq!(end_stream_remaining(17, 1, 1), 17);
|
|
assert_eq!(ZSTD_rust_endStreamRemaining(17, 1, 1), 17);
|
|
}
|
|
|
|
#[test]
|
|
fn end_stream_remaining_adds_block_header_without_checksum() {
|
|
assert_eq!(end_stream_remaining(17, 0, 0), 20);
|
|
assert_eq!(ZSTD_rust_endStreamRemaining(17, 0, 0), 20);
|
|
}
|
|
|
|
#[test]
|
|
fn end_stream_remaining_adds_block_header_and_checksum() {
|
|
assert_eq!(end_stream_remaining(17, 0, 1), 24);
|
|
assert_eq!(ZSTD_rust_endStreamRemaining(17, 0, 1), 24);
|
|
}
|
|
|
|
#[test]
|
|
fn end_stream_remaining_wraps_size_t_additions() {
|
|
assert_eq!(end_stream_remaining(usize::MAX, 0, 1), 6);
|
|
assert_eq!(ZSTD_rust_endStreamRemaining(usize::MAX, 0, 1), 6);
|
|
}
|
|
|
|
#[test]
|
|
fn check_buffer_stability_accepts_matching_stable_input() {
|
|
let expected_src = b"input".as_ptr().cast::<c_void>();
|
|
|
|
assert_eq!(
|
|
check_buffer_stability(
|
|
ZSTD_BM_STABLE,
|
|
ZSTD_BM_BUFFERED,
|
|
expected_src,
|
|
11,
|
|
expected_src,
|
|
11,
|
|
0,
|
|
37,
|
|
5,
|
|
),
|
|
0
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_STABLE,
|
|
ZSTD_BM_BUFFERED,
|
|
expected_src,
|
|
11,
|
|
expected_src,
|
|
11,
|
|
0,
|
|
37,
|
|
5,
|
|
),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn check_buffer_stability_rejects_changed_input_pointer_or_position() {
|
|
let expected_src = b"input".as_ptr().cast::<c_void>();
|
|
let other_src = b"other".as_ptr().cast::<c_void>();
|
|
let error = ERROR(ZstdErrorCode::StabilityConditionNotRespected);
|
|
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_STABLE,
|
|
ZSTD_BM_BUFFERED,
|
|
expected_src,
|
|
11,
|
|
other_src,
|
|
11,
|
|
0,
|
|
37,
|
|
5,
|
|
),
|
|
error
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_STABLE,
|
|
ZSTD_BM_BUFFERED,
|
|
expected_src,
|
|
11,
|
|
expected_src,
|
|
12,
|
|
0,
|
|
37,
|
|
5,
|
|
),
|
|
error
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn check_buffer_stability_ignores_input_changes_in_buffered_mode() {
|
|
let expected_src = b"input".as_ptr().cast::<c_void>();
|
|
let other_src = b"other".as_ptr().cast::<c_void>();
|
|
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_BUFFERED,
|
|
ZSTD_BM_BUFFERED,
|
|
expected_src,
|
|
11,
|
|
other_src,
|
|
12,
|
|
0,
|
|
37,
|
|
5,
|
|
),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn check_buffer_stability_validates_stable_output_remainder() {
|
|
let expected_src = b"input".as_ptr().cast::<c_void>();
|
|
let error = ERROR(ZstdErrorCode::StabilityConditionNotRespected);
|
|
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_BUFFERED,
|
|
ZSTD_BM_STABLE,
|
|
expected_src,
|
|
0,
|
|
ptr::null(),
|
|
0,
|
|
32,
|
|
40,
|
|
8,
|
|
),
|
|
0
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_BUFFERED,
|
|
ZSTD_BM_STABLE,
|
|
expected_src,
|
|
0,
|
|
ptr::null(),
|
|
0,
|
|
32,
|
|
40,
|
|
7,
|
|
),
|
|
error
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_checkBufferStability(
|
|
ZSTD_BM_BUFFERED,
|
|
ZSTD_BM_STABLE,
|
|
expected_src,
|
|
0,
|
|
ptr::null(),
|
|
0,
|
|
usize::MAX,
|
|
3,
|
|
4,
|
|
),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sequence_copier_selector_returns_no_delimiters_mode() {
|
|
assert_eq!(
|
|
select_sequence_copier(ZSTD_SF_NO_BLOCK_DELIMITERS),
|
|
ZSTD_SF_NO_BLOCK_DELIMITERS
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_selectSequenceCopier(ZSTD_SF_NO_BLOCK_DELIMITERS),
|
|
ZSTD_SF_NO_BLOCK_DELIMITERS
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sequence_copier_selector_returns_explicit_delimiters_mode() {
|
|
assert_eq!(
|
|
select_sequence_copier(ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS),
|
|
ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_selectSequenceCopier(ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS),
|
|
ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS
|
|
);
|
|
}
|
|
|
|
#[cfg(debug_assertions)]
|
|
#[test]
|
|
#[should_panic]
|
|
fn sequence_copier_selector_rejects_invalid_mode_in_debug() {
|
|
let _ = select_sequence_copier(-1);
|
|
}
|
|
|
|
#[cfg(debug_assertions)]
|
|
#[test]
|
|
#[should_panic]
|
|
fn sequence_copier_selector_rejects_unsupported_mode_in_debug() {
|
|
let _ = select_sequence_copier(2);
|
|
}
|
|
|
|
#[cfg(not(debug_assertions))]
|
|
#[test]
|
|
fn sequence_copier_selector_falls_back_to_no_delimiters_in_release() {
|
|
assert_eq!(
|
|
ZSTD_rust_selectSequenceCopier(-1),
|
|
ZSTD_SF_NO_BLOCK_DELIMITERS
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_selectSequenceCopier(2),
|
|
ZSTD_SF_NO_BLOCK_DELIMITERS
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn invalidate_rep_codes_clears_all_entries() {
|
|
let mut rep = [11u32, 22, 33];
|
|
|
|
unsafe { ZSTD_rust_invalidateRepCodes(rep.as_mut_ptr()) };
|
|
|
|
assert_eq!(rep, [0; ZSTD_REP_NUM]);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_local_dict_ignores_size_without_a_buffer() {
|
|
assert_eq!(sizeof_local_dict(0, 37, 11), 11);
|
|
assert_eq!(ZSTD_rust_sizeofLocalDict(0, 37, 11), 11);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_local_dict_adds_present_buffer_and_cdict_sizes() {
|
|
assert_eq!(sizeof_local_dict(1, 37, 11), 48);
|
|
assert_eq!(sizeof_local_dict(1, 0, 11), 11);
|
|
assert_eq!(ZSTD_rust_sizeofLocalDict(1, 37, 11), 48);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_local_dict_wraps_like_c_size_t_addition() {
|
|
assert_eq!(sizeof_local_dict(1, usize::MAX, 1), 0);
|
|
assert_eq!(sizeof_local_dict(0, usize::MAX, usize::MAX), usize::MAX);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_cdict_handles_zero_components() {
|
|
assert_eq!(sizeof_cdict(0, 0), 0);
|
|
assert_eq!(ZSTD_rust_sizeofCDict(0, 0), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_cdict_adds_components_in_order() {
|
|
assert_eq!(sizeof_cdict(17, 25), 42);
|
|
assert_eq!(ZSTD_rust_sizeofCDict(17, 25), 42);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_cdict_wraps_like_c_size_t_addition() {
|
|
assert_eq!(sizeof_cdict(usize::MAX, 1), 0);
|
|
assert_eq!(ZSTD_rust_sizeofCDict(usize::MAX, 1), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_cctx_handles_zero_components() {
|
|
assert_eq!(sizeof_cctx(0, 0, 0, 0), 0);
|
|
assert_eq!(ZSTD_rust_sizeofCCtx(0, 0, 0, 0), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_cctx_adds_components_in_order() {
|
|
assert_eq!(sizeof_cctx(1, 2, 3, 4), 10);
|
|
assert_eq!(ZSTD_rust_sizeofCCtx(1, 2, 3, 4), 10);
|
|
}
|
|
|
|
#[test]
|
|
fn sizeof_cctx_wraps_like_c_size_t_addition() {
|
|
assert_eq!(sizeof_cctx(usize::MAX, 1, 2, 3), 5);
|
|
assert_eq!(ZSTD_rust_sizeofCCtx(usize::MAX, 1, 2, 3), 5);
|
|
}
|
|
|
|
#[test]
|
|
fn estimate_workspace_size_handles_zero_components() {
|
|
assert_eq!(estimate_workspace_size(0, 0, 0, 0, 0, 0, 0, 0, 0), 0);
|
|
assert_eq!(
|
|
ZSTD_rust_estimateWorkspaceSize(0, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn estimate_workspace_size_adds_components_in_order() {
|
|
assert_eq!(estimate_workspace_size(1, 2, 3, 4, 5, 6, 7, 8, 9), 45);
|
|
assert_eq!(
|
|
ZSTD_rust_estimateWorkspaceSize(1, 2, 3, 4, 5, 6, 7, 8, 9),
|
|
45
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn estimate_workspace_size_wraps_at_multiple_operand_positions() {
|
|
assert_eq!(
|
|
estimate_workspace_size(usize::MAX, 1, 0, 0, 0, 0, 0, 0, 0),
|
|
0
|
|
);
|
|
assert_eq!(
|
|
estimate_workspace_size(0, usize::MAX, 1, 0, 0, 0, 0, 0, 0),
|
|
0
|
|
);
|
|
assert_eq!(
|
|
estimate_workspace_size(0, 0, 0, 0, 0, usize::MAX, 1, 0, 0),
|
|
0
|
|
);
|
|
assert_eq!(
|
|
ZSTD_rust_estimateWorkspaceSize(0, 0, 0, 0, 0, 0, 0, usize::MAX, 1),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn public_one_shot_abi_is_c_compatible() {
|
|
let entry: unsafe extern "C" fn(*mut c_void, usize, *const c_void, usize, c_int) -> usize =
|
|
ZSTD_compress;
|
|
let context_entry: unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*mut c_void,
|
|
usize,
|
|
*const c_void,
|
|
usize,
|
|
c_int,
|
|
) -> usize = ZSTD_compressCCtx;
|
|
assert_eq!(size_of::<usize>(), size_of::<*const c_void>());
|
|
let _ = entry;
|
|
let _ = context_entry;
|
|
}
|
|
|
|
#[test]
|
|
fn explicit_context_simple_api_matches_one_shot_path() {
|
|
let input = b"explicit context compression remains a simple API";
|
|
let capacity = crate::zstd_compress_api::ZSTD_compressBound(input.len());
|
|
let mut output = vec![0u8; capacity];
|
|
let written = unsafe {
|
|
ZSTD_compressCCtx(
|
|
std::ptr::dangling_mut::<c_void>(),
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
input.as_ptr().cast(),
|
|
input.len(),
|
|
3,
|
|
)
|
|
};
|
|
assert!(!ERR_isError(written));
|
|
output.truncate(written);
|
|
let one_shot = compress_input(input, 3);
|
|
assert_eq!(output, one_shot);
|
|
if let Some(restored) = system_round_trip(&output) {
|
|
assert_eq!(restored, input);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn one_shot_round_trip_across_block_boundaries() {
|
|
let mut input = Vec::with_capacity(128 * 1024 + 37);
|
|
for index in 0..(128 * 1024 + 37) {
|
|
input.push(((index * 17) ^ (index / 31)) as u8);
|
|
}
|
|
|
|
let compressed = compress_input(&input, 3);
|
|
if let Some(restored) = system_round_trip(&compressed) {
|
|
assert_eq!(restored, input);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn empty_and_short_inputs_have_valid_frames() {
|
|
for input in [b"".as_slice(), b"a", b"abcdefg", b"abcdefgh"] {
|
|
let compressed = compress_input(input, 1);
|
|
if let Some(restored) = system_round_trip(&compressed) {
|
|
assert_eq!(restored, input);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn public_error_paths_match_size_t_error_contract() {
|
|
let mut output = [0u8; 64];
|
|
let source = [1u8; 8];
|
|
assert_eq!(
|
|
unsafe {
|
|
ZSTD_compress(
|
|
output.as_mut_ptr().cast(),
|
|
0,
|
|
source.as_ptr().cast(),
|
|
source.len(),
|
|
3,
|
|
)
|
|
},
|
|
ERROR(ZstdErrorCode::DstSizeTooSmall)
|
|
);
|
|
assert_eq!(
|
|
unsafe { ZSTD_compress(ptr::null_mut(), 1, source.as_ptr().cast(), source.len(), 3) },
|
|
ERROR(ZstdErrorCode::DstBufferNull)
|
|
);
|
|
assert_eq!(
|
|
unsafe { ZSTD_compress(output.as_mut_ptr().cast(), output.len(), ptr::null(), 1, 3) },
|
|
ERROR(ZstdErrorCode::SrcSizeWrong)
|
|
);
|
|
}
|
|
}
|