Move regular and by-reference CDict parameter selection and default-level normalization behind a Rust-owned boundary. Keep advanced allocation, private workspace setup, and CDict scalar publication in C callbacks. Test Plan: - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml --release create_cdict -- --nocapture - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml --release - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --release --all-targets -- -D warnings - ulimit -v 41943040; make -B -C programs -j1 zstd - ulimit -v 41943040; make -C tests -j1 test-zstream ZSTREAM_TESTTIME=-T1s
3284 lines
111 KiB
Rust
3284 lines
111 KiB
Rust
#![allow(non_camel_case_types)]
|
|
#![allow(non_snake_case)]
|
|
#![allow(clippy::too_many_arguments)]
|
|
|
|
//! Dictionary entropy-header loading.
|
|
//!
|
|
//! This is the Rust leaf for `ZSTD_dictNCountRepeat()` and
|
|
//! `ZSTD_loadCEntropy()` in `lib/compress/zstd_compress.c`. C retains the
|
|
//! original symbol through a thin compatibility shim and keeps dictionary
|
|
//! content loading and compression-context ownership on its side.
|
|
|
|
use crate::bits::ZSTD_highbit32;
|
|
use crate::common::{LL_FSE_LOG, MAX_LL, MAX_ML, MAX_OFF, ML_FSE_LOG, OFF_FSE_LOG};
|
|
use crate::entropy_common::FSE_readNCount;
|
|
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
|
|
use crate::fse_compress::FSE_buildCTable_wksp;
|
|
use crate::huf_compress::HUF_readCTable;
|
|
use crate::zstd_compress_params::{
|
|
ZSTD_compressionParameters, ZSTD_frameParameters, ZSTD_parameters,
|
|
ZSTD_rust_params_defaultCLevel, ZSTD_rust_params_getCParams,
|
|
ZSTD_rust_params_getParamsInternal, ZSTD_rust_params_shouldAttachDict,
|
|
ZSTD_CONTENTSIZE_UNKNOWN, ZSTD_RUST_CPM_CREATE_CDICT, ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
|
};
|
|
use crate::zstd_compress_params_api::ZSTD_CCtx_params;
|
|
use crate::zstd_compress_stats::{
|
|
ZSTD_compressedBlockState_t, ZSTD_rust_resetCompressedBlockState,
|
|
};
|
|
use std::ffi::c_void;
|
|
use std::mem::{offset_of, size_of};
|
|
use std::os::raw::{c_int, c_short, c_uint};
|
|
use std::ptr;
|
|
use std::slice;
|
|
|
|
const HUF_REPEAT_CHECK: c_int = 1;
|
|
const HUF_REPEAT_VALID: c_int = 2;
|
|
const FSE_REPEAT_CHECK: c_int = 1;
|
|
const FSE_REPEAT_VALID: c_int = 2;
|
|
const HUF_WORKSPACE_SIZE: usize = (8 << 10) + 512;
|
|
const DICTIONARY_ID_AND_MAGIC_SIZE: usize = 8;
|
|
const REPCODE_SECTION_SIZE: usize = 12;
|
|
const ZSTD_MAGIC_DICTIONARY: u32 = 0xEC30_A437;
|
|
const ZSTD_DCT_AUTO: c_int = 0;
|
|
const ZSTD_DCT_RAW_CONTENT: c_int = 1;
|
|
const ZSTD_DCT_FULL_DICT: c_int = 2;
|
|
|
|
/// C keeps match-state/content insertion private because it depends on the
|
|
/// configuration-sensitive `ZSTD_MatchState_t`, `ldmState_t`, workspace, and
|
|
/// parameter layouts. Rust owns the dictionary dispatch and calls this narrow
|
|
/// operation only after selecting the raw or full-dictionary path.
|
|
pub type LoadDictionaryContentFn = unsafe extern "C" fn(
|
|
match_state: *mut c_void,
|
|
ldm_state: *mut c_void,
|
|
workspace_state: *mut c_void,
|
|
params: *const c_void,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
dtlm: c_int,
|
|
tfp: c_int,
|
|
) -> usize;
|
|
|
|
/// These callbacks are deliberately opaque: C retains the private CCtx,
|
|
/// local/prefix dictionary layouts, allocation, and CDict lifetime rules.
|
|
/// Rust owns the stage checks and the order in which clear/assign operations
|
|
/// are selected.
|
|
pub type CctxDictionaryClearFn = unsafe extern "C" fn(context: *mut c_void);
|
|
pub type CctxAssignLocalDictFn = unsafe extern "C" fn(
|
|
context: *mut c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_load_method: c_int,
|
|
dict_content_type: c_int,
|
|
) -> usize;
|
|
pub type CctxAssignCDictFn = unsafe extern "C" fn(context: *mut c_void, cdict: *const c_void);
|
|
pub type CctxAssignPrefixDictFn = unsafe extern "C" fn(
|
|
context: *mut c_void,
|
|
prefix: *const c_void,
|
|
prefix_size: usize,
|
|
dict_content_type: c_int,
|
|
);
|
|
|
|
#[inline]
|
|
fn dictionary_corrupted() -> usize {
|
|
ERROR(ZstdErrorCode::DictionaryCorrupted)
|
|
}
|
|
|
|
const ZSTD_CCTX_INIT_STAGE: c_int = 0;
|
|
#[cfg(test)]
|
|
const ZSTD_DLM_BY_REF: c_int = 1;
|
|
|
|
#[inline]
|
|
fn stage_wrong() -> usize {
|
|
ERROR(ZstdErrorCode::StageWrong)
|
|
}
|
|
|
|
type CompressBeginUsingCDictInitParamsFn =
|
|
unsafe extern "C" fn(*mut c_void, *const ZSTD_parameters, c_int);
|
|
type CompressBeginUsingCDictAdjustWindowFn = unsafe extern "C" fn(*mut c_void, c_uint);
|
|
type CompressBeginUsingCDictBeginFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, *const c_void, u64) -> usize;
|
|
|
|
/// Explicit projection for `ZSTD_compressBegin_usingCDict_internal`.
|
|
///
|
|
/// Rust owns CDict/source-size parameter selection, initialization ordering,
|
|
/// and the source-window floor. C retains the private parameter/context
|
|
/// layouts and the final begin operation behind callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressBeginUsingCDictState {
|
|
cctx: *mut c_void,
|
|
cdict: *const c_void,
|
|
cctx_params: *mut ZSTD_CCtx_params,
|
|
cdict_cparams: *const ZSTD_compressionParameters,
|
|
cdict_content_size: *const usize,
|
|
cdict_compression_level: *const c_int,
|
|
f_params: *const ZSTD_frameParameters,
|
|
pledged_src_size: *const u64,
|
|
exclusion_mask: *const c_uint,
|
|
init_params: CompressBeginUsingCDictInitParamsFn,
|
|
adjust_window: CompressBeginUsingCDictAdjustWindowFn,
|
|
begin: CompressBeginUsingCDictBeginFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressBeginUsingCDictState, cctx) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressBeginUsingCDictState, cdict) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginUsingCDictState, cctx_params) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginUsingCDictState, cdict_cparams) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginUsingCDictState, cdict_content_size)
|
|
== 4 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_compressBeginUsingCDictState,
|
|
cdict_compression_level
|
|
) == 5 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_compressBeginUsingCDictState, f_params) == 6 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginUsingCDictState, pledged_src_size)
|
|
== 7 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginUsingCDictState, exclusion_mask)
|
|
== size_of::<[usize; 8]>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginUsingCDictState, init_params) == 9 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginUsingCDictState, adjust_window)
|
|
== 10 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_compressBeginUsingCDictState, begin) == 11 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_compressBeginUsingCDictState>() == size_of::<[usize; 12]>());
|
|
};
|
|
|
|
const CDICT_PARAMS_SRC_SIZE_CUTOFF: u64 = 128 * 1024;
|
|
const CDICT_PARAMS_DICT_SIZE_MULTIPLIER: u64 = 6;
|
|
const CDICT_WINDOW_SOURCE_SIZE_CAP: u64 = 1 << 19;
|
|
|
|
/// Apply the CDict begin policy while keeping private C state behind callbacks.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressBeginUsingCDict(
|
|
state: *const ZSTD_rust_compressBeginUsingCDictState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.cdict.is_null() {
|
|
return ERROR(ZstdErrorCode::DictionaryWrong);
|
|
}
|
|
if state.cctx_params.is_null()
|
|
|| state.cdict_cparams.is_null()
|
|
|| state.cdict_content_size.is_null()
|
|
|| state.cdict_compression_level.is_null()
|
|
|| state.f_params.is_null()
|
|
|| state.pledged_src_size.is_null()
|
|
|| state.exclusion_mask.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let cdict_cparams = unsafe { *state.cdict_cparams };
|
|
let cdict_content_size = unsafe { *state.cdict_content_size };
|
|
let cdict_compression_level = unsafe { *state.cdict_compression_level };
|
|
let pledged_src_size = unsafe { *state.pledged_src_size };
|
|
let cdict_size_threshold =
|
|
(cdict_content_size as u64).wrapping_mul(CDICT_PARAMS_DICT_SIZE_MULTIPLIER);
|
|
let cparams = if pledged_src_size < CDICT_PARAMS_SRC_SIZE_CUTOFF
|
|
|| pledged_src_size < cdict_size_threshold
|
|
|| pledged_src_size == ZSTD_CONTENTSIZE_UNKNOWN
|
|
|| cdict_compression_level == 0
|
|
{
|
|
cdict_cparams
|
|
} else {
|
|
ZSTD_rust_params_getCParams(
|
|
cdict_compression_level,
|
|
pledged_src_size,
|
|
cdict_content_size,
|
|
unsafe { *state.exclusion_mask },
|
|
)
|
|
};
|
|
let params = ZSTD_parameters {
|
|
cParams: cparams,
|
|
fParams: unsafe { *state.f_params },
|
|
};
|
|
|
|
unsafe {
|
|
(state.init_params)(state.cctx_params.cast(), ¶ms, cdict_compression_level);
|
|
}
|
|
if pledged_src_size != ZSTD_CONTENTSIZE_UNKNOWN {
|
|
let limited_src_size = pledged_src_size.min(CDICT_WINDOW_SOURCE_SIZE_CAP) as u32;
|
|
let limited_src_log = if limited_src_size > 1 {
|
|
ZSTD_highbit32(limited_src_size - 1) + 1
|
|
} else {
|
|
1
|
|
};
|
|
unsafe {
|
|
(state.adjust_window)(state.cctx_params.cast(), limited_src_log);
|
|
}
|
|
}
|
|
unsafe {
|
|
(state.begin)(
|
|
state.cctx,
|
|
state.cdict,
|
|
state.cctx_params.cast_const().cast(),
|
|
pledged_src_size,
|
|
)
|
|
}
|
|
}
|
|
|
|
type CompressUsingCDictBeginFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, *const ZSTD_frameParameters, u64) -> usize;
|
|
type CompressUsingCDictEndFn =
|
|
unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize) -> usize;
|
|
|
|
/// Explicit projection for the public `ZSTD_compress_usingCDict` wrapper.
|
|
///
|
|
/// Rust owns the hardcoded frame policy and begin-then-end ordering. C
|
|
/// retains the private CDict begin path and end-of-frame implementation behind
|
|
/// callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressUsingCDictState {
|
|
callback_context: *mut c_void,
|
|
cdict: *const c_void,
|
|
begin: CompressUsingCDictBeginFn,
|
|
end: CompressUsingCDictEndFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressUsingCDictState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressUsingCDictState, cdict) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressUsingCDictState, begin) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressUsingCDictState, end) == 3 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_compressUsingCDictState>() == size_of::<[usize; 4]>());
|
|
};
|
|
|
|
/// Start a CDict frame and finish it through the existing C-owned leaves.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressUsingCDict(
|
|
state: *const ZSTD_rust_compressUsingCDictState,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.callback_context.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
if state.cdict.is_null() {
|
|
return ERROR(ZstdErrorCode::DictionaryWrong);
|
|
}
|
|
|
|
let f_params = ZSTD_frameParameters {
|
|
contentSizeFlag: 1,
|
|
checksumFlag: 0,
|
|
noDictIDFlag: 0,
|
|
};
|
|
let begin_result = unsafe {
|
|
(state.begin)(
|
|
state.callback_context,
|
|
state.cdict,
|
|
&f_params,
|
|
src_size as u64,
|
|
)
|
|
};
|
|
if ERR_isError(begin_result) {
|
|
return begin_result;
|
|
}
|
|
unsafe { (state.end)(state.callback_context, dst, dst_capacity, src, src_size) }
|
|
}
|
|
|
|
type CompressBeginUsingCDictPublicBeginFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, *const ZSTD_frameParameters, u64) -> usize;
|
|
|
|
/// Explicit projection for the public `ZSTD_compressBegin_usingCDict` wrapper.
|
|
///
|
|
/// Rust owns the fixed frame policy and unknown-source pledge. C retains the
|
|
/// private CDict begin path behind a callback.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressBeginUsingCDictPublicState {
|
|
callback_context: *mut c_void,
|
|
cdict: *const c_void,
|
|
begin: CompressBeginUsingCDictPublicBeginFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(
|
|
offset_of!(
|
|
ZSTD_rust_compressBeginUsingCDictPublicState,
|
|
callback_context
|
|
) == 0
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_compressBeginUsingCDictPublicState, cdict) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginUsingCDictPublicState, begin) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_compressBeginUsingCDictPublicState>() == size_of::<[usize; 3]>());
|
|
};
|
|
|
|
/// Start a CDict frame with the legacy fixed frame policy.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressBeginUsingCDictPublic(
|
|
state: *const ZSTD_rust_compressBeginUsingCDictPublicState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.cdict.is_null() {
|
|
return ERROR(ZstdErrorCode::DictionaryWrong);
|
|
}
|
|
if state.callback_context.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let f_params = ZSTD_frameParameters {
|
|
contentSizeFlag: 0,
|
|
checksumFlag: 0,
|
|
noDictIDFlag: 0,
|
|
};
|
|
unsafe {
|
|
(state.begin)(
|
|
state.callback_context,
|
|
state.cdict,
|
|
&f_params,
|
|
ZSTD_CONTENTSIZE_UNKNOWN,
|
|
)
|
|
}
|
|
}
|
|
|
|
type CreateCDictFn = unsafe extern "C" fn(
|
|
*mut c_void,
|
|
*const c_void,
|
|
usize,
|
|
c_int,
|
|
c_int,
|
|
*const ZSTD_compressionParameters,
|
|
) -> *mut c_void;
|
|
type CreateCDictSetCompressionLevelFn = unsafe extern "C" fn(*mut c_void, *mut c_void, c_int);
|
|
|
|
/// Explicit projection for the public `ZSTD_createCDict` constructors.
|
|
///
|
|
/// Rust owns create-CDict parameter selection and default-level normalization.
|
|
/// C retains advanced allocation, private workspace setup, and CDict scalar
|
|
/// publication behind callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_createCDictState {
|
|
callback_context: *mut c_void,
|
|
exclusion_mask: *const c_uint,
|
|
create: CreateCDictFn,
|
|
set_compression_level: CreateCDictSetCompressionLevelFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_createCDictState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_createCDictState, exclusion_mask) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_createCDictState, create) == 2 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_createCDictState, set_compression_level) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(size_of::<ZSTD_rust_createCDictState>() == size_of::<[usize; 4]>());
|
|
};
|
|
|
|
/// Select parameters and construct a regular or by-reference CDict.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_createCDict(
|
|
state: *const ZSTD_rust_createCDictState,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
compression_level: c_int,
|
|
dict_load_method: c_int,
|
|
) -> *mut c_void {
|
|
if state.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.exclusion_mask.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let cparams = ZSTD_rust_params_getParamsInternal(
|
|
compression_level,
|
|
ZSTD_CONTENTSIZE_UNKNOWN,
|
|
dict_size,
|
|
ZSTD_RUST_CPM_CREATE_CDICT,
|
|
unsafe { *state.exclusion_mask },
|
|
)
|
|
.cParams;
|
|
let cdict = unsafe {
|
|
(state.create)(
|
|
state.callback_context,
|
|
dict,
|
|
dict_size,
|
|
dict_load_method,
|
|
ZSTD_DCT_AUTO,
|
|
&cparams,
|
|
)
|
|
};
|
|
if cdict.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let normalized_level = if compression_level == 0 {
|
|
ZSTD_rust_params_defaultCLevel()
|
|
} else {
|
|
compression_level
|
|
};
|
|
unsafe { (state.set_compression_level)(state.callback_context, cdict, normalized_level) };
|
|
cdict
|
|
}
|
|
|
|
type CompressBeginUsingDictBeginFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, usize, *const c_void, u64) -> usize;
|
|
|
|
/// Explicit projection for the public `ZSTD_compressBegin_usingDict` family.
|
|
///
|
|
/// Rust owns unknown-source parameter selection and default-level
|
|
/// normalization. C retains private `ZSTD_CCtx_params` initialization and the
|
|
/// final `ZSTD_compressBegin_internal` call behind callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressBeginUsingDictState {
|
|
cctx: *mut c_void,
|
|
cctx_params: *mut ZSTD_CCtx_params,
|
|
exclusion_mask: *const c_uint,
|
|
init_params: CompressBeginUsingCDictInitParamsFn,
|
|
begin: CompressBeginUsingDictBeginFn,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_compressBeginUsingDictState, cctx) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressBeginUsingDictState, cctx_params) == size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginUsingDictState, exclusion_mask) == 2 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginUsingDictState, init_params) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_compressBeginUsingDictState, begin) == 4 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_compressBeginUsingDictState>() == size_of::<[usize; 5]>());
|
|
};
|
|
|
|
/// Select parameters and start a dictionary-backed frame.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressBeginUsingDict(
|
|
state: *const ZSTD_rust_compressBeginUsingDictState,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
compression_level: c_int,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.cctx.is_null() || state.cctx_params.is_null() || state.exclusion_mask.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let params = ZSTD_rust_params_getParamsInternal(
|
|
compression_level,
|
|
ZSTD_CONTENTSIZE_UNKNOWN,
|
|
dict_size,
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
|
unsafe { *state.exclusion_mask },
|
|
);
|
|
let init_level = if compression_level == 0 {
|
|
ZSTD_rust_params_defaultCLevel()
|
|
} else {
|
|
compression_level
|
|
};
|
|
unsafe {
|
|
(state.init_params)(state.cctx_params.cast(), ¶ms, init_level);
|
|
(state.begin)(
|
|
state.cctx,
|
|
dict,
|
|
dict_size,
|
|
state.cctx_params.cast_const().cast(),
|
|
ZSTD_CONTENTSIZE_UNKNOWN,
|
|
)
|
|
}
|
|
}
|
|
|
|
type InitCDictAssignContentFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, usize, c_int) -> usize;
|
|
type InitCDictReserveEntropyFn = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
|
|
type InitCDictResetBlockStateFn = unsafe extern "C" fn(*mut c_void);
|
|
type InitCDictResetMatchStateFn = unsafe extern "C" fn(*mut c_void, *const c_void, c_int) -> usize;
|
|
type InitCDictInsertDictionaryFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, *const c_void, usize, c_int) -> usize;
|
|
|
|
/// Projection for CDict content/state initialization.
|
|
///
|
|
/// Rust owns the initialization order and scalar field policy. C callbacks
|
|
/// retain workspace allocation, match-state reset, and dictionary insertion
|
|
/// because those operations use private CDict layouts.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_initCDictState {
|
|
callback_context: *mut c_void,
|
|
params: *const c_void,
|
|
c_params: *const ZSTD_compressionParameters,
|
|
match_state_c_params: *mut ZSTD_compressionParameters,
|
|
dedicated_dict_search: *mut c_int,
|
|
enable_dedicated_dict_search: *const c_int,
|
|
use_row_match_finder: *const c_int,
|
|
dict_content: *mut *const c_void,
|
|
dict_content_size: *mut usize,
|
|
dict_content_type: *mut c_int,
|
|
entropy_workspace: *mut *mut c_void,
|
|
dict_id: *mut c_uint,
|
|
compression_level: *mut c_int,
|
|
content_size_flag: *mut c_int,
|
|
assign_content: Option<InitCDictAssignContentFn>,
|
|
reserve_entropy: Option<InitCDictReserveEntropyFn>,
|
|
reset_block_state: Option<InitCDictResetBlockStateFn>,
|
|
reset_match_state: Option<InitCDictResetMatchStateFn>,
|
|
insert_dictionary: Option<InitCDictInsertDictionaryFn>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<InitCDictAssignContentFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitCDictReserveEntropyFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitCDictResetBlockStateFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitCDictResetMatchStateFn>() == size_of::<usize>());
|
|
assert!(size_of::<InitCDictInsertDictionaryFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, params) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, c_params) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, match_state_c_params) == 3 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, dedicated_dict_search) == 4 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_initCDictState, enable_dedicated_dict_search)
|
|
== 5 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, use_row_match_finder) == 6 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, dict_content) == 7 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, dict_content_size) == size_of::<[usize; 8]>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, dict_content_type) == 9 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, entropy_workspace) == 10 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, dict_id) == 11 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, compression_level) == 12 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, content_size_flag) == 13 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, assign_content) == 14 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, reserve_entropy) == 15 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, reset_block_state) == 16 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, reset_match_state) == 17 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_initCDictState, insert_dictionary) == 18 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_initCDictState>() == size_of::<[usize; 19]>());
|
|
};
|
|
|
|
const CDICT_DEFAULT_CLEVEL: c_int = 3;
|
|
|
|
/// Initialize a private CDict through narrow C callbacks.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_initCDict(
|
|
state: *const ZSTD_rust_initCDictState,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_load_method: c_int,
|
|
dict_content_type: c_int,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let Some(assign_content) = state.assign_content else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(reserve_entropy) = state.reserve_entropy else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(reset_block_state) = state.reset_block_state else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(reset_match_state) = state.reset_match_state else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(insert_dictionary) = state.insert_dictionary else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
if state.callback_context.is_null()
|
|
|| state.params.is_null()
|
|
|| state.c_params.is_null()
|
|
|| state.match_state_c_params.is_null()
|
|
|| state.dedicated_dict_search.is_null()
|
|
|| state.enable_dedicated_dict_search.is_null()
|
|
|| state.use_row_match_finder.is_null()
|
|
|| state.dict_content.is_null()
|
|
|| state.dict_content_size.is_null()
|
|
|| state.dict_content_type.is_null()
|
|
|| state.entropy_workspace.is_null()
|
|
|| state.dict_id.is_null()
|
|
|| state.compression_level.is_null()
|
|
|| state.content_size_flag.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let assign_result =
|
|
unsafe { assign_content(state.callback_context, dict, dict_size, dict_load_method) };
|
|
if ERR_isError(assign_result) {
|
|
return assign_result;
|
|
}
|
|
|
|
unsafe {
|
|
*state.match_state_c_params = *state.c_params;
|
|
*state.dedicated_dict_search = *state.enable_dedicated_dict_search;
|
|
*state.dict_content_size = dict_size;
|
|
*state.dict_content_type = dict_content_type;
|
|
}
|
|
|
|
let entropy_workspace = unsafe { reserve_entropy(state.callback_context) };
|
|
if entropy_workspace.is_null() {
|
|
return ERROR(ZstdErrorCode::MemoryAllocation);
|
|
}
|
|
unsafe {
|
|
*state.entropy_workspace = entropy_workspace;
|
|
}
|
|
|
|
unsafe { reset_block_state(state.callback_context) };
|
|
let reset_match_result = unsafe {
|
|
reset_match_state(
|
|
state.callback_context,
|
|
state.c_params.cast(),
|
|
*state.use_row_match_finder,
|
|
)
|
|
};
|
|
if ERR_isError(reset_match_result) {
|
|
return reset_match_result;
|
|
}
|
|
|
|
unsafe {
|
|
*state.compression_level = CDICT_DEFAULT_CLEVEL;
|
|
*state.content_size_flag = 1;
|
|
}
|
|
let dict_content = unsafe { *state.dict_content };
|
|
let dict_id = unsafe {
|
|
insert_dictionary(
|
|
state.callback_context,
|
|
state.params,
|
|
dict_content,
|
|
dict_size,
|
|
dict_content_type,
|
|
)
|
|
};
|
|
if ERR_isError(dict_id) {
|
|
return dict_id;
|
|
}
|
|
if dict_id > c_uint::MAX as usize {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe { *state.dict_id = dict_id as c_uint };
|
|
0
|
|
}
|
|
|
|
type CompressBeginResetInternalFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, u64, usize, c_int) -> usize;
|
|
type CompressBeginResetUsingCDictFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, *const c_void, u64, c_int) -> usize;
|
|
type CompressBeginInsertDictionaryFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, *const c_void, usize, c_int, c_int) -> usize;
|
|
|
|
/// Projection for the dictionary-selection portion of
|
|
/// `ZSTD_compressBegin_internal`.
|
|
///
|
|
/// Rust owns the CDict attach decision and dictionary-result publication. C
|
|
/// retains the context reset, CDict attach, and private insertion callbacks.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_compressBeginState {
|
|
callback_context: *mut c_void,
|
|
params: *const c_void,
|
|
cdict: *const c_void,
|
|
cdict_content_size: *const usize,
|
|
cdict_compression_level: *const c_int,
|
|
attach_dict_pref: *const c_int,
|
|
dict: *const c_void,
|
|
dict_size: *const usize,
|
|
dict_content_type: *const c_int,
|
|
dtlm: *const c_int,
|
|
pledged_src_size: *const u64,
|
|
zbuff: *const c_int,
|
|
force_load: *const c_int,
|
|
dict_id: *mut c_uint,
|
|
dict_content_size: *mut usize,
|
|
reset_internal: Option<CompressBeginResetInternalFn>,
|
|
reset_using_cdict: Option<CompressBeginResetUsingCDictFn>,
|
|
insert_dictionary: Option<CompressBeginInsertDictionaryFn>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<CompressBeginResetInternalFn>() == size_of::<usize>());
|
|
assert!(size_of::<CompressBeginResetUsingCDictFn>() == size_of::<usize>());
|
|
assert!(size_of::<CompressBeginInsertDictionaryFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, params) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, cdict) == 2 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, cdict_content_size) == 3 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginState, cdict_compression_level) == 4 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, attach_dict_pref) == 5 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, dict) == 6 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, dict_size) == 7 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, dict_content_type) == size_of::<[usize; 8]>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, dtlm) == 9 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, pledged_src_size) == 10 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, zbuff) == 11 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, force_load) == size_of::<[usize; 12]>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, dict_id) == 13 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_compressBeginState, dict_content_size) == size_of::<[usize; 14]>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, reset_internal) == 15 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, reset_using_cdict) == 16 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_compressBeginState, insert_dictionary) == 17 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_compressBeginState>() == size_of::<[usize; 18]>());
|
|
};
|
|
|
|
/// Select and begin a dictionary-backed compression context.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressBegin(
|
|
state: *const ZSTD_rust_compressBeginState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let Some(reset_internal) = state.reset_internal else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(reset_using_cdict) = state.reset_using_cdict else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(insert_dictionary) = state.insert_dictionary else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
if state.callback_context.is_null()
|
|
|| state.params.is_null()
|
|
|| state.cdict_content_size.is_null() && !state.cdict.is_null()
|
|
|| state.cdict_compression_level.is_null() && !state.cdict.is_null()
|
|
|| state.attach_dict_pref.is_null()
|
|
|| state.dict_size.is_null()
|
|
|| state.dict_content_type.is_null()
|
|
|| state.dtlm.is_null()
|
|
|| state.pledged_src_size.is_null()
|
|
|| state.zbuff.is_null()
|
|
|| state.force_load.is_null()
|
|
|| state.dict_id.is_null()
|
|
|| state.dict_content_size.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let cdict_present = !state.cdict.is_null();
|
|
let pledged_src_size = unsafe { *state.pledged_src_size };
|
|
let dict_content_size = if cdict_present {
|
|
unsafe { *state.cdict_content_size }
|
|
} else {
|
|
unsafe { *state.dict_size }
|
|
};
|
|
let cdict_compression_level = if cdict_present {
|
|
unsafe { *state.cdict_compression_level }
|
|
} else {
|
|
0
|
|
};
|
|
let should_attach = cdict_present
|
|
&& dict_content_size > 0
|
|
&& (pledged_src_size < CDICT_PARAMS_SRC_SIZE_CUTOFF
|
|
|| pledged_src_size
|
|
< (dict_content_size as u64).wrapping_mul(CDICT_PARAMS_DICT_SIZE_MULTIPLIER)
|
|
|| pledged_src_size == ZSTD_CONTENTSIZE_UNKNOWN
|
|
|| cdict_compression_level == 0)
|
|
&& unsafe { *state.attach_dict_pref } != unsafe { *state.force_load };
|
|
if should_attach {
|
|
return unsafe {
|
|
reset_using_cdict(
|
|
state.callback_context,
|
|
state.cdict,
|
|
state.params,
|
|
pledged_src_size,
|
|
*state.zbuff,
|
|
)
|
|
};
|
|
}
|
|
|
|
let reset_result = unsafe {
|
|
reset_internal(
|
|
state.callback_context,
|
|
state.params,
|
|
pledged_src_size,
|
|
dict_content_size,
|
|
*state.zbuff,
|
|
)
|
|
};
|
|
if ERR_isError(reset_result) {
|
|
return reset_result;
|
|
}
|
|
|
|
let dict_id = unsafe {
|
|
insert_dictionary(
|
|
state.callback_context,
|
|
state.cdict,
|
|
state.dict,
|
|
*state.dict_size,
|
|
*state.dict_content_type,
|
|
*state.dtlm,
|
|
)
|
|
};
|
|
if ERR_isError(dict_id) {
|
|
return dict_id;
|
|
}
|
|
if dict_id > c_uint::MAX as usize {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
unsafe {
|
|
*state.dict_id = dict_id as c_uint;
|
|
*state.dict_content_size = dict_content_size;
|
|
}
|
|
0
|
|
}
|
|
|
|
type ResetCCtxUsingCDictAttachFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, *const c_void, u64, c_int) -> usize;
|
|
type ResetCCtxUsingCDictCopyFn =
|
|
unsafe extern "C" fn(*mut c_void, *const c_void, *const c_void, u64, c_int) -> usize;
|
|
|
|
/// Projection for the attach-versus-copy decision in
|
|
/// `ZSTD_resetCCtx_usingCDict`.
|
|
///
|
|
/// Rust owns the dictionary attachment policy. C retains both reset
|
|
/// implementations because they operate on private CCtx, CDict, workspace,
|
|
/// and match-state layouts.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_resetCCtxUsingCDictState {
|
|
callback_context: *mut c_void,
|
|
cdict: *const c_void,
|
|
params: *const c_void,
|
|
cdict_strategy: *const c_int,
|
|
dedicated_dict_search: *const c_int,
|
|
attach_dict_pref: *const c_int,
|
|
force_window: *const c_int,
|
|
pledged_src_size: *const u64,
|
|
zbuff: *const c_int,
|
|
attach: Option<ResetCCtxUsingCDictAttachFn>,
|
|
copy: Option<ResetCCtxUsingCDictCopyFn>,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(size_of::<ResetCCtxUsingCDictAttachFn>() == size_of::<usize>());
|
|
assert!(size_of::<ResetCCtxUsingCDictCopyFn>() == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxUsingCDictState, callback_context) == 0);
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxUsingCDictState, cdict) == size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxUsingCDictState, params) == 2 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxUsingCDictState, cdict_strategy) == 3 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxUsingCDictState, dedicated_dict_search)
|
|
== 4 * size_of::<usize>()
|
|
);
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxUsingCDictState, attach_dict_pref) == 5 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxUsingCDictState, force_window) == 6 * size_of::<usize>());
|
|
assert!(
|
|
offset_of!(ZSTD_rust_resetCCtxUsingCDictState, pledged_src_size) == 7 * size_of::<usize>()
|
|
);
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxUsingCDictState, zbuff) == size_of::<[usize; 8]>());
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxUsingCDictState, attach) == 9 * size_of::<usize>());
|
|
assert!(offset_of!(ZSTD_rust_resetCCtxUsingCDictState, copy) == 10 * size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_resetCCtxUsingCDictState>() == size_of::<[usize; 11]>());
|
|
};
|
|
|
|
/// Select the private C reset implementation for an already-built CDict.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_resetCCtxUsingCDict(
|
|
state: *const ZSTD_rust_resetCCtxUsingCDictState,
|
|
) -> usize {
|
|
if state.is_null() {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
let state = unsafe { &*state };
|
|
let Some(attach) = state.attach else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
let Some(copy) = state.copy else {
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
};
|
|
if state.callback_context.is_null()
|
|
|| state.cdict.is_null()
|
|
|| state.params.is_null()
|
|
|| state.cdict_strategy.is_null()
|
|
|| state.dedicated_dict_search.is_null()
|
|
|| state.attach_dict_pref.is_null()
|
|
|| state.force_window.is_null()
|
|
|| state.pledged_src_size.is_null()
|
|
|| state.zbuff.is_null()
|
|
{
|
|
return ERROR(ZstdErrorCode::Generic);
|
|
}
|
|
|
|
let should_attach = ZSTD_rust_params_shouldAttachDict(
|
|
unsafe { *state.cdict_strategy },
|
|
unsafe { *state.dedicated_dict_search },
|
|
unsafe { *state.pledged_src_size },
|
|
unsafe { *state.attach_dict_pref },
|
|
unsafe { *state.force_window },
|
|
) != 0;
|
|
let callback = if should_attach { attach } else { copy };
|
|
unsafe {
|
|
callback(
|
|
state.callback_context,
|
|
state.cdict,
|
|
state.params,
|
|
*state.pledged_src_size,
|
|
*state.zbuff,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Scalar projections for the public CDict query helpers.
|
|
#[repr(C)]
|
|
pub struct ZSTD_rust_cdictQueryState {
|
|
c_params: *const ZSTD_compressionParameters,
|
|
dict_id: *const c_uint,
|
|
}
|
|
|
|
const _: () = {
|
|
assert!(offset_of!(ZSTD_rust_cdictQueryState, c_params) == 0);
|
|
assert!(offset_of!(ZSTD_rust_cdictQueryState, dict_id) == size_of::<usize>());
|
|
assert!(size_of::<ZSTD_rust_cdictQueryState>() == 2 * size_of::<usize>());
|
|
};
|
|
|
|
/// Return a copied compression-parameter snapshot from a CDict projection.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_getCParamsFromCDict(
|
|
state: *const ZSTD_rust_cdictQueryState,
|
|
) -> ZSTD_compressionParameters {
|
|
if state.is_null() {
|
|
return ZSTD_compressionParameters::default();
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.c_params.is_null() {
|
|
return ZSTD_compressionParameters::default();
|
|
}
|
|
unsafe { *state.c_params }
|
|
}
|
|
|
|
/// Return the dictionary ID from a CDict projection, preserving NULL -> 0.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_getDictIDFromCDict(
|
|
state: *const ZSTD_rust_cdictQueryState,
|
|
) -> c_uint {
|
|
if state.is_null() {
|
|
return 0;
|
|
}
|
|
let state = unsafe { &*state };
|
|
if state.dict_id.is_null() {
|
|
return 0;
|
|
}
|
|
unsafe { *state.dict_id }
|
|
}
|
|
|
|
/// Rust-owned policy for `ZSTD_CCtx_loadDictionary_advanced()`.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_CCtx_loadDictionaryAdvanced(
|
|
context: *mut c_void,
|
|
stream_stage: c_int,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_load_method: c_int,
|
|
dict_content_type: c_int,
|
|
clear_dictionaries: CctxDictionaryClearFn,
|
|
assign_local_dict: CctxAssignLocalDictFn,
|
|
) -> usize {
|
|
if stream_stage != ZSTD_CCTX_INIT_STAGE {
|
|
return stage_wrong();
|
|
}
|
|
|
|
unsafe { clear_dictionaries(context) };
|
|
if dict.is_null() || dict_size == 0 {
|
|
return 0;
|
|
}
|
|
|
|
unsafe {
|
|
assign_local_dict(
|
|
context,
|
|
dict,
|
|
dict_size,
|
|
dict_load_method,
|
|
dict_content_type,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Rust-owned policy for `ZSTD_CCtx_refCDict()`.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_CCtx_refCDict(
|
|
context: *mut c_void,
|
|
stream_stage: c_int,
|
|
cdict: *const c_void,
|
|
clear_dictionaries: CctxDictionaryClearFn,
|
|
assign_cdict: CctxAssignCDictFn,
|
|
) -> usize {
|
|
if stream_stage != ZSTD_CCTX_INIT_STAGE {
|
|
return stage_wrong();
|
|
}
|
|
|
|
unsafe { clear_dictionaries(context) };
|
|
unsafe { assign_cdict(context, cdict) };
|
|
0
|
|
}
|
|
|
|
/// Rust-owned policy for `ZSTD_CCtx_refPrefix_advanced()`.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_CCtx_refPrefixAdvanced(
|
|
context: *mut c_void,
|
|
stream_stage: c_int,
|
|
prefix: *const c_void,
|
|
prefix_size: usize,
|
|
dict_content_type: c_int,
|
|
clear_dictionaries: CctxDictionaryClearFn,
|
|
assign_prefix_dict: CctxAssignPrefixDictFn,
|
|
) -> usize {
|
|
if stream_stage != ZSTD_CCTX_INIT_STAGE {
|
|
return stage_wrong();
|
|
}
|
|
|
|
unsafe { clear_dictionaries(context) };
|
|
if prefix.is_null() || prefix_size == 0 {
|
|
return 0;
|
|
}
|
|
|
|
unsafe { assign_prefix_dict(context, prefix, prefix_size, dict_content_type) };
|
|
0
|
|
}
|
|
|
|
/// Matches C's `ZSTD_dictNCountRepeat()`.
|
|
#[inline]
|
|
fn dict_n_count_repeat(
|
|
normalized_counter: &[c_short],
|
|
dict_max_symbol_value: usize,
|
|
max_symbol_value: usize,
|
|
) -> c_int {
|
|
if dict_max_symbol_value < max_symbol_value {
|
|
return FSE_REPEAT_CHECK;
|
|
}
|
|
if normalized_counter.len() <= max_symbol_value
|
|
|| normalized_counter[..=max_symbol_value].contains(&0)
|
|
{
|
|
return FSE_REPEAT_CHECK;
|
|
}
|
|
FSE_REPEAT_VALID
|
|
}
|
|
|
|
/// Reads and builds one dictionary FSE table, advancing `offset` on success.
|
|
///
|
|
/// C supplies `max_symbol_value` from `FSE_readNCount()` to the table builder
|
|
/// for the match-length and literal-length tables, but deliberately builds the
|
|
/// offset table with `MaxOff` so the unused tail is initialized. The caller
|
|
/// passes that distinction through `table_max_symbol_value`.
|
|
unsafe fn read_and_build_fse_table(
|
|
dictionary: &[u8],
|
|
offset: &mut usize,
|
|
normalized_counter: &mut [c_short],
|
|
initial_max_symbol_value: c_uint,
|
|
max_table_log: c_uint,
|
|
table: *mut c_uint,
|
|
table_max_symbol_value: c_uint,
|
|
workspace: *mut c_void,
|
|
) -> Result<c_uint, ()> {
|
|
let input = &dictionary[*offset..];
|
|
let mut max_symbol_value = initial_max_symbol_value;
|
|
let mut table_log = 0u32;
|
|
let header_size = unsafe {
|
|
FSE_readNCount(
|
|
normalized_counter.as_mut_ptr(),
|
|
&mut max_symbol_value,
|
|
&mut table_log,
|
|
input.as_ptr().cast(),
|
|
input.len(),
|
|
)
|
|
};
|
|
if ERR_isError(header_size)
|
|
|| header_size > input.len()
|
|
|| max_symbol_value as usize >= normalized_counter.len()
|
|
|| table_log > max_table_log
|
|
{
|
|
return Err(());
|
|
}
|
|
|
|
let build_result = unsafe {
|
|
FSE_buildCTable_wksp(
|
|
table,
|
|
normalized_counter.as_ptr(),
|
|
table_max_symbol_value,
|
|
table_log,
|
|
workspace,
|
|
HUF_WORKSPACE_SIZE,
|
|
)
|
|
};
|
|
if ERR_isError(build_result) {
|
|
return Err(());
|
|
}
|
|
|
|
*offset = offset.checked_add(header_size).ok_or(())?;
|
|
Ok(max_symbol_value)
|
|
}
|
|
|
|
/// Rust implementation of C `ZSTD_loadCEntropy()`.
|
|
///
|
|
/// The input is assumed to have the dictionary magic and ID in its first eight
|
|
/// bytes, matching the C function's documented precondition. Malformed or
|
|
/// truncated entropy headers are converted to `ZSTD_error_dictionaryCorrupted`
|
|
/// just as the C `RETURN_ERROR_IF(..., dictionary_corrupted, ...)` paths do.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_loadCEntropy(
|
|
bs: *mut ZSTD_compressedBlockState_t,
|
|
workspace: *mut c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
) -> usize {
|
|
if bs.is_null()
|
|
|| dict.is_null()
|
|
|| workspace.is_null()
|
|
|| dict_size < DICTIONARY_ID_AND_MAGIC_SIZE
|
|
{
|
|
return dictionary_corrupted();
|
|
}
|
|
|
|
let dictionary = unsafe { slice::from_raw_parts(dict.cast::<u8>(), dict_size) };
|
|
let mut offset = DICTIONARY_ID_AND_MAGIC_SIZE;
|
|
|
|
unsafe {
|
|
(*bs).entropy.huf.repeatMode = HUF_REPEAT_CHECK;
|
|
}
|
|
|
|
let mut huf_max_symbol_value = 255u32;
|
|
let mut huf_has_zero_weights = 1u32;
|
|
let huf_header_size = unsafe {
|
|
HUF_readCTable(
|
|
(*bs).entropy.huf.CTable.as_mut_ptr(),
|
|
&mut huf_max_symbol_value,
|
|
dictionary[offset..].as_ptr().cast(),
|
|
dictionary.len() - offset,
|
|
&mut huf_has_zero_weights,
|
|
)
|
|
};
|
|
if ERR_isError(huf_header_size) || huf_header_size > dictionary.len().saturating_sub(offset) {
|
|
return dictionary_corrupted();
|
|
}
|
|
if huf_has_zero_weights == 0 && huf_max_symbol_value == 255 {
|
|
unsafe {
|
|
(*bs).entropy.huf.repeatMode = HUF_REPEAT_VALID;
|
|
}
|
|
}
|
|
offset = match offset.checked_add(huf_header_size) {
|
|
Some(value) => value,
|
|
None => return dictionary_corrupted(),
|
|
};
|
|
|
|
let mut offcode_ncount = [0i16; MAX_OFF + 1];
|
|
let offcode_max_symbol_value = MAX_OFF as c_uint;
|
|
let offcode_header_max = match unsafe {
|
|
read_and_build_fse_table(
|
|
dictionary,
|
|
&mut offset,
|
|
&mut offcode_ncount,
|
|
offcode_max_symbol_value,
|
|
OFF_FSE_LOG as c_uint,
|
|
(*bs).entropy.fse.offcodeCTable.as_mut_ptr(),
|
|
MAX_OFF as c_uint,
|
|
workspace,
|
|
)
|
|
} {
|
|
Ok(value) => value,
|
|
Err(()) => return dictionary_corrupted(),
|
|
};
|
|
|
|
let mut matchlength_ncount = [0i16; MAX_ML + 1];
|
|
let matchlength_max_symbol_value = MAX_ML as c_uint;
|
|
let matchlength_header_max = match unsafe {
|
|
read_and_build_fse_table(
|
|
dictionary,
|
|
&mut offset,
|
|
&mut matchlength_ncount,
|
|
matchlength_max_symbol_value,
|
|
ML_FSE_LOG as c_uint,
|
|
(*bs).entropy.fse.matchlengthCTable.as_mut_ptr(),
|
|
matchlength_max_symbol_value,
|
|
workspace,
|
|
)
|
|
} {
|
|
Ok(value) => value,
|
|
Err(()) => return dictionary_corrupted(),
|
|
};
|
|
unsafe {
|
|
(*bs).entropy.fse.matchlength_repeatMode =
|
|
dict_n_count_repeat(&matchlength_ncount, matchlength_header_max as usize, MAX_ML);
|
|
}
|
|
|
|
let mut litlength_ncount = [0i16; MAX_LL + 1];
|
|
let litlength_max_symbol_value = MAX_LL as c_uint;
|
|
let litlength_header_max = match unsafe {
|
|
read_and_build_fse_table(
|
|
dictionary,
|
|
&mut offset,
|
|
&mut litlength_ncount,
|
|
litlength_max_symbol_value,
|
|
LL_FSE_LOG as c_uint,
|
|
(*bs).entropy.fse.litlengthCTable.as_mut_ptr(),
|
|
litlength_max_symbol_value,
|
|
workspace,
|
|
)
|
|
} {
|
|
Ok(value) => value,
|
|
Err(()) => return dictionary_corrupted(),
|
|
};
|
|
unsafe {
|
|
(*bs).entropy.fse.litlength_repeatMode =
|
|
dict_n_count_repeat(&litlength_ncount, litlength_header_max as usize, MAX_LL);
|
|
}
|
|
|
|
if dictionary.len().saturating_sub(offset) < REPCODE_SECTION_SIZE {
|
|
return dictionary_corrupted();
|
|
}
|
|
unsafe {
|
|
(*bs).rep[0] = u32::from_le_bytes(dictionary[offset..offset + 4].try_into().unwrap());
|
|
(*bs).rep[1] = u32::from_le_bytes(dictionary[offset + 4..offset + 8].try_into().unwrap());
|
|
(*bs).rep[2] = u32::from_le_bytes(dictionary[offset + 8..offset + 12].try_into().unwrap());
|
|
}
|
|
offset += REPCODE_SECTION_SIZE;
|
|
|
|
let dict_content_size = dictionary.len() - offset;
|
|
let offcode_max = if dict_content_size <= (u32::MAX as usize) - (128 << 10) {
|
|
let max_offset = (dict_content_size + (128 << 10)) as u32;
|
|
ZSTD_highbit32(max_offset) as usize
|
|
} else {
|
|
MAX_OFF
|
|
};
|
|
let required_offcode_max = offcode_max.min(MAX_OFF);
|
|
let offcode_repeat_mode = dict_n_count_repeat(
|
|
&offcode_ncount,
|
|
offcode_header_max as usize,
|
|
required_offcode_max,
|
|
);
|
|
unsafe {
|
|
(*bs).entropy.fse.offcode_repeatMode = offcode_repeat_mode;
|
|
}
|
|
|
|
unsafe {
|
|
for &rep in &(*bs).rep {
|
|
if rep == 0 || rep as usize > dict_content_size {
|
|
return dictionary_corrupted();
|
|
}
|
|
}
|
|
}
|
|
|
|
offset
|
|
}
|
|
|
|
/// Rust-owned dispatch for C's `ZSTD_compress_insertDictionary()`.
|
|
///
|
|
/// The callback is the only operation that still crosses back into C. It
|
|
/// receives opaque pointers because the content loader needs private C
|
|
/// layouts, while Rust owns short-dictionary handling, mode selection, block
|
|
/// state reset, dictionary magic/ID handling, and entropy setup.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn ZSTD_rust_compressInsertDictionary(
|
|
bs: *mut ZSTD_compressedBlockState_t,
|
|
match_state: *mut c_void,
|
|
ldm_state: *mut c_void,
|
|
workspace_state: *mut c_void,
|
|
params: *const c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_content_type: c_int,
|
|
dtlm: c_int,
|
|
tfp: c_int,
|
|
workspace: *mut c_void,
|
|
no_dict_id_flag: c_int,
|
|
load_dictionary_content: LoadDictionaryContentFn,
|
|
) -> usize {
|
|
if dict.is_null() || dict_size < DICTIONARY_ID_AND_MAGIC_SIZE {
|
|
if dict_content_type == ZSTD_DCT_FULL_DICT {
|
|
return ERROR(ZstdErrorCode::DictionaryWrong);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
unsafe { ZSTD_rust_resetCompressedBlockState(bs) };
|
|
|
|
if dict_content_type == ZSTD_DCT_RAW_CONTENT {
|
|
return unsafe {
|
|
load_dictionary_content(
|
|
match_state,
|
|
ldm_state,
|
|
workspace_state,
|
|
params,
|
|
dict,
|
|
dict_size,
|
|
dtlm,
|
|
tfp,
|
|
)
|
|
};
|
|
}
|
|
|
|
let dict_magic = unsafe { u32::from_le(ptr::read_unaligned(dict.cast::<u32>())) };
|
|
if dict_magic != ZSTD_MAGIC_DICTIONARY {
|
|
if dict_content_type == ZSTD_DCT_AUTO {
|
|
return unsafe {
|
|
load_dictionary_content(
|
|
match_state,
|
|
ldm_state,
|
|
workspace_state,
|
|
params,
|
|
dict,
|
|
dict_size,
|
|
dtlm,
|
|
tfp,
|
|
)
|
|
};
|
|
}
|
|
if dict_content_type == ZSTD_DCT_FULL_DICT {
|
|
return ERROR(ZstdErrorCode::DictionaryWrong);
|
|
}
|
|
debug_assert!(false, "invalid dictionary content type");
|
|
}
|
|
|
|
let dict_id = if no_dict_id_flag != 0 {
|
|
0
|
|
} else {
|
|
unsafe {
|
|
u32::from_le(ptr::read_unaligned(dict.cast::<u8>().add(4).cast::<u32>())) as usize
|
|
}
|
|
};
|
|
let entropy_size = unsafe { ZSTD_rust_loadCEntropy(bs, workspace, dict, dict_size) };
|
|
if ERR_isError(entropy_size) {
|
|
return entropy_size;
|
|
}
|
|
if entropy_size > dict_size {
|
|
return dictionary_corrupted();
|
|
}
|
|
|
|
let content_result = unsafe {
|
|
load_dictionary_content(
|
|
match_state,
|
|
ptr::null_mut(),
|
|
workspace_state,
|
|
params,
|
|
dict.cast::<u8>().add(entropy_size).cast(),
|
|
dict_size - entropy_size,
|
|
dtlm,
|
|
tfp,
|
|
)
|
|
};
|
|
if ERR_isError(content_result) {
|
|
return content_result;
|
|
}
|
|
dict_id
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::errors::ERR_getErrorCode;
|
|
use crate::fse_compress::{FSE_normalizeCount, FSE_writeNCount};
|
|
use crate::huf_compress::{HUF_buildCTable_wksp, HUF_writeCTable_wksp};
|
|
use crate::mem::MEM_writeLE32;
|
|
use std::mem::{size_of, MaybeUninit};
|
|
|
|
const DICT_CONTENT_SIZE: usize = 16;
|
|
|
|
#[derive(Clone, Copy)]
|
|
enum ZeroWeight {
|
|
None,
|
|
Huffman,
|
|
MatchLength,
|
|
LiteralLength,
|
|
Offset,
|
|
}
|
|
|
|
struct DictionaryLoadProbe {
|
|
calls: usize,
|
|
ldm_state: *mut c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dtlm: c_int,
|
|
tfp: c_int,
|
|
}
|
|
|
|
impl Default for DictionaryLoadProbe {
|
|
fn default() -> Self {
|
|
Self {
|
|
calls: 0,
|
|
ldm_state: ptr::null_mut(),
|
|
dict: ptr::null(),
|
|
dict_size: 0,
|
|
dtlm: 0,
|
|
tfp: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
struct CctxPolicyProbe {
|
|
events: Vec<u8>,
|
|
local_dict: *const c_void,
|
|
local_dict_size: usize,
|
|
local_dict_load_method: c_int,
|
|
local_dict_content_type: c_int,
|
|
local_result: usize,
|
|
cdict: *const c_void,
|
|
prefix: *const c_void,
|
|
prefix_size: usize,
|
|
prefix_content_type: c_int,
|
|
}
|
|
|
|
impl Default for CctxPolicyProbe {
|
|
fn default() -> Self {
|
|
Self {
|
|
events: Vec::new(),
|
|
local_dict: ptr::null(),
|
|
local_dict_size: 0,
|
|
local_dict_load_method: 0,
|
|
local_dict_content_type: 0,
|
|
local_result: 0,
|
|
cdict: ptr::null(),
|
|
prefix: ptr::null(),
|
|
prefix_size: 0,
|
|
prefix_content_type: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
struct InitCDictProbe {
|
|
events: Vec<&'static str>,
|
|
assigned_dict: *const c_void,
|
|
assigned_size: usize,
|
|
assigned_load_method: c_int,
|
|
reset_c_params: ZSTD_compressionParameters,
|
|
reset_use_row_match_finder: c_int,
|
|
inserted_params: *const c_void,
|
|
inserted_dict: *const c_void,
|
|
inserted_size: usize,
|
|
inserted_content_type: c_int,
|
|
assign_result: usize,
|
|
entropy_workspace: *mut c_void,
|
|
reset_match_result: usize,
|
|
insert_result: usize,
|
|
}
|
|
|
|
impl Default for InitCDictProbe {
|
|
fn default() -> Self {
|
|
Self {
|
|
events: Vec::new(),
|
|
assigned_dict: ptr::null(),
|
|
assigned_size: 0,
|
|
assigned_load_method: 0,
|
|
reset_c_params: ZSTD_compressionParameters::default(),
|
|
reset_use_row_match_finder: 0,
|
|
inserted_params: ptr::null(),
|
|
inserted_dict: ptr::null(),
|
|
inserted_size: 0,
|
|
inserted_content_type: 0,
|
|
assign_result: 0,
|
|
entropy_workspace: ptr::null_mut(),
|
|
reset_match_result: 0,
|
|
insert_result: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe fn init_cdict_probe(context: *mut c_void) -> &'static mut InitCDictProbe {
|
|
unsafe { &mut *context.cast::<InitCDictProbe>() }
|
|
}
|
|
|
|
unsafe extern "C" fn init_cdict_assign_content(
|
|
context: *mut c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_load_method: c_int,
|
|
) -> usize {
|
|
let probe = unsafe { init_cdict_probe(context) };
|
|
probe.events.push("assign");
|
|
probe.assigned_dict = dict;
|
|
probe.assigned_size = dict_size;
|
|
probe.assigned_load_method = dict_load_method;
|
|
probe.assign_result
|
|
}
|
|
|
|
unsafe extern "C" fn init_cdict_reserve_entropy(context: *mut c_void) -> *mut c_void {
|
|
let probe = unsafe { init_cdict_probe(context) };
|
|
probe.events.push("reserve");
|
|
probe.entropy_workspace
|
|
}
|
|
|
|
unsafe extern "C" fn init_cdict_reset_block_state(context: *mut c_void) {
|
|
unsafe { init_cdict_probe(context) }
|
|
.events
|
|
.push("reset-block");
|
|
}
|
|
|
|
unsafe extern "C" fn init_cdict_reset_match_state(
|
|
context: *mut c_void,
|
|
c_params: *const c_void,
|
|
use_row_match_finder: c_int,
|
|
) -> usize {
|
|
let probe = unsafe { init_cdict_probe(context) };
|
|
probe.events.push("reset-match");
|
|
probe.reset_c_params = unsafe { *c_params.cast::<ZSTD_compressionParameters>() };
|
|
probe.reset_use_row_match_finder = use_row_match_finder;
|
|
probe.reset_match_result
|
|
}
|
|
|
|
unsafe extern "C" fn init_cdict_insert_dictionary(
|
|
context: *mut c_void,
|
|
params: *const c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_content_type: c_int,
|
|
) -> usize {
|
|
let probe = unsafe { init_cdict_probe(context) };
|
|
probe.events.push("insert");
|
|
probe.inserted_params = params;
|
|
probe.inserted_dict = dict;
|
|
probe.inserted_size = dict_size;
|
|
probe.inserted_content_type = dict_content_type;
|
|
probe.insert_result
|
|
}
|
|
|
|
#[test]
|
|
fn cdict_init_orders_callbacks_and_publishes_state() {
|
|
let dictionary = [1u8, 2, 3, 4];
|
|
let mut probe = InitCDictProbe {
|
|
entropy_workspace: 0x1000usize as *mut c_void,
|
|
insert_result: 0x1234,
|
|
..Default::default()
|
|
};
|
|
let c_params = ZSTD_compressionParameters {
|
|
windowLog: 21,
|
|
chainLog: 18,
|
|
hashLog: 19,
|
|
searchLog: 4,
|
|
minMatch: 5,
|
|
targetLength: 16,
|
|
strategy: 3,
|
|
};
|
|
let mut match_state_c_params = ZSTD_compressionParameters::default();
|
|
let enable_dedicated_dict_search = 1;
|
|
let use_row_match_finder = 2;
|
|
let mut dedicated_dict_search = 0;
|
|
let mut dict_content = dictionary.as_ptr().cast::<c_void>();
|
|
let mut dict_content_size = 0;
|
|
let mut dict_content_type = 0;
|
|
let mut entropy_workspace = ptr::null_mut();
|
|
let mut dict_id = 0;
|
|
let mut compression_level = 99;
|
|
let mut content_size_flag = 0;
|
|
let params = 0x2000usize as *const c_void;
|
|
let callback_context = (&mut probe as *mut InitCDictProbe).cast();
|
|
let state = ZSTD_rust_initCDictState {
|
|
callback_context,
|
|
params,
|
|
c_params: &c_params,
|
|
match_state_c_params: &mut match_state_c_params,
|
|
dedicated_dict_search: &mut dedicated_dict_search,
|
|
enable_dedicated_dict_search: &enable_dedicated_dict_search,
|
|
use_row_match_finder: &use_row_match_finder,
|
|
dict_content: &mut dict_content,
|
|
dict_content_size: &mut dict_content_size,
|
|
dict_content_type: &mut dict_content_type,
|
|
entropy_workspace: &mut entropy_workspace,
|
|
dict_id: &mut dict_id,
|
|
compression_level: &mut compression_level,
|
|
content_size_flag: &mut content_size_flag,
|
|
assign_content: Some(init_cdict_assign_content),
|
|
reserve_entropy: Some(init_cdict_reserve_entropy),
|
|
reset_block_state: Some(init_cdict_reset_block_state),
|
|
reset_match_state: Some(init_cdict_reset_match_state),
|
|
insert_dictionary: Some(init_cdict_insert_dictionary),
|
|
};
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_initCDict(
|
|
&state,
|
|
dictionary.as_ptr().cast(),
|
|
dictionary.len(),
|
|
ZSTD_DLM_BY_REF,
|
|
ZSTD_DCT_RAW_CONTENT,
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(
|
|
probe.events,
|
|
["assign", "reserve", "reset-block", "reset-match", "insert"]
|
|
);
|
|
assert_eq!(probe.assigned_dict, dictionary.as_ptr().cast());
|
|
assert_eq!(probe.assigned_size, dictionary.len());
|
|
assert_eq!(probe.assigned_load_method, ZSTD_DLM_BY_REF);
|
|
assert_eq!(probe.reset_c_params, c_params);
|
|
assert_eq!(probe.reset_use_row_match_finder, use_row_match_finder);
|
|
assert_eq!(probe.inserted_params, params);
|
|
assert_eq!(probe.inserted_dict, dictionary.as_ptr().cast());
|
|
assert_eq!(probe.inserted_size, dictionary.len());
|
|
assert_eq!(probe.inserted_content_type, ZSTD_DCT_RAW_CONTENT);
|
|
assert_eq!(match_state_c_params, c_params);
|
|
assert_eq!(dedicated_dict_search, enable_dedicated_dict_search);
|
|
assert_eq!(dict_content_size, dictionary.len());
|
|
assert_eq!(dict_content_type, ZSTD_DCT_RAW_CONTENT);
|
|
assert_eq!(entropy_workspace, probe.entropy_workspace);
|
|
assert_eq!(dict_id, probe.insert_result as c_uint);
|
|
assert_eq!(compression_level, 3);
|
|
assert_eq!(content_size_flag, 1);
|
|
}
|
|
|
|
struct CompressBeginProbe {
|
|
events: Vec<&'static str>,
|
|
reset_params: *const c_void,
|
|
reset_loaded_dict_size: usize,
|
|
reset_pledged_src_size: u64,
|
|
reset_zbuff: c_int,
|
|
reset_result: usize,
|
|
attach_cdict: *const c_void,
|
|
attach_params: *const c_void,
|
|
attach_pledged_src_size: u64,
|
|
attach_zbuff: c_int,
|
|
attach_result: usize,
|
|
insert_cdict: *const c_void,
|
|
insert_dict: *const c_void,
|
|
insert_size: usize,
|
|
insert_content_type: c_int,
|
|
insert_dtlm: c_int,
|
|
insert_result: usize,
|
|
}
|
|
|
|
impl Default for CompressBeginProbe {
|
|
fn default() -> Self {
|
|
Self {
|
|
events: Vec::new(),
|
|
reset_params: ptr::null(),
|
|
reset_loaded_dict_size: 0,
|
|
reset_pledged_src_size: 0,
|
|
reset_zbuff: 0,
|
|
reset_result: 0,
|
|
attach_cdict: ptr::null(),
|
|
attach_params: ptr::null(),
|
|
attach_pledged_src_size: 0,
|
|
attach_zbuff: 0,
|
|
attach_result: 0,
|
|
insert_cdict: ptr::null(),
|
|
insert_dict: ptr::null(),
|
|
insert_size: 0,
|
|
insert_content_type: 0,
|
|
insert_dtlm: 0,
|
|
insert_result: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe fn compress_begin_probe(context: *mut c_void) -> &'static mut CompressBeginProbe {
|
|
unsafe { &mut *context.cast::<CompressBeginProbe>() }
|
|
}
|
|
|
|
unsafe extern "C" fn compress_begin_reset_internal(
|
|
context: *mut c_void,
|
|
params: *const c_void,
|
|
pledged_src_size: u64,
|
|
loaded_dict_size: usize,
|
|
zbuff: c_int,
|
|
) -> usize {
|
|
let probe = unsafe { compress_begin_probe(context) };
|
|
probe.events.push("reset");
|
|
probe.reset_params = params;
|
|
probe.reset_pledged_src_size = pledged_src_size;
|
|
probe.reset_loaded_dict_size = loaded_dict_size;
|
|
probe.reset_zbuff = zbuff;
|
|
probe.reset_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_begin_reset_using_cdict(
|
|
context: *mut c_void,
|
|
cdict: *const c_void,
|
|
params: *const c_void,
|
|
pledged_src_size: u64,
|
|
zbuff: c_int,
|
|
) -> usize {
|
|
let probe = unsafe { compress_begin_probe(context) };
|
|
probe.events.push("attach");
|
|
probe.attach_cdict = cdict;
|
|
probe.attach_params = params;
|
|
probe.attach_pledged_src_size = pledged_src_size;
|
|
probe.attach_zbuff = zbuff;
|
|
probe.attach_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_begin_insert_dictionary(
|
|
context: *mut c_void,
|
|
cdict: *const c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_content_type: c_int,
|
|
dtlm: c_int,
|
|
) -> usize {
|
|
let probe = unsafe { compress_begin_probe(context) };
|
|
probe.events.push("insert");
|
|
probe.insert_cdict = cdict;
|
|
probe.insert_dict = dict;
|
|
probe.insert_size = dict_size;
|
|
probe.insert_content_type = dict_content_type;
|
|
probe.insert_dtlm = dtlm;
|
|
probe.insert_result
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn compress_begin_test_state(
|
|
probe: &mut CompressBeginProbe,
|
|
params: *const c_void,
|
|
cdict: *const c_void,
|
|
cdict_content_size: *const usize,
|
|
cdict_compression_level: *const c_int,
|
|
attach_dict_pref: &c_int,
|
|
dict: *const c_void,
|
|
dict_size: &usize,
|
|
dict_content_type: &c_int,
|
|
dtlm: &c_int,
|
|
pledged_src_size: &u64,
|
|
zbuff: &c_int,
|
|
force_load: &c_int,
|
|
dict_id: &mut c_uint,
|
|
dict_content_size: &mut usize,
|
|
) -> ZSTD_rust_compressBeginState {
|
|
ZSTD_rust_compressBeginState {
|
|
callback_context: (probe as *mut CompressBeginProbe).cast(),
|
|
params,
|
|
cdict,
|
|
cdict_content_size,
|
|
cdict_compression_level,
|
|
attach_dict_pref,
|
|
dict,
|
|
dict_size,
|
|
dict_content_type,
|
|
dtlm,
|
|
pledged_src_size,
|
|
zbuff,
|
|
force_load,
|
|
dict_id,
|
|
dict_content_size,
|
|
reset_internal: Some(compress_begin_reset_internal),
|
|
reset_using_cdict: Some(compress_begin_reset_using_cdict),
|
|
insert_dictionary: Some(compress_begin_insert_dictionary),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_begin_resets_then_inserts_for_loaded_dictionary() {
|
|
let dictionary = [1u8, 2, 3, 4];
|
|
let mut probe = CompressBeginProbe {
|
|
insert_result: 17,
|
|
..Default::default()
|
|
};
|
|
let params = 0x3000usize as *const c_void;
|
|
let attach_dict_pref = 0;
|
|
let dict_size = dictionary.len();
|
|
let dict_content_type = ZSTD_DCT_RAW_CONTENT;
|
|
let dtlm = 7;
|
|
let pledged_src_size = 1u64 << 20;
|
|
let zbuff = 9;
|
|
let force_load = 1;
|
|
let mut dict_id = 0;
|
|
let mut dict_content_size = 0;
|
|
let state = compress_begin_test_state(
|
|
&mut probe,
|
|
params,
|
|
ptr::null(),
|
|
ptr::null(),
|
|
ptr::null(),
|
|
&attach_dict_pref,
|
|
dictionary.as_ptr().cast(),
|
|
&dict_size,
|
|
&dict_content_type,
|
|
&dtlm,
|
|
&pledged_src_size,
|
|
&zbuff,
|
|
&force_load,
|
|
&mut dict_id,
|
|
&mut dict_content_size,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_compressBegin(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.events, ["reset", "insert"]);
|
|
assert_eq!(probe.reset_params, params);
|
|
assert_eq!(probe.reset_loaded_dict_size, dictionary.len());
|
|
assert_eq!(probe.reset_pledged_src_size, pledged_src_size);
|
|
assert_eq!(probe.reset_zbuff, zbuff);
|
|
assert!(probe.insert_cdict.is_null());
|
|
assert_eq!(probe.insert_dict, dictionary.as_ptr().cast());
|
|
assert_eq!(probe.insert_size, dictionary.len());
|
|
assert_eq!(probe.insert_content_type, dict_content_type);
|
|
assert_eq!(probe.insert_dtlm, dtlm);
|
|
assert_eq!(dict_id, probe.insert_result as c_uint);
|
|
assert_eq!(dict_content_size, dictionary.len());
|
|
}
|
|
|
|
#[test]
|
|
fn compress_begin_attaches_small_cdict_without_insertion() {
|
|
let mut probe = CompressBeginProbe::default();
|
|
let params = 0x3000usize as *const c_void;
|
|
let cdict = 0x4000usize as *const c_void;
|
|
let cdict_content_size = 16;
|
|
let cdict_compression_level = 0;
|
|
let attach_dict_pref = 0;
|
|
let dict_size = 0;
|
|
let dict_content_type = ZSTD_DCT_AUTO;
|
|
let dtlm = 7;
|
|
let pledged_src_size = ZSTD_CONTENTSIZE_UNKNOWN;
|
|
let zbuff = 9;
|
|
let force_load = 1;
|
|
let mut dict_id = 0;
|
|
let mut dict_content_size = 0;
|
|
let state = compress_begin_test_state(
|
|
&mut probe,
|
|
params,
|
|
cdict,
|
|
&cdict_content_size,
|
|
&cdict_compression_level,
|
|
&attach_dict_pref,
|
|
ptr::null(),
|
|
&dict_size,
|
|
&dict_content_type,
|
|
&dtlm,
|
|
&pledged_src_size,
|
|
&zbuff,
|
|
&force_load,
|
|
&mut dict_id,
|
|
&mut dict_content_size,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_compressBegin(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.events, ["attach"]);
|
|
assert_eq!(probe.attach_cdict, cdict);
|
|
assert_eq!(probe.attach_params, params);
|
|
assert_eq!(probe.attach_pledged_src_size, pledged_src_size);
|
|
assert_eq!(probe.attach_zbuff, zbuff);
|
|
}
|
|
|
|
struct ResetUsingCDictProbe {
|
|
events: Vec<&'static str>,
|
|
attach_cdict: *const c_void,
|
|
attach_params: *const c_void,
|
|
attach_pledged_src_size: u64,
|
|
attach_zbuff: c_int,
|
|
attach_result: usize,
|
|
copy_cdict: *const c_void,
|
|
copy_params: *const c_void,
|
|
copy_pledged_src_size: u64,
|
|
copy_zbuff: c_int,
|
|
copy_result: usize,
|
|
}
|
|
|
|
impl Default for ResetUsingCDictProbe {
|
|
fn default() -> Self {
|
|
Self {
|
|
events: Vec::new(),
|
|
attach_cdict: ptr::null(),
|
|
attach_params: ptr::null(),
|
|
attach_pledged_src_size: 0,
|
|
attach_zbuff: 0,
|
|
attach_result: 0,
|
|
copy_cdict: ptr::null(),
|
|
copy_params: ptr::null(),
|
|
copy_pledged_src_size: 0,
|
|
copy_zbuff: 0,
|
|
copy_result: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe fn reset_using_cdict_probe(context: *mut c_void) -> &'static mut ResetUsingCDictProbe {
|
|
unsafe { &mut *context.cast::<ResetUsingCDictProbe>() }
|
|
}
|
|
|
|
unsafe extern "C" fn reset_using_cdict_attach(
|
|
context: *mut c_void,
|
|
cdict: *const c_void,
|
|
params: *const c_void,
|
|
pledged_src_size: u64,
|
|
zbuff: c_int,
|
|
) -> usize {
|
|
let probe = unsafe { reset_using_cdict_probe(context) };
|
|
probe.events.push("attach");
|
|
probe.attach_cdict = cdict;
|
|
probe.attach_params = params;
|
|
probe.attach_pledged_src_size = pledged_src_size;
|
|
probe.attach_zbuff = zbuff;
|
|
probe.attach_result
|
|
}
|
|
|
|
unsafe extern "C" fn reset_using_cdict_copy(
|
|
context: *mut c_void,
|
|
cdict: *const c_void,
|
|
params: *const c_void,
|
|
pledged_src_size: u64,
|
|
zbuff: c_int,
|
|
) -> usize {
|
|
let probe = unsafe { reset_using_cdict_probe(context) };
|
|
probe.events.push("copy");
|
|
probe.copy_cdict = cdict;
|
|
probe.copy_params = params;
|
|
probe.copy_pledged_src_size = pledged_src_size;
|
|
probe.copy_zbuff = zbuff;
|
|
probe.copy_result
|
|
}
|
|
|
|
fn reset_using_cdict_test_state(
|
|
probe: &mut ResetUsingCDictProbe,
|
|
cdict: *const c_void,
|
|
params: *const c_void,
|
|
strategy: &c_int,
|
|
dedicated_dict_search: &c_int,
|
|
attach_dict_pref: &c_int,
|
|
force_window: &c_int,
|
|
pledged_src_size: &u64,
|
|
zbuff: &c_int,
|
|
) -> ZSTD_rust_resetCCtxUsingCDictState {
|
|
ZSTD_rust_resetCCtxUsingCDictState {
|
|
callback_context: (probe as *mut ResetUsingCDictProbe).cast(),
|
|
cdict,
|
|
params,
|
|
cdict_strategy: strategy,
|
|
dedicated_dict_search,
|
|
attach_dict_pref,
|
|
force_window,
|
|
pledged_src_size,
|
|
zbuff,
|
|
attach: Some(reset_using_cdict_attach),
|
|
copy: Some(reset_using_cdict_copy),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn reset_using_cdict_selects_attach_at_policy_cutoff() {
|
|
let mut probe = ResetUsingCDictProbe::default();
|
|
let cdict = 0x4000usize as *const c_void;
|
|
let params = 0x3000usize as *const c_void;
|
|
let strategy = 1;
|
|
let dedicated_dict_search = 0;
|
|
let attach_dict_pref = 0;
|
|
let force_window = 0;
|
|
let pledged_src_size = 8 * 1024;
|
|
let zbuff = 9;
|
|
let state = reset_using_cdict_test_state(
|
|
&mut probe,
|
|
cdict,
|
|
params,
|
|
&strategy,
|
|
&dedicated_dict_search,
|
|
&attach_dict_pref,
|
|
&force_window,
|
|
&pledged_src_size,
|
|
&zbuff,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCCtxUsingCDict(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.events, ["attach"]);
|
|
assert_eq!(probe.attach_cdict, cdict);
|
|
assert_eq!(probe.attach_params, params);
|
|
assert_eq!(probe.attach_pledged_src_size, pledged_src_size);
|
|
assert_eq!(probe.attach_zbuff, zbuff);
|
|
assert!(probe.copy_cdict.is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn reset_using_cdict_selects_copy_and_propagates_its_error() {
|
|
let mut probe = ResetUsingCDictProbe {
|
|
copy_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..Default::default()
|
|
};
|
|
let cdict = 0x4000usize as *const c_void;
|
|
let params = 0x3000usize as *const c_void;
|
|
let strategy = 1;
|
|
let dedicated_dict_search = 0;
|
|
let attach_dict_pref = 0;
|
|
let force_window = 0;
|
|
let pledged_src_size = 8 * 1024 + 1;
|
|
let zbuff = 9;
|
|
let state = reset_using_cdict_test_state(
|
|
&mut probe,
|
|
cdict,
|
|
params,
|
|
&strategy,
|
|
&dedicated_dict_search,
|
|
&attach_dict_pref,
|
|
&force_window,
|
|
&pledged_src_size,
|
|
&zbuff,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCCtxUsingCDict(&state) };
|
|
|
|
assert_eq!(result, probe.copy_result);
|
|
assert_eq!(probe.events, ["copy"]);
|
|
assert_eq!(probe.copy_cdict, cdict);
|
|
assert_eq!(probe.copy_params, params);
|
|
assert_eq!(probe.copy_pledged_src_size, pledged_src_size);
|
|
assert_eq!(probe.copy_zbuff, zbuff);
|
|
assert!(probe.attach_cdict.is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn reset_using_cdict_dedicated_search_overrides_force_copy() {
|
|
let mut probe = ResetUsingCDictProbe::default();
|
|
let strategy = 1;
|
|
let dedicated_dict_search = 1;
|
|
let attach_dict_pref = 2;
|
|
let force_window = 1;
|
|
let pledged_src_size = u64::MAX;
|
|
let zbuff = 9;
|
|
let state = reset_using_cdict_test_state(
|
|
&mut probe,
|
|
0x4000usize as *const c_void,
|
|
0x3000usize as *const c_void,
|
|
&strategy,
|
|
&dedicated_dict_search,
|
|
&attach_dict_pref,
|
|
&force_window,
|
|
&pledged_src_size,
|
|
&zbuff,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_resetCCtxUsingCDict(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.events, ["attach"]);
|
|
}
|
|
|
|
unsafe fn cctx_policy_probe(context: *mut c_void) -> &'static mut CctxPolicyProbe {
|
|
unsafe { &mut *context.cast::<CctxPolicyProbe>() }
|
|
}
|
|
|
|
unsafe extern "C" fn policy_clear(context: *mut c_void) {
|
|
unsafe { cctx_policy_probe(context) }.events.push(3);
|
|
}
|
|
|
|
unsafe extern "C" fn policy_assign_local(
|
|
context: *mut c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_load_method: c_int,
|
|
dict_content_type: c_int,
|
|
) -> usize {
|
|
let probe = unsafe { cctx_policy_probe(context) };
|
|
probe.events.push(4);
|
|
probe.local_dict = dict;
|
|
probe.local_dict_size = dict_size;
|
|
probe.local_dict_load_method = dict_load_method;
|
|
probe.local_dict_content_type = dict_content_type;
|
|
probe.local_result
|
|
}
|
|
|
|
unsafe extern "C" fn policy_assign_cdict(context: *mut c_void, cdict: *const c_void) {
|
|
let probe = unsafe { cctx_policy_probe(context) };
|
|
probe.events.push(5);
|
|
probe.cdict = cdict;
|
|
}
|
|
|
|
unsafe extern "C" fn policy_assign_prefix(
|
|
context: *mut c_void,
|
|
prefix: *const c_void,
|
|
prefix_size: usize,
|
|
dict_content_type: c_int,
|
|
) {
|
|
let probe = unsafe { cctx_policy_probe(context) };
|
|
probe.events.push(6);
|
|
probe.prefix = prefix;
|
|
probe.prefix_size = prefix_size;
|
|
probe.prefix_content_type = dict_content_type;
|
|
}
|
|
|
|
unsafe extern "C" fn record_dictionary_load(
|
|
match_state: *mut c_void,
|
|
ldm_state: *mut c_void,
|
|
_workspace_state: *mut c_void,
|
|
_params: *const c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dtlm: c_int,
|
|
tfp: c_int,
|
|
) -> usize {
|
|
let probe = unsafe { &mut *match_state.cast::<DictionaryLoadProbe>() };
|
|
probe.calls += 1;
|
|
probe.ldm_state = ldm_state;
|
|
probe.dict = dict;
|
|
probe.dict_size = dict_size;
|
|
probe.dtlm = dtlm;
|
|
probe.tfp = tfp;
|
|
37
|
|
}
|
|
|
|
unsafe fn dispatch_for_test(
|
|
state: &mut ZSTD_compressedBlockState_t,
|
|
probe: &mut DictionaryLoadProbe,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_content_type: c_int,
|
|
workspace: *mut c_void,
|
|
no_dict_id_flag: c_int,
|
|
ldm_state: *mut c_void,
|
|
) -> usize {
|
|
unsafe {
|
|
ZSTD_rust_compressInsertDictionary(
|
|
state,
|
|
(probe as *mut DictionaryLoadProbe).cast(),
|
|
ldm_state,
|
|
ptr::null_mut(),
|
|
ptr::null(),
|
|
dict,
|
|
dict_size,
|
|
dict_content_type,
|
|
1,
|
|
2,
|
|
workspace,
|
|
no_dict_id_flag,
|
|
record_dictionary_load,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_dictionary_policy_checks_stage_and_orders_callbacks() {
|
|
let mut probe = CctxPolicyProbe::default();
|
|
let dictionary = [1u8, 2, 3];
|
|
let context = (&mut probe as *mut CctxPolicyProbe).cast::<c_void>();
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_CCtx_loadDictionaryAdvanced(
|
|
context,
|
|
1,
|
|
dictionary.as_ptr().cast(),
|
|
dictionary.len(),
|
|
ZSTD_DLM_BY_REF,
|
|
ZSTD_DCT_RAW_CONTENT,
|
|
policy_clear,
|
|
policy_assign_local,
|
|
)
|
|
};
|
|
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
|
|
assert!(probe.events.is_empty());
|
|
|
|
probe.local_result = ERROR(ZstdErrorCode::MemoryAllocation);
|
|
let result = unsafe {
|
|
ZSTD_rust_CCtx_loadDictionaryAdvanced(
|
|
context,
|
|
ZSTD_CCTX_INIT_STAGE,
|
|
dictionary.as_ptr().cast(),
|
|
dictionary.len(),
|
|
ZSTD_DLM_BY_REF,
|
|
ZSTD_DCT_RAW_CONTENT,
|
|
policy_clear,
|
|
policy_assign_local,
|
|
)
|
|
};
|
|
assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation));
|
|
assert_eq!(probe.events, [3, 4]);
|
|
assert_eq!(probe.local_dict, dictionary.as_ptr().cast());
|
|
assert_eq!(probe.local_dict_size, dictionary.len());
|
|
assert_eq!(probe.local_dict_load_method, ZSTD_DLM_BY_REF);
|
|
assert_eq!(probe.local_dict_content_type, ZSTD_DCT_RAW_CONTENT);
|
|
}
|
|
|
|
#[test]
|
|
fn cctx_dictionary_policy_clears_before_cdict_and_prefix_assignment() {
|
|
let mut probe = CctxPolicyProbe::default();
|
|
let cdict_marker = 17u8;
|
|
let prefix = [4u8, 5, 6];
|
|
let context = (&mut probe as *mut CctxPolicyProbe).cast::<c_void>();
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_CCtx_refCDict(
|
|
context,
|
|
ZSTD_CCTX_INIT_STAGE,
|
|
(&cdict_marker as *const u8).cast(),
|
|
policy_clear,
|
|
policy_assign_cdict,
|
|
)
|
|
};
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.events, [3, 5]);
|
|
assert_eq!(probe.cdict, (&cdict_marker as *const u8).cast());
|
|
|
|
probe.events.clear();
|
|
let result = unsafe {
|
|
ZSTD_rust_CCtx_refPrefixAdvanced(
|
|
context,
|
|
ZSTD_CCTX_INIT_STAGE,
|
|
prefix.as_ptr().cast(),
|
|
prefix.len(),
|
|
ZSTD_DCT_FULL_DICT,
|
|
policy_clear,
|
|
policy_assign_prefix,
|
|
)
|
|
};
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.events, [3, 6]);
|
|
assert_eq!(probe.prefix, prefix.as_ptr().cast());
|
|
assert_eq!(probe.prefix_size, prefix.len());
|
|
assert_eq!(probe.prefix_content_type, ZSTD_DCT_FULL_DICT);
|
|
|
|
probe.events.clear();
|
|
let result = unsafe {
|
|
ZSTD_rust_CCtx_refPrefixAdvanced(
|
|
context,
|
|
ZSTD_CCTX_INIT_STAGE,
|
|
ptr::null(),
|
|
0,
|
|
ZSTD_DCT_AUTO,
|
|
policy_clear,
|
|
policy_assign_prefix,
|
|
)
|
|
};
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.events, [3]);
|
|
}
|
|
|
|
#[test]
|
|
fn cdict_query_projection_returns_private_snapshots() {
|
|
let cparams = ZSTD_compressionParameters {
|
|
windowLog: 17,
|
|
chainLog: 12,
|
|
hashLog: 13,
|
|
searchLog: 1,
|
|
minMatch: 4,
|
|
targetLength: 16,
|
|
strategy: 3,
|
|
};
|
|
let dict_id = 0x1234_5678;
|
|
let state = ZSTD_rust_cdictQueryState {
|
|
c_params: &cparams,
|
|
dict_id: &dict_id,
|
|
};
|
|
|
|
assert_eq!(unsafe { ZSTD_rust_getCParamsFromCDict(&state) }, cparams);
|
|
assert_eq!(unsafe { ZSTD_rust_getDictIDFromCDict(&state) }, dict_id);
|
|
}
|
|
|
|
#[test]
|
|
fn cdict_query_projection_handles_missing_snapshots() {
|
|
let state = ZSTD_rust_cdictQueryState {
|
|
c_params: ptr::null(),
|
|
dict_id: ptr::null(),
|
|
};
|
|
|
|
assert_eq!(
|
|
unsafe { ZSTD_rust_getCParamsFromCDict(&state) },
|
|
ZSTD_compressionParameters::default()
|
|
);
|
|
assert_eq!(unsafe { ZSTD_rust_getDictIDFromCDict(&state) }, 0);
|
|
assert_eq!(
|
|
unsafe { ZSTD_rust_getCParamsFromCDict(ptr::null()) },
|
|
ZSTD_compressionParameters::default()
|
|
);
|
|
assert_eq!(unsafe { ZSTD_rust_getDictIDFromCDict(ptr::null()) }, 0);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CdictBeginProbe {
|
|
events: Vec<&'static str>,
|
|
initialized: Option<ZSTD_parameters>,
|
|
compression_level: c_int,
|
|
min_window_logs: Vec<c_uint>,
|
|
begin_result: usize,
|
|
}
|
|
|
|
unsafe fn cdict_begin_probe(context: *mut c_void) -> &'static mut CdictBeginProbe {
|
|
unsafe { &mut *context.cast::<CdictBeginProbe>() }
|
|
}
|
|
|
|
unsafe extern "C" fn cdict_begin_init(
|
|
context: *mut c_void,
|
|
params: *const ZSTD_parameters,
|
|
compression_level: c_int,
|
|
) {
|
|
let probe = unsafe { cdict_begin_probe(context) };
|
|
probe.events.push("init");
|
|
probe.initialized = Some(unsafe { *params });
|
|
probe.compression_level = compression_level;
|
|
}
|
|
|
|
unsafe extern "C" fn cdict_begin_adjust_window(context: *mut c_void, min_window_log: c_uint) {
|
|
let probe = unsafe { cdict_begin_probe(context) };
|
|
probe.events.push("adjust");
|
|
probe.min_window_logs.push(min_window_log);
|
|
}
|
|
|
|
unsafe extern "C" fn cdict_begin_begin(
|
|
context: *mut c_void,
|
|
_cdict: *const c_void,
|
|
_cctx_params: *const c_void,
|
|
_pledged_src_size: u64,
|
|
) -> usize {
|
|
let probe = unsafe { cdict_begin_probe(context) };
|
|
probe.events.push("begin");
|
|
probe.begin_result
|
|
}
|
|
|
|
fn cdict_begin_test_state(
|
|
probe: &mut CdictBeginProbe,
|
|
cdict: *const c_void,
|
|
cdict_cparams: &ZSTD_compressionParameters,
|
|
cdict_content_size: &usize,
|
|
cdict_compression_level: &c_int,
|
|
f_params: &ZSTD_frameParameters,
|
|
pledged_src_size: &u64,
|
|
exclusion_mask: &c_uint,
|
|
) -> ZSTD_rust_compressBeginUsingCDictState {
|
|
ZSTD_rust_compressBeginUsingCDictState {
|
|
cctx: (probe as *mut CdictBeginProbe).cast(),
|
|
cdict,
|
|
cctx_params: (probe as *mut CdictBeginProbe).cast(),
|
|
cdict_cparams,
|
|
cdict_content_size,
|
|
cdict_compression_level,
|
|
f_params,
|
|
pledged_src_size,
|
|
exclusion_mask,
|
|
init_params: cdict_begin_init,
|
|
adjust_window: cdict_begin_adjust_window,
|
|
begin: cdict_begin_begin,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cdict_begin_rejects_a_null_dictionary_before_reading_state() {
|
|
let state = ZSTD_rust_compressBeginUsingCDictState {
|
|
cctx: ptr::null_mut(),
|
|
cdict: ptr::null(),
|
|
cctx_params: ptr::null_mut(),
|
|
cdict_cparams: ptr::null(),
|
|
cdict_content_size: ptr::null(),
|
|
cdict_compression_level: ptr::null(),
|
|
f_params: ptr::null(),
|
|
pledged_src_size: ptr::null(),
|
|
exclusion_mask: ptr::null(),
|
|
init_params: cdict_begin_init,
|
|
adjust_window: cdict_begin_adjust_window,
|
|
begin: cdict_begin_begin,
|
|
};
|
|
|
|
let result = unsafe { ZSTD_rust_compressBeginUsingCDict(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DictionaryWrong));
|
|
}
|
|
|
|
#[test]
|
|
fn cdict_begin_reuses_cdict_params_below_the_source_cutoff() {
|
|
let mut probe = CdictBeginProbe::default();
|
|
let cdict_cparams = ZSTD_compressionParameters {
|
|
windowLog: 17,
|
|
chainLog: 12,
|
|
hashLog: 13,
|
|
searchLog: 1,
|
|
minMatch: 4,
|
|
targetLength: 16,
|
|
strategy: 3,
|
|
};
|
|
let cdict_content_size = 1usize;
|
|
let cdict_compression_level = 3;
|
|
let f_params = ZSTD_frameParameters::default();
|
|
let pledged_src_size = CDICT_PARAMS_SRC_SIZE_CUTOFF - 1;
|
|
let exclusion_mask = 0;
|
|
let state = cdict_begin_test_state(
|
|
&mut probe,
|
|
ptr::dangling(),
|
|
&cdict_cparams,
|
|
&cdict_content_size,
|
|
&cdict_compression_level,
|
|
&f_params,
|
|
&pledged_src_size,
|
|
&exclusion_mask,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_compressBeginUsingCDict(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.events, ["init", "adjust", "begin"]);
|
|
assert_eq!(probe.initialized.unwrap().cParams, cdict_cparams);
|
|
assert_eq!(probe.compression_level, cdict_compression_level);
|
|
assert_eq!(probe.min_window_logs, [17]);
|
|
}
|
|
|
|
#[test]
|
|
fn cdict_begin_reuses_cdict_params_for_unknown_source_without_adjustment() {
|
|
let mut probe = CdictBeginProbe::default();
|
|
let cdict_cparams = ZSTD_compressionParameters {
|
|
windowLog: 17,
|
|
chainLog: 12,
|
|
hashLog: 13,
|
|
searchLog: 1,
|
|
minMatch: 4,
|
|
targetLength: 16,
|
|
strategy: 3,
|
|
};
|
|
let cdict_content_size = 1usize;
|
|
let cdict_compression_level = 3;
|
|
let f_params = ZSTD_frameParameters::default();
|
|
let pledged_src_size = ZSTD_CONTENTSIZE_UNKNOWN;
|
|
let exclusion_mask = 0;
|
|
let state = cdict_begin_test_state(
|
|
&mut probe,
|
|
ptr::dangling(),
|
|
&cdict_cparams,
|
|
&cdict_content_size,
|
|
&cdict_compression_level,
|
|
&f_params,
|
|
&pledged_src_size,
|
|
&exclusion_mask,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_compressBeginUsingCDict(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.events, ["init", "begin"]);
|
|
assert_eq!(probe.initialized.unwrap().cParams, cdict_cparams);
|
|
assert!(probe.min_window_logs.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn cdict_begin_reuses_cdict_params_for_dictionary_ratio_or_zero_level() {
|
|
let cdict_cparams = ZSTD_compressionParameters {
|
|
windowLog: 10,
|
|
chainLog: 6,
|
|
hashLog: 6,
|
|
searchLog: 1,
|
|
minMatch: 3,
|
|
targetLength: 0,
|
|
strategy: 1,
|
|
};
|
|
let cdict_content_size = 64 * 1024usize;
|
|
let f_params = ZSTD_frameParameters::default();
|
|
let exclusion_mask = 0;
|
|
|
|
let mut ratio_probe = CdictBeginProbe::default();
|
|
let ratio_compression_level = 3;
|
|
let ratio_pledged_src_size = 256 * 1024;
|
|
let ratio_state = cdict_begin_test_state(
|
|
&mut ratio_probe,
|
|
ptr::dangling(),
|
|
&cdict_cparams,
|
|
&cdict_content_size,
|
|
&ratio_compression_level,
|
|
&f_params,
|
|
&ratio_pledged_src_size,
|
|
&exclusion_mask,
|
|
);
|
|
let ratio_result = unsafe { ZSTD_rust_compressBeginUsingCDict(&ratio_state) };
|
|
|
|
assert_eq!(ratio_result, 0);
|
|
assert_eq!(ratio_probe.initialized.unwrap().cParams, cdict_cparams);
|
|
|
|
let mut zero_level_probe = CdictBeginProbe::default();
|
|
let zero_level_compression_level = 0;
|
|
let zero_level_pledged_src_size = 1 << 20;
|
|
let zero_level_state = cdict_begin_test_state(
|
|
&mut zero_level_probe,
|
|
ptr::dangling(),
|
|
&cdict_cparams,
|
|
&cdict_content_size,
|
|
&zero_level_compression_level,
|
|
&f_params,
|
|
&zero_level_pledged_src_size,
|
|
&exclusion_mask,
|
|
);
|
|
let zero_level_result = unsafe { ZSTD_rust_compressBeginUsingCDict(&zero_level_state) };
|
|
|
|
assert_eq!(zero_level_result, 0);
|
|
assert_eq!(zero_level_probe.initialized.unwrap().cParams, cdict_cparams);
|
|
}
|
|
|
|
#[test]
|
|
fn cdict_begin_selects_public_params_at_the_source_cutoff() {
|
|
let mut probe = CdictBeginProbe::default();
|
|
let cdict_cparams = ZSTD_compressionParameters {
|
|
windowLog: 10,
|
|
chainLog: 6,
|
|
hashLog: 6,
|
|
searchLog: 1,
|
|
minMatch: 3,
|
|
targetLength: 0,
|
|
strategy: 1,
|
|
};
|
|
let cdict_content_size = 1usize;
|
|
let cdict_compression_level = 3;
|
|
let f_params = ZSTD_frameParameters::default();
|
|
let pledged_src_size = CDICT_PARAMS_SRC_SIZE_CUTOFF;
|
|
let exclusion_mask = 0;
|
|
let state = cdict_begin_test_state(
|
|
&mut probe,
|
|
ptr::dangling(),
|
|
&cdict_cparams,
|
|
&cdict_content_size,
|
|
&cdict_compression_level,
|
|
&f_params,
|
|
&pledged_src_size,
|
|
&exclusion_mask,
|
|
);
|
|
let expected_cparams = ZSTD_rust_params_getCParams(
|
|
cdict_compression_level,
|
|
pledged_src_size,
|
|
cdict_content_size,
|
|
exclusion_mask,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_compressBeginUsingCDict(&state) };
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.initialized.unwrap().cParams, expected_cparams);
|
|
assert_eq!(probe.events, ["init", "adjust", "begin"]);
|
|
}
|
|
|
|
#[test]
|
|
fn cdict_begin_preserves_order_and_begin_errors_for_large_sources() {
|
|
let mut probe = CdictBeginProbe {
|
|
begin_result: ERROR(ZstdErrorCode::DstSizeTooSmall),
|
|
..Default::default()
|
|
};
|
|
let cdict_cparams = ZSTD_compressionParameters {
|
|
windowLog: 10,
|
|
chainLog: 6,
|
|
hashLog: 6,
|
|
searchLog: 1,
|
|
minMatch: 3,
|
|
targetLength: 0,
|
|
strategy: 1,
|
|
};
|
|
let cdict_content_size = 1usize;
|
|
let cdict_compression_level = 3;
|
|
let f_params = ZSTD_frameParameters::default();
|
|
let pledged_src_size = 1 << 20;
|
|
let exclusion_mask = 0;
|
|
let state = cdict_begin_test_state(
|
|
&mut probe,
|
|
ptr::dangling(),
|
|
&cdict_cparams,
|
|
&cdict_content_size,
|
|
&cdict_compression_level,
|
|
&f_params,
|
|
&pledged_src_size,
|
|
&exclusion_mask,
|
|
);
|
|
|
|
let result = unsafe { ZSTD_rust_compressBeginUsingCDict(&state) };
|
|
|
|
assert_eq!(result, probe.begin_result);
|
|
assert_eq!(probe.events, ["init", "adjust", "begin"]);
|
|
assert_eq!(probe.min_window_logs, [19]);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CompressUsingCDictProbe {
|
|
events: Vec<&'static str>,
|
|
cdict: *const c_void,
|
|
frame_params: ZSTD_frameParameters,
|
|
pledged_src_size: u64,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
begin_result: usize,
|
|
end_result: usize,
|
|
}
|
|
|
|
unsafe extern "C" fn compress_using_cdict_test_begin(
|
|
context: *mut c_void,
|
|
cdict: *const c_void,
|
|
frame_params: *const ZSTD_frameParameters,
|
|
pledged_src_size: u64,
|
|
) -> usize {
|
|
let probe = unsafe { &mut *context.cast::<CompressUsingCDictProbe>() };
|
|
probe.events.push("begin");
|
|
probe.cdict = cdict;
|
|
probe.frame_params = unsafe { *frame_params };
|
|
probe.pledged_src_size = pledged_src_size;
|
|
probe.begin_result
|
|
}
|
|
|
|
unsafe extern "C" fn compress_using_cdict_test_end(
|
|
context: *mut c_void,
|
|
dst: *mut c_void,
|
|
dst_capacity: usize,
|
|
src: *const c_void,
|
|
src_size: usize,
|
|
) -> usize {
|
|
let probe = unsafe { &mut *context.cast::<CompressUsingCDictProbe>() };
|
|
probe.events.push("end");
|
|
probe.dst = dst;
|
|
probe.dst_capacity = dst_capacity;
|
|
probe.src = src;
|
|
probe.src_size = src_size;
|
|
probe.end_result
|
|
}
|
|
|
|
fn compress_using_cdict_test_state(
|
|
probe: &mut CompressUsingCDictProbe,
|
|
cdict: *const c_void,
|
|
) -> ZSTD_rust_compressUsingCDictState {
|
|
ZSTD_rust_compressUsingCDictState {
|
|
callback_context: (probe as *mut CompressUsingCDictProbe).cast(),
|
|
cdict,
|
|
begin: compress_using_cdict_test_begin,
|
|
end: compress_using_cdict_test_end,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_using_cdict_preserves_frame_policy_and_begin_end_order() {
|
|
let mut probe = CompressUsingCDictProbe {
|
|
end_result: 37,
|
|
..Default::default()
|
|
};
|
|
let cdict = 0x4000usize as *const c_void;
|
|
let state = compress_using_cdict_test_state(&mut probe, cdict);
|
|
let mut dst = [0u8; 8];
|
|
let src = [1u8, 2, 3];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressUsingCDict(
|
|
&state,
|
|
dst.as_mut_ptr().cast(),
|
|
dst.len(),
|
|
src.as_ptr().cast(),
|
|
src.len(),
|
|
)
|
|
};
|
|
|
|
assert_eq!(result, probe.end_result);
|
|
assert_eq!(probe.events, ["begin", "end"]);
|
|
assert_eq!(probe.cdict, cdict);
|
|
assert_eq!(probe.frame_params.contentSizeFlag, 1);
|
|
assert_eq!(probe.frame_params.checksumFlag, 0);
|
|
assert_eq!(probe.frame_params.noDictIDFlag, 0);
|
|
assert_eq!(probe.pledged_src_size, src.len() as u64);
|
|
assert_eq!(probe.dst, dst.as_mut_ptr().cast());
|
|
assert_eq!(probe.dst_capacity, dst.len());
|
|
assert_eq!(probe.src, src.as_ptr().cast());
|
|
assert_eq!(probe.src_size, src.len());
|
|
}
|
|
|
|
#[test]
|
|
fn compress_using_cdict_stops_before_end_after_begin_error() {
|
|
let mut probe = CompressUsingCDictProbe {
|
|
begin_result: ERROR(ZstdErrorCode::MemoryAllocation),
|
|
..Default::default()
|
|
};
|
|
let state = compress_using_cdict_test_state(&mut probe, ptr::dangling());
|
|
|
|
let result =
|
|
unsafe { ZSTD_rust_compressUsingCDict(&state, ptr::null_mut(), 0, ptr::null(), 0) };
|
|
|
|
assert_eq!(result, probe.begin_result);
|
|
assert_eq!(probe.events, ["begin"]);
|
|
}
|
|
|
|
fn compress_begin_using_cdict_public_test_state(
|
|
probe: &mut CompressUsingCDictProbe,
|
|
cdict: *const c_void,
|
|
) -> ZSTD_rust_compressBeginUsingCDictPublicState {
|
|
ZSTD_rust_compressBeginUsingCDictPublicState {
|
|
callback_context: (probe as *mut CompressUsingCDictProbe).cast(),
|
|
cdict,
|
|
begin: compress_using_cdict_test_begin,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_begin_using_cdict_public_preserves_fixed_frame_policy() {
|
|
let mut probe = CompressUsingCDictProbe {
|
|
begin_result: 43,
|
|
..Default::default()
|
|
};
|
|
let cdict = 0x5000usize as *const c_void;
|
|
let state = compress_begin_using_cdict_public_test_state(&mut probe, cdict);
|
|
|
|
let result = unsafe { ZSTD_rust_compressBeginUsingCDictPublic(&state) };
|
|
|
|
assert_eq!(result, probe.begin_result);
|
|
assert_eq!(probe.events, ["begin"]);
|
|
assert_eq!(probe.cdict, cdict);
|
|
assert_eq!(probe.frame_params.contentSizeFlag, 0);
|
|
assert_eq!(probe.frame_params.checksumFlag, 0);
|
|
assert_eq!(probe.frame_params.noDictIDFlag, 0);
|
|
assert_eq!(probe.pledged_src_size, ZSTD_CONTENTSIZE_UNKNOWN);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_begin_using_cdict_public_rejects_null_dictionary_before_callback() {
|
|
let mut probe = CompressUsingCDictProbe::default();
|
|
let state = compress_begin_using_cdict_public_test_state(&mut probe, ptr::null());
|
|
|
|
let result = unsafe { ZSTD_rust_compressBeginUsingCDictPublic(&state) };
|
|
|
|
assert_eq!(result, ERROR(ZstdErrorCode::DictionaryWrong));
|
|
assert!(probe.events.is_empty());
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CreateCDictProbe {
|
|
events: Vec<&'static str>,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_load_method: c_int,
|
|
dict_content_type: c_int,
|
|
cparams: ZSTD_compressionParameters,
|
|
cdict_result: *mut c_void,
|
|
published_cdict: *mut c_void,
|
|
compression_level: c_int,
|
|
}
|
|
|
|
unsafe extern "C" fn create_cdict_test_create(
|
|
context: *mut c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
dict_load_method: c_int,
|
|
dict_content_type: c_int,
|
|
cparams: *const ZSTD_compressionParameters,
|
|
) -> *mut c_void {
|
|
let probe = unsafe { &mut *context.cast::<CreateCDictProbe>() };
|
|
probe.events.push("create");
|
|
probe.dict = dict;
|
|
probe.dict_size = dict_size;
|
|
probe.dict_load_method = dict_load_method;
|
|
probe.dict_content_type = dict_content_type;
|
|
probe.cparams = unsafe { *cparams };
|
|
probe.cdict_result
|
|
}
|
|
|
|
unsafe extern "C" fn create_cdict_test_set_compression_level(
|
|
context: *mut c_void,
|
|
cdict: *mut c_void,
|
|
compression_level: c_int,
|
|
) {
|
|
let probe = unsafe { &mut *context.cast::<CreateCDictProbe>() };
|
|
probe.events.push("set");
|
|
probe.published_cdict = cdict;
|
|
probe.compression_level = compression_level;
|
|
}
|
|
|
|
fn create_cdict_test_state(
|
|
probe: &mut CreateCDictProbe,
|
|
exclusion_mask: &c_uint,
|
|
) -> ZSTD_rust_createCDictState {
|
|
ZSTD_rust_createCDictState {
|
|
callback_context: (probe as *mut CreateCDictProbe).cast(),
|
|
exclusion_mask,
|
|
create: create_cdict_test_create,
|
|
set_compression_level: create_cdict_test_set_compression_level,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn create_cdict_selects_params_and_normalizes_default_level() {
|
|
let mut probe = CreateCDictProbe {
|
|
cdict_result: ptr::dangling_mut(),
|
|
..Default::default()
|
|
};
|
|
let dict = [1u8, 2, 3, 4];
|
|
let exclusion_mask = 0;
|
|
let state = create_cdict_test_state(&mut probe, &exclusion_mask);
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_createCDict(&state, dict.as_ptr().cast(), dict.len(), 0, ZSTD_DLM_BY_REF)
|
|
};
|
|
|
|
let expected_cparams = ZSTD_rust_params_getParamsInternal(
|
|
0,
|
|
ZSTD_CONTENTSIZE_UNKNOWN,
|
|
dict.len(),
|
|
ZSTD_RUST_CPM_CREATE_CDICT,
|
|
exclusion_mask,
|
|
)
|
|
.cParams;
|
|
assert_eq!(result, probe.cdict_result);
|
|
assert_eq!(probe.events, ["create", "set"]);
|
|
assert_eq!(probe.dict, dict.as_ptr().cast());
|
|
assert_eq!(probe.dict_size, dict.len());
|
|
assert_eq!(probe.dict_load_method, ZSTD_DLM_BY_REF);
|
|
assert_eq!(probe.dict_content_type, ZSTD_DCT_AUTO);
|
|
assert_eq!(probe.cparams, expected_cparams);
|
|
assert_eq!(probe.published_cdict, probe.cdict_result);
|
|
assert_eq!(probe.compression_level, ZSTD_rust_params_defaultCLevel());
|
|
}
|
|
|
|
#[test]
|
|
fn create_cdict_stops_before_publication_after_creation_failure() {
|
|
let mut probe = CreateCDictProbe::default();
|
|
let exclusion_mask = 0;
|
|
let state = create_cdict_test_state(&mut probe, &exclusion_mask);
|
|
|
|
let result = unsafe { ZSTD_rust_createCDict(&state, ptr::null(), 0, 3, ZSTD_DLM_BY_REF) };
|
|
|
|
assert!(result.is_null());
|
|
assert_eq!(probe.events, ["create"]);
|
|
assert!(probe.published_cdict.is_null());
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct CompressBeginUsingDictProbe {
|
|
events: Vec<&'static str>,
|
|
params: ZSTD_parameters,
|
|
compression_level: c_int,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
cctx_params: *const c_void,
|
|
pledged_src_size: u64,
|
|
begin_result: usize,
|
|
}
|
|
|
|
unsafe extern "C" fn compress_begin_using_dict_test_init(
|
|
context: *mut c_void,
|
|
params: *const ZSTD_parameters,
|
|
compression_level: c_int,
|
|
) {
|
|
let probe = unsafe { &mut *context.cast::<CompressBeginUsingDictProbe>() };
|
|
probe.events.push("init");
|
|
probe.params = unsafe { *params };
|
|
probe.compression_level = compression_level;
|
|
}
|
|
|
|
unsafe extern "C" fn compress_begin_using_dict_test_begin(
|
|
context: *mut c_void,
|
|
dict: *const c_void,
|
|
dict_size: usize,
|
|
cctx_params: *const c_void,
|
|
pledged_src_size: u64,
|
|
) -> usize {
|
|
let probe = unsafe { &mut *context.cast::<CompressBeginUsingDictProbe>() };
|
|
probe.events.push("begin");
|
|
probe.dict = dict;
|
|
probe.dict_size = dict_size;
|
|
probe.cctx_params = cctx_params;
|
|
probe.pledged_src_size = pledged_src_size;
|
|
probe.begin_result
|
|
}
|
|
|
|
fn compress_begin_using_dict_test_state(
|
|
probe: &mut CompressBeginUsingDictProbe,
|
|
exclusion_mask: &c_uint,
|
|
) -> ZSTD_rust_compressBeginUsingDictState {
|
|
ZSTD_rust_compressBeginUsingDictState {
|
|
cctx: (probe as *mut CompressBeginUsingDictProbe).cast(),
|
|
cctx_params: (probe as *mut CompressBeginUsingDictProbe).cast(),
|
|
exclusion_mask,
|
|
init_params: compress_begin_using_dict_test_init,
|
|
begin: compress_begin_using_dict_test_begin,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compress_begin_using_dict_selects_unknown_source_params_and_default_level() {
|
|
let mut probe = CompressBeginUsingDictProbe::default();
|
|
let exclusion_mask = 0;
|
|
let state = compress_begin_using_dict_test_state(&mut probe, &exclusion_mask);
|
|
let dict = [1u8, 2, 3];
|
|
|
|
let result = unsafe {
|
|
ZSTD_rust_compressBeginUsingDict(&state, dict.as_ptr().cast(), dict.len(), 0)
|
|
};
|
|
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.events, ["init", "begin"]);
|
|
assert_eq!(
|
|
probe.params,
|
|
ZSTD_rust_params_getParamsInternal(
|
|
0,
|
|
ZSTD_CONTENTSIZE_UNKNOWN,
|
|
dict.len(),
|
|
ZSTD_RUST_CPM_NO_ATTACH_DICT,
|
|
exclusion_mask,
|
|
)
|
|
);
|
|
assert_eq!(probe.compression_level, ZSTD_rust_params_defaultCLevel());
|
|
assert_eq!(probe.dict, dict.as_ptr().cast());
|
|
assert_eq!(probe.dict_size, dict.len());
|
|
assert_eq!(
|
|
probe.cctx_params,
|
|
(&probe as *const CompressBeginUsingDictProbe).cast()
|
|
);
|
|
assert_eq!(probe.pledged_src_size, ZSTD_CONTENTSIZE_UNKNOWN);
|
|
}
|
|
|
|
#[test]
|
|
fn compress_begin_using_dict_propagates_begin_errors_after_initialization() {
|
|
let mut probe = CompressBeginUsingDictProbe {
|
|
begin_result: ERROR(ZstdErrorCode::StageWrong),
|
|
..Default::default()
|
|
};
|
|
let exclusion_mask = 0;
|
|
let state = compress_begin_using_dict_test_state(&mut probe, &exclusion_mask);
|
|
|
|
let result = unsafe { ZSTD_rust_compressBeginUsingDict(&state, ptr::null(), 0, 3) };
|
|
|
|
assert_eq!(result, probe.begin_result);
|
|
assert_eq!(probe.events, ["init", "begin"]);
|
|
}
|
|
|
|
fn assert_dictionary_corrupted(result: usize) {
|
|
assert!(ERR_isError(result));
|
|
assert_eq!(
|
|
ERR_getErrorCode(result),
|
|
ZstdErrorCode::DictionaryCorrupted as i32
|
|
);
|
|
}
|
|
|
|
unsafe fn write_fse_header(
|
|
dictionary: &mut [u8],
|
|
offset: &mut usize,
|
|
max_symbol_value: usize,
|
|
table_log: u32,
|
|
zero_weight: bool,
|
|
) {
|
|
let mut counts = vec![1u32; max_symbol_value + 1];
|
|
if zero_weight {
|
|
counts[0] = 0;
|
|
}
|
|
let total = counts.iter().map(|&count| count as usize).sum();
|
|
let mut normalized = vec![0i16; max_symbol_value + 1];
|
|
let normalized_log = FSE_normalizeCount(
|
|
normalized.as_mut_ptr(),
|
|
table_log,
|
|
counts.as_ptr(),
|
|
total,
|
|
max_symbol_value as c_uint,
|
|
1,
|
|
);
|
|
assert!(!ERR_isError(normalized_log));
|
|
let header_size = FSE_writeNCount(
|
|
dictionary.as_mut_ptr().add(*offset).cast(),
|
|
dictionary.len() - *offset,
|
|
normalized.as_ptr(),
|
|
max_symbol_value as c_uint,
|
|
normalized_log as c_uint,
|
|
);
|
|
assert!(!ERR_isError(header_size));
|
|
*offset += header_size;
|
|
}
|
|
|
|
fn make_dictionary(zero_weight: ZeroWeight) -> Vec<u8> {
|
|
let mut dictionary = vec![0u8; 2048];
|
|
unsafe {
|
|
MEM_writeLE32(dictionary.as_mut_ptr().cast(), 0xEC30_A437);
|
|
MEM_writeLE32(dictionary.as_mut_ptr().add(4).cast(), 1234);
|
|
}
|
|
let mut offset = DICTIONARY_ID_AND_MAGIC_SIZE;
|
|
|
|
let mut huf_counts = std::array::from_fn::<_, 256, _>(|symbol| (symbol + 1) as u32);
|
|
if matches!(zero_weight, ZeroWeight::Huffman) {
|
|
huf_counts[0] = 0;
|
|
}
|
|
let mut huf_table = [0usize; 257];
|
|
let mut workspace = [0u32; HUF_WORKSPACE_SIZE / size_of::<u32>()];
|
|
let huff_log = unsafe {
|
|
HUF_buildCTable_wksp(
|
|
huf_table.as_mut_ptr(),
|
|
huf_counts.as_ptr(),
|
|
255,
|
|
11,
|
|
workspace.as_mut_ptr().cast(),
|
|
size_of::<[u32; HUF_WORKSPACE_SIZE / size_of::<u32>()]>(),
|
|
)
|
|
};
|
|
assert!(!ERR_isError(huff_log));
|
|
let huf_header_size = unsafe {
|
|
HUF_writeCTable_wksp(
|
|
dictionary.as_mut_ptr().add(offset).cast(),
|
|
dictionary.len() - offset,
|
|
huf_table.as_ptr(),
|
|
255,
|
|
huff_log as c_uint,
|
|
workspace.as_mut_ptr().cast(),
|
|
size_of::<[u32; HUF_WORKSPACE_SIZE / size_of::<u32>()]>(),
|
|
)
|
|
};
|
|
assert!(!ERR_isError(huf_header_size));
|
|
offset += huf_header_size;
|
|
|
|
unsafe {
|
|
write_fse_header(
|
|
&mut dictionary,
|
|
&mut offset,
|
|
MAX_OFF,
|
|
OFF_FSE_LOG as u32,
|
|
matches!(zero_weight, ZeroWeight::Offset),
|
|
);
|
|
write_fse_header(
|
|
&mut dictionary,
|
|
&mut offset,
|
|
MAX_ML,
|
|
ML_FSE_LOG as u32,
|
|
matches!(zero_weight, ZeroWeight::MatchLength),
|
|
);
|
|
write_fse_header(
|
|
&mut dictionary,
|
|
&mut offset,
|
|
MAX_LL,
|
|
LL_FSE_LOG as u32,
|
|
matches!(zero_weight, ZeroWeight::LiteralLength),
|
|
);
|
|
MEM_writeLE32(dictionary.as_mut_ptr().add(offset).cast(), 1);
|
|
MEM_writeLE32(dictionary.as_mut_ptr().add(offset + 4).cast(), 4);
|
|
MEM_writeLE32(dictionary.as_mut_ptr().add(offset + 8).cast(), 8);
|
|
}
|
|
offset += REPCODE_SECTION_SIZE;
|
|
dictionary[offset..offset + DICT_CONTENT_SIZE].fill(0xA5);
|
|
offset += DICT_CONTENT_SIZE;
|
|
dictionary.truncate(offset);
|
|
dictionary
|
|
}
|
|
|
|
fn load(dictionary: &[u8]) -> (usize, ZSTD_compressedBlockState_t) {
|
|
let mut state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
let mut workspace = [0u32; HUF_WORKSPACE_SIZE / size_of::<u32>()];
|
|
let result = unsafe {
|
|
ZSTD_rust_loadCEntropy(
|
|
&mut state,
|
|
workspace.as_mut_ptr().cast(),
|
|
dictionary.as_ptr().cast(),
|
|
dictionary.len(),
|
|
)
|
|
};
|
|
(result, state)
|
|
}
|
|
|
|
#[test]
|
|
fn valid_dictionary_loads_entropy_and_marks_repeat_tables_valid() {
|
|
let dictionary = make_dictionary(ZeroWeight::None);
|
|
let (header_size, state) = load(&dictionary);
|
|
assert_eq!(header_size, dictionary.len() - DICT_CONTENT_SIZE);
|
|
assert_eq!(state.rep, [1, 4, 8]);
|
|
assert_eq!(state.entropy.huf.repeatMode, HUF_REPEAT_VALID);
|
|
assert_eq!(state.entropy.fse.offcode_repeatMode, FSE_REPEAT_VALID);
|
|
assert_eq!(state.entropy.fse.matchlength_repeatMode, FSE_REPEAT_VALID);
|
|
assert_eq!(state.entropy.fse.litlength_repeatMode, FSE_REPEAT_VALID);
|
|
}
|
|
|
|
#[test]
|
|
fn zero_weights_select_check_repeat_modes() {
|
|
for zero_weight in [
|
|
ZeroWeight::Huffman,
|
|
ZeroWeight::Offset,
|
|
ZeroWeight::MatchLength,
|
|
ZeroWeight::LiteralLength,
|
|
] {
|
|
let dictionary = make_dictionary(zero_weight);
|
|
let (header_size, state) = load(&dictionary);
|
|
assert_eq!(header_size, dictionary.len() - DICT_CONTENT_SIZE);
|
|
match zero_weight {
|
|
ZeroWeight::Huffman => {
|
|
assert_eq!(state.entropy.huf.repeatMode, HUF_REPEAT_CHECK)
|
|
}
|
|
ZeroWeight::Offset => {
|
|
assert_eq!(state.entropy.fse.offcode_repeatMode, FSE_REPEAT_CHECK)
|
|
}
|
|
ZeroWeight::MatchLength => {
|
|
assert_eq!(state.entropy.fse.matchlength_repeatMode, FSE_REPEAT_CHECK)
|
|
}
|
|
ZeroWeight::LiteralLength => {
|
|
assert_eq!(state.entropy.fse.litlength_repeatMode, FSE_REPEAT_CHECK)
|
|
}
|
|
ZeroWeight::None => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn truncated_entropy_headers_are_rejected() {
|
|
let dictionary = make_dictionary(ZeroWeight::None);
|
|
let (header_size, _) = load(&dictionary);
|
|
for truncated_size in [0, 7, 8, header_size - 1] {
|
|
let truncated = &dictionary[..truncated_size];
|
|
let (result, _) = load(truncated);
|
|
assert_dictionary_corrupted(result);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn corrupt_repcode_header_is_rejected() {
|
|
let mut dictionary = make_dictionary(ZeroWeight::None);
|
|
let (header_size, _) = load(&dictionary);
|
|
let repcode_offset = header_size - REPCODE_SECTION_SIZE;
|
|
dictionary[repcode_offset..repcode_offset + 4].fill(0);
|
|
let (result, _) = load(&dictionary);
|
|
assert_dictionary_corrupted(result);
|
|
}
|
|
|
|
#[test]
|
|
fn short_or_missing_dictionary_preserves_state_and_full_mode_error() {
|
|
let mut state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
state.rep = [9, 10, 11];
|
|
let mut probe = DictionaryLoadProbe::default();
|
|
|
|
let result = unsafe {
|
|
dispatch_for_test(
|
|
&mut state,
|
|
&mut probe,
|
|
ptr::null(),
|
|
0,
|
|
ZSTD_DCT_AUTO,
|
|
ptr::null_mut(),
|
|
0,
|
|
ptr::null_mut(),
|
|
)
|
|
};
|
|
assert_eq!(result, 0);
|
|
assert_eq!(state.rep, [9, 10, 11]);
|
|
|
|
let short_dictionary = [0u8; DICTIONARY_ID_AND_MAGIC_SIZE - 1];
|
|
let result = unsafe {
|
|
dispatch_for_test(
|
|
&mut state,
|
|
&mut probe,
|
|
short_dictionary.as_ptr().cast(),
|
|
short_dictionary.len(),
|
|
ZSTD_DCT_FULL_DICT,
|
|
ptr::null_mut(),
|
|
0,
|
|
ptr::null_mut(),
|
|
)
|
|
};
|
|
assert!(ERR_isError(result));
|
|
assert_eq!(
|
|
ERR_getErrorCode(result),
|
|
ZstdErrorCode::DictionaryWrong as i32
|
|
);
|
|
assert_eq!(state.rep, [9, 10, 11]);
|
|
|
|
state.rep = [12, 13, 14];
|
|
let wrong_magic_dictionary = [0u8; DICTIONARY_ID_AND_MAGIC_SIZE];
|
|
let result = unsafe {
|
|
dispatch_for_test(
|
|
&mut state,
|
|
&mut probe,
|
|
wrong_magic_dictionary.as_ptr().cast(),
|
|
wrong_magic_dictionary.len(),
|
|
ZSTD_DCT_FULL_DICT,
|
|
ptr::null_mut(),
|
|
0,
|
|
ptr::null_mut(),
|
|
)
|
|
};
|
|
assert!(ERR_isError(result));
|
|
assert_eq!(
|
|
ERR_getErrorCode(result),
|
|
ZstdErrorCode::DictionaryWrong as i32
|
|
);
|
|
assert_eq!(state.rep, [1, 4, 8]);
|
|
assert_eq!(probe.calls, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn raw_and_auto_dictionary_modes_use_the_content_callback() {
|
|
let mut state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
let mut probe = DictionaryLoadProbe::default();
|
|
let raw_dictionary = [0xEC, 0x30, 0xA4, 0x37, 0, 0, 0, 0];
|
|
let result = unsafe {
|
|
dispatch_for_test(
|
|
&mut state,
|
|
&mut probe,
|
|
raw_dictionary.as_ptr().cast(),
|
|
raw_dictionary.len(),
|
|
ZSTD_DCT_RAW_CONTENT,
|
|
ptr::null_mut(),
|
|
0,
|
|
ptr::dangling_mut::<c_void>(),
|
|
)
|
|
};
|
|
assert_eq!(result, 37);
|
|
assert_eq!(probe.calls, 1);
|
|
assert_eq!(probe.dict, raw_dictionary.as_ptr().cast());
|
|
assert_eq!(probe.dict_size, raw_dictionary.len());
|
|
assert_eq!(probe.ldm_state, ptr::dangling_mut::<c_void>());
|
|
|
|
let auto_dictionary = [0u8; DICTIONARY_ID_AND_MAGIC_SIZE];
|
|
let result = unsafe {
|
|
dispatch_for_test(
|
|
&mut state,
|
|
&mut probe,
|
|
auto_dictionary.as_ptr().cast(),
|
|
auto_dictionary.len(),
|
|
ZSTD_DCT_AUTO,
|
|
ptr::null_mut(),
|
|
0,
|
|
ptr::dangling_mut::<c_void>(),
|
|
)
|
|
};
|
|
assert_eq!(result, 37);
|
|
assert_eq!(probe.calls, 2);
|
|
assert_eq!(probe.dict, auto_dictionary.as_ptr().cast());
|
|
assert_eq!(probe.dict_size, auto_dictionary.len());
|
|
}
|
|
|
|
#[test]
|
|
fn full_dictionary_loads_entropy_and_returns_or_suppresses_id() {
|
|
let dictionary = make_dictionary(ZeroWeight::None);
|
|
let mut workspace = [0u32; HUF_WORKSPACE_SIZE / size_of::<u32>()];
|
|
let mut state =
|
|
unsafe { MaybeUninit::<ZSTD_compressedBlockState_t>::zeroed().assume_init() };
|
|
let mut probe = DictionaryLoadProbe::default();
|
|
let result = unsafe {
|
|
dispatch_for_test(
|
|
&mut state,
|
|
&mut probe,
|
|
dictionary.as_ptr().cast(),
|
|
dictionary.len(),
|
|
ZSTD_DCT_FULL_DICT,
|
|
workspace.as_mut_ptr().cast(),
|
|
0,
|
|
ptr::dangling_mut::<c_void>(),
|
|
)
|
|
};
|
|
assert_eq!(result, 1234);
|
|
assert_eq!(probe.calls, 1);
|
|
assert_eq!(probe.ldm_state, ptr::null_mut());
|
|
assert_eq!(probe.dict_size, DICT_CONTENT_SIZE);
|
|
|
|
let result = unsafe {
|
|
dispatch_for_test(
|
|
&mut state,
|
|
&mut probe,
|
|
dictionary.as_ptr().cast(),
|
|
dictionary.len(),
|
|
ZSTD_DCT_FULL_DICT,
|
|
workspace.as_mut_ptr().cast(),
|
|
1,
|
|
ptr::dangling_mut::<c_void>(),
|
|
)
|
|
};
|
|
assert_eq!(result, 0);
|
|
assert_eq!(probe.calls, 2);
|
|
}
|
|
|
|
#[test]
|
|
fn dict_n_count_repeat_requires_coverage_and_nonzero_weights() {
|
|
let mut normalized = [1i16; 4];
|
|
assert_eq!(dict_n_count_repeat(&normalized, 3, 3), FSE_REPEAT_VALID);
|
|
normalized[2] = 0;
|
|
assert_eq!(dict_n_count_repeat(&normalized, 3, 3), FSE_REPEAT_CHECK);
|
|
assert_eq!(dict_n_count_repeat(&normalized, 2, 2), FSE_REPEAT_CHECK);
|
|
}
|
|
}
|