Files
zstd-rs/rust/src/zstd_compress_dictionary.rs
T
ddidderr 9f4d92808b feat(compress): move CDict constructor policy into Rust
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
2026-07-19 16:14:34 +02:00

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(), &params, 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(), &params, 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);
}
}