zstd-pure-rs 0.2.0

Pure-Rust port of the Zstandard (zstd) compression library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
//! Port of `SeqStore_t` and the storeSeq helpers from
//! `lib/compress/zstd_compress_internal.h`. This is the buffer the
//! match finder writes into — one `SeqDef` per found match, with the
//! literals packed contiguously in a side array.

#![allow(non_snake_case)]

pub use crate::decompress::zstd_decompress_block::ZSTD_REP_NUM;

/// Upstream MINMATCH.
pub const MINMATCH: u32 = 3;

/// Port of `ZSTD_SequenceLength`. Fully-decoded lit/match lengths
/// returned by `ZSTD_getSequenceLength` after the long-length bit is
/// resolved and `MINMATCH` is added back to matchLength.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ZSTD_SequenceLength {
    pub litLength: u32,
    pub matchLength: u32,
}

/// Port of `SeqDef`. Compact encoded sequence entry:
///   - `offBase`: if > `ZSTD_REP_NUM`, a full offset shifted
///     up by `ZSTD_REP_NUM`. Otherwise a 1/2/3 repcode id.
///   - `litLength`: literal byte count (u16; if it would overflow,
///     the parent `SeqStore_t` sets `longLengthType` + pos).
///   - `mlBase`: matchLength minus `MINMATCH` (u16; same overflow rule).
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SeqDef {
    pub offBase: u32,
    pub litLength: u16,
    pub mlBase: u16,
}

/// Port of `ZSTD_longLengthType_e`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ZSTD_longLengthType_e {
    #[default]
    ZSTD_llt_none,
    ZSTD_llt_literalLength,
    ZSTD_llt_matchLength,
}

/// Port of `SeqStore_t`. Upstream uses owned raw pointers into
/// caller-provided buffers — we keep the buffers inline (owned
/// `Vec`s) plus current-end indices that behave like the upstream
/// `sequences` / `lit` cursors.
#[derive(Debug, Clone)]
pub struct SeqStore_t {
    pub sequences: Vec<SeqDef>,
    pub literals: Vec<u8>,
    pub llCode: Vec<u8>,
    pub mlCode: Vec<u8>,
    pub ofCode: Vec<u8>,
    pub maxNbSeq: usize,
    pub maxNbLit: usize,
    pub longLengthType: ZSTD_longLengthType_e,
    pub longLengthPos: u32,
}

impl SeqStore_t {
    /// Create an empty store with upstream's typical capacities
    /// (maxNbSeq = `ZSTD_BLOCKSIZE_MAX / 3`, maxNbLit = `128 KB`).
    /// The literals buffer is over-allocated by `WILDCOPY_OVERLENGTH`
    /// (32 bytes) so `ZSTD_storeSeq`'s inline 16-byte SIMD wildcopy
    /// can write past `litLength` without checking — mirroring
    /// upstream's `litLimit_w = litLimit - WILDCOPY_OVERLENGTH` slack.
    pub fn with_capacity(maxNbSeq: usize, maxNbLit: usize) -> Self {
        Self {
            sequences: Vec::with_capacity(maxNbSeq),
            literals: Vec::with_capacity(
                maxNbLit + crate::common::zstd_internal::WILDCOPY_OVERLENGTH,
            ),
            llCode: Vec::with_capacity(maxNbSeq),
            mlCode: Vec::with_capacity(maxNbSeq),
            ofCode: Vec::with_capacity(maxNbSeq),
            maxNbSeq,
            maxNbLit,
            longLengthType: ZSTD_longLengthType_e::ZSTD_llt_none,
            longLengthPos: 0,
        }
    }

    pub fn reset(&mut self) {
        self.sequences.clear();
        self.literals.clear();
        self.llCode.clear();
        self.mlCode.clear();
        self.ofCode.clear();
        self.longLengthType = ZSTD_longLengthType_e::ZSTD_llt_none;
        self.longLengthPos = 0;
    }
}

#[inline(always)]
fn extend_literals(dst: &mut Vec<u8>, src: &[u8]) {
    let old_len = dst.len();
    let new_len = old_len + src.len();
    if new_len <= dst.capacity() {
        unsafe {
            core::ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr().add(old_len), src.len());
            dst.set_len(new_len);
        }
    } else {
        dst.extend_from_slice(src);
    }
}

/// Port of `ZSTD_storeLastLiterals`. Appends a trailing-literals run
/// to the seq store — used at the end of a block when the matcher
/// stops before the block boundary.
#[inline]
pub fn ZSTD_storeLastLiterals(ss: &mut SeqStore_t, anchor: &[u8]) {
    extend_literals(&mut ss.literals, anchor);
}

/// Port of `ZSTD_resetSeqStore`. Clears the sequence + literals
/// cursors so the next block can reuse the allocation.
#[inline]
pub fn ZSTD_resetSeqStore(ss: &mut SeqStore_t) {
    ss.sequences.clear();
    ss.literals.clear();
    ss.llCode.clear();
    ss.mlCode.clear();
    ss.ofCode.clear();
    ss.longLengthType = ZSTD_longLengthType_e::ZSTD_llt_none;
}

/// Reuses `dst` to represent the sequence subrange `[startIdx, endIdx)`
/// of `originalSeqStore`. Upstream does this with pointer arithmetic into
/// caller-owned buffers; the Rust store is still owned, so we copy only the
/// active subranges while preserving `dst` allocations.
pub fn ZSTD_deriveSeqStoreChunkInto(
    dst: &mut SeqStore_t,
    originalSeqStore: &SeqStore_t,
    startIdx: usize,
    endIdx: usize,
) {
    debug_assert!(!core::ptr::eq(dst, originalSeqStore));
    debug_assert!(endIdx <= originalSeqStore.sequences.len());
    debug_assert!(startIdx <= endIdx);

    // Sum literals bytes before startIdx (consumed by prior chunks)
    // and within [startIdx, endIdx) (kept for this chunk).
    let prior_lits: usize = originalSeqStore.sequences[..startIdx]
        .iter()
        .enumerate()
        .map(|(i, s)| {
            let mut n = s.litLength as usize;
            if i as u32 == originalSeqStore.longLengthPos
                && originalSeqStore.longLengthType == ZSTD_longLengthType_e::ZSTD_llt_literalLength
            {
                n += 0x10000;
            }
            n
        })
        .sum();

    let chunk_lits: usize = originalSeqStore.sequences[startIdx..endIdx]
        .iter()
        .enumerate()
        .map(|(i, s)| {
            let mut n = s.litLength as usize;
            let orig_idx = startIdx + i;
            if orig_idx as u32 == originalSeqStore.longLengthPos
                && originalSeqStore.longLengthType == ZSTD_longLengthType_e::ZSTD_llt_literalLength
            {
                n += 0x10000;
            }
            n
        })
        .sum();

    let is_last_chunk = endIdx == originalSeqStore.sequences.len();
    let lit_take = if is_last_chunk {
        // Take everything from prior_lits through end (includes trailing literals).
        originalSeqStore.literals.len().saturating_sub(prior_lits)
    } else {
        chunk_lits
    };

    let (longLengthType, longLengthPos) = match originalSeqStore.longLengthType {
        ZSTD_longLengthType_e::ZSTD_llt_none => (ZSTD_longLengthType_e::ZSTD_llt_none, 0),
        _ => {
            let p = originalSeqStore.longLengthPos as usize;
            if p < startIdx || p > endIdx {
                (ZSTD_longLengthType_e::ZSTD_llt_none, 0)
            } else {
                (originalSeqStore.longLengthType, (p - startIdx) as u32)
            }
        }
    };

    dst.sequences.clear();
    dst.sequences
        .extend_from_slice(&originalSeqStore.sequences[startIdx..endIdx]);
    dst.literals.clear();
    dst.literals
        .extend_from_slice(&originalSeqStore.literals[prior_lits..prior_lits + lit_take]);
    dst.llCode.clear();
    if originalSeqStore.llCode.len() >= endIdx {
        dst.llCode
            .extend_from_slice(&originalSeqStore.llCode[startIdx..endIdx]);
    }
    dst.mlCode.clear();
    if originalSeqStore.mlCode.len() >= endIdx {
        dst.mlCode
            .extend_from_slice(&originalSeqStore.mlCode[startIdx..endIdx]);
    }
    dst.ofCode.clear();
    if originalSeqStore.ofCode.len() >= endIdx {
        dst.ofCode
            .extend_from_slice(&originalSeqStore.ofCode[startIdx..endIdx]);
    }
    dst.maxNbSeq = originalSeqStore.maxNbSeq;
    dst.maxNbLit = originalSeqStore.maxNbLit;
    dst.longLengthType = longLengthType;
    dst.longLengthPos = longLengthPos;
}

/// Port of `ZSTD_deriveSeqStoreChunk` (`zstd_compress.c:4025`). Builds
/// a new `SeqStore_t` that represents the sequence subrange
/// `[startIdx, endIdx)` of `originalSeqStore`, used by the block
/// splitter to cost-estimate potential splits.
///
/// `longLengthPos` is rebased or cleared if it falls outside the
/// chunk range, mirroring upstream exactly.
pub fn ZSTD_deriveSeqStoreChunk(
    originalSeqStore: &SeqStore_t,
    startIdx: usize,
    endIdx: usize,
) -> SeqStore_t {
    let mut dst = SeqStore_t {
        sequences: Vec::new(),
        literals: Vec::new(),
        llCode: Vec::new(),
        mlCode: Vec::new(),
        ofCode: Vec::new(),
        maxNbSeq: originalSeqStore.maxNbSeq,
        maxNbLit: originalSeqStore.maxNbLit,
        longLengthType: ZSTD_longLengthType_e::ZSTD_llt_none,
        longLengthPos: 0,
    };
    ZSTD_deriveSeqStoreChunkInto(&mut dst, originalSeqStore, startIdx, endIdx);
    dst
}

/// Port of `ZSTD_countSeqStoreLiteralsBytes` (`zstd_compress.c:3993`).
/// Sum of all sequences' literal-length fields, plus 0x10000 when the
/// long-length flag marks a literal-length overflow.
pub fn ZSTD_countSeqStoreLiteralsBytes(seqStore: &SeqStore_t) -> usize {
    let mut literalsBytes: usize = 0;
    for (i, seq) in seqStore.sequences.iter().enumerate() {
        literalsBytes += seq.litLength as usize;
        if i as u32 == seqStore.longLengthPos
            && seqStore.longLengthType == ZSTD_longLengthType_e::ZSTD_llt_literalLength
        {
            literalsBytes += 0x10000;
        }
    }
    literalsBytes
}

/// Port of `ZSTD_countSeqStoreMatchBytes` (`zstd_compress.c:4008`). Sum
/// of decoded match lengths (`mlBase + MINMATCH`) across the seqStore,
/// plus 0x10000 when the long-length flag marks a match-length overflow.
pub fn ZSTD_countSeqStoreMatchBytes(seqStore: &SeqStore_t) -> usize {
    let mut matchBytes: usize = 0;
    for (i, seq) in seqStore.sequences.iter().enumerate() {
        matchBytes += seq.mlBase as usize + MINMATCH as usize;
        if i as u32 == seqStore.longLengthPos
            && seqStore.longLengthType == ZSTD_longLengthType_e::ZSTD_llt_matchLength
        {
            matchBytes += 0x10000;
        }
    }
    matchBytes
}

/// Port of `ZSTD_getSequenceLength`. Decodes the long-length bit and
/// adds `MINMATCH` back to matchLength, returning both lengths.
///
/// `seqIdx` is the 0-based index of the sequence within
/// `seqStore.sequences` — upstream passes a `SeqDef*`, but since Rust
/// doesn't allow &SeqStore + &SeqDef simultaneously, we pass the
/// index and look up both pieces via the store.
pub fn ZSTD_getSequenceLength(seqStore: &SeqStore_t, seqIdx: usize) -> ZSTD_SequenceLength {
    let seq = seqStore.sequences[seqIdx];
    let mut seqLen = ZSTD_SequenceLength {
        litLength: seq.litLength as u32,
        matchLength: seq.mlBase as u32 + MINMATCH,
    };
    if seqStore.longLengthPos == seqIdx as u32 {
        match seqStore.longLengthType {
            ZSTD_longLengthType_e::ZSTD_llt_literalLength => seqLen.litLength += 0x10000,
            ZSTD_longLengthType_e::ZSTD_llt_matchLength => seqLen.matchLength += 0x10000,
            ZSTD_longLengthType_e::ZSTD_llt_none => {}
        }
    }
    seqLen
}

// ---- offBase helpers (match upstream's macros) ---------------------------

/// Port of `REPCODE_TO_OFFBASE` macro. Encodes a repcode ID (1, 2, or 3)
/// as its offBase value — identity in the low-id half of the sumtype.
#[inline]
pub fn REPCODE_TO_OFFBASE(r: u32) -> u32 {
    debug_assert!(r >= 1 && r <= ZSTD_REP_NUM as u32);
    r
}

/// Port of `OFFSET_TO_OFFBASE` macro. Encodes a positive raw offset as
/// `offset + ZSTD_REP_NUM`, placing it above the repcode reserved range.
#[inline]
pub fn OFFSET_TO_OFFBASE(offset: u32) -> u32 {
    debug_assert!(offset > 0);
    offset.wrapping_add(ZSTD_REP_NUM as u32)
}

/// Port of `OFFBASE_IS_OFFSET` macro. True when `o` carries a full raw
/// offset (`> ZSTD_REP_NUM`) rather than a repcode ID.
#[inline]
pub fn OFFBASE_IS_OFFSET(o: u32) -> bool {
    o > ZSTD_REP_NUM as u32
}

/// Port of `OFFBASE_IS_REPCODE` macro. True when `o` is a repcode ID in
/// `1..=ZSTD_REP_NUM`.
#[inline]
pub fn OFFBASE_IS_REPCODE(o: u32) -> bool {
    1 <= o && o <= ZSTD_REP_NUM as u32
}

/// Port of `OFFBASE_TO_OFFSET` macro. Inverse of `OFFSET_TO_OFFBASE` —
/// strips the `ZSTD_REP_NUM` bias to recover the raw offset.
#[inline]
pub fn OFFBASE_TO_OFFSET(o: u32) -> u32 {
    debug_assert!(OFFBASE_IS_OFFSET(o));
    o - ZSTD_REP_NUM as u32
}

/// Port of `OFFBASE_TO_REPCODE` macro. Inverse of `REPCODE_TO_OFFBASE` —
/// identity for IDs 1/2/3.
#[inline]
pub fn OFFBASE_TO_REPCODE(o: u32) -> u32 {
    debug_assert!(OFFBASE_IS_REPCODE(o));
    o
}

/// Port of `ZSTD_storeSeqOnly`. Appends a `SeqDef` to the store
/// without copying literal bytes. Long litLength / mlBase are
/// flagged by setting `longLengthType` + `longLengthPos`.
#[inline(always)]
pub fn ZSTD_storeSeqOnly(
    seqStore: &mut SeqStore_t,
    litLength: usize,
    offBase: u32,
    matchLength: usize,
) {
    debug_assert!(seqStore.sequences.len() < seqStore.maxNbSeq);
    debug_assert!(litLength <= crate::decompress::zstd_decompress_block::ZSTD_BLOCKSIZE_MAX);
    debug_assert!(matchLength >= MINMATCH as usize);
    debug_assert!(matchLength <= crate::decompress::zstd_decompress_block::ZSTD_BLOCKSIZE_MAX);

    // litLength may exceed U16; flag as long.
    if litLength > 0xFFFF {
        debug_assert_eq!(
            seqStore.longLengthType,
            ZSTD_longLengthType_e::ZSTD_llt_none
        );
        seqStore.longLengthType = ZSTD_longLengthType_e::ZSTD_llt_literalLength;
        seqStore.longLengthPos = seqStore.sequences.len() as u32;
    }
    let mlBase = matchLength - MINMATCH as usize;
    if mlBase > 0xFFFF {
        debug_assert_eq!(
            seqStore.longLengthType,
            ZSTD_longLengthType_e::ZSTD_llt_none
        );
        seqStore.longLengthType = ZSTD_longLengthType_e::ZSTD_llt_matchLength;
        seqStore.longLengthPos = seqStore.sequences.len() as u32;
    }
    seqStore.sequences.push(SeqDef {
        offBase,
        litLength: litLength as u16,
        mlBase: mlBase as u16,
    });
}

/// Port of `ZSTD_storeSeq`. Writes a full sequence — appends
/// `litLength` bytes from `literals` into the store's literal
/// buffer, then calls `ZSTD_storeSeqOnly`.
///
/// Mirrors upstream's `ZSTD_copy16` + `ZSTD_wildcopy` pattern: literal
/// runs are typically short (avg 5–10 bytes for silesia L1), and going
/// through `Vec::extend_from_slice` → glibc memmove dispatch is far
/// slower than an inline 16-byte SIMD copy. The destination buffer is
/// over-allocated by `WILDCOPY_OVERLENGTH` so a 16-byte write past
/// `litLength` never overruns capacity. The source slice's overrun
/// protection is the responsibility of the caller — for callers
/// that pass `&src[anchor..]` (most matchers do), this can hold until
/// we get near end-of-block. We mirror upstream's `litEnd <=
/// litLimit_w` check by falling back to an exact copy unless the
/// source slice has `WILDCOPY_OVERLENGTH` bytes of slack after the
/// literal run.
#[inline(always)]
pub fn ZSTD_storeSeq(
    seqStore: &mut SeqStore_t,
    litLength: usize,
    literals: &[u8],
    offBase: u32,
    matchLength: usize,
) {
    assert!(litLength <= literals.len());
    assert!(seqStore.literals.len() + litLength <= seqStore.maxNbLit);
    if litLength != 0 {
        let cur_len = seqStore.literals.len();
        let has_wildcopy_slack = litLength
            <= literals
                .len()
                .saturating_sub(crate::common::zstd_internal::WILDCOPY_OVERLENGTH);
        if has_wildcopy_slack {
            // Match upstream's ZSTD_copy16 + ZSTD_wildcopy pattern:
            // always copy 16 bytes (single xmm load+store), then loop
            // 16-byte chunks until litLength is reached. Destination has
            // `WILDCOPY_OVERLENGTH=32` bytes of slack past `maxNbLit`.
            unsafe {
                let dst = seqStore.literals.as_mut_ptr().add(cur_len);
                let src = literals.as_ptr();
                // Fast wildcopy path: source has 16-byte slack.
                // Always copy 16, then 16-byte loop for the rest.
                core::ptr::copy_nonoverlapping(src, dst, 16);
                let mut off = 16;
                while off < litLength {
                    core::ptr::copy_nonoverlapping(src.add(off), dst.add(off), 16);
                    off += 16;
                }
                seqStore.literals.set_len(cur_len + litLength);
            }
        } else {
            extend_literals(&mut seqStore.literals, &literals[..litLength]);
        }
    }
    ZSTD_storeSeqOnly(seqStore, litLength, offBase, matchLength);
}

/// Port of `Repcodes_t`.
#[derive(Debug, Clone, Copy, Default)]
pub struct Repcodes_t {
    pub rep: [u32; ZSTD_REP_NUM],
}

/// Port of `ZSTD_updateRep`. Updates `rep\[\]` in place following
/// upstream's sumtype:
///   - `OFFBASE_IS_OFFSET`: shift rep\[1\]→\[2\], \[0\]→\[1\], store
///     new offset at \[0\].
///   - Otherwise (repcode 1..=3): adjust by `ll0` (literal length == 0 signal)
///     and promote the selected old repcode.
pub fn ZSTD_updateRep(rep: &mut [u32; ZSTD_REP_NUM], offBase: u32, ll0: u32) {
    if OFFBASE_IS_OFFSET(offBase) {
        rep[2] = rep[1];
        rep[1] = rep[0];
        rep[0] = OFFBASE_TO_OFFSET(offBase);
    } else {
        let repCode = OFFBASE_TO_REPCODE(offBase) + ll0 - 1;
        if repCode > 0 {
            let currentOffset = if repCode == ZSTD_REP_NUM as u32 {
                rep[0].wrapping_sub(1)
            } else {
                rep[repCode as usize]
            };
            if repCode >= 2 {
                rep[2] = rep[1];
            }
            rep[1] = rep[0];
            rep[0] = currentOffset;
        }
    }
}

/// Port of `ZSTD_resolveRepcodeToRawOffset` (`zstd_compress.c:4061`).
/// Given a repcode-flavored offBase, literal-length-zero flag, and the
/// current `rep[]`, returns the raw offset bytes the repcode resolves
/// to. The `(repCode == ZSTD_REP_NUM) → rep[0] - 1` corner case is
/// preserved for symmetry with `ZSTD_updateRep`; the caller must handle
/// the edge case where `rep[0] == 1` produces a zero offset (invalid,
/// discarded downstream by `ZSTD_seqStore_resolveOffCodes`).
pub fn ZSTD_resolveRepcodeToRawOffset(rep: &[u32; ZSTD_REP_NUM], offBase: u32, ll0: u32) -> u32 {
    debug_assert!(OFFBASE_IS_REPCODE(offBase));
    let adjustedRepCode = OFFBASE_TO_REPCODE(offBase) - 1 + ll0;
    if adjustedRepCode == ZSTD_REP_NUM as u32 {
        debug_assert!(ll0 != 0);
        rep[0].wrapping_sub(1)
    } else {
        rep[adjustedRepCode as usize]
    }
}

/// Port of `ZSTD_validateSeqStore` (`zstd_compress.c:3261`). Debug-
/// only assertion that every sequence's decoded matchLength respects
/// the minimum dictated by `cParams.minMatch` (3 when minMatch=3, else
/// 4). In release builds this is a no-op; in debug builds it panics
/// on violation.
pub fn ZSTD_validateSeqStore(seqStore: &SeqStore_t, minMatchParam: u32) {
    #[cfg(debug_assertions)]
    {
        let matchLenLowerBound: u32 = if minMatchParam == 3 { 3 } else { 4 };
        for idx in 0..seqStore.sequences.len() {
            let seqLen = ZSTD_getSequenceLength(seqStore, idx);
            debug_assert!(seqLen.matchLength >= matchLenLowerBound);
        }
    }
    #[cfg(not(debug_assertions))]
    {
        let _ = (seqStore, minMatchParam);
    }
}

/// Port of `ZSTD_seqStore_resolveOffCodes` (`zstd_compress.c:4093`).
/// Walks `seqStore.sequences` in order, maintaining parallel
/// decompression-side (`dRepcodes`) and compression-side (`cRepcodes`)
/// repcode histories. Whenever a repcode-flavored `offBase` would
/// resolve to different raw offsets on the two sides — which can
/// happen after an RLE/raw-block emission perturbs the implicit
/// decoder history — the sequence's `offBase` is rewritten to
/// explicitly carry the compression-side raw offset via
/// `OFFSET_TO_OFFBASE`, so the decoder's rep history matches.
///
/// Post-condition: both rep histories are advanced — `dRepcodes` via
/// the (possibly rewritten) `seq.offBase` (the value the decoder will
/// see), `cRepcodes` via the original `offBase` (the compressor's
/// actual choice).
pub fn ZSTD_seqStore_resolveOffCodes(
    dRepcodes: &mut Repcodes_t,
    cRepcodes: &mut Repcodes_t,
    seqStore: &mut SeqStore_t,
    nbSeq: u32,
) {
    let longLitLenIdx = if seqStore.longLengthType == ZSTD_longLengthType_e::ZSTD_llt_literalLength
    {
        seqStore.longLengthPos
    } else {
        nbSeq
    };
    for idx in 0..(nbSeq as usize) {
        let seq = &mut seqStore.sequences[idx];
        let ll0 = (seq.litLength == 0 && idx as u32 != longLitLenIdx) as u32;
        let offBase = seq.offBase;
        debug_assert!(offBase > 0);
        if OFFBASE_IS_REPCODE(offBase) {
            let dRaw = ZSTD_resolveRepcodeToRawOffset(&dRepcodes.rep, offBase, ll0);
            let cRaw = ZSTD_resolveRepcodeToRawOffset(&cRepcodes.rep, offBase, ll0);
            if dRaw != cRaw {
                seq.offBase = OFFSET_TO_OFFBASE(cRaw);
            }
        }
        let new_offBase = seq.offBase;
        ZSTD_updateRep(&mut dRepcodes.rep, new_offBase, ll0);
        ZSTD_updateRep(&mut cRepcodes.rep, offBase, ll0);
    }
}

/// Port of `ZSTD_newRep`. Non-destructive variant of
/// `ZSTD_updateRep`.
pub fn ZSTD_newRep(rep: &[u32; ZSTD_REP_NUM], offBase: u32, ll0: u32) -> Repcodes_t {
    let mut newReps = Repcodes_t { rep: *rep };
    ZSTD_updateRep(&mut newReps.rep, offBase, ll0);
    newReps
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn ZSTD_longLengthType_e_discriminants_match_upstream() {
        // Upstream: `typedef enum { ZSTD_llt_none, ZSTD_llt_literalLength,
        // ZSTD_llt_matchLength } ZSTD_longLengthType_e;` — default
        // sequential 0/1/2. `ZSTD_seqToCodes` stamps a single sequence's
        // long-length flag into this u32 field; drift would mis-route
        // the "which length exceeds the FSE code max" discriminator.
        assert_eq!(ZSTD_longLengthType_e::ZSTD_llt_none as u32, 0);
        assert_eq!(ZSTD_longLengthType_e::ZSTD_llt_literalLength as u32, 1);
        assert_eq!(ZSTD_longLengthType_e::ZSTD_llt_matchLength as u32, 2);
    }

    #[test]
    fn storeLastLiterals_appends() {
        let mut ss = SeqStore_t::with_capacity(16, 128);
        ss.literals.extend_from_slice(b"head");
        ZSTD_storeLastLiterals(&mut ss, b"tail");
        assert_eq!(ss.literals, b"headtail");
    }

    #[test]
    fn resetSeqStore_clears_cursors() {
        let mut ss = SeqStore_t::with_capacity(16, 128);
        ss.literals.extend_from_slice(b"junk");
        ZSTD_storeSeqOnly(&mut ss, 5, OFFSET_TO_OFFBASE(17), 4);
        ss.llCode.extend_from_slice(&[1, 2, 3]);
        ss.mlCode.extend_from_slice(&[4, 5, 6]);
        ss.ofCode.extend_from_slice(&[7, 8, 9]);
        ss.longLengthType = ZSTD_longLengthType_e::ZSTD_llt_matchLength;
        ZSTD_resetSeqStore(&mut ss);
        assert!(ss.sequences.is_empty());
        assert!(ss.literals.is_empty());
        assert!(ss.llCode.is_empty());
        assert!(ss.mlCode.is_empty());
        assert!(ss.ofCode.is_empty());
        assert_eq!(ss.longLengthType, ZSTD_longLengthType_e::ZSTD_llt_none);
    }

    #[test]
    fn getSequenceLength_adds_minmatch_back() {
        let mut ss = SeqStore_t::with_capacity(8, 128);
        ZSTD_storeSeqOnly(&mut ss, 5, OFFSET_TO_OFFBASE(17), 10);
        let got = ZSTD_getSequenceLength(&ss, 0);
        assert_eq!(got.litLength, 5);
        // mlBase = 10 - MINMATCH = 7; decoded = 7 + MINMATCH = 10.
        assert_eq!(got.matchLength, 10);
    }

    #[test]
    fn getSequenceLength_resolves_long_litLength() {
        let mut ss = SeqStore_t::with_capacity(8, 128);
        ZSTD_storeSeqOnly(&mut ss, 5, OFFSET_TO_OFFBASE(17), 8);
        ss.longLengthType = ZSTD_longLengthType_e::ZSTD_llt_literalLength;
        ss.longLengthPos = 0;
        let got = ZSTD_getSequenceLength(&ss, 0);
        // 5 + 0x10000 = 65541.
        assert_eq!(got.litLength, 65541);
        assert_eq!(got.matchLength, 8);
    }

    #[test]
    fn getSequenceLength_resolves_long_matchLength() {
        let mut ss = SeqStore_t::with_capacity(8, 128);
        ZSTD_storeSeqOnly(&mut ss, 4, OFFSET_TO_OFFBASE(17), 10);
        ss.longLengthType = ZSTD_longLengthType_e::ZSTD_llt_matchLength;
        ss.longLengthPos = 0;
        let got = ZSTD_getSequenceLength(&ss, 0);
        assert_eq!(got.litLength, 4);
        // decoded = 10 + 0x10000 = 65546.
        assert_eq!(got.matchLength, 10 + 0x10000);
    }

    #[test]
    fn getSequenceLength_long_pos_only_applies_to_target_seq() {
        let mut ss = SeqStore_t::with_capacity(8, 128);
        ZSTD_storeSeqOnly(&mut ss, 3, OFFSET_TO_OFFBASE(17), 8);
        ZSTD_storeSeqOnly(&mut ss, 2, OFFSET_TO_OFFBASE(17), 10);
        ss.longLengthType = ZSTD_longLengthType_e::ZSTD_llt_literalLength;
        ss.longLengthPos = 1; // second seq
        let s0 = ZSTD_getSequenceLength(&ss, 0);
        let s1 = ZSTD_getSequenceLength(&ss, 1);
        assert_eq!(s0.litLength, 3); // untouched
        assert_eq!(s1.litLength, 2 + 0x10000); // long
    }

    #[test]
    fn deriveSeqStoreChunk_keeps_long_length_at_end_boundary_like_c() {
        let mut ss = SeqStore_t::with_capacity(8, 128);
        ZSTD_storeSeqOnly(&mut ss, 3, OFFSET_TO_OFFBASE(10), 7);
        ZSTD_storeSeqOnly(&mut ss, 4, OFFSET_TO_OFFBASE(11), 8);
        ZSTD_storeSeqOnly(&mut ss, 5, OFFSET_TO_OFFBASE(12), 9);
        ss.literals.extend_from_slice(&[b'a'; 12]);
        ss.longLengthType = ZSTD_longLengthType_e::ZSTD_llt_matchLength;
        ss.longLengthPos = 2;

        let chunk = ZSTD_deriveSeqStoreChunk(&ss, 0, 2);
        assert_eq!(chunk.sequences.len(), 2);
        assert_eq!(
            chunk.longLengthType,
            ZSTD_longLengthType_e::ZSTD_llt_matchLength
        );
        assert_eq!(chunk.longLengthPos, 2);
    }

    #[test]
    fn deriveSeqStoreChunk_end_boundary_marker_is_safe_for_seq_to_codes() {
        let mut ss = SeqStore_t::with_capacity(8, 128);
        ZSTD_storeSeqOnly(&mut ss, 3, OFFSET_TO_OFFBASE(10), 7);
        ZSTD_storeSeqOnly(&mut ss, 4, OFFSET_TO_OFFBASE(11), 8);
        ZSTD_storeSeqOnly(
            &mut ss,
            5,
            OFFSET_TO_OFFBASE(12),
            0x10000 + MINMATCH as usize,
        );
        ss.literals.extend_from_slice(&[b'a'; 12]);

        let mut chunk = ZSTD_deriveSeqStoreChunk(&ss, 0, 2);
        crate::compress::zstd_compress::ZSTD_seqToCodes(&mut chunk);
        assert_eq!(chunk.llCode.len(), 2);
        assert_eq!(chunk.mlCode.len(), 2);
        assert_eq!(chunk.ofCode.len(), 2);
    }

    #[test]
    fn offbase_sumtype_roundtrips() {
        // Full offset: encode → is_offset → decode.
        let ob = OFFSET_TO_OFFBASE(42);
        assert!(OFFBASE_IS_OFFSET(ob));
        assert_eq!(OFFBASE_TO_OFFSET(ob), 42);

        let wrapped = OFFSET_TO_OFFBASE(u32::MAX);
        assert_eq!(wrapped, (u32::MAX).wrapping_add(ZSTD_REP_NUM as u32));

        // Repcode: 1, 2, 3 don't cross the ZSTD_REP_NUM threshold.
        for r in 1..=3u32 {
            let ob = REPCODE_TO_OFFBASE(r);
            assert!(OFFBASE_IS_REPCODE(ob));
            assert_eq!(OFFBASE_TO_REPCODE(ob), r);
        }
        assert!(!OFFBASE_IS_REPCODE(0));
    }

    #[test]
    fn store_seq_only_appends() {
        let mut ss = SeqStore_t::with_capacity(16, 1024);
        ZSTD_storeSeqOnly(&mut ss, 5, OFFSET_TO_OFFBASE(17), 4);
        assert_eq!(ss.sequences.len(), 1);
        let s = ss.sequences[0];
        assert_eq!(s.litLength, 5);
        assert!(OFFBASE_IS_OFFSET(s.offBase));
        assert_eq!(OFFBASE_TO_OFFSET(s.offBase), 17);
        assert_eq!(s.mlBase, 4 - MINMATCH as u16);
    }

    #[test]
    fn store_seq_appends_literals_and_bumps_sequences() {
        let mut ss = SeqStore_t::with_capacity(16, 1024);
        let src = b"hello matchdata";
        ZSTD_storeSeq(&mut ss, 5, src, REPCODE_TO_OFFBASE(1), 3);
        assert_eq!(ss.literals, b"hello");
        assert_eq!(ss.sequences.len(), 1);
        assert_eq!(ss.sequences[0].litLength, 5);
        // MINMATCH=3 → mlBase = 0.
        assert_eq!(ss.sequences[0].mlBase, 0);
    }

    #[test]
    fn store_seq_near_literal_limit_uses_exact_copy() {
        let mut ss = SeqStore_t::with_capacity(16, 1024);
        let src = b"abcdefghijklmnopqrst";
        ZSTD_storeSeq(&mut ss, src.len(), src, OFFSET_TO_OFFBASE(33), 5);
        assert_eq!(ss.literals, src);
        assert_eq!(ss.sequences.len(), 1);
        assert_eq!(ss.sequences[0].litLength, src.len() as u16);
    }

    #[test]
    fn store_seq_long_litlength_sets_flag() {
        let mut ss = SeqStore_t::with_capacity(16, 200_000);
        let lit = vec![b'x'; 0x10001];
        ZSTD_storeSeq(&mut ss, 0x10001, &lit, OFFSET_TO_OFFBASE(100), 5);
        assert_eq!(
            ss.longLengthType,
            ZSTD_longLengthType_e::ZSTD_llt_literalLength
        );
        assert_eq!(ss.longLengthPos, 0);
    }

    #[test]
    fn update_rep_full_offset_shifts_history() {
        let mut rep = [1u32, 4, 8];
        ZSTD_updateRep(&mut rep, OFFSET_TO_OFFBASE(42), 0);
        assert_eq!(rep, [42, 1, 4]);
    }

    #[test]
    fn update_rep_repcode_1_noop() {
        // Repcode 1 with ll0=0 → repCode = 1 + 0 - 1 = 0 → no change.
        let rep_before = [5u32, 10, 20];
        let mut rep = rep_before;
        ZSTD_updateRep(&mut rep, REPCODE_TO_OFFBASE(1), 0);
        assert_eq!(rep, rep_before);
    }

    #[test]
    fn update_rep_repcode_2_promotes() {
        // Repcode 2 with ll0=0 → repCode = 2 + 0 - 1 = 1 → pick rep[1],
        // shift rep[0]→rep[1].
        let mut rep = [5u32, 10, 20];
        ZSTD_updateRep(&mut rep, REPCODE_TO_OFFBASE(2), 0);
        assert_eq!(rep, [10, 5, 20]);
    }

    #[test]
    fn update_rep_uses_rep0_minus_one_when_repCode_equals_ZSTD_REP_NUM() {
        // Special case: repcode 3 with ll0=1 → repCode = 3+1-1 = 3 (=
        // ZSTD_REP_NUM). Upstream uses `rep[0] - 1` as the new offset
        // in this branch (the "virtual 4th repcode" slot). Pin this
        // corner so a future refactor can't swap it with rep[2].
        let mut rep = [5u32, 10, 20];
        ZSTD_updateRep(&mut rep, REPCODE_TO_OFFBASE(3), 1);
        assert_eq!(rep, [4, 5, 10]); // new rep[0] = 5-1 = 4
    }

    #[test]
    fn update_rep_rep0_minus_one_wraps_like_u32() {
        let mut rep = [0u32, 10, 20];
        ZSTD_updateRep(&mut rep, REPCODE_TO_OFFBASE(3), 1);
        assert_eq!(rep, [u32::MAX, 0, 10]);
    }

    #[test]
    fn newRep_is_non_destructive_to_input() {
        // `ZSTD_newRep` is the non-destructive sibling — returns a
        // fresh Repcodes_t without mutating the caller's array.
        let rep = [5u32, 10, 20];
        let result = ZSTD_newRep(&rep, OFFSET_TO_OFFBASE(42), 0);
        assert_eq!(rep, [5, 10, 20], "input must not be mutated");
        assert_eq!(result.rep, [42, 5, 10]);
    }
}