From 1046c18b0d7fa4ae4cbac95f78e537735e12ecea Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 19 Jul 2026 20:29:47 +0200 Subject: [PATCH] feat(compress): move CCtx copy orchestration into Rust Move the private CCtx-copy stage check, reset error handling, and callback ordering into a Rust projection while keeping C-owned context layout and table pointer arithmetic behind narrow callbacks. The table-size calculation now stays in the callback that runs after stage validation, preserving the copy-too-soon failure path instead of touching uninitialized source params. Test Plan: - ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml (713 passed) - ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings - ulimit -v 41943040 && make -j1 - ulimit -v 41943040 && make -j1 -C tests test-fuzzer FUZZERTEST=-T3s FUZZER_FLAGS=--no-big-tests (380 cases) - ulimit -v 41943040 && make -j1 -C tests test-zstream ZSTREAM_TESTTIME=-T2s (84 deterministic plus 109 randomized cases) --- lib/compress/zstd_compress.c | 255 +++++++++++++++++++++---------- rust/src/zstd_compress.rs | 280 +++++++++++++++++++++++++++++++++++ 2 files changed, 458 insertions(+), 77 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index e000b6d3d..201066d76 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -457,6 +457,35 @@ typedef char ZSTD_rust_reset_cctx_state_layout[ == 6 * sizeof(void*) && sizeof(ZSTD_rust_resetCCtxState) == 7 * sizeof(void*)) ? 1 : -1]; +typedef size_t (*ZSTD_rust_copyCCtxCheckStage_f)( + void* context, const void* srcCCtx); +typedef void (*ZSTD_rust_copyCCtxCopyState_f)( + void* context, const void* srcCCtx); +typedef size_t (*ZSTD_rust_copyCCtxReset_f)( + void* context, const void* srcCCtx, + const ZSTD_frameParameters* fParams, + U64 pledgedSrcSize, int zbuff); +typedef void (*ZSTD_rust_copyCCtxMarkTables_f)(void* context); +typedef void (*ZSTD_rust_copyCCtxCopyTables_f)( + void* context, const void* srcCCtx); +typedef struct { + void* callbackContext; + const void* srcCCtx; + const ZSTD_frameParameters* fParams; + U64 pledgedSrcSize; + ZSTD_rust_copyCCtxCheckStage_f checkStage; + ZSTD_rust_copyCCtxCopyState_f copyCustomMem; + ZSTD_rust_copyCCtxReset_f reset; + ZSTD_rust_copyCCtxMarkTables_f markTablesDirty; + ZSTD_rust_copyCCtxCopyTables_f copyTables; + ZSTD_rust_copyCCtxMarkTables_f markTablesClean; + ZSTD_rust_copyCCtxCopyState_f copyMatchState; + ZSTD_rust_copyCCtxCopyState_f copyDictState; + ZSTD_rust_copyCCtxCopyState_f copyBlockState; + int zbuff; +} ZSTD_rust_copyCCtxInternalState; +size_t ZSTD_rust_copyCCtxInternal( + const ZSTD_rust_copyCCtxInternalState* state); typedef size_t (*ZSTD_rust_copyCCtxInternal_f)( void* context, const void* srcCCtx, const ZSTD_frameParameters* fParams, @@ -481,6 +510,24 @@ typedef char ZSTD_rust_copy_cctx_state_layout[ == 5 * sizeof(void*) && sizeof(ZSTD_rust_copyCCtxState) == 6 * sizeof(void*)) ? 1 : -1]; +typedef char ZSTD_rust_copy_cctx_internal_state_layout[ + (offsetof(ZSTD_rust_copyCCtxInternalState, callbackContext) == 0 + && offsetof(ZSTD_rust_copyCCtxInternalState, srcCCtx) + == sizeof(void*) + && offsetof(ZSTD_rust_copyCCtxInternalState, fParams) + == 2 * sizeof(void*) + && offsetof(ZSTD_rust_copyCCtxInternalState, pledgedSrcSize) + == 3 * sizeof(void*) + && offsetof(ZSTD_rust_copyCCtxInternalState, checkStage) + == 3 * sizeof(void*) + sizeof(U64) + && offsetof(ZSTD_rust_copyCCtxInternalState, zbuff) + == 3 * sizeof(void*) + sizeof(U64) + + 9 * sizeof(void*) + && sizeof(ZSTD_rust_copyCCtxInternalState) + == ((offsetof(ZSTD_rust_copyCCtxInternalState, zbuff) + + sizeof(int) + sizeof(void*) - 1) / sizeof(void*)) + * sizeof(void*)) + ? 1 : -1]; typedef void (*ZSTD_rust_compressAdvancedInitParams_f)( void* context, const ZSTD_parameters* params); typedef size_t (*ZSTD_rust_compressAdvancedInternal_f)( @@ -4407,94 +4454,148 @@ static size_t ZSTD_resetCCtx_usingCDict(ZSTD_CCtx* cctx, return ZSTD_rust_resetCCtxUsingCDict(&state); } -/*! ZSTD_copyCCtx_internal() : - * Duplicate an existing context `srcCCtx` into another one `dstCCtx`. - * Only works during stage ZSTDcs_init (i.e. after creation, but before first call to ZSTD_compressContinue()). - * The "context", in this case, refers to the hash and chain tables, - * entropy tables, and dictionary references. - * `windowLog` value is enforced if != 0, otherwise value is copied from srcCCtx. - * @return : 0, or an error code */ -static size_t ZSTD_copyCCtx_internal(ZSTD_CCtx* dstCCtx, - const ZSTD_CCtx* srcCCtx, - ZSTD_frameParameters fParams, - U64 pledgedSrcSize, - ZSTD_buffered_policy_e zbuff) +static size_t ZSTD_rust_copyCCtx_check_stage( + void* context, const void* srcCCtx) { - RETURN_ERROR_IF(srcCCtx->stage!=ZSTDcs_init, stage_wrong, + const ZSTD_CCtx* const src = (const ZSTD_CCtx*)srcCCtx; + (void)context; + RETURN_ERROR_IF(src->stage != ZSTDcs_init, stage_wrong, "Can't copy a ctx that's not in init stage."); DEBUGLOG(5, "ZSTD_copyCCtx_internal"); - ZSTD_memcpy(&dstCCtx->customMem, &srcCCtx->customMem, sizeof(ZSTD_customMem)); - { ZSTD_CCtx_params params = dstCCtx->requestedParams; - /* Copy only compression parameters related to tables. */ - params.cParams = srcCCtx->appliedParams.cParams; - assert(srcCCtx->appliedParams.useRowMatchFinder != ZSTD_ps_auto); - assert(srcCCtx->appliedParams.postBlockSplitter != ZSTD_ps_auto); - assert(srcCCtx->appliedParams.ldmParams.enableLdm != ZSTD_ps_auto); - params.useRowMatchFinder = srcCCtx->appliedParams.useRowMatchFinder; - params.postBlockSplitter = srcCCtx->appliedParams.postBlockSplitter; - params.ldmParams = srcCCtx->appliedParams.ldmParams; - params.fParams = fParams; - params.maxBlockSize = srcCCtx->appliedParams.maxBlockSize; - ZSTD_resetCCtx_internal(dstCCtx, ¶ms, pledgedSrcSize, - /* loadedDictSize */ 0, - ZSTDcrp_leaveDirty, zbuff); - assert(dstCCtx->appliedParams.cParams.windowLog == srcCCtx->appliedParams.cParams.windowLog); - assert(dstCCtx->appliedParams.cParams.strategy == srcCCtx->appliedParams.cParams.strategy); - assert(dstCCtx->appliedParams.cParams.hashLog == srcCCtx->appliedParams.cParams.hashLog); - assert(dstCCtx->appliedParams.cParams.chainLog == srcCCtx->appliedParams.cParams.chainLog); - assert(dstCCtx->blockState.matchState.hashLog3 == srcCCtx->blockState.matchState.hashLog3); - } - - ZSTD_cwksp_mark_tables_dirty(&dstCCtx->workspace); - - /* copy tables */ - { size_t const chainSize = ZSTD_allocateChainTable(srcCCtx->appliedParams.cParams.strategy, - srcCCtx->appliedParams.useRowMatchFinder, - 0 /* forDDSDict */) - ? ((size_t)1 << srcCCtx->appliedParams.cParams.chainLog) - : 0; - size_t const hSize = (size_t)1 << srcCCtx->appliedParams.cParams.hashLog; - U32 const h3log = srcCCtx->blockState.matchState.hashLog3; - size_t const h3Size = h3log ? ((size_t)1 << h3log) : 0; - - ZSTD_memcpy(dstCCtx->blockState.matchState.hashTable, - srcCCtx->blockState.matchState.hashTable, - hSize * sizeof(U32)); - ZSTD_memcpy(dstCCtx->blockState.matchState.chainTable, - srcCCtx->blockState.matchState.chainTable, - chainSize * sizeof(U32)); - ZSTD_memcpy(dstCCtx->blockState.matchState.hashTable3, - srcCCtx->blockState.matchState.hashTable3, - h3Size * sizeof(U32)); - } - - ZSTD_cwksp_mark_tables_clean(&dstCCtx->workspace); - - /* copy dictionary offsets */ - { - const ZSTD_MatchState_t* srcMatchState = &srcCCtx->blockState.matchState; - ZSTD_MatchState_t* dstMatchState = &dstCCtx->blockState.matchState; - dstMatchState->window = srcMatchState->window; - dstMatchState->nextToUpdate = srcMatchState->nextToUpdate; - dstMatchState->loadedDictEnd= srcMatchState->loadedDictEnd; - } - dstCCtx->dictID = srcCCtx->dictID; - dstCCtx->dictContentSize = srcCCtx->dictContentSize; - - /* copy block state */ - ZSTD_memcpy(dstCCtx->blockState.prevCBlock, srcCCtx->blockState.prevCBlock, sizeof(*srcCCtx->blockState.prevCBlock)); - return 0; } +static void ZSTD_rust_copyCCtx_copy_custom_mem( + void* context, const void* srcCCtx) +{ + ZSTD_CCtx* const dst = (ZSTD_CCtx*)context; + const ZSTD_CCtx* const src = (const ZSTD_CCtx*)srcCCtx; + ZSTD_memcpy(&dst->customMem, &src->customMem, sizeof(ZSTD_customMem)); +} + +static size_t ZSTD_rust_copyCCtx_reset( + void* context, const void* srcCCtx, + const ZSTD_frameParameters* fParams, + U64 pledgedSrcSize, int zbuff) +{ + ZSTD_CCtx* const dst = (ZSTD_CCtx*)context; + const ZSTD_CCtx* const src = (const ZSTD_CCtx*)srcCCtx; + ZSTD_CCtx_params params = dst->requestedParams; + + /* Copy only compression parameters related to tables. */ + params.cParams = src->appliedParams.cParams; + assert(src->appliedParams.useRowMatchFinder != ZSTD_ps_auto); + assert(src->appliedParams.postBlockSplitter != ZSTD_ps_auto); + assert(src->appliedParams.ldmParams.enableLdm != ZSTD_ps_auto); + params.useRowMatchFinder = src->appliedParams.useRowMatchFinder; + params.postBlockSplitter = src->appliedParams.postBlockSplitter; + params.ldmParams = src->appliedParams.ldmParams; + params.fParams = *fParams; + params.maxBlockSize = src->appliedParams.maxBlockSize; + { size_t const resetError = ZSTD_resetCCtx_internal( + dst, ¶ms, pledgedSrcSize, + /* loadedDictSize */ 0, + ZSTDcrp_leaveDirty, + (ZSTD_buffered_policy_e)zbuff); + if (ZSTD_isError(resetError)) return resetError; + } + assert(dst->appliedParams.cParams.windowLog == src->appliedParams.cParams.windowLog); + assert(dst->appliedParams.cParams.strategy == src->appliedParams.cParams.strategy); + assert(dst->appliedParams.cParams.hashLog == src->appliedParams.cParams.hashLog); + assert(dst->appliedParams.cParams.chainLog == src->appliedParams.cParams.chainLog); + assert(dst->blockState.matchState.hashLog3 == src->blockState.matchState.hashLog3); + return 0; +} + +static void ZSTD_rust_copyCCtx_mark_tables_dirty(void* context) +{ + ZSTD_CCtx* const dst = (ZSTD_CCtx*)context; + ZSTD_cwksp_mark_tables_dirty(&dst->workspace); +} + +static void ZSTD_rust_copyCCtx_copy_tables( + void* context, const void* srcCCtx) +{ + ZSTD_CCtx* const dst = (ZSTD_CCtx*)context; + const ZSTD_CCtx* const src = (const ZSTD_CCtx*)srcCCtx; + size_t const chainSize = ZSTD_allocateChainTable( + src->appliedParams.cParams.strategy, + src->appliedParams.useRowMatchFinder, + 0 /* forDDSDict */) + ? ((size_t)1 << src->appliedParams.cParams.chainLog) + : 0; + size_t const hSize = (size_t)1 << src->appliedParams.cParams.hashLog; + U32 const h3log = src->blockState.matchState.hashLog3; + size_t const h3Size = h3log ? ((size_t)1 << h3log) : 0; + ZSTD_memcpy(dst->blockState.matchState.hashTable, + src->blockState.matchState.hashTable, + hSize * sizeof(U32)); + ZSTD_memcpy(dst->blockState.matchState.chainTable, + src->blockState.matchState.chainTable, + chainSize * sizeof(U32)); + ZSTD_memcpy(dst->blockState.matchState.hashTable3, + src->blockState.matchState.hashTable3, + h3Size * sizeof(U32)); +} + +static void ZSTD_rust_copyCCtx_mark_tables_clean(void* context) +{ + ZSTD_CCtx* const dst = (ZSTD_CCtx*)context; + ZSTD_cwksp_mark_tables_clean(&dst->workspace); +} + +static void ZSTD_rust_copyCCtx_copy_match_state( + void* context, const void* srcCCtx) +{ + ZSTD_CCtx* const dst = (ZSTD_CCtx*)context; + const ZSTD_CCtx* const src = (const ZSTD_CCtx*)srcCCtx; + const ZSTD_MatchState_t* const srcMatchState = &src->blockState.matchState; + ZSTD_MatchState_t* const dstMatchState = &dst->blockState.matchState; + dstMatchState->window = srcMatchState->window; + dstMatchState->nextToUpdate = srcMatchState->nextToUpdate; + dstMatchState->loadedDictEnd = srcMatchState->loadedDictEnd; +} + +static void ZSTD_rust_copyCCtx_copy_dict_state( + void* context, const void* srcCCtx) +{ + ZSTD_CCtx* const dst = (ZSTD_CCtx*)context; + const ZSTD_CCtx* const src = (const ZSTD_CCtx*)srcCCtx; + dst->dictID = src->dictID; + dst->dictContentSize = src->dictContentSize; +} + +static void ZSTD_rust_copyCCtx_copy_block_state( + void* context, const void* srcCCtx) +{ + ZSTD_CCtx* const dst = (ZSTD_CCtx*)context; + const ZSTD_CCtx* const src = (const ZSTD_CCtx*)srcCCtx; + ZSTD_memcpy(dst->blockState.prevCBlock, + src->blockState.prevCBlock, + sizeof(*src->blockState.prevCBlock)); +} + static size_t ZSTD_rust_copyCCtx_internal_callback( void* context, const void* srcCCtx, const ZSTD_frameParameters* fParams, U64 pledgedSrcSize, int zbuff) { - return ZSTD_copyCCtx_internal( - (ZSTD_CCtx*)context, (const ZSTD_CCtx*)srcCCtx, - *fParams, pledgedSrcSize, (ZSTD_buffered_policy_e)zbuff); + ZSTD_rust_copyCCtxInternalState state; + state.callbackContext = context; + state.srcCCtx = srcCCtx; + state.fParams = fParams; + state.pledgedSrcSize = pledgedSrcSize; + state.checkStage = ZSTD_rust_copyCCtx_check_stage; + state.copyCustomMem = ZSTD_rust_copyCCtx_copy_custom_mem; + state.reset = ZSTD_rust_copyCCtx_reset; + state.markTablesDirty = ZSTD_rust_copyCCtx_mark_tables_dirty; + state.copyTables = ZSTD_rust_copyCCtx_copy_tables; + state.markTablesClean = ZSTD_rust_copyCCtx_mark_tables_clean; + state.copyMatchState = ZSTD_rust_copyCCtx_copy_match_state; + state.copyDictState = ZSTD_rust_copyCCtx_copy_dict_state; + state.copyBlockState = ZSTD_rust_copyCCtx_copy_block_state; + state.zbuff = zbuff; + return ZSTD_rust_copyCCtxInternal(&state); } /*! ZSTD_copyCCtx() : diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index c317fe08c..221e93c60 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -1651,6 +1651,133 @@ pub unsafe extern "C" fn ZSTD_rust_copyCCtx(state: *const ZSTD_rust_copyCCtxStat } } +type CopyCCtxCheckStageFn = unsafe extern "C" fn(*mut c_void, *const c_void) -> usize; +type CopyCCtxCopyStateFn = unsafe extern "C" fn(*mut c_void, *const c_void); +type CopyCCtxResetFn = unsafe extern "C" fn( + *mut c_void, + *const c_void, + *const ZSTD_frameParameters, + u64, + c_int, +) -> usize; +type CopyCCtxMarkTablesFn = unsafe extern "C" fn(*mut c_void); +type CopyCCtxCopyTablesFn = unsafe extern "C" fn(*mut c_void, *const c_void); + +/// Projection for the private `ZSTD_copyCCtx_internal` operation. +/// +/// Rust owns the stage/error branch and the order of the reset, workspace, +/// table, dictionary, and block-state operations. C retains access to the +/// private `ZSTD_CCtx` layout behind callbacks. +#[repr(C)] +pub struct ZSTD_rust_copyCCtxInternalState { + callback_context: *mut c_void, + src_cctx: *const c_void, + f_params: *const ZSTD_frameParameters, + pledged_src_size: u64, + check_stage: Option, + copy_custom_mem: Option, + reset: Option, + mark_tables_dirty: Option, + copy_tables: Option, + mark_tables_clean: Option, + copy_match_state: Option, + copy_dict_state: Option, + copy_block_state: Option, + zbuff: c_int, +} + +const _: () = { + assert!(size_of::() == size_of::()); + assert!(size_of::() == size_of::()); + assert!(size_of::() == size_of::()); + assert!(size_of::() == size_of::()); + assert!(size_of::() == size_of::()); + assert!(offset_of!(ZSTD_rust_copyCCtxInternalState, callback_context) == 0); + assert!(offset_of!(ZSTD_rust_copyCCtxInternalState, src_cctx) == size_of::()); + assert!(offset_of!(ZSTD_rust_copyCCtxInternalState, f_params) == 2 * size_of::()); + assert!( + offset_of!(ZSTD_rust_copyCCtxInternalState, pledged_src_size) == 3 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_copyCCtxInternalState, check_stage) + == 3 * size_of::() + size_of::() + ); + assert!( + offset_of!(ZSTD_rust_copyCCtxInternalState, zbuff) + == 3 * size_of::() + size_of::() + 9 * size_of::() + ); + assert!( + size_of::() + == (offset_of!(ZSTD_rust_copyCCtxInternalState, zbuff) + size_of::()) + .div_ceil(size_of::()) + * size_of::() + ); +}; + +/// Run the private context-copy operation through C-owned layout callbacks. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_copyCCtxInternal( + state: *const ZSTD_rust_copyCCtxInternalState, +) -> usize { + if state.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + let state = unsafe { &*state }; + let ( + Some(check_stage), + Some(copy_custom_mem), + Some(reset), + Some(mark_tables_dirty), + Some(copy_tables), + Some(mark_tables_clean), + Some(copy_match_state), + Some(copy_dict_state), + Some(copy_block_state), + ) = ( + state.check_stage, + state.copy_custom_mem, + state.reset, + state.mark_tables_dirty, + state.copy_tables, + state.mark_tables_clean, + state.copy_match_state, + state.copy_dict_state, + state.copy_block_state, + ) + else { + return ERROR(ZstdErrorCode::Generic); + }; + if state.callback_context.is_null() || state.src_cctx.is_null() || state.f_params.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + + let stage_error = unsafe { check_stage(state.callback_context, state.src_cctx) }; + if ERR_isError(stage_error) { + return stage_error; + } + + unsafe { + copy_custom_mem(state.callback_context, state.src_cctx); + let reset_error = reset( + state.callback_context, + state.src_cctx, + state.f_params, + state.pledged_src_size, + state.zbuff, + ); + if ERR_isError(reset_error) { + return reset_error; + } + mark_tables_dirty(state.callback_context); + copy_tables(state.callback_context, state.src_cctx); + mark_tables_clean(state.callback_context); + copy_match_state(state.callback_context, state.src_cctx); + copy_dict_state(state.callback_context, state.src_cctx); + copy_block_state(state.callback_context, state.src_cctx); + } + 0 +} + type CompressAdvancedInitParamsFn = unsafe extern "C" fn(*mut c_void, *const ZSTD_parameters); type CompressAdvancedInternalFn = unsafe extern "C" fn( *mut c_void, @@ -13094,6 +13221,159 @@ mod tests { assert_eq!(context.frame_params.noDictIDFlag, 1); } + #[derive(Default)] + struct CopyCCtxInternalTestContext { + events: Vec<&'static str>, + stage_result: usize, + reset_result: usize, + frame_params: ZSTD_frameParameters, + pledged_src_size: u64, + zbuff: c_int, + } + + unsafe fn copy_cctx_internal_test_context( + context: *mut c_void, + ) -> &'static mut CopyCCtxInternalTestContext { + unsafe { &mut *context.cast::() } + } + + unsafe extern "C" fn copy_cctx_internal_test_check_stage( + context: *mut c_void, + _src_cctx: *const c_void, + ) -> usize { + let context = unsafe { copy_cctx_internal_test_context(context) }; + context.events.push("check"); + context.stage_result + } + + unsafe extern "C" fn copy_cctx_internal_test_copy_state( + context: *mut c_void, + _src_cctx: *const c_void, + ) { + let context = unsafe { copy_cctx_internal_test_context(context) }; + let event = match context.events.len() { + 1 => "custom", + 6 => "match", + 7 => "dict", + 8 => "block", + _ => panic!("unexpected copy-state callback order"), + }; + context.events.push(event); + } + + unsafe extern "C" fn copy_cctx_internal_test_reset( + context: *mut c_void, + _src_cctx: *const c_void, + f_params: *const ZSTD_frameParameters, + pledged_src_size: u64, + zbuff: c_int, + ) -> usize { + let context = unsafe { copy_cctx_internal_test_context(context) }; + context.events.push("reset"); + context.frame_params = unsafe { *f_params }; + context.pledged_src_size = pledged_src_size; + context.zbuff = zbuff; + context.reset_result + } + + unsafe extern "C" fn copy_cctx_internal_test_mark_tables_dirty(context: *mut c_void) { + unsafe { copy_cctx_internal_test_context(context) } + .events + .push("dirty"); + } + + unsafe extern "C" fn copy_cctx_internal_test_copy_tables( + context: *mut c_void, + _src_cctx: *const c_void, + ) { + let context = unsafe { copy_cctx_internal_test_context(context) }; + context.events.push("tables"); + } + + unsafe extern "C" fn copy_cctx_internal_test_mark_tables_clean(context: *mut c_void) { + unsafe { copy_cctx_internal_test_context(context) } + .events + .push("clean"); + } + + fn copy_cctx_internal_test_state( + context: &mut CopyCCtxInternalTestContext, + src_cctx: *const c_void, + f_params: &ZSTD_frameParameters, + ) -> ZSTD_rust_copyCCtxInternalState { + ZSTD_rust_copyCCtxInternalState { + callback_context: (context as *mut CopyCCtxInternalTestContext).cast(), + src_cctx, + f_params, + pledged_src_size: 123, + check_stage: Some(copy_cctx_internal_test_check_stage), + copy_custom_mem: Some(copy_cctx_internal_test_copy_state), + reset: Some(copy_cctx_internal_test_reset), + mark_tables_dirty: Some(copy_cctx_internal_test_mark_tables_dirty), + copy_tables: Some(copy_cctx_internal_test_copy_tables), + mark_tables_clean: Some(copy_cctx_internal_test_mark_tables_clean), + copy_match_state: Some(copy_cctx_internal_test_copy_state), + copy_dict_state: Some(copy_cctx_internal_test_copy_state), + copy_block_state: Some(copy_cctx_internal_test_copy_state), + zbuff: 7, + } + } + + #[test] + fn copy_cctx_internal_runs_private_callbacks_in_original_order() { + let mut context = CopyCCtxInternalTestContext::default(); + let f_params = ZSTD_frameParameters { + contentSizeFlag: 1, + checksumFlag: 1, + noDictIDFlag: 1, + }; + let state = + copy_cctx_internal_test_state(&mut context, 0x5000usize as *const c_void, &f_params); + + let result = unsafe { ZSTD_rust_copyCCtxInternal(&state) }; + + assert_eq!(result, 0); + assert_eq!( + context.events, + ["check", "custom", "reset", "dirty", "tables", "clean", "match", "dict", "block"] + ); + assert_eq!(context.frame_params, f_params); + assert_eq!(context.pledged_src_size, 123); + assert_eq!(context.zbuff, 7); + } + + #[test] + fn copy_cctx_internal_rejects_stage_before_mutation() { + let mut context = CopyCCtxInternalTestContext { + stage_result: ERROR(ZstdErrorCode::StageWrong), + ..CopyCCtxInternalTestContext::default() + }; + let f_params = ZSTD_frameParameters::default(); + let state = + copy_cctx_internal_test_state(&mut context, 0x5000usize as *const c_void, &f_params); + + let result = unsafe { ZSTD_rust_copyCCtxInternal(&state) }; + + assert_eq!(result, ERROR(ZstdErrorCode::StageWrong)); + assert_eq!(context.events, ["check"]); + } + + #[test] + fn copy_cctx_internal_propagates_reset_error_before_table_mutation() { + let mut context = CopyCCtxInternalTestContext { + reset_result: ERROR(ZstdErrorCode::MemoryAllocation), + ..CopyCCtxInternalTestContext::default() + }; + let f_params = ZSTD_frameParameters::default(); + let state = + copy_cctx_internal_test_state(&mut context, 0x5000usize as *const c_void, &f_params); + + let result = unsafe { ZSTD_rust_copyCCtxInternal(&state) }; + + assert_eq!(result, ERROR(ZstdErrorCode::MemoryAllocation)); + assert_eq!(context.events, ["check", "custom", "reset"]); + } + #[derive(Default)] struct CompressAdvancedTestContext { events: Vec<&'static str>,