Skip to main content

sley_pack/
lib.rs

1// sley#7: untrusted-input parsing crate — fallible ops propagate errors;
2// the only retained `expect`s would be documented compile-time invariants.
3#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
4
5use flate2::{Compress, Compression, FlushCompress, Status};
6use sley_core::{
7    ByteBudget, CancelFlag, GitError, ObjectFormat, ObjectId, ResourceLimitKind, Result,
8    StreamingDigest,
9    primitives::{u16_be, u32_be, u64_be},
10};
11use sley_formats::Bundle;
12use sley_object::{EncodedObject, ObjectType};
13use std::borrow::Borrow;
14use std::cell::RefCell;
15use std::cmp::Ordering;
16use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
17use std::fmt;
18use std::io::Write;
19use std::ops::Range;
20use std::sync::Arc;
21
22// --- Mechanical module split (W21) -----------------------------------------
23// The former single ~10k-line lib.rs is partitioned into contiguous
24// submodules along its existing function-cluster seams. Each submodule pulls
25// the crate-root scope in via `use super::*` and is re-exported below so every
26// `sley_pack::X` path (public API and intra-crate) resolves unchanged.
27// This is a pure code move: no function body was altered.
28mod bounded_read;
29mod delta;
30mod fix_thin;
31mod index;
32pub mod inflate;
33mod limits;
34mod parallel_index;
35mod read;
36mod write;
37
38pub use bounded_read::*;
39pub(crate) use delta::*;
40pub use fix_thin::*;
41pub use index::*;
42pub use limits::{MAX_READ_DELTA_CHAIN_DEPTH, PACK_OBJECT_COUNT_PREALLOC_CAP};
43pub(crate) use limits::{checked_pack_object_count, pack_entry_prealloc};
44pub use parallel_index::*;
45pub use read::*;
46pub use write::*;
47
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct PackEntry {
50    pub oid: ObjectId,
51    pub compressed_size: u64,
52    pub uncompressed_size: u64,
53    pub offset: u64,
54}
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct RepackPolicy {
57    pub write_bitmaps: bool,
58    pub cruft_packs: bool,
59    pub geometric_factor: Option<u8>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct PackFile {
64    pub version: u32,
65    pub entries: Vec<PackObject>,
66    pub checksum: ObjectId,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct PackObject {
71    pub entry: PackEntry,
72    pub object: EncodedObject,
73}
74
75/// Per-object statistics for one entry of a verified pack, in the shape
76/// `git verify-pack -v` reports.
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct PackVerifyStat {
79    /// Resolved object id.
80    pub oid: ObjectId,
81    /// Resolved object type (the delta's *result* type, not `ofs-delta`).
82    pub object_type: ObjectType,
83    /// Resolved (inflated) object size in bytes.
84    pub size: u64,
85    /// Bytes this object occupies in the pack: the offset delta to the next
86    /// object, or to the trailing checksum for the last object.
87    pub size_in_pack: u64,
88    /// In-pack byte offset where this object's entry begins.
89    pub offset: u64,
90    /// Delta chain depth: `0` for undeltified objects, base-depth + 1 otherwise.
91    pub delta_depth: u32,
92    /// For delta objects, the id of the *immediate* base object (which may
93    /// itself be a delta). `None` for undeltified objects.
94    pub base_oid: Option<ObjectId>,
95}
96
97/// Result of [`PackFile::verify_pack_stats`]: per-object stats in pack offset
98/// order plus the pack's trailing checksum.
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct PackVerifyStats {
101    pub objects: Vec<PackVerifyStat>,
102    pub checksum: ObjectId,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct PackWrite {
107    pub pack: Vec<u8>,
108    pub index: Vec<u8>,
109    pub checksum: ObjectId,
110    pub entries: Vec<PackIndexEntry>,
111    pub delta_count: u32,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub struct PackWriteSummary {
116    pub index: Vec<u8>,
117    pub checksum: ObjectId,
118    pub entries: Vec<PackIndexEntry>,
119    pub delta_count: u32,
120    pub pack_size: u64,
121    /// High-water mark of decoded window bodies plus retained delta bases,
122    /// as charged by [`PackWriteLimits`]. Does not include zlib output buffers
123    /// or allocator slack.
124    pub peak_working_set_bytes: u64,
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub struct PackInput<'a> {
129    pub oid: &'a ObjectId,
130    pub object: &'a EncodedObject,
131}
132
133/// Repository-derived metadata that can improve pack planning without
134/// changing the encoding policy in [`PackWriteOptions`].
135#[derive(Debug, Clone, Copy, Default)]
136pub struct PackPlanningHints<'a> {
137    name_hashes: Option<&'a HashMap<ObjectId, u32>>,
138}
139
140impl<'a> PackPlanningHints<'a> {
141    pub const fn new() -> Self {
142        Self { name_hashes: None }
143    }
144
145    /// Supply Git-compatible path-name hashes keyed by object id. Missing
146    /// objects retain Git's unnamed zero hash.
147    pub const fn with_name_hashes(mut self, name_hashes: &'a HashMap<ObjectId, u32>) -> Self {
148        self.name_hashes = Some(name_hashes);
149        self
150    }
151
152    pub(crate) const fn name_hashes(self) -> Option<&'a HashMap<ObjectId, u32>> {
153        self.name_hashes
154    }
155}
156
157/// Sort items using the pack writer's current delta-planning order.
158///
159/// `metadata` returns the object id, type, decoded size, and Git-compatible
160/// path-name hash for an item. Keeping the comparison here prevents writer
161/// clients from duplicating the pack writer's ordering policy.
162pub fn sort_by_pack_planning_order<T>(
163    items: &mut [T],
164    mut metadata: impl FnMut(&T) -> (ObjectId, ObjectType, u64, u32),
165) {
166    items.sort_by(|left, right| compare_pack_planning_metadata(metadata(left), metadata(right)));
167}
168
169fn compare_pack_planning_metadata(
170    left: (ObjectId, ObjectType, u64, u32),
171    right: (ObjectId, ObjectType, u64, u32),
172) -> Ordering {
173    pack_planning_type_rank(left.1)
174        .cmp(&pack_planning_type_rank(right.1))
175        .then_with(|| right.3.cmp(&left.3))
176        .then_with(|| right.2.cmp(&left.2))
177        .then_with(|| left.0.as_bytes().cmp(right.0.as_bytes()))
178}
179
180const fn pack_planning_type_rank(object_type: ObjectType) -> u8 {
181    match object_type {
182        ObjectType::Tag => 0,
183        ObjectType::Blob => 1,
184        ObjectType::Tree => 2,
185        ObjectType::Commit => 3,
186    }
187}
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189enum PackObjectKind {
190    Commit,
191    Tree,
192    Blob,
193    Tag,
194    OfsDelta,
195    RefDelta,
196}
197
198#[derive(Debug, Clone, PartialEq, Eq)]
199enum DeltaBase {
200    Offset(u64),
201    Ref(ObjectId),
202}
203
204/// One pack entry as stored on disk, used by [`PackFile::verify_pack_stats`] to
205/// recover the delta structure and on-disk stream size that resolved
206/// [`PackObject`]s no longer carry.
207struct OnDiskEntry {
208    offset: u64,
209    base: Option<DeltaBase>,
210    stream_size: u64,
211}
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213struct EntryHeader {
214    kind: PackObjectKind,
215    size: u64,
216}
217fn next_byte(bytes: &[u8], offset: &mut usize) -> Result<u8> {
218    let Some(byte) = bytes.get(*offset).copied() else {
219        return Err(GitError::InvalidFormat(
220            "truncated pack entry header".into(),
221        ));
222    };
223    *offset += 1;
224    Ok(byte)
225}
226
227fn checked_range(
228    start: usize,
229    count: usize,
230    width: usize,
231    total: usize,
232) -> Result<std::ops::Range<usize>> {
233    let len = count
234        .checked_mul(width)
235        .ok_or_else(|| GitError::InvalidFormat("pack index table overflow".into()))?;
236    let end = start
237        .checked_add(len)
238        .ok_or_else(|| GitError::InvalidFormat("pack index table overflow".into()))?;
239    if end > total {
240        return Err(GitError::InvalidFormat("truncated pack index table".into()));
241    }
242    Ok(start..end)
243}
244
245fn validate_position_permutation(positions: &[u32]) -> Result<()> {
246    let mut seen = vec![false; positions.len()];
247    for position in positions {
248        let idx = *position as usize;
249        if idx >= positions.len() {
250            return Err(GitError::InvalidFormat(format!(
251                "invalid rev-index position {position}"
252            )));
253        }
254        if seen[idx] {
255            return Err(GitError::InvalidFormat(format!(
256                "invalid rev-index position {position}"
257            )));
258        }
259        seen[idx] = true;
260    }
261    Ok(())
262}
263
264// Reused zlib inflate state. Resetting and reusing one `Decompress` avoids
265// allocating a fresh (~10 KiB) `InflateState` for every object and delta decoded —
266// an allocation that dominated bulk reads. Borrowed only for the duration of a
267// single inflate; the recursive pack reader fully inflates each entry's data before
268// recursing to its base, so the borrow never nests.
269thread_local! {
270    pub(crate) static INFLATE: RefCell<flate2::Decompress> = RefCell::new(flate2::Decompress::new(true));
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use flate2::Compression;
277    use flate2::read::ZlibDecoder;
278    use flate2::write::ZlibEncoder;
279    use sley_core::AtomicCancel;
280    use std::fs;
281    use std::io::{Read, Write};
282    use std::path::{Path, PathBuf};
283    use std::process::Command;
284    use std::time::{SystemTime, UNIX_EPOCH};
285
286    fn multi_blob_pack(count: usize) -> Vec<u8> {
287        let objects = (0..count)
288            .map(|idx| {
289                EncodedObject::new(
290                    ObjectType::Blob,
291                    format!("stream cancel pack object {idx}\n").into_bytes(),
292                )
293            })
294            .collect::<Vec<_>>();
295        PackFile::write_undeltified(&objects, ObjectFormat::Sha1)
296            .expect("test operation should succeed")
297            .pack
298    }
299
300    #[test]
301    fn parallel_index_already_cancelled_returns_cancelled() {
302        let pack = multi_blob_pack(4);
303        let source = AtomicCancel::new();
304        source.cancel();
305        let err = PackIndex::write_v2_for_pack_with_options(
306            &pack,
307            ObjectFormat::Sha1,
308            |_| Ok(None),
309            PackIndexOptions::default(),
310            CancelFlag::new(&source),
311            |_| {},
312        )
313        .expect_err("pre-cancelled index should fail");
314        assert_eq!(err, GitError::Cancelled);
315    }
316
317    #[test]
318    fn parallel_index_is_identical_with_one_or_many_workers() {
319        let pack = multi_blob_pack(128);
320        let serial_schedule = PackIndex::write_v2_for_pack_with_options(
321            &pack,
322            ObjectFormat::Sha1,
323            |_| Ok(None),
324            PackIndexOptions::default().with_threads(1),
325            CancelFlag::never(),
326            |_| {},
327        )
328        .expect("one-worker schedule");
329        let parallel_schedule = PackIndex::write_v2_for_pack_with_options(
330            &pack,
331            ObjectFormat::Sha1,
332            |_| Ok(None),
333            PackIndexOptions::default().with_threads(32),
334            CancelFlag::never(),
335            |_| {},
336        )
337        .expect("many-worker schedule");
338        assert_eq!(parallel_schedule, serial_schedule);
339    }
340
341    #[test]
342    fn write_packed_from_source_respects_cancel_between_windows() {
343        let format = ObjectFormat::Sha1;
344        let count = 4u32;
345        let objects = (0..count)
346            .map(|idx| {
347                EncodedObject::new(
348                    ObjectType::Blob,
349                    format!("write-cancel object {idx}\n").into_bytes(),
350                )
351            })
352            .collect::<Vec<_>>();
353        let object_ids = objects
354            .iter()
355            .map(|object| object.object_id(format).expect("oid"))
356            .collect::<Vec<_>>();
357        let object_map = object_ids
358            .iter()
359            .copied()
360            .zip(objects.into_iter().map(Arc::new))
361            .collect::<HashMap<_, _>>();
362
363        let source = AtomicCancel::new();
364        source.cancel();
365        let mut written = Vec::new();
366        let err = PackFile::write_packed_from_source_to_writer_with_cancel(
367            object_ids.iter().copied(),
368            count,
369            format,
370            &PackWriteOptions::new().with_reorder(false),
371            PackWriteLimits::default(),
372            |oid| {
373                object_map
374                    .get(oid)
375                    .cloned()
376                    .ok_or_else(|| GitError::not_found(format!("missing test object {oid}")))
377            },
378            &mut written,
379            CancelFlag::new(&source),
380        )
381        .expect_err("pre-cancelled pack write should fail");
382        assert_eq!(err, GitError::Cancelled);
383        assert!(
384            written.is_empty() || written.len() < 32,
385            "should not have finished a full pack after cancel"
386        );
387    }
388
389    fn delta_pack_options(prefer_ofs_delta: bool) -> PackWriteOptions {
390        PackWriteOptions::new()
391            .with_prefer_ofs_delta(prefer_ofs_delta)
392            .with_reorder(false)
393    }
394
395    #[test]
396    fn parses_single_blob_pack() {
397        let pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"hello\n");
398        let parsed = PackFile::parse_sha1(&pack).expect("test operation should succeed");
399        assert_eq!(parsed.version, 2);
400        assert_eq!(parsed.entries.len(), 1);
401        let object = &parsed.entries[0].object;
402        assert_eq!(object.object_type, ObjectType::Blob);
403        assert_eq!(object.body, b"hello\n");
404        assert_eq!(
405            parsed.entries[0].entry.oid.to_hex(),
406            "ce013625030ba8dba906f756967f9e9ca394464a"
407        );
408    }
409
410    #[test]
411    fn parses_single_blob_pack_sha256() {
412        let pack = single_object_pack(ObjectFormat::Sha256, ObjectType::Blob, b"hello\n");
413        let parsed =
414            PackFile::parse(&pack, ObjectFormat::Sha256).expect("test operation should succeed");
415        assert_eq!(parsed.version, 2);
416        assert_eq!(parsed.entries.len(), 1);
417        let object = &parsed.entries[0].object;
418        assert_eq!(object.object_type, ObjectType::Blob);
419        assert_eq!(object.body, b"hello\n");
420        assert_eq!(
421            parsed.entries[0].entry.oid,
422            object
423                .object_id(ObjectFormat::Sha256)
424                .expect("test operation should succeed")
425        );
426    }
427
428    #[test]
429    fn parses_bundle_pack_payload_with_bundle_format() {
430        let pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"bundle\n");
431        let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"bundle\n")
432            .expect("test operation should succeed");
433        let bundle_bytes = format!("# v2 git bundle\n{oid} refs/heads/main\n\n")
434            .into_bytes()
435            .into_iter()
436            .chain(pack)
437            .collect::<Vec<_>>();
438        let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
439            .expect("test operation should succeed");
440
441        let parsed = PackFile::parse_bundle(&bundle).expect("test operation should succeed");
442        assert_eq!(parsed.entries.len(), 1);
443        assert_eq!(parsed.entries[0].object.object_type, ObjectType::Blob);
444        assert_eq!(parsed.entries[0].object.body, b"bundle\n");
445    }
446
447    /// Build a pack whose single blob entry header LIES about its decompressed
448    /// size: it declares `declared_size` while the actual zlib payload only
449    /// inflates to `real_body`. A short `real_body` plus a `declared_size` of
450    /// `u64::MAX` is the decompression-bomb shape — the header claims terabytes
451    /// from a handful of compressed bytes.
452    fn lying_size_blob_pack(format: ObjectFormat, declared_size: u64, real_body: &[u8]) -> Vec<u8> {
453        let mut pack = Vec::new();
454        pack.extend_from_slice(b"PACK");
455        pack.extend_from_slice(&2u32.to_be_bytes());
456        pack.extend_from_slice(&1u32.to_be_bytes());
457        // Object type 3 == blob; size varint encodes the *attacker-declared* size.
458        write_pack_entry_header_kind(&mut pack, 3, declared_size);
459        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
460        encoder
461            .write_all(real_body)
462            .expect("test operation should succeed");
463        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
464        let checksum =
465            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
466        pack.extend_from_slice(checksum.as_bytes());
467        pack
468    }
469
470    /// Regression: a crafted pack object header declaring a gigantic decompressed
471    /// size with a tiny compressed payload must NOT drive an up-front
472    /// reservation/allocation of that declared size (OOM/abort). sley#2: the
473    /// header `size` is attacker-controlled over the network (install_raw_pack →
474    /// sley-fetch), so it must be validated/bounded before any `Vec::reserve`.
475    ///
476    /// On the unfixed code, `inflate_into` did `out.reserve(header.size as usize)`
477    /// with `header.size == u64::MAX`, which panics with "capacity overflow" (or
478    /// aborts on alloc failure) *before* the size-mismatch check could fire. We
479    /// run parse on a worker thread so that panic surfaces as a `join()` error
480    /// rather than killing the test process; the fix turns this into a clean
481    /// `Err` returned normally.
482    #[test]
483    fn rejects_decompression_bomb_header_without_oom() {
484        for &declared in &[u64::MAX, 100 * 1024 * 1024 * 1024, u64::from(u32::MAX) * 4] {
485            let pack = lying_size_blob_pack(ObjectFormat::Sha1, declared, b"tiny\n");
486            let handle = std::thread::spawn(move || PackFile::parse_sha1(&pack));
487            let result = handle.join();
488            // The parse thread must not have panicked/aborted on a huge reserve.
489            assert!(
490                result.is_ok(),
491                "parsing a bomb header (declared={declared}) panicked instead of erroring cleanly"
492            );
493            // And parsing must reject the lie (decoded len != declared size).
494            let parse_result = result.expect("parse thread should not panic on a bomb header");
495            assert!(
496                parse_result.is_err(),
497                "bomb header (declared={declared}) should be rejected as invalid"
498            );
499        }
500    }
501
502    /// Build a 2-object pack: a real base blob followed by a delta (ref or ofs)
503    /// whose *result-size* varint lies, declaring `declared_result_size`, while
504    /// carrying a tiny real instruction stream. The delta's base-size varint is
505    /// set correctly (so the base-size check at the top of `apply_pack_delta`
506    /// passes and we reach the result reservation). Used to drive the sley#35
507    /// delta-result-size bomb.
508    fn lying_result_size_delta_pack(
509        format: ObjectFormat,
510        declared_result_size: u64,
511        delta_kind: DeltaKind,
512    ) -> Vec<u8> {
513        let base = b"hello";
514        let result = b"hello world"; // real produced length = 11
515
516        // Hand-build a delta with a truthful base-size and a LYING result-size.
517        let mut delta = Vec::new();
518        write_delta_varint(&mut delta, base.len() as u64);
519        write_delta_varint(&mut delta, declared_result_size);
520        // Real instructions: copy `base` then insert " world".
521        let suffix = &result[base.len()..];
522        delta.push(0x90); // copy, 1 size byte present (bit 0x10)
523        delta.push(base.len() as u8);
524        delta.push(suffix.len() as u8);
525        delta.extend_from_slice(suffix);
526
527        let mut pack = Vec::new();
528        pack.extend_from_slice(b"PACK");
529        pack.extend_from_slice(&2u32.to_be_bytes());
530        pack.extend_from_slice(&2u32.to_be_bytes());
531
532        let base_offset = pack.len();
533        write_entry_header(&mut pack, ObjectType::Blob, base.len() as u64);
534        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
535        encoder
536            .write_all(base)
537            .expect("test operation should succeed");
538        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
539
540        let delta_offset = pack.len();
541        write_pack_entry_header_kind(
542            &mut pack,
543            match delta_kind {
544                DeltaKind::Offset => 6,
545                DeltaKind::Ref => 7,
546            },
547            delta.len() as u64,
548        );
549        match delta_kind {
550            DeltaKind::Offset => write_ofs_delta_offset(&mut pack, delta_offset - base_offset),
551            DeltaKind::Ref => {
552                let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
553                    .expect("test operation should succeed");
554                pack.extend_from_slice(base_oid.as_bytes());
555            }
556        }
557        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
558        encoder
559            .write_all(&delta)
560            .expect("test operation should succeed");
561        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
562
563        let checksum =
564            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
565        pack.extend_from_slice(checksum.as_bytes());
566        pack
567    }
568
569    /// Regression (sley#35): the 2nd instance of the sley#2 decompression-bomb
570    /// class. `apply_pack_delta` read an attacker-controlled `result_size` varint
571    /// from a network delta and fed it straight to `Vec::with_capacity`. A tiny
572    /// delta declaring `result_size == u64::MAX` (or ~1 TiB) aborts the process
573    /// ("capacity overflow"/alloc failure, SIGABRT) BEFORE the post-decode
574    /// size-mismatch check can reject the lie. Both ref-delta and ofs-delta paths
575    /// reach the same reservation, so both must be safe. We resolve the pack on a
576    /// worker thread so an abort/panic surfaces as a `join()` error rather than
577    /// killing the whole test binary; the fix turns the bomb into a clean `Err`.
578    #[test]
579    fn rejects_delta_result_size_bomb_without_oom() {
580        let bombs: &[u64] = &[u64::MAX, 1024 * 1024 * 1024 * 1024];
581        for &declared in bombs {
582            for delta_kind in [DeltaKind::Ref, DeltaKind::Offset] {
583                let pack = lying_result_size_delta_pack(ObjectFormat::Sha1, declared, delta_kind);
584                let handle = std::thread::spawn(move || PackFile::parse_sha1(&pack));
585                let join_result = handle.join();
586                assert!(
587                    join_result.is_ok(),
588                    "delta bomb (declared={declared}, kind={delta_kind:?}) panicked/aborted \
589                     instead of erroring cleanly"
590                );
591                let parse_result =
592                    join_result.expect("parse thread should not panic on a delta bomb");
593                assert!(
594                    parse_result.is_err(),
595                    "delta bomb (declared={declared}, kind={delta_kind:?}) should be rejected \
596                     as invalid (result.len() != declared)"
597                );
598            }
599        }
600    }
601
602    /// A legitimate (truthful) delta whose result-size varint matches the real
603    /// produced length must still resolve correctly — the bound only caps the
604    /// speculative reservation, it must not break real delta application.
605    #[test]
606    fn applies_legitimate_delta_after_result_size_bound() {
607        for delta_kind in [DeltaKind::Ref, DeltaKind::Offset] {
608            let base = b"hello";
609            let result = b"hello world";
610            let pack = two_object_delta_pack(ObjectFormat::Sha1, base, result, delta_kind);
611            let parsed = PackFile::parse_sha1(&pack).expect("legitimate delta should resolve");
612            assert_eq!(parsed.entries.len(), 2);
613            assert_eq!(parsed.entries[0].object.body, base);
614            assert_eq!(parsed.entries[1].object.body, result);
615        }
616    }
617
618    #[test]
619    fn bounded_delta_application_polls_cancel_between_commands() {
620        let base = vec![b'x'; 4096];
621        let mut result = base.clone();
622        result.extend_from_slice(b"changed");
623        let delta = DeltaIndex::new(&base).delta(&result).expect("delta");
624        let plan = plan_pack_delta(&base, &delta).expect("plan");
625        let mut out = Vec::with_capacity(result.len());
626        let source = AtomicCancel::new();
627        source.cancel();
628
629        assert_eq!(
630            apply_pack_delta_exact(&base, &delta, plan, &mut out, CancelFlag::new(&source)),
631            Err(GitError::Cancelled)
632        );
633        assert!(out.is_empty());
634    }
635
636    #[test]
637    fn legacy_delta_preserves_final_size_mismatch_and_copy_heavy_growth() {
638        let base = b"x";
639        let overproducing = vec![1, 1, 2, b'x', b'y'];
640        assert_eq!(
641            apply_pack_delta(base, &overproducing),
642            Err(GitError::InvalidObject(
643                "delta result size mismatch: expected 1, got 2".into()
644            ))
645        );
646
647        let copies = 4096usize;
648        let mut copy_heavy = Vec::new();
649        write_delta_varint(&mut copy_heavy, base.len() as u64);
650        write_delta_varint(&mut copy_heavy, copies as u64);
651        for _ in 0..copies {
652            // Copy one byte from base offset zero.
653            copy_heavy.extend_from_slice(&[0x91, 0, 1]);
654        }
655        assert_eq!(
656            apply_pack_delta(base, &copy_heavy).expect("copy-heavy legacy delta"),
657            vec![b'x'; copies]
658        );
659    }
660
661    #[test]
662    fn rejects_bundle_pack_payload_with_wrong_object_format() {
663        let pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"bundle\n");
664        let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha256, "blob", b"bundle\n")
665            .expect("test operation should succeed");
666        let bundle_bytes =
667            format!("# v3 git bundle\n@object-format=sha256\n{oid} refs/heads/main\n\n")
668                .into_bytes()
669                .into_iter()
670                .chain(pack)
671                .collect::<Vec<_>>();
672        let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
673            .expect("test operation should succeed");
674
675        assert!(PackFile::parse_bundle(&bundle).is_err());
676    }
677
678    fn assert_pack_index_view_matches_owned(index: &[u8], format: ObjectFormat) {
679        let owned = PackIndex::parse(index, format).expect("test operation should succeed");
680        let view = PackIndexView::parse(index, format).expect("test operation should succeed");
681        let owned_view =
682            PackIndexViewData::parse(Arc::from(index.to_vec().into_boxed_slice()), format)
683                .expect("test operation should succeed");
684
685        assert_eq!(view.version, owned.version);
686        assert_eq!(view.count, owned.entries.len());
687        assert_eq!(view.count(), owned.entries.len());
688        assert_eq!(view.fanout(), &owned.fanout);
689        assert_eq!(view.pack_checksum, owned.pack_checksum);
690        assert_eq!(view.index_checksum, owned.index_checksum);
691        assert_eq!(owned_view.version, owned.version);
692        assert_eq!(owned_view.count(), owned.entries.len());
693        assert_eq!(owned_view.fanout(), &owned.fanout);
694        assert_eq!(owned_view.pack_checksum, owned.pack_checksum);
695        assert_eq!(owned_view.index_checksum, owned.index_checksum);
696        for entry in &owned.entries {
697            let owned_found = owned
698                .find(&entry.oid)
699                .expect("test operation should succeed");
700            let expected = Some(PackIndexLookup {
701                crc32: owned_found.crc32,
702                offset: owned_found.offset,
703            });
704            assert_eq!(view.find(&entry.oid), expected);
705            assert_eq!(owned_view.find(&entry.oid), expected);
706        }
707    }
708
709    #[test]
710    fn writes_pack_and_index_that_round_trip() {
711        let object = EncodedObject::new(ObjectType::Blob, b"hello\n".to_vec());
712        let written = PackFile::write_undeltified_sha1(std::slice::from_ref(&object))
713            .expect("test operation should succeed");
714        let pack = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
715        let index =
716            PackIndex::parse_v2_sha1(&written.index).expect("test operation should succeed");
717        let oid = object
718            .object_id(ObjectFormat::Sha1)
719            .expect("test operation should succeed");
720        assert_eq!(pack.entries[0].object, object);
721        assert_eq!(index.pack_checksum, pack.checksum);
722        assert_eq!(
723            index
724                .find(&oid)
725                .expect("test operation should succeed")
726                .offset,
727            12
728        );
729    }
730
731    #[test]
732    fn pack_index_view_matches_owned_index_for_generated_sha1_pack() {
733        let objects = (0..8)
734            .map(|idx| {
735                EncodedObject::new(
736                    ObjectType::Blob,
737                    format!("borrowed pack index view sha1 object {idx}\n").into_bytes(),
738                )
739            })
740            .collect::<Vec<_>>();
741        let written = PackFile::write_packed(&objects, ObjectFormat::Sha1)
742            .expect("test operation should succeed");
743
744        assert_pack_index_view_matches_owned(&written.index, ObjectFormat::Sha1);
745
746        let view =
747            PackIndexView::parse_v2_sha1(&written.index).expect("test operation should succeed");
748        let missing = sley_core::object_id_for_bytes(
749            ObjectFormat::Sha1,
750            "blob",
751            b"not present in borrowed index\n",
752        )
753        .expect("test operation should succeed");
754        assert_eq!(view.find(&missing), None);
755    }
756
757    #[test]
758    fn writes_sha256_pack_and_index_that_round_trip() {
759        let object = EncodedObject::new(ObjectType::Blob, b"hello sha256\n".to_vec());
760        let written =
761            PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha256)
762                .expect("test operation should succeed");
763        let pack = PackFile::parse(&written.pack, ObjectFormat::Sha256)
764            .expect("test operation should succeed");
765        let index = PackIndex::parse(&written.index, ObjectFormat::Sha256)
766            .expect("test operation should succeed");
767        let oid = object
768            .object_id(ObjectFormat::Sha256)
769            .expect("test operation should succeed");
770        assert_eq!(pack.entries[0].object, object);
771        assert_eq!(index.pack_checksum, pack.checksum);
772        assert_eq!(index.pack_checksum.format(), ObjectFormat::Sha256);
773        assert_eq!(index.index_checksum.format(), ObjectFormat::Sha256);
774        assert_eq!(
775            index
776                .find(&oid)
777                .expect("test operation should succeed")
778                .offset,
779            12
780        );
781    }
782
783    #[test]
784    fn pack_index_view_matches_owned_index_for_generated_sha256_pack() {
785        let objects = (0..4)
786            .map(|idx| {
787                EncodedObject::new(
788                    ObjectType::Blob,
789                    format!("borrowed pack index view sha256 object {idx}\n").into_bytes(),
790                )
791            })
792            .collect::<Vec<_>>();
793        let written = PackFile::write_undeltified(&objects, ObjectFormat::Sha256)
794            .expect("test operation should succeed");
795
796        assert_pack_index_view_matches_owned(&written.index, ObjectFormat::Sha256);
797    }
798
799    #[test]
800    fn indexes_existing_sha256_pack_bytes() {
801        let object = EncodedObject::new(ObjectType::Blob, b"index raw sha256 pack\n".to_vec());
802        let written =
803            PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha256)
804                .expect("test operation should succeed");
805
806        let indexed = PackIndex::write_v2_for_pack(&written.pack, ObjectFormat::Sha256)
807            .expect("test operation should succeed");
808        let index = PackIndex::parse(&indexed.index, ObjectFormat::Sha256)
809            .expect("test operation should succeed");
810
811        assert_eq!(indexed.pack_checksum, written.checksum);
812        assert_eq!(indexed.entries, written.entries);
813        assert_eq!(index.pack_checksum, written.checksum);
814        assert_eq!(index.entries, written.entries);
815    }
816
817    #[test]
818    fn indexes_existing_delta_pack_bytes() {
819        let (base, changed) = similar_blob_objects();
820        let options = delta_pack_options(true);
821        let written = PackFile::write_packed_with_options(
822            &[base, changed.clone()],
823            ObjectFormat::Sha1,
824            &options,
825        )
826        .expect("test operation should succeed");
827
828        let indexed = PackIndex::write_v2_for_pack(&written.pack, ObjectFormat::Sha1)
829            .expect("test operation should succeed");
830        let index =
831            PackIndex::parse_v2_sha1(&indexed.index).expect("test operation should succeed");
832        let changed_oid = changed
833            .object_id(ObjectFormat::Sha1)
834            .expect("test operation should succeed");
835
836        assert_eq!(indexed.pack_checksum, written.checksum);
837        assert_eq!(indexed.entries, written.entries);
838        assert_eq!(
839            index
840                .find(&changed_oid)
841                .expect("test operation should succeed")
842                .offset,
843            written.entries[1].offset
844        );
845        assert_eq!(
846            index
847                .find(&changed_oid)
848                .expect("test operation should succeed")
849                .crc32,
850            written.entries[1].crc32
851        );
852    }
853
854    #[test]
855    fn git_generated_pack_is_identical_with_one_and_many_workers() {
856        let root = unique_temp_dir("parallel-git-pack");
857        let repository = root.join("repo");
858        fs::create_dir_all(&repository).expect("create git fixture directory");
859        run_git_success(&repository, &["init", "-q"]);
860        run_git_success(&repository, &["config", "user.name", "Sley Test"]);
861        run_git_success(
862            &repository,
863            &["config", "user.email", "sley@example.invalid"],
864        );
865        for index in 0..64 {
866            let body = format!("git generated pack object {index}\n").repeat(128);
867            fs::write(repository.join(format!("object-{index:03}.txt")), body)
868                .expect("write git fixture object");
869        }
870        run_git_success(&repository, &["add", "."]);
871        run_git_success(&repository, &["commit", "-q", "-m", "pack fixture"]);
872        for index in 0..64 {
873            let path = repository.join(format!("object-{index:03}.txt"));
874            let mut body = fs::read(&path).expect("read git fixture object");
875            body.extend_from_slice(format!("second version {index}\n").as_bytes());
876            fs::write(path, body).expect("update git fixture object");
877        }
878        run_git_success(&repository, &["add", "."]);
879        run_git_success(&repository, &["commit", "-q", "-m", "deltified fixture"]);
880        run_git_success(&repository, &["gc", "--aggressive", "--prune=now"]);
881
882        let pack_dir = repository.join(".git/objects/pack");
883        let pack_path = largest_path_with_extension(&pack_dir, "pack");
884        let pack = fs::read(&pack_path).expect("read git-generated pack");
885        let git_index = fs::read(pack_path.with_extension("idx")).expect("read git index");
886        let one = PackIndex::write_v2_for_pack_with_options(
887            &pack,
888            ObjectFormat::Sha1,
889            |_| Ok(None),
890            PackIndexOptions::default().with_threads(1),
891            CancelFlag::never(),
892            |_| {},
893        )
894        .expect("index git pack with one worker");
895        let many = PackIndex::write_v2_for_pack_with_options(
896            &pack,
897            ObjectFormat::Sha1,
898            |_| Ok(None),
899            PackIndexOptions::default().with_threads(64),
900            CancelFlag::never(),
901            |_| {},
902        )
903        .expect("index git pack with many workers");
904        assert_eq!(many, one);
905        assert_eq!(many.index, git_index);
906        fs::remove_dir_all(root).expect("remove git fixture");
907    }
908
909    #[test]
910    fn parallel_index_rejects_self_referential_ref_delta_with_valid_checksum() {
911        let format = ObjectFormat::Sha1;
912        let mut pack = ref_delta_chain_pack(format, 1, false);
913        let descriptors = pack_entry_descriptors(&pack, format);
914        let delta_offset = descriptors[1].offset as usize;
915        let mut base_oid_offset = delta_offset;
916        let header = parse_entry_header(&pack, &mut base_oid_offset).expect("delta header");
917        assert_eq!(header.kind, PackObjectKind::RefDelta);
918        let self_oid = sley_core::object_id_for_bytes(format, "blob", &chain_bodies(1)[1])
919            .expect("self object id");
920        let base_oid_end = base_oid_offset + format.raw_len();
921        pack[base_oid_offset..base_oid_end].copy_from_slice(self_oid.as_bytes());
922        let trailer_offset = pack.len() - format.raw_len();
923        let checksum = sley_core::digest_bytes(format, &pack[..trailer_offset])
924            .expect("recompute valid pack checksum");
925        pack[trailer_offset..].copy_from_slice(checksum.as_bytes());
926
927        let error = PackIndex::write_v2_for_pack_with_options(
928            &pack,
929            format,
930            |_| Ok(None),
931            PackIndexOptions::default().with_threads(32),
932            CancelFlag::never(),
933            |_| {},
934        )
935        .expect_err("a self-referential delta chain must be rejected");
936        assert!(
937            error.to_string().contains("cyclic") || error.to_string().contains("unresolved"),
938            "unexpected chain error: {error}"
939        );
940    }
941
942    #[test]
943    fn writes_ref_delta_pack_and_index_that_round_trip() {
944        let (base, changed) = similar_blob_objects();
945        let options = delta_pack_options(false);
946        let written = PackFile::write_packed_with_options(
947            &[base.clone(), changed.clone()],
948            ObjectFormat::Sha1,
949            &options,
950        )
951        .expect("test operation should succeed");
952        let mut second_offset = written.entries[1].offset as usize;
953        let header = parse_entry_header(&written.pack, &mut second_offset)
954            .expect("test operation should succeed");
955        assert_eq!(header.kind, PackObjectKind::RefDelta);
956
957        let pack = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
958        let index =
959            PackIndex::parse_v2_sha1(&written.index).expect("test operation should succeed");
960        let oid = changed
961            .object_id(ObjectFormat::Sha1)
962            .expect("test operation should succeed");
963        assert_eq!(pack.entries[0].object, base);
964        assert_eq!(pack.entries[1].object, changed);
965        assert_eq!(index.pack_checksum, pack.checksum);
966        assert_eq!(
967            index
968                .find(&oid)
969                .expect("test operation should succeed")
970                .offset,
971            written.entries[1].offset
972        );
973    }
974
975    #[test]
976    fn read_object_at_matches_full_parse_for_ofs_delta_pack() {
977        let (base, changed) = similar_blob_objects();
978        let options = delta_pack_options(true);
979        let written =
980            PackFile::write_packed_with_options(&[base, changed], ObjectFormat::Sha1, &options)
981                .expect("test operation should succeed");
982        // Ensure the pack genuinely contains an ofs-delta (else the test is vacuous).
983        let mut second = written.entries[1].offset as usize;
984        assert_eq!(
985            parse_entry_header(&written.pack, &mut second)
986                .expect("test operation should succeed")
987                .kind,
988            PackObjectKind::OfsDelta
989        );
990        // Ground truth from a full parse; single-object decode must match at every offset.
991        let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
992        for po in &parsed.entries {
993            let got =
994                read_object_at_arc(&written.pack, po.entry.offset, ObjectFormat::Sha1, |_| {
995                    Ok(None)
996                })
997                .expect("test operation should succeed");
998            assert_eq!(*got, po.object, "offset {}", po.entry.offset);
999        }
1000    }
1001
1002    /// A [`HeaderTypeCache`] over a plain map, for asserting the cached header
1003    /// read is byte-identical to the uncached one cold and warm (sley#26).
1004    #[derive(Default)]
1005    struct MapHeaderTypeCache(HashMap<u64, PackObjectHeader>);
1006
1007    impl HeaderTypeCache for MapHeaderTypeCache {
1008        fn get(&self, pack_offset: u64) -> Option<PackObjectHeader> {
1009            self.0.get(&pack_offset).copied()
1010        }
1011        fn put(&mut self, pack_offset: u64, header: PackObjectHeader) {
1012            self.0.insert(pack_offset, header);
1013        }
1014    }
1015
1016    #[test]
1017    fn read_object_header_at_cached_matches_uncached_cold_and_warm_for_ofs_delta() {
1018        let (base, changed) = similar_blob_objects();
1019        let options = delta_pack_options(true);
1020        let written =
1021            PackFile::write_packed_with_options(&[base, changed], ObjectFormat::Sha1, &options)
1022                .expect("test operation should succeed");
1023        // Ensure the pack genuinely contains an ofs-delta (else the test is vacuous).
1024        let mut second = written.entries[1].offset as usize;
1025        assert_eq!(
1026            parse_entry_header(&written.pack, &mut second)
1027                .expect("test operation should succeed")
1028                .kind,
1029            PackObjectKind::OfsDelta
1030        );
1031
1032        let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
1033        let mut cache = MapHeaderTypeCache::default();
1034        for po in &parsed.entries {
1035            let uncached = read_object_header_at(
1036                &written.pack,
1037                po.entry.offset,
1038                ObjectFormat::Sha1,
1039                0,
1040                |_, _| Ok(None),
1041            )
1042            .expect("test operation should succeed");
1043            // Type inherited from the chain base; size is the inflated body length.
1044            assert_eq!(
1045                uncached.type_and_size(),
1046                (po.object.object_type, po.object.body.len() as u64),
1047                "uncached header at offset {}",
1048                po.entry.offset
1049            );
1050            // Cold cache: must agree with the uncached read and populate the memo.
1051            let cold = read_object_header_at_with_cache(
1052                &written.pack,
1053                po.entry.offset,
1054                ObjectFormat::Sha1,
1055                0,
1056                |_, _| Ok(None),
1057                &mut cache,
1058            )
1059            .expect("test operation should succeed");
1060            assert_eq!(cold, uncached, "cold cache at offset {}", po.entry.offset);
1061        }
1062        // Warm cache: every offset now resolves from the memo and is still correct,
1063        // proving the fast path does not change behavior (sley#26).
1064        for po in &parsed.entries {
1065            let warm = read_object_header_at_with_cache(
1066                &written.pack,
1067                po.entry.offset,
1068                ObjectFormat::Sha1,
1069                0,
1070                |_, _| panic!("warm cache must not re-walk the chain"),
1071                &mut cache,
1072            )
1073            .expect("test operation should succeed");
1074            assert_eq!(
1075                warm.type_and_size(),
1076                (po.object.object_type, po.object.body.len() as u64),
1077                "warm cache at offset {}",
1078                po.entry.offset
1079            );
1080        }
1081    }
1082
1083    #[test]
1084    fn read_object_at_matches_full_parse_for_ref_delta_pack() {
1085        let (base, changed) = similar_blob_objects();
1086        let options = delta_pack_options(false);
1087        let written =
1088            PackFile::write_packed_with_options(&[base, changed], ObjectFormat::Sha1, &options)
1089                .expect("test operation should succeed");
1090        let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
1091        let by_oid: HashMap<ObjectId, Arc<EncodedObject>> = parsed
1092            .entries
1093            .iter()
1094            .map(|po| (po.entry.oid, Arc::new(po.object.clone())))
1095            .collect();
1096        for po in &parsed.entries {
1097            let got =
1098                read_object_at_arc(&written.pack, po.entry.offset, ObjectFormat::Sha1, |oid| {
1099                    Ok(by_oid.get(oid).cloned())
1100                })
1101                .expect("test operation should succeed");
1102            assert_eq!(*got, po.object);
1103        }
1104    }
1105
1106    /// A test-only [`PackDeltaCache`] that records every decode and counts hits,
1107    /// used to prove the cached decode path is byte-identical to the uncached
1108    /// one and that bases are reused across reads.
1109    #[derive(Default)]
1110    struct CountingDeltaCache {
1111        map: std::cell::RefCell<HashMap<u64, Arc<EncodedObject>>>,
1112        hits: std::cell::Cell<usize>,
1113        inserts: std::cell::Cell<usize>,
1114    }
1115
1116    impl PackDeltaCache for CountingDeltaCache {
1117        fn get(&self, offset: u64) -> Option<Arc<EncodedObject>> {
1118            let hit = self.map.borrow().get(&offset).cloned();
1119            if hit.is_some() {
1120                self.hits.set(self.hits.get() + 1);
1121            }
1122            hit
1123        }
1124        fn insert(&self, offset: u64, object: Arc<EncodedObject>) {
1125            self.inserts.set(self.inserts.get() + 1);
1126            self.map.borrow_mut().insert(offset, object);
1127        }
1128    }
1129
1130    #[test]
1131    fn read_object_at_with_cache_matches_uncached_and_reuses_bases() {
1132        // A multi-object pack with a real ofs-delta chain so the cache has bases
1133        // to reuse. Build several similar blobs to encourage deltification.
1134        let mut objects = Vec::new();
1135        for idx in 0..8u32 {
1136            let mut body = vec![b'x'; 4096];
1137            body.extend_from_slice(format!("\nvariant {idx}\n").as_bytes());
1138            objects.push(EncodedObject::new(ObjectType::Blob, body));
1139        }
1140        let options = delta_pack_options(true);
1141        let written = PackFile::write_packed_with_options(&objects, ObjectFormat::Sha1, &options)
1142            .expect("test operation should succeed");
1143        let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
1144
1145        let cache = CountingDeltaCache::default();
1146        // Read every object twice through the cache; each result must equal the
1147        // ground-truth from the full parse, byte for byte, both times.
1148        for _ in 0..2 {
1149            for po in &parsed.entries {
1150                let got = read_object_at_with_cache_arc(
1151                    &written.pack,
1152                    po.entry.offset,
1153                    ObjectFormat::Sha1,
1154                    |_| Ok(None),
1155                    &cache,
1156                )
1157                .expect("test operation should succeed");
1158                assert_eq!(*got, po.object, "offset {}", po.entry.offset);
1159            }
1160        }
1161        // The second pass reads everything straight from the cache, so there must
1162        // be at least one hit (proving reuse, not just correctness).
1163        assert!(cache.hits.get() > 0, "cache never served a warm object");
1164    }
1165
1166    #[test]
1167    fn writes_ofs_delta_pack_and_index_that_round_trip() {
1168        let (base, changed) = similar_blob_objects();
1169        let options = delta_pack_options(true);
1170        let written = PackFile::write_packed_with_options(
1171            &[base.clone(), changed.clone()],
1172            ObjectFormat::Sha1,
1173            &options,
1174        )
1175        .expect("test operation should succeed");
1176        let mut second_offset = written.entries[1].offset as usize;
1177        let header = parse_entry_header(&written.pack, &mut second_offset)
1178            .expect("test operation should succeed");
1179        assert_eq!(header.kind, PackObjectKind::OfsDelta);
1180
1181        let pack = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
1182        let index =
1183            PackIndex::parse_v2_sha1(&written.index).expect("test operation should succeed");
1184        let oid = changed
1185            .object_id(ObjectFormat::Sha1)
1186            .expect("test operation should succeed");
1187        assert_eq!(pack.entries[0].object, base);
1188        assert_eq!(pack.entries[1].object, changed);
1189        assert_eq!(index.pack_checksum, pack.checksum);
1190        assert_eq!(
1191            index
1192                .find(&oid)
1193                .expect("test operation should succeed")
1194                .offset,
1195            written.entries[1].offset
1196        );
1197    }
1198
1199    #[test]
1200    fn resolves_ofs_delta_pack_entry() {
1201        let base = b"hello";
1202        let result = b"hello world";
1203        let pack = two_object_delta_pack(ObjectFormat::Sha1, base, result, DeltaKind::Offset);
1204        let parsed = PackFile::parse_sha1(&pack).expect("test operation should succeed");
1205        assert_eq!(parsed.entries.len(), 2);
1206        assert_eq!(parsed.entries[0].object.body, base);
1207        assert_eq!(parsed.entries[1].object.body, result);
1208        assert_eq!(
1209            parsed.entries[1].entry.oid,
1210            sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
1211                .expect("test operation should succeed")
1212        );
1213    }
1214
1215    #[test]
1216    fn resolves_ref_delta_pack_entry() {
1217        let base = b"hello";
1218        let result = b"hello world";
1219        let pack = two_object_delta_pack(ObjectFormat::Sha1, base, result, DeltaKind::Ref);
1220        let parsed = PackFile::parse_sha1(&pack).expect("test operation should succeed");
1221        assert_eq!(parsed.entries.len(), 2);
1222        assert_eq!(parsed.entries[0].object.body, base);
1223        assert_eq!(parsed.entries[1].object.body, result);
1224        assert_eq!(
1225            parsed.entries[1].entry.oid,
1226            sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
1227                .expect("test operation should succeed")
1228        );
1229    }
1230
1231    #[test]
1232    fn resolves_thin_ref_delta_pack_entry_with_external_base() {
1233        let base = b"hello";
1234        let result = b"hello world";
1235        let pack = thin_ref_delta_pack(ObjectFormat::Sha1, base, result);
1236        assert!(PackFile::parse_sha1(&pack).is_err());
1237
1238        let base_oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", base)
1239            .expect("test operation should succeed");
1240        let parsed = PackFile::parse_thin(&pack, ObjectFormat::Sha1, |oid| {
1241            if oid == &base_oid {
1242                Ok(Some(EncodedObject::new(ObjectType::Blob, base.to_vec())))
1243            } else {
1244                Ok(None)
1245            }
1246        })
1247        .expect("test operation should succeed");
1248        assert_eq!(parsed.entries.len(), 1);
1249        assert_eq!(parsed.entries[0].object.body, result);
1250        assert_eq!(
1251            parsed.entries[0].entry.oid,
1252            sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
1253                .expect("test operation should succeed")
1254        );
1255    }
1256
1257    #[test]
1258    fn fixes_thin_pack_and_indexes_it_without_external_bases() {
1259        for format in [ObjectFormat::Sha1, ObjectFormat::Sha256] {
1260            let base = b"hello";
1261            let result = b"hello world";
1262            let pack = thin_ref_delta_pack(format, base, result);
1263            let base_object = EncodedObject::new(ObjectType::Blob, base.to_vec());
1264            let base_oid = base_object
1265                .object_id(format)
1266                .expect("test operation should succeed");
1267
1268            let fixed = fix_thin_pack(&pack, format, |oid| {
1269                Ok((oid == &base_oid).then(|| base_object.clone()))
1270            })
1271            .expect("thin pack completion should succeed");
1272
1273            assert_eq!(u32_be(&fixed.pack[8..12]), 2);
1274            assert_eq!(fixed.appended_bases, vec![base_oid]);
1275            assert_eq!(fixed.index.entries.len(), 2);
1276            let parsed =
1277                PackFile::parse(&fixed.pack, format).expect("completed pack should stand alone");
1278            assert_eq!(parsed.entries.len(), 2);
1279            assert_eq!(parsed.entries[0].object.body, result);
1280            assert_eq!(parsed.entries[1].object, base_object);
1281            let rebuilt = PackIndex::write_v2_for_pack(&fixed.pack, format)
1282                .expect("completed pack should index without a resolver");
1283            assert_eq!(rebuilt, fixed.index);
1284        }
1285    }
1286
1287    #[test]
1288    fn fix_thin_is_idempotent_for_a_complete_pack() {
1289        let original = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"complete\n");
1290        let mut resolver_called = false;
1291        let fixed = fix_thin_pack(&original, ObjectFormat::Sha1, |_| {
1292            resolver_called = true;
1293            Ok(None)
1294        })
1295        .expect("complete pack should remain complete");
1296
1297        assert_eq!(fixed.pack, original);
1298        assert!(fixed.appended_bases.is_empty());
1299        assert!(!resolver_called);
1300        assert_eq!(fixed.index.entries.len(), 1);
1301    }
1302
1303    #[test]
1304    fn fix_thin_does_not_duplicate_a_base_already_in_the_body() {
1305        let pack =
1306            two_object_delta_pack(ObjectFormat::Sha1, b"hello", b"hello world", DeltaKind::Ref);
1307        let original_count = u32_be(&pack[8..12]);
1308        let mut resolver_called = false;
1309        let fixed = fix_thin_pack(&pack, ObjectFormat::Sha1, |_| {
1310            resolver_called = true;
1311            Ok(None)
1312        })
1313        .expect("in-pack ref base should remain self-contained");
1314
1315        assert_eq!(fixed.pack, pack);
1316        assert_eq!(u32_be(&fixed.pack[8..12]), original_count);
1317        assert_eq!(fixed.index.entries.len(), original_count as usize);
1318        assert!(fixed.appended_bases.is_empty());
1319        assert!(!resolver_called);
1320    }
1321
1322    #[test]
1323    fn fix_thin_appends_a_repeated_external_base_once() {
1324        let base = b"shared";
1325        let pack =
1326            thin_ref_delta_pack_many(ObjectFormat::Sha1, base, &[b"shared one", b"shared two"]);
1327        let base_object = EncodedObject::new(ObjectType::Blob, base.to_vec());
1328        let base_oid = base_object
1329            .object_id(ObjectFormat::Sha1)
1330            .expect("test operation should succeed");
1331        let mut calls = 0;
1332        let fixed = fix_thin_pack(&pack, ObjectFormat::Sha1, |oid| {
1333            calls += 1;
1334            Ok((oid == &base_oid).then(|| base_object.clone()))
1335        })
1336        .expect("shared base completion should succeed");
1337
1338        assert_eq!(calls, 1);
1339        assert_eq!(fixed.appended_bases, vec![base_oid]);
1340        assert_eq!(u32_be(&fixed.pack[8..12]), 3);
1341        assert_eq!(fixed.index.entries.len(), 3);
1342    }
1343
1344    #[test]
1345    fn fix_thin_rejects_missing_and_wrong_external_bases() {
1346        let base = b"hello";
1347        let pack = thin_ref_delta_pack(ObjectFormat::Sha1, base, b"hello world");
1348        assert!(fix_thin_pack(&pack, ObjectFormat::Sha1, |_| Ok(None)).is_err());
1349
1350        let wrong = EncodedObject::new(ObjectType::Blob, b"wrong".to_vec());
1351        let error = fix_thin_pack(&pack, ObjectFormat::Sha1, |_| Ok(Some(wrong.clone())))
1352            .expect_err("wrong base body must not be accepted for the requested oid");
1353        assert!(error.to_string().contains("resolved to object"));
1354    }
1355
1356    #[test]
1357    fn fix_thin_preserves_the_delta_depth_limit_boundary() {
1358        let base = b"hello";
1359        let pack = thin_ref_delta_pack(ObjectFormat::Sha1, base, b"hello world");
1360        let base_object = EncodedObject::new(ObjectType::Blob, base.to_vec());
1361        let base_oid = base_object
1362            .object_id(ObjectFormat::Sha1)
1363            .expect("test operation should succeed");
1364        let resolve = |oid: &ObjectId| Ok((oid == &base_oid).then(|| base_object.clone()));
1365
1366        fix_thin_pack_with_limits(
1367            &pack,
1368            ObjectFormat::Sha1,
1369            resolve,
1370            PackReadLimits {
1371                max_delta_depth: 1,
1372                ..PackReadLimits::default()
1373            },
1374        )
1375        .expect("one delta must pass at the exact limit");
1376
1377        let error = fix_thin_pack_with_limits(
1378            &pack,
1379            ObjectFormat::Sha1,
1380            resolve,
1381            PackReadLimits {
1382                max_delta_depth: 0,
1383                ..PackReadLimits::default()
1384            },
1385        )
1386        .expect_err("one delta must fail immediately above the configured limit");
1387        assert!(error.to_string().contains("observed depth 1"));
1388    }
1389
1390    #[test]
1391    fn parallel_indexer_resolves_thin_bases_but_rejects_missing_ones() {
1392        let base = b"hello";
1393        let result = b"hello stream";
1394        let pack = thin_ref_delta_pack(ObjectFormat::Sha1, base, result);
1395        let base_object = EncodedObject::new(ObjectType::Blob, base.to_vec());
1396        let base_oid = base_object
1397            .object_id(ObjectFormat::Sha1)
1398            .expect("test operation should succeed");
1399
1400        let built = PackIndex::write_v2_for_pack_with_base(&pack, ObjectFormat::Sha1, |oid| {
1401            Ok((oid == &base_oid).then(|| base_object.clone()))
1402        })
1403        .expect("parallel indexer should resolve the supplied base");
1404        assert_eq!(built.entries.len(), 1);
1405        assert_eq!(
1406            built.entries[0].oid,
1407            sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
1408                .expect("test operation should succeed")
1409        );
1410
1411        let error = PackIndex::write_v2_for_pack_with_base(&pack, ObjectFormat::Sha1, |_| Ok(None))
1412            .expect_err("an unresolved base must remain an error");
1413        assert!(error.to_string().contains("unresolved"));
1414    }
1415
1416    #[test]
1417    fn fixed_pack_passes_upstream_git_and_supports_clone() {
1418        let format = ObjectFormat::Sha1;
1419        let base = EncodedObject::new(
1420            ObjectType::Blob,
1421            b"the original contents are deliberately similar\n".repeat(32),
1422        );
1423        let mut target_body = base.body.clone();
1424        target_body.extend_from_slice(b"completed thin pack\n");
1425        let target = EncodedObject::new(ObjectType::Blob, target_body);
1426        let target_oid = target
1427            .object_id(format)
1428            .expect("test operation should succeed");
1429
1430        let mut tree_body = b"100644 payload\0".to_vec();
1431        tree_body.extend_from_slice(target_oid.as_bytes());
1432        let tree = EncodedObject::new(ObjectType::Tree, tree_body);
1433        let tree_oid = tree
1434            .object_id(format)
1435            .expect("test operation should succeed");
1436        let commit = EncodedObject::new(
1437            ObjectType::Commit,
1438            format!(
1439                "tree {tree_oid}\nauthor Sley <sley@example.com> 1 +0000\n\
1440                 committer Sley <sley@example.com> 1 +0000\n\nthin pack fixture\n"
1441            )
1442            .into_bytes(),
1443        );
1444        let commit_oid = commit
1445            .object_id(format)
1446            .expect("test operation should succeed");
1447        let base_oid = base
1448            .object_id(format)
1449            .expect("test operation should succeed");
1450        let thin = PackFile::write_thin(
1451            &[commit, tree, target],
1452            format,
1453            HashMap::from([(base_oid, base.clone())]),
1454        )
1455        .expect("write thin pack");
1456        assert!(thin.delta_count > 0, "fixture must really be thin");
1457        assert!(PackIndex::write_v2_for_pack(&thin.pack, format).is_err());
1458
1459        let fixed = fix_thin_pack(&thin.pack, format, |oid| {
1460            Ok((oid == &base_oid).then(|| base.clone()))
1461        })
1462        .expect("fix thin pack");
1463        let root = unique_temp_dir("fix-thin-git");
1464        let strict_pack = root.join("strict.pack");
1465        fs::create_dir_all(&root).expect("create test root");
1466        fs::write(&strict_pack, &fixed.pack).expect("write strict pack");
1467        run_git_success(&root, &["index-pack", "--strict", "strict.pack"]);
1468
1469        let complete_repo = root.join("complete.git");
1470        run_git_success(
1471            &root,
1472            &[
1473                "init",
1474                "--bare",
1475                "-q",
1476                complete_repo.to_str().expect("utf8 complete repo path"),
1477            ],
1478        );
1479        let complete_pack_dir = complete_repo.join("objects/pack");
1480        let complete_stem = format!("pack-{}", fixed.index.pack_checksum);
1481        fs::write(
1482            complete_pack_dir.join(format!("{complete_stem}.pack")),
1483            &fixed.pack,
1484        )
1485        .expect("write completed repository pack");
1486        fs::write(
1487            complete_pack_dir.join(format!("{complete_stem}.idx")),
1488            &fixed.index.index,
1489        )
1490        .expect("write completed repository index");
1491        fs::create_dir_all(complete_repo.join("refs/heads")).expect("create refs");
1492        fs::write(
1493            complete_repo.join("refs/heads/main"),
1494            format!("{commit_oid}\n"),
1495        )
1496        .expect("write main ref");
1497        fs::write(complete_repo.join("HEAD"), b"ref: refs/heads/main\n").expect("write HEAD");
1498        run_git_success(&complete_repo, &["fsck", "--full"]);
1499
1500        let clone_path = root.join("clone");
1501        run_git_success(
1502            &root,
1503            &[
1504                "clone",
1505                "--no-local",
1506                "-q",
1507                complete_repo.to_str().expect("utf8 complete repo path"),
1508                clone_path.to_str().expect("utf8 clone path"),
1509            ],
1510        );
1511        run_git_success(&clone_path, &["fsck", "--full"]);
1512
1513        let thin_repo = root.join("thin.git");
1514        run_git_success(
1515            &root,
1516            &[
1517                "init",
1518                "--bare",
1519                "-q",
1520                thin_repo.to_str().expect("utf8 thin repo path"),
1521            ],
1522        );
1523        let thin_index = PackIndex::write_v2_for_pack_with_base(&thin.pack, format, |oid| {
1524            Ok((oid == &base_oid).then(|| base.clone()))
1525        })
1526        .expect("sley can build the deliberately permissive thin index");
1527        let thin_pack_dir = thin_repo.join("objects/pack");
1528        let thin_stem = format!("pack-{}", thin_index.pack_checksum);
1529        fs::write(thin_pack_dir.join(format!("{thin_stem}.pack")), &thin.pack)
1530            .expect("write thin repository pack");
1531        fs::write(
1532            thin_pack_dir.join(format!("{thin_stem}.idx")),
1533            &thin_index.index,
1534        )
1535        .expect("write thin repository index");
1536        fs::create_dir_all(thin_repo.join("refs/heads")).expect("create thin refs");
1537        fs::write(thin_repo.join("refs/heads/main"), format!("{commit_oid}\n"))
1538            .expect("write thin main ref");
1539        fs::write(thin_repo.join("HEAD"), b"ref: refs/heads/main\n").expect("write thin HEAD");
1540        let broken_clone_path = root.join("broken-clone");
1541        let failed_clone = Command::new("git")
1542            .current_dir(&root)
1543            .args([
1544                "clone",
1545                "--no-local",
1546                "-q",
1547                thin_repo.to_str().expect("utf8 thin repo path"),
1548                broken_clone_path.to_str().expect("utf8 broken clone path"),
1549            ])
1550            .output()
1551            .expect("run failing clone");
1552        assert!(
1553            !failed_clone.status.success(),
1554            "clone from uncompleted thin pack unexpectedly succeeded"
1555        );
1556
1557        fs::remove_dir_all(root).expect("remove git interoperability fixture");
1558    }
1559
1560    #[test]
1561    fn rejects_bad_pack_checksum() {
1562        let mut pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"hello\n");
1563        let last = pack.len() - 1;
1564        pack[last] ^= 1;
1565        assert!(PackFile::parse_sha1(&pack).is_err());
1566    }
1567
1568    #[test]
1569    fn raw_pack_index_rejects_bad_pack_checksum() {
1570        let mut pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"hello\n");
1571        let last = pack.len() - 1;
1572        pack[last] ^= 1;
1573        assert!(PackIndex::write_v2_for_pack(&pack, ObjectFormat::Sha1).is_err());
1574    }
1575
1576    #[test]
1577    fn pack_index_writer_preserves_duplicate_object_ids() {
1578        let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"same\n")
1579            .expect("test operation should succeed");
1580        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1581            .expect("test operation should succeed");
1582        let entries = vec![
1583            PackIndexEntry {
1584                oid,
1585                crc32: 1,
1586                offset: 12,
1587            },
1588            PackIndexEntry {
1589                oid,
1590                crc32: 2,
1591                offset: 24,
1592            },
1593        ];
1594        let index = PackIndex::write_v2(ObjectFormat::Sha1, &entries, &pack_checksum)
1595            .expect("duplicate objects are valid in a pack index");
1596        let parsed = PackIndex::parse(&index, ObjectFormat::Sha1)
1597            .expect("duplicate-object pack index should parse");
1598        assert_eq!(parsed.entries.len(), 2);
1599        assert!(parsed.entries.iter().all(|entry| entry.oid == oid));
1600        assert!(parsed.find(&oid).is_some());
1601    }
1602
1603    #[test]
1604    fn parses_single_entry_pack_index() {
1605        let oid = ObjectId::from_hex(
1606            ObjectFormat::Sha1,
1607            "ce013625030ba8dba906f756967f9e9ca394464a",
1608        )
1609        .expect("test operation should succeed");
1610        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1611            .expect("test operation should succeed");
1612        let index = single_entry_index(
1613            ObjectFormat::Sha1,
1614            oid,
1615            0x1234_5678,
1616            12,
1617            pack_checksum.clone(),
1618        );
1619        let parsed = PackIndex::parse_v2_sha1(&index).expect("test operation should succeed");
1620        assert_eq!(parsed.version, 2);
1621        assert_eq!(parsed.pack_checksum, pack_checksum);
1622        assert_eq!(parsed.entries.len(), 1);
1623        assert_eq!(
1624            parsed
1625                .find(&oid)
1626                .expect("test operation should succeed")
1627                .offset,
1628            12
1629        );
1630        assert_eq!(
1631            parsed
1632                .find(&oid)
1633                .expect("test operation should succeed")
1634                .crc32,
1635            0x1234_5678
1636        );
1637        assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha1);
1638    }
1639
1640    #[test]
1641    fn parses_single_entry_pack_index_v1() {
1642        let oid = ObjectId::from_hex(
1643            ObjectFormat::Sha1,
1644            "ce013625030ba8dba906f756967f9e9ca394464a",
1645        )
1646        .expect("test operation should succeed");
1647        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1648            .expect("test operation should succeed");
1649        let index =
1650            single_entry_index_v1(ObjectFormat::Sha1, oid, 0x1234_5678, pack_checksum.clone());
1651        let parsed =
1652            PackIndex::parse(&index, ObjectFormat::Sha1).expect("test operation should succeed");
1653        assert_eq!(parsed.version, 1);
1654        assert_eq!(parsed.pack_checksum, pack_checksum);
1655        assert_eq!(parsed.entries.len(), 1);
1656        assert_eq!(
1657            parsed
1658                .find(&oid)
1659                .expect("test operation should succeed")
1660                .offset,
1661            0x1234_5678
1662        );
1663        assert_eq!(
1664            parsed
1665                .find(&oid)
1666                .expect("test operation should succeed")
1667                .crc32,
1668            0
1669        );
1670        assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha1);
1671    }
1672
1673    #[test]
1674    fn rejects_bad_pack_index_v1_checksum() {
1675        let oid = ObjectId::from_hex(
1676            ObjectFormat::Sha1,
1677            "ce013625030ba8dba906f756967f9e9ca394464a",
1678        )
1679        .expect("test operation should succeed");
1680        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1681            .expect("test operation should succeed");
1682        let mut index = single_entry_index_v1(ObjectFormat::Sha1, oid, 12, pack_checksum);
1683        let last = index.len() - 1;
1684        index[last] ^= 1;
1685        assert!(PackIndex::parse(&index, ObjectFormat::Sha1).is_err());
1686    }
1687
1688    #[test]
1689    fn pack_index_view_reads_v2_large_offsets() {
1690        let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"large offset a\n")
1691            .expect("test operation should succeed");
1692        let second =
1693            sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"large offset b\n")
1694                .expect("test operation should succeed");
1695        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1696            .expect("test operation should succeed");
1697        let entries = vec![
1698            PackIndexEntry {
1699                oid: first,
1700                crc32: 0x1111_2222,
1701                offset: 0x8000_0000,
1702            },
1703            PackIndexEntry {
1704                oid: second,
1705                crc32: 0x3333_4444,
1706                offset: 0x1_0000_0042,
1707            },
1708        ];
1709        let index = PackIndex::write_v2(ObjectFormat::Sha1, &entries, &pack_checksum)
1710            .expect("test operation should succeed");
1711
1712        assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha1);
1713        let view = PackIndexView::parse(&index, ObjectFormat::Sha1)
1714            .expect("test operation should succeed");
1715        for entry in entries {
1716            assert_eq!(
1717                view.find(&entry.oid),
1718                Some(PackIndexLookup {
1719                    crc32: entry.crc32,
1720                    offset: entry.offset,
1721                })
1722            );
1723        }
1724    }
1725
1726    #[test]
1727    fn pack_index_view_default_parse_checks_index_checksum() {
1728        let oid = ObjectId::from_hex(
1729            ObjectFormat::Sha1,
1730            "ce013625030ba8dba906f756967f9e9ca394464a",
1731        )
1732        .expect("test operation should succeed");
1733        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1734            .expect("test operation should succeed");
1735        let mut index = single_entry_index(ObjectFormat::Sha1, oid, 0x1234_5678, 12, pack_checksum);
1736        let last = index.len() - 1;
1737        index[last] ^= 1;
1738
1739        assert!(PackIndexView::parse(&index, ObjectFormat::Sha1).is_err());
1740        let view = PackIndexView::parse_without_checksum(&index, ObjectFormat::Sha1)
1741            .expect("test operation should succeed");
1742        let trusted_view = PackIndexViewData::parse_trusted_without_checksum(
1743            Arc::from(index.clone().into_boxed_slice()),
1744            ObjectFormat::Sha1,
1745        )
1746        .expect("test operation should succeed");
1747        assert_eq!(
1748            view.find(&oid),
1749            Some(PackIndexLookup {
1750                crc32: 0x1234_5678,
1751                offset: 12,
1752            })
1753        );
1754        assert_eq!(
1755            trusted_view.find(&oid),
1756            Some(PackIndexLookup {
1757                crc32: 0x1234_5678,
1758                offset: 12,
1759            })
1760        );
1761    }
1762
1763    #[test]
1764    fn reverse_index_resolves_oid_at_offset() {
1765        let objects = (0..3)
1766            .map(|idx| {
1767                EncodedObject::new(
1768                    ObjectType::Blob,
1769                    format!("reverse index lookup object {idx}\n").into_bytes(),
1770                )
1771            })
1772            .collect::<Vec<_>>();
1773        let written = PackFile::write_packed(&objects, ObjectFormat::Sha1)
1774            .expect("test operation should succeed");
1775        let index = PackIndex::parse(&written.index, ObjectFormat::Sha1)
1776            .expect("test operation should succeed");
1777        let view = PackIndexViewData::parse_trusted_without_checksum(
1778            Arc::from(written.index.into_boxed_slice()),
1779            ObjectFormat::Sha1,
1780        )
1781        .expect("test operation should succeed");
1782        let positions = pack_order_index_positions(&index.entries);
1783        let reverse = PackReverseIndex::parse(
1784            &PackReverseIndex::write(ObjectFormat::Sha1, &positions, &index.pack_checksum)
1785                .expect("test operation should succeed"),
1786            ObjectFormat::Sha1,
1787            index.entries.len(),
1788        )
1789        .expect("test operation should succeed");
1790
1791        for entry in &index.entries {
1792            assert_eq!(
1793                reverse
1794                    .oid_at_offset(&view, entry.offset)
1795                    .expect("test operation should succeed"),
1796                entry.oid
1797            );
1798        }
1799        assert!(reverse.oid_at_offset(&view, 999).is_none());
1800    }
1801
1802    #[test]
1803    fn parses_pack_reverse_index() {
1804        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1805            .expect("test operation should succeed");
1806        let reverse_index = PackReverseIndex::write(ObjectFormat::Sha1, &[2, 0, 1], &pack_checksum)
1807            .expect("test operation should succeed");
1808        let parsed = PackReverseIndex::parse(&reverse_index, ObjectFormat::Sha1, 3)
1809            .expect("test operation should succeed");
1810        assert_eq!(parsed.version, 1);
1811        assert_eq!(parsed.format, ObjectFormat::Sha1);
1812        assert_eq!(parsed.positions, vec![2, 0, 1]);
1813        assert_eq!(parsed.pack_checksum, pack_checksum);
1814        assert_eq!(
1815            PackReverseIndex::write(ObjectFormat::Sha1, &parsed.positions, &parsed.pack_checksum)
1816                .expect("test operation should succeed"),
1817            reverse_index
1818        );
1819    }
1820
1821    #[test]
1822    fn rejects_bad_pack_reverse_index_checksum() {
1823        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1824            .expect("test operation should succeed");
1825        let mut reverse_index = PackReverseIndex::write(ObjectFormat::Sha1, &[0], &pack_checksum)
1826            .expect("test operation should succeed");
1827        let last = reverse_index.len() - 1;
1828        reverse_index[last] ^= 1;
1829        assert!(matches!(
1830            PackReverseIndex::parse(&reverse_index, ObjectFormat::Sha1, 1),
1831            Err(GitError::InvalidFormat(message)) if message == "invalid checksum"
1832        ));
1833    }
1834
1835    #[test]
1836    fn classifies_pack_reverse_index_corruption_for_fsck() {
1837        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1838            .expect("test operation should succeed");
1839        let valid = PackReverseIndex::write(ObjectFormat::Sha1, &[0], &pack_checksum)
1840            .expect("test operation should succeed");
1841        for (offset, value, expected) in [
1842            (1, 7, "unknown signature"),
1843            (7, 2, "unsupported version 2"),
1844            (11, 3, "unsupported hash id 3"),
1845            (14, 7, "invalid rev-index position 1792"),
1846        ] {
1847            let mut corrupt = valid.clone();
1848            corrupt[offset] = value;
1849            assert!(matches!(
1850                PackReverseIndex::parse(&corrupt, ObjectFormat::Sha1, 1),
1851                Err(GitError::InvalidFormat(message)) if message == expected
1852            ));
1853        }
1854    }
1855
1856    #[test]
1857    fn rejects_bad_pack_reverse_index_positions() {
1858        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1859            .expect("test operation should succeed");
1860        let duplicate = pack_reverse_index(ObjectFormat::Sha1, &[0, 0], pack_checksum.clone());
1861        assert!(PackReverseIndex::parse(&duplicate, ObjectFormat::Sha1, 2).is_err());
1862        let out_of_range = pack_reverse_index(ObjectFormat::Sha1, &[0, 2], pack_checksum);
1863        assert!(PackReverseIndex::parse(&out_of_range, ObjectFormat::Sha1, 2).is_err());
1864        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1865            .expect("test operation should succeed");
1866        assert!(PackReverseIndex::write(ObjectFormat::Sha1, &[0, 0], &pack_checksum).is_err());
1867        assert!(PackReverseIndex::write(ObjectFormat::Sha1, &[0, 2], &pack_checksum).is_err());
1868    }
1869
1870    #[test]
1871    fn parses_pack_mtimes() {
1872        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1873            .expect("test operation should succeed");
1874        let mtimes = PackMtimes::write(
1875            ObjectFormat::Sha1,
1876            &[1, 1_700_000_000, u32::MAX],
1877            &pack_checksum,
1878        )
1879        .expect("test operation should succeed");
1880        let parsed = PackMtimes::parse(&mtimes, ObjectFormat::Sha1, 3)
1881            .expect("test operation should succeed");
1882        assert_eq!(parsed.version, 1);
1883        assert_eq!(parsed.format, ObjectFormat::Sha1);
1884        assert_eq!(parsed.mtimes, vec![1, 1_700_000_000, u32::MAX]);
1885        assert_eq!(parsed.pack_checksum, pack_checksum);
1886        assert_eq!(
1887            PackMtimes::write(ObjectFormat::Sha1, &parsed.mtimes, &parsed.pack_checksum)
1888                .expect("test operation should succeed"),
1889            mtimes
1890        );
1891    }
1892
1893    #[test]
1894    fn rejects_bad_pack_mtimes_checksum() {
1895        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1896            .expect("test operation should succeed");
1897        let mut mtimes = PackMtimes::write(ObjectFormat::Sha1, &[1], &pack_checksum)
1898            .expect("test operation should succeed");
1899        let last = mtimes.len() - 1;
1900        mtimes[last] ^= 1;
1901        assert!(PackMtimes::parse(&mtimes, ObjectFormat::Sha1, 1).is_err());
1902    }
1903
1904    #[test]
1905    fn rejects_bad_pack_mtimes_shape() {
1906        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1907            .expect("test operation should succeed");
1908        let mtimes = pack_mtimes(ObjectFormat::Sha1, &[1, 2], pack_checksum.clone());
1909        assert!(PackMtimes::parse(&mtimes, ObjectFormat::Sha1, 1).is_err());
1910
1911        let mut wrong_hash = pack_mtimes(ObjectFormat::Sha1, &[1], pack_checksum);
1912        wrong_hash[11] = 2;
1913        let checksum_offset = wrong_hash.len() - ObjectFormat::Sha1.raw_len();
1914        let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &wrong_hash[..checksum_offset])
1915            .expect("test operation should succeed");
1916        wrong_hash[checksum_offset..].copy_from_slice(checksum.as_bytes());
1917        assert!(PackMtimes::parse(&wrong_hash, ObjectFormat::Sha1, 1).is_err());
1918    }
1919
1920    #[test]
1921    fn parses_multi_pack_index_header_and_chunk_lookup() {
1922        let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
1923            .expect("test operation should succeed");
1924        let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
1925            .expect("test operation should succeed");
1926        let chunks = midx_chunks_with_pack_names(
1927            ObjectFormat::Sha1,
1928            b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
1929            &[(first.clone(), 0, 12), (second.clone(), 1, 0x1_0000_0000)],
1930        );
1931        let midx = multi_pack_index(ObjectFormat::Sha1, 2, 2, &chunks);
1932        let parsed = MultiPackIndex::parse(&midx, ObjectFormat::Sha1)
1933            .expect("test operation should succeed");
1934        assert_eq!(parsed.version, 2);
1935        assert_eq!(parsed.format, ObjectFormat::Sha1);
1936        assert_eq!(parsed.pack_count, 2);
1937        assert_eq!(parsed.pack_names, vec!["pack-a.idx", "pack-b.idx"]);
1938        assert_eq!(parsed.object_count, 2);
1939        assert_eq!(parsed.objects.len(), 2);
1940        assert_eq!(
1941            parsed
1942                .find(&first)
1943                .expect("test operation should succeed")
1944                .pack_int_id,
1945            0
1946        );
1947        assert_eq!(
1948            parsed
1949                .find(&first)
1950                .expect("test operation should succeed")
1951                .offset,
1952            12
1953        );
1954        assert_eq!(
1955            parsed
1956                .find(&second)
1957                .expect("test operation should succeed")
1958                .pack_int_id,
1959            1
1960        );
1961        assert_eq!(
1962            parsed
1963                .find(&second)
1964                .expect("test operation should succeed")
1965                .offset,
1966            0x1_0000_0000
1967        );
1968        assert_eq!(parsed.reverse_index, None);
1969        assert_eq!(parsed.bitmapped_packs, None);
1970        assert_eq!(parsed.chunks.len(), 5);
1971        assert_eq!(parsed.chunks[0].id, *b"PNAM");
1972        assert_eq!(parsed.chunks[0].offset, 84);
1973        assert_eq!(parsed.chunks[0].len, 24);
1974        assert_eq!(parsed.chunks[1].id, *b"OIDF");
1975        assert_eq!(parsed.chunks[1].offset, 108);
1976        assert_eq!(parsed.chunks[1].len, 1024);
1977    }
1978
1979    #[test]
1980    fn raw_multi_pack_index_lookup_finds_pack_and_offset() {
1981        let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
1982            .expect("test operation should succeed");
1983        let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
1984            .expect("test operation should succeed");
1985        let missing = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"missing\n")
1986            .expect("test operation should succeed");
1987        let chunks = midx_chunks_with_pack_names(
1988            ObjectFormat::Sha1,
1989            b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
1990            &[(first.clone(), 0, 12), (second.clone(), 1, 0x1_0000_0000)],
1991        );
1992        let midx = Arc::new(multi_pack_index(ObjectFormat::Sha1, 2, 2, &chunks));
1993        let lookup = MultiPackIndexOidLookup::parse(midx, ObjectFormat::Sha1)
1994            .expect("test operation should succeed");
1995
1996        assert!(lookup.contains(&first));
1997        assert!(lookup.contains(&second));
1998        assert!(!lookup.contains(&missing));
1999
2000        let first_entry = lookup
2001            .find(&first)
2002            .expect("test operation should succeed")
2003            .expect("object should be present");
2004        assert_eq!(
2005            lookup.pack_name(first_entry.pack_int_id),
2006            Some("pack-a.idx")
2007        );
2008        assert_eq!(first_entry.offset, 12);
2009
2010        let second_entry = lookup
2011            .find(&second)
2012            .expect("test operation should succeed")
2013            .expect("object should be present");
2014        assert_eq!(
2015            lookup.pack_name(second_entry.pack_int_id),
2016            Some("pack-b.idx")
2017        );
2018        assert_eq!(second_entry.offset, 0x1_0000_0000);
2019        assert!(
2020            lookup
2021                .find(&missing)
2022                .expect("test operation should succeed")
2023                .is_none()
2024        );
2025    }
2026
2027    #[test]
2028    fn rejects_bad_multi_pack_index_checksum() {
2029        let chunks = midx_chunks_with_pack_names(ObjectFormat::Sha1, Vec::new(), &[]);
2030        let mut midx = multi_pack_index(ObjectFormat::Sha1, 1, 0, &chunks);
2031        let last = midx.len() - 1;
2032        midx[last] ^= 1;
2033        assert!(MultiPackIndex::parse(&midx, ObjectFormat::Sha1).is_err());
2034    }
2035
2036    #[test]
2037    fn rejects_bad_multi_pack_index_shape() {
2038        let chunks = midx_chunks_with_pack_names(ObjectFormat::Sha1, Vec::new(), &[]);
2039        let mut wrong_hash = multi_pack_index(ObjectFormat::Sha1, 1, 0, &chunks);
2040        wrong_hash[5] = 2;
2041        let checksum_offset = wrong_hash.len() - ObjectFormat::Sha1.raw_len();
2042        let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &wrong_hash[..checksum_offset])
2043            .expect("test operation should succeed");
2044        wrong_hash[checksum_offset..].copy_from_slice(checksum.as_bytes());
2045        assert!(MultiPackIndex::parse(&wrong_hash, ObjectFormat::Sha1).is_err());
2046
2047        let mut missing_terminator = multi_pack_index(ObjectFormat::Sha1, 1, 0, &chunks);
2048        missing_terminator[12] = b'B';
2049        let checksum_offset = missing_terminator.len() - ObjectFormat::Sha1.raw_len();
2050        let checksum =
2051            sley_core::digest_bytes(ObjectFormat::Sha1, &missing_terminator[..checksum_offset])
2052                .expect("test operation should succeed");
2053        missing_terminator[checksum_offset..].copy_from_slice(checksum.as_bytes());
2054        assert!(MultiPackIndex::parse(&missing_terminator, ObjectFormat::Sha1).is_err());
2055
2056        let mut bad_offset = multi_pack_index(
2057            ObjectFormat::Sha1,
2058            2,
2059            0,
2060            &midx_chunks_with_pack_names(ObjectFormat::Sha1, Vec::new(), &[]),
2061        );
2062        bad_offset[16..24].copy_from_slice(&0u64.to_be_bytes());
2063        let checksum_offset = bad_offset.len() - ObjectFormat::Sha1.raw_len();
2064        let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &bad_offset[..checksum_offset])
2065            .expect("test operation should succeed");
2066        bad_offset[checksum_offset..].copy_from_slice(checksum.as_bytes());
2067        assert!(MultiPackIndex::parse(&bad_offset, ObjectFormat::Sha1).is_err());
2068    }
2069
2070    #[test]
2071    fn rejects_bad_multi_pack_index_pack_names() {
2072        let missing = multi_pack_index(ObjectFormat::Sha1, 2, 1, &[]);
2073        assert!(MultiPackIndex::parse(&missing, ObjectFormat::Sha1).is_err());
2074
2075        let too_few = multi_pack_index(
2076            ObjectFormat::Sha1,
2077            2,
2078            2,
2079            &midx_chunks_with_pack_names(ObjectFormat::Sha1, b"pack-a.idx\0".to_vec(), &[]),
2080        );
2081        assert!(MultiPackIndex::parse(&too_few, ObjectFormat::Sha1).is_err());
2082
2083        let bad_padding = multi_pack_index(
2084            ObjectFormat::Sha1,
2085            2,
2086            1,
2087            &midx_chunks_with_pack_names(ObjectFormat::Sha1, b"pack-a.idx\0xxxx".to_vec(), &[]),
2088        );
2089        assert!(MultiPackIndex::parse(&bad_padding, ObjectFormat::Sha1).is_err());
2090
2091        let unsorted_v1 = multi_pack_index(
2092            ObjectFormat::Sha1,
2093            1,
2094            2,
2095            &midx_chunks_with_pack_names(
2096                ObjectFormat::Sha1,
2097                b"pack-b.idx\0pack-a.idx\0".to_vec(),
2098                &[],
2099            ),
2100        );
2101        assert!(MultiPackIndex::parse(&unsorted_v1, ObjectFormat::Sha1).is_err());
2102
2103        let unsorted_v2 = multi_pack_index(
2104            ObjectFormat::Sha1,
2105            2,
2106            2,
2107            &midx_chunks_with_pack_names(
2108                ObjectFormat::Sha1,
2109                b"pack-b.idx\0pack-a.idx\0".to_vec(),
2110                &[],
2111            ),
2112        );
2113        let parsed = MultiPackIndex::parse(&unsorted_v2, ObjectFormat::Sha1)
2114            .expect("test operation should succeed");
2115        assert_eq!(parsed.pack_names, vec!["pack-b.idx", "pack-a.idx"]);
2116    }
2117
2118    #[test]
2119    fn rejects_bad_multi_pack_index_object_tables() {
2120        let oid_a = ObjectId::from_hex(
2121            ObjectFormat::Sha1,
2122            "1111111111111111111111111111111111111111",
2123        )
2124        .expect("test operation should succeed");
2125        let oid_b = ObjectId::from_hex(
2126            ObjectFormat::Sha1,
2127            "2222222222222222222222222222222222222222",
2128        )
2129        .expect("test operation should succeed");
2130
2131        let missing_oidf = multi_pack_index(
2132            ObjectFormat::Sha1,
2133            2,
2134            1,
2135            &[(*b"PNAM", b"pack-a.idx\0\0".to_vec())],
2136        );
2137        assert!(MultiPackIndex::parse(&missing_oidf, ObjectFormat::Sha1).is_err());
2138
2139        let bad_fanout = vec![
2140            (*b"PNAM", b"pack-a.idx\0\0".to_vec()),
2141            (*b"OIDF", vec![0; 256 * 4]),
2142            (*b"OIDL", oid_a.as_bytes().to_vec()),
2143            (*b"OOFF", midx_ooff_entries(&[(0, 12)], &mut Vec::new())),
2144        ];
2145        let bad_fanout = multi_pack_index(ObjectFormat::Sha1, 2, 1, &bad_fanout);
2146        assert!(MultiPackIndex::parse(&bad_fanout, ObjectFormat::Sha1).is_err());
2147
2148        let mut unsorted = Vec::new();
2149        unsorted.push((*b"PNAM", b"pack-a.idx\0\0".to_vec()));
2150        unsorted.push((*b"OIDF", midx_oid_fanout(&[oid_a.clone(), oid_b.clone()])));
2151        let mut oid_lookup = Vec::new();
2152        oid_lookup.extend_from_slice(oid_b.as_bytes());
2153        oid_lookup.extend_from_slice(oid_a.as_bytes());
2154        unsorted.push((*b"OIDL", oid_lookup));
2155        unsorted.push((
2156            *b"OOFF",
2157            midx_ooff_entries(&[(0, 12), (0, 24)], &mut Vec::new()),
2158        ));
2159        let unsorted = multi_pack_index(ObjectFormat::Sha1, 2, 1, &unsorted);
2160        assert!(MultiPackIndex::parse(&unsorted, ObjectFormat::Sha1).is_err());
2161
2162        let bad_pack = multi_pack_index(
2163            ObjectFormat::Sha1,
2164            2,
2165            1,
2166            &midx_chunks_with_pack_names(
2167                ObjectFormat::Sha1,
2168                b"pack-a.idx\0\0".to_vec(),
2169                &[(oid_a.clone(), 1, 12)],
2170            ),
2171        );
2172        assert!(MultiPackIndex::parse(&bad_pack, ObjectFormat::Sha1).is_err());
2173
2174        let mut large_offsets = Vec::new();
2175        let missing_loff = vec![
2176            (*b"PNAM", b"pack-a.idx\0\0".to_vec()),
2177            (*b"OIDF", midx_oid_fanout(std::slice::from_ref(&oid_a))),
2178            (*b"OIDL", oid_a.as_bytes().to_vec()),
2179            (
2180                *b"OOFF",
2181                midx_ooff_entries(&[(0, 0x1_0000_0000)], &mut large_offsets),
2182            ),
2183        ];
2184        let missing_loff = multi_pack_index(ObjectFormat::Sha1, 2, 1, &missing_loff);
2185        assert!(MultiPackIndex::parse(&missing_loff, ObjectFormat::Sha1).is_err());
2186
2187        let mut bad_loff =
2188            midx_chunks_with_pack_names(ObjectFormat::Sha1, b"pack-a.idx\0\0".to_vec(), &[]);
2189        bad_loff.push((*b"LOFF", vec![0]));
2190        let bad_loff = multi_pack_index(ObjectFormat::Sha1, 2, 1, &bad_loff);
2191        assert!(MultiPackIndex::parse(&bad_loff, ObjectFormat::Sha1).is_err());
2192    }
2193
2194    #[test]
2195    fn parses_multi_pack_index_bitmap_chunks() {
2196        let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
2197            .expect("test operation should succeed");
2198        let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
2199            .expect("test operation should succeed");
2200        let mut chunks = midx_chunks_with_pack_names(
2201            ObjectFormat::Sha1,
2202            b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
2203            &[(first, 0, 12), (second, 1, 24)],
2204        );
2205        chunks.push((*b"RIDX", midx_u32_table(&[1, 0])));
2206        chunks.push((*b"BTMP", midx_bitmap_packs(&[(0, 1), (1, 1)])));
2207        let midx = multi_pack_index(ObjectFormat::Sha1, 2, 2, &chunks);
2208
2209        let parsed = MultiPackIndex::parse(&midx, ObjectFormat::Sha1)
2210            .expect("test operation should succeed");
2211        assert_eq!(parsed.reverse_index, Some(vec![1, 0]));
2212        assert_eq!(
2213            parsed.bitmapped_packs,
2214            Some(vec![
2215                MultiPackBitmapPack {
2216                    bitmap_pos: 0,
2217                    bitmap_nr: 1,
2218                },
2219                MultiPackBitmapPack {
2220                    bitmap_pos: 1,
2221                    bitmap_nr: 1,
2222                },
2223            ])
2224        );
2225    }
2226
2227    #[test]
2228    fn writes_multi_pack_index_that_round_trips() {
2229        let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
2230            .expect("test operation should succeed");
2231        let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
2232            .expect("test operation should succeed");
2233        let bytes = MultiPackIndex::write(
2234            ObjectFormat::Sha1,
2235            2,
2236            &["pack-b.idx".into(), "pack-a.idx".into()],
2237            &[
2238                MultiPackIndexEntry {
2239                    oid: second.clone(),
2240                    pack_int_id: 0,
2241                    offset: 0x1_0000_0000,
2242                    force_large_offset: false,
2243                },
2244                MultiPackIndexEntry {
2245                    oid: first.clone(),
2246                    pack_int_id: 1,
2247                    offset: 12,
2248                    force_large_offset: false,
2249                },
2250            ],
2251        )
2252        .expect("test operation should succeed");
2253
2254        let parsed = MultiPackIndex::parse(&bytes, ObjectFormat::Sha1)
2255            .expect("test operation should succeed");
2256        assert_eq!(parsed.version, 2);
2257        assert_eq!(parsed.pack_names, vec!["pack-b.idx", "pack-a.idx"]);
2258        assert_eq!(parsed.object_count, 2);
2259        assert_eq!(
2260            parsed
2261                .find(&first)
2262                .expect("test operation should succeed")
2263                .pack_int_id,
2264            1
2265        );
2266        assert_eq!(
2267            parsed
2268                .find(&first)
2269                .expect("test operation should succeed")
2270                .offset,
2271            12
2272        );
2273        assert_eq!(
2274            parsed
2275                .find(&second)
2276                .expect("test operation should succeed")
2277                .pack_int_id,
2278            0
2279        );
2280        assert_eq!(
2281            parsed
2282                .find(&second)
2283                .expect("test operation should succeed")
2284                .offset,
2285            0x1_0000_0000
2286        );
2287        assert!(parsed.chunks.iter().any(|chunk| chunk.id == *b"LOFF"));
2288    }
2289
2290    #[test]
2291    fn write_multi_pack_index_rejects_invalid_inputs() {
2292        let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"object\n")
2293            .expect("test operation should succeed");
2294        assert!(MultiPackIndex::write(ObjectFormat::Sha1, 3, &["pack-a.idx".into()], &[]).is_err());
2295        assert!(
2296            MultiPackIndex::write(
2297                ObjectFormat::Sha1,
2298                1,
2299                &["pack-b.idx".into(), "pack-a.idx".into()],
2300                &[],
2301            )
2302            .is_err()
2303        );
2304        assert!(MultiPackIndex::write(ObjectFormat::Sha1, 2, &["pack/a.idx".into()], &[]).is_err());
2305        assert!(
2306            MultiPackIndex::write(
2307                ObjectFormat::Sha1,
2308                2,
2309                &["pack-a.idx".into()],
2310                &[MultiPackIndexEntry {
2311                    oid,
2312                    pack_int_id: 1,
2313                    offset: 12,
2314                    force_large_offset: false,
2315                }],
2316            )
2317            .is_err()
2318        );
2319        assert!(
2320            MultiPackIndex::write(
2321                ObjectFormat::Sha1,
2322                2,
2323                &["pack-a.idx".into()],
2324                &[
2325                    MultiPackIndexEntry {
2326                        oid,
2327                        pack_int_id: 0,
2328                        offset: 12,
2329                        force_large_offset: false,
2330                    },
2331                    MultiPackIndexEntry {
2332                        oid,
2333                        pack_int_id: 0,
2334                        offset: 24,
2335                        force_large_offset: false,
2336                    },
2337                ],
2338            )
2339            .is_err()
2340        );
2341    }
2342
2343    #[test]
2344    fn rejects_bad_multi_pack_index_bitmap_chunks() {
2345        let oid_a = ObjectId::from_hex(
2346            ObjectFormat::Sha1,
2347            "1111111111111111111111111111111111111111",
2348        )
2349        .expect("test operation should succeed");
2350        let oid_b = ObjectId::from_hex(
2351            ObjectFormat::Sha1,
2352            "2222222222222222222222222222222222222222",
2353        )
2354        .expect("test operation should succeed");
2355
2356        let mut duplicate_ridx = midx_chunks_with_pack_names(
2357            ObjectFormat::Sha1,
2358            b"pack-a.idx\0\0".to_vec(),
2359            &[(oid_a.clone(), 0, 12), (oid_b.clone(), 0, 24)],
2360        );
2361        duplicate_ridx.push((*b"RIDX", midx_u32_table(&[0, 0])));
2362        let duplicate_ridx = multi_pack_index(ObjectFormat::Sha1, 2, 1, &duplicate_ridx);
2363        assert!(MultiPackIndex::parse(&duplicate_ridx, ObjectFormat::Sha1).is_err());
2364
2365        let mut short_btmp = midx_chunks_with_pack_names(
2366            ObjectFormat::Sha1,
2367            b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
2368            &[(oid_a.clone(), 0, 12), (oid_b.clone(), 1, 24)],
2369        );
2370        short_btmp.push((*b"BTMP", midx_bitmap_packs(&[(0, 1)])));
2371        let short_btmp = multi_pack_index(ObjectFormat::Sha1, 2, 2, &short_btmp);
2372        assert!(MultiPackIndex::parse(&short_btmp, ObjectFormat::Sha1).is_err());
2373
2374        let mut out_of_range_btmp = midx_chunks_with_pack_names(
2375            ObjectFormat::Sha1,
2376            b"pack-a.idx\0\0".to_vec(),
2377            &[(oid_a, 0, 12), (oid_b, 0, 24)],
2378        );
2379        out_of_range_btmp.push((*b"BTMP", midx_bitmap_packs(&[(1, 2)])));
2380        let out_of_range_btmp = multi_pack_index(ObjectFormat::Sha1, 2, 1, &out_of_range_btmp);
2381        assert!(MultiPackIndex::parse(&out_of_range_btmp, ObjectFormat::Sha1).is_err());
2382    }
2383
2384    #[test]
2385    fn parses_pack_bitmap_index_with_hash_cache() {
2386        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
2387            .expect("test operation should succeed");
2388        let bitmap = pack_bitmap_index(
2389            ObjectFormat::Sha1,
2390            3,
2391            PackBitmapIndex::OPTION_FULL_DAG | PackBitmapIndex::OPTION_HASH_CACHE,
2392            &pack_checksum,
2393            &[(2, 0, 1, &[0b101])],
2394            Some(&[0x1111_1111, 0x2222_2222, 0x3333_3333]),
2395        );
2396
2397        let parsed = PackBitmapIndex::parse(&bitmap, ObjectFormat::Sha1, 3)
2398            .expect("test operation should succeed");
2399        assert_eq!(parsed.version, 1);
2400        assert_eq!(parsed.format, ObjectFormat::Sha1);
2401        assert_eq!(
2402            parsed.options,
2403            PackBitmapIndex::OPTION_FULL_DAG | PackBitmapIndex::OPTION_HASH_CACHE
2404        );
2405        assert_eq!(parsed.pack_checksum, pack_checksum);
2406        assert_eq!(parsed.type_bitmaps.commits.bit_size, 3);
2407        assert_eq!(parsed.type_bitmaps.trees.bit_size, 3);
2408        assert_eq!(parsed.entries.len(), 1);
2409        let entry = parsed
2410            .entry_for_index_position(2)
2411            .expect("test operation should succeed");
2412        assert_eq!(entry.xor_offset, 0);
2413        assert_eq!(entry.flags, 1);
2414        assert_eq!(entry.bitmap.words, ewah_literal_words(&[0b101]));
2415        assert_eq!(
2416            parsed.name_hash_cache,
2417            Some(vec![0x1111_1111, 0x2222_2222, 0x3333_3333])
2418        );
2419    }
2420
2421    #[test]
2422    fn parses_pack_bitmap_index_sha256() {
2423        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
2424            .expect("test operation should succeed");
2425        let bitmap = pack_bitmap_index(
2426            ObjectFormat::Sha256,
2427            2,
2428            PackBitmapIndex::OPTION_FULL_DAG,
2429            &pack_checksum,
2430            &[(0, 0, 0, &[0b11])],
2431            None,
2432        );
2433
2434        let parsed = PackBitmapIndex::parse(&bitmap, ObjectFormat::Sha256, 2)
2435            .expect("test operation should succeed");
2436        assert_eq!(parsed.version, 1);
2437        assert_eq!(parsed.format, ObjectFormat::Sha256);
2438        assert_eq!(parsed.pack_checksum, pack_checksum);
2439        assert_eq!(parsed.index_checksum.format(), ObjectFormat::Sha256);
2440        assert_eq!(parsed.entries[0].object_position, 0);
2441        assert_eq!(parsed.name_hash_cache, None);
2442    }
2443
2444    #[test]
2445    fn parses_upstream_git_written_pack_bitmap_index() {
2446        let root = unique_temp_dir("git-pack-bitmap-upstream");
2447        fs::create_dir_all(&root).expect("test operation should succeed");
2448        {
2449            run_git_success(&root, &["init", "-q", "-b", "main"]);
2450            run_git_success(
2451                &root,
2452                &[
2453                    "-c",
2454                    "user.name=Example User",
2455                    "-c",
2456                    "user.email=example@example.invalid",
2457                    "commit",
2458                    "--allow-empty",
2459                    "-q",
2460                    "-m",
2461                    "one",
2462                ],
2463            );
2464            run_git_success(
2465                &root,
2466                &[
2467                    "-c",
2468                    "user.name=Example User",
2469                    "-c",
2470                    "user.email=example@example.invalid",
2471                    "commit",
2472                    "--allow-empty",
2473                    "-q",
2474                    "-m",
2475                    "two",
2476                ],
2477            );
2478            run_git_success(&root, &["repack", "-adb"]);
2479            let pack_dir = root.join(".git").join("objects").join("pack");
2480            let idx_path = single_path_with_extension(&pack_dir, "idx");
2481            let bitmap_path = single_path_with_extension(&pack_dir, "bitmap");
2482            let index = PackIndex::parse(
2483                &fs::read(idx_path).expect("test operation should succeed"),
2484                ObjectFormat::Sha1,
2485            )
2486            .expect("test operation should succeed");
2487            let bitmap = PackBitmapIndex::parse(
2488                &fs::read(bitmap_path).expect("test operation should succeed"),
2489                ObjectFormat::Sha1,
2490                index.entries.len(),
2491            )
2492            .expect("test operation should succeed");
2493            assert_eq!(bitmap.pack_checksum, index.pack_checksum);
2494            assert!(!bitmap.entries.is_empty());
2495        };
2496        let _ = fs::remove_dir_all(&root);
2497    }
2498
2499    #[test]
2500    fn rejects_bad_pack_bitmap_index_header_and_checksum() {
2501        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
2502            .expect("test operation should succeed");
2503        let bitmap = pack_bitmap_index(
2504            ObjectFormat::Sha1,
2505            1,
2506            PackBitmapIndex::OPTION_FULL_DAG,
2507            &pack_checksum,
2508            &[(0, 0, 0, &[1])],
2509            None,
2510        );
2511
2512        let mut bad_signature = bitmap.clone();
2513        bad_signature[0] = b'X';
2514        assert!(PackBitmapIndex::parse(&bad_signature, ObjectFormat::Sha1, 1).is_err());
2515
2516        let mut bad_version = bitmap.clone();
2517        bad_version[5] = 2;
2518        refresh_trailing_checksum(ObjectFormat::Sha1, &mut bad_version);
2519        assert!(PackBitmapIndex::parse(&bad_version, ObjectFormat::Sha1, 1).is_err());
2520
2521        let mut bad_option = bitmap.clone();
2522        bad_option[7] = 0x20;
2523        refresh_trailing_checksum(ObjectFormat::Sha1, &mut bad_option);
2524        assert!(PackBitmapIndex::parse(&bad_option, ObjectFormat::Sha1, 1).is_err());
2525
2526        let mut bad_checksum = bitmap;
2527        let last = bad_checksum.len() - 1;
2528        bad_checksum[last] ^= 1;
2529        assert!(PackBitmapIndex::parse(&bad_checksum, ObjectFormat::Sha1, 1).is_err());
2530    }
2531
2532    #[test]
2533    fn rejects_bad_pack_bitmap_index_ewah_and_entries() {
2534        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
2535            .expect("test operation should succeed");
2536        let bitmap = pack_bitmap_index(
2537            ObjectFormat::Sha1,
2538            2,
2539            PackBitmapIndex::OPTION_FULL_DAG,
2540            &pack_checksum,
2541            &[(0, 0, 0, &[0b01]), (1, 1, 0, &[0b11])],
2542            None,
2543        );
2544
2545        let mut truncated = bitmap;
2546        truncated.truncate(truncated.len() - ObjectFormat::Sha1.raw_len() - 1);
2547        refresh_trailing_checksum(ObjectFormat::Sha1, &mut truncated);
2548        assert!(PackBitmapIndex::parse(&truncated, ObjectFormat::Sha1, 2).is_err());
2549
2550        let mut out_of_range_position = pack_bitmap_index(
2551            ObjectFormat::Sha1,
2552            2,
2553            PackBitmapIndex::OPTION_FULL_DAG,
2554            &pack_checksum,
2555            &[(2, 0, 0, &[0b01])],
2556            None,
2557        );
2558        assert!(PackBitmapIndex::parse(&out_of_range_position, ObjectFormat::Sha1, 2).is_err());
2559        refresh_trailing_checksum(ObjectFormat::Sha1, &mut out_of_range_position);
2560        assert!(PackBitmapIndex::parse(&out_of_range_position, ObjectFormat::Sha1, 2).is_err());
2561
2562        let invalid_xor = pack_bitmap_index(
2563            ObjectFormat::Sha1,
2564            2,
2565            PackBitmapIndex::OPTION_FULL_DAG,
2566            &pack_checksum,
2567            &[(0, 1, 0, &[0b01])],
2568            None,
2569        );
2570        assert!(PackBitmapIndex::parse(&invalid_xor, ObjectFormat::Sha1, 2).is_err());
2571    }
2572
2573    #[test]
2574    fn parses_single_entry_pack_index_sha256() {
2575        let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha256, "blob", b"hello sha256\n")
2576            .expect("test operation should succeed");
2577        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
2578            .expect("test operation should succeed");
2579        let index = single_entry_index(
2580            ObjectFormat::Sha256,
2581            oid,
2582            0x1234_5678,
2583            12,
2584            pack_checksum.clone(),
2585        );
2586        let parsed =
2587            PackIndex::parse(&index, ObjectFormat::Sha256).expect("test operation should succeed");
2588        assert_eq!(parsed.version, 2);
2589        assert_eq!(parsed.pack_checksum, pack_checksum);
2590        assert_eq!(parsed.entries.len(), 1);
2591        assert_eq!(
2592            parsed
2593                .find(&oid)
2594                .expect("test operation should succeed")
2595                .offset,
2596            12
2597        );
2598        assert_eq!(
2599            parsed
2600                .find(&oid)
2601                .expect("test operation should succeed")
2602                .crc32,
2603            0x1234_5678
2604        );
2605        assert_eq!(parsed.index_checksum.format(), ObjectFormat::Sha256);
2606        assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha256);
2607    }
2608
2609    #[test]
2610    fn write_packed_deltifies_similar_blobs_and_round_trips_sha1() {
2611        write_packed_deltifies_similar_blobs_and_round_trips(ObjectFormat::Sha1);
2612    }
2613
2614    #[test]
2615    fn write_packed_deltifies_similar_blobs_and_round_trips_sha256() {
2616        write_packed_deltifies_similar_blobs_and_round_trips(ObjectFormat::Sha256);
2617    }
2618
2619    #[test]
2620    fn write_packed_rejects_duplicate_objects() {
2621        let object = EncodedObject::new(ObjectType::Blob, b"same\n".to_vec());
2622        assert!(PackFile::write_packed(&[object.clone(), object], ObjectFormat::Sha1,).is_err());
2623    }
2624
2625    #[test]
2626    fn write_packed_with_known_ids_validates_ids_before_trusting_them() {
2627        let object = EncodedObject::new(ObjectType::Blob, b"same\n".to_vec());
2628        let sha1 = object
2629            .object_id(ObjectFormat::Sha1)
2630            .expect("test operation should succeed");
2631        let sha256 = object
2632            .object_id(ObjectFormat::Sha256)
2633            .expect("test operation should succeed");
2634        let duplicate = [
2635            PackInput {
2636                oid: &sha1,
2637                object: &object,
2638            },
2639            PackInput {
2640                oid: &sha1,
2641                object: &object,
2642            },
2643        ];
2644        assert!(PackFile::write_packed_with_known_ids(&duplicate, ObjectFormat::Sha1).is_err());
2645
2646        let wrong_format = [PackInput {
2647            oid: &sha256,
2648            object: &object,
2649        }];
2650        assert!(PackFile::write_packed_with_known_ids(&wrong_format, ObjectFormat::Sha1).is_err());
2651    }
2652
2653    #[test]
2654    fn write_packed_with_known_ids_to_writer_matches_in_memory_pack() {
2655        let objects = similar_blob_family(6);
2656        let object_ids = objects
2657            .iter()
2658            .map(|object| {
2659                object
2660                    .object_id(ObjectFormat::Sha1)
2661                    .expect("test operation should succeed")
2662            })
2663            .collect::<Vec<_>>();
2664        let inputs = objects
2665            .iter()
2666            .zip(&object_ids)
2667            .map(|(object, oid)| PackInput { oid, object })
2668            .collect::<Vec<_>>();
2669        let options = PackWriteOptions::new();
2670        let in_memory = PackFile::write_packed_with_known_ids_and_options(
2671            &inputs,
2672            ObjectFormat::Sha1,
2673            &options,
2674        )
2675        .expect("test operation should succeed");
2676        let mut written = Vec::new();
2677        let streamed = PackFile::write_packed_with_known_ids_to_writer(
2678            &inputs,
2679            ObjectFormat::Sha1,
2680            &options,
2681            &mut written,
2682        )
2683        .expect("test operation should succeed");
2684
2685        assert_eq!(written, in_memory.pack);
2686        assert_eq!(streamed.index, in_memory.index);
2687        assert_eq!(streamed.checksum, in_memory.checksum);
2688        assert_eq!(streamed.entries, in_memory.entries);
2689        assert_eq!(streamed.delta_count, in_memory.delta_count);
2690        assert_eq!(streamed.pack_size, in_memory.pack.len() as u64);
2691    }
2692
2693    #[test]
2694    fn write_packed_from_source_to_writer_deltifies_across_windows() {
2695        let format = ObjectFormat::Sha1;
2696        // Keep this candidate inside Git's first-delta budget (half the
2697        // target size minus one raw object id), while still straddling two
2698        // byte-budgeted compression windows.
2699        let shared = b"cross-window base payload with enough shared anchors\n".repeat(64);
2700        let mut base_body = shared.clone();
2701        base_body.extend_from_slice(b"base\n");
2702        let mut target_body = shared;
2703        target_body.extend_from_slice(b"target\n");
2704        let objects = vec![
2705            EncodedObject::new(ObjectType::Blob, base_body),
2706            EncodedObject::new(ObjectType::Blob, target_body),
2707        ];
2708
2709        let object_ids = objects
2710            .iter()
2711            .map(|object| {
2712                object
2713                    .object_id(format)
2714                    .expect("test operation should succeed")
2715            })
2716            .collect::<Vec<_>>();
2717        let base_oid = object_ids[0];
2718        let target_oid = object_ids[1];
2719        let object_map = object_ids
2720            .iter()
2721            .copied()
2722            .zip(objects.into_iter().map(Arc::new))
2723            .collect::<HashMap<_, _>>();
2724
2725        let options = PackWriteOptions::new().with_reorder(false).with_window(10);
2726        let one_object = object_map.get(&base_oid).expect("base").body.len() as u64 + 80;
2727        let limits =
2728            PackWriteLimits::new().with_compression_working_set(ByteBudget::new(one_object));
2729        let mut written = Vec::new();
2730        let summary = PackFile::write_packed_from_source_to_writer(
2731            object_ids.iter().copied(),
2732            2,
2733            format,
2734            &options,
2735            limits,
2736            |oid| {
2737                object_map
2738                    .get(oid)
2739                    .cloned()
2740                    .ok_or_else(|| GitError::not_found(format!("missing test object {oid}")))
2741            },
2742            &mut written,
2743        )
2744        .expect("test operation should succeed");
2745
2746        assert!(
2747            summary.delta_count > 0,
2748            "expected source-backed streaming writer to find deltas"
2749        );
2750        let stats =
2751            PackFile::verify_pack_stats(&written, format).expect("test operation should succeed");
2752        let target = stats
2753            .objects
2754            .iter()
2755            .find(|entry| entry.oid == target_oid)
2756            .expect("target object should be present");
2757        assert_eq!(target.base_oid, Some(base_oid));
2758    }
2759
2760    fn write_packed_deltifies_similar_blobs_and_round_trips(format: ObjectFormat) {
2761        let objects = similar_blob_family(8);
2762        let packed =
2763            PackFile::write_packed(&objects, format).expect("test operation should succeed");
2764        let undeltified =
2765            PackFile::write_undeltified(&objects, format).expect("test operation should succeed");
2766
2767        // The whole point of delta selection: the packed output is smaller than
2768        // storing every object undeltified.
2769        assert!(
2770            packed.pack.len() < undeltified.pack.len(),
2771            "expected delta pack ({}) smaller than undeltified pack ({})",
2772            packed.pack.len(),
2773            undeltified.pack.len()
2774        );
2775
2776        // At least one object must actually be stored as a delta.
2777        let kinds = pack_entry_kinds(&packed.pack, format);
2778        let delta_count = kinds
2779            .iter()
2780            .filter(|kind| matches!(kind, PackObjectKind::OfsDelta | PackObjectKind::RefDelta))
2781            .count();
2782        assert!(
2783            delta_count >= 1,
2784            "expected at least one delta entry, found kinds {kinds:?}"
2785        );
2786
2787        // Round-trip: every original object reconstructs byte-for-byte.
2788        let parsed = PackFile::parse(&packed.pack, format).expect("test operation should succeed");
2789        assert_eq!(parsed.entries.len(), objects.len());
2790        for object in &objects {
2791            let oid = object
2792                .object_id(format)
2793                .expect("test operation should succeed");
2794            let found = parsed
2795                .entries
2796                .iter()
2797                .find(|entry| entry.entry.oid == oid)
2798                .unwrap_or_else(|| panic!("object {oid} missing from parsed pack"));
2799            assert_eq!(&found.object, object, "object {oid} did not round-trip");
2800        }
2801
2802        // The index must agree with the pack and locate every object.
2803        let index = PackIndex::parse(&packed.index, format).expect("test operation should succeed");
2804        assert_eq!(index.pack_checksum, packed.checksum);
2805        for object in &objects {
2806            let oid = object
2807                .object_id(format)
2808                .expect("test operation should succeed");
2809            assert!(index.find(&oid).is_some(), "index missing {oid}");
2810        }
2811    }
2812
2813    #[test]
2814    fn write_packed_emits_ofs_delta_by_default() {
2815        let objects = similar_blob_family(6);
2816        let packed = PackFile::write_packed(&objects, ObjectFormat::Sha1)
2817            .expect("test operation should succeed");
2818        let kinds = pack_entry_kinds(&packed.pack, ObjectFormat::Sha1);
2819        assert!(
2820            kinds.contains(&PackObjectKind::OfsDelta),
2821            "expected an ofs-delta entry by default, found {kinds:?}"
2822        );
2823        assert!(
2824            !kinds.contains(&PackObjectKind::RefDelta),
2825            "default self-contained pack must not use ref-delta, found {kinds:?}"
2826        );
2827        // Round-trips.
2828        assert!(PackFile::parse(&packed.pack, ObjectFormat::Sha1).is_ok());
2829    }
2830
2831    #[test]
2832    fn write_packed_can_emit_ref_delta() {
2833        let objects = similar_blob_family(6);
2834        let options = PackWriteOptions::new().with_prefer_ofs_delta(false);
2835        let packed = PackFile::write_packed_with_options(&objects, ObjectFormat::Sha1, &options)
2836            .expect("test operation should succeed");
2837        let kinds = pack_entry_kinds(&packed.pack, ObjectFormat::Sha1);
2838        assert!(
2839            kinds.contains(&PackObjectKind::RefDelta),
2840            "expected a ref-delta entry, found {kinds:?}"
2841        );
2842        assert!(
2843            !kinds.contains(&PackObjectKind::OfsDelta),
2844            "ref-delta mode must not emit ofs-delta, found {kinds:?}"
2845        );
2846
2847        // Ref-delta packs are still self-contained here, so they round-trip
2848        // without any external base lookup.
2849        let parsed = PackFile::parse(&packed.pack, ObjectFormat::Sha1)
2850            .expect("test operation should succeed");
2851        assert_eq!(parsed.entries.len(), objects.len());
2852    }
2853
2854    #[test]
2855    fn write_packed_bounds_delta_chain_depth() {
2856        // A long chain of progressively-modified blobs. With a large window
2857        // every object could otherwise delta against its immediate predecessor,
2858        // forming a chain as long as the input.
2859        let objects = incremental_blob_chain(20);
2860        let format = ObjectFormat::Sha1;
2861
2862        for max_depth in [1usize, 2, 5] {
2863            let options = PackWriteOptions::new()
2864                .with_window(20)
2865                .with_depth(max_depth);
2866            let packed = PackFile::write_packed_with_options(&objects, format, &options)
2867                .expect("test operation should succeed");
2868
2869            let depths = pack_entry_depths(&packed.pack, format);
2870            let observed = depths.iter().copied().max().unwrap_or(0);
2871            assert!(
2872                observed <= max_depth,
2873                "max chain depth {observed} exceeded bound {max_depth}"
2874            );
2875
2876            // Still correct: round-trips byte-for-byte.
2877            let parsed =
2878                PackFile::parse(&packed.pack, format).expect("test operation should succeed");
2879            for object in &objects {
2880                let oid = object
2881                    .object_id(format)
2882                    .expect("test operation should succeed");
2883                let found = parsed
2884                    .entries
2885                    .iter()
2886                    .find(|entry| entry.entry.oid == oid)
2887                    .expect("test operation should succeed");
2888                assert_eq!(&found.object, object);
2889            }
2890        }
2891    }
2892
2893    #[test]
2894    fn write_packed_depth_zero_stores_everything_undeltified() {
2895        let objects = similar_blob_family(5);
2896        let options = PackWriteOptions::new().with_depth(0);
2897        let packed = PackFile::write_packed_with_options(&objects, ObjectFormat::Sha1, &options)
2898            .expect("test operation should succeed");
2899        let kinds = pack_entry_kinds(&packed.pack, ObjectFormat::Sha1);
2900        assert!(
2901            kinds
2902                .iter()
2903                .all(|kind| !matches!(kind, PackObjectKind::OfsDelta | PackObjectKind::RefDelta)),
2904            "depth 0 must disable deltas, found {kinds:?}"
2905        );
2906    }
2907
2908    #[test]
2909    fn write_thin_uses_external_base_and_round_trips_sha1() {
2910        write_thin_uses_external_base_and_round_trips(ObjectFormat::Sha1);
2911    }
2912
2913    #[test]
2914    fn write_thin_uses_external_base_and_round_trips_sha256() {
2915        write_thin_uses_external_base_and_round_trips(ObjectFormat::Sha256);
2916    }
2917
2918    fn write_thin_uses_external_base_and_round_trips(format: ObjectFormat) {
2919        // The base object stays OUT of the pack; only `target` is written, as a
2920        // ref-delta against the external base's object id.
2921        let base = blob_with_marker("EXTERNAL-BASE");
2922        let target = blob_with_marker("EXTERNAL-TARGET");
2923        let base_oid = base
2924            .object_id(format)
2925            .expect("test operation should succeed");
2926
2927        let mut external = HashMap::new();
2928        external.insert(base_oid, base.clone());
2929        let packed = PackFile::write_thin(std::slice::from_ref(&target), format, external)
2930            .expect("test operation should succeed");
2931
2932        // Exactly one entry, encoded as a ref-delta to the external base.
2933        let kinds = pack_entry_kinds(&packed.pack, format);
2934        assert_eq!(kinds, vec![PackObjectKind::RefDelta]);
2935
2936        // The external base reference must be the base oid.
2937        let mut offset = 12usize;
2938        let header =
2939            parse_entry_header(&packed.pack, &mut offset).expect("test operation should succeed");
2940        assert_eq!(header.kind, PackObjectKind::RefDelta);
2941        let referenced =
2942            ObjectId::from_raw(format, &packed.pack[offset..offset + format.raw_len()])
2943                .expect("test operation should succeed");
2944        assert_eq!(referenced, base_oid);
2945
2946        // A plain (non-thin) parse fails: the base is not present.
2947        assert!(PackFile::parse(&packed.pack, format).is_err());
2948
2949        // A thin parse that supplies the external base reconstructs the target.
2950        let parsed = PackFile::parse_thin(&packed.pack, format, |oid| {
2951            if oid == &base_oid {
2952                Ok(Some(base.clone()))
2953            } else {
2954                Ok(None)
2955            }
2956        })
2957        .expect("test operation should succeed");
2958        assert_eq!(parsed.entries.len(), 1);
2959        assert_eq!(parsed.entries[0].object, target);
2960    }
2961
2962    #[test]
2963    fn write_packed_preserves_distinct_objects_with_no_similarity() {
2964        // Unrelated objects: nothing should delta, but the pack must still be
2965        // valid and complete.
2966        let objects = vec![
2967            EncodedObject::new(ObjectType::Blob, b"alpha distinct\n".to_vec()),
2968            EncodedObject::new(ObjectType::Tree, vec![0u8; 0]),
2969            EncodedObject::new(ObjectType::Commit, b"tree 0000\n".to_vec()),
2970        ];
2971        let format = ObjectFormat::Sha1;
2972        let packed =
2973            PackFile::write_packed(&objects, format).expect("test operation should succeed");
2974        let parsed = PackFile::parse(&packed.pack, format).expect("test operation should succeed");
2975        assert_eq!(parsed.entries.len(), objects.len());
2976        for object in &objects {
2977            let oid = object
2978                .object_id(format)
2979                .expect("test operation should succeed");
2980            assert!(parsed.entries.iter().any(|entry| entry.entry.oid == oid));
2981        }
2982    }
2983
2984    /// Build a family of blobs that all share a large common region but differ
2985    /// in a marker placed in the *middle*, so a good delta finds copy regions on
2986    /// both sides of the change.
2987    fn similar_blob_family(count: usize) -> Vec<EncodedObject> {
2988        let mut common_head = Vec::new();
2989        for _ in 0..200 {
2990            common_head.extend_from_slice(b"shared header line for delta testing\n");
2991        }
2992        let mut common_tail = Vec::new();
2993        for _ in 0..200 {
2994            common_tail.extend_from_slice(b"shared trailer line for delta testing\n");
2995        }
2996        (0..count)
2997            .map(|idx| {
2998                let mut body = common_head.clone();
2999                body.extend_from_slice(format!("UNIQUE MIDDLE MARKER NUMBER {idx}\n").as_bytes());
3000                body.extend_from_slice(&common_tail);
3001                EncodedObject::new(ObjectType::Blob, body)
3002            })
3003            .collect()
3004    }
3005
3006    /// Build a chain where each blob is the previous one plus an appended line,
3007    /// so each is highly similar to its predecessor.
3008    fn incremental_blob_chain(count: usize) -> Vec<EncodedObject> {
3009        let mut body = Vec::new();
3010        for _ in 0..100 {
3011            body.extend_from_slice(b"baseline content shared across the whole chain\n");
3012        }
3013        let mut objects = Vec::with_capacity(count);
3014        for idx in 0..count {
3015            body.extend_from_slice(format!("appended unique line {idx}\n").as_bytes());
3016            objects.push(EncodedObject::new(ObjectType::Blob, body.clone()));
3017        }
3018        objects
3019    }
3020
3021    fn blob_with_marker(marker: &str) -> EncodedObject {
3022        let mut body = Vec::new();
3023        for _ in 0..150 {
3024            body.extend_from_slice(b"common body shared between base and target\n");
3025        }
3026        body.extend_from_slice(marker.as_bytes());
3027        body.push(b'\n');
3028        for _ in 0..150 {
3029            body.extend_from_slice(b"more common body shared between objects\n");
3030        }
3031        EncodedObject::new(ObjectType::Blob, body)
3032    }
3033
3034    /// Classify every entry in a pack (in pack order) by its on-disk kind.
3035    fn pack_entry_kinds(pack: &[u8], format: ObjectFormat) -> Vec<PackObjectKind> {
3036        pack_entry_descriptors(pack, format)
3037            .into_iter()
3038            .map(|descriptor| descriptor.kind)
3039            .collect()
3040    }
3041
3042    /// Compute each entry's delta chain depth (0 = undeltified base), in pack
3043    /// order. Entries always appear after their in-pack bases, so a single
3044    /// forward pass suffices.
3045    fn pack_entry_depths(pack: &[u8], format: ObjectFormat) -> Vec<usize> {
3046        let descriptors = pack_entry_descriptors(pack, format);
3047        let mut depth_by_offset: HashMap<u64, usize> = HashMap::new();
3048        let mut depths = Vec::with_capacity(descriptors.len());
3049        for descriptor in &descriptors {
3050            let depth = match &descriptor.base {
3051                EntryBase::None => 0,
3052                EntryBase::Offset(base_offset) => {
3053                    depth_by_offset.get(base_offset).copied().unwrap_or(0) + 1
3054                }
3055                // Ref-delta to an in-pack base: look it up by offset via oid is
3056                // unnecessary for these tests (which only use ofs-delta for the
3057                // chains), so treat as depth 1 if unknown.
3058                EntryBase::Ref => 1,
3059            };
3060            depth_by_offset.insert(descriptor.offset, depth);
3061            depths.push(depth);
3062        }
3063        depths
3064    }
3065
3066    struct EntryDescriptor {
3067        offset: u64,
3068        kind: PackObjectKind,
3069        base: EntryBase,
3070    }
3071
3072    enum EntryBase {
3073        None,
3074        Offset(u64),
3075        Ref,
3076    }
3077
3078    fn pack_entry_descriptors(pack: &[u8], format: ObjectFormat) -> Vec<EntryDescriptor> {
3079        let trailer_offset = pack.len() - format.raw_len();
3080        let count = u32_be(&pack[8..12]) as usize;
3081        let mut offset = 12usize;
3082        let mut descriptors = Vec::with_capacity(count);
3083        for _ in 0..count {
3084            let entry_offset = offset as u64;
3085            let header =
3086                parse_entry_header(pack, &mut offset).expect("test operation should succeed");
3087            let base = match header.kind {
3088                PackObjectKind::OfsDelta => {
3089                    let base_offset = parse_ofs_delta_base_offset(pack, &mut offset, entry_offset)
3090                        .expect("test operation should succeed");
3091                    EntryBase::Offset(base_offset)
3092                }
3093                PackObjectKind::RefDelta => {
3094                    offset += format.raw_len();
3095                    EntryBase::Ref
3096                }
3097                _ => EntryBase::None,
3098            };
3099            let mut decoder = ZlibDecoder::new(&pack[offset..trailer_offset]);
3100            let mut body = Vec::new();
3101            decoder
3102                .read_to_end(&mut body)
3103                .expect("test operation should succeed");
3104            offset += decoder.total_in() as usize;
3105            descriptors.push(EntryDescriptor {
3106                offset: entry_offset,
3107                kind: header.kind,
3108                base,
3109            });
3110        }
3111        descriptors
3112    }
3113
3114    fn similar_blob_objects() -> (EncodedObject, EncodedObject) {
3115        let mut base = Vec::new();
3116        for _ in 0..300 {
3117            base.extend_from_slice(b"common payload\n");
3118        }
3119        base.extend_from_slice(b"base\n");
3120        let mut changed = Vec::new();
3121        for _ in 0..300 {
3122            changed.extend_from_slice(b"common payload\n");
3123        }
3124        changed.extend_from_slice(b"changed\n");
3125        (
3126            EncodedObject::new(ObjectType::Blob, base),
3127            EncodedObject::new(ObjectType::Blob, changed),
3128        )
3129    }
3130
3131    fn single_object_pack(format: ObjectFormat, object_type: ObjectType, body: &[u8]) -> Vec<u8> {
3132        let mut pack = Vec::new();
3133        pack.extend_from_slice(b"PACK");
3134        pack.extend_from_slice(&2u32.to_be_bytes());
3135        pack.extend_from_slice(&1u32.to_be_bytes());
3136        write_entry_header(&mut pack, object_type, body.len() as u64);
3137        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
3138        encoder
3139            .write_all(body)
3140            .expect("test operation should succeed");
3141        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
3142        let checksum =
3143            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
3144        pack.extend_from_slice(checksum.as_bytes());
3145        pack
3146    }
3147
3148    #[derive(Clone, Copy, Debug)]
3149    enum DeltaKind {
3150        Offset,
3151        Ref,
3152    }
3153
3154    fn two_object_delta_pack(
3155        format: ObjectFormat,
3156        base: &[u8],
3157        result: &[u8],
3158        delta_kind: DeltaKind,
3159    ) -> Vec<u8> {
3160        let mut pack = Vec::new();
3161        pack.extend_from_slice(b"PACK");
3162        pack.extend_from_slice(&2u32.to_be_bytes());
3163        pack.extend_from_slice(&2u32.to_be_bytes());
3164
3165        let base_offset = pack.len();
3166        write_entry_header(&mut pack, ObjectType::Blob, base.len() as u64);
3167        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
3168        encoder
3169            .write_all(base)
3170            .expect("test operation should succeed");
3171        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
3172
3173        let delta = append_suffix_delta(base, result);
3174        let delta_offset = pack.len();
3175        write_pack_entry_header_kind(
3176            &mut pack,
3177            match delta_kind {
3178                DeltaKind::Offset => 6,
3179                DeltaKind::Ref => 7,
3180            },
3181            delta.len() as u64,
3182        );
3183        match delta_kind {
3184            DeltaKind::Offset => write_ofs_delta_offset(&mut pack, delta_offset - base_offset),
3185            DeltaKind::Ref => {
3186                let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
3187                    .expect("test operation should succeed");
3188                pack.extend_from_slice(base_oid.as_bytes());
3189            }
3190        }
3191        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
3192        encoder
3193            .write_all(&delta)
3194            .expect("test operation should succeed");
3195        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
3196
3197        let checksum =
3198            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
3199        pack.extend_from_slice(checksum.as_bytes());
3200        pack
3201    }
3202
3203    fn thin_ref_delta_pack(format: ObjectFormat, base: &[u8], result: &[u8]) -> Vec<u8> {
3204        let mut pack = Vec::new();
3205        pack.extend_from_slice(b"PACK");
3206        pack.extend_from_slice(&2u32.to_be_bytes());
3207        pack.extend_from_slice(&1u32.to_be_bytes());
3208
3209        let delta = append_suffix_delta(base, result);
3210        write_pack_entry_header_kind(&mut pack, 7, delta.len() as u64);
3211        let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
3212            .expect("test operation should succeed");
3213        pack.extend_from_slice(base_oid.as_bytes());
3214        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
3215        encoder
3216            .write_all(&delta)
3217            .expect("test operation should succeed");
3218        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
3219
3220        let checksum =
3221            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
3222        pack.extend_from_slice(checksum.as_bytes());
3223        pack
3224    }
3225
3226    fn thin_ref_delta_pack_many(format: ObjectFormat, base: &[u8], results: &[&[u8]]) -> Vec<u8> {
3227        let mut pack = Vec::new();
3228        pack.extend_from_slice(b"PACK");
3229        pack.extend_from_slice(&2u32.to_be_bytes());
3230        pack.extend_from_slice(&(results.len() as u32).to_be_bytes());
3231        let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
3232            .expect("test operation should succeed");
3233        for result in results {
3234            let delta = append_suffix_delta(base, result);
3235            write_pack_entry_header_kind(&mut pack, 7, delta.len() as u64);
3236            pack.extend_from_slice(base_oid.as_bytes());
3237            let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
3238            encoder
3239                .write_all(&delta)
3240                .expect("test operation should succeed");
3241            pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
3242        }
3243        let checksum =
3244            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
3245        pack.extend_from_slice(checksum.as_bytes());
3246        pack
3247    }
3248
3249    fn unique_temp_dir(name: &str) -> PathBuf {
3250        let nanos = SystemTime::now()
3251            .duration_since(UNIX_EPOCH)
3252            .expect("test operation should succeed")
3253            .as_nanos();
3254        std::env::temp_dir().join(format!("sley-{name}-{}-{nanos}", std::process::id()))
3255    }
3256
3257    fn run_git_success(cwd: &Path, args: &[&str]) {
3258        let output = Command::new("git")
3259            .current_dir(cwd)
3260            .args(args)
3261            .output()
3262            .unwrap_or_else(|err| panic!("failed to run git {args:?}: {err}"));
3263        assert!(
3264            output.status.success(),
3265            "git {args:?} failed with status {:?}\nstdout:\n{}\nstderr:\n{}",
3266            output.status.code(),
3267            String::from_utf8_lossy(&output.stdout),
3268            String::from_utf8_lossy(&output.stderr)
3269        );
3270    }
3271
3272    fn single_path_with_extension(dir: &Path, extension: &str) -> PathBuf {
3273        let mut paths = fs::read_dir(dir)
3274            .expect("test operation should succeed")
3275            .map(|entry| entry.expect("test operation should succeed").path())
3276            .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some(extension))
3277            .collect::<Vec<_>>();
3278        assert_eq!(paths.len(), 1, "expected one .{extension} file");
3279        paths.remove(0)
3280    }
3281
3282    fn largest_path_with_extension(dir: &Path, extension: &str) -> PathBuf {
3283        fs::read_dir(dir)
3284            .expect("read fixture directory")
3285            .map(|entry| entry.expect("read fixture entry").path())
3286            .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some(extension))
3287            .max_by_key(|path| fs::metadata(path).expect("read fixture metadata").len())
3288            .expect("at least one fixture path")
3289    }
3290
3291    fn pack_bitmap_index(
3292        format: ObjectFormat,
3293        object_count: u32,
3294        options: u16,
3295        pack_checksum: &ObjectId,
3296        entries: &[(u32, u8, u8, &[u64])],
3297        name_hash_cache: Option<&[u32]>,
3298    ) -> Vec<u8> {
3299        let mut out = Vec::new();
3300        out.extend_from_slice(b"BITM");
3301        out.extend_from_slice(&1u16.to_be_bytes());
3302        out.extend_from_slice(&options.to_be_bytes());
3303        out.extend_from_slice(&(entries.len() as u32).to_be_bytes());
3304        out.extend_from_slice(pack_checksum.as_bytes());
3305        write_test_ewah(&mut out, object_count, &[0b001]);
3306        write_test_ewah(&mut out, object_count, &[0b010]);
3307        write_test_ewah(&mut out, object_count, &[0b100]);
3308        write_test_ewah(&mut out, object_count, &[0]);
3309        for (position, xor_offset, flags, words) in entries {
3310            out.extend_from_slice(&position.to_be_bytes());
3311            out.push(*xor_offset);
3312            out.push(*flags);
3313            write_test_ewah(&mut out, object_count, words);
3314        }
3315        if let Some(cache) = name_hash_cache {
3316            for value in cache {
3317                out.extend_from_slice(&value.to_be_bytes());
3318            }
3319        }
3320        let checksum =
3321            sley_core::digest_bytes(format, &out).expect("test operation should succeed");
3322        out.extend_from_slice(checksum.as_bytes());
3323        out
3324    }
3325
3326    fn write_test_ewah(out: &mut Vec<u8>, bit_size: u32, literals: &[u64]) {
3327        out.extend_from_slice(&bit_size.to_be_bytes());
3328        let words = ewah_literal_words(literals);
3329        out.extend_from_slice(&(words.len() as u32).to_be_bytes());
3330        for word in words {
3331            out.extend_from_slice(&word.to_be_bytes());
3332        }
3333        out.extend_from_slice(&0u32.to_be_bytes());
3334    }
3335
3336    fn ewah_literal_words(literals: &[u64]) -> Vec<u64> {
3337        let rlw = (literals.len() as u64) << 33;
3338        let mut words = vec![rlw];
3339        words.extend_from_slice(literals);
3340        words
3341    }
3342
3343    fn refresh_trailing_checksum(format: ObjectFormat, bytes: &mut [u8]) {
3344        let checksum_offset = bytes.len() - format.raw_len();
3345        let checksum = sley_core::digest_bytes(format, &bytes[..checksum_offset])
3346            .expect("test operation should succeed");
3347        bytes[checksum_offset..].copy_from_slice(checksum.as_bytes());
3348    }
3349
3350    fn append_suffix_delta(base: &[u8], result: &[u8]) -> Vec<u8> {
3351        assert!(result.starts_with(base));
3352        let suffix = &result[base.len()..];
3353        assert!(base.len() < 0x10000);
3354        assert!(suffix.len() < 0x80);
3355        let mut delta = Vec::new();
3356        write_delta_varint(&mut delta, base.len() as u64);
3357        write_delta_varint(&mut delta, result.len() as u64);
3358        delta.push(0x90);
3359        delta.push(base.len() as u8);
3360        delta.push(suffix.len() as u8);
3361        delta.extend_from_slice(suffix);
3362        delta
3363    }
3364
3365    fn write_delta_varint(out: &mut Vec<u8>, mut value: u64) {
3366        loop {
3367            let mut byte = (value as u8) & 0x7f;
3368            value >>= 7;
3369            if value != 0 {
3370                byte |= 0x80;
3371            }
3372            out.push(byte);
3373            if value == 0 {
3374                break;
3375            }
3376        }
3377    }
3378
3379    fn write_pack_entry_header_kind(out: &mut Vec<u8>, type_code: u8, mut size: u64) {
3380        let mut byte = (type_code << 4) | ((size as u8) & 0x0f);
3381        size >>= 4;
3382        if size != 0 {
3383            byte |= 0x80;
3384        }
3385        out.push(byte);
3386        while size != 0 {
3387            let mut byte = (size as u8) & 0x7f;
3388            size >>= 7;
3389            if size != 0 {
3390                byte |= 0x80;
3391            }
3392            out.push(byte);
3393        }
3394    }
3395
3396    fn write_ofs_delta_offset(out: &mut Vec<u8>, relative: usize) {
3397        assert!(relative < 0x80);
3398        out.push(relative as u8);
3399    }
3400
3401    fn single_entry_index(
3402        format: ObjectFormat,
3403        oid: ObjectId,
3404        crc32: u32,
3405        offset: u32,
3406        pack_checksum: ObjectId,
3407    ) -> Vec<u8> {
3408        let mut index = Vec::new();
3409        index.extend_from_slice(&[0xff, b't', b'O', b'c']);
3410        index.extend_from_slice(&2u32.to_be_bytes());
3411        for idx in 0..256 {
3412            let count = if idx >= usize::from(oid.as_bytes()[0]) {
3413                1u32
3414            } else {
3415                0u32
3416            };
3417            index.extend_from_slice(&count.to_be_bytes());
3418        }
3419        index.extend_from_slice(oid.as_bytes());
3420        index.extend_from_slice(&crc32.to_be_bytes());
3421        index.extend_from_slice(&offset.to_be_bytes());
3422        index.extend_from_slice(pack_checksum.as_bytes());
3423        let checksum =
3424            sley_core::digest_bytes(format, &index).expect("test operation should succeed");
3425        index.extend_from_slice(checksum.as_bytes());
3426        index
3427    }
3428
3429    fn single_entry_index_v1(
3430        format: ObjectFormat,
3431        oid: ObjectId,
3432        offset: u32,
3433        pack_checksum: ObjectId,
3434    ) -> Vec<u8> {
3435        let mut index = Vec::new();
3436        for idx in 0..256 {
3437            let count = if idx >= usize::from(oid.as_bytes()[0]) {
3438                1u32
3439            } else {
3440                0u32
3441            };
3442            index.extend_from_slice(&count.to_be_bytes());
3443        }
3444        index.extend_from_slice(&offset.to_be_bytes());
3445        index.extend_from_slice(oid.as_bytes());
3446        index.extend_from_slice(pack_checksum.as_bytes());
3447        let checksum =
3448            sley_core::digest_bytes(format, &index).expect("test operation should succeed");
3449        index.extend_from_slice(checksum.as_bytes());
3450        index
3451    }
3452
3453    fn pack_reverse_index(
3454        format: ObjectFormat,
3455        positions: &[u32],
3456        pack_checksum: ObjectId,
3457    ) -> Vec<u8> {
3458        let mut reverse_index = Vec::new();
3459        reverse_index.extend_from_slice(b"RIDX");
3460        reverse_index.extend_from_slice(&1u32.to_be_bytes());
3461        reverse_index.extend_from_slice(&hash_function_id(format).to_be_bytes());
3462        for position in positions {
3463            reverse_index.extend_from_slice(&position.to_be_bytes());
3464        }
3465        reverse_index.extend_from_slice(pack_checksum.as_bytes());
3466        let checksum =
3467            sley_core::digest_bytes(format, &reverse_index).expect("test operation should succeed");
3468        reverse_index.extend_from_slice(checksum.as_bytes());
3469        reverse_index
3470    }
3471
3472    fn pack_mtimes(format: ObjectFormat, mtimes: &[u32], pack_checksum: ObjectId) -> Vec<u8> {
3473        let mut out = Vec::new();
3474        out.extend_from_slice(b"MTME");
3475        out.extend_from_slice(&1u32.to_be_bytes());
3476        out.extend_from_slice(&hash_function_id(format).to_be_bytes());
3477        for mtime in mtimes {
3478            out.extend_from_slice(&mtime.to_be_bytes());
3479        }
3480        out.extend_from_slice(pack_checksum.as_bytes());
3481        let checksum =
3482            sley_core::digest_bytes(format, &out).expect("test operation should succeed");
3483        out.extend_from_slice(checksum.as_bytes());
3484        out
3485    }
3486
3487    fn midx_chunks_with_pack_names(
3488        _format: ObjectFormat,
3489        pack_names: Vec<u8>,
3490        entries: &[(ObjectId, u32, u64)],
3491    ) -> Vec<([u8; 4], Vec<u8>)> {
3492        let mut entries = entries.to_vec();
3493        entries.sort_by(|left, right| left.0.as_bytes().cmp(right.0.as_bytes()));
3494        let object_ids: Vec<ObjectId> = entries.iter().map(|entry| entry.0).collect();
3495        let mut large_offsets = Vec::new();
3496        let mut chunks = vec![
3497            (*b"PNAM", pack_names),
3498            (*b"OIDF", midx_oid_fanout(&object_ids)),
3499            (*b"OIDL", midx_oid_lookup(&object_ids)),
3500            (
3501                *b"OOFF",
3502                midx_ooff_entries(
3503                    &entries
3504                        .iter()
3505                        .map(|(_oid, pack_int_id, offset)| (*pack_int_id, *offset))
3506                        .collect::<Vec<_>>(),
3507                    &mut large_offsets,
3508                ),
3509            ),
3510        ];
3511        if !large_offsets.is_empty() {
3512            chunks.push((*b"LOFF", large_offsets));
3513        }
3514        chunks
3515    }
3516
3517    fn midx_oid_fanout(object_ids: &[ObjectId]) -> Vec<u8> {
3518        let mut counts = [0u32; 256];
3519        for oid in object_ids {
3520            counts[oid.as_bytes()[0] as usize] += 1;
3521        }
3522        let mut running = 0u32;
3523        let mut out = Vec::new();
3524        for count in counts {
3525            running += count;
3526            out.extend_from_slice(&running.to_be_bytes());
3527        }
3528        out
3529    }
3530
3531    fn midx_oid_lookup(object_ids: &[ObjectId]) -> Vec<u8> {
3532        let mut out = Vec::new();
3533        for oid in object_ids {
3534            out.extend_from_slice(oid.as_bytes());
3535        }
3536        out
3537    }
3538
3539    fn midx_ooff_entries(entries: &[(u32, u64)], large_offsets: &mut Vec<u8>) -> Vec<u8> {
3540        let mut out = Vec::new();
3541        for (pack_int_id, offset) in entries {
3542            out.extend_from_slice(&pack_int_id.to_be_bytes());
3543            if *offset < 0x8000_0000 {
3544                out.extend_from_slice(&(*offset as u32).to_be_bytes());
3545            } else {
3546                let large_idx = (large_offsets.len() / 8) as u32;
3547                out.extend_from_slice(&(0x8000_0000 | large_idx).to_be_bytes());
3548                large_offsets.extend_from_slice(&offset.to_be_bytes());
3549            }
3550        }
3551        out
3552    }
3553
3554    fn midx_u32_table(values: &[u32]) -> Vec<u8> {
3555        let mut out = Vec::new();
3556        for value in values {
3557            out.extend_from_slice(&value.to_be_bytes());
3558        }
3559        out
3560    }
3561
3562    fn midx_bitmap_packs(entries: &[(u32, u32)]) -> Vec<u8> {
3563        let mut out = Vec::new();
3564        for (bitmap_pos, bitmap_nr) in entries {
3565            out.extend_from_slice(&bitmap_pos.to_be_bytes());
3566            out.extend_from_slice(&bitmap_nr.to_be_bytes());
3567        }
3568        out
3569    }
3570
3571    fn multi_pack_index(
3572        format: ObjectFormat,
3573        version: u8,
3574        pack_count: u32,
3575        chunks: &[([u8; 4], Vec<u8>)],
3576    ) -> Vec<u8> {
3577        let lookup_len = (chunks.len() + 1) * 12;
3578        let mut out = Vec::new();
3579        out.extend_from_slice(b"MIDX");
3580        out.push(version);
3581        out.push(hash_function_id(format) as u8);
3582        out.push(chunks.len() as u8);
3583        out.push(0);
3584        out.extend_from_slice(&pack_count.to_be_bytes());
3585        let mut chunk_offset = (12 + lookup_len) as u64;
3586        for (id, data) in chunks {
3587            out.extend_from_slice(id);
3588            out.extend_from_slice(&chunk_offset.to_be_bytes());
3589            chunk_offset += data.len() as u64;
3590        }
3591        out.extend_from_slice(&[0, 0, 0, 0]);
3592        out.extend_from_slice(&chunk_offset.to_be_bytes());
3593        for (_id, data) in chunks {
3594            out.extend_from_slice(data);
3595        }
3596        let checksum =
3597            sley_core::digest_bytes(format, &out).expect("test operation should succeed");
3598        out.extend_from_slice(checksum.as_bytes());
3599        out
3600    }
3601
3602    // ---- EWAH encoder / bitmap writer tests ------------------------------
3603
3604    fn pack_checksum_sha1() -> ObjectId {
3605        sley_core::digest_bytes(ObjectFormat::Sha1, b"pack").expect("test operation should succeed")
3606    }
3607
3608    fn parse_ewah_bytes(bytes: &[u8]) -> EwahBitmap {
3609        // Wrap the EWAH body with the surrounding offset bookkeeping the parser
3610        // expects: a checksum offset that lies just past the serialised bitmap.
3611        let mut offset = 0usize;
3612        let checksum_offset = bytes.len();
3613        parse_bitmap_ewah(bytes, &mut offset, checksum_offset, 0)
3614            .expect("test operation should succeed")
3615    }
3616
3617    #[test]
3618    fn ewah_encodes_single_literal_word_matching_helper() {
3619        // A bitmap whose only word is a literal must serialise as one RLW with
3620        // literal_len == 1 followed by the literal, identical to the test
3621        // helper used by the existing parser tests.
3622        let ewah = EwahBitmap::from_words(64, &[0b101]).expect("test operation should succeed");
3623        assert_eq!(ewah.words, ewah_literal_words(&[0b101]));
3624        assert_eq!(ewah.rlw_position, 0);
3625        assert_eq!(ewah.bit_size, 64);
3626    }
3627
3628    #[test]
3629    fn ewah_byte_layout_is_big_endian() {
3630        let ewah = EwahBitmap::from_words(64, &[0x0102_0304_0506_0708])
3631            .expect("test operation should succeed");
3632        let bytes = ewah.to_bytes();
3633        let mut expected = Vec::new();
3634        expected.extend_from_slice(&64u32.to_be_bytes()); // bit_size
3635        expected.extend_from_slice(&2u32.to_be_bytes()); // word count: rlw + literal
3636        expected.extend_from_slice(&(1u64 << 33).to_be_bytes()); // rlw: literal_len = 1
3637        expected.extend_from_slice(&0x0102_0304_0506_0708u64.to_be_bytes());
3638        expected.extend_from_slice(&0u32.to_be_bytes()); // rlw_position
3639        assert_eq!(bytes, expected);
3640    }
3641
3642    #[test]
3643    fn ewah_empty_bitmap_serialises_like_git() {
3644        let ewah = EwahBitmap::empty();
3645        let bytes = ewah.to_bytes();
3646        // bit_size = 0, word_count = 0, rlw_position = 0.
3647        assert_eq!(bytes, vec![0u8; 12]);
3648        // It must still parse and decode to nothing.
3649        let parsed = parse_ewah_bytes(&bytes);
3650        assert_eq!(parsed, ewah);
3651        assert!(
3652            parsed
3653                .to_positions()
3654                .expect("test operation should succeed")
3655                .is_empty()
3656        );
3657    }
3658
3659    #[test]
3660    fn ewah_compresses_clean_zero_run() {
3661        // Three all-zero words followed by a literal: the encoder should emit a
3662        // single RLW carrying a run of 3 clean-zero words plus one literal.
3663        let ewah =
3664            EwahBitmap::from_words(256, &[0, 0, 0, 0b1]).expect("test operation should succeed");
3665        assert_eq!(ewah.words.len(), 2, "expected one RLW plus one literal");
3666        let rlw = ewah.words[0];
3667        assert_eq!(rlw & 1, 0, "run bit should be zero");
3668        assert_eq!((rlw >> 1) & 0xffff_ffff, 3, "run length should be 3");
3669        assert_eq!(rlw >> 33, 1, "literal length should be 1");
3670        assert_eq!(ewah.words[1], 0b1);
3671    }
3672
3673    #[test]
3674    fn ewah_compresses_clean_ones_run() {
3675        let ewah = EwahBitmap::from_words(192, &[u64::MAX, u64::MAX, u64::MAX])
3676            .expect("test operation should succeed");
3677        // Pure run of ones, no literals: one RLW only.
3678        assert_eq!(ewah.words.len(), 1);
3679        let rlw = ewah.words[0];
3680        assert_eq!(rlw & 1, 1, "run bit should be one");
3681        assert_eq!((rlw >> 1) & 0xffff_ffff, 3, "run length should be 3");
3682        assert_eq!(rlw >> 33, 0, "no literals");
3683    }
3684
3685    #[test]
3686    fn ewah_run_then_literal_then_run_roundtrips() {
3687        let words = vec![0, 0, 0xdead_beef, u64::MAX, u64::MAX, 0, 0xabc];
3688        let bit_size = (words.len() * 64) as u32;
3689        let ewah = EwahBitmap::from_words(bit_size, &words).expect("test operation should succeed");
3690        assert_eq!(
3691            ewah.to_words().expect("test operation should succeed"),
3692            words
3693        );
3694    }
3695
3696    #[test]
3697    fn ewah_drops_trailing_clean_zero_words() {
3698        // Trailing all-zero words beyond a literal carry no information and git
3699        // does not serialise them, but to_words() restores them up to bit_size.
3700        let words = vec![0b1, 0, 0, 0];
3701        let ewah = EwahBitmap::from_words(1, &words).expect("test operation should succeed");
3702        // bit_size of 1 means a single backing word.
3703        assert_eq!(ewah.bit_size, 1);
3704        assert_eq!(
3705            ewah.to_words().expect("test operation should succeed"),
3706            vec![0b1]
3707        );
3708    }
3709
3710    #[test]
3711    fn ewah_from_positions_roundtrips_via_positions() {
3712        let positions = [0u32, 1, 63, 64, 65, 200, 511];
3713        let ewah =
3714            EwahBitmap::from_positions(512, &positions).expect("test operation should succeed");
3715        let mut decoded = ewah.to_positions().expect("test operation should succeed");
3716        decoded.sort_unstable();
3717        assert_eq!(decoded, positions);
3718    }
3719
3720    #[test]
3721    fn ewah_from_positions_dedupes_and_orders() {
3722        let ewah = EwahBitmap::from_positions(128, &[100, 5, 100, 5, 5])
3723            .expect("test operation should succeed");
3724        assert_eq!(
3725            ewah.to_positions().expect("test operation should succeed"),
3726            vec![5, 100]
3727        );
3728    }
3729
3730    #[test]
3731    fn ewah_huge_zero_run_spans_multiple_rlws() {
3732        // A run longer than the 32-bit running-length field forces the encoder
3733        // to emit more than one RLW. Use one literal bit far out, with a bit
3734        // size large enough to exceed u32::MAX clean words is impractical, so
3735        // assert the field arithmetic via a direct builder run instead.
3736        let mut builder = EwahBuilder::new(0);
3737        builder.add_empty_words(false, 0xffff_ffff);
3738        builder.add_empty_words(false, 5);
3739        let ewah = builder.finish().expect("test operation should succeed");
3740        assert_eq!(ewah.words.len(), 2, "run split across two RLWs");
3741        assert_eq!((ewah.words[0] >> 1) & 0xffff_ffff, 0xffff_ffff);
3742        assert_eq!(ewah.words[1] & 1, 0);
3743        assert_eq!((ewah.words[1] >> 1) & 0xffff_ffff, 5);
3744        assert_eq!(ewah.rlw_position, 1);
3745    }
3746
3747    #[test]
3748    fn ewah_from_words_rejects_oversized_bit_size() {
3749        // bit_size demands two words but only one is supplied.
3750        assert!(EwahBitmap::from_words(65, &[0]).is_err());
3751    }
3752
3753    #[test]
3754    fn ewah_from_positions_rejects_out_of_range() {
3755        assert!(EwahBitmap::from_positions(64, &[64]).is_err());
3756    }
3757
3758    #[test]
3759    fn ewah_serialised_bytes_reparse_to_equal_bitmap() {
3760        // Exercise the full encode -> serialise -> parse loop for a non-trivial
3761        // pattern and assert structural equality against the parser's model.
3762        let words = vec![0, u64::MAX, 0x1234_5678_9abc_def0, 0, 0, 0xff];
3763        let bit_size = (words.len() * 64) as u32;
3764        let ewah = EwahBitmap::from_words(bit_size, &words).expect("test operation should succeed");
3765        let bytes = ewah.to_bytes();
3766        let parsed = parse_ewah_bytes(&bytes);
3767        assert_eq!(parsed, ewah);
3768        assert_eq!(
3769            parsed.to_words().expect("test operation should succeed"),
3770            words
3771        );
3772    }
3773
3774    #[test]
3775    fn pack_bitmap_index_write_parse_roundtrip_sha1() {
3776        // commit, tree, blob in pack order; one selected commit reaching all.
3777        let object_types = [ObjectType::Commit, ObjectType::Tree, ObjectType::Blob];
3778        let bytes = write_bitmap(
3779            ObjectFormat::Sha1,
3780            pack_checksum_sha1(),
3781            &object_types,
3782            &[(0u32, 0u32, vec![1u32, 2u32])],
3783            None,
3784        )
3785        .expect("test operation should succeed");
3786        assert_eq!(&bytes[..4], b"BITM");
3787
3788        let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 3)
3789            .expect("test operation should succeed");
3790        assert_eq!(parsed.version, 1);
3791        assert_eq!(parsed.options, PackBitmapIndex::OPTION_FULL_DAG);
3792        assert_eq!(parsed.pack_checksum, pack_checksum_sha1());
3793        assert_eq!(
3794            parsed
3795                .type_bitmaps
3796                .commits
3797                .to_positions()
3798                .expect("test operation should succeed"),
3799            vec![0]
3800        );
3801        assert_eq!(
3802            parsed
3803                .type_bitmaps
3804                .trees
3805                .to_positions()
3806                .expect("test operation should succeed"),
3807            vec![1]
3808        );
3809        assert_eq!(
3810            parsed
3811                .type_bitmaps
3812                .blobs
3813                .to_positions()
3814                .expect("test operation should succeed"),
3815            vec![2]
3816        );
3817        assert!(
3818            parsed
3819                .type_bitmaps
3820                .tags
3821                .to_positions()
3822                .expect("test operation should succeed")
3823                .is_empty()
3824        );
3825        assert_eq!(parsed.entries.len(), 1);
3826        let entry = parsed
3827            .entry_for_index_position(0)
3828            .expect("test operation should succeed");
3829        assert_eq!(entry.xor_offset, 0);
3830        assert_eq!(entry.flags, 0);
3831        assert_eq!(
3832            entry
3833                .bitmap
3834                .to_positions()
3835                .expect("test operation should succeed"),
3836            vec![0, 1, 2]
3837        );
3838        assert_eq!(parsed.name_hash_cache, None);
3839    }
3840
3841    #[test]
3842    fn pack_bitmap_index_write_parse_roundtrip_sha256() {
3843        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
3844            .expect("test operation should succeed");
3845        let object_types = [ObjectType::Commit, ObjectType::Tree];
3846        let bytes = write_bitmap(
3847            ObjectFormat::Sha256,
3848            pack_checksum.clone(),
3849            &object_types,
3850            &[(0u32, 0u32, vec![1u32])],
3851            None,
3852        )
3853        .expect("test operation should succeed");
3854        let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha256, 2)
3855            .expect("test operation should succeed");
3856        assert_eq!(parsed.format, ObjectFormat::Sha256);
3857        assert_eq!(parsed.pack_checksum, pack_checksum);
3858        assert_eq!(parsed.index_checksum.format(), ObjectFormat::Sha256);
3859        assert_eq!(
3860            parsed.entries[0]
3861                .bitmap
3862                .to_positions()
3863                .expect("test operation should succeed"),
3864            vec![0, 1]
3865        );
3866    }
3867
3868    #[test]
3869    fn pack_bitmap_index_write_includes_name_hash_cache() {
3870        let object_types = [ObjectType::Commit, ObjectType::Tree, ObjectType::Blob];
3871        let cache = vec![0x1111_1111u32, 0x2222_2222, 0x3333_3333];
3872        let bytes = write_bitmap(
3873            ObjectFormat::Sha1,
3874            pack_checksum_sha1(),
3875            &object_types,
3876            &[(0u32, 0u32, vec![1u32, 2u32])],
3877            Some(cache.clone()),
3878        )
3879        .expect("test operation should succeed");
3880        let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 3)
3881            .expect("test operation should succeed");
3882        assert_eq!(
3883            parsed.options,
3884            PackBitmapIndex::OPTION_FULL_DAG | PackBitmapIndex::OPTION_HASH_CACHE
3885        );
3886        assert_eq!(parsed.name_hash_cache, Some(cache));
3887    }
3888
3889    #[test]
3890    fn pack_bitmap_writer_supports_multiple_commits() {
3891        let object_types = [
3892            ObjectType::Commit,
3893            ObjectType::Commit,
3894            ObjectType::Tree,
3895            ObjectType::Blob,
3896        ];
3897        let mut writer =
3898            PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3899                .expect("test operation should succeed");
3900        writer
3901            .add_commit(0, 0, &[2, 3])
3902            .expect("test operation should succeed");
3903        writer
3904            .add_commit(1, 1, &[2])
3905            .expect("test operation should succeed");
3906        let bytes = writer.write().expect("test operation should succeed");
3907        let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 4)
3908            .expect("test operation should succeed");
3909        assert_eq!(parsed.entries.len(), 2);
3910        assert_eq!(
3911            parsed
3912                .type_bitmaps
3913                .commits
3914                .to_positions()
3915                .expect("test operation should succeed"),
3916            vec![0, 1]
3917        );
3918        let first = parsed
3919            .entry_for_index_position(0)
3920            .expect("test operation should succeed");
3921        assert_eq!(
3922            first
3923                .bitmap
3924                .to_positions()
3925                .expect("test operation should succeed"),
3926            vec![0, 2, 3]
3927        );
3928        let second = parsed
3929            .entry_for_index_position(1)
3930            .expect("test operation should succeed");
3931        assert_eq!(
3932            second
3933                .bitmap
3934                .to_positions()
3935                .expect("test operation should succeed"),
3936            vec![1, 2]
3937        );
3938    }
3939
3940    #[test]
3941    fn pack_bitmap_writer_roundtrips_lookup_table() {
3942        let object_types = [ObjectType::Commit, ObjectType::Commit, ObjectType::Tree];
3943        let mut writer =
3944            PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3945                .expect("test operation should succeed")
3946                .with_lookup_table(true);
3947        writer
3948            .add_commit(0, 1, &[2])
3949            .expect("test operation should succeed");
3950        writer
3951            .add_commit(1, 0, &[2])
3952            .expect("test operation should succeed");
3953        let bytes = writer.write().expect("test operation should succeed");
3954        let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 3)
3955            .expect("test operation should succeed");
3956        assert!(parsed.lookup_table);
3957        assert_ne!(parsed.options & PackBitmapIndex::OPTION_LOOKUP_TABLE, 0);
3958        assert_eq!(parsed.entries.len(), 2);
3959    }
3960
3961    #[test]
3962    fn pack_bitmap_index_recomputes_checksum_on_write() {
3963        // The provided index_checksum field is ignored; write recomputes it so
3964        // a bogus placeholder still produces a valid, parseable file.
3965        let object_types = [ObjectType::Commit, ObjectType::Blob];
3966        let writer = PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3967            .expect("test operation should succeed");
3968        let mut index = writer.build().expect("test operation should succeed");
3969        // build() sets an all-zero placeholder checksum.
3970        assert_eq!(index.index_checksum.as_bytes(), [0u8; 20]);
3971        index.entries.clear(); // mutate the model after build
3972        index.entries.push(PackBitmapEntry {
3973            object_position: 0,
3974            xor_offset: 0,
3975            flags: 0,
3976            bitmap: EwahBitmap::from_positions(2, &[0, 1]).expect("test operation should succeed"),
3977        });
3978        let bytes = index.write().expect("test operation should succeed");
3979        // Parsing validates the trailing checksum, so a wrong checksum fails.
3980        let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 2)
3981            .expect("test operation should succeed");
3982        assert_ne!(parsed.index_checksum.as_bytes(), [0u8; 20]);
3983    }
3984
3985    #[test]
3986    fn pack_bitmap_writer_rejects_non_commit_selection() {
3987        let object_types = [ObjectType::Commit, ObjectType::Blob];
3988        let mut writer =
3989            PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3990                .expect("test operation should succeed");
3991        // Position 1 is a blob, not a commit.
3992        assert!(writer.add_commit(1, 1, &[]).is_err());
3993        // Position 5 is out of range entirely.
3994        assert!(writer.add_commit(5, 5, &[]).is_err());
3995        // Index position out of range.
3996        assert!(writer.add_commit(0, 5, &[]).is_err());
3997        // Reachable position out of range.
3998        assert!(writer.add_commit(0, 0, &[9]).is_err());
3999    }
4000
4001    #[test]
4002    fn pack_bitmap_writer_rejects_checksum_format_mismatch() {
4003        let sha256_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
4004            .expect("test operation should succeed");
4005        assert!(
4006            PackBitmapWriter::new(ObjectFormat::Sha1, sha256_checksum, &[ObjectType::Commit])
4007                .is_err()
4008        );
4009    }
4010
4011    #[test]
4012    fn pack_bitmap_writer_rejects_bad_name_hash_cache_len() {
4013        let writer = PackBitmapWriter::new(
4014            ObjectFormat::Sha1,
4015            pack_checksum_sha1(),
4016            &[ObjectType::Commit],
4017        )
4018        .expect("test operation should succeed");
4019        assert!(writer.with_name_hash_cache(vec![1, 2]).is_err());
4020    }
4021
4022    #[test]
4023    fn pack_bitmap_index_write_rejects_inconsistent_cache_flag() {
4024        let mut index = PackBitmapWriter::new(
4025            ObjectFormat::Sha1,
4026            pack_checksum_sha1(),
4027            &[ObjectType::Commit],
4028        )
4029        .expect("test operation should succeed")
4030        .build()
4031        .expect("test operation should succeed");
4032        // Flag set but no cache present.
4033        index.options |= PackBitmapIndex::OPTION_HASH_CACHE;
4034        assert!(index.write().is_err());
4035        // Cache present but flag missing.
4036        index.options = PackBitmapIndex::OPTION_FULL_DAG;
4037        index.name_hash_cache = Some(vec![0]);
4038        assert!(index.write().is_err());
4039    }
4040
4041    #[test]
4042    fn write_bitmap_roundtrips_through_upstream_git_parser() {
4043        // Build a real pack with git, then overwrite reachability with our own
4044        // writer using the real pack checksum and object types, and confirm our
4045        // bytes parse under the same parser that reads upstream bitmaps.
4046        let root = unique_temp_dir("git-pack-bitmap-writer");
4047        fs::create_dir_all(&root).expect("test operation should succeed");
4048        {
4049            run_git_success(&root, &["init", "-q", "-b", "main"]);
4050            run_git_success(
4051                &root,
4052                &[
4053                    "-c",
4054                    "user.name=Example User",
4055                    "-c",
4056                    "user.email=example@example.invalid",
4057                    "commit",
4058                    "--allow-empty",
4059                    "-q",
4060                    "-m",
4061                    "one",
4062                ],
4063            );
4064            run_git_success(&root, &["repack", "-adb"]);
4065            let pack_dir = root.join(".git").join("objects").join("pack");
4066            let idx_path = single_path_with_extension(&pack_dir, "idx");
4067            let index = PackIndex::parse(
4068                &fs::read(idx_path).expect("test operation should succeed"),
4069                ObjectFormat::Sha1,
4070            )
4071            .expect("test operation should succeed");
4072            // Read object types from the pack so the type bitmaps are accurate.
4073            let pack_path = single_path_with_extension(&pack_dir, "pack");
4074            let pack =
4075                PackFile::parse_sha1(&fs::read(pack_path).expect("test operation should succeed"))
4076                    .expect("test operation should succeed");
4077            // Map each index entry (sorted by oid) to its pack offset, then to a
4078            // pack-order position so positions line up with the index ordering.
4079            let mut offsets: Vec<u64> = index.entries.iter().map(|entry| entry.offset).collect();
4080            offsets.sort_unstable();
4081            let position_of = |offset: u64| -> u32 {
4082                offsets
4083                    .iter()
4084                    .position(|value| *value == offset)
4085                    .expect("test operation should succeed") as u32
4086            };
4087            let mut object_types = vec![ObjectType::Blob; index.entries.len()];
4088            for entry in &index.entries {
4089                let position = position_of(entry.offset) as usize;
4090                // Find the parsed object at this pack offset to read its type.
4091                if let Some(parsed) = pack
4092                    .entries
4093                    .iter()
4094                    .find(|po| po.entry.offset == entry.offset)
4095                {
4096                    object_types[position] = parsed.object.object_type;
4097                }
4098            }
4099            // Select the first commit position we find and reach everything.
4100            let commit_position = object_types
4101                .iter()
4102                .position(|ty| *ty == ObjectType::Commit)
4103                .expect("test operation should succeed") as u32;
4104            // The entry records the commit's position in the oid-sorted index.
4105            let commit_index_position = index
4106                .entries
4107                .iter()
4108                .position(|entry| position_of(entry.offset) == commit_position)
4109                .expect("test operation should succeed")
4110                as u32;
4111            let reachable: Vec<u32> = (0..index.entries.len() as u32).collect();
4112            let bytes = write_bitmap(
4113                ObjectFormat::Sha1,
4114                index.pack_checksum.clone(),
4115                &object_types,
4116                &[(commit_position, commit_index_position, reachable)],
4117                None,
4118            )
4119            .expect("test operation should succeed");
4120            let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, index.entries.len())
4121                .expect("test operation should succeed");
4122            assert_eq!(parsed.pack_checksum, index.pack_checksum);
4123            assert_eq!(parsed.entries.len(), 1);
4124            assert_eq!(
4125                parsed.entries[0]
4126                    .bitmap
4127                    .to_positions()
4128                    .expect("test operation should succeed")
4129                    .len(),
4130                index.entries.len()
4131            );
4132        };
4133        let _ = fs::remove_dir_all(&root);
4134    }
4135
4136    // ---- sley#4 / sley#5: bounds on untrusted pack input --------------------
4137
4138    fn zlib_compress(bytes: &[u8]) -> Vec<u8> {
4139        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
4140        encoder
4141            .write_all(bytes)
4142            .expect("test operation should succeed");
4143        encoder.finish().expect("test operation should succeed")
4144    }
4145
4146    /// A structurally valid 32-byte pack — correct signature, version, and
4147    /// trailing checksum — whose object-count field says `declared`.
4148    fn pack_with_declared_object_count(declared: u32) -> Vec<u8> {
4149        let mut pack = Vec::new();
4150        pack.extend_from_slice(b"PACK");
4151        pack.extend_from_slice(&2u32.to_be_bytes());
4152        pack.extend_from_slice(&declared.to_be_bytes());
4153        let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &pack)
4154            .expect("test operation should succeed");
4155        pack.extend_from_slice(checksum.as_bytes());
4156        pack
4157    }
4158
4159    /// Regression (sley#4): the pack header's 32-bit object count is
4160    /// attacker-controlled and went straight to `Vec::with_capacity`. A 32-byte
4161    /// pack declaring `u32::MAX` objects asks the allocator for ~480 GiB before
4162    /// a single entry is inspected; that allocation fails and Rust's OOM handler
4163    /// calls `abort()`. Hence the child process: `abort()` takes the whole test
4164    /// binary with it, so neither `JoinHandle::join` nor `catch_unwind` can
4165    /// observe it the way they observe the sley#35 delta bomb.
4166    #[test]
4167    fn rejects_absurd_declared_object_count_without_preallocating() {
4168        const CHILD_ENV: &str = "SLEY_PACK_OBJECT_COUNT_BOMB_CHILD";
4169        const TEST_PATH: &str = "tests::rejects_absurd_declared_object_count_without_preallocating";
4170
4171        if std::env::var_os(CHILD_ENV).is_some() {
4172            for declared in [u32::MAX, 1 << 30, 1 << 24] {
4173                let pack = pack_with_declared_object_count(declared);
4174                assert!(
4175                    PackFile::parse_sha1(&pack).is_err(),
4176                    "a 32-byte pack declaring {declared} objects must be rejected"
4177                );
4178                assert!(
4179                    PackIndex::write_v2_for_pack(&pack, ObjectFormat::Sha1).is_err(),
4180                    "index-pack must reject {declared} objects in a 32-byte pack too"
4181                );
4182                assert!(
4183                    PackFile::verify_pack_stats(&pack, ObjectFormat::Sha1).is_err(),
4184                    "verify-pack must reject {declared} objects in a 32-byte pack too"
4185                );
4186            }
4187            return;
4188        }
4189
4190        let exe = std::env::current_exe().expect("test binary path should be available");
4191        let status = Command::new(exe)
4192            .args(["--exact", TEST_PATH, "--nocapture"])
4193            .env(CHILD_ENV, "1")
4194            .status()
4195            .expect("re-running the test binary should succeed");
4196        assert!(
4197            status.success(),
4198            "child process died ({status:?}) parsing packs with oversized declared \
4199             object counts — the declared count reached an allocation instead of a \
4200             bounds check"
4201        );
4202    }
4203
4204    /// A pack that declares a merely implausible count — not one large enough
4205    /// to abort the process — is still rejected by the header check rather than
4206    /// part-way through the entry loop.
4207    #[test]
4208    fn rejects_declared_object_count_larger_than_pack_can_hold() {
4209        let pack = pack_with_declared_object_count(64);
4210        let error = PackFile::parse_sha1(&pack).expect_err("declared count must be rejected");
4211        assert!(
4212            format!("{error}").contains("only has room for"),
4213            "expected a declared-count bound error, got: {error}"
4214        );
4215    }
4216
4217    /// A delta that ignores its base and emits `result` as a single insert
4218    /// instruction. Every link of a chain built from this is the same tiny
4219    /// size, so chain *depth* is the only variable under test.
4220    fn literal_delta(base_len: usize, result: &[u8]) -> Vec<u8> {
4221        assert!(result.len() <= 0x7f, "insert instruction size is one byte");
4222        let mut delta = Vec::new();
4223        write_delta_varint(&mut delta, base_len as u64);
4224        write_delta_varint(&mut delta, result.len() as u64);
4225        delta.push(result.len() as u8);
4226        delta.extend_from_slice(result);
4227        delta
4228    }
4229
4230    /// Distinct 8-byte bodies, so no two links of a chain share an object id.
4231    fn chain_bodies(depth: usize) -> Vec<Vec<u8>> {
4232        (0..=depth)
4233            .map(|idx| format!("{idx:08}").into_bytes())
4234            .collect()
4235    }
4236
4237    /// One `ofs-delta` chain of `depth` links on top of a single blob, laid out
4238    /// front to back the way a real packer emits it.
4239    fn ofs_delta_chain_pack(format: ObjectFormat, depth: usize) -> Vec<u8> {
4240        let bodies = chain_bodies(depth);
4241        let mut pack = Vec::new();
4242        pack.extend_from_slice(b"PACK");
4243        pack.extend_from_slice(&2u32.to_be_bytes());
4244        pack.extend_from_slice(&(depth as u32 + 1).to_be_bytes());
4245
4246        let mut base_offset = pack.len();
4247        write_entry_header(&mut pack, ObjectType::Blob, bodies[0].len() as u64);
4248        pack.extend_from_slice(&zlib_compress(&bodies[0]));
4249
4250        for idx in 1..=depth {
4251            let delta = literal_delta(bodies[idx - 1].len(), &bodies[idx]);
4252            let entry_offset = pack.len();
4253            write_pack_entry_header_kind(&mut pack, 6, delta.len() as u64);
4254            write_ofs_delta_offset(&mut pack, entry_offset - base_offset);
4255            pack.extend_from_slice(&zlib_compress(&delta));
4256            base_offset = entry_offset;
4257        }
4258
4259        let checksum =
4260            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
4261        pack.extend_from_slice(checksum.as_bytes());
4262        pack
4263    }
4264
4265    /// One `ref-delta` chain of `depth` links on top of a single blob. When
4266    /// `reversed`, the deltas are laid out deepest-first — legal for ref-deltas
4267    /// (unlike ofs-deltas, whose base must precede them) and the adversarial
4268    /// shape from sley#5: a naive resolver advances the chain by only one link
4269    /// per full-list scan, so an unbounded chain costs one scan per link.
4270    fn ref_delta_chain_pack(format: ObjectFormat, depth: usize, reversed: bool) -> Vec<u8> {
4271        let bodies = chain_bodies(depth);
4272
4273        let mut base_entry = Vec::new();
4274        write_entry_header(&mut base_entry, ObjectType::Blob, bodies[0].len() as u64);
4275        base_entry.extend_from_slice(&zlib_compress(&bodies[0]));
4276
4277        let mut delta_entries = Vec::with_capacity(depth);
4278        for idx in 1..=depth {
4279            let delta = literal_delta(bodies[idx - 1].len(), &bodies[idx]);
4280            let base_oid = sley_core::object_id_for_bytes(format, "blob", &bodies[idx - 1])
4281                .expect("test operation should succeed");
4282            let mut entry = Vec::new();
4283            write_pack_entry_header_kind(&mut entry, 7, delta.len() as u64);
4284            entry.extend_from_slice(base_oid.as_bytes());
4285            entry.extend_from_slice(&zlib_compress(&delta));
4286            delta_entries.push(entry);
4287        }
4288        if reversed {
4289            delta_entries.reverse();
4290        }
4291
4292        let mut pack = Vec::new();
4293        pack.extend_from_slice(b"PACK");
4294        pack.extend_from_slice(&2u32.to_be_bytes());
4295        pack.extend_from_slice(&(depth as u32 + 1).to_be_bytes());
4296        if !reversed {
4297            pack.extend_from_slice(&base_entry);
4298        }
4299        for entry in &delta_entries {
4300            pack.extend_from_slice(entry);
4301        }
4302        if reversed {
4303            pack.extend_from_slice(&base_entry);
4304        }
4305
4306        let checksum =
4307            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
4308        pack.extend_from_slice(checksum.as_bytes());
4309        pack
4310    }
4311
4312    #[test]
4313    fn header_read_bounds_ofs_delta_recursion_at_the_shared_ceiling() {
4314        let at_ceiling = ofs_delta_chain_pack(ObjectFormat::Sha1, MAX_READ_DELTA_CHAIN_DEPTH);
4315        let at_ceiling_offset = pack_entry_descriptors(&at_ceiling, ObjectFormat::Sha1)
4316            .last()
4317            .expect("the pack has entries")
4318            .offset;
4319        let header = read_object_header_at(
4320            &at_ceiling,
4321            at_ceiling_offset,
4322            ObjectFormat::Sha1,
4323            0,
4324            |_, _| Ok(None),
4325        )
4326        .expect("a header chain at the ceiling must resolve");
4327        assert_eq!(header.type_and_size(), (ObjectType::Blob, 8));
4328
4329        let over_ceiling = ofs_delta_chain_pack(ObjectFormat::Sha1, 5_000);
4330        let over_ceiling_offset = pack_entry_descriptors(&over_ceiling, ObjectFormat::Sha1)
4331            .last()
4332            .expect("the pack has entries")
4333            .offset;
4334        let error = std::thread::Builder::new()
4335            .stack_size(512 * 1024)
4336            .spawn(move || {
4337                read_object_header_at(
4338                    &over_ceiling,
4339                    over_ceiling_offset,
4340                    ObjectFormat::Sha1,
4341                    0,
4342                    |_, _| Ok(None),
4343                )
4344                .expect_err("a header chain past the ceiling must be rejected")
4345            })
4346            .expect("spawn small-stack thread")
4347            .join()
4348            .expect("the bounded header walk must not overflow its stack");
4349        let rejected_depth = MAX_READ_DELTA_CHAIN_DEPTH + 1;
4350        assert!(
4351            error
4352                .to_string()
4353                .contains(&format!("observed depth {rejected_depth}")),
4354            "expected an actionable depth error, got: {error}"
4355        );
4356    }
4357
4358    #[test]
4359    fn header_read_combines_prior_and_local_delta_depth() {
4360        let pack = ofs_delta_chain_pack(ObjectFormat::Sha1, 2);
4361        let offset = pack_entry_descriptors(&pack, ObjectFormat::Sha1)
4362            .last()
4363            .expect("the pack has entries")
4364            .offset;
4365        let error = read_object_header_at(
4366            &pack,
4367            offset,
4368            ObjectFormat::Sha1,
4369            MAX_READ_DELTA_CHAIN_DEPTH - 1,
4370            |_, _| Ok(None),
4371        )
4372        .expect_err("local ofs-deltas must count prior cross-pack delta links");
4373        let rejected_depth = MAX_READ_DELTA_CHAIN_DEPTH + 1;
4374        assert!(
4375            error
4376                .to_string()
4377                .contains(&format!("observed depth {rejected_depth}")),
4378            "expected the combined depth in the error, got: {error}"
4379        );
4380    }
4381
4382    #[test]
4383    fn header_cache_preserves_the_cumulative_depth_limit() {
4384        let pack = ofs_delta_chain_pack(ObjectFormat::Sha1, 3);
4385        let descriptors = pack_entry_descriptors(&pack, ObjectFormat::Sha1);
4386        let target_offset = descriptors[3].offset;
4387        let cached_base_offset = descriptors[2].offset;
4388        let initial_depth = MAX_READ_DELTA_CHAIN_DEPTH - 2;
4389        let rejected_depth = MAX_READ_DELTA_CHAIN_DEPTH + 1;
4390
4391        let mut direct_cache = MapHeaderTypeCache::default();
4392        read_object_header_at_with_cache(
4393            &pack,
4394            target_offset,
4395            ObjectFormat::Sha1,
4396            0,
4397            |_, _| Ok(None),
4398            &mut direct_cache,
4399        )
4400        .expect("the top-level read should warm the target header");
4401        let direct_error = read_object_header_at_with_cache(
4402            &pack,
4403            target_offset,
4404            ObjectFormat::Sha1,
4405            initial_depth,
4406            |_, _| Ok(None),
4407            &mut direct_cache,
4408        )
4409        .expect_err("a direct cache hit must include its represented chain depth");
4410        assert!(
4411            direct_error
4412                .to_string()
4413                .contains(&format!("observed depth {rejected_depth}")),
4414            "expected the direct cached depth in the error, got: {direct_error}"
4415        );
4416
4417        let mut base_cache = MapHeaderTypeCache::default();
4418        read_object_header_at_with_cache(
4419            &pack,
4420            cached_base_offset,
4421            ObjectFormat::Sha1,
4422            0,
4423            |_, _| Ok(None),
4424            &mut base_cache,
4425        )
4426        .expect("the top-level read should warm the target's immediate base");
4427        let base_error = read_object_header_at_with_cache(
4428            &pack,
4429            target_offset,
4430            ObjectFormat::Sha1,
4431            initial_depth,
4432            |_, _| Ok(None),
4433            &mut base_cache,
4434        )
4435        .expect_err("an internal base-cache hit must include its represented chain depth");
4436        assert!(
4437            base_error
4438                .to_string()
4439                .contains(&format!("observed depth {rejected_depth}")),
4440            "expected the cached base depth in the error, got: {base_error}"
4441        );
4442    }
4443
4444    /// A chain exactly at the ceiling still resolves: the bound must not reject
4445    /// anything sley's own writer (or git at its `pack.depth` default) emits.
4446    #[test]
4447    fn resolves_delta_chain_at_the_depth_ceiling() {
4448        for pack in [
4449            ofs_delta_chain_pack(ObjectFormat::Sha1, DEFAULT_PACK_DEPTH),
4450            ref_delta_chain_pack(ObjectFormat::Sha1, DEFAULT_PACK_DEPTH, false),
4451        ] {
4452            let parsed = PackFile::parse_sha1(&pack).expect("a chain at the ceiling must resolve");
4453            assert_eq!(parsed.entries.len(), DEFAULT_PACK_DEPTH + 1);
4454        }
4455    }
4456
4457    #[test]
4458    fn configured_delta_chain_depth_limit_allows_deeper_pack() {
4459        let limits = PackReadLimits {
4460            max_delta_depth: 60,
4461            ..PackReadLimits::default()
4462        };
4463        let pack = ofs_delta_chain_pack(ObjectFormat::Sha1, 60);
4464
4465        let parsed = PackFile::parse_with_limits(&pack, ObjectFormat::Sha1, limits)
4466            .expect("a chain at the configured ceiling must resolve");
4467        assert_eq!(parsed.entries.len(), 61);
4468
4469        let indexed = PackIndex::write_v2_for_pack_with_limits(&pack, ObjectFormat::Sha1, limits)
4470            .expect("the in-memory indexer must use the configured ceiling");
4471        assert_eq!(indexed.entries.len(), 61);
4472    }
4473
4474    #[test]
4475    fn configured_delta_chain_depth_limit_remains_finite_and_actionable() {
4476        let limits = PackReadLimits {
4477            max_delta_depth: 60,
4478            ..PackReadLimits::default()
4479        };
4480        let pack = ofs_delta_chain_pack(ObjectFormat::Sha1, 61);
4481
4482        let error = PackFile::parse_with_limits(&pack, ObjectFormat::Sha1, limits)
4483            .expect_err("a chain past the configured ceiling must be rejected");
4484        let message = format!("{error}");
4485
4486        assert!(
4487            message.contains("observed depth 61"),
4488            "expected the observed depth in the error, got: {error}"
4489        );
4490        assert!(
4491            message.contains("configured limit 60"),
4492            "expected the configured limit in the error, got: {error}"
4493        );
4494        assert!(
4495            message.contains("PackReadLimits::max_delta_depth")
4496                && message.contains("git repack --depth="),
4497            "expected actionable remedies in the error, got: {error}"
4498        );
4499    }
4500
4501    /// Regression (sley#5): the read path enforced no chain-depth limit at all.
4502    /// `DEFAULT_PACK_DEPTH` existed but was consulted only when *writing*.
4503    #[test]
4504    fn rejects_delta_chain_deeper_than_the_ceiling() {
4505        for pack in [
4506            ofs_delta_chain_pack(ObjectFormat::Sha1, DEFAULT_PACK_DEPTH + 1),
4507            ref_delta_chain_pack(ObjectFormat::Sha1, DEFAULT_PACK_DEPTH + 1, false),
4508        ] {
4509            let error =
4510                PackFile::parse_sha1(&pack).expect_err("a chain past the ceiling must be rejected");
4511            assert!(
4512                format!("{error}").contains("exceeds maximum depth"),
4513                "expected a chain-depth error, got: {error}"
4514            );
4515        }
4516    }
4517
4518    /// Regression (sley#5): a long chain laid out back to front made the former
4519    /// resolver run one full pass per link — O(N^2) scans on top of O(N) delta
4520    /// applications — with nothing to stop it. The dependency-level resolver
4521    /// rejects the chain at the configured depth ceiling.
4522    #[test]
4523    fn rejects_adversarially_ordered_long_delta_chain_promptly() {
4524        let pack = ref_delta_chain_pack(ObjectFormat::Sha1, 5_000, true);
4525        let started = std::time::Instant::now();
4526        let error = PackFile::parse_sha1(&pack)
4527            .expect_err("a long adversarial chain must be rejected, not resolved");
4528        let elapsed = started.elapsed();
4529        assert!(
4530            format!("{error}").contains("exceeds maximum depth"),
4531            "expected a chain-depth error, got: {error}"
4532        );
4533        assert!(
4534            elapsed < std::time::Duration::from_secs(20),
4535            "rejection took {elapsed:?}; resolution is still doing work proportional \
4536             to the chain length"
4537        );
4538    }
4539
4540    /// `index-pack` resolves through the same helper, so it inherits the same
4541    /// ceiling — the bound belongs to the shared resolver, not to one caller.
4542    #[test]
4543    fn index_pack_rejects_delta_chain_deeper_than_the_ceiling() {
4544        let pack = ofs_delta_chain_pack(ObjectFormat::Sha1, DEFAULT_PACK_DEPTH + 1);
4545        let error = PackIndex::write_v2_for_pack(&pack, ObjectFormat::Sha1)
4546            .expect_err("index-pack must reject a chain past the ceiling");
4547        assert!(
4548            format!("{error}").contains("exceeds maximum depth"),
4549            "expected a chain-depth error, got: {error}"
4550        );
4551    }
4552}