feat(compress): move context parameter policy into Rust

Move ZSTD_CCtx_setParameter stage authorization, supported-parameter
validation, static-context worker guard, and max-block side effect into
a Rust-owned ABI bridge while retaining C-owned context and parameter
storage. Preserve update authorization and error ordering.

Test Plan:
- cargo test --manifest-path rust/Cargo.toml --lib
- cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
- make -B -C programs -j1 zstd
- make -C tests -j1 test-zstream ZSTREAM_TESTTIME=-T1s
- focused set_parameter unit tests
This commit is contained in:
2026-07-19 13:48:06 +02:00
parent b2e7be480a
commit 245cb36f91
2 changed files with 328 additions and 69 deletions
+29 -68
View File
@@ -266,6 +266,27 @@ typedef char ZSTD_rust_set_parameters_using_cctx_params_state_layout[
&& sizeof(ZSTD_rust_setParametersUsingCCtxParamsState)
== 4 * sizeof(void*))
? 1 : -1];
typedef struct {
ZSTD_CCtx_params* requestedParams;
int streamStage;
int* cParamsChanged;
size_t staticSize;
unsigned* rustSimpleCompress2MaxBlockSizeSet;
} ZSTD_rust_setParameterState;
size_t ZSTD_rust_setParameter(const ZSTD_rust_setParameterState* state,
int param, int value);
typedef char ZSTD_rust_set_parameter_state_layout[
(offsetof(ZSTD_rust_setParameterState, requestedParams) == 0
&& offsetof(ZSTD_rust_setParameterState, streamStage) == sizeof(void*)
&& offsetof(ZSTD_rust_setParameterState, cParamsChanged)
== 2 * sizeof(void*)
&& offsetof(ZSTD_rust_setParameterState, staticSize)
== 3 * sizeof(void*)
&& offsetof(ZSTD_rust_setParameterState,
rustSimpleCompress2MaxBlockSizeSet)
== 4 * sizeof(void*)
&& sizeof(ZSTD_rust_setParameterState) == 5 * sizeof(void*))
? 1 : -1];
typedef void (*ZSTD_rust_resetCCtxClearAllDicts_f)(void* context);
typedef size_t (*ZSTD_rust_resetCCtxResetParams_f)(void* context);
typedef struct {
@@ -974,7 +995,6 @@ int ZSTD_rust_cctx_params_is_multithreaded(void);
int ZSTD_rust_cctx_params_nb_workers_max(void);
int ZSTD_rust_cctx_params_job_size_min(void);
int ZSTD_rust_cctx_params_job_size_max(void);
int ZSTD_rust_isUpdateAuthorized(int param);
ZSTD_CCtx_params* ZSTD_rust_createCCtxParams(ZSTD_customMem customMem);
size_t ZSTD_rust_freeCCtxParams(ZSTD_CCtx_params* params);
size_t ZSTD_rust_CCtxParams_init_advanced(ZSTD_CCtx_params* cctxParams,
@@ -1873,76 +1893,17 @@ int ZSTD_rust_cctx_params_job_size_max(void)
} while (0)
static int ZSTD_isUpdateAuthorized(ZSTD_cParameter param)
{
return ZSTD_rust_isUpdateAuthorized((int)param);
}
size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, int value)
{
ZSTD_rust_setParameterState state;
DEBUGLOG(4, "ZSTD_CCtx_setParameter (%i, %i)", (int)param, value);
if (cctx->streamStage != zcss_init) {
if (ZSTD_isUpdateAuthorized(param)) {
cctx->cParamsChanged = 1;
} else {
RETURN_ERROR(stage_wrong, "can only set params in cctx init stage");
} }
switch(param)
{
case ZSTD_c_nbWorkers:
RETURN_ERROR_IF((value!=0) && cctx->staticSize, parameter_unsupported,
"MT not compatible with static alloc");
break;
case ZSTD_c_compressionLevel:
case ZSTD_c_windowLog:
case ZSTD_c_hashLog:
case ZSTD_c_chainLog:
case ZSTD_c_searchLog:
case ZSTD_c_minMatch:
case ZSTD_c_targetLength:
case ZSTD_c_strategy:
case ZSTD_c_ldmHashRateLog:
case ZSTD_c_format:
case ZSTD_c_contentSizeFlag:
case ZSTD_c_checksumFlag:
case ZSTD_c_dictIDFlag:
case ZSTD_c_forceMaxWindow:
case ZSTD_c_forceAttachDict:
case ZSTD_c_literalCompressionMode:
case ZSTD_c_jobSize:
case ZSTD_c_overlapLog:
case ZSTD_c_rsyncable:
case ZSTD_c_enableDedicatedDictSearch:
case ZSTD_c_enableLongDistanceMatching:
case ZSTD_c_ldmHashLog:
case ZSTD_c_ldmMinMatch:
case ZSTD_c_ldmBucketSizeLog:
case ZSTD_c_targetCBlockSize:
case ZSTD_c_srcSizeHint:
case ZSTD_c_stableInBuffer:
case ZSTD_c_stableOutBuffer:
case ZSTD_c_blockDelimiters:
case ZSTD_c_validateSequences:
case ZSTD_c_splitAfterSequences:
case ZSTD_c_blockSplitterLevel:
case ZSTD_c_useRowMatchFinder:
case ZSTD_c_deterministicRefPrefix:
case ZSTD_c_prefetchCDictTables:
case ZSTD_c_enableSeqProducerFallback:
case ZSTD_c_maxBlockSize:
case ZSTD_c_repcodeResolution:
break;
default: RETURN_ERROR(parameter_unsupported, "unknown parameter");
}
{ size_t const result = ZSTD_CCtxParams_setParameter(&cctx->requestedParams, param, value);
if (!ZSTD_isError(result) && param == ZSTD_c_maxBlockSize) {
cctx->rustSimpleCompress2MaxBlockSizeSet = 1;
}
return result;
}
state.requestedParams = &cctx->requestedParams;
state.streamStage = (int)cctx->streamStage;
state.cParamsChanged = &cctx->cParamsChanged;
state.staticSize = cctx->staticSize;
state.rustSimpleCompress2MaxBlockSizeSet =
&cctx->rustSimpleCompress2MaxBlockSizeSet;
return ZSTD_rust_setParameter(&state, (int)param, value);
}
size_t ZSTD_CCtx_getParameter(ZSTD_CCtx const* cctx, ZSTD_cParameter param, int* value)
+299 -1
View File
@@ -27,7 +27,9 @@ use crate::zstd_compress_params::{
ZSTD_rust_params_adjustCParams, ZSTD_rust_params_maxNbSeq, ZSTD_rust_params_selectCParams,
ZSTD_RUST_CPM_NO_ATTACH_DICT, ZSTD_RUST_PS_AUTO, ZSTD_RUST_PS_DISABLE,
};
use crate::zstd_compress_params_api::ZSTD_CCtx_params;
use crate::zstd_compress_params_api::{
ZSTD_CCtxParams_setParameter, ZSTD_CCtx_params, ZSTD_rust_isUpdateAuthorized,
};
use crate::zstd_compress_sequences::SeqDef;
use crate::zstd_compress_stats::{
update_rep, SeqCollector, SeqStore_t, ZSTD_Sequence, ZSTD_SequencePosition,
@@ -121,9 +123,38 @@ const ZSTD_C_SEARCH_LOG: c_int = 104;
const ZSTD_C_MIN_MATCH: c_int = 105;
const ZSTD_C_TARGET_LENGTH: c_int = 106;
const ZSTD_C_STRATEGY: c_int = 107;
const ZSTD_C_FORMAT: c_int = 10;
const ZSTD_C_COMPRESSION_LEVEL: c_int = 100;
const ZSTD_C_TARGET_C_BLOCK_SIZE: c_int = 130;
const ZSTD_C_ENABLE_LDM: c_int = 160;
const ZSTD_C_LDM_HASH_LOG: c_int = 161;
const ZSTD_C_LDM_MIN_MATCH: c_int = 162;
const ZSTD_C_LDM_BUCKET_SIZE_LOG: c_int = 163;
const ZSTD_C_LDM_HASH_RATE_LOG: c_int = 164;
const ZSTD_C_CONTENT_SIZE_FLAG: c_int = 200;
const ZSTD_C_CHECKSUM_FLAG: c_int = 201;
const ZSTD_C_DICT_ID_FLAG: c_int = 202;
const ZSTD_C_NB_WORKERS: c_int = 400;
const ZSTD_C_JOB_SIZE: c_int = 401;
const ZSTD_C_OVERLAP_LOG: c_int = 402;
const ZSTD_C_RSYNCABLE: c_int = 500;
const ZSTD_C_FORCE_MAX_WINDOW: c_int = 1000;
const ZSTD_C_FORCE_ATTACH_DICT: c_int = 1001;
const ZSTD_C_LITERAL_COMPRESSION_MODE: c_int = 1002;
const ZSTD_C_SRC_SIZE_HINT: c_int = 1004;
const ZSTD_C_ENABLE_DEDICATED_DICT_SEARCH: c_int = 1005;
const ZSTD_C_STABLE_IN_BUFFER: c_int = 1006;
const ZSTD_C_STABLE_OUT_BUFFER: c_int = 1007;
const ZSTD_C_BLOCK_DELIMITERS: c_int = 1008;
const ZSTD_C_VALIDATE_SEQUENCES: c_int = 1009;
const ZSTD_C_SPLIT_AFTER_SEQUENCES: c_int = 1010;
const ZSTD_C_USE_ROW_MATCH_FINDER: c_int = 1011;
const ZSTD_C_DETERMINISTIC_REF_PREFIX: c_int = 1012;
const ZSTD_C_PREFETCH_CDICT_TABLES: c_int = 1013;
const ZSTD_C_ENABLE_SEQ_PRODUCER_FALLBACK: c_int = 1014;
const ZSTD_C_MAX_BLOCK_SIZE: c_int = 1015;
const ZSTD_C_REPCODE_RESOLUTION: c_int = 1016;
const ZSTD_C_BLOCK_SPLITTER_LEVEL: c_int = 1017;
const ZSTD_RESET_SESSION_ONLY: c_int = 1;
const ZSTD_RESET_PARAMETERS: c_int = 2;
const ZSTD_RESET_SESSION_AND_PARAMETERS: c_int = 3;
@@ -872,6 +903,115 @@ pub unsafe extern "C" fn ZSTD_rust_setParametersUsingCCtxParams(
0
}
/// Explicit projection for `ZSTD_CCtx_setParameter`.
///
/// Rust owns the stage authorization and accepted-parameter policy while the
/// C-owned context exposes only the fields whose side effects remain private.
#[repr(C)]
pub struct ZSTD_rust_setParameterState {
requested_params: *mut ZSTD_CCtx_params,
stream_stage: c_int,
c_params_changed: *mut c_int,
static_size: usize,
rust_simple_compress2_max_block_size_set: *mut c_uint,
}
const _: () = {
assert!(offset_of!(ZSTD_rust_setParameterState, requested_params) == 0);
assert!(offset_of!(ZSTD_rust_setParameterState, stream_stage) == size_of::<usize>());
assert!(offset_of!(ZSTD_rust_setParameterState, c_params_changed) == 2 * size_of::<usize>());
assert!(offset_of!(ZSTD_rust_setParameterState, static_size) == 3 * size_of::<usize>());
assert!(
offset_of!(
ZSTD_rust_setParameterState,
rust_simple_compress2_max_block_size_set
) == 4 * size_of::<usize>()
);
assert!(size_of::<ZSTD_rust_setParameterState>() == 5 * size_of::<usize>());
};
#[inline]
fn set_parameter_is_supported(param: c_int) -> bool {
matches!(
param,
ZSTD_C_COMPRESSION_LEVEL
| ZSTD_C_WINDOW_LOG
| ZSTD_C_HASH_LOG
| ZSTD_C_CHAIN_LOG
| ZSTD_C_SEARCH_LOG
| ZSTD_C_MIN_MATCH
| ZSTD_C_TARGET_LENGTH
| ZSTD_C_STRATEGY
| ZSTD_C_LDM_HASH_RATE_LOG
| ZSTD_C_FORMAT
| ZSTD_C_CONTENT_SIZE_FLAG
| ZSTD_C_CHECKSUM_FLAG
| ZSTD_C_DICT_ID_FLAG
| ZSTD_C_FORCE_MAX_WINDOW
| ZSTD_C_FORCE_ATTACH_DICT
| ZSTD_C_LITERAL_COMPRESSION_MODE
| ZSTD_C_JOB_SIZE
| ZSTD_C_OVERLAP_LOG
| ZSTD_C_RSYNCABLE
| ZSTD_C_ENABLE_DEDICATED_DICT_SEARCH
| ZSTD_C_ENABLE_LDM
| ZSTD_C_LDM_HASH_LOG
| ZSTD_C_LDM_MIN_MATCH
| ZSTD_C_LDM_BUCKET_SIZE_LOG
| ZSTD_C_TARGET_C_BLOCK_SIZE
| ZSTD_C_SRC_SIZE_HINT
| ZSTD_C_STABLE_IN_BUFFER
| ZSTD_C_STABLE_OUT_BUFFER
| ZSTD_C_BLOCK_DELIMITERS
| ZSTD_C_VALIDATE_SEQUENCES
| ZSTD_C_SPLIT_AFTER_SEQUENCES
| ZSTD_C_BLOCK_SPLITTER_LEVEL
| ZSTD_C_USE_ROW_MATCH_FINDER
| ZSTD_C_DETERMINISTIC_REF_PREFIX
| ZSTD_C_PREFETCH_CDICT_TABLES
| ZSTD_C_ENABLE_SEQ_PRODUCER_FALLBACK
| ZSTD_C_MAX_BLOCK_SIZE
| ZSTD_C_REPCODE_RESOLUTION
)
}
/// Apply one context parameter through the C-owned parameter object.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_setParameter(
state: *const ZSTD_rust_setParameterState,
param: c_int,
value: c_int,
) -> usize {
if state.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let state = unsafe { &*state };
if state.stream_stage != ZSTD_CSTREAM_STAGE_INIT {
if ZSTD_rust_isUpdateAuthorized(param) == 0 {
return ERROR(ZstdErrorCode::StageWrong);
}
unsafe {
*state.c_params_changed = 1;
}
}
if param == ZSTD_C_NB_WORKERS {
if value != 0 && state.static_size != 0 {
return ERROR(ZstdErrorCode::ParameterUnsupported);
}
} else if !set_parameter_is_supported(param) {
return ERROR(ZstdErrorCode::ParameterUnsupported);
}
let result = unsafe { ZSTD_CCtxParams_setParameter(state.requested_params, param, value) };
if !ERR_isError(result) && param == ZSTD_C_MAX_BLOCK_SIZE {
unsafe {
*state.rust_simple_compress2_max_block_size_set = 1;
}
}
result
}
type ResetCCtxClearAllDictsFn = unsafe extern "C" fn(*mut c_void);
type ResetCCtxResetParamsFn = unsafe extern "C" fn(*mut c_void) -> usize;
@@ -10180,6 +10320,164 @@ mod tests {
assert_eq!(result, ERROR(ZstdErrorCode::Generic));
}
fn set_parameter_test_state(
requested_params: &mut ZSTD_CCtx_params,
stream_stage: c_int,
c_params_changed: &mut c_int,
static_size: usize,
max_block_size_set: &mut c_uint,
) -> ZSTD_rust_setParameterState {
ZSTD_rust_setParameterState {
requested_params,
stream_stage,
c_params_changed,
static_size,
rust_simple_compress2_max_block_size_set: max_block_size_set,
}
}
fn set_parameter_test_get(requested_params: &ZSTD_CCtx_params, param: c_int) -> (usize, c_int) {
let mut value = -1;
let result = unsafe { ZSTD_CCtxParams_getParameter(requested_params, param, &mut value) };
(result, value)
}
#[test]
fn set_parameter_authorizes_in_flight_updates_and_marks_cparams_changed() {
let mut requested_params =
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
let mut c_params_changed = 0;
let mut max_block_size_set = 0;
let state = set_parameter_test_state(
&mut requested_params,
ZSTD_CSTREAM_STAGE_LOAD,
&mut c_params_changed,
0,
&mut max_block_size_set,
);
let result = unsafe { ZSTD_rust_setParameter(&state, ZSTD_C_COMPRESSION_LEVEL, 5) };
assert_eq!(result, 5);
assert_eq!(c_params_changed, 1);
assert_eq!(max_block_size_set, 0);
assert_eq!(
set_parameter_test_get(&requested_params, ZSTD_C_COMPRESSION_LEVEL),
(0, 5)
);
}
#[test]
fn set_parameter_rejects_unauthorized_stage_updates_without_mutation() {
let mut requested_params =
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
let mut c_params_changed = 0;
let mut max_block_size_set = 0;
let state = set_parameter_test_state(
&mut requested_params,
ZSTD_CSTREAM_STAGE_LOAD,
&mut c_params_changed,
0,
&mut max_block_size_set,
);
let result = unsafe { ZSTD_rust_setParameter(&state, ZSTD_C_FORMAT, 1) };
assert_eq!(result, ERROR(ZstdErrorCode::StageWrong));
assert_eq!(c_params_changed, 0);
assert_eq!(
set_parameter_test_get(&requested_params, ZSTD_C_FORMAT),
(0, 0)
);
}
#[test]
fn set_parameter_rejects_nonzero_workers_for_static_contexts() {
let mut requested_params =
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
let mut c_params_changed = 0;
let mut max_block_size_set = 0;
let state = set_parameter_test_state(
&mut requested_params,
ZSTD_CSTREAM_STAGE_INIT,
&mut c_params_changed,
1,
&mut max_block_size_set,
);
let result = unsafe { ZSTD_rust_setParameter(&state, ZSTD_C_NB_WORKERS, 1) };
assert_eq!(result, ERROR(ZstdErrorCode::ParameterUnsupported));
assert_eq!(c_params_changed, 0);
assert_eq!(
set_parameter_test_get(&requested_params, ZSTD_C_FORMAT),
(0, 0)
);
}
#[test]
fn set_parameter_rejects_unknown_parameters() {
let mut requested_params =
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
let mut c_params_changed = 0;
let mut max_block_size_set = 0;
let state = set_parameter_test_state(
&mut requested_params,
ZSTD_CSTREAM_STAGE_INIT,
&mut c_params_changed,
0,
&mut max_block_size_set,
);
let result = unsafe { ZSTD_rust_setParameter(&state, 12345, 1) };
assert_eq!(result, ERROR(ZstdErrorCode::ParameterUnsupported));
assert_eq!(
set_parameter_test_get(&requested_params, ZSTD_C_FORMAT),
(0, 0)
);
}
#[test]
fn set_parameter_marks_max_block_size_after_success() {
let mut requested_params =
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
let mut c_params_changed = 0;
let mut max_block_size_set = 0;
let state = set_parameter_test_state(
&mut requested_params,
ZSTD_CSTREAM_STAGE_INIT,
&mut c_params_changed,
0,
&mut max_block_size_set,
);
let result = unsafe { ZSTD_rust_setParameter(&state, ZSTD_C_MAX_BLOCK_SIZE, 0) };
assert_eq!(result, 0);
assert_eq!(max_block_size_set, 1);
}
#[test]
fn set_parameter_does_not_mark_max_block_size_after_underlying_error() {
let mut requested_params =
unsafe { MaybeUninit::<ZSTD_CCtx_params>::zeroed().assume_init() };
let mut c_params_changed = 0;
let mut max_block_size_set = 0;
let state = set_parameter_test_state(
&mut requested_params,
ZSTD_CSTREAM_STAGE_INIT,
&mut c_params_changed,
0,
&mut max_block_size_set,
);
let result = unsafe { ZSTD_rust_setParameter(&state, ZSTD_C_MAX_BLOCK_SIZE, 1) };
assert_eq!(result, ERROR(ZstdErrorCode::ParameterOutOfBound));
assert_eq!(max_block_size_set, 0);
}
#[derive(Default)]
struct SetCParamsTestContext {
events: Vec<&'static str>,