diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index d61ea93f4..d8b5e5eaf 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -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) diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index 5f970537a..a8dcf4f87 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -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::()); + assert!(offset_of!(ZSTD_rust_setParameterState, c_params_changed) == 2 * size_of::()); + assert!(offset_of!(ZSTD_rust_setParameterState, static_size) == 3 * size_of::()); + assert!( + offset_of!( + ZSTD_rust_setParameterState, + rust_simple_compress2_max_block_size_set + ) == 4 * size_of::() + ); + assert!(size_of::() == 5 * size_of::()); +}; + +#[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::::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::::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::::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::::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::::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::::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>,