feat(compress): move sequence-store validation to Rust

Keep the C DEBUGLEVEL gate and delegate the read-only match-length validation through the existing SeqStore ABI. Preserve long-match side-band decoding and the original 3-or-4 byte lower-bound assertion.

Test Plan:

- cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression

- cargo clippy --manifest-path rust/Cargo.toml --no-default-features --features compression --all-targets

- cargo +nightly fmt --manifest-path rust/Cargo.toml

- make -B -C lib -j2 lib

- make -C tests -j2 fuzzer

- tests/fuzzer -s4560 -t47 -i48 -v
This commit is contained in:
2026-07-18 08:28:49 +02:00
parent 56ae331f09
commit 7f22db844d
2 changed files with 66 additions and 9 deletions
+2 -9
View File
@@ -277,6 +277,7 @@ void ZSTD_rust_storeLastLiterals(SeqStore_t* seqStorePtr,
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);
size_t ZSTD_rust_convertSequencesNoRepcodes(
SeqDef* dstSeqs, const ZSTD_Sequence* inSeqs, size_t nbSequences);
BlockSummary ZSTD_rust_get1BlockSummary(const ZSTD_Sequence* seqs,
@@ -2348,15 +2349,7 @@ static size_t ZSTD_fastSequenceLengthSum(ZSTD_Sequence const* seqBuf, size_t seq
static void ZSTD_validateSeqStore(const SeqStore_t* seqStore, const ZSTD_compressionParameters* cParams)
{
#if DEBUGLEVEL >= 1
const SeqDef* seq = seqStore->sequencesStart;
const SeqDef* const seqEnd = seqStore->sequences;
size_t const matchLenLowerBound = cParams->minMatch == 3 ? 3 : 4;
for (; seq < seqEnd; ++seq) {
const ZSTD_SequenceLength seqLength = ZSTD_getSequenceLength(seqStore, seq);
assert(seqLength.matchLength >= matchLenLowerBound);
(void)seqLength;
(void)matchLenLowerBound;
}
ZSTD_rust_validateSeqStore(seqStore, (U32)cParams->minMatch);
#else
(void)seqStore;
(void)cParams;
+64
View File
@@ -1151,6 +1151,31 @@ pub unsafe extern "C" fn ZSTD_rust_fastSequenceLengthSum(
lit_len_sum.wrapping_add(match_len_sum)
}
/// Validates the match lengths produced in a sequence store. This is the
/// Rust leaf for C's `ZSTD_validateSeqStore()`; the C wrapper retains the
/// `DEBUGLEVEL` gate so release builds keep the original no-op behavior.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_validateSeqStore(
seq_store: *const SeqStore_t,
min_match: c_uint,
) {
let seq_store = unsafe { &*seq_store };
let match_len_lower_bound = if min_match == 3 { 3 } else { 4 };
let nb_sequences =
unsafe { seq_store.sequences.offset_from(seq_store.sequencesStart) } as usize;
for index in 0..nb_sequences {
let sequence = unsafe { *seq_store.sequencesStart.add(index) };
let mut match_length = sequence.mlBase as usize + MINMATCH;
if index == seq_store.longLengthPos as usize
&& seq_store.longLengthType == ZSTD_LLT_MATCH_LENGTH
{
match_length += 0x10000;
}
assert!(match_length >= match_len_lower_bound);
}
}
/// Returns whether all bytes in the input have the same value.
///
/// This keeps the original C helper's precondition that `src` points to at
@@ -3378,6 +3403,45 @@ mod tests {
);
}
#[test]
fn validate_seq_store_accepts_ordinary_match_lengths() {
let mut fixture = TestSeqStore::new(
vec![
SeqDef {
offBase: 1,
litLength: 0,
mlBase: 0,
},
SeqDef {
offBase: 1,
litLength: 0,
mlBase: 1,
},
],
vec![],
);
let seq_store = fixture.seq_store();
unsafe { ZSTD_rust_validateSeqStore(&seq_store, 3) };
}
#[test]
fn validate_seq_store_accounts_for_long_match_metadata() {
let mut fixture = TestSeqStore::new(
vec![SeqDef {
offBase: 1,
litLength: 0,
mlBase: 0,
}],
vec![],
);
let mut seq_store = fixture.seq_store();
seq_store.longLengthType = ZSTD_LLT_MATCH_LENGTH;
seq_store.longLengthPos = 0;
unsafe { ZSTD_rust_validateSeqStore(&seq_store, 4) };
}
#[test]
fn post_process_rejects_external_count_overflow() {
let mut out = [ZSTD_Sequence {