From 3b989ab3ee475a842cf2ba2029249e89125c8ccc Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 19 Jul 2026 10:12:21 +0200 Subject: [PATCH] feat(compress): move sequence API orchestration into Rust Move the high-level orchestration of ZSTD_compressSequences() and ZSTD_compressSequencesAndLiterals() into Rust. Rust now owns the validation precedence, CCtx initialization handoff, frame-header and checksum ordering, block-loop dispatch, and output accounting for both public sequence APIs. Keep the private CCtx, sequence-store, block-state, checksum, and conversion layouts in C. C supplies scalar block projections and callbacks for private initialization, frame-header emission, checksum operations, and sequence-state preparation, preserving the ABI boundary and the existing codec leaves. Test Plan: - focused sequence API policy tests: 4 passed - cargo test --manifest-path rust/Cargo.toml --all-targets -- --test-threads=1: 533 passed - cargo test --manifest-path rust/cli/Cargo.toml --all-targets -- --test-threads=1: 169 passed - legacy compression/decompression/dictionary-builder feature matrix: 588 passed - six library/CLI clippy gates with -D warnings - capped serial native library/program rebuilds, 41 CLI tests, Rust library smoke, and full test-zstd round trips - capped serial stress gates: 278 fuzzer cases, 84+129+143 zstream cases, and 1,601 decode-corpus cases - every heavyweight command used CARGO_BUILD_JOBS=1 or make -j1 and ulimit -v 41943040; no worker/native process remained afterward Commit is intentionally unsigned because GPG pinentry hangs in this non-interactive environment. --- lib/compress/zstd_compress.c | 380 ++++++++++++++++++-------------- rust/src/zstd_compress.rs | 415 ++++++++++++++++++++++++++++++++++- 2 files changed, 626 insertions(+), 169 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 6caf369c1..ae4666096 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -1021,6 +1021,72 @@ typedef char ZSTD_rust_sequence_literals_state_layout[ == 6 * sizeof(void*) + 4 * sizeof(int) + 3 * sizeof(void*)) ? 1 : -1]; +/* The public sequence APIs keep their CCtx initialization, frame header, and + * checksum state in C, but Rust owns the ordering and output accounting. The + * two block-loop projections above are populated by the initialization + * callback after the private CCtx has been initialized. */ +typedef struct ZSTD_rust_sequenceApiState_s ZSTD_rust_sequenceApiState; +typedef size_t (*ZSTD_rust_sequenceApiInit_f)( + void* context, size_t pledgedSrcSize, + ZSTD_rust_sequenceApiState* state); +typedef size_t (*ZSTD_rust_sequenceApiWriteFrameHeader_f)( + void* context, void* dst, size_t dstCapacity, size_t pledgedSrcSize); +typedef void (*ZSTD_rust_sequenceApiUpdateChecksum_f)( + void* context, const void* src, size_t srcSize); +typedef U32 (*ZSTD_rust_sequenceApiDigestChecksum_f)(void* context); +typedef void (*ZSTD_rust_sequenceApiWriteChecksum_f)( + void* context, void* dst, U32 checksum); + +struct ZSTD_rust_sequenceApiState_s { + void* callbackContext; + ZSTD_rust_sequenceCompressionState* sequenceState; + ZSTD_rust_sequenceLiteralsState* sequenceLiteralsState; + ZSTD_rust_sequenceApiInit_f init; + ZSTD_rust_sequenceApiWriteFrameHeader_f writeFrameHeader; + ZSTD_rust_sequenceApiUpdateChecksum_f updateChecksum; + ZSTD_rust_sequenceApiDigestChecksum_f digestChecksum; + ZSTD_rust_sequenceApiWriteChecksum_f writeChecksum; + int checksumFlag; + int blockDelimiters; + int validateSequences; +}; +size_t ZSTD_rust_compressSequences( + ZSTD_rust_sequenceApiState* state, + void* dst, size_t dstCapacity, + const ZSTD_Sequence* inSeqs, size_t inSeqsSize, + const void* src, size_t srcSize); +size_t ZSTD_rust_compressSequencesAndLiterals( + ZSTD_rust_sequenceApiState* state, + void* dst, size_t dstCapacity, + const ZSTD_Sequence* inSeqs, size_t inSeqsSize, + const void* literals, size_t litSize, size_t litCapacity, + size_t decompressedSize); +typedef char ZSTD_rust_sequence_api_state_layout[ + (offsetof(ZSTD_rust_sequenceApiState, callbackContext) == 0 + && offsetof(ZSTD_rust_sequenceApiState, sequenceState) + == sizeof(void*) + && offsetof(ZSTD_rust_sequenceApiState, sequenceLiteralsState) + == 2 * sizeof(void*) + && offsetof(ZSTD_rust_sequenceApiState, init) + == 3 * sizeof(void*) + && offsetof(ZSTD_rust_sequenceApiState, writeFrameHeader) + == 4 * sizeof(void*) + && offsetof(ZSTD_rust_sequenceApiState, updateChecksum) + == 5 * sizeof(void*) + && offsetof(ZSTD_rust_sequenceApiState, digestChecksum) + == 6 * sizeof(void*) + && offsetof(ZSTD_rust_sequenceApiState, writeChecksum) + == 7 * sizeof(void*) + && offsetof(ZSTD_rust_sequenceApiState, checksumFlag) + == 8 * sizeof(void*) + && offsetof(ZSTD_rust_sequenceApiState, blockDelimiters) + == 8 * sizeof(void*) + sizeof(int) + && offsetof(ZSTD_rust_sequenceApiState, validateSequences) + == 8 * sizeof(void*) + 2 * sizeof(int) + && sizeof(ZSTD_rust_sequenceApiState) + == (sizeof(void*) == 8 ? 80 : 44)) + ? 1 : -1]; + typedef char ZSTD_rust_stats_seqdef_layout[(sizeof(SeqDef) == 8) ? 1 : -1]; typedef char ZSTD_rust_stats_block_summary_layout[ (sizeof(BlockSummary) == 3 * sizeof(size_t)) ? 1 : -1]; @@ -5387,100 +5453,6 @@ ZSTD_transferSequences_wBlockDelim(ZSTD_CCtx* cctx, ZSTD_hasExtSeqProd(&cctx->appliedParams)); } -/* Compress all provided sequences, block-by-block. - * - * Returns the cumulative size of all compressed blocks (including their headers), - * otherwise a ZSTD error. - */ -static size_t -ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const ZSTD_Sequence* inSeqs, size_t inSeqsSize, - const void* src, size_t srcSize) -{ - U32 dictSize; - ZSTD_rust_sequenceCompressionState state; - - if (cctx->cdict) { - dictSize = (U32)cctx->cdict->dictContentSize; - } else if (cctx->prefixDict.dict) { - dictSize = (U32)cctx->prefixDict.dictSize; - } else { - dictSize = 0; - } - - state.seqStore = &cctx->seqStore; - state.prevCBlock = &cctx->blockState.prevCBlock; - state.nextCBlock = &cctx->blockState.nextCBlock; - state.tmpWorkspace = cctx->tmpWorkspace; - state.tmpWkspSize = cctx->tmpWkspSize; - state.blockSizeMax = cctx->blockSizeMax; - state.bmi2 = cctx->bmi2; - state.blockDelimiters = (int)cctx->appliedParams.blockDelimiters; - state.strategy = (int)cctx->appliedParams.cParams.strategy; - state.disableLiteralCompression = ZSTD_literalsCompressionIsDisabled(&cctx->appliedParams); - state.searchForExternalRepcodes = (int)cctx->appliedParams.searchForExternalRepcodes; - state.validateSequences = cctx->appliedParams.validateSequences; - state.minMatch = cctx->appliedParams.cParams.minMatch; - state.windowLog = cctx->appliedParams.cParams.windowLog; - state.dictSize = dictSize; - state.useSequenceProducer = ZSTD_hasExtSeqProd(&cctx->appliedParams); - state.isFirstBlock = &cctx->isFirstBlock; - - DEBUGLOG(4, "ZSTD_compressSequences_internal srcSize: %zu, inSeqsSize: %zu", srcSize, inSeqsSize); - return ZSTD_rust_compressSequencesInternal(&state, dst, dstCapacity, - inSeqs, inSeqsSize, src, srcSize); -} - -size_t ZSTD_compressSequences(ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const ZSTD_Sequence* inSeqs, size_t inSeqsSize, - const void* src, size_t srcSize) -{ - BYTE* op = (BYTE*)dst; - size_t cSize = 0; - - /* Transparent initialization stage, same as compressStream2() */ - DEBUGLOG(4, "ZSTD_compressSequences (nbSeqs=%zu,dstCapacity=%zu)", inSeqsSize, dstCapacity); - assert(cctx != NULL); - FORWARD_IF_ERROR(ZSTD_CCtx_init_compressStream2(cctx, ZSTD_e_end, srcSize), "CCtx initialization failed"); - - /* Begin writing output, starting with frame header */ - { size_t const frameHeaderSize = ZSTD_writeFrameHeader(op, dstCapacity, - &cctx->appliedParams, srcSize, cctx->dictID); - op += frameHeaderSize; - assert(frameHeaderSize <= dstCapacity); - dstCapacity -= frameHeaderSize; - cSize += frameHeaderSize; - } - if (cctx->appliedParams.fParams.checksumFlag && srcSize) { - XXH64_update(&cctx->xxhState, src, srcSize); - } - - /* Now generate compressed blocks */ - { size_t const cBlocksSize = ZSTD_compressSequences_internal(cctx, - op, dstCapacity, - inSeqs, inSeqsSize, - src, srcSize); - FORWARD_IF_ERROR(cBlocksSize, "Compressing blocks failed!"); - cSize += cBlocksSize; - assert(cBlocksSize <= dstCapacity); - dstCapacity -= cBlocksSize; - } - - /* Complete with frame checksum, if needed */ - if (cctx->appliedParams.fParams.checksumFlag) { - U32 const checksum = (U32) XXH64_digest(&cctx->xxhState); - RETURN_ERROR_IF(dstCapacity<4, dstSize_tooSmall, "no room for checksum"); - DEBUGLOG(4, "Write checksum : %08X", (unsigned)checksum); - MEM_writeLE32((char*)dst + cSize, checksum); - cSize += 4; - } - - DEBUGLOG(4, "Final compressed size: %zu", cSize); - return cSize; -} - /* The public symbol remains C-facing for fullbench and the sequence API, but * the sequence-store conversion itself is Rust-owned. */ size_t ZSTD_convertBlockSequences(ZSTD_CCtx* cctx, @@ -5505,41 +5477,151 @@ static size_t ZSTD_convertBlockSequencesForRust( repcodeResolution); } -BlockSummary ZSTD_get1BlockSummary(const ZSTD_Sequence* seqs, size_t nbSeqs) +static void ZSTD_rust_sequenceApi_prepareStates( + ZSTD_CCtx* cctx, + ZSTD_rust_sequenceCompressionState* sequenceState, + ZSTD_rust_sequenceLiteralsState* sequenceLiteralsState) { - return ZSTD_rust_get1BlockSummary(seqs, nbSeqs); -} + U32 dictSize; + if (cctx->cdict) { + dictSize = (U32)cctx->cdict->dictContentSize; + } else if (cctx->prefixDict.dict) { + dictSize = (U32)cctx->prefixDict.dictSize; + } else { + dictSize = 0; + } -static size_t -ZSTD_compressSequencesAndLiterals_internal(ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const ZSTD_Sequence* inSeqs, size_t nbSequences, - const void* literals, size_t litSize, size_t srcSize) -{ - ZSTD_rust_sequenceLiteralsState state; - int const repcodeResolution = - (cctx->appliedParams.searchForExternalRepcodes == ZSTD_ps_enable); + sequenceState->seqStore = &cctx->seqStore; + sequenceState->prevCBlock = &cctx->blockState.prevCBlock; + sequenceState->nextCBlock = &cctx->blockState.nextCBlock; + sequenceState->tmpWorkspace = cctx->tmpWorkspace; + sequenceState->tmpWkspSize = cctx->tmpWkspSize; + sequenceState->blockSizeMax = cctx->blockSizeMax; + sequenceState->bmi2 = cctx->bmi2; + sequenceState->blockDelimiters = (int)cctx->appliedParams.blockDelimiters; + sequenceState->strategy = (int)cctx->appliedParams.cParams.strategy; + sequenceState->disableLiteralCompression = + ZSTD_literalsCompressionIsDisabled(&cctx->appliedParams); + sequenceState->searchForExternalRepcodes = + (int)cctx->appliedParams.searchForExternalRepcodes; + sequenceState->validateSequences = cctx->appliedParams.validateSequences; + sequenceState->minMatch = cctx->appliedParams.cParams.minMatch; + sequenceState->windowLog = cctx->appliedParams.cParams.windowLog; + sequenceState->dictSize = dictSize; + sequenceState->useSequenceProducer = ZSTD_hasExtSeqProd(&cctx->appliedParams); + sequenceState->isFirstBlock = &cctx->isFirstBlock; assert(cctx->appliedParams.searchForExternalRepcodes != ZSTD_ps_auto); - state.seqStore = &cctx->seqStore; - state.prevCBlock = &cctx->blockState.prevCBlock; - state.nextCBlock = &cctx->blockState.nextCBlock; - state.tmpWorkspace = cctx->tmpWorkspace; - state.tmpWkspSize = cctx->tmpWkspSize; - state.blockSizeMax = cctx->blockSizeMax; - state.bmi2 = cctx->bmi2; - state.strategy = (int)cctx->appliedParams.cParams.strategy; - state.disableLiteralCompression = + sequenceLiteralsState->seqStore = &cctx->seqStore; + sequenceLiteralsState->prevCBlock = &cctx->blockState.prevCBlock; + sequenceLiteralsState->nextCBlock = &cctx->blockState.nextCBlock; + sequenceLiteralsState->tmpWorkspace = cctx->tmpWorkspace; + sequenceLiteralsState->tmpWkspSize = cctx->tmpWkspSize; + sequenceLiteralsState->blockSizeMax = cctx->blockSizeMax; + sequenceLiteralsState->bmi2 = cctx->bmi2; + sequenceLiteralsState->strategy = (int)cctx->appliedParams.cParams.strategy; + sequenceLiteralsState->disableLiteralCompression = ZSTD_literalsCompressionIsDisabled(&cctx->appliedParams); - state.repcodeResolution = repcodeResolution; - state.isFirstBlock = &cctx->isFirstBlock; - state.callbackContext = cctx; - state.convertBlockSequences = ZSTD_convertBlockSequencesForRust; + sequenceLiteralsState->repcodeResolution = + (cctx->appliedParams.searchForExternalRepcodes == ZSTD_ps_enable); + sequenceLiteralsState->isFirstBlock = &cctx->isFirstBlock; + sequenceLiteralsState->callbackContext = cctx; + sequenceLiteralsState->convertBlockSequences = + ZSTD_convertBlockSequencesForRust; +} - return ZSTD_rust_compressSequencesAndLiteralsInternal( - &state, dst, dstCapacity, inSeqs, nbSequences, literals, litSize, - srcSize); +static size_t ZSTD_rust_sequenceApi_init( + void* context, size_t pledgedSrcSize, + ZSTD_rust_sequenceApiState* state) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; + size_t initResult; + + if (cctx == NULL || state == NULL + || state->sequenceState == NULL + || state->sequenceLiteralsState == NULL) { + return ERROR(GENERIC); + } + + initResult = ZSTD_CCtx_init_compressStream2( + cctx, ZSTD_e_end, pledgedSrcSize); + if (ERR_isError(initResult)) { + return initResult; + } + + ZSTD_rust_sequenceApi_prepareStates( + cctx, state->sequenceState, state->sequenceLiteralsState); + state->checksumFlag = cctx->appliedParams.fParams.checksumFlag; + state->blockDelimiters = (int)cctx->appliedParams.blockDelimiters; + state->validateSequences = cctx->appliedParams.validateSequences; + return 0; +} + +static size_t ZSTD_rust_sequenceApi_writeFrameHeader( + void* context, void* dst, size_t dstCapacity, size_t pledgedSrcSize) +{ + ZSTD_CCtx const* const cctx = (ZSTD_CCtx const*)context; + return ZSTD_writeFrameHeader(dst, dstCapacity, &cctx->appliedParams, + pledgedSrcSize, cctx->dictID); +} + +static void ZSTD_rust_sequenceApi_updateChecksum( + void* context, const void* src, size_t srcSize) +{ + ZSTD_CCtx* const cctx = (ZSTD_CCtx*)context; + (void)XXH64_update(&cctx->xxhState, src, srcSize); +} + +static U32 ZSTD_rust_sequenceApi_digestChecksum(void* context) +{ + ZSTD_CCtx const* const cctx = (ZSTD_CCtx const*)context; + return (U32)XXH64_digest(&cctx->xxhState); +} + +static void ZSTD_rust_sequenceApi_writeChecksum( + void* context, void* dst, U32 checksum) +{ + (void)context; + DEBUGLOG(4, "Write checksum : %08X", (unsigned)checksum); + MEM_writeLE32((char*)dst, checksum); +} + +static void ZSTD_rust_sequenceApi_initState( + ZSTD_rust_sequenceApiState* state, + ZSTD_CCtx* cctx, + ZSTD_rust_sequenceCompressionState* sequenceState, + ZSTD_rust_sequenceLiteralsState* sequenceLiteralsState) +{ + state->callbackContext = cctx; + state->sequenceState = sequenceState; + state->sequenceLiteralsState = sequenceLiteralsState; + state->init = ZSTD_rust_sequenceApi_init; + state->writeFrameHeader = ZSTD_rust_sequenceApi_writeFrameHeader; + state->updateChecksum = ZSTD_rust_sequenceApi_updateChecksum; + state->digestChecksum = ZSTD_rust_sequenceApi_digestChecksum; + state->writeChecksum = ZSTD_rust_sequenceApi_writeChecksum; + state->checksumFlag = 0; + state->blockDelimiters = 0; + state->validateSequences = 0; +} + +size_t ZSTD_compressSequences(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const ZSTD_Sequence* inSeqs, size_t inSeqsSize, + const void* src, size_t srcSize) +{ + ZSTD_rust_sequenceCompressionState sequenceState; + ZSTD_rust_sequenceLiteralsState sequenceLiteralsState; + ZSTD_rust_sequenceApiState state; + + DEBUGLOG(4, "ZSTD_compressSequences (nbSeqs=%zu,dstCapacity=%zu)", + inSeqsSize, dstCapacity); + assert(cctx != NULL); + ZSTD_rust_sequenceApi_initState( + &state, cctx, &sequenceState, &sequenceLiteralsState); + return ZSTD_rust_compressSequences( + &state, dst, dstCapacity, inSeqs, inSeqsSize, src, srcSize); } size_t @@ -5549,49 +5631,23 @@ ZSTD_compressSequencesAndLiterals(ZSTD_CCtx* cctx, const void* literals, size_t litSize, size_t litCapacity, size_t decompressedSize) { - BYTE* op = (BYTE*)dst; - size_t cSize = 0; + ZSTD_rust_sequenceCompressionState sequenceState; + ZSTD_rust_sequenceLiteralsState sequenceLiteralsState; + ZSTD_rust_sequenceApiState state; - /* Transparent initialization stage, same as compressStream2() */ - DEBUGLOG(4, "ZSTD_compressSequencesAndLiterals (dstCapacity=%zu)", dstCapacity); + DEBUGLOG(4, "ZSTD_compressSequencesAndLiterals (dstCapacity=%zu)", + dstCapacity); assert(cctx != NULL); - if (litCapacity < litSize) { - RETURN_ERROR(workSpace_tooSmall, "literals buffer is not large enough: must be at least 8 bytes larger than litSize (risk of read out-of-bound)"); - } - FORWARD_IF_ERROR(ZSTD_CCtx_init_compressStream2(cctx, ZSTD_e_end, decompressedSize), "CCtx initialization failed"); + ZSTD_rust_sequenceApi_initState( + &state, cctx, &sequenceState, &sequenceLiteralsState); + return ZSTD_rust_compressSequencesAndLiterals( + &state, dst, dstCapacity, inSeqs, inSeqsSize, literals, litSize, + litCapacity, decompressedSize); +} - if (cctx->appliedParams.blockDelimiters == ZSTD_sf_noBlockDelimiters) { - RETURN_ERROR(frameParameter_unsupported, "This mode is only compatible with explicit delimiters"); - } - if (cctx->appliedParams.validateSequences) { - RETURN_ERROR(parameter_unsupported, "This mode is not compatible with Sequence validation"); - } - if (cctx->appliedParams.fParams.checksumFlag) { - RETURN_ERROR(frameParameter_unsupported, "this mode is not compatible with frame checksum"); - } - - /* Begin writing output, starting with frame header */ - { size_t const frameHeaderSize = ZSTD_writeFrameHeader(op, dstCapacity, - &cctx->appliedParams, decompressedSize, cctx->dictID); - op += frameHeaderSize; - assert(frameHeaderSize <= dstCapacity); - dstCapacity -= frameHeaderSize; - cSize += frameHeaderSize; - } - - /* Now generate compressed blocks */ - { size_t const cBlocksSize = ZSTD_compressSequencesAndLiterals_internal(cctx, - op, dstCapacity, - inSeqs, inSeqsSize, - literals, litSize, decompressedSize); - FORWARD_IF_ERROR(cBlocksSize, "Compressing blocks failed!"); - cSize += cBlocksSize; - assert(cBlocksSize <= dstCapacity); - dstCapacity -= cBlocksSize; - } - - DEBUGLOG(4, "Final compressed size: %zu", cSize); - return cSize; +BlockSummary ZSTD_get1BlockSummary(const ZSTD_Sequence* seqs, size_t nbSeqs) +{ + return ZSTD_rust_get1BlockSummary(seqs, nbSeqs); } /*====== Finalize ======*/ diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index a2900af0c..63dd42494 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -1443,6 +1443,107 @@ const _: () = { ); }; +type SequenceApiInitFn = + unsafe extern "C" fn(*mut c_void, usize, *mut ZSTD_rust_sequenceApiState) -> usize; +type SequenceApiWriteFrameHeaderFn = + unsafe extern "C" fn(*mut c_void, *mut c_void, usize, usize) -> usize; +type SequenceApiUpdateChecksumFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize); +type SequenceApiDigestChecksumFn = unsafe extern "C" fn(*mut c_void) -> c_uint; +type SequenceApiWriteChecksumFn = unsafe extern "C" fn(*mut c_void, *mut c_void, c_uint); + +/// Explicit projection for the public sequence-compression API orchestration. +/// +/// Rust owns validation ordering, frame-header/checksum sequencing, and +/// output accounting. C retains the private CCtx, sequence-store, block, +/// and checksum layouts through the two block-state projections and callbacks. +#[repr(C)] +pub struct ZSTD_rust_sequenceApiState { + callback_context: *mut c_void, + sequence_state: *mut ZSTD_rust_sequenceCompressionState, + sequence_literals_state: *mut ZSTD_rust_sequenceLiteralsState, + init: SequenceApiInitFn, + write_frame_header: SequenceApiWriteFrameHeaderFn, + update_checksum: SequenceApiUpdateChecksumFn, + digest_checksum: SequenceApiDigestChecksumFn, + write_checksum: SequenceApiWriteChecksumFn, + checksum_flag: c_int, + block_delimiters: c_int, + validate_sequences: c_int, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_sequenceApiState, callback_context) == 0); + assert!(offset_of!(ZSTD_rust_sequenceApiState, sequence_state) == size_of::()); + assert!( + offset_of!(ZSTD_rust_sequenceApiState, sequence_literals_state) == 2 * size_of::() + ); + assert!(offset_of!(ZSTD_rust_sequenceApiState, init) == 3 * size_of::()); + assert!(offset_of!(ZSTD_rust_sequenceApiState, write_frame_header) == 4 * size_of::()); + assert!(offset_of!(ZSTD_rust_sequenceApiState, update_checksum) == 5 * size_of::()); + assert!(offset_of!(ZSTD_rust_sequenceApiState, digest_checksum) == 6 * size_of::()); + assert!(offset_of!(ZSTD_rust_sequenceApiState, write_checksum) == 7 * size_of::()); + assert!(offset_of!(ZSTD_rust_sequenceApiState, checksum_flag) == size_of::<[usize; 8]>()); + assert!( + offset_of!(ZSTD_rust_sequenceApiState, block_delimiters) + == size_of::<[usize; 8]>() + size_of::() + ); + assert!( + offset_of!(ZSTD_rust_sequenceApiState, validate_sequences) + == size_of::<[usize; 8]>() + 2 * size_of::() + ); + assert!( + size_of::() == if size_of::() == 8 { 80 } else { 44 } + ); +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +struct SequenceApiPlan { + update_input_checksum: bool, + append_frame_checksum: bool, +} + +/// Apply the public sequence API's post-initialization validation policy. +/// +/// The order is intentional and matches the C entry point: the literals +/// variant rejects no-delimiter mode first, then sequence validation, then a +/// frame checksum. The ordinary variant permits all three independently. +fn sequence_api_plan( + with_literals: bool, + block_delimiters: c_int, + validate_sequences: c_int, + checksum_flag: c_int, +) -> Result { + if with_literals { + if block_delimiters == ZSTD_SF_NO_BLOCK_DELIMITERS { + return Err(ERROR(ZstdErrorCode::FrameParameterUnsupported)); + } + if validate_sequences != 0 { + return Err(ERROR(ZstdErrorCode::ParameterUnsupported)); + } + if checksum_flag != 0 { + return Err(ERROR(ZstdErrorCode::FrameParameterUnsupported)); + } + return Ok(SequenceApiPlan { + update_input_checksum: false, + append_frame_checksum: false, + }); + } + + Ok(SequenceApiPlan { + update_input_checksum: checksum_flag != 0, + append_frame_checksum: checksum_flag != 0, + }) +} + +#[inline] +fn sequence_api_validate_literal_capacity(lit_size: usize, lit_capacity: usize) -> usize { + if lit_capacity < lit_size { + ERROR(ZstdErrorCode::WorkSpaceTooSmall) + } else { + 0 + } +} + /// Explicit projection of the state used by `ZSTD_compressSeqStore_singleBlock`. /// /// Sequence-store construction and split discovery remain in C. Only the @@ -3685,10 +3786,10 @@ unsafe fn write_empty_sequence_block(dst: *mut u8, dst_capacity: usize) -> usize /// Rust implementation of the per-block loop from /// `ZSTD_compressSequences_internal()`. /// -/// Context initialization, frame-header/checksum handling, and public API -/// validation remain in C. The projected state keeps the ABI explicit while -/// allowing the loop to reuse the existing Rust sequence-transfer, entropy, -/// and block-serialization leaves. +/// Public initialization, frame-header/checksum ordering, and validation are +/// driven by the Rust API orchestrator below. The projected state keeps the +/// ABI explicit while allowing this loop to reuse the existing Rust +/// sequence-transfer, entropy, and block-serialization leaves. #[no_mangle] pub unsafe extern "C" fn ZSTD_rust_compressSequencesInternal( state: *const ZSTD_rust_sequenceCompressionState, @@ -3926,9 +4027,10 @@ unsafe fn write_empty_sequence_literals_block(dst: *mut u8, dst_capacity: usize) /// Rust implementation of the block loop from /// ZSTD_compressSequencesAndLiterals_internal. /// -/// The C wrapper retains public-API initialization and the CCtx-dependent -/// sequence conversion callback. Rust owns the external literal cursor, -/// per-block entropy pass, compressed-block framing, and completion checks. +/// The C wrapper retains the private CCtx-dependent state and sequence +/// conversion callback. Rust owns the public validation/ordering layer, the +/// external literal cursor, per-block entropy pass, compressed-block framing, +/// and completion checks. #[no_mangle] pub unsafe extern "C" fn ZSTD_rust_compressSequencesAndLiteralsInternal( state: *const ZSTD_rust_sequenceLiteralsState, @@ -4086,6 +4188,251 @@ pub unsafe extern "C" fn ZSTD_rust_compressSequencesAndLiteralsInternal( c_size } +unsafe fn sequence_api_prepare( + state: &mut ZSTD_rust_sequenceApiState, + pledged_src_size: usize, + with_literals: bool, + lit_size: usize, + lit_capacity: usize, +) -> Result { + if with_literals { + let capacity_result = sequence_api_validate_literal_capacity(lit_size, lit_capacity); + if ERR_isError(capacity_result) { + return Err(capacity_result); + } + } + + if state.callback_context.is_null() + || state.sequence_state.is_null() + || state.sequence_literals_state.is_null() + || state.init as usize == 0 + || state.write_frame_header as usize == 0 + || state.update_checksum as usize == 0 + || state.digest_checksum as usize == 0 + || state.write_checksum as usize == 0 + { + return Err(ERROR(ZstdErrorCode::Generic)); + } + + let init_result = unsafe { + (state.init)( + state.callback_context, + pledged_src_size, + state as *mut ZSTD_rust_sequenceApiState, + ) + }; + if ERR_isError(init_result) { + return Err(init_result); + } + + sequence_api_plan( + with_literals, + state.block_delimiters, + state.validate_sequences, + state.checksum_flag, + ) +} + +unsafe fn sequence_api_write_frame_header( + state: &ZSTD_rust_sequenceApiState, + op: &mut *mut u8, + dst_capacity: &mut usize, + c_size: &mut usize, + pledged_src_size: usize, +) -> usize { + let frame_header_size = unsafe { + (state.write_frame_header)( + state.callback_context, + (*op).cast(), + *dst_capacity, + pledged_src_size, + ) + }; + if ERR_isError(frame_header_size) { + return frame_header_size; + } + if frame_header_size > *dst_capacity { + return ERROR(ZstdErrorCode::DstSizeTooSmall); + } + + unsafe { + *op = (*op).add(frame_header_size); + } + *dst_capacity -= frame_header_size; + *c_size = c_size.wrapping_add(frame_header_size); + 0 +} + +unsafe fn sequence_api_append_frame_checksum( + state: &ZSTD_rust_sequenceApiState, + plan: SequenceApiPlan, + op: *mut u8, + dst_capacity: usize, + c_size: &mut usize, +) -> usize { + if !plan.append_frame_checksum { + return 0; + } + + /* Keep the original ordering: digest the checksum before checking the + * remaining destination capacity. */ + let checksum = unsafe { (state.digest_checksum)(state.callback_context) }; + if dst_capacity < 4 { + return ERROR(ZstdErrorCode::DstSizeTooSmall); + } + unsafe { + (state.write_checksum)(state.callback_context, op.cast(), checksum); + } + *c_size = c_size.wrapping_add(4); + 0 +} + +/// Rust-owned orchestration for `ZSTD_compressSequences`. +/// +/// The C wrapper provides the post-initialization block-state projection and +/// callbacks for the private CCtx/header/checksum operations. Rust preserves +/// the public ordering: initialize, write the frame header, update the input +/// checksum, emit blocks, then append the frame checksum. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_compressSequences( + state: *mut ZSTD_rust_sequenceApiState, + dst: *mut c_void, + dst_capacity: usize, + in_seqs: *const ZSTD_Sequence, + in_seqs_size: usize, + src: *const c_void, + src_size: usize, +) -> usize { + if state.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + let state = unsafe { &mut *state }; + let plan = match unsafe { sequence_api_prepare(state, src_size, false, 0, 0) } { + Ok(plan) => plan, + Err(result) => return result, + }; + + let mut op = dst.cast::(); + let mut dst_capacity = dst_capacity; + let mut c_size = 0usize; + let header_result = unsafe { + sequence_api_write_frame_header(state, &mut op, &mut dst_capacity, &mut c_size, src_size) + }; + if ERR_isError(header_result) { + return header_result; + } + + if plan.update_input_checksum && src_size != 0 { + unsafe { (state.update_checksum)(state.callback_context, src, src_size) }; + } + + let block_size = unsafe { + ZSTD_rust_compressSequencesInternal( + &*state.sequence_state, + op.cast(), + dst_capacity, + in_seqs, + in_seqs_size, + src, + src_size, + ) + }; + if ERR_isError(block_size) { + return block_size; + } + if block_size > dst_capacity { + return ERROR(ZstdErrorCode::DstSizeTooSmall); + } + unsafe { + op = op.add(block_size); + } + dst_capacity -= block_size; + c_size = c_size.wrapping_add(block_size); + + let checksum_result = + unsafe { sequence_api_append_frame_checksum(state, plan, op, dst_capacity, &mut c_size) }; + if ERR_isError(checksum_result) { + return checksum_result; + } + + c_size +} + +/// Rust-owned orchestration for `ZSTD_compressSequencesAndLiterals`. +/// +/// The literal-capacity check intentionally precedes CCtx initialization, and +/// the post-initialization incompatibility checks retain their original +/// precedence. The existing Rust block loop remains responsible for literal +/// accounting, sequence conversion, and block codec operations. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_compressSequencesAndLiterals( + state: *mut ZSTD_rust_sequenceApiState, + dst: *mut c_void, + dst_capacity: usize, + in_seqs: *const ZSTD_Sequence, + in_seqs_size: usize, + literals: *const c_void, + lit_size: usize, + lit_capacity: usize, + decompressed_size: usize, +) -> usize { + if state.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + let state = unsafe { &mut *state }; + let plan = match unsafe { + sequence_api_prepare(state, decompressed_size, true, lit_size, lit_capacity) + } { + Ok(plan) => plan, + Err(result) => return result, + }; + + let mut op = dst.cast::(); + let mut dst_capacity = dst_capacity; + let mut c_size = 0usize; + let header_result = unsafe { + sequence_api_write_frame_header( + state, + &mut op, + &mut dst_capacity, + &mut c_size, + decompressed_size, + ) + }; + if ERR_isError(header_result) { + return header_result; + } + + let block_size = unsafe { + ZSTD_rust_compressSequencesAndLiteralsInternal( + &*state.sequence_literals_state, + op.cast(), + dst_capacity, + in_seqs, + in_seqs_size, + literals, + lit_size, + decompressed_size, + ) + }; + if ERR_isError(block_size) { + return block_size; + } + if block_size > dst_capacity { + return ERROR(ZstdErrorCode::DstSizeTooSmall); + } + dst_capacity -= block_size; + c_size = c_size.wrapping_add(block_size); + + let checksum_result = + unsafe { sequence_api_append_frame_checksum(state, plan, op, dst_capacity, &mut c_size) }; + if ERR_isError(checksum_result) { + return checksum_result; + } + + c_size +} + unsafe fn compress_frame( dst: *mut c_void, dst_capacity: usize, @@ -4546,6 +4893,7 @@ pub unsafe extern "C" fn ZSTD_compressStream2( #[cfg(test)] mod tests { use super::*; + use crate::errors::ERR_getErrorCode; use std::io::Write; use std::process::{Command, Stdio}; @@ -6992,6 +7340,59 @@ mod tests { ); } + #[test] + fn sequence_api_plan_places_input_and_frame_checksums_around_blocks() { + assert_eq!( + sequence_api_plan(false, ZSTD_SF_NO_BLOCK_DELIMITERS, 1, 1), + Ok(SequenceApiPlan { + update_input_checksum: true, + append_frame_checksum: true, + }) + ); + } + + #[test] + fn sequence_api_plan_disables_checksums_for_literals_variant() { + assert_eq!( + sequence_api_plan(true, ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS, 0, 0), + Ok(SequenceApiPlan { + update_input_checksum: false, + append_frame_checksum: false, + }) + ); + } + + #[test] + fn sequence_api_plan_preserves_literals_validation_precedence() { + assert_eq!( + ERR_getErrorCode( + sequence_api_plan(true, ZSTD_SF_NO_BLOCK_DELIMITERS, 1, 1).unwrap_err() + ), + ZstdErrorCode::FrameParameterUnsupported as i32 + ); + assert_eq!( + ERR_getErrorCode( + sequence_api_plan(true, ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS, 1, 1).unwrap_err() + ), + ZstdErrorCode::ParameterUnsupported as i32 + ); + assert_eq!( + ERR_getErrorCode( + sequence_api_plan(true, ZSTD_SF_EXPLICIT_BLOCK_DELIMITERS, 0, 1).unwrap_err() + ), + ZstdErrorCode::FrameParameterUnsupported as i32 + ); + } + + #[test] + fn sequence_api_literal_capacity_check_precedes_initialization() { + assert_eq!( + sequence_api_validate_literal_capacity(9, 8), + ERROR(ZstdErrorCode::WorkSpaceTooSmall) + ); + assert_eq!(sequence_api_validate_literal_capacity(8, 9), 0); + } + #[test] fn sequence_copier_selector_returns_no_delimiters_mode() { assert_eq!(