diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index a86a6bfae..d712d2c4d 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -147,6 +147,59 @@ typedef char ZSTD_rust_frame_chunk_state_layout[ && sizeof(ZSTD_rust_frameChunkState) == 12 * sizeof(void*) + sizeof(S64) + 6 * sizeof(int)) ? 1 : -1]; + +/* The high-level continue/block entry points are Rust-owned. This projection + * carries only mutable scalar state and C callbacks; the private CCtx and + * match-state layout never crosses the ABI. */ +typedef size_t (*ZSTD_rust_compressContinueHeader_f)(void* context, + void* dst, + size_t dstCapacity); +typedef void (*ZSTD_rust_compressContinueWindow_f)(void* context, + const void* src, + size_t srcSize); +typedef size_t (*ZSTD_rust_compressContinueBlock_f)(void* context, + void* dst, + size_t dstCapacity, + const void* src, + size_t srcSize, + U32 lastFrameChunk); +typedef struct { + void* callbackContext; + ZSTD_rust_compressContinueHeader_f writeFrameHeader; + ZSTD_rust_compressContinueWindow_f updateWindow; + ZSTD_rust_compressContinueWindow_f correctOverflow; + ZSTD_rust_compressContinueBlock_f compressFrameChunk; + ZSTD_rust_compressContinueBlock_f compressBlock; + ZSTD_compressionStage_e* stage; + unsigned long long* consumedSrcSize; + unsigned long long* producedCSize; + U64 pledgedSrcSizePlusOne; + size_t blockSizeMax; + int checkBlockSize; +} ZSTD_rust_compressContinueState; +size_t ZSTD_rust_compressContinue( + const ZSTD_rust_compressContinueState* state, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + U32 frame, U32 lastFrameChunk); +typedef char ZSTD_rust_compress_continue_state_layout[ + (offsetof(ZSTD_rust_compressContinueState, callbackContext) == 0 + && offsetof(ZSTD_rust_compressContinueState, writeFrameHeader) == sizeof(void*) + && offsetof(ZSTD_rust_compressContinueState, updateWindow) == 2 * sizeof(void*) + && offsetof(ZSTD_rust_compressContinueState, correctOverflow) == 3 * sizeof(void*) + && offsetof(ZSTD_rust_compressContinueState, compressFrameChunk) == 4 * sizeof(void*) + && offsetof(ZSTD_rust_compressContinueState, compressBlock) == 5 * sizeof(void*) + && offsetof(ZSTD_rust_compressContinueState, stage) == 6 * sizeof(void*) + && offsetof(ZSTD_rust_compressContinueState, consumedSrcSize) == 7 * sizeof(void*) + && offsetof(ZSTD_rust_compressContinueState, producedCSize) == 8 * sizeof(void*) + && offsetof(ZSTD_rust_compressContinueState, pledgedSrcSizePlusOne) == 9 * sizeof(void*) + && offsetof(ZSTD_rust_compressContinueState, blockSizeMax) + == 9 * sizeof(void*) + sizeof(U64) + && offsetof(ZSTD_rust_compressContinueState, checkBlockSize) + == 9 * sizeof(void*) + sizeof(U64) + sizeof(size_t) + && sizeof(ZSTD_rust_compressContinueState) + == (sizeof(void*) == 8 ? 96 : 52)) + ? 1 : -1]; /* The target-sized block body only needs this narrow projection of ZSTD_CCtx. * Matchfinder/window state, sequence-store construction, and outer repeat-mode * cleanup remain in C. */ @@ -3069,6 +3122,84 @@ static size_t ZSTD_writeFrameHeader(void* dst, size_t dstCapacity, pledgedSrcSize, dictID); } +static size_t ZSTD_rust_compressContinue_writeFrameHeader( + void* context, void* dst, size_t dstCapacity) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; + return ZSTD_writeFrameHeader( + dst, dstCapacity, &cctx->appliedParams, + cctx->pledgedSrcSizePlusOne - 1, cctx->dictID); +} + +static void ZSTD_rust_compressContinue_updateWindow( + void* context, const void* src, size_t srcSize) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; + ZSTD_MatchState_t* const ms = &cctx->blockState.matchState; + + if (!ZSTD_window_update(&ms->window, src, srcSize, ms->forceNonContiguous)) { + ms->forceNonContiguous = 0; + ms->nextToUpdate = ms->window.dictLimit; + } + if (cctx->appliedParams.ldmParams.enableLdm == ZSTD_ps_enable) { + ZSTD_window_update(&cctx->ldmState.window, src, srcSize, + /* forceNonContiguous */ 0); + } +} + +static void ZSTD_rust_compressContinue_correctOverflow( + void* context, const void* src, size_t srcSize) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; + ZSTD_MatchState_t* const ms = &cctx->blockState.matchState; + + ZSTD_overflowCorrectIfNeeded( + ms, &cctx->workspace, &cctx->appliedParams, + src, (const BYTE*)src + srcSize); +} + +static size_t ZSTD_rust_compressContinue_frameChunk( + void* context, void* dst, size_t dstCapacity, + const void* src, size_t srcSize, U32 lastFrameChunk) +{ + return ZSTD_compress_frameChunk( + (ZSTD_CCtx*)context, dst, dstCapacity, src, srcSize, + lastFrameChunk); +} + +static size_t ZSTD_rust_compressContinue_block( + void* context, void* dst, size_t dstCapacity, + const void* src, size_t srcSize, U32 lastFrameChunk) +{ + (void)lastFrameChunk; + return ZSTD_compressBlock_internal( + (ZSTD_CCtx*)context, dst, dstCapacity, src, srcSize, + 0 /* frame */); +} + +static size_t ZSTD_compressContinue_dispatch( + ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + U32 frame, U32 lastFrameChunk, + size_t blockSizeMax, int checkBlockSize) +{ + ZSTD_rust_compressContinueState state; + state.callbackContext = cctx; + state.writeFrameHeader = ZSTD_rust_compressContinue_writeFrameHeader; + state.updateWindow = ZSTD_rust_compressContinue_updateWindow; + state.correctOverflow = ZSTD_rust_compressContinue_correctOverflow; + state.compressFrameChunk = ZSTD_rust_compressContinue_frameChunk; + state.compressBlock = ZSTD_rust_compressContinue_block; + state.stage = &cctx->stage; + state.consumedSrcSize = &cctx->consumedSrcSize; + state.producedCSize = &cctx->producedCSize; + state.pledgedSrcSizePlusOne = cctx->pledgedSrcSizePlusOne; + state.blockSizeMax = blockSizeMax; + state.checkBlockSize = checkBlockSize; + return ZSTD_rust_compressContinue( + &state, dst, dstCapacity, src, srcSize, frame, lastFrameChunk); +} + void ZSTD_referenceExternalSequences(ZSTD_CCtx* cctx, rawSeq* seq, size_t nbSeq) { assert(cctx->stage == ZSTDcs_init); @@ -3081,75 +3212,15 @@ void ZSTD_referenceExternalSequences(ZSTD_CCtx* cctx, rawSeq* seq, size_t nbSeq) } -static size_t ZSTD_compressContinue_internal (ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, - U32 frame, U32 lastFrameChunk) -{ - ZSTD_MatchState_t* const ms = &cctx->blockState.matchState; - size_t fhSize = 0; - - DEBUGLOG(5, "ZSTD_compressContinue_internal, stage: %u, srcSize: %u", - cctx->stage, (unsigned)srcSize); - RETURN_ERROR_IF(cctx->stage==ZSTDcs_created, stage_wrong, - "missing init (ZSTD_compressBegin)"); - - if (frame && (cctx->stage==ZSTDcs_init)) { - fhSize = ZSTD_writeFrameHeader(dst, dstCapacity, &cctx->appliedParams, - cctx->pledgedSrcSizePlusOne-1, cctx->dictID); - FORWARD_IF_ERROR(fhSize, "ZSTD_writeFrameHeader failed"); - assert(fhSize <= dstCapacity); - dstCapacity -= fhSize; - dst = (char*)dst + fhSize; - cctx->stage = ZSTDcs_ongoing; - } - - if (!srcSize) return fhSize; /* do not generate an empty block if no input */ - - if (!ZSTD_window_update(&ms->window, src, srcSize, ms->forceNonContiguous)) { - ms->forceNonContiguous = 0; - ms->nextToUpdate = ms->window.dictLimit; - } - if (cctx->appliedParams.ldmParams.enableLdm == ZSTD_ps_enable) { - ZSTD_window_update(&cctx->ldmState.window, src, srcSize, /* forceNonContiguous */ 0); - } - - if (!frame) { - /* overflow check and correction for block mode */ - ZSTD_overflowCorrectIfNeeded( - ms, &cctx->workspace, &cctx->appliedParams, - src, (BYTE const*)src + srcSize); - } - - DEBUGLOG(5, "ZSTD_compressContinue_internal (blockSize=%u)", (unsigned)cctx->blockSizeMax); - { size_t const cSize = frame ? - ZSTD_compress_frameChunk (cctx, dst, dstCapacity, src, srcSize, lastFrameChunk) : - ZSTD_compressBlock_internal (cctx, dst, dstCapacity, src, srcSize, 0 /* frame */); - int srcSizeWrong; - FORWARD_IF_ERROR(cSize, "%s", frame ? "ZSTD_compress_frameChunk failed" : "ZSTD_compressBlock_internal failed"); - srcSizeWrong = ZSTD_rust_updateFrameProgression( - &cctx->consumedSrcSize, &cctx->producedCSize, - cctx->pledgedSrcSizePlusOne, srcSize, cSize, fhSize); - assert(!(cctx->appliedParams.fParams.contentSizeFlag && cctx->pledgedSrcSizePlusOne == 0)); - if (cctx->pledgedSrcSizePlusOne != 0) { /* control src size */ - ZSTD_STATIC_ASSERT(ZSTD_CONTENTSIZE_UNKNOWN == (unsigned long long)-1); - RETURN_ERROR_IF( - srcSizeWrong, - srcSize_wrong, - "error : pledgedSrcSize = %u, while realSrcSize >= %u", - (unsigned)cctx->pledgedSrcSizePlusOne-1, - (unsigned)cctx->consumedSrcSize); - } - return cSize + fhSize; - } -} - size_t ZSTD_compressContinue_public(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize) { DEBUGLOG(5, "ZSTD_compressContinue (srcSize=%u)", (unsigned)srcSize); - return ZSTD_compressContinue_internal(cctx, dst, dstCapacity, src, srcSize, 1 /* frame mode */, 0 /* last chunk */); + return ZSTD_compressContinue_dispatch( + cctx, dst, dstCapacity, src, srcSize, + 1 /* frame mode */, 0 /* last chunk */, + cctx->blockSizeMax, 0 /* block size already selected */); } /* NOTE: Must just wrap ZSTD_compressContinue_public() */ @@ -3177,10 +3248,10 @@ size_t ZSTD_getBlockSize(const ZSTD_CCtx* cctx) size_t ZSTD_compressBlock_deprecated(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize) { DEBUGLOG(5, "ZSTD_compressBlock: srcSize = %u", (unsigned)srcSize); - { size_t const blockSizeMax = ZSTD_getBlockSize_deprecated(cctx); - RETURN_ERROR_IF(srcSize > blockSizeMax, srcSize_wrong, "input is larger than a block"); } - - return ZSTD_compressContinue_internal(cctx, dst, dstCapacity, src, srcSize, 0 /* frame mode */, 0 /* last chunk */); + return ZSTD_compressContinue_dispatch( + cctx, dst, dstCapacity, src, srcSize, + 0 /* block mode */, 0 /* last chunk */, + ZSTD_getBlockSize_deprecated(cctx), 1 /* validate block size */); } /* NOTE: Must just wrap ZSTD_compressBlock_deprecated() */ @@ -3581,10 +3652,11 @@ size_t ZSTD_compressEnd_public(ZSTD_CCtx* cctx, const void* src, size_t srcSize) { size_t endResult; - size_t const cSize = ZSTD_compressContinue_internal(cctx, - dst, dstCapacity, src, srcSize, - 1 /* frame mode */, 1 /* last chunk */); - FORWARD_IF_ERROR(cSize, "ZSTD_compressContinue_internal failed"); + size_t const cSize = ZSTD_compressContinue_dispatch( + cctx, dst, dstCapacity, src, srcSize, + 1 /* frame mode */, 1 /* last chunk */, + cctx->blockSizeMax, 0 /* block size already selected */); + FORWARD_IF_ERROR(cSize, "ZSTD_compressContinue failed"); endResult = ZSTD_writeEpilogue(cctx, (char*)dst + cSize, dstCapacity-cSize); FORWARD_IF_ERROR(endResult, "ZSTD_writeEpilogue failed"); assert(!(cctx->appliedParams.fParams.contentSizeFlag && cctx->pledgedSrcSizePlusOne == 0)); diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index dfd9309ce..1222c8fd9 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -340,6 +340,185 @@ pub unsafe extern "C" fn ZSTD_rust_compressFrameChunk( } } +type CompressContinueHeaderFn = unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> usize; +type CompressContinueWindowFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize); +type CompressContinueBlockFn = + unsafe extern "C" fn(*mut c_void, *mut c_void, usize, *const c_void, usize, c_uint) -> usize; + +/// Explicit projection for the high-level continue and deprecated block APIs. +/// +/// Rust owns stage transitions, frame-header sequencing, input progression, +/// and dispatch between the already-migrated frame-chunk/block bodies. The +/// opaque callback context remains in C, where callbacks update the private +/// match-state windows and invoke the C-owned context-sensitive operations. +#[repr(C)] +pub struct ZSTD_rust_compressContinueState { + callback_context: *mut c_void, + write_frame_header: CompressContinueHeaderFn, + update_window: CompressContinueWindowFn, + correct_overflow: CompressContinueWindowFn, + compress_frame_chunk: CompressContinueBlockFn, + compress_block: CompressContinueBlockFn, + stage: *mut c_int, + consumed_src_size: *mut u64, + produced_c_size: *mut u64, + pledged_src_size_plus_one: u64, + block_size_max: usize, + check_block_size: c_int, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_compressContinueState, callback_context) == 0); + assert!(offset_of!(ZSTD_rust_compressContinueState, write_frame_header) == size_of::()); + assert!(offset_of!(ZSTD_rust_compressContinueState, update_window) == 2 * size_of::()); + assert!( + offset_of!(ZSTD_rust_compressContinueState, correct_overflow) == 3 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_compressContinueState, compress_frame_chunk) == 4 * size_of::() + ); + assert!(offset_of!(ZSTD_rust_compressContinueState, compress_block) == 5 * size_of::()); + assert!(offset_of!(ZSTD_rust_compressContinueState, stage) == 6 * size_of::()); + assert!( + offset_of!(ZSTD_rust_compressContinueState, consumed_src_size) == 7 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_compressContinueState, produced_c_size) + == 8 * (usize::BITS as usize / 8) + ); + assert!( + offset_of!(ZSTD_rust_compressContinueState, pledged_src_size_plus_one) + == 9 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_compressContinueState, block_size_max) + == 9 * size_of::() + size_of::() + ); + assert!( + offset_of!(ZSTD_rust_compressContinueState, check_block_size) + == 9 * size_of::() + size_of::() + size_of::() + ); + assert!( + size_of::() + == if size_of::() == 8 { 96 } else { 52 } + ); +}; + +const ZSTD_COMPRESSION_STAGE_CREATED: c_int = 0; +const ZSTD_COMPRESSION_STAGE_INIT: c_int = 1; +const ZSTD_COMPRESSION_STAGE_ONGOING: c_int = 2; + +#[allow(clippy::too_many_arguments)] +unsafe fn compress_continue_body_with( + state: &ZSTD_rust_compressContinueState, + mut dst: *mut c_void, + mut dst_capacity: usize, + src: *const c_void, + src_size: usize, + frame: c_uint, + last_frame_chunk: c_uint, +) -> usize { + if state.stage.is_null() || state.consumed_src_size.is_null() || state.produced_c_size.is_null() + { + return ERROR(ZstdErrorCode::Generic); + } + + /* The deprecated block entry point performed this check before entering + * the old internal routine. Keep that ordering in the Rust orchestration. */ + if state.check_block_size != 0 && src_size > state.block_size_max { + return ERROR(ZstdErrorCode::SrcSizeWrong); + } + + if unsafe { *state.stage } == ZSTD_COMPRESSION_STAGE_CREATED { + return ERROR(ZstdErrorCode::StageWrong); + } + + let mut frame_header_size = 0usize; + if frame != 0 && unsafe { *state.stage } == ZSTD_COMPRESSION_STAGE_INIT { + frame_header_size = + unsafe { (state.write_frame_header)(state.callback_context, dst, dst_capacity) }; + if ERR_isError(frame_header_size) { + return frame_header_size; + } + if frame_header_size > dst_capacity { + return ERROR(ZstdErrorCode::DstSizeTooSmall); + } + unsafe { + *state.stage = ZSTD_COMPRESSION_STAGE_ONGOING; + } + dst = unsafe { dst.cast::().add(frame_header_size).cast() }; + dst_capacity -= frame_header_size; + } + + if src_size == 0 { + return frame_header_size; + } + + unsafe { (state.update_window)(state.callback_context, src, src_size) }; + if frame == 0 { + unsafe { (state.correct_overflow)(state.callback_context, src, src_size) }; + } + + let compressed_size = if frame != 0 { + unsafe { + (state.compress_frame_chunk)( + state.callback_context, + dst, + dst_capacity, + src, + src_size, + last_frame_chunk, + ) + } + } else { + unsafe { + (state.compress_block)(state.callback_context, dst, dst_capacity, src, src_size, 0) + } + }; + if ERR_isError(compressed_size) { + return compressed_size; + } + + let source_size_wrong = update_frame_progression( + unsafe { &mut *state.consumed_src_size }, + unsafe { &mut *state.produced_c_size }, + state.pledged_src_size_plus_one, + src_size, + compressed_size, + frame_header_size, + ); + if state.pledged_src_size_plus_one != 0 && source_size_wrong { + return ERROR(ZstdErrorCode::SrcSizeWrong); + } + compressed_size.wrapping_add(frame_header_size) +} + +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_compressContinue( + state: *const ZSTD_rust_compressContinueState, + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, + frame: c_uint, + last_frame_chunk: c_uint, +) -> usize { + if state.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + unsafe { + compress_continue_body_with( + &*state, + dst, + dst_capacity, + src, + src_size, + frame, + last_frame_chunk, + ) + } +} + /// Explicit projection of the state used by `ZSTD_compressSequences_internal`. /// /// The C context and its function-pointer-bearing parameter structure remain @@ -3161,6 +3340,327 @@ mod tests { assert_eq!(output, [0xa5; 5]); } + #[derive(Default)] + struct CompressContinueTestContext { + header_calls: usize, + update_window_calls: usize, + overflow_calls: usize, + frame_calls: usize, + block_calls: usize, + last_frame_chunk: c_uint, + header_result: usize, + frame_result: usize, + block_result: usize, + } + + unsafe fn compress_continue_test_context( + context: *mut c_void, + ) -> &'static mut CompressContinueTestContext { + unsafe { &mut *context.cast::() } + } + + unsafe extern "C" fn compress_continue_test_header( + context: *mut c_void, + _dst: *mut c_void, + _dst_capacity: usize, + ) -> usize { + let context = unsafe { compress_continue_test_context(context) }; + context.header_calls += 1; + context.header_result + } + + unsafe extern "C" fn compress_continue_test_window( + context: *mut c_void, + _src: *const c_void, + _src_size: usize, + ) { + let context = unsafe { compress_continue_test_context(context) }; + context.update_window_calls += 1; + } + + unsafe extern "C" fn compress_continue_test_overflow( + context: *mut c_void, + _src: *const c_void, + _src_size: usize, + ) { + let context = unsafe { compress_continue_test_context(context) }; + context.overflow_calls += 1; + } + + unsafe extern "C" fn compress_continue_test_frame( + context: *mut c_void, + _dst: *mut c_void, + _dst_capacity: usize, + _src: *const c_void, + _src_size: usize, + last_frame_chunk: c_uint, + ) -> usize { + let context = unsafe { compress_continue_test_context(context) }; + context.frame_calls += 1; + context.last_frame_chunk = last_frame_chunk; + context.frame_result + } + + unsafe extern "C" fn compress_continue_test_block( + context: *mut c_void, + _dst: *mut c_void, + _dst_capacity: usize, + _src: *const c_void, + _src_size: usize, + _last_frame_chunk: c_uint, + ) -> usize { + let context = unsafe { compress_continue_test_context(context) }; + context.block_calls += 1; + context.block_result + } + + fn compress_continue_test_state( + context: &mut CompressContinueTestContext, + stage: &mut c_int, + consumed_src_size: &mut u64, + produced_c_size: &mut u64, + pledged_src_size_plus_one: u64, + block_size_max: usize, + check_block_size: c_int, + ) -> ZSTD_rust_compressContinueState { + ZSTD_rust_compressContinueState { + callback_context: (context as *mut CompressContinueTestContext).cast(), + write_frame_header: compress_continue_test_header, + update_window: compress_continue_test_window, + correct_overflow: compress_continue_test_overflow, + compress_frame_chunk: compress_continue_test_frame, + compress_block: compress_continue_test_block, + stage, + consumed_src_size, + produced_c_size, + pledged_src_size_plus_one, + block_size_max, + check_block_size, + } + } + + #[test] + fn compress_continue_starts_frame_and_updates_progression() { + let mut context = CompressContinueTestContext { + header_result: 4, + frame_result: 6, + ..CompressContinueTestContext::default() + }; + let mut stage = ZSTD_COMPRESSION_STAGE_INIT; + let mut consumed = 7; + let mut produced = 11; + let state = compress_continue_test_state( + &mut context, + &mut stage, + &mut consumed, + &mut produced, + 0, + 16, + 0, + ); + let source = [0x11u8; 5]; + let mut output = [0xa5u8; 32]; + + let result = unsafe { + ZSTD_rust_compressContinue( + &state, + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + 1, + 1, + ) + }; + + assert_eq!(result, 10); + assert_eq!(stage, ZSTD_COMPRESSION_STAGE_ONGOING); + assert_eq!(consumed, 12); + assert_eq!(produced, 21); + assert_eq!(context.header_calls, 1); + assert_eq!(context.update_window_calls, 1); + assert_eq!(context.overflow_calls, 0); + assert_eq!(context.frame_calls, 1); + assert_eq!(context.block_calls, 0); + assert_eq!(context.last_frame_chunk, 1); + } + + #[test] + fn compress_continue_empty_frame_only_writes_header() { + let mut context = CompressContinueTestContext { + header_result: 3, + ..CompressContinueTestContext::default() + }; + let mut stage = ZSTD_COMPRESSION_STAGE_INIT; + let mut consumed = 7; + let mut produced = 11; + let state = compress_continue_test_state( + &mut context, + &mut stage, + &mut consumed, + &mut produced, + 0, + 16, + 0, + ); + let mut output = [0xa5u8; 8]; + + let result = unsafe { + ZSTD_rust_compressContinue( + &state, + output.as_mut_ptr().cast(), + output.len(), + ptr::null(), + 0, + 1, + 0, + ) + }; + + assert_eq!(result, 3); + assert_eq!(stage, ZSTD_COMPRESSION_STAGE_ONGOING); + assert_eq!((consumed, produced), (7, 11)); + assert_eq!(context.header_calls, 1); + assert_eq!(context.update_window_calls, 0); + assert_eq!(context.frame_calls, 0); + assert_eq!(output, [0xa5; 8]); + } + + #[test] + fn compress_continue_rejects_invalid_stage_and_oversized_block_before_callbacks() { + let mut context = CompressContinueTestContext::default(); + let mut stage = ZSTD_COMPRESSION_STAGE_INIT; + let mut consumed = 4; + let mut produced = 9; + let mut state = compress_continue_test_state( + &mut context, + &mut stage, + &mut consumed, + &mut produced, + 0, + 4, + 1, + ); + let source = [0x22u8; 5]; + let mut output = [0xa5u8; 16]; + + let oversized = unsafe { + ZSTD_rust_compressContinue( + &state, + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + 0, + 0, + ) + }; + assert_eq!(oversized, ERROR(ZstdErrorCode::SrcSizeWrong)); + assert_eq!(stage, ZSTD_COMPRESSION_STAGE_INIT); + assert_eq!((consumed, produced), (4, 9)); + assert_eq!(context.update_window_calls, 0); + assert_eq!(context.block_calls, 0); + + state.check_block_size = 0; + stage = ZSTD_COMPRESSION_STAGE_CREATED; + let created = unsafe { + ZSTD_rust_compressContinue( + &state, + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + 0, + 0, + ) + }; + assert_eq!(created, ERROR(ZstdErrorCode::StageWrong)); + assert_eq!(stage, ZSTD_COMPRESSION_STAGE_CREATED); + assert_eq!(context.update_window_calls, 0); + assert_eq!(context.block_calls, 0); + } + + #[test] + fn compress_continue_propagates_block_error_without_progression() { + let mut context = CompressContinueTestContext { + block_result: ERROR(ZstdErrorCode::DstSizeTooSmall), + ..CompressContinueTestContext::default() + }; + let mut stage = ZSTD_COMPRESSION_STAGE_ONGOING; + let mut consumed = 4; + let mut produced = 9; + let state = compress_continue_test_state( + &mut context, + &mut stage, + &mut consumed, + &mut produced, + 0, + 16, + 0, + ); + let source = [0x33u8; 5]; + let mut output = [0xa5u8; 16]; + + let result = unsafe { + ZSTD_rust_compressContinue( + &state, + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + 0, + 0, + ) + }; + + assert_eq!(result, ERROR(ZstdErrorCode::DstSizeTooSmall)); + assert_eq!( + (stage, consumed, produced), + (ZSTD_COMPRESSION_STAGE_ONGOING, 4, 9) + ); + assert_eq!(context.update_window_calls, 1); + assert_eq!(context.overflow_calls, 1); + assert_eq!(context.block_calls, 1); + } + + #[test] + fn compress_continue_reports_pledge_overrun_after_updating_counters() { + let mut context = CompressContinueTestContext { + block_result: 3, + ..CompressContinueTestContext::default() + }; + let mut stage = ZSTD_COMPRESSION_STAGE_ONGOING; + let mut consumed = 0; + let mut produced = 0; + let state = compress_continue_test_state( + &mut context, + &mut stage, + &mut consumed, + &mut produced, + 6, + 16, + 0, + ); + let source = [0x44u8; 6]; + let mut output = [0xa5u8; 16]; + + let result = unsafe { + ZSTD_rust_compressContinue( + &state, + output.as_mut_ptr().cast(), + output.len(), + source.as_ptr().cast(), + source.len(), + 0, + 0, + ) + }; + + assert_eq!(result, ERROR(ZstdErrorCode::SrcSizeWrong)); + assert_eq!((consumed, produced), (6, 3)); + assert_eq!(context.block_calls, 1); + } + #[test] fn simple_strategy_follows_source_size_tiers() { assert_eq!(ZSTD_rust_compressCCtxStrategy(0, 1), ZSTD_FAST);