From 9a68253710fa429feff1cef6affc028d4096f0b6 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 19 Jul 2026 14:41:09 +0200 Subject: [PATCH] feat(compress): move external producer success path into Rust Move block-level external sequence producer invocation, result post-processing, source-length validation, and transfer ordering into a Rust-owned ABI leaf. Retain the private CCtx transfer callback and C-side fallback/block-compressor selection, preserving producer-error fallback eligibility and direct invalid-sequence/transfer failures. Test Plan: - cargo test --manifest-path rust/Cargo.toml --lib - cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings - make -B -C programs -j1 zstd - make -C tests -j1 test-zstream ZSTREAM_TESTTIME=-T1s - focused external_sequence_producer unit tests --- lib/compress/zstd_compress.c | 139 +++++++---- rust/src/zstd_compress.rs | 464 ++++++++++++++++++++++++++++++++++- 2 files changed, 545 insertions(+), 58 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 1fac39aec..d187d709e 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -1163,14 +1163,9 @@ void ZSTD_rust_confirmRepcodesAndEntropyTables( void ZSTD_rust_storeLastLiterals(SeqStore_t* seqStorePtr, const BYTE* anchor, size_t lastLLSize); void ZSTD_rust_resetSeqStore(SeqStore_t* ssPtr); -size_t ZSTD_rust_fastSequenceLengthSum(const ZSTD_Sequence* seqBuf, - size_t seqBufSize); void ZSTD_rust_validateSeqStore(const SeqStore_t* seqStore, U32 minMatch); BlockSummary ZSTD_rust_get1BlockSummary(const ZSTD_Sequence* seqs, size_t nbSeqs); -size_t ZSTD_rust_postProcessSequenceProducerResult( - ZSTD_Sequence* outSeqs, size_t nbExternalSeqs, - size_t outSeqsCapacity, size_t srcSize); size_t ZSTD_rust_deriveBlockSplits( U32* partitions, U32 nbSeq, const SeqStore_t* originalSeqStore, @@ -1344,6 +1339,57 @@ typedef char ZSTD_rust_build_seq_store_state_layout[ == 7 * sizeof(void*) + sizeof(U32) + sizeof(int)) ? 1 : -1]; +typedef size_t (*ZSTD_rust_externalSequenceTransfer_f)( + void* context, ZSTD_SequencePosition* seqPos, + const ZSTD_Sequence* inSeqs, size_t inSeqsSize, + const void* src, size_t blockSize); +typedef struct { + void* callbackContext; + void* producerState; + ZSTD_sequenceProducer_F producer; + ZSTD_Sequence* extSeqBuf; + const size_t* extSeqBufCapacity; + const void* src; + const size_t* srcSize; + const int* compressionLevel; + const size_t* windowSize; + ZSTD_rust_externalSequenceTransfer_f transfer; + size_t* externalSeqCount; + int* seqStoreComplete; + int* allowFallback; +} ZSTD_rust_externalSequenceProducerState; +size_t ZSTD_rust_tryExternalSequenceProducer( + const ZSTD_rust_externalSequenceProducerState* state); +typedef char ZSTD_rust_external_sequence_producer_state_layout[ + (offsetof(ZSTD_rust_externalSequenceProducerState, callbackContext) == 0 + && offsetof(ZSTD_rust_externalSequenceProducerState, producerState) + == sizeof(void*) + && offsetof(ZSTD_rust_externalSequenceProducerState, producer) + == 2 * sizeof(void*) + && offsetof(ZSTD_rust_externalSequenceProducerState, extSeqBuf) + == 3 * sizeof(void*) + && offsetof(ZSTD_rust_externalSequenceProducerState, extSeqBufCapacity) + == 4 * sizeof(void*) + && offsetof(ZSTD_rust_externalSequenceProducerState, src) + == 5 * sizeof(void*) + && offsetof(ZSTD_rust_externalSequenceProducerState, srcSize) + == 6 * sizeof(void*) + && offsetof(ZSTD_rust_externalSequenceProducerState, compressionLevel) + == 7 * sizeof(void*) + && offsetof(ZSTD_rust_externalSequenceProducerState, windowSize) + == 8 * sizeof(void*) + && offsetof(ZSTD_rust_externalSequenceProducerState, transfer) + == 9 * sizeof(void*) + && offsetof(ZSTD_rust_externalSequenceProducerState, externalSeqCount) + == 10 * sizeof(void*) + && offsetof(ZSTD_rust_externalSequenceProducerState, seqStoreComplete) + == 11 * sizeof(void*) + && offsetof(ZSTD_rust_externalSequenceProducerState, allowFallback) + == 12 * sizeof(void*) + && sizeof(ZSTD_rust_externalSequenceProducerState) + == 13 * sizeof(void*)) + ? 1 : -1]; + /* The sequence-compression loop receives only the state it actually reads or * updates. In particular, neither ZSTD_CCtx nor a C function pointer crosses * the Rust ABI. */ @@ -3419,29 +3465,6 @@ void ZSTD_resetSeqStore(SeqStore_t* ssPtr) ZSTD_rust_resetSeqStore(ssPtr); } -/* ZSTD_postProcessSequenceProducerResult() : - * Validates and post-processes sequences obtained through the external matchfinder API: - * - Checks whether nbExternalSeqs represents an error condition. - * - Appends a block delimiter to outSeqs if one is not already present. - * See zstd.h for context regarding block delimiters. - * Returns the number of sequences after post-processing, or an error code. */ -static size_t ZSTD_postProcessSequenceProducerResult( - ZSTD_Sequence* outSeqs, size_t nbExternalSeqs, size_t outSeqsCapacity, size_t srcSize -) { - return ZSTD_rust_postProcessSequenceProducerResult( - outSeqs, nbExternalSeqs, outSeqsCapacity, srcSize); -} - -/* ZSTD_fastSequenceLengthSum() : - * Returns sum(litLen) + sum(matchLen) + lastLits for *seqBuf*. - * Similar to another function in zstd_compress.c (determine_blockSize), - * except it doesn't check for a block delimiter to end summation. - * Removing the early exit allows the compiler to auto-vectorize (https://godbolt.org/z/cY1cajz9P). - * This function can be deleted and replaced by determine_blockSize after we resolve issue #3456. */ -static size_t ZSTD_fastSequenceLengthSum(ZSTD_Sequence const* seqBuf, size_t seqBufSize) { - return ZSTD_rust_fastSequenceLengthSum(seqBuf, seqBufSize); -} - static size_t ZSTD_transferSequences_wBlockDelim(ZSTD_CCtx* cctx, ZSTD_SequencePosition* seqPos, @@ -3449,6 +3472,17 @@ ZSTD_transferSequences_wBlockDelim(ZSTD_CCtx* cctx, const void* src, size_t blockSize, ZSTD_ParamSwitch_e externalRepSearch); +static size_t ZSTD_rust_externalSequenceProducer_transfer( + void* context, ZSTD_SequencePosition* seqPos, + const ZSTD_Sequence* inSeqs, size_t inSeqsSize, + const void* src, size_t blockSize) +{ + ZSTD_CCtx* const zc = (ZSTD_CCtx*)context; + return ZSTD_transferSequences_wBlockDelim( + zc, seqPos, inSeqs, inSeqsSize, src, blockSize, + zc->appliedParams.searchForExternalRepcodes); +} + typedef enum { ZSTDbss_compress, ZSTDbss_noCompress } ZSTD_BuildSeqStore_e; static void ZSTD_rust_buildSeqStore_skipSmallBlock(void* context, size_t srcSize) @@ -3537,41 +3571,38 @@ static size_t ZSTD_rust_buildSeqStore_selectSequences( zc->appliedParams.useRowMatchFinder, src, srcSize); assert(ldmSeqStore.pos == ldmSeqStore.size); } else if (ZSTD_hasExtSeqProd(&zc->appliedParams)) { + size_t nbExternalSeqs = 0; + int allowFallback = 0; + ZSTD_rust_externalSequenceProducerState state; + size_t const windowSize = (size_t)1 << zc->appliedParams.cParams.windowLog; assert(zc->extSeqBufCapacity >= ZSTD_sequenceBound(srcSize)); assert(zc->appliedParams.extSeqProdFunc != NULL); - { U32 const windowSize = (U32)1 << zc->appliedParams.cParams.windowLog; - size_t const nbExternalSeqs = (zc->appliedParams.extSeqProdFunc)( - zc->appliedParams.extSeqProdState, - zc->extSeqBuf, zc->extSeqBufCapacity, - src, srcSize, NULL, 0, - zc->appliedParams.compressionLevel, windowSize); - size_t const nbPostProcessedSeqs = ZSTD_postProcessSequenceProducerResult( - zc->extSeqBuf, nbExternalSeqs, - zc->extSeqBufCapacity, srcSize); + state.callbackContext = zc; + state.producerState = zc->appliedParams.extSeqProdState; + state.producer = zc->appliedParams.extSeqProdFunc; + state.extSeqBuf = zc->extSeqBuf; + state.extSeqBufCapacity = &zc->extSeqBufCapacity; + state.src = src; + state.srcSize = &srcSize; + state.compressionLevel = &zc->appliedParams.compressionLevel; + state.windowSize = &windowSize; + state.transfer = ZSTD_rust_externalSequenceProducer_transfer; + state.externalSeqCount = &nbExternalSeqs; + state.seqStoreComplete = seqStoreComplete; + state.allowFallback = &allowFallback; - /* Return early if there is no error, since the delimiter already - * carries the block's final literals. */ - if (!ZSTD_isError(nbPostProcessedSeqs)) { - ZSTD_SequencePosition seqPos = {0,0,0}; - size_t const seqLenSum = ZSTD_fastSequenceLengthSum( - zc->extSeqBuf, nbPostProcessedSeqs); - RETURN_ERROR_IF(seqLenSum > srcSize, externalSequences_invalid, - "External sequences imply too large a block!"); - FORWARD_IF_ERROR(ZSTD_transferSequences_wBlockDelim( - zc, &seqPos, zc->extSeqBuf, nbPostProcessedSeqs, - src, srcSize, - zc->appliedParams.searchForExternalRepcodes), - "Failed to copy external sequences to seqStore!"); + { size_t const producerResult = + ZSTD_rust_tryExternalSequenceProducer(&state); + if (*seqStoreComplete) { ms->ldmSeqStore = NULL; - *seqStoreComplete = 1; DEBUGLOG(5, "Copied %lu sequences from external sequence producer to internal seqStore.", (unsigned long)nbExternalSeqs); - return 0; + return producerResult; } - if (!zc->appliedParams.enableMatchFinderFallback) - return nbPostProcessedSeqs; + if (!allowFallback || !zc->appliedParams.enableMatchFinderFallback) + return producerResult; { ZSTD_BlockCompressor_f const blockCompressor = ZSTD_selectBlockCompressor( diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index 40f33a67b..27e3bb516 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -37,8 +37,9 @@ use crate::zstd_compress_stats::{ ZSTD_rust_convertSequencesNoRepcodes, ZSTD_rust_copyBlockSequences, ZSTD_rust_countSeqStoreLiteralsBytes, ZSTD_rust_countSeqStoreMatchBytes, ZSTD_rust_deriveSeqStoreChunk, ZSTD_rust_determineBlockSize, ZSTD_rust_entropyCompressSeqStore, - ZSTD_rust_entropyCompressSeqStore_internal, ZSTD_rust_finalizeOffBase, - ZSTD_rust_get1BlockSummary, ZSTD_rust_isRLE, ZSTD_rust_maybeRLE, ZSTD_rust_resetSeqStore, + ZSTD_rust_entropyCompressSeqStore_internal, ZSTD_rust_fastSequenceLengthSum, + ZSTD_rust_finalizeOffBase, ZSTD_rust_get1BlockSummary, ZSTD_rust_isRLE, ZSTD_rust_maybeRLE, + ZSTD_rust_postProcessSequenceProducerResult, ZSTD_rust_resetSeqStore, ZSTD_rust_seqStore_resolveOffCodes, ZSTD_rust_storeLastLiterals, ZSTD_rust_transferSequencesNoDelim, ZSTD_rust_transferSequencesWBlockDelim, ZSTD_rust_validateSeqStore, ZSTD_LLT_LITERAL_LENGTH, ZSTD_LLT_MATCH_LENGTH, @@ -184,8 +185,8 @@ type BuildSeqStoreSelectFn = unsafe extern "C" fn( /// Explicit projection for the sequence-store builder. /// /// Rust owns the threshold/reset/repcode/literal-store orchestration. The -/// callbacks retain the private matchfinder, LDM, and external sequence -/// producer operations in C without passing `ZSTD_CCtx` across the ABI. +/// callbacks retain the private matchfinder, LDM, and external-producer +/// fallback operations in C without passing `ZSTD_CCtx` across the ABI. #[repr(C)] pub struct ZSTD_rust_buildSeqStoreState { seq_store: *mut SeqStore_t, @@ -227,6 +228,181 @@ const _: () = { ); }; +type ExternalSequenceProducerFn = unsafe extern "C" fn( + *mut c_void, + *mut ZSTD_Sequence, + usize, + *const c_void, + usize, + *const c_void, + usize, + c_int, + usize, +) -> usize; +type ExternalSequenceTransferFn = unsafe extern "C" fn( + *mut c_void, + *mut ZSTD_SequencePosition, + *const ZSTD_Sequence, + usize, + *const c_void, + usize, +) -> usize; + +/// Projection for the successful block-level external sequence-producer path. +/// +/// Rust owns producer invocation, result post-processing, length validation, +/// and transfer ordering. The transfer callback retains the private CCtx +/// match-state projection; C owns fallback selection after this leaf returns. +#[repr(C)] +pub struct ZSTD_rust_externalSequenceProducerState { + callback_context: *mut c_void, + producer_state: *mut c_void, + producer: Option, + ext_seq_buf: *mut ZSTD_Sequence, + ext_seq_buf_capacity: *const usize, + src: *const c_void, + src_size: *const usize, + compression_level: *const c_int, + window_size: *const usize, + transfer: Option, + external_seq_count: *mut usize, + seq_store_complete: *mut c_int, + allow_fallback: *mut c_int, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_externalSequenceProducerState, callback_context) == 0); + assert!( + offset_of!(ZSTD_rust_externalSequenceProducerState, producer_state) == size_of::() + ); + assert!( + offset_of!(ZSTD_rust_externalSequenceProducerState, producer) == 2 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_externalSequenceProducerState, ext_seq_buf) == 3 * size_of::() + ); + assert!( + offset_of!( + ZSTD_rust_externalSequenceProducerState, + ext_seq_buf_capacity + ) == 4 * size_of::() + ); + assert!(offset_of!(ZSTD_rust_externalSequenceProducerState, src) == 5 * size_of::()); + assert!( + offset_of!(ZSTD_rust_externalSequenceProducerState, src_size) == 6 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_externalSequenceProducerState, compression_level) + == 7 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_externalSequenceProducerState, window_size) == size_of::<[usize; 8]>() + ); + assert!( + offset_of!(ZSTD_rust_externalSequenceProducerState, transfer) == 9 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_externalSequenceProducerState, external_seq_count) + == 10 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_externalSequenceProducerState, seq_store_complete) + == 11 * size_of::() + ); + assert!( + offset_of!(ZSTD_rust_externalSequenceProducerState, allow_fallback) + == 12 * size_of::() + ); + assert!(size_of::() == 13 * size_of::()); +}; + +/// Try an external sequence producer; C retains only the fallback decision. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_tryExternalSequenceProducer( + state: *const ZSTD_rust_externalSequenceProducerState, +) -> usize { + if state.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + let state = unsafe { &*state }; + let Some(producer) = state.producer else { + return ERROR(ZstdErrorCode::Generic); + }; + let Some(transfer) = state.transfer else { + return ERROR(ZstdErrorCode::Generic); + }; + if state.ext_seq_buf.is_null() + || state.ext_seq_buf_capacity.is_null() + || state.src.is_null() + || state.src_size.is_null() + || state.compression_level.is_null() + || state.window_size.is_null() + || state.external_seq_count.is_null() + || state.seq_store_complete.is_null() + || state.allow_fallback.is_null() + { + return ERROR(ZstdErrorCode::Generic); + } + + unsafe { + *state.seq_store_complete = 0; + *state.allow_fallback = 0; + } + let ext_seq_buf_capacity = unsafe { *state.ext_seq_buf_capacity }; + let src_size = unsafe { *state.src_size }; + let nb_external_seqs = unsafe { + producer( + state.producer_state, + state.ext_seq_buf, + ext_seq_buf_capacity, + state.src, + src_size, + ptr::null(), + 0, + *state.compression_level, + *state.window_size, + ) + }; + unsafe { *state.external_seq_count = nb_external_seqs }; + + let nb_post_processed_seqs = unsafe { + ZSTD_rust_postProcessSequenceProducerResult( + state.ext_seq_buf, + nb_external_seqs, + ext_seq_buf_capacity, + src_size, + ) + }; + if ERR_isError(nb_post_processed_seqs) { + unsafe { *state.allow_fallback = 1 }; + return nb_post_processed_seqs; + } + + let seq_len_sum = + unsafe { ZSTD_rust_fastSequenceLengthSum(state.ext_seq_buf, nb_post_processed_seqs) }; + if seq_len_sum > src_size { + return ERROR(ZstdErrorCode::ExternalSequencesInvalid); + } + + let mut seq_pos = ZSTD_SequencePosition::default(); + let transfer_result = unsafe { + transfer( + state.callback_context, + &mut seq_pos, + state.ext_seq_buf, + nb_post_processed_seqs, + state.src, + src_size, + ) + }; + if ERR_isError(transfer_result) { + return transfer_result; + } + + unsafe { *state.seq_store_complete = 1 }; + 0 +} + /// Explicit projection of the state used by `ZSTD_compress_frameChunk`. /// /// The Rust side owns the per-frame block loop and its savings/dispatch @@ -6038,6 +6214,286 @@ mod tests { const ZSTD_BTOPT: c_int = 7; const ZSTD_BTULTRA2: c_int = 9; + #[derive(Default)] + struct ExternalSequenceProducerProbe { + events: Vec<&'static str>, + sequences: Vec, + producer_result: usize, + producer_dict: *const c_void, + producer_dict_size: usize, + producer_level: c_int, + producer_window_size: usize, + transfer_sequence_count: usize, + transfer_src_size: usize, + transfer_result: usize, + } + + unsafe fn external_sequence_producer_probe( + context: *mut c_void, + ) -> &'static mut ExternalSequenceProducerProbe { + unsafe { &mut *context.cast::() } + } + + unsafe extern "C" fn external_sequence_producer_test_producer( + context: *mut c_void, + out_seqs: *mut ZSTD_Sequence, + out_seqs_capacity: usize, + _src: *const c_void, + _src_size: usize, + dict: *const c_void, + dict_size: usize, + compression_level: c_int, + window_size: usize, + ) -> usize { + let probe = unsafe { external_sequence_producer_probe(context) }; + probe.events.push("produce"); + probe.producer_dict = dict; + probe.producer_dict_size = dict_size; + probe.producer_level = compression_level; + probe.producer_window_size = window_size; + assert!(probe.sequences.len() <= out_seqs_capacity); + unsafe { + ptr::copy_nonoverlapping(probe.sequences.as_ptr(), out_seqs, probe.sequences.len()); + } + probe.producer_result + } + + unsafe extern "C" fn external_sequence_producer_test_transfer( + context: *mut c_void, + _seq_pos: *mut ZSTD_SequencePosition, + _in_seqs: *const ZSTD_Sequence, + in_seqs_size: usize, + _src: *const c_void, + block_size: usize, + ) -> usize { + let probe = unsafe { external_sequence_producer_probe(context) }; + probe.events.push("transfer"); + probe.transfer_sequence_count = in_seqs_size; + probe.transfer_src_size = block_size; + probe.transfer_result + } + + fn external_sequence_producer_test_state( + probe: &mut ExternalSequenceProducerProbe, + ext_seq_buf: &mut [ZSTD_Sequence], + ext_seq_buf_capacity: &usize, + src: &[u8], + src_size: &usize, + compression_level: &c_int, + window_size: &usize, + external_seq_count: &mut usize, + seq_store_complete: &mut c_int, + allow_fallback: &mut c_int, + ) -> ZSTD_rust_externalSequenceProducerState { + let callback_context = (probe as *mut ExternalSequenceProducerProbe).cast(); + ZSTD_rust_externalSequenceProducerState { + callback_context, + producer_state: callback_context, + producer: Some(external_sequence_producer_test_producer), + ext_seq_buf: ext_seq_buf.as_mut_ptr(), + ext_seq_buf_capacity, + src: src.as_ptr().cast(), + src_size, + compression_level, + window_size, + transfer: Some(external_sequence_producer_test_transfer), + external_seq_count, + seq_store_complete, + allow_fallback, + } + } + + #[test] + fn external_sequence_producer_success_transfers_after_post_processing() { + let source = [1u8, 2, 3, 4, 5]; + let mut probe = ExternalSequenceProducerProbe { + sequences: vec![ZSTD_Sequence { + offset: 0, + litLength: source.len() as u32, + matchLength: 0, + rep: 0, + }], + producer_result: 1, + ..Default::default() + }; + let mut ext_seq_buf = [ZSTD_Sequence { + offset: 0, + litLength: 0, + matchLength: 0, + rep: 0, + }; 2]; + let ext_seq_buf_capacity = ext_seq_buf.len(); + let src_size = source.len(); + let compression_level = 7; + let window_size = 1 << 20; + let mut external_seq_count = 0; + let mut seq_store_complete = 0; + let mut allow_fallback = 0; + let state = external_sequence_producer_test_state( + &mut probe, + &mut ext_seq_buf, + &ext_seq_buf_capacity, + &source, + &src_size, + &compression_level, + &window_size, + &mut external_seq_count, + &mut seq_store_complete, + &mut allow_fallback, + ); + + let result = unsafe { ZSTD_rust_tryExternalSequenceProducer(&state) }; + + assert_eq!(result, 0); + assert_eq!(probe.events, ["produce", "transfer"]); + assert!(probe.producer_dict.is_null()); + assert_eq!(probe.producer_dict_size, 0); + assert_eq!(probe.producer_level, compression_level); + assert_eq!(probe.producer_window_size, window_size); + assert_eq!(probe.transfer_sequence_count, 1); + assert_eq!(probe.transfer_src_size, source.len()); + assert_eq!(external_seq_count, 1); + assert_eq!(seq_store_complete, 1); + assert_eq!(allow_fallback, 0); + } + + #[test] + fn external_sequence_producer_errors_enable_fallback_before_transfer() { + let source = [1u8, 2, 3]; + let mut probe = ExternalSequenceProducerProbe { + producer_result: ERROR(ZstdErrorCode::SequenceProducerFailed), + ..Default::default() + }; + let mut ext_seq_buf = [ZSTD_Sequence { + offset: 0, + litLength: 0, + matchLength: 0, + rep: 0, + }; 2]; + let ext_seq_buf_capacity = ext_seq_buf.len(); + let src_size = source.len(); + let compression_level = 3; + let window_size = 1 << 20; + let mut external_seq_count = 0; + let mut seq_store_complete = 0; + let mut allow_fallback = 0; + let state = external_sequence_producer_test_state( + &mut probe, + &mut ext_seq_buf, + &ext_seq_buf_capacity, + &source, + &src_size, + &compression_level, + &window_size, + &mut external_seq_count, + &mut seq_store_complete, + &mut allow_fallback, + ); + + let result = unsafe { ZSTD_rust_tryExternalSequenceProducer(&state) }; + + assert_eq!(result, ERROR(ZstdErrorCode::SequenceProducerFailed)); + assert_eq!(probe.events, ["produce"]); + assert_eq!(external_seq_count, result); + assert_eq!(seq_store_complete, 0); + assert_eq!(allow_fallback, 1); + } + + #[test] + fn external_sequence_producer_invalid_length_does_not_enable_fallback() { + let source = [1u8, 2, 3]; + let mut probe = ExternalSequenceProducerProbe { + sequences: vec![ZSTD_Sequence { + offset: 0, + litLength: (source.len() + 1) as u32, + matchLength: 0, + rep: 0, + }], + producer_result: 1, + ..Default::default() + }; + let mut ext_seq_buf = [ZSTD_Sequence { + offset: 0, + litLength: 0, + matchLength: 0, + rep: 0, + }; 2]; + let ext_seq_buf_capacity = ext_seq_buf.len(); + let src_size = source.len(); + let compression_level = 3; + let window_size = 1 << 20; + let mut external_seq_count = 0; + let mut seq_store_complete = 0; + let mut allow_fallback = 0; + let state = external_sequence_producer_test_state( + &mut probe, + &mut ext_seq_buf, + &ext_seq_buf_capacity, + &source, + &src_size, + &compression_level, + &window_size, + &mut external_seq_count, + &mut seq_store_complete, + &mut allow_fallback, + ); + + let result = unsafe { ZSTD_rust_tryExternalSequenceProducer(&state) }; + + assert_eq!(result, ERROR(ZstdErrorCode::ExternalSequencesInvalid)); + assert_eq!(probe.events, ["produce"]); + assert_eq!(seq_store_complete, 0); + assert_eq!(allow_fallback, 0); + } + + #[test] + fn external_sequence_producer_transfer_errors_do_not_enable_fallback() { + let source = [1u8, 2, 3]; + let mut probe = ExternalSequenceProducerProbe { + sequences: vec![ZSTD_Sequence { + offset: 0, + litLength: source.len() as u32, + matchLength: 0, + rep: 0, + }], + producer_result: 1, + transfer_result: ERROR(ZstdErrorCode::ExternalSequencesInvalid), + ..Default::default() + }; + let mut ext_seq_buf = [ZSTD_Sequence { + offset: 0, + litLength: 0, + matchLength: 0, + rep: 0, + }; 2]; + let ext_seq_buf_capacity = ext_seq_buf.len(); + let src_size = source.len(); + let compression_level = 3; + let window_size = 1 << 20; + let mut external_seq_count = 0; + let mut seq_store_complete = 0; + let mut allow_fallback = 0; + let state = external_sequence_producer_test_state( + &mut probe, + &mut ext_seq_buf, + &ext_seq_buf_capacity, + &source, + &src_size, + &compression_level, + &window_size, + &mut external_seq_count, + &mut seq_store_complete, + &mut allow_fallback, + ); + + let result = unsafe { ZSTD_rust_tryExternalSequenceProducer(&state) }; + + assert_eq!(result, ERROR(ZstdErrorCode::ExternalSequencesInvalid)); + assert_eq!(probe.events, ["produce", "transfer"]); + assert_eq!(seq_store_complete, 0); + assert_eq!(allow_fallback, 0); + } + #[derive(Default)] struct Compress2TestContext { events: Vec<&'static str>,