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::{GitError, ObjectFormat, ObjectId, Result, StreamingDigest};
7use sley_formats::Bundle;
8use sley_object::{EncodedObject, ObjectType};
9use std::borrow::Borrow;
10use std::cell::RefCell;
11use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
12use std::fmt;
13use std::fs::File;
14use std::io::{Read, Seek, SeekFrom, Write};
15use std::ops::Range;
16use std::path::Path;
17use std::sync::Arc;
18
19// --- Mechanical module split (W21) -----------------------------------------
20// The former single ~10k-line lib.rs is partitioned into contiguous
21// submodules along its existing function-cluster seams. Each submodule pulls
22// the crate-root scope in via `use super::*` and is re-exported below so every
23// `sley_pack::X` path (public API and intra-crate) resolves unchanged.
24// This is a pure code move: no function body was altered.
25pub mod inflate;
26mod delta;
27mod index;
28mod read;
29mod stream;
30mod write;
31
32pub(crate) use delta::*;
33pub use index::*;
34pub use read::*;
35pub(crate) use stream::*;
36pub use write::*;
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct PackEntry {
40    pub oid: ObjectId,
41    pub compressed_size: u64,
42    pub uncompressed_size: u64,
43    pub offset: u64,
44}
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct RepackPolicy {
47    pub write_bitmaps: bool,
48    pub cruft_packs: bool,
49    pub geometric_factor: Option<u8>,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct PackFile {
54    pub version: u32,
55    pub entries: Vec<PackObject>,
56    pub checksum: ObjectId,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct PackObject {
61    pub entry: PackEntry,
62    pub object: EncodedObject,
63}
64
65/// Per-object statistics for one entry of a verified pack, in the shape
66/// `git verify-pack -v` reports.
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct PackVerifyStat {
69    /// Resolved object id.
70    pub oid: ObjectId,
71    /// Resolved object type (the delta's *result* type, not `ofs-delta`).
72    pub object_type: ObjectType,
73    /// Resolved (inflated) object size in bytes.
74    pub size: u64,
75    /// Bytes this object occupies in the pack: the offset delta to the next
76    /// object, or to the trailing checksum for the last object.
77    pub size_in_pack: u64,
78    /// In-pack byte offset where this object's entry begins.
79    pub offset: u64,
80    /// Delta chain depth: `0` for undeltified objects, base-depth + 1 otherwise.
81    pub delta_depth: u32,
82    /// For delta objects, the id of the *immediate* base object (which may
83    /// itself be a delta). `None` for undeltified objects.
84    pub base_oid: Option<ObjectId>,
85}
86
87/// Result of [`PackFile::verify_pack_stats`]: per-object stats in pack offset
88/// order plus the pack's trailing checksum.
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct PackVerifyStats {
91    pub objects: Vec<PackVerifyStat>,
92    pub checksum: ObjectId,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct PackWrite {
97    pub pack: Vec<u8>,
98    pub index: Vec<u8>,
99    pub checksum: ObjectId,
100    pub entries: Vec<PackIndexEntry>,
101    pub delta_count: u32,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct PackWriteSummary {
106    pub index: Vec<u8>,
107    pub checksum: ObjectId,
108    pub entries: Vec<PackIndexEntry>,
109    pub delta_count: u32,
110    pub pack_size: u64,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub struct PackInput<'a> {
115    pub oid: &'a ObjectId,
116    pub object: &'a EncodedObject,
117}
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119enum PackObjectKind {
120    Commit,
121    Tree,
122    Blob,
123    Tag,
124    OfsDelta,
125    RefDelta,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
129enum ParsedPackEntry {
130    Resolved(PackObject),
131    Delta {
132        base: DeltaBase,
133        compressed_size: u64,
134        delta_size: u64,
135        offset: u64,
136        delta: Vec<u8>,
137    },
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
141enum DeltaBase {
142    Offset(u64),
143    Ref(ObjectId),
144}
145
146/// One pack entry as stored on disk, used by [`PackFile::verify_pack_stats`] to
147/// recover the delta structure and on-disk stream size that resolved
148/// [`PackObject`]s no longer carry.
149struct OnDiskEntry {
150    offset: u64,
151    base: Option<DeltaBase>,
152    stream_size: u64,
153}
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
155struct EntryHeader {
156    kind: PackObjectKind,
157    size: u64,
158}
159fn next_byte(bytes: &[u8], offset: &mut usize) -> Result<u8> {
160    let Some(byte) = bytes.get(*offset).copied() else {
161        return Err(GitError::InvalidFormat(
162            "truncated pack entry header".into(),
163        ));
164    };
165    *offset += 1;
166    Ok(byte)
167}
168
169fn u16_be(bytes: &[u8]) -> u16 {
170    u16::from_be_bytes([bytes[0], bytes[1]])
171}
172
173fn u32_be(bytes: &[u8]) -> u32 {
174    u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
175}
176
177fn u64_be(bytes: &[u8]) -> u64 {
178    u64::from_be_bytes([
179        bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
180    ])
181}
182fn checked_range(
183    start: usize,
184    count: usize,
185    width: usize,
186    total: usize,
187) -> Result<std::ops::Range<usize>> {
188    let len = count
189        .checked_mul(width)
190        .ok_or_else(|| GitError::InvalidFormat("pack index table overflow".into()))?;
191    let end = start
192        .checked_add(len)
193        .ok_or_else(|| GitError::InvalidFormat("pack index table overflow".into()))?;
194    if end > total {
195        return Err(GitError::InvalidFormat("truncated pack index table".into()));
196    }
197    Ok(start..end)
198}
199
200fn validate_position_permutation(positions: &[u32]) -> Result<()> {
201    let mut seen = vec![false; positions.len()];
202    for position in positions {
203        let idx = *position as usize;
204        if idx >= positions.len() {
205            return Err(GitError::InvalidFormat(
206                "reverse index position points past object table".into(),
207            ));
208        }
209        if seen[idx] {
210            return Err(GitError::InvalidFormat(
211                "reverse index position is duplicated".into(),
212            ));
213        }
214        seen[idx] = true;
215    }
216    Ok(())
217}
218
219// Reused zlib inflate state. Resetting and reusing one `Decompress` avoids
220// allocating a fresh (~10 KiB) `InflateState` for every object and delta decoded —
221// an allocation that dominated bulk reads. Borrowed only for the duration of a
222// single inflate; the recursive pack reader fully inflates each entry's data before
223// recursing to its base, so the borrow never nests.
224thread_local! {
225    pub(crate) static INFLATE: RefCell<flate2::Decompress> = RefCell::new(flate2::Decompress::new(true));
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231    use flate2::Compression;
232    use flate2::read::ZlibDecoder;
233    use flate2::write::ZlibEncoder;
234    use std::fs;
235    use std::io::Read;
236    use std::io::Write;
237    use std::path::{Path, PathBuf};
238    use std::process::Command;
239    use std::time::{SystemTime, UNIX_EPOCH};
240
241    fn delta_pack_options(prefer_ofs_delta: bool) -> PackWriteOptions {
242        PackWriteOptions::new()
243            .with_prefer_ofs_delta(prefer_ofs_delta)
244            .with_reorder(false)
245    }
246
247    #[test]
248    fn parses_single_blob_pack() {
249        let pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"hello\n");
250        let parsed = PackFile::parse_sha1(&pack).expect("test operation should succeed");
251        assert_eq!(parsed.version, 2);
252        assert_eq!(parsed.entries.len(), 1);
253        let object = &parsed.entries[0].object;
254        assert_eq!(object.object_type, ObjectType::Blob);
255        assert_eq!(object.body, b"hello\n");
256        assert_eq!(
257            parsed.entries[0].entry.oid.to_hex(),
258            "ce013625030ba8dba906f756967f9e9ca394464a"
259        );
260    }
261
262    #[test]
263    fn parses_single_blob_pack_sha256() {
264        let pack = single_object_pack(ObjectFormat::Sha256, ObjectType::Blob, b"hello\n");
265        let parsed =
266            PackFile::parse(&pack, ObjectFormat::Sha256).expect("test operation should succeed");
267        assert_eq!(parsed.version, 2);
268        assert_eq!(parsed.entries.len(), 1);
269        let object = &parsed.entries[0].object;
270        assert_eq!(object.object_type, ObjectType::Blob);
271        assert_eq!(object.body, b"hello\n");
272        assert_eq!(
273            parsed.entries[0].entry.oid,
274            object
275                .object_id(ObjectFormat::Sha256)
276                .expect("test operation should succeed")
277        );
278    }
279
280    #[test]
281    fn parses_bundle_pack_payload_with_bundle_format() {
282        let pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"bundle\n");
283        let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"bundle\n")
284            .expect("test operation should succeed");
285        let bundle_bytes = format!("# v2 git bundle\n{oid} refs/heads/main\n\n")
286            .into_bytes()
287            .into_iter()
288            .chain(pack)
289            .collect::<Vec<_>>();
290        let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
291            .expect("test operation should succeed");
292
293        let parsed = PackFile::parse_bundle(&bundle).expect("test operation should succeed");
294        assert_eq!(parsed.entries.len(), 1);
295        assert_eq!(parsed.entries[0].object.object_type, ObjectType::Blob);
296        assert_eq!(parsed.entries[0].object.body, b"bundle\n");
297    }
298
299    /// Build a pack whose single blob entry header LIES about its decompressed
300    /// size: it declares `declared_size` while the actual zlib payload only
301    /// inflates to `real_body`. A short `real_body` plus a `declared_size` of
302    /// `u64::MAX` is the decompression-bomb shape — the header claims terabytes
303    /// from a handful of compressed bytes.
304    fn lying_size_blob_pack(format: ObjectFormat, declared_size: u64, real_body: &[u8]) -> Vec<u8> {
305        let mut pack = Vec::new();
306        pack.extend_from_slice(b"PACK");
307        pack.extend_from_slice(&2u32.to_be_bytes());
308        pack.extend_from_slice(&1u32.to_be_bytes());
309        // Object type 3 == blob; size varint encodes the *attacker-declared* size.
310        write_pack_entry_header_kind(&mut pack, 3, declared_size);
311        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
312        encoder
313            .write_all(real_body)
314            .expect("test operation should succeed");
315        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
316        let checksum =
317            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
318        pack.extend_from_slice(checksum.as_bytes());
319        pack
320    }
321
322    /// Regression: a crafted pack object header declaring a gigantic decompressed
323    /// size with a tiny compressed payload must NOT drive an up-front
324    /// reservation/allocation of that declared size (OOM/abort). sley#2: the
325    /// header `size` is attacker-controlled over the network (install_raw_pack →
326    /// sley-fetch), so it must be validated/bounded before any `Vec::reserve`.
327    ///
328    /// On the unfixed code, `inflate_into` did `out.reserve(header.size as usize)`
329    /// with `header.size == u64::MAX`, which panics with "capacity overflow" (or
330    /// aborts on alloc failure) *before* the size-mismatch check could fire. We
331    /// run parse on a worker thread so that panic surfaces as a `join()` error
332    /// rather than killing the test process; the fix turns this into a clean
333    /// `Err` returned normally.
334    #[test]
335    fn rejects_decompression_bomb_header_without_oom() {
336        for &declared in &[u64::MAX, 100 * 1024 * 1024 * 1024, u64::from(u32::MAX) * 4] {
337            let pack = lying_size_blob_pack(ObjectFormat::Sha1, declared, b"tiny\n");
338            let handle = std::thread::spawn(move || PackFile::parse_sha1(&pack));
339            let result = handle.join();
340            // The parse thread must not have panicked/aborted on a huge reserve.
341            assert!(
342                result.is_ok(),
343                "parsing a bomb header (declared={declared}) panicked instead of erroring cleanly"
344            );
345            // And parsing must reject the lie (decoded len != declared size).
346            let parse_result = result.expect("parse thread should not panic on a bomb header");
347            assert!(
348                parse_result.is_err(),
349                "bomb header (declared={declared}) should be rejected as invalid"
350            );
351        }
352    }
353
354    /// Build a 2-object pack: a real base blob followed by a delta (ref or ofs)
355    /// whose *result-size* varint lies, declaring `declared_result_size`, while
356    /// carrying a tiny real instruction stream. The delta's base-size varint is
357    /// set correctly (so the base-size check at the top of `apply_pack_delta`
358    /// passes and we reach the result reservation). Used to drive the sley#35
359    /// delta-result-size bomb.
360    fn lying_result_size_delta_pack(
361        format: ObjectFormat,
362        declared_result_size: u64,
363        delta_kind: DeltaKind,
364    ) -> Vec<u8> {
365        let base = b"hello";
366        let result = b"hello world"; // real produced length = 11
367
368        // Hand-build a delta with a truthful base-size and a LYING result-size.
369        let mut delta = Vec::new();
370        write_delta_varint(&mut delta, base.len() as u64);
371        write_delta_varint(&mut delta, declared_result_size);
372        // Real instructions: copy `base` then insert " world".
373        let suffix = &result[base.len()..];
374        delta.push(0x90); // copy, 1 size byte present (bit 0x10)
375        delta.push(base.len() as u8);
376        delta.push(suffix.len() as u8);
377        delta.extend_from_slice(suffix);
378
379        let mut pack = Vec::new();
380        pack.extend_from_slice(b"PACK");
381        pack.extend_from_slice(&2u32.to_be_bytes());
382        pack.extend_from_slice(&2u32.to_be_bytes());
383
384        let base_offset = pack.len();
385        write_entry_header(&mut pack, ObjectType::Blob, base.len() as u64);
386        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
387        encoder
388            .write_all(base)
389            .expect("test operation should succeed");
390        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
391
392        let delta_offset = pack.len();
393        write_pack_entry_header_kind(
394            &mut pack,
395            match delta_kind {
396                DeltaKind::Offset => 6,
397                DeltaKind::Ref => 7,
398            },
399            delta.len() as u64,
400        );
401        match delta_kind {
402            DeltaKind::Offset => write_ofs_delta_offset(&mut pack, delta_offset - base_offset),
403            DeltaKind::Ref => {
404                let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
405                    .expect("test operation should succeed");
406                pack.extend_from_slice(base_oid.as_bytes());
407            }
408        }
409        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
410        encoder
411            .write_all(&delta)
412            .expect("test operation should succeed");
413        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
414
415        let checksum =
416            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
417        pack.extend_from_slice(checksum.as_bytes());
418        pack
419    }
420
421    /// Regression (sley#35): the 2nd instance of the sley#2 decompression-bomb
422    /// class. `apply_pack_delta` read an attacker-controlled `result_size` varint
423    /// from a network delta and fed it straight to `Vec::with_capacity`. A tiny
424    /// delta declaring `result_size == u64::MAX` (or ~1 TiB) aborts the process
425    /// ("capacity overflow"/alloc failure, SIGABRT) BEFORE the post-decode
426    /// size-mismatch check can reject the lie. Both ref-delta and ofs-delta paths
427    /// reach the same reservation, so both must be safe. We resolve the pack on a
428    /// worker thread so an abort/panic surfaces as a `join()` error rather than
429    /// killing the whole test binary; the fix turns the bomb into a clean `Err`.
430    #[test]
431    fn rejects_delta_result_size_bomb_without_oom() {
432        let bombs: &[u64] = &[u64::MAX, 1024 * 1024 * 1024 * 1024];
433        for &declared in bombs {
434            for delta_kind in [DeltaKind::Ref, DeltaKind::Offset] {
435                let pack = lying_result_size_delta_pack(ObjectFormat::Sha1, declared, delta_kind);
436                let handle = std::thread::spawn(move || PackFile::parse_sha1(&pack));
437                let join_result = handle.join();
438                assert!(
439                    join_result.is_ok(),
440                    "delta bomb (declared={declared}, kind={delta_kind:?}) panicked/aborted \
441                     instead of erroring cleanly"
442                );
443                let parse_result =
444                    join_result.expect("parse thread should not panic on a delta bomb");
445                assert!(
446                    parse_result.is_err(),
447                    "delta bomb (declared={declared}, kind={delta_kind:?}) should be rejected \
448                     as invalid (result.len() != declared)"
449                );
450            }
451        }
452    }
453
454    /// A legitimate (truthful) delta whose result-size varint matches the real
455    /// produced length must still resolve correctly — the bound only caps the
456    /// speculative reservation, it must not break real delta application.
457    #[test]
458    fn applies_legitimate_delta_after_result_size_bound() {
459        for delta_kind in [DeltaKind::Ref, DeltaKind::Offset] {
460            let base = b"hello";
461            let result = b"hello world";
462            let pack = two_object_delta_pack(ObjectFormat::Sha1, base, result, delta_kind);
463            let parsed = PackFile::parse_sha1(&pack).expect("legitimate delta should resolve");
464            assert_eq!(parsed.entries.len(), 2);
465            assert_eq!(parsed.entries[0].object.body, base);
466            assert_eq!(parsed.entries[1].object.body, result);
467        }
468    }
469
470    #[test]
471    fn rejects_bundle_pack_payload_with_wrong_object_format() {
472        let pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"bundle\n");
473        let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha256, "blob", b"bundle\n")
474            .expect("test operation should succeed");
475        let bundle_bytes =
476            format!("# v3 git bundle\n@object-format=sha256\n{oid} refs/heads/main\n\n")
477                .into_bytes()
478                .into_iter()
479                .chain(pack)
480                .collect::<Vec<_>>();
481        let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
482            .expect("test operation should succeed");
483
484        assert!(PackFile::parse_bundle(&bundle).is_err());
485    }
486
487    fn assert_pack_index_view_matches_owned(index: &[u8], format: ObjectFormat) {
488        let owned = PackIndex::parse(index, format).expect("test operation should succeed");
489        let view = PackIndexView::parse(index, format).expect("test operation should succeed");
490        let owned_view =
491            PackIndexViewData::parse(Arc::from(index.to_vec().into_boxed_slice()), format)
492                .expect("test operation should succeed");
493
494        assert_eq!(view.version, owned.version);
495        assert_eq!(view.count, owned.entries.len());
496        assert_eq!(view.count(), owned.entries.len());
497        assert_eq!(view.fanout(), &owned.fanout);
498        assert_eq!(view.pack_checksum, owned.pack_checksum);
499        assert_eq!(view.index_checksum, owned.index_checksum);
500        assert_eq!(owned_view.version, owned.version);
501        assert_eq!(owned_view.count(), owned.entries.len());
502        assert_eq!(owned_view.fanout(), &owned.fanout);
503        assert_eq!(owned_view.pack_checksum, owned.pack_checksum);
504        assert_eq!(owned_view.index_checksum, owned.index_checksum);
505        for entry in &owned.entries {
506            let owned_found = owned
507                .find(&entry.oid)
508                .expect("test operation should succeed");
509            let expected = Some(PackIndexLookup {
510                crc32: owned_found.crc32,
511                offset: owned_found.offset,
512            });
513            assert_eq!(view.find(&entry.oid), expected);
514            assert_eq!(owned_view.find(&entry.oid), expected);
515        }
516    }
517
518    #[test]
519    fn writes_pack_and_index_that_round_trip() {
520        let object = EncodedObject::new(ObjectType::Blob, b"hello\n".to_vec());
521        let written = PackFile::write_undeltified_sha1(std::slice::from_ref(&object))
522            .expect("test operation should succeed");
523        let pack = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
524        let index =
525            PackIndex::parse_v2_sha1(&written.index).expect("test operation should succeed");
526        let oid = object
527            .object_id(ObjectFormat::Sha1)
528            .expect("test operation should succeed");
529        assert_eq!(pack.entries[0].object, object);
530        assert_eq!(index.pack_checksum, pack.checksum);
531        assert_eq!(
532            index
533                .find(&oid)
534                .expect("test operation should succeed")
535                .offset,
536            12
537        );
538    }
539
540    #[test]
541    fn pack_index_view_matches_owned_index_for_generated_sha1_pack() {
542        let objects = (0..8)
543            .map(|idx| {
544                EncodedObject::new(
545                    ObjectType::Blob,
546                    format!("borrowed pack index view sha1 object {idx}\n").into_bytes(),
547                )
548            })
549            .collect::<Vec<_>>();
550        let written = PackFile::write_packed(&objects, ObjectFormat::Sha1)
551            .expect("test operation should succeed");
552
553        assert_pack_index_view_matches_owned(&written.index, ObjectFormat::Sha1);
554
555        let view =
556            PackIndexView::parse_v2_sha1(&written.index).expect("test operation should succeed");
557        let missing = sley_core::object_id_for_bytes(
558            ObjectFormat::Sha1,
559            "blob",
560            b"not present in borrowed index\n",
561        )
562        .expect("test operation should succeed");
563        assert_eq!(view.find(&missing), None);
564    }
565
566    #[test]
567    fn writes_sha256_pack_and_index_that_round_trip() {
568        let object = EncodedObject::new(ObjectType::Blob, b"hello sha256\n".to_vec());
569        let written =
570            PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha256)
571                .expect("test operation should succeed");
572        let pack = PackFile::parse(&written.pack, ObjectFormat::Sha256)
573            .expect("test operation should succeed");
574        let index = PackIndex::parse(&written.index, ObjectFormat::Sha256)
575            .expect("test operation should succeed");
576        let oid = object
577            .object_id(ObjectFormat::Sha256)
578            .expect("test operation should succeed");
579        assert_eq!(pack.entries[0].object, object);
580        assert_eq!(index.pack_checksum, pack.checksum);
581        assert_eq!(index.pack_checksum.format(), ObjectFormat::Sha256);
582        assert_eq!(index.index_checksum.format(), ObjectFormat::Sha256);
583        assert_eq!(
584            index
585                .find(&oid)
586                .expect("test operation should succeed")
587                .offset,
588            12
589        );
590    }
591
592    #[test]
593    fn pack_index_view_matches_owned_index_for_generated_sha256_pack() {
594        let objects = (0..4)
595            .map(|idx| {
596                EncodedObject::new(
597                    ObjectType::Blob,
598                    format!("borrowed pack index view sha256 object {idx}\n").into_bytes(),
599                )
600            })
601            .collect::<Vec<_>>();
602        let written = PackFile::write_undeltified(&objects, ObjectFormat::Sha256)
603            .expect("test operation should succeed");
604
605        assert_pack_index_view_matches_owned(&written.index, ObjectFormat::Sha256);
606    }
607
608    #[test]
609    fn indexes_existing_sha256_pack_bytes() {
610        let object = EncodedObject::new(ObjectType::Blob, b"index raw sha256 pack\n".to_vec());
611        let written =
612            PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha256)
613                .expect("test operation should succeed");
614
615        let indexed = PackIndex::write_v2_for_pack(&written.pack, ObjectFormat::Sha256)
616            .expect("test operation should succeed");
617        let index = PackIndex::parse(&indexed.index, ObjectFormat::Sha256)
618            .expect("test operation should succeed");
619
620        assert_eq!(indexed.pack_checksum, written.checksum);
621        assert_eq!(indexed.entries, written.entries);
622        assert_eq!(index.pack_checksum, written.checksum);
623        assert_eq!(index.entries, written.entries);
624    }
625
626    #[test]
627    fn indexes_existing_delta_pack_bytes() {
628        let (base, changed) = similar_blob_objects();
629        let options = delta_pack_options(true);
630        let written = PackFile::write_packed_with_options(
631            &[base, changed.clone()],
632            ObjectFormat::Sha1,
633            &options,
634        )
635        .expect("test operation should succeed");
636
637        let indexed = PackIndex::write_v2_for_pack_sha1(&written.pack)
638            .expect("test operation should succeed");
639        let index =
640            PackIndex::parse_v2_sha1(&indexed.index).expect("test operation should succeed");
641        let changed_oid = changed
642            .object_id(ObjectFormat::Sha1)
643            .expect("test operation should succeed");
644
645        assert_eq!(indexed.pack_checksum, written.checksum);
646        assert_eq!(indexed.entries, written.entries);
647        assert_eq!(
648            index
649                .find(&changed_oid)
650                .expect("test operation should succeed")
651                .offset,
652            written.entries[1].offset
653        );
654        assert_eq!(
655            index
656                .find(&changed_oid)
657                .expect("test operation should succeed")
658                .crc32,
659            written.entries[1].crc32
660        );
661    }
662
663    #[test]
664    fn writes_ref_delta_pack_and_index_that_round_trip() {
665        let (base, changed) = similar_blob_objects();
666        let options = delta_pack_options(false);
667        let written = PackFile::write_packed_with_options(
668            &[base.clone(), changed.clone()],
669            ObjectFormat::Sha1,
670            &options,
671        )
672        .expect("test operation should succeed");
673        let mut second_offset = written.entries[1].offset as usize;
674        let header = parse_entry_header(&written.pack, &mut second_offset)
675            .expect("test operation should succeed");
676        assert_eq!(header.kind, PackObjectKind::RefDelta);
677
678        let pack = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
679        let index =
680            PackIndex::parse_v2_sha1(&written.index).expect("test operation should succeed");
681        let oid = changed
682            .object_id(ObjectFormat::Sha1)
683            .expect("test operation should succeed");
684        assert_eq!(pack.entries[0].object, base);
685        assert_eq!(pack.entries[1].object, changed);
686        assert_eq!(index.pack_checksum, pack.checksum);
687        assert_eq!(
688            index
689                .find(&oid)
690                .expect("test operation should succeed")
691                .offset,
692            written.entries[1].offset
693        );
694    }
695
696    #[test]
697    fn read_object_at_matches_full_parse_for_ofs_delta_pack() {
698        let (base, changed) = similar_blob_objects();
699        let options = delta_pack_options(true);
700        let written = PackFile::write_packed_with_options(
701            &[base, changed.clone()],
702            ObjectFormat::Sha1,
703            &options,
704        )
705        .expect("test operation should succeed");
706        // Ensure the pack genuinely contains an ofs-delta (else the test is vacuous).
707        let mut second = written.entries[1].offset as usize;
708        assert_eq!(
709            parse_entry_header(&written.pack, &mut second)
710                .expect("test operation should succeed")
711                .kind,
712            PackObjectKind::OfsDelta
713        );
714        // Ground truth from a full parse; single-object decode must match at every offset.
715        let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
716        for po in &parsed.entries {
717            let got =
718                read_object_at_arc(&written.pack, po.entry.offset, ObjectFormat::Sha1, |_| {
719                    Ok(None)
720                })
721                .expect("test operation should succeed");
722            assert_eq!(*got, po.object, "offset {}", po.entry.offset);
723        }
724    }
725
726    /// A [`HeaderTypeCache`] over a plain map, for asserting the cached header
727    /// read is byte-identical to the uncached one cold and warm (sley#26).
728    #[derive(Default)]
729    struct MapHeaderTypeCache(HashMap<u64, (ObjectType, u64)>);
730
731    impl HeaderTypeCache for MapHeaderTypeCache {
732        fn get(&self, pack_offset: u64) -> Option<(ObjectType, u64)> {
733            self.0.get(&pack_offset).copied()
734        }
735        fn put(&mut self, pack_offset: u64, header: (ObjectType, u64)) {
736            self.0.insert(pack_offset, header);
737        }
738    }
739
740    #[test]
741    fn read_object_header_at_cached_matches_uncached_cold_and_warm_for_ofs_delta() {
742        let (base, changed) = similar_blob_objects();
743        let options = delta_pack_options(true);
744        let written =
745            PackFile::write_packed_with_options(&[base, changed], ObjectFormat::Sha1, &options)
746                .expect("test operation should succeed");
747        // Ensure the pack genuinely contains an ofs-delta (else the test is vacuous).
748        let mut second = written.entries[1].offset as usize;
749        assert_eq!(
750            parse_entry_header(&written.pack, &mut second)
751                .expect("test operation should succeed")
752                .kind,
753            PackObjectKind::OfsDelta
754        );
755
756        let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
757        let mut cache = MapHeaderTypeCache::default();
758        for po in &parsed.entries {
759            let uncached =
760                read_object_header_at(&written.pack, po.entry.offset, ObjectFormat::Sha1, |_| {
761                    Ok(None)
762                })
763                .expect("test operation should succeed");
764            // Type inherited from the chain base; size is the inflated body length.
765            assert_eq!(
766                uncached,
767                (po.object.object_type, po.object.body.len() as u64),
768                "uncached header at offset {}",
769                po.entry.offset
770            );
771            // Cold cache: must agree with the uncached read and populate the memo.
772            let cold = read_object_header_at_with_cache(
773                &written.pack,
774                po.entry.offset,
775                ObjectFormat::Sha1,
776                |_| Ok(None),
777                &mut cache,
778            )
779            .expect("test operation should succeed");
780            assert_eq!(cold, uncached, "cold cache at offset {}", po.entry.offset);
781        }
782        // Warm cache: every offset now resolves from the memo and is still correct,
783        // proving the fast path does not change behavior (sley#26).
784        for po in &parsed.entries {
785            let warm = read_object_header_at_with_cache(
786                &written.pack,
787                po.entry.offset,
788                ObjectFormat::Sha1,
789                |_| panic!("warm cache must not re-walk the chain"),
790                &mut cache,
791            )
792            .expect("test operation should succeed");
793            assert_eq!(
794                warm,
795                (po.object.object_type, po.object.body.len() as u64),
796                "warm cache at offset {}",
797                po.entry.offset
798            );
799        }
800    }
801
802    #[test]
803    fn read_object_at_matches_full_parse_for_ref_delta_pack() {
804        let (base, changed) = similar_blob_objects();
805        let options = delta_pack_options(false);
806        let written = PackFile::write_packed_with_options(
807            &[base, changed.clone()],
808            ObjectFormat::Sha1,
809            &options,
810        )
811        .expect("test operation should succeed");
812        let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
813        let by_oid: HashMap<ObjectId, Arc<EncodedObject>> = parsed
814            .entries
815            .iter()
816            .map(|po| (po.entry.oid, Arc::new(po.object.clone())))
817            .collect();
818        for po in &parsed.entries {
819            let got =
820                read_object_at_arc(&written.pack, po.entry.offset, ObjectFormat::Sha1, |oid| {
821                    Ok(by_oid.get(oid).cloned())
822                })
823                .expect("test operation should succeed");
824            assert_eq!(*got, po.object);
825        }
826    }
827
828    /// A test-only [`PackDeltaCache`] that records every decode and counts hits,
829    /// used to prove the cached decode path is byte-identical to the uncached
830    /// one and that bases are reused across reads.
831    #[derive(Default)]
832    struct CountingDeltaCache {
833        map: std::cell::RefCell<HashMap<u64, Arc<EncodedObject>>>,
834        hits: std::cell::Cell<usize>,
835        inserts: std::cell::Cell<usize>,
836    }
837
838    impl PackDeltaCache for CountingDeltaCache {
839        fn get(&self, offset: u64) -> Option<Arc<EncodedObject>> {
840            let hit = self.map.borrow().get(&offset).cloned();
841            if hit.is_some() {
842                self.hits.set(self.hits.get() + 1);
843            }
844            hit
845        }
846        fn insert(&self, offset: u64, object: Arc<EncodedObject>) {
847            self.inserts.set(self.inserts.get() + 1);
848            self.map.borrow_mut().insert(offset, object);
849        }
850    }
851
852    #[test]
853    fn read_object_at_with_cache_matches_uncached_and_reuses_bases() {
854        // A multi-object pack with a real ofs-delta chain so the cache has bases
855        // to reuse. Build several similar blobs to encourage deltification.
856        let mut objects = Vec::new();
857        for idx in 0..8u32 {
858            let mut body = vec![b'x'; 4096];
859            body.extend_from_slice(format!("\nvariant {idx}\n").as_bytes());
860            objects.push(EncodedObject::new(ObjectType::Blob, body));
861        }
862        let options = delta_pack_options(true);
863        let written = PackFile::write_packed_with_options(&objects, ObjectFormat::Sha1, &options)
864            .expect("test operation should succeed");
865        let parsed = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
866
867        let cache = CountingDeltaCache::default();
868        // Read every object twice through the cache; each result must equal the
869        // ground-truth from the full parse, byte for byte, both times.
870        for _ in 0..2 {
871            for po in &parsed.entries {
872                let got = read_object_at_with_cache_arc(
873                    &written.pack,
874                    po.entry.offset,
875                    ObjectFormat::Sha1,
876                    |_| Ok(None),
877                    &cache,
878                )
879                .expect("test operation should succeed");
880                assert_eq!(*got, po.object, "offset {}", po.entry.offset);
881            }
882        }
883        // The second pass reads everything straight from the cache, so there must
884        // be at least one hit (proving reuse, not just correctness).
885        assert!(cache.hits.get() > 0, "cache never served a warm object");
886    }
887
888    #[test]
889    fn writes_ofs_delta_pack_and_index_that_round_trip() {
890        let (base, changed) = similar_blob_objects();
891        let options = delta_pack_options(true);
892        let written = PackFile::write_packed_with_options(
893            &[base.clone(), changed.clone()],
894            ObjectFormat::Sha1,
895            &options,
896        )
897        .expect("test operation should succeed");
898        let mut second_offset = written.entries[1].offset as usize;
899        let header = parse_entry_header(&written.pack, &mut second_offset)
900            .expect("test operation should succeed");
901        assert_eq!(header.kind, PackObjectKind::OfsDelta);
902
903        let pack = PackFile::parse_sha1(&written.pack).expect("test operation should succeed");
904        let index =
905            PackIndex::parse_v2_sha1(&written.index).expect("test operation should succeed");
906        let oid = changed
907            .object_id(ObjectFormat::Sha1)
908            .expect("test operation should succeed");
909        assert_eq!(pack.entries[0].object, base);
910        assert_eq!(pack.entries[1].object, changed);
911        assert_eq!(index.pack_checksum, pack.checksum);
912        assert_eq!(
913            index
914                .find(&oid)
915                .expect("test operation should succeed")
916                .offset,
917            written.entries[1].offset
918        );
919    }
920
921    #[test]
922    fn resolves_ofs_delta_pack_entry() {
923        let base = b"hello";
924        let result = b"hello world";
925        let pack = two_object_delta_pack(ObjectFormat::Sha1, base, result, DeltaKind::Offset);
926        let parsed = PackFile::parse_sha1(&pack).expect("test operation should succeed");
927        assert_eq!(parsed.entries.len(), 2);
928        assert_eq!(parsed.entries[0].object.body, base);
929        assert_eq!(parsed.entries[1].object.body, result);
930        assert_eq!(
931            parsed.entries[1].entry.oid,
932            sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
933                .expect("test operation should succeed")
934        );
935    }
936
937    #[test]
938    fn resolves_ref_delta_pack_entry() {
939        let base = b"hello";
940        let result = b"hello world";
941        let pack = two_object_delta_pack(ObjectFormat::Sha1, base, result, DeltaKind::Ref);
942        let parsed = PackFile::parse_sha1(&pack).expect("test operation should succeed");
943        assert_eq!(parsed.entries.len(), 2);
944        assert_eq!(parsed.entries[0].object.body, base);
945        assert_eq!(parsed.entries[1].object.body, result);
946        assert_eq!(
947            parsed.entries[1].entry.oid,
948            sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
949                .expect("test operation should succeed")
950        );
951    }
952
953    #[test]
954    fn resolves_thin_ref_delta_pack_entry_with_external_base() {
955        let base = b"hello";
956        let result = b"hello world";
957        let pack = thin_ref_delta_pack(ObjectFormat::Sha1, base, result);
958        assert!(PackFile::parse_sha1(&pack).is_err());
959
960        let base_oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", base)
961            .expect("test operation should succeed");
962        let parsed = PackFile::parse_thin(&pack, ObjectFormat::Sha1, |oid| {
963            if oid == &base_oid {
964                Ok(Some(EncodedObject::new(ObjectType::Blob, base.to_vec())))
965            } else {
966                Ok(None)
967            }
968        })
969        .expect("test operation should succeed");
970        assert_eq!(parsed.entries.len(), 1);
971        assert_eq!(parsed.entries[0].object.body, result);
972        assert_eq!(
973            parsed.entries[0].entry.oid,
974            sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", result)
975                .expect("test operation should succeed")
976        );
977    }
978
979    #[test]
980    fn rejects_bad_pack_checksum() {
981        let mut pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"hello\n");
982        let last = pack.len() - 1;
983        pack[last] ^= 1;
984        assert!(PackFile::parse_sha1(&pack).is_err());
985    }
986
987    #[test]
988    fn raw_pack_index_rejects_bad_pack_checksum() {
989        let mut pack = single_object_pack(ObjectFormat::Sha1, ObjectType::Blob, b"hello\n");
990        let last = pack.len() - 1;
991        pack[last] ^= 1;
992        assert!(PackIndex::write_v2_for_pack_sha1(&pack).is_err());
993    }
994
995    #[test]
996    fn pack_index_writer_rejects_duplicate_object_ids() {
997        let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"same\n")
998            .expect("test operation should succeed");
999        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1000            .expect("test operation should succeed");
1001        let entries = vec![
1002            PackIndexEntry {
1003                oid,
1004                crc32: 1,
1005                offset: 12,
1006            },
1007            PackIndexEntry {
1008                oid,
1009                crc32: 2,
1010                offset: 24,
1011            },
1012        ];
1013        assert!(PackIndex::write_v2(ObjectFormat::Sha1, &entries, &pack_checksum).is_err());
1014    }
1015
1016    #[test]
1017    fn parses_single_entry_pack_index() {
1018        let oid = ObjectId::from_hex(
1019            ObjectFormat::Sha1,
1020            "ce013625030ba8dba906f756967f9e9ca394464a",
1021        )
1022        .expect("test operation should succeed");
1023        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1024            .expect("test operation should succeed");
1025        let index = single_entry_index(
1026            ObjectFormat::Sha1,
1027            oid,
1028            0x1234_5678,
1029            12,
1030            pack_checksum.clone(),
1031        );
1032        let parsed = PackIndex::parse_v2_sha1(&index).expect("test operation should succeed");
1033        assert_eq!(parsed.version, 2);
1034        assert_eq!(parsed.pack_checksum, pack_checksum);
1035        assert_eq!(parsed.entries.len(), 1);
1036        assert_eq!(
1037            parsed
1038                .find(&oid)
1039                .expect("test operation should succeed")
1040                .offset,
1041            12
1042        );
1043        assert_eq!(
1044            parsed
1045                .find(&oid)
1046                .expect("test operation should succeed")
1047                .crc32,
1048            0x1234_5678
1049        );
1050        assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha1);
1051    }
1052
1053    #[test]
1054    fn parses_single_entry_pack_index_v1() {
1055        let oid = ObjectId::from_hex(
1056            ObjectFormat::Sha1,
1057            "ce013625030ba8dba906f756967f9e9ca394464a",
1058        )
1059        .expect("test operation should succeed");
1060        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1061            .expect("test operation should succeed");
1062        let index =
1063            single_entry_index_v1(ObjectFormat::Sha1, oid, 0x1234_5678, pack_checksum.clone());
1064        let parsed =
1065            PackIndex::parse(&index, ObjectFormat::Sha1).expect("test operation should succeed");
1066        assert_eq!(parsed.version, 1);
1067        assert_eq!(parsed.pack_checksum, pack_checksum);
1068        assert_eq!(parsed.entries.len(), 1);
1069        assert_eq!(
1070            parsed
1071                .find(&oid)
1072                .expect("test operation should succeed")
1073                .offset,
1074            0x1234_5678
1075        );
1076        assert_eq!(
1077            parsed
1078                .find(&oid)
1079                .expect("test operation should succeed")
1080                .crc32,
1081            0
1082        );
1083        assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha1);
1084    }
1085
1086    #[test]
1087    fn rejects_bad_pack_index_v1_checksum() {
1088        let oid = ObjectId::from_hex(
1089            ObjectFormat::Sha1,
1090            "ce013625030ba8dba906f756967f9e9ca394464a",
1091        )
1092        .expect("test operation should succeed");
1093        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1094            .expect("test operation should succeed");
1095        let mut index = single_entry_index_v1(ObjectFormat::Sha1, oid, 12, pack_checksum);
1096        let last = index.len() - 1;
1097        index[last] ^= 1;
1098        assert!(PackIndex::parse(&index, ObjectFormat::Sha1).is_err());
1099    }
1100
1101    #[test]
1102    fn pack_index_view_reads_v2_large_offsets() {
1103        let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"large offset a\n")
1104            .expect("test operation should succeed");
1105        let second =
1106            sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"large offset b\n")
1107                .expect("test operation should succeed");
1108        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1109            .expect("test operation should succeed");
1110        let entries = vec![
1111            PackIndexEntry {
1112                oid: first,
1113                crc32: 0x1111_2222,
1114                offset: 0x8000_0000,
1115            },
1116            PackIndexEntry {
1117                oid: second,
1118                crc32: 0x3333_4444,
1119                offset: 0x1_0000_0042,
1120            },
1121        ];
1122        let index = PackIndex::write_v2(ObjectFormat::Sha1, &entries, &pack_checksum)
1123            .expect("test operation should succeed");
1124
1125        assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha1);
1126        let view = PackIndexView::parse(&index, ObjectFormat::Sha1)
1127            .expect("test operation should succeed");
1128        for entry in entries {
1129            assert_eq!(
1130                view.find(&entry.oid),
1131                Some(PackIndexLookup {
1132                    crc32: entry.crc32,
1133                    offset: entry.offset,
1134                })
1135            );
1136        }
1137    }
1138
1139    #[test]
1140    fn pack_index_view_default_parse_checks_index_checksum() {
1141        let oid = ObjectId::from_hex(
1142            ObjectFormat::Sha1,
1143            "ce013625030ba8dba906f756967f9e9ca394464a",
1144        )
1145        .expect("test operation should succeed");
1146        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1147            .expect("test operation should succeed");
1148        let mut index = single_entry_index(ObjectFormat::Sha1, oid, 0x1234_5678, 12, pack_checksum);
1149        let last = index.len() - 1;
1150        index[last] ^= 1;
1151
1152        assert!(PackIndexView::parse(&index, ObjectFormat::Sha1).is_err());
1153        let view = PackIndexView::parse_without_checksum(&index, ObjectFormat::Sha1)
1154            .expect("test operation should succeed");
1155        let trusted_view = PackIndexViewData::parse_trusted_without_checksum(
1156            Arc::from(index.clone().into_boxed_slice()),
1157            ObjectFormat::Sha1,
1158        )
1159        .expect("test operation should succeed");
1160        assert_eq!(
1161            view.find(&oid),
1162            Some(PackIndexLookup {
1163                crc32: 0x1234_5678,
1164                offset: 12,
1165            })
1166        );
1167        assert_eq!(
1168            trusted_view.find(&oid),
1169            Some(PackIndexLookup {
1170                crc32: 0x1234_5678,
1171                offset: 12,
1172            })
1173        );
1174    }
1175
1176    #[test]
1177    fn reverse_index_resolves_oid_at_offset() {
1178        let objects = (0..3)
1179            .map(|idx| {
1180                EncodedObject::new(
1181                    ObjectType::Blob,
1182                    format!("reverse index lookup object {idx}\n").into_bytes(),
1183                )
1184            })
1185            .collect::<Vec<_>>();
1186        let written = PackFile::write_packed(&objects, ObjectFormat::Sha1)
1187            .expect("test operation should succeed");
1188        let index = PackIndex::parse(&written.index, ObjectFormat::Sha1)
1189            .expect("test operation should succeed");
1190        let view = PackIndexViewData::parse_trusted_without_checksum(
1191            Arc::from(written.index.clone().into_boxed_slice()),
1192            ObjectFormat::Sha1,
1193        )
1194        .expect("test operation should succeed");
1195        let positions = pack_order_index_positions(&index.entries);
1196        let reverse = PackReverseIndex::parse(
1197            &PackReverseIndex::write(ObjectFormat::Sha1, &positions, &index.pack_checksum)
1198                .expect("test operation should succeed"),
1199            ObjectFormat::Sha1,
1200            index.entries.len(),
1201        )
1202        .expect("test operation should succeed");
1203
1204        for entry in &index.entries {
1205            assert_eq!(
1206                reverse
1207                    .oid_at_offset(&view, entry.offset)
1208                    .expect("test operation should succeed"),
1209                entry.oid
1210            );
1211        }
1212        assert!(reverse.oid_at_offset(&view, 999).is_none());
1213    }
1214
1215    #[test]
1216    fn parses_pack_reverse_index() {
1217        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1218            .expect("test operation should succeed");
1219        let reverse_index = PackReverseIndex::write(ObjectFormat::Sha1, &[2, 0, 1], &pack_checksum)
1220            .expect("test operation should succeed");
1221        let parsed = PackReverseIndex::parse(&reverse_index, ObjectFormat::Sha1, 3)
1222            .expect("test operation should succeed");
1223        assert_eq!(parsed.version, 1);
1224        assert_eq!(parsed.format, ObjectFormat::Sha1);
1225        assert_eq!(parsed.positions, vec![2, 0, 1]);
1226        assert_eq!(parsed.pack_checksum, pack_checksum);
1227        assert_eq!(
1228            PackReverseIndex::write(ObjectFormat::Sha1, &parsed.positions, &parsed.pack_checksum)
1229                .expect("test operation should succeed"),
1230            reverse_index
1231        );
1232    }
1233
1234    #[test]
1235    fn rejects_bad_pack_reverse_index_checksum() {
1236        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1237            .expect("test operation should succeed");
1238        let mut reverse_index = PackReverseIndex::write(ObjectFormat::Sha1, &[0], &pack_checksum)
1239            .expect("test operation should succeed");
1240        let last = reverse_index.len() - 1;
1241        reverse_index[last] ^= 1;
1242        assert!(PackReverseIndex::parse(&reverse_index, ObjectFormat::Sha1, 1).is_err());
1243    }
1244
1245    #[test]
1246    fn rejects_bad_pack_reverse_index_positions() {
1247        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1248            .expect("test operation should succeed");
1249        let duplicate = pack_reverse_index(ObjectFormat::Sha1, &[0, 0], pack_checksum.clone());
1250        assert!(PackReverseIndex::parse(&duplicate, ObjectFormat::Sha1, 2).is_err());
1251        let out_of_range = pack_reverse_index(ObjectFormat::Sha1, &[0, 2], pack_checksum);
1252        assert!(PackReverseIndex::parse(&out_of_range, ObjectFormat::Sha1, 2).is_err());
1253        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1254            .expect("test operation should succeed");
1255        assert!(PackReverseIndex::write(ObjectFormat::Sha1, &[0, 0], &pack_checksum).is_err());
1256        assert!(PackReverseIndex::write(ObjectFormat::Sha1, &[0, 2], &pack_checksum).is_err());
1257    }
1258
1259    #[test]
1260    fn parses_pack_mtimes() {
1261        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1262            .expect("test operation should succeed");
1263        let mtimes = PackMtimes::write(
1264            ObjectFormat::Sha1,
1265            &[1, 1_700_000_000, u32::MAX],
1266            &pack_checksum,
1267        )
1268        .expect("test operation should succeed");
1269        let parsed = PackMtimes::parse(&mtimes, ObjectFormat::Sha1, 3)
1270            .expect("test operation should succeed");
1271        assert_eq!(parsed.version, 1);
1272        assert_eq!(parsed.format, ObjectFormat::Sha1);
1273        assert_eq!(parsed.mtimes, vec![1, 1_700_000_000, u32::MAX]);
1274        assert_eq!(parsed.pack_checksum, pack_checksum);
1275        assert_eq!(
1276            PackMtimes::write(ObjectFormat::Sha1, &parsed.mtimes, &parsed.pack_checksum)
1277                .expect("test operation should succeed"),
1278            mtimes
1279        );
1280    }
1281
1282    #[test]
1283    fn rejects_bad_pack_mtimes_checksum() {
1284        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1285            .expect("test operation should succeed");
1286        let mut mtimes = PackMtimes::write(ObjectFormat::Sha1, &[1], &pack_checksum)
1287            .expect("test operation should succeed");
1288        let last = mtimes.len() - 1;
1289        mtimes[last] ^= 1;
1290        assert!(PackMtimes::parse(&mtimes, ObjectFormat::Sha1, 1).is_err());
1291    }
1292
1293    #[test]
1294    fn rejects_bad_pack_mtimes_shape() {
1295        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1296            .expect("test operation should succeed");
1297        let mtimes = pack_mtimes(ObjectFormat::Sha1, &[1, 2], pack_checksum.clone());
1298        assert!(PackMtimes::parse(&mtimes, ObjectFormat::Sha1, 1).is_err());
1299
1300        let mut wrong_hash = pack_mtimes(ObjectFormat::Sha1, &[1], pack_checksum);
1301        wrong_hash[11] = 2;
1302        let checksum_offset = wrong_hash.len() - ObjectFormat::Sha1.raw_len();
1303        let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &wrong_hash[..checksum_offset])
1304            .expect("test operation should succeed");
1305        wrong_hash[checksum_offset..].copy_from_slice(checksum.as_bytes());
1306        assert!(PackMtimes::parse(&wrong_hash, ObjectFormat::Sha1, 1).is_err());
1307    }
1308
1309    #[test]
1310    fn parses_multi_pack_index_header_and_chunk_lookup() {
1311        let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
1312            .expect("test operation should succeed");
1313        let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
1314            .expect("test operation should succeed");
1315        let chunks = midx_chunks_with_pack_names(
1316            ObjectFormat::Sha1,
1317            b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
1318            &[(first.clone(), 0, 12), (second.clone(), 1, 0x1_0000_0000)],
1319        );
1320        let midx = multi_pack_index(ObjectFormat::Sha1, 2, 2, &chunks);
1321        let parsed = MultiPackIndex::parse(&midx, ObjectFormat::Sha1)
1322            .expect("test operation should succeed");
1323        assert_eq!(parsed.version, 2);
1324        assert_eq!(parsed.format, ObjectFormat::Sha1);
1325        assert_eq!(parsed.pack_count, 2);
1326        assert_eq!(parsed.pack_names, vec!["pack-a.idx", "pack-b.idx"]);
1327        assert_eq!(parsed.object_count, 2);
1328        assert_eq!(parsed.objects.len(), 2);
1329        assert_eq!(
1330            parsed
1331                .find(&first)
1332                .expect("test operation should succeed")
1333                .pack_int_id,
1334            0
1335        );
1336        assert_eq!(
1337            parsed
1338                .find(&first)
1339                .expect("test operation should succeed")
1340                .offset,
1341            12
1342        );
1343        assert_eq!(
1344            parsed
1345                .find(&second)
1346                .expect("test operation should succeed")
1347                .pack_int_id,
1348            1
1349        );
1350        assert_eq!(
1351            parsed
1352                .find(&second)
1353                .expect("test operation should succeed")
1354                .offset,
1355            0x1_0000_0000
1356        );
1357        assert_eq!(parsed.reverse_index, None);
1358        assert_eq!(parsed.bitmapped_packs, None);
1359        assert_eq!(parsed.chunks.len(), 5);
1360        assert_eq!(parsed.chunks[0].id, *b"PNAM");
1361        assert_eq!(parsed.chunks[0].offset, 84);
1362        assert_eq!(parsed.chunks[0].len, 24);
1363        assert_eq!(parsed.chunks[1].id, *b"OIDF");
1364        assert_eq!(parsed.chunks[1].offset, 108);
1365        assert_eq!(parsed.chunks[1].len, 1024);
1366    }
1367
1368    #[test]
1369    fn raw_multi_pack_index_lookup_finds_pack_and_offset() {
1370        let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
1371            .expect("test operation should succeed");
1372        let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
1373            .expect("test operation should succeed");
1374        let missing = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"missing\n")
1375            .expect("test operation should succeed");
1376        let chunks = midx_chunks_with_pack_names(
1377            ObjectFormat::Sha1,
1378            b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
1379            &[(first.clone(), 0, 12), (second.clone(), 1, 0x1_0000_0000)],
1380        );
1381        let midx = Arc::new(multi_pack_index(ObjectFormat::Sha1, 2, 2, &chunks));
1382        let lookup = MultiPackIndexOidLookup::parse(midx, ObjectFormat::Sha1)
1383            .expect("test operation should succeed");
1384
1385        assert!(lookup.contains(&first));
1386        assert!(lookup.contains(&second));
1387        assert!(!lookup.contains(&missing));
1388
1389        let first_entry = lookup
1390            .find(&first)
1391            .expect("test operation should succeed")
1392            .expect("object should be present");
1393        assert_eq!(
1394            lookup.pack_name(first_entry.pack_int_id),
1395            Some("pack-a.idx")
1396        );
1397        assert_eq!(first_entry.offset, 12);
1398
1399        let second_entry = lookup
1400            .find(&second)
1401            .expect("test operation should succeed")
1402            .expect("object should be present");
1403        assert_eq!(
1404            lookup.pack_name(second_entry.pack_int_id),
1405            Some("pack-b.idx")
1406        );
1407        assert_eq!(second_entry.offset, 0x1_0000_0000);
1408        assert!(
1409            lookup
1410                .find(&missing)
1411                .expect("test operation should succeed")
1412                .is_none()
1413        );
1414    }
1415
1416    #[test]
1417    fn rejects_bad_multi_pack_index_checksum() {
1418        let chunks = midx_chunks_with_pack_names(ObjectFormat::Sha1, Vec::new(), &[]);
1419        let mut midx = multi_pack_index(ObjectFormat::Sha1, 1, 0, &chunks);
1420        let last = midx.len() - 1;
1421        midx[last] ^= 1;
1422        assert!(MultiPackIndex::parse(&midx, ObjectFormat::Sha1).is_err());
1423    }
1424
1425    #[test]
1426    fn rejects_bad_multi_pack_index_shape() {
1427        let chunks = midx_chunks_with_pack_names(ObjectFormat::Sha1, Vec::new(), &[]);
1428        let mut wrong_hash = multi_pack_index(ObjectFormat::Sha1, 1, 0, &chunks);
1429        wrong_hash[5] = 2;
1430        let checksum_offset = wrong_hash.len() - ObjectFormat::Sha1.raw_len();
1431        let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &wrong_hash[..checksum_offset])
1432            .expect("test operation should succeed");
1433        wrong_hash[checksum_offset..].copy_from_slice(checksum.as_bytes());
1434        assert!(MultiPackIndex::parse(&wrong_hash, ObjectFormat::Sha1).is_err());
1435
1436        let mut missing_terminator = multi_pack_index(ObjectFormat::Sha1, 1, 0, &chunks);
1437        missing_terminator[12] = b'B';
1438        let checksum_offset = missing_terminator.len() - ObjectFormat::Sha1.raw_len();
1439        let checksum =
1440            sley_core::digest_bytes(ObjectFormat::Sha1, &missing_terminator[..checksum_offset])
1441                .expect("test operation should succeed");
1442        missing_terminator[checksum_offset..].copy_from_slice(checksum.as_bytes());
1443        assert!(MultiPackIndex::parse(&missing_terminator, ObjectFormat::Sha1).is_err());
1444
1445        let mut bad_offset = multi_pack_index(
1446            ObjectFormat::Sha1,
1447            2,
1448            0,
1449            &midx_chunks_with_pack_names(ObjectFormat::Sha1, Vec::new(), &[]),
1450        );
1451        bad_offset[16..24].copy_from_slice(&0u64.to_be_bytes());
1452        let checksum_offset = bad_offset.len() - ObjectFormat::Sha1.raw_len();
1453        let checksum = sley_core::digest_bytes(ObjectFormat::Sha1, &bad_offset[..checksum_offset])
1454            .expect("test operation should succeed");
1455        bad_offset[checksum_offset..].copy_from_slice(checksum.as_bytes());
1456        assert!(MultiPackIndex::parse(&bad_offset, ObjectFormat::Sha1).is_err());
1457    }
1458
1459    #[test]
1460    fn rejects_bad_multi_pack_index_pack_names() {
1461        let missing = multi_pack_index(ObjectFormat::Sha1, 2, 1, &[]);
1462        assert!(MultiPackIndex::parse(&missing, ObjectFormat::Sha1).is_err());
1463
1464        let too_few = multi_pack_index(
1465            ObjectFormat::Sha1,
1466            2,
1467            2,
1468            &midx_chunks_with_pack_names(ObjectFormat::Sha1, b"pack-a.idx\0".to_vec(), &[]),
1469        );
1470        assert!(MultiPackIndex::parse(&too_few, ObjectFormat::Sha1).is_err());
1471
1472        let bad_padding = multi_pack_index(
1473            ObjectFormat::Sha1,
1474            2,
1475            1,
1476            &midx_chunks_with_pack_names(ObjectFormat::Sha1, b"pack-a.idx\0xxxx".to_vec(), &[]),
1477        );
1478        assert!(MultiPackIndex::parse(&bad_padding, ObjectFormat::Sha1).is_err());
1479
1480        let unsorted_v1 = multi_pack_index(
1481            ObjectFormat::Sha1,
1482            1,
1483            2,
1484            &midx_chunks_with_pack_names(
1485                ObjectFormat::Sha1,
1486                b"pack-b.idx\0pack-a.idx\0".to_vec(),
1487                &[],
1488            ),
1489        );
1490        assert!(MultiPackIndex::parse(&unsorted_v1, ObjectFormat::Sha1).is_err());
1491
1492        let unsorted_v2 = multi_pack_index(
1493            ObjectFormat::Sha1,
1494            2,
1495            2,
1496            &midx_chunks_with_pack_names(
1497                ObjectFormat::Sha1,
1498                b"pack-b.idx\0pack-a.idx\0".to_vec(),
1499                &[],
1500            ),
1501        );
1502        let parsed = MultiPackIndex::parse(&unsorted_v2, ObjectFormat::Sha1)
1503            .expect("test operation should succeed");
1504        assert_eq!(parsed.pack_names, vec!["pack-b.idx", "pack-a.idx"]);
1505    }
1506
1507    #[test]
1508    fn rejects_bad_multi_pack_index_object_tables() {
1509        let oid_a = ObjectId::from_hex(
1510            ObjectFormat::Sha1,
1511            "1111111111111111111111111111111111111111",
1512        )
1513        .expect("test operation should succeed");
1514        let oid_b = ObjectId::from_hex(
1515            ObjectFormat::Sha1,
1516            "2222222222222222222222222222222222222222",
1517        )
1518        .expect("test operation should succeed");
1519
1520        let missing_oidf = multi_pack_index(
1521            ObjectFormat::Sha1,
1522            2,
1523            1,
1524            &[(*b"PNAM", b"pack-a.idx\0\0".to_vec())],
1525        );
1526        assert!(MultiPackIndex::parse(&missing_oidf, ObjectFormat::Sha1).is_err());
1527
1528        let bad_fanout = vec![
1529            (*b"PNAM", b"pack-a.idx\0\0".to_vec()),
1530            (*b"OIDF", vec![0; 256 * 4]),
1531            (*b"OIDL", oid_a.as_bytes().to_vec()),
1532            (*b"OOFF", midx_ooff_entries(&[(0, 12)], &mut Vec::new())),
1533        ];
1534        let bad_fanout = multi_pack_index(ObjectFormat::Sha1, 2, 1, &bad_fanout);
1535        assert!(MultiPackIndex::parse(&bad_fanout, ObjectFormat::Sha1).is_err());
1536
1537        let mut unsorted = Vec::new();
1538        unsorted.push((*b"PNAM", b"pack-a.idx\0\0".to_vec()));
1539        unsorted.push((*b"OIDF", midx_oid_fanout(&[oid_a.clone(), oid_b.clone()])));
1540        let mut oid_lookup = Vec::new();
1541        oid_lookup.extend_from_slice(oid_b.as_bytes());
1542        oid_lookup.extend_from_slice(oid_a.as_bytes());
1543        unsorted.push((*b"OIDL", oid_lookup));
1544        unsorted.push((
1545            *b"OOFF",
1546            midx_ooff_entries(&[(0, 12), (0, 24)], &mut Vec::new()),
1547        ));
1548        let unsorted = multi_pack_index(ObjectFormat::Sha1, 2, 1, &unsorted);
1549        assert!(MultiPackIndex::parse(&unsorted, ObjectFormat::Sha1).is_err());
1550
1551        let bad_pack = multi_pack_index(
1552            ObjectFormat::Sha1,
1553            2,
1554            1,
1555            &midx_chunks_with_pack_names(
1556                ObjectFormat::Sha1,
1557                b"pack-a.idx\0\0".to_vec(),
1558                &[(oid_a.clone(), 1, 12)],
1559            ),
1560        );
1561        assert!(MultiPackIndex::parse(&bad_pack, ObjectFormat::Sha1).is_err());
1562
1563        let mut large_offsets = Vec::new();
1564        let missing_loff = vec![
1565            (*b"PNAM", b"pack-a.idx\0\0".to_vec()),
1566            (*b"OIDF", midx_oid_fanout(std::slice::from_ref(&oid_a))),
1567            (*b"OIDL", oid_a.as_bytes().to_vec()),
1568            (
1569                *b"OOFF",
1570                midx_ooff_entries(&[(0, 0x1_0000_0000)], &mut large_offsets),
1571            ),
1572        ];
1573        let missing_loff = multi_pack_index(ObjectFormat::Sha1, 2, 1, &missing_loff);
1574        assert!(MultiPackIndex::parse(&missing_loff, ObjectFormat::Sha1).is_err());
1575
1576        let mut bad_loff =
1577            midx_chunks_with_pack_names(ObjectFormat::Sha1, b"pack-a.idx\0\0".to_vec(), &[]);
1578        bad_loff.push((*b"LOFF", vec![0]));
1579        let bad_loff = multi_pack_index(ObjectFormat::Sha1, 2, 1, &bad_loff);
1580        assert!(MultiPackIndex::parse(&bad_loff, ObjectFormat::Sha1).is_err());
1581    }
1582
1583    #[test]
1584    fn parses_multi_pack_index_bitmap_chunks() {
1585        let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
1586            .expect("test operation should succeed");
1587        let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
1588            .expect("test operation should succeed");
1589        let mut chunks = midx_chunks_with_pack_names(
1590            ObjectFormat::Sha1,
1591            b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
1592            &[(first, 0, 12), (second, 1, 24)],
1593        );
1594        chunks.push((*b"RIDX", midx_u32_table(&[1, 0])));
1595        chunks.push((*b"BTMP", midx_bitmap_packs(&[(0, 1), (1, 1)])));
1596        let midx = multi_pack_index(ObjectFormat::Sha1, 2, 2, &chunks);
1597
1598        let parsed = MultiPackIndex::parse(&midx, ObjectFormat::Sha1)
1599            .expect("test operation should succeed");
1600        assert_eq!(parsed.reverse_index, Some(vec![1, 0]));
1601        assert_eq!(
1602            parsed.bitmapped_packs,
1603            Some(vec![
1604                MultiPackBitmapPack {
1605                    bitmap_pos: 0,
1606                    bitmap_nr: 1,
1607                },
1608                MultiPackBitmapPack {
1609                    bitmap_pos: 1,
1610                    bitmap_nr: 1,
1611                },
1612            ])
1613        );
1614    }
1615
1616    #[test]
1617    fn writes_multi_pack_index_that_round_trips() {
1618        let first = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"first object\n")
1619            .expect("test operation should succeed");
1620        let second = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"second object\n")
1621            .expect("test operation should succeed");
1622        let bytes = MultiPackIndex::write(
1623            ObjectFormat::Sha1,
1624            2,
1625            &["pack-b.idx".into(), "pack-a.idx".into()],
1626            &[
1627                MultiPackIndexEntry {
1628                    oid: second.clone(),
1629                    pack_int_id: 0,
1630                    offset: 0x1_0000_0000,
1631                    force_large_offset: false,
1632                },
1633                MultiPackIndexEntry {
1634                    oid: first.clone(),
1635                    pack_int_id: 1,
1636                    offset: 12,
1637                    force_large_offset: false,
1638                },
1639            ],
1640        )
1641        .expect("test operation should succeed");
1642
1643        let parsed = MultiPackIndex::parse(&bytes, ObjectFormat::Sha1)
1644            .expect("test operation should succeed");
1645        assert_eq!(parsed.version, 2);
1646        assert_eq!(parsed.pack_names, vec!["pack-b.idx", "pack-a.idx"]);
1647        assert_eq!(parsed.object_count, 2);
1648        assert_eq!(
1649            parsed
1650                .find(&first)
1651                .expect("test operation should succeed")
1652                .pack_int_id,
1653            1
1654        );
1655        assert_eq!(
1656            parsed
1657                .find(&first)
1658                .expect("test operation should succeed")
1659                .offset,
1660            12
1661        );
1662        assert_eq!(
1663            parsed
1664                .find(&second)
1665                .expect("test operation should succeed")
1666                .pack_int_id,
1667            0
1668        );
1669        assert_eq!(
1670            parsed
1671                .find(&second)
1672                .expect("test operation should succeed")
1673                .offset,
1674            0x1_0000_0000
1675        );
1676        assert!(parsed.chunks.iter().any(|chunk| chunk.id == *b"LOFF"));
1677    }
1678
1679    #[test]
1680    fn write_multi_pack_index_rejects_invalid_inputs() {
1681        let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"object\n")
1682            .expect("test operation should succeed");
1683        assert!(MultiPackIndex::write(ObjectFormat::Sha1, 3, &["pack-a.idx".into()], &[]).is_err());
1684        assert!(
1685            MultiPackIndex::write(
1686                ObjectFormat::Sha1,
1687                1,
1688                &["pack-b.idx".into(), "pack-a.idx".into()],
1689                &[],
1690            )
1691            .is_err()
1692        );
1693        assert!(MultiPackIndex::write(ObjectFormat::Sha1, 2, &["pack/a.idx".into()], &[]).is_err());
1694        assert!(
1695            MultiPackIndex::write(
1696                ObjectFormat::Sha1,
1697                2,
1698                &["pack-a.idx".into()],
1699                &[MultiPackIndexEntry {
1700                    oid,
1701                    pack_int_id: 1,
1702                    offset: 12,
1703                    force_large_offset: false,
1704                }],
1705            )
1706            .is_err()
1707        );
1708        assert!(
1709            MultiPackIndex::write(
1710                ObjectFormat::Sha1,
1711                2,
1712                &["pack-a.idx".into()],
1713                &[
1714                    MultiPackIndexEntry {
1715                        oid,
1716                        pack_int_id: 0,
1717                        offset: 12,
1718                        force_large_offset: false,
1719                    },
1720                    MultiPackIndexEntry {
1721                        oid,
1722                        pack_int_id: 0,
1723                        offset: 24,
1724                        force_large_offset: false,
1725                    },
1726                ],
1727            )
1728            .is_err()
1729        );
1730    }
1731
1732    #[test]
1733    fn rejects_bad_multi_pack_index_bitmap_chunks() {
1734        let oid_a = ObjectId::from_hex(
1735            ObjectFormat::Sha1,
1736            "1111111111111111111111111111111111111111",
1737        )
1738        .expect("test operation should succeed");
1739        let oid_b = ObjectId::from_hex(
1740            ObjectFormat::Sha1,
1741            "2222222222222222222222222222222222222222",
1742        )
1743        .expect("test operation should succeed");
1744
1745        let mut duplicate_ridx = midx_chunks_with_pack_names(
1746            ObjectFormat::Sha1,
1747            b"pack-a.idx\0\0".to_vec(),
1748            &[(oid_a.clone(), 0, 12), (oid_b.clone(), 0, 24)],
1749        );
1750        duplicate_ridx.push((*b"RIDX", midx_u32_table(&[0, 0])));
1751        let duplicate_ridx = multi_pack_index(ObjectFormat::Sha1, 2, 1, &duplicate_ridx);
1752        assert!(MultiPackIndex::parse(&duplicate_ridx, ObjectFormat::Sha1).is_err());
1753
1754        let mut short_btmp = midx_chunks_with_pack_names(
1755            ObjectFormat::Sha1,
1756            b"pack-a.idx\0pack-b.idx\0\0\0".to_vec(),
1757            &[(oid_a.clone(), 0, 12), (oid_b.clone(), 1, 24)],
1758        );
1759        short_btmp.push((*b"BTMP", midx_bitmap_packs(&[(0, 1)])));
1760        let short_btmp = multi_pack_index(ObjectFormat::Sha1, 2, 2, &short_btmp);
1761        assert!(MultiPackIndex::parse(&short_btmp, ObjectFormat::Sha1).is_err());
1762
1763        let mut out_of_range_btmp = midx_chunks_with_pack_names(
1764            ObjectFormat::Sha1,
1765            b"pack-a.idx\0\0".to_vec(),
1766            &[(oid_a, 0, 12), (oid_b, 0, 24)],
1767        );
1768        out_of_range_btmp.push((*b"BTMP", midx_bitmap_packs(&[(1, 2)])));
1769        let out_of_range_btmp = multi_pack_index(ObjectFormat::Sha1, 2, 1, &out_of_range_btmp);
1770        assert!(MultiPackIndex::parse(&out_of_range_btmp, ObjectFormat::Sha1).is_err());
1771    }
1772
1773    #[test]
1774    fn parses_pack_bitmap_index_with_hash_cache() {
1775        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1776            .expect("test operation should succeed");
1777        let bitmap = pack_bitmap_index(
1778            ObjectFormat::Sha1,
1779            3,
1780            PackBitmapIndex::OPTION_FULL_DAG | PackBitmapIndex::OPTION_HASH_CACHE,
1781            &pack_checksum,
1782            &[(2, 0, 1, &[0b101])],
1783            Some(&[0x1111_1111, 0x2222_2222, 0x3333_3333]),
1784        );
1785
1786        let parsed = PackBitmapIndex::parse(&bitmap, ObjectFormat::Sha1, 3)
1787            .expect("test operation should succeed");
1788        assert_eq!(parsed.version, 1);
1789        assert_eq!(parsed.format, ObjectFormat::Sha1);
1790        assert_eq!(
1791            parsed.options,
1792            PackBitmapIndex::OPTION_FULL_DAG | PackBitmapIndex::OPTION_HASH_CACHE
1793        );
1794        assert_eq!(parsed.pack_checksum, pack_checksum);
1795        assert_eq!(parsed.type_bitmaps.commits.bit_size, 3);
1796        assert_eq!(parsed.type_bitmaps.trees.bit_size, 3);
1797        assert_eq!(parsed.entries.len(), 1);
1798        let entry = parsed
1799            .entry_for_index_position(2)
1800            .expect("test operation should succeed");
1801        assert_eq!(entry.xor_offset, 0);
1802        assert_eq!(entry.flags, 1);
1803        assert_eq!(entry.bitmap.words, ewah_literal_words(&[0b101]));
1804        assert_eq!(
1805            parsed.name_hash_cache,
1806            Some(vec![0x1111_1111, 0x2222_2222, 0x3333_3333])
1807        );
1808    }
1809
1810    #[test]
1811    fn parses_pack_bitmap_index_sha256() {
1812        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
1813            .expect("test operation should succeed");
1814        let bitmap = pack_bitmap_index(
1815            ObjectFormat::Sha256,
1816            2,
1817            PackBitmapIndex::OPTION_FULL_DAG,
1818            &pack_checksum,
1819            &[(0, 0, 0, &[0b11])],
1820            None,
1821        );
1822
1823        let parsed = PackBitmapIndex::parse(&bitmap, ObjectFormat::Sha256, 2)
1824            .expect("test operation should succeed");
1825        assert_eq!(parsed.version, 1);
1826        assert_eq!(parsed.format, ObjectFormat::Sha256);
1827        assert_eq!(parsed.pack_checksum, pack_checksum);
1828        assert_eq!(parsed.index_checksum.format(), ObjectFormat::Sha256);
1829        assert_eq!(parsed.entries[0].object_position, 0);
1830        assert_eq!(parsed.name_hash_cache, None);
1831    }
1832
1833    #[test]
1834    fn parses_upstream_git_written_pack_bitmap_index() {
1835        let root = unique_temp_dir("git-pack-bitmap-upstream");
1836        fs::create_dir_all(&root).expect("test operation should succeed");
1837        {
1838            run_git_success(&root, &["init", "-q", "-b", "main"]);
1839            run_git_success(
1840                &root,
1841                &[
1842                    "-c",
1843                    "user.name=Example User",
1844                    "-c",
1845                    "user.email=example@example.invalid",
1846                    "commit",
1847                    "--allow-empty",
1848                    "-q",
1849                    "-m",
1850                    "one",
1851                ],
1852            );
1853            run_git_success(
1854                &root,
1855                &[
1856                    "-c",
1857                    "user.name=Example User",
1858                    "-c",
1859                    "user.email=example@example.invalid",
1860                    "commit",
1861                    "--allow-empty",
1862                    "-q",
1863                    "-m",
1864                    "two",
1865                ],
1866            );
1867            run_git_success(&root, &["repack", "-adb"]);
1868            let pack_dir = root.join(".git").join("objects").join("pack");
1869            let idx_path = single_path_with_extension(&pack_dir, "idx");
1870            let bitmap_path = single_path_with_extension(&pack_dir, "bitmap");
1871            let index = PackIndex::parse(
1872                &fs::read(idx_path).expect("test operation should succeed"),
1873                ObjectFormat::Sha1,
1874            )
1875            .expect("test operation should succeed");
1876            let bitmap = PackBitmapIndex::parse(
1877                &fs::read(bitmap_path).expect("test operation should succeed"),
1878                ObjectFormat::Sha1,
1879                index.entries.len(),
1880            )
1881            .expect("test operation should succeed");
1882            assert_eq!(bitmap.pack_checksum, index.pack_checksum);
1883            assert!(!bitmap.entries.is_empty());
1884        };
1885        let _ = fs::remove_dir_all(&root);
1886    }
1887
1888    #[test]
1889    fn rejects_bad_pack_bitmap_index_header_and_checksum() {
1890        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1891            .expect("test operation should succeed");
1892        let bitmap = pack_bitmap_index(
1893            ObjectFormat::Sha1,
1894            1,
1895            PackBitmapIndex::OPTION_FULL_DAG,
1896            &pack_checksum,
1897            &[(0, 0, 0, &[1])],
1898            None,
1899        );
1900
1901        let mut bad_signature = bitmap.clone();
1902        bad_signature[0] = b'X';
1903        assert!(PackBitmapIndex::parse(&bad_signature, ObjectFormat::Sha1, 1).is_err());
1904
1905        let mut bad_version = bitmap.clone();
1906        bad_version[5] = 2;
1907        refresh_trailing_checksum(ObjectFormat::Sha1, &mut bad_version);
1908        assert!(PackBitmapIndex::parse(&bad_version, ObjectFormat::Sha1, 1).is_err());
1909
1910        let mut bad_option = bitmap.clone();
1911        bad_option[7] = 0x20;
1912        refresh_trailing_checksum(ObjectFormat::Sha1, &mut bad_option);
1913        assert!(PackBitmapIndex::parse(&bad_option, ObjectFormat::Sha1, 1).is_err());
1914
1915        let mut bad_checksum = bitmap;
1916        let last = bad_checksum.len() - 1;
1917        bad_checksum[last] ^= 1;
1918        assert!(PackBitmapIndex::parse(&bad_checksum, ObjectFormat::Sha1, 1).is_err());
1919    }
1920
1921    #[test]
1922    fn rejects_bad_pack_bitmap_index_ewah_and_entries() {
1923        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha1, b"pack")
1924            .expect("test operation should succeed");
1925        let bitmap = pack_bitmap_index(
1926            ObjectFormat::Sha1,
1927            2,
1928            PackBitmapIndex::OPTION_FULL_DAG,
1929            &pack_checksum,
1930            &[(0, 0, 0, &[0b01]), (1, 1, 0, &[0b11])],
1931            None,
1932        );
1933
1934        let mut truncated = bitmap.clone();
1935        truncated.truncate(truncated.len() - ObjectFormat::Sha1.raw_len() - 1);
1936        refresh_trailing_checksum(ObjectFormat::Sha1, &mut truncated);
1937        assert!(PackBitmapIndex::parse(&truncated, ObjectFormat::Sha1, 2).is_err());
1938
1939        let mut out_of_range_position = pack_bitmap_index(
1940            ObjectFormat::Sha1,
1941            2,
1942            PackBitmapIndex::OPTION_FULL_DAG,
1943            &pack_checksum,
1944            &[(2, 0, 0, &[0b01])],
1945            None,
1946        );
1947        assert!(PackBitmapIndex::parse(&out_of_range_position, ObjectFormat::Sha1, 2).is_err());
1948        refresh_trailing_checksum(ObjectFormat::Sha1, &mut out_of_range_position);
1949        assert!(PackBitmapIndex::parse(&out_of_range_position, ObjectFormat::Sha1, 2).is_err());
1950
1951        let invalid_xor = pack_bitmap_index(
1952            ObjectFormat::Sha1,
1953            2,
1954            PackBitmapIndex::OPTION_FULL_DAG,
1955            &pack_checksum,
1956            &[(0, 1, 0, &[0b01])],
1957            None,
1958        );
1959        assert!(PackBitmapIndex::parse(&invalid_xor, ObjectFormat::Sha1, 2).is_err());
1960    }
1961
1962    #[test]
1963    fn parses_single_entry_pack_index_sha256() {
1964        let oid = sley_core::object_id_for_bytes(ObjectFormat::Sha256, "blob", b"hello sha256\n")
1965            .expect("test operation should succeed");
1966        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
1967            .expect("test operation should succeed");
1968        let index = single_entry_index(
1969            ObjectFormat::Sha256,
1970            oid,
1971            0x1234_5678,
1972            12,
1973            pack_checksum.clone(),
1974        );
1975        let parsed =
1976            PackIndex::parse(&index, ObjectFormat::Sha256).expect("test operation should succeed");
1977        assert_eq!(parsed.version, 2);
1978        assert_eq!(parsed.pack_checksum, pack_checksum);
1979        assert_eq!(parsed.entries.len(), 1);
1980        assert_eq!(
1981            parsed
1982                .find(&oid)
1983                .expect("test operation should succeed")
1984                .offset,
1985            12
1986        );
1987        assert_eq!(
1988            parsed
1989                .find(&oid)
1990                .expect("test operation should succeed")
1991                .crc32,
1992            0x1234_5678
1993        );
1994        assert_eq!(parsed.index_checksum.format(), ObjectFormat::Sha256);
1995        assert_pack_index_view_matches_owned(&index, ObjectFormat::Sha256);
1996    }
1997
1998    #[test]
1999    fn write_packed_deltifies_similar_blobs_and_round_trips_sha1() {
2000        write_packed_deltifies_similar_blobs_and_round_trips(ObjectFormat::Sha1);
2001    }
2002
2003    #[test]
2004    fn write_packed_deltifies_similar_blobs_and_round_trips_sha256() {
2005        write_packed_deltifies_similar_blobs_and_round_trips(ObjectFormat::Sha256);
2006    }
2007
2008    #[test]
2009    fn write_packed_rejects_duplicate_objects() {
2010        let object = EncodedObject::new(ObjectType::Blob, b"same\n".to_vec());
2011        assert!(PackFile::write_packed(&[object.clone(), object], ObjectFormat::Sha1,).is_err());
2012    }
2013
2014    #[test]
2015    fn write_packed_with_known_ids_validates_ids_before_trusting_them() {
2016        let object = EncodedObject::new(ObjectType::Blob, b"same\n".to_vec());
2017        let sha1 = object
2018            .object_id(ObjectFormat::Sha1)
2019            .expect("test operation should succeed");
2020        let sha256 = object
2021            .object_id(ObjectFormat::Sha256)
2022            .expect("test operation should succeed");
2023        let duplicate = [
2024            PackInput {
2025                oid: &sha1,
2026                object: &object,
2027            },
2028            PackInput {
2029                oid: &sha1,
2030                object: &object,
2031            },
2032        ];
2033        assert!(PackFile::write_packed_with_known_ids(&duplicate, ObjectFormat::Sha1).is_err());
2034
2035        let wrong_format = [PackInput {
2036            oid: &sha256,
2037            object: &object,
2038        }];
2039        assert!(PackFile::write_packed_with_known_ids(&wrong_format, ObjectFormat::Sha1).is_err());
2040    }
2041
2042    #[test]
2043    fn write_packed_with_known_ids_to_writer_matches_in_memory_pack() {
2044        let objects = similar_blob_family(6);
2045        let object_ids = objects
2046            .iter()
2047            .map(|object| {
2048                object
2049                    .object_id(ObjectFormat::Sha1)
2050                    .expect("test operation should succeed")
2051            })
2052            .collect::<Vec<_>>();
2053        let inputs = objects
2054            .iter()
2055            .zip(&object_ids)
2056            .map(|(object, oid)| PackInput { oid, object })
2057            .collect::<Vec<_>>();
2058        let options = PackWriteOptions::new();
2059        let in_memory = PackFile::write_packed_with_known_ids_and_options(
2060            &inputs,
2061            ObjectFormat::Sha1,
2062            &options,
2063        )
2064        .expect("test operation should succeed");
2065        let mut written = Vec::new();
2066        let streamed = PackFile::write_packed_with_known_ids_to_writer(
2067            &inputs,
2068            ObjectFormat::Sha1,
2069            &options,
2070            &mut written,
2071        )
2072        .expect("test operation should succeed");
2073
2074        assert_eq!(written, in_memory.pack);
2075        assert_eq!(streamed.index, in_memory.index);
2076        assert_eq!(streamed.checksum, in_memory.checksum);
2077        assert_eq!(streamed.entries, in_memory.entries);
2078        assert_eq!(streamed.delta_count, in_memory.delta_count);
2079        assert_eq!(streamed.pack_size, in_memory.pack.len() as u64);
2080    }
2081
2082    #[test]
2083    fn write_packed_from_source_to_writer_deltifies_across_windows() {
2084        let format = ObjectFormat::Sha1;
2085        let mut objects = Vec::new();
2086        for idx in 0..PACK_STREAM_COMPRESSION_WINDOW_OBJECTS - 1 {
2087            objects.push(EncodedObject::new(
2088                ObjectType::Blob,
2089                format!("unrelated streamed source object {idx:04}\n").into_bytes(),
2090            ));
2091        }
2092        let base_body = b"cross-window base payload with enough shared anchors\nbase\n".to_vec();
2093        let target_body =
2094            b"cross-window base payload with enough shared anchors\ntarget\n".to_vec();
2095        objects.push(EncodedObject::new(ObjectType::Blob, base_body));
2096        objects.push(EncodedObject::new(ObjectType::Blob, target_body));
2097
2098        let object_ids = objects
2099            .iter()
2100            .map(|object| {
2101                object
2102                    .object_id(format)
2103                    .expect("test operation should succeed")
2104            })
2105            .collect::<Vec<_>>();
2106        let base_oid = object_ids[PACK_STREAM_COMPRESSION_WINDOW_OBJECTS - 1];
2107        let target_oid = object_ids[PACK_STREAM_COMPRESSION_WINDOW_OBJECTS];
2108        let object_map = object_ids
2109            .iter()
2110            .copied()
2111            .zip(objects.into_iter().map(Arc::new))
2112            .collect::<HashMap<_, _>>();
2113
2114        let options = PackWriteOptions::new().with_reorder(false).with_window(10);
2115        let mut written = Vec::new();
2116        let summary = PackFile::write_packed_from_source_to_writer(
2117            &object_ids,
2118            format,
2119            &options,
2120            |oid| {
2121                object_map
2122                    .get(oid)
2123                    .cloned()
2124                    .ok_or_else(|| GitError::not_found(format!("missing test object {oid}")))
2125            },
2126            &mut written,
2127        )
2128        .expect("test operation should succeed");
2129
2130        assert!(
2131            summary.delta_count > 0,
2132            "expected source-backed streaming writer to find deltas"
2133        );
2134        let stats =
2135            PackFile::verify_pack_stats(&written, format).expect("test operation should succeed");
2136        let target = stats
2137            .objects
2138            .iter()
2139            .find(|entry| entry.oid == target_oid)
2140            .expect("target object should be present");
2141        assert_eq!(target.base_oid, Some(base_oid));
2142    }
2143
2144    fn write_packed_deltifies_similar_blobs_and_round_trips(format: ObjectFormat) {
2145        let objects = similar_blob_family(8);
2146        let packed =
2147            PackFile::write_packed(&objects, format).expect("test operation should succeed");
2148        let undeltified =
2149            PackFile::write_undeltified(&objects, format).expect("test operation should succeed");
2150
2151        // The whole point of delta selection: the packed output is smaller than
2152        // storing every object undeltified.
2153        assert!(
2154            packed.pack.len() < undeltified.pack.len(),
2155            "expected delta pack ({}) smaller than undeltified pack ({})",
2156            packed.pack.len(),
2157            undeltified.pack.len()
2158        );
2159
2160        // At least one object must actually be stored as a delta.
2161        let kinds = pack_entry_kinds(&packed.pack, format);
2162        let delta_count = kinds
2163            .iter()
2164            .filter(|kind| matches!(kind, PackObjectKind::OfsDelta | PackObjectKind::RefDelta))
2165            .count();
2166        assert!(
2167            delta_count >= 1,
2168            "expected at least one delta entry, found kinds {kinds:?}"
2169        );
2170
2171        // Round-trip: every original object reconstructs byte-for-byte.
2172        let parsed = PackFile::parse(&packed.pack, format).expect("test operation should succeed");
2173        assert_eq!(parsed.entries.len(), objects.len());
2174        for object in &objects {
2175            let oid = object
2176                .object_id(format)
2177                .expect("test operation should succeed");
2178            let found = parsed
2179                .entries
2180                .iter()
2181                .find(|entry| entry.entry.oid == oid)
2182                .unwrap_or_else(|| panic!("object {oid} missing from parsed pack"));
2183            assert_eq!(&found.object, object, "object {oid} did not round-trip");
2184        }
2185
2186        // The index must agree with the pack and locate every object.
2187        let index = PackIndex::parse(&packed.index, format).expect("test operation should succeed");
2188        assert_eq!(index.pack_checksum, packed.checksum);
2189        for object in &objects {
2190            let oid = object
2191                .object_id(format)
2192                .expect("test operation should succeed");
2193            assert!(index.find(&oid).is_some(), "index missing {oid}");
2194        }
2195    }
2196
2197    #[test]
2198    fn write_packed_emits_ofs_delta_by_default() {
2199        let objects = similar_blob_family(6);
2200        let packed = PackFile::write_packed(&objects, ObjectFormat::Sha1)
2201            .expect("test operation should succeed");
2202        let kinds = pack_entry_kinds(&packed.pack, ObjectFormat::Sha1);
2203        assert!(
2204            kinds.contains(&PackObjectKind::OfsDelta),
2205            "expected an ofs-delta entry by default, found {kinds:?}"
2206        );
2207        assert!(
2208            !kinds.contains(&PackObjectKind::RefDelta),
2209            "default self-contained pack must not use ref-delta, found {kinds:?}"
2210        );
2211        // Round-trips.
2212        assert!(PackFile::parse(&packed.pack, ObjectFormat::Sha1).is_ok());
2213    }
2214
2215    #[test]
2216    fn write_packed_can_emit_ref_delta() {
2217        let objects = similar_blob_family(6);
2218        let options = PackWriteOptions::new().with_prefer_ofs_delta(false);
2219        let packed = PackFile::write_packed_with_options(&objects, ObjectFormat::Sha1, &options)
2220            .expect("test operation should succeed");
2221        let kinds = pack_entry_kinds(&packed.pack, ObjectFormat::Sha1);
2222        assert!(
2223            kinds.contains(&PackObjectKind::RefDelta),
2224            "expected a ref-delta entry, found {kinds:?}"
2225        );
2226        assert!(
2227            !kinds.contains(&PackObjectKind::OfsDelta),
2228            "ref-delta mode must not emit ofs-delta, found {kinds:?}"
2229        );
2230
2231        // Ref-delta packs are still self-contained here, so they round-trip
2232        // without any external base lookup.
2233        let parsed = PackFile::parse(&packed.pack, ObjectFormat::Sha1)
2234            .expect("test operation should succeed");
2235        assert_eq!(parsed.entries.len(), objects.len());
2236    }
2237
2238    #[test]
2239    fn write_packed_bounds_delta_chain_depth() {
2240        // A long chain of progressively-modified blobs. With a large window
2241        // every object could otherwise delta against its immediate predecessor,
2242        // forming a chain as long as the input.
2243        let objects = incremental_blob_chain(20);
2244        let format = ObjectFormat::Sha1;
2245
2246        for max_depth in [1usize, 2, 5] {
2247            let options = PackWriteOptions::new()
2248                .with_window(20)
2249                .with_depth(max_depth);
2250            let packed = PackFile::write_packed_with_options(&objects, format, &options)
2251                .expect("test operation should succeed");
2252
2253            let depths = pack_entry_depths(&packed.pack, format);
2254            let observed = depths.iter().copied().max().unwrap_or(0);
2255            assert!(
2256                observed <= max_depth,
2257                "max chain depth {observed} exceeded bound {max_depth}"
2258            );
2259
2260            // Still correct: round-trips byte-for-byte.
2261            let parsed =
2262                PackFile::parse(&packed.pack, format).expect("test operation should succeed");
2263            for object in &objects {
2264                let oid = object
2265                    .object_id(format)
2266                    .expect("test operation should succeed");
2267                let found = parsed
2268                    .entries
2269                    .iter()
2270                    .find(|entry| entry.entry.oid == oid)
2271                    .expect("test operation should succeed");
2272                assert_eq!(&found.object, object);
2273            }
2274        }
2275    }
2276
2277    #[test]
2278    fn write_packed_depth_zero_stores_everything_undeltified() {
2279        let objects = similar_blob_family(5);
2280        let options = PackWriteOptions::new().with_depth(0);
2281        let packed = PackFile::write_packed_with_options(&objects, ObjectFormat::Sha1, &options)
2282            .expect("test operation should succeed");
2283        let kinds = pack_entry_kinds(&packed.pack, ObjectFormat::Sha1);
2284        assert!(
2285            kinds
2286                .iter()
2287                .all(|kind| !matches!(kind, PackObjectKind::OfsDelta | PackObjectKind::RefDelta)),
2288            "depth 0 must disable deltas, found {kinds:?}"
2289        );
2290    }
2291
2292    #[test]
2293    fn write_thin_uses_external_base_and_round_trips_sha1() {
2294        write_thin_uses_external_base_and_round_trips(ObjectFormat::Sha1);
2295    }
2296
2297    #[test]
2298    fn write_thin_uses_external_base_and_round_trips_sha256() {
2299        write_thin_uses_external_base_and_round_trips(ObjectFormat::Sha256);
2300    }
2301
2302    fn write_thin_uses_external_base_and_round_trips(format: ObjectFormat) {
2303        // The base object stays OUT of the pack; only `target` is written, as a
2304        // ref-delta against the external base's object id.
2305        let base = blob_with_marker("EXTERNAL-BASE");
2306        let target = blob_with_marker("EXTERNAL-TARGET");
2307        let base_oid = base
2308            .object_id(format)
2309            .expect("test operation should succeed");
2310
2311        let mut external = HashMap::new();
2312        external.insert(base_oid, base.clone());
2313        let packed = PackFile::write_thin(std::slice::from_ref(&target), format, external)
2314            .expect("test operation should succeed");
2315
2316        // Exactly one entry, encoded as a ref-delta to the external base.
2317        let kinds = pack_entry_kinds(&packed.pack, format);
2318        assert_eq!(kinds, vec![PackObjectKind::RefDelta]);
2319
2320        // The external base reference must be the base oid.
2321        let mut offset = 12usize;
2322        let header =
2323            parse_entry_header(&packed.pack, &mut offset).expect("test operation should succeed");
2324        assert_eq!(header.kind, PackObjectKind::RefDelta);
2325        let referenced =
2326            ObjectId::from_raw(format, &packed.pack[offset..offset + format.raw_len()])
2327                .expect("test operation should succeed");
2328        assert_eq!(referenced, base_oid);
2329
2330        // A plain (non-thin) parse fails: the base is not present.
2331        assert!(PackFile::parse(&packed.pack, format).is_err());
2332
2333        // A thin parse that supplies the external base reconstructs the target.
2334        let parsed = PackFile::parse_thin(&packed.pack, format, |oid| {
2335            if oid == &base_oid {
2336                Ok(Some(base.clone()))
2337            } else {
2338                Ok(None)
2339            }
2340        })
2341        .expect("test operation should succeed");
2342        assert_eq!(parsed.entries.len(), 1);
2343        assert_eq!(parsed.entries[0].object, target);
2344    }
2345
2346    #[test]
2347    fn write_packed_preserves_distinct_objects_with_no_similarity() {
2348        // Unrelated objects: nothing should delta, but the pack must still be
2349        // valid and complete.
2350        let objects = vec![
2351            EncodedObject::new(ObjectType::Blob, b"alpha distinct\n".to_vec()),
2352            EncodedObject::new(ObjectType::Tree, vec![0u8; 0]),
2353            EncodedObject::new(ObjectType::Commit, b"tree 0000\n".to_vec()),
2354        ];
2355        let format = ObjectFormat::Sha1;
2356        let packed =
2357            PackFile::write_packed(&objects, format).expect("test operation should succeed");
2358        let parsed = PackFile::parse(&packed.pack, format).expect("test operation should succeed");
2359        assert_eq!(parsed.entries.len(), objects.len());
2360        for object in &objects {
2361            let oid = object
2362                .object_id(format)
2363                .expect("test operation should succeed");
2364            assert!(parsed.entries.iter().any(|entry| entry.entry.oid == oid));
2365        }
2366    }
2367
2368    /// Build a family of blobs that all share a large common region but differ
2369    /// in a marker placed in the *middle*, so a good delta finds copy regions on
2370    /// both sides of the change.
2371    fn similar_blob_family(count: usize) -> Vec<EncodedObject> {
2372        let mut common_head = Vec::new();
2373        for _ in 0..200 {
2374            common_head.extend_from_slice(b"shared header line for delta testing\n");
2375        }
2376        let mut common_tail = Vec::new();
2377        for _ in 0..200 {
2378            common_tail.extend_from_slice(b"shared trailer line for delta testing\n");
2379        }
2380        (0..count)
2381            .map(|idx| {
2382                let mut body = common_head.clone();
2383                body.extend_from_slice(format!("UNIQUE MIDDLE MARKER NUMBER {idx}\n").as_bytes());
2384                body.extend_from_slice(&common_tail);
2385                EncodedObject::new(ObjectType::Blob, body)
2386            })
2387            .collect()
2388    }
2389
2390    /// Build a chain where each blob is the previous one plus an appended line,
2391    /// so each is highly similar to its predecessor.
2392    fn incremental_blob_chain(count: usize) -> Vec<EncodedObject> {
2393        let mut body = Vec::new();
2394        for _ in 0..100 {
2395            body.extend_from_slice(b"baseline content shared across the whole chain\n");
2396        }
2397        let mut objects = Vec::with_capacity(count);
2398        for idx in 0..count {
2399            body.extend_from_slice(format!("appended unique line {idx}\n").as_bytes());
2400            objects.push(EncodedObject::new(ObjectType::Blob, body.clone()));
2401        }
2402        objects
2403    }
2404
2405    fn blob_with_marker(marker: &str) -> EncodedObject {
2406        let mut body = Vec::new();
2407        for _ in 0..150 {
2408            body.extend_from_slice(b"common body shared between base and target\n");
2409        }
2410        body.extend_from_slice(marker.as_bytes());
2411        body.push(b'\n');
2412        for _ in 0..150 {
2413            body.extend_from_slice(b"more common body shared between objects\n");
2414        }
2415        EncodedObject::new(ObjectType::Blob, body)
2416    }
2417
2418    /// Classify every entry in a pack (in pack order) by its on-disk kind.
2419    fn pack_entry_kinds(pack: &[u8], format: ObjectFormat) -> Vec<PackObjectKind> {
2420        pack_entry_descriptors(pack, format)
2421            .into_iter()
2422            .map(|descriptor| descriptor.kind)
2423            .collect()
2424    }
2425
2426    /// Compute each entry's delta chain depth (0 = undeltified base), in pack
2427    /// order. Entries always appear after their in-pack bases, so a single
2428    /// forward pass suffices.
2429    fn pack_entry_depths(pack: &[u8], format: ObjectFormat) -> Vec<usize> {
2430        let descriptors = pack_entry_descriptors(pack, format);
2431        let mut depth_by_offset: HashMap<u64, usize> = HashMap::new();
2432        let mut depths = Vec::with_capacity(descriptors.len());
2433        for descriptor in &descriptors {
2434            let depth = match &descriptor.base {
2435                EntryBase::None => 0,
2436                EntryBase::Offset(base_offset) => {
2437                    depth_by_offset.get(base_offset).copied().unwrap_or(0) + 1
2438                }
2439                // Ref-delta to an in-pack base: look it up by offset via oid is
2440                // unnecessary for these tests (which only use ofs-delta for the
2441                // chains), so treat as depth 1 if unknown.
2442                EntryBase::Ref => 1,
2443            };
2444            depth_by_offset.insert(descriptor.offset, depth);
2445            depths.push(depth);
2446        }
2447        depths
2448    }
2449
2450    struct EntryDescriptor {
2451        offset: u64,
2452        kind: PackObjectKind,
2453        base: EntryBase,
2454    }
2455
2456    enum EntryBase {
2457        None,
2458        Offset(u64),
2459        Ref,
2460    }
2461
2462    fn pack_entry_descriptors(pack: &[u8], format: ObjectFormat) -> Vec<EntryDescriptor> {
2463        let trailer_offset = pack.len() - format.raw_len();
2464        let count = u32_be(&pack[8..12]) as usize;
2465        let mut offset = 12usize;
2466        let mut descriptors = Vec::with_capacity(count);
2467        for _ in 0..count {
2468            let entry_offset = offset as u64;
2469            let header =
2470                parse_entry_header(pack, &mut offset).expect("test operation should succeed");
2471            let base = match header.kind {
2472                PackObjectKind::OfsDelta => {
2473                    let base_offset = parse_ofs_delta_base_offset(pack, &mut offset, entry_offset)
2474                        .expect("test operation should succeed");
2475                    EntryBase::Offset(base_offset)
2476                }
2477                PackObjectKind::RefDelta => {
2478                    offset += format.raw_len();
2479                    EntryBase::Ref
2480                }
2481                _ => EntryBase::None,
2482            };
2483            let mut decoder = ZlibDecoder::new(&pack[offset..trailer_offset]);
2484            let mut body = Vec::new();
2485            decoder
2486                .read_to_end(&mut body)
2487                .expect("test operation should succeed");
2488            offset += decoder.total_in() as usize;
2489            descriptors.push(EntryDescriptor {
2490                offset: entry_offset,
2491                kind: header.kind,
2492                base,
2493            });
2494        }
2495        descriptors
2496    }
2497
2498    fn similar_blob_objects() -> (EncodedObject, EncodedObject) {
2499        let mut base = Vec::new();
2500        for _ in 0..300 {
2501            base.extend_from_slice(b"common payload\n");
2502        }
2503        base.extend_from_slice(b"base\n");
2504        let mut changed = Vec::new();
2505        for _ in 0..300 {
2506            changed.extend_from_slice(b"common payload\n");
2507        }
2508        changed.extend_from_slice(b"changed\n");
2509        (
2510            EncodedObject::new(ObjectType::Blob, base),
2511            EncodedObject::new(ObjectType::Blob, changed),
2512        )
2513    }
2514
2515    fn single_object_pack(format: ObjectFormat, object_type: ObjectType, body: &[u8]) -> Vec<u8> {
2516        let mut pack = Vec::new();
2517        pack.extend_from_slice(b"PACK");
2518        pack.extend_from_slice(&2u32.to_be_bytes());
2519        pack.extend_from_slice(&1u32.to_be_bytes());
2520        write_entry_header(&mut pack, object_type, body.len() as u64);
2521        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
2522        encoder
2523            .write_all(body)
2524            .expect("test operation should succeed");
2525        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
2526        let checksum =
2527            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
2528        pack.extend_from_slice(checksum.as_bytes());
2529        pack
2530    }
2531
2532    #[derive(Clone, Copy, Debug)]
2533    enum DeltaKind {
2534        Offset,
2535        Ref,
2536    }
2537
2538    fn two_object_delta_pack(
2539        format: ObjectFormat,
2540        base: &[u8],
2541        result: &[u8],
2542        delta_kind: DeltaKind,
2543    ) -> Vec<u8> {
2544        let mut pack = Vec::new();
2545        pack.extend_from_slice(b"PACK");
2546        pack.extend_from_slice(&2u32.to_be_bytes());
2547        pack.extend_from_slice(&2u32.to_be_bytes());
2548
2549        let base_offset = pack.len();
2550        write_entry_header(&mut pack, ObjectType::Blob, base.len() as u64);
2551        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
2552        encoder
2553            .write_all(base)
2554            .expect("test operation should succeed");
2555        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
2556
2557        let delta = append_suffix_delta(base, result);
2558        let delta_offset = pack.len();
2559        write_pack_entry_header_kind(
2560            &mut pack,
2561            match delta_kind {
2562                DeltaKind::Offset => 6,
2563                DeltaKind::Ref => 7,
2564            },
2565            delta.len() as u64,
2566        );
2567        match delta_kind {
2568            DeltaKind::Offset => write_ofs_delta_offset(&mut pack, delta_offset - base_offset),
2569            DeltaKind::Ref => {
2570                let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
2571                    .expect("test operation should succeed");
2572                pack.extend_from_slice(base_oid.as_bytes());
2573            }
2574        }
2575        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
2576        encoder
2577            .write_all(&delta)
2578            .expect("test operation should succeed");
2579        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
2580
2581        let checksum =
2582            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
2583        pack.extend_from_slice(checksum.as_bytes());
2584        pack
2585    }
2586
2587    fn thin_ref_delta_pack(format: ObjectFormat, base: &[u8], result: &[u8]) -> Vec<u8> {
2588        let mut pack = Vec::new();
2589        pack.extend_from_slice(b"PACK");
2590        pack.extend_from_slice(&2u32.to_be_bytes());
2591        pack.extend_from_slice(&1u32.to_be_bytes());
2592
2593        let delta = append_suffix_delta(base, result);
2594        write_pack_entry_header_kind(&mut pack, 7, delta.len() as u64);
2595        let base_oid = sley_core::object_id_for_bytes(format, "blob", base)
2596            .expect("test operation should succeed");
2597        pack.extend_from_slice(base_oid.as_bytes());
2598        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
2599        encoder
2600            .write_all(&delta)
2601            .expect("test operation should succeed");
2602        pack.extend_from_slice(&encoder.finish().expect("test operation should succeed"));
2603
2604        let checksum =
2605            sley_core::digest_bytes(format, &pack).expect("test operation should succeed");
2606        pack.extend_from_slice(checksum.as_bytes());
2607        pack
2608    }
2609
2610    fn unique_temp_dir(name: &str) -> PathBuf {
2611        let nanos = SystemTime::now()
2612            .duration_since(UNIX_EPOCH)
2613            .expect("test operation should succeed")
2614            .as_nanos();
2615        std::env::temp_dir().join(format!("sley-{name}-{}-{nanos}", std::process::id()))
2616    }
2617
2618    fn run_git_success(cwd: &Path, args: &[&str]) {
2619        let output = Command::new("git")
2620            .current_dir(cwd)
2621            .args(args)
2622            .output()
2623            .unwrap_or_else(|err| panic!("failed to run git {args:?}: {err}"));
2624        assert!(
2625            output.status.success(),
2626            "git {args:?} failed with status {:?}\nstdout:\n{}\nstderr:\n{}",
2627            output.status.code(),
2628            String::from_utf8_lossy(&output.stdout),
2629            String::from_utf8_lossy(&output.stderr)
2630        );
2631    }
2632
2633    fn single_path_with_extension(dir: &Path, extension: &str) -> PathBuf {
2634        let mut paths = fs::read_dir(dir)
2635            .expect("test operation should succeed")
2636            .map(|entry| entry.expect("test operation should succeed").path())
2637            .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some(extension))
2638            .collect::<Vec<_>>();
2639        assert_eq!(paths.len(), 1, "expected one .{extension} file");
2640        paths.remove(0)
2641    }
2642
2643    fn pack_bitmap_index(
2644        format: ObjectFormat,
2645        object_count: u32,
2646        options: u16,
2647        pack_checksum: &ObjectId,
2648        entries: &[(u32, u8, u8, &[u64])],
2649        name_hash_cache: Option<&[u32]>,
2650    ) -> Vec<u8> {
2651        let mut out = Vec::new();
2652        out.extend_from_slice(b"BITM");
2653        out.extend_from_slice(&1u16.to_be_bytes());
2654        out.extend_from_slice(&options.to_be_bytes());
2655        out.extend_from_slice(&(entries.len() as u32).to_be_bytes());
2656        out.extend_from_slice(pack_checksum.as_bytes());
2657        write_test_ewah(&mut out, object_count, &[0b001]);
2658        write_test_ewah(&mut out, object_count, &[0b010]);
2659        write_test_ewah(&mut out, object_count, &[0b100]);
2660        write_test_ewah(&mut out, object_count, &[0]);
2661        for (position, xor_offset, flags, words) in entries {
2662            out.extend_from_slice(&position.to_be_bytes());
2663            out.push(*xor_offset);
2664            out.push(*flags);
2665            write_test_ewah(&mut out, object_count, words);
2666        }
2667        if let Some(cache) = name_hash_cache {
2668            for value in cache {
2669                out.extend_from_slice(&value.to_be_bytes());
2670            }
2671        }
2672        let checksum =
2673            sley_core::digest_bytes(format, &out).expect("test operation should succeed");
2674        out.extend_from_slice(checksum.as_bytes());
2675        out
2676    }
2677
2678    fn write_test_ewah(out: &mut Vec<u8>, bit_size: u32, literals: &[u64]) {
2679        out.extend_from_slice(&bit_size.to_be_bytes());
2680        let words = ewah_literal_words(literals);
2681        out.extend_from_slice(&(words.len() as u32).to_be_bytes());
2682        for word in words {
2683            out.extend_from_slice(&word.to_be_bytes());
2684        }
2685        out.extend_from_slice(&0u32.to_be_bytes());
2686    }
2687
2688    fn ewah_literal_words(literals: &[u64]) -> Vec<u64> {
2689        let rlw = (literals.len() as u64) << 33;
2690        let mut words = vec![rlw];
2691        words.extend_from_slice(literals);
2692        words
2693    }
2694
2695    fn refresh_trailing_checksum(format: ObjectFormat, bytes: &mut [u8]) {
2696        let checksum_offset = bytes.len() - format.raw_len();
2697        let checksum = sley_core::digest_bytes(format, &bytes[..checksum_offset])
2698            .expect("test operation should succeed");
2699        bytes[checksum_offset..].copy_from_slice(checksum.as_bytes());
2700    }
2701
2702    fn append_suffix_delta(base: &[u8], result: &[u8]) -> Vec<u8> {
2703        assert!(result.starts_with(base));
2704        let suffix = &result[base.len()..];
2705        assert!(base.len() < 0x10000);
2706        assert!(suffix.len() < 0x80);
2707        let mut delta = Vec::new();
2708        write_delta_varint(&mut delta, base.len() as u64);
2709        write_delta_varint(&mut delta, result.len() as u64);
2710        delta.push(0x90);
2711        delta.push(base.len() as u8);
2712        delta.push(suffix.len() as u8);
2713        delta.extend_from_slice(suffix);
2714        delta
2715    }
2716
2717    fn write_delta_varint(out: &mut Vec<u8>, mut value: u64) {
2718        loop {
2719            let mut byte = (value as u8) & 0x7f;
2720            value >>= 7;
2721            if value != 0 {
2722                byte |= 0x80;
2723            }
2724            out.push(byte);
2725            if value == 0 {
2726                break;
2727            }
2728        }
2729    }
2730
2731    fn write_pack_entry_header_kind(out: &mut Vec<u8>, type_code: u8, mut size: u64) {
2732        let mut byte = (type_code << 4) | ((size as u8) & 0x0f);
2733        size >>= 4;
2734        if size != 0 {
2735            byte |= 0x80;
2736        }
2737        out.push(byte);
2738        while size != 0 {
2739            let mut byte = (size as u8) & 0x7f;
2740            size >>= 7;
2741            if size != 0 {
2742                byte |= 0x80;
2743            }
2744            out.push(byte);
2745        }
2746    }
2747
2748    fn write_ofs_delta_offset(out: &mut Vec<u8>, relative: usize) {
2749        assert!(relative < 0x80);
2750        out.push(relative as u8);
2751    }
2752
2753    fn single_entry_index(
2754        format: ObjectFormat,
2755        oid: ObjectId,
2756        crc32: u32,
2757        offset: u32,
2758        pack_checksum: ObjectId,
2759    ) -> Vec<u8> {
2760        let mut index = Vec::new();
2761        index.extend_from_slice(&[0xff, b't', b'O', b'c']);
2762        index.extend_from_slice(&2u32.to_be_bytes());
2763        for idx in 0..256 {
2764            let count = if idx >= usize::from(oid.as_bytes()[0]) {
2765                1u32
2766            } else {
2767                0u32
2768            };
2769            index.extend_from_slice(&count.to_be_bytes());
2770        }
2771        index.extend_from_slice(oid.as_bytes());
2772        index.extend_from_slice(&crc32.to_be_bytes());
2773        index.extend_from_slice(&offset.to_be_bytes());
2774        index.extend_from_slice(pack_checksum.as_bytes());
2775        let checksum =
2776            sley_core::digest_bytes(format, &index).expect("test operation should succeed");
2777        index.extend_from_slice(checksum.as_bytes());
2778        index
2779    }
2780
2781    fn single_entry_index_v1(
2782        format: ObjectFormat,
2783        oid: ObjectId,
2784        offset: u32,
2785        pack_checksum: ObjectId,
2786    ) -> Vec<u8> {
2787        let mut index = Vec::new();
2788        for idx in 0..256 {
2789            let count = if idx >= usize::from(oid.as_bytes()[0]) {
2790                1u32
2791            } else {
2792                0u32
2793            };
2794            index.extend_from_slice(&count.to_be_bytes());
2795        }
2796        index.extend_from_slice(&offset.to_be_bytes());
2797        index.extend_from_slice(oid.as_bytes());
2798        index.extend_from_slice(pack_checksum.as_bytes());
2799        let checksum =
2800            sley_core::digest_bytes(format, &index).expect("test operation should succeed");
2801        index.extend_from_slice(checksum.as_bytes());
2802        index
2803    }
2804
2805    fn pack_reverse_index(
2806        format: ObjectFormat,
2807        positions: &[u32],
2808        pack_checksum: ObjectId,
2809    ) -> Vec<u8> {
2810        let mut reverse_index = Vec::new();
2811        reverse_index.extend_from_slice(b"RIDX");
2812        reverse_index.extend_from_slice(&1u32.to_be_bytes());
2813        reverse_index.extend_from_slice(&hash_function_id(format).to_be_bytes());
2814        for position in positions {
2815            reverse_index.extend_from_slice(&position.to_be_bytes());
2816        }
2817        reverse_index.extend_from_slice(pack_checksum.as_bytes());
2818        let checksum =
2819            sley_core::digest_bytes(format, &reverse_index).expect("test operation should succeed");
2820        reverse_index.extend_from_slice(checksum.as_bytes());
2821        reverse_index
2822    }
2823
2824    fn pack_mtimes(format: ObjectFormat, mtimes: &[u32], pack_checksum: ObjectId) -> Vec<u8> {
2825        let mut out = Vec::new();
2826        out.extend_from_slice(b"MTME");
2827        out.extend_from_slice(&1u32.to_be_bytes());
2828        out.extend_from_slice(&hash_function_id(format).to_be_bytes());
2829        for mtime in mtimes {
2830            out.extend_from_slice(&mtime.to_be_bytes());
2831        }
2832        out.extend_from_slice(pack_checksum.as_bytes());
2833        let checksum =
2834            sley_core::digest_bytes(format, &out).expect("test operation should succeed");
2835        out.extend_from_slice(checksum.as_bytes());
2836        out
2837    }
2838
2839    fn midx_chunks_with_pack_names(
2840        _format: ObjectFormat,
2841        pack_names: Vec<u8>,
2842        entries: &[(ObjectId, u32, u64)],
2843    ) -> Vec<([u8; 4], Vec<u8>)> {
2844        let mut entries = entries.to_vec();
2845        entries.sort_by(|left, right| left.0.as_bytes().cmp(right.0.as_bytes()));
2846        let object_ids: Vec<ObjectId> = entries.iter().map(|entry| entry.0).collect();
2847        let mut large_offsets = Vec::new();
2848        let mut chunks = vec![
2849            (*b"PNAM", pack_names),
2850            (*b"OIDF", midx_oid_fanout(&object_ids)),
2851            (*b"OIDL", midx_oid_lookup(&object_ids)),
2852            (
2853                *b"OOFF",
2854                midx_ooff_entries(
2855                    &entries
2856                        .iter()
2857                        .map(|(_oid, pack_int_id, offset)| (*pack_int_id, *offset))
2858                        .collect::<Vec<_>>(),
2859                    &mut large_offsets,
2860                ),
2861            ),
2862        ];
2863        if !large_offsets.is_empty() {
2864            chunks.push((*b"LOFF", large_offsets));
2865        }
2866        chunks
2867    }
2868
2869    fn midx_oid_fanout(object_ids: &[ObjectId]) -> Vec<u8> {
2870        let mut counts = [0u32; 256];
2871        for oid in object_ids {
2872            counts[oid.as_bytes()[0] as usize] += 1;
2873        }
2874        let mut running = 0u32;
2875        let mut out = Vec::new();
2876        for count in counts {
2877            running += count;
2878            out.extend_from_slice(&running.to_be_bytes());
2879        }
2880        out
2881    }
2882
2883    fn midx_oid_lookup(object_ids: &[ObjectId]) -> Vec<u8> {
2884        let mut out = Vec::new();
2885        for oid in object_ids {
2886            out.extend_from_slice(oid.as_bytes());
2887        }
2888        out
2889    }
2890
2891    fn midx_ooff_entries(entries: &[(u32, u64)], large_offsets: &mut Vec<u8>) -> Vec<u8> {
2892        let mut out = Vec::new();
2893        for (pack_int_id, offset) in entries {
2894            out.extend_from_slice(&pack_int_id.to_be_bytes());
2895            if *offset < 0x8000_0000 {
2896                out.extend_from_slice(&(*offset as u32).to_be_bytes());
2897            } else {
2898                let large_idx = (large_offsets.len() / 8) as u32;
2899                out.extend_from_slice(&(0x8000_0000 | large_idx).to_be_bytes());
2900                large_offsets.extend_from_slice(&offset.to_be_bytes());
2901            }
2902        }
2903        out
2904    }
2905
2906    fn midx_u32_table(values: &[u32]) -> Vec<u8> {
2907        let mut out = Vec::new();
2908        for value in values {
2909            out.extend_from_slice(&value.to_be_bytes());
2910        }
2911        out
2912    }
2913
2914    fn midx_bitmap_packs(entries: &[(u32, u32)]) -> Vec<u8> {
2915        let mut out = Vec::new();
2916        for (bitmap_pos, bitmap_nr) in entries {
2917            out.extend_from_slice(&bitmap_pos.to_be_bytes());
2918            out.extend_from_slice(&bitmap_nr.to_be_bytes());
2919        }
2920        out
2921    }
2922
2923    fn multi_pack_index(
2924        format: ObjectFormat,
2925        version: u8,
2926        pack_count: u32,
2927        chunks: &[([u8; 4], Vec<u8>)],
2928    ) -> Vec<u8> {
2929        let lookup_len = (chunks.len() + 1) * 12;
2930        let mut out = Vec::new();
2931        out.extend_from_slice(b"MIDX");
2932        out.push(version);
2933        out.push(hash_function_id(format) as u8);
2934        out.push(chunks.len() as u8);
2935        out.push(0);
2936        out.extend_from_slice(&pack_count.to_be_bytes());
2937        let mut chunk_offset = (12 + lookup_len) as u64;
2938        for (id, data) in chunks {
2939            out.extend_from_slice(id);
2940            out.extend_from_slice(&chunk_offset.to_be_bytes());
2941            chunk_offset += data.len() as u64;
2942        }
2943        out.extend_from_slice(&[0, 0, 0, 0]);
2944        out.extend_from_slice(&chunk_offset.to_be_bytes());
2945        for (_id, data) in chunks {
2946            out.extend_from_slice(data);
2947        }
2948        let checksum =
2949            sley_core::digest_bytes(format, &out).expect("test operation should succeed");
2950        out.extend_from_slice(checksum.as_bytes());
2951        out
2952    }
2953
2954    // ---- EWAH encoder / bitmap writer tests ------------------------------
2955
2956    fn pack_checksum_sha1() -> ObjectId {
2957        sley_core::digest_bytes(ObjectFormat::Sha1, b"pack").expect("test operation should succeed")
2958    }
2959
2960    fn parse_ewah_bytes(bytes: &[u8]) -> EwahBitmap {
2961        // Wrap the EWAH body with the surrounding offset bookkeeping the parser
2962        // expects: a checksum offset that lies just past the serialised bitmap.
2963        let mut offset = 0usize;
2964        let checksum_offset = bytes.len();
2965        parse_bitmap_ewah(bytes, &mut offset, checksum_offset, 0)
2966            .expect("test operation should succeed")
2967    }
2968
2969    #[test]
2970    fn ewah_encodes_single_literal_word_matching_helper() {
2971        // A bitmap whose only word is a literal must serialise as one RLW with
2972        // literal_len == 1 followed by the literal, identical to the test
2973        // helper used by the existing parser tests.
2974        let ewah = EwahBitmap::from_words(64, &[0b101]).expect("test operation should succeed");
2975        assert_eq!(ewah.words, ewah_literal_words(&[0b101]));
2976        assert_eq!(ewah.rlw_position, 0);
2977        assert_eq!(ewah.bit_size, 64);
2978    }
2979
2980    #[test]
2981    fn ewah_byte_layout_is_big_endian() {
2982        let ewah = EwahBitmap::from_words(64, &[0x0102_0304_0506_0708])
2983            .expect("test operation should succeed");
2984        let bytes = ewah.to_bytes();
2985        let mut expected = Vec::new();
2986        expected.extend_from_slice(&64u32.to_be_bytes()); // bit_size
2987        expected.extend_from_slice(&2u32.to_be_bytes()); // word count: rlw + literal
2988        expected.extend_from_slice(&(1u64 << 33).to_be_bytes()); // rlw: literal_len = 1
2989        expected.extend_from_slice(&0x0102_0304_0506_0708u64.to_be_bytes());
2990        expected.extend_from_slice(&0u32.to_be_bytes()); // rlw_position
2991        assert_eq!(bytes, expected);
2992    }
2993
2994    #[test]
2995    fn ewah_empty_bitmap_serialises_like_git() {
2996        let ewah = EwahBitmap::empty();
2997        let bytes = ewah.to_bytes();
2998        // bit_size = 0, word_count = 0, rlw_position = 0.
2999        assert_eq!(bytes, vec![0u8; 12]);
3000        // It must still parse and decode to nothing.
3001        let parsed = parse_ewah_bytes(&bytes);
3002        assert_eq!(parsed, ewah);
3003        assert!(
3004            parsed
3005                .to_positions()
3006                .expect("test operation should succeed")
3007                .is_empty()
3008        );
3009    }
3010
3011    #[test]
3012    fn ewah_compresses_clean_zero_run() {
3013        // Three all-zero words followed by a literal: the encoder should emit a
3014        // single RLW carrying a run of 3 clean-zero words plus one literal.
3015        let ewah =
3016            EwahBitmap::from_words(256, &[0, 0, 0, 0b1]).expect("test operation should succeed");
3017        assert_eq!(ewah.words.len(), 2, "expected one RLW plus one literal");
3018        let rlw = ewah.words[0];
3019        assert_eq!(rlw & 1, 0, "run bit should be zero");
3020        assert_eq!((rlw >> 1) & 0xffff_ffff, 3, "run length should be 3");
3021        assert_eq!(rlw >> 33, 1, "literal length should be 1");
3022        assert_eq!(ewah.words[1], 0b1);
3023    }
3024
3025    #[test]
3026    fn ewah_compresses_clean_ones_run() {
3027        let ewah = EwahBitmap::from_words(192, &[u64::MAX, u64::MAX, u64::MAX])
3028            .expect("test operation should succeed");
3029        // Pure run of ones, no literals: one RLW only.
3030        assert_eq!(ewah.words.len(), 1);
3031        let rlw = ewah.words[0];
3032        assert_eq!(rlw & 1, 1, "run bit should be one");
3033        assert_eq!((rlw >> 1) & 0xffff_ffff, 3, "run length should be 3");
3034        assert_eq!(rlw >> 33, 0, "no literals");
3035    }
3036
3037    #[test]
3038    fn ewah_run_then_literal_then_run_roundtrips() {
3039        let words = vec![0, 0, 0xdead_beef, u64::MAX, u64::MAX, 0, 0xabc];
3040        let bit_size = (words.len() * 64) as u32;
3041        let ewah = EwahBitmap::from_words(bit_size, &words).expect("test operation should succeed");
3042        assert_eq!(
3043            ewah.to_words().expect("test operation should succeed"),
3044            words
3045        );
3046    }
3047
3048    #[test]
3049    fn ewah_drops_trailing_clean_zero_words() {
3050        // Trailing all-zero words beyond a literal carry no information and git
3051        // does not serialise them, but to_words() restores them up to bit_size.
3052        let words = vec![0b1, 0, 0, 0];
3053        let ewah = EwahBitmap::from_words(1, &words).expect("test operation should succeed");
3054        // bit_size of 1 means a single backing word.
3055        assert_eq!(ewah.bit_size, 1);
3056        assert_eq!(
3057            ewah.to_words().expect("test operation should succeed"),
3058            vec![0b1]
3059        );
3060    }
3061
3062    #[test]
3063    fn ewah_from_positions_roundtrips_via_positions() {
3064        let positions = [0u32, 1, 63, 64, 65, 200, 511];
3065        let ewah =
3066            EwahBitmap::from_positions(512, &positions).expect("test operation should succeed");
3067        let mut decoded = ewah.to_positions().expect("test operation should succeed");
3068        decoded.sort_unstable();
3069        assert_eq!(decoded, positions);
3070    }
3071
3072    #[test]
3073    fn ewah_from_positions_dedupes_and_orders() {
3074        let ewah = EwahBitmap::from_positions(128, &[100, 5, 100, 5, 5])
3075            .expect("test operation should succeed");
3076        assert_eq!(
3077            ewah.to_positions().expect("test operation should succeed"),
3078            vec![5, 100]
3079        );
3080    }
3081
3082    #[test]
3083    fn ewah_huge_zero_run_spans_multiple_rlws() {
3084        // A run longer than the 32-bit running-length field forces the encoder
3085        // to emit more than one RLW. Use one literal bit far out, with a bit
3086        // size large enough to exceed u32::MAX clean words is impractical, so
3087        // assert the field arithmetic via a direct builder run instead.
3088        let mut builder = EwahBuilder::new(0);
3089        builder.add_empty_words(false, 0xffff_ffff);
3090        builder.add_empty_words(false, 5);
3091        let ewah = builder.finish().expect("test operation should succeed");
3092        assert_eq!(ewah.words.len(), 2, "run split across two RLWs");
3093        assert_eq!((ewah.words[0] >> 1) & 0xffff_ffff, 0xffff_ffff);
3094        assert_eq!(ewah.words[1] & 1, 0);
3095        assert_eq!((ewah.words[1] >> 1) & 0xffff_ffff, 5);
3096        assert_eq!(ewah.rlw_position, 1);
3097    }
3098
3099    #[test]
3100    fn ewah_from_words_rejects_oversized_bit_size() {
3101        // bit_size demands two words but only one is supplied.
3102        assert!(EwahBitmap::from_words(65, &[0]).is_err());
3103    }
3104
3105    #[test]
3106    fn ewah_from_positions_rejects_out_of_range() {
3107        assert!(EwahBitmap::from_positions(64, &[64]).is_err());
3108    }
3109
3110    #[test]
3111    fn ewah_serialised_bytes_reparse_to_equal_bitmap() {
3112        // Exercise the full encode -> serialise -> parse loop for a non-trivial
3113        // pattern and assert structural equality against the parser's model.
3114        let words = vec![0, u64::MAX, 0x1234_5678_9abc_def0, 0, 0, 0xff];
3115        let bit_size = (words.len() * 64) as u32;
3116        let ewah = EwahBitmap::from_words(bit_size, &words).expect("test operation should succeed");
3117        let bytes = ewah.to_bytes();
3118        let parsed = parse_ewah_bytes(&bytes);
3119        assert_eq!(parsed, ewah);
3120        assert_eq!(
3121            parsed.to_words().expect("test operation should succeed"),
3122            words
3123        );
3124    }
3125
3126    #[test]
3127    fn pack_bitmap_index_write_parse_roundtrip_sha1() {
3128        // commit, tree, blob in pack order; one selected commit reaching all.
3129        let object_types = [ObjectType::Commit, ObjectType::Tree, ObjectType::Blob];
3130        let bytes = write_bitmap(
3131            ObjectFormat::Sha1,
3132            pack_checksum_sha1(),
3133            &object_types,
3134            &[(0u32, 0u32, vec![1u32, 2u32])],
3135            None,
3136        )
3137        .expect("test operation should succeed");
3138        assert_eq!(&bytes[..4], b"BITM");
3139
3140        let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 3)
3141            .expect("test operation should succeed");
3142        assert_eq!(parsed.version, 1);
3143        assert_eq!(parsed.options, PackBitmapIndex::OPTION_FULL_DAG);
3144        assert_eq!(parsed.pack_checksum, pack_checksum_sha1());
3145        assert_eq!(
3146            parsed
3147                .type_bitmaps
3148                .commits
3149                .to_positions()
3150                .expect("test operation should succeed"),
3151            vec![0]
3152        );
3153        assert_eq!(
3154            parsed
3155                .type_bitmaps
3156                .trees
3157                .to_positions()
3158                .expect("test operation should succeed"),
3159            vec![1]
3160        );
3161        assert_eq!(
3162            parsed
3163                .type_bitmaps
3164                .blobs
3165                .to_positions()
3166                .expect("test operation should succeed"),
3167            vec![2]
3168        );
3169        assert!(
3170            parsed
3171                .type_bitmaps
3172                .tags
3173                .to_positions()
3174                .expect("test operation should succeed")
3175                .is_empty()
3176        );
3177        assert_eq!(parsed.entries.len(), 1);
3178        let entry = parsed
3179            .entry_for_index_position(0)
3180            .expect("test operation should succeed");
3181        assert_eq!(entry.xor_offset, 0);
3182        assert_eq!(entry.flags, 0);
3183        assert_eq!(
3184            entry
3185                .bitmap
3186                .to_positions()
3187                .expect("test operation should succeed"),
3188            vec![0, 1, 2]
3189        );
3190        assert_eq!(parsed.name_hash_cache, None);
3191    }
3192
3193    #[test]
3194    fn pack_bitmap_index_write_parse_roundtrip_sha256() {
3195        let pack_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
3196            .expect("test operation should succeed");
3197        let object_types = [ObjectType::Commit, ObjectType::Tree];
3198        let bytes = write_bitmap(
3199            ObjectFormat::Sha256,
3200            pack_checksum.clone(),
3201            &object_types,
3202            &[(0u32, 0u32, vec![1u32])],
3203            None,
3204        )
3205        .expect("test operation should succeed");
3206        let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha256, 2)
3207            .expect("test operation should succeed");
3208        assert_eq!(parsed.format, ObjectFormat::Sha256);
3209        assert_eq!(parsed.pack_checksum, pack_checksum);
3210        assert_eq!(parsed.index_checksum.format(), ObjectFormat::Sha256);
3211        assert_eq!(
3212            parsed.entries[0]
3213                .bitmap
3214                .to_positions()
3215                .expect("test operation should succeed"),
3216            vec![0, 1]
3217        );
3218    }
3219
3220    #[test]
3221    fn pack_bitmap_index_write_includes_name_hash_cache() {
3222        let object_types = [ObjectType::Commit, ObjectType::Tree, ObjectType::Blob];
3223        let cache = vec![0x1111_1111u32, 0x2222_2222, 0x3333_3333];
3224        let bytes = write_bitmap(
3225            ObjectFormat::Sha1,
3226            pack_checksum_sha1(),
3227            &object_types,
3228            &[(0u32, 0u32, vec![1u32, 2u32])],
3229            Some(cache.clone()),
3230        )
3231        .expect("test operation should succeed");
3232        let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 3)
3233            .expect("test operation should succeed");
3234        assert_eq!(
3235            parsed.options,
3236            PackBitmapIndex::OPTION_FULL_DAG | PackBitmapIndex::OPTION_HASH_CACHE
3237        );
3238        assert_eq!(parsed.name_hash_cache, Some(cache));
3239    }
3240
3241    #[test]
3242    fn pack_bitmap_writer_supports_multiple_commits() {
3243        let object_types = [
3244            ObjectType::Commit,
3245            ObjectType::Commit,
3246            ObjectType::Tree,
3247            ObjectType::Blob,
3248        ];
3249        let mut writer =
3250            PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3251                .expect("test operation should succeed");
3252        writer
3253            .add_commit(0, 0, &[2, 3])
3254            .expect("test operation should succeed");
3255        writer
3256            .add_commit(1, 1, &[2])
3257            .expect("test operation should succeed");
3258        let bytes = writer.write().expect("test operation should succeed");
3259        let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 4)
3260            .expect("test operation should succeed");
3261        assert_eq!(parsed.entries.len(), 2);
3262        assert_eq!(
3263            parsed
3264                .type_bitmaps
3265                .commits
3266                .to_positions()
3267                .expect("test operation should succeed"),
3268            vec![0, 1]
3269        );
3270        let first = parsed
3271            .entry_for_index_position(0)
3272            .expect("test operation should succeed");
3273        assert_eq!(
3274            first
3275                .bitmap
3276                .to_positions()
3277                .expect("test operation should succeed"),
3278            vec![0, 2, 3]
3279        );
3280        let second = parsed
3281            .entry_for_index_position(1)
3282            .expect("test operation should succeed");
3283        assert_eq!(
3284            second
3285                .bitmap
3286                .to_positions()
3287                .expect("test operation should succeed"),
3288            vec![1, 2]
3289        );
3290    }
3291
3292    #[test]
3293    fn pack_bitmap_index_recomputes_checksum_on_write() {
3294        // The provided index_checksum field is ignored; write recomputes it so
3295        // a bogus placeholder still produces a valid, parseable file.
3296        let object_types = [ObjectType::Commit, ObjectType::Blob];
3297        let writer = PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3298            .expect("test operation should succeed");
3299        let mut index = writer.build().expect("test operation should succeed");
3300        // build() sets an all-zero placeholder checksum.
3301        assert_eq!(index.index_checksum.as_bytes(), [0u8; 20]);
3302        index.entries.clear(); // mutate the model after build
3303        index.entries.push(PackBitmapEntry {
3304            object_position: 0,
3305            xor_offset: 0,
3306            flags: 0,
3307            bitmap: EwahBitmap::from_positions(2, &[0, 1]).expect("test operation should succeed"),
3308        });
3309        let bytes = index.write().expect("test operation should succeed");
3310        // Parsing validates the trailing checksum, so a wrong checksum fails.
3311        let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, 2)
3312            .expect("test operation should succeed");
3313        assert_ne!(parsed.index_checksum.as_bytes(), [0u8; 20]);
3314    }
3315
3316    #[test]
3317    fn pack_bitmap_writer_rejects_non_commit_selection() {
3318        let object_types = [ObjectType::Commit, ObjectType::Blob];
3319        let mut writer =
3320            PackBitmapWriter::new(ObjectFormat::Sha1, pack_checksum_sha1(), &object_types)
3321                .expect("test operation should succeed");
3322        // Position 1 is a blob, not a commit.
3323        assert!(writer.add_commit(1, 1, &[]).is_err());
3324        // Position 5 is out of range entirely.
3325        assert!(writer.add_commit(5, 5, &[]).is_err());
3326        // Index position out of range.
3327        assert!(writer.add_commit(0, 5, &[]).is_err());
3328        // Reachable position out of range.
3329        assert!(writer.add_commit(0, 0, &[9]).is_err());
3330    }
3331
3332    #[test]
3333    fn pack_bitmap_writer_rejects_checksum_format_mismatch() {
3334        let sha256_checksum = sley_core::digest_bytes(ObjectFormat::Sha256, b"pack")
3335            .expect("test operation should succeed");
3336        assert!(
3337            PackBitmapWriter::new(ObjectFormat::Sha1, sha256_checksum, &[ObjectType::Commit])
3338                .is_err()
3339        );
3340    }
3341
3342    #[test]
3343    fn pack_bitmap_writer_rejects_bad_name_hash_cache_len() {
3344        let writer = PackBitmapWriter::new(
3345            ObjectFormat::Sha1,
3346            pack_checksum_sha1(),
3347            &[ObjectType::Commit],
3348        )
3349        .expect("test operation should succeed");
3350        assert!(writer.with_name_hash_cache(vec![1, 2]).is_err());
3351    }
3352
3353    #[test]
3354    fn pack_bitmap_index_write_rejects_inconsistent_cache_flag() {
3355        let mut index = PackBitmapWriter::new(
3356            ObjectFormat::Sha1,
3357            pack_checksum_sha1(),
3358            &[ObjectType::Commit],
3359        )
3360        .expect("test operation should succeed")
3361        .build()
3362        .expect("test operation should succeed");
3363        // Flag set but no cache present.
3364        index.options |= PackBitmapIndex::OPTION_HASH_CACHE;
3365        assert!(index.write().is_err());
3366        // Cache present but flag missing.
3367        index.options = PackBitmapIndex::OPTION_FULL_DAG;
3368        index.name_hash_cache = Some(vec![0]);
3369        assert!(index.write().is_err());
3370    }
3371
3372    #[test]
3373    fn write_bitmap_roundtrips_through_upstream_git_parser() {
3374        // Build a real pack with git, then overwrite reachability with our own
3375        // writer using the real pack checksum and object types, and confirm our
3376        // bytes parse under the same parser that reads upstream bitmaps.
3377        let root = unique_temp_dir("git-pack-bitmap-writer");
3378        fs::create_dir_all(&root).expect("test operation should succeed");
3379        {
3380            run_git_success(&root, &["init", "-q", "-b", "main"]);
3381            run_git_success(
3382                &root,
3383                &[
3384                    "-c",
3385                    "user.name=Example User",
3386                    "-c",
3387                    "user.email=example@example.invalid",
3388                    "commit",
3389                    "--allow-empty",
3390                    "-q",
3391                    "-m",
3392                    "one",
3393                ],
3394            );
3395            run_git_success(&root, &["repack", "-adb"]);
3396            let pack_dir = root.join(".git").join("objects").join("pack");
3397            let idx_path = single_path_with_extension(&pack_dir, "idx");
3398            let index = PackIndex::parse(
3399                &fs::read(idx_path).expect("test operation should succeed"),
3400                ObjectFormat::Sha1,
3401            )
3402            .expect("test operation should succeed");
3403            // Read object types from the pack so the type bitmaps are accurate.
3404            let pack_path = single_path_with_extension(&pack_dir, "pack");
3405            let pack =
3406                PackFile::parse_sha1(&fs::read(pack_path).expect("test operation should succeed"))
3407                    .expect("test operation should succeed");
3408            // Map each index entry (sorted by oid) to its pack offset, then to a
3409            // pack-order position so positions line up with the index ordering.
3410            let mut offsets: Vec<u64> = index.entries.iter().map(|entry| entry.offset).collect();
3411            offsets.sort_unstable();
3412            let position_of = |offset: u64| -> u32 {
3413                offsets
3414                    .iter()
3415                    .position(|value| *value == offset)
3416                    .expect("test operation should succeed") as u32
3417            };
3418            let mut object_types = vec![ObjectType::Blob; index.entries.len()];
3419            for entry in &index.entries {
3420                let position = position_of(entry.offset) as usize;
3421                // Find the parsed object at this pack offset to read its type.
3422                if let Some(parsed) = pack
3423                    .entries
3424                    .iter()
3425                    .find(|po| po.entry.offset == entry.offset)
3426                {
3427                    object_types[position] = parsed.object.object_type;
3428                }
3429            }
3430            // Select the first commit position we find and reach everything.
3431            let commit_position = object_types
3432                .iter()
3433                .position(|ty| *ty == ObjectType::Commit)
3434                .expect("test operation should succeed") as u32;
3435            // The entry records the commit's position in the oid-sorted index.
3436            let commit_index_position = index
3437                .entries
3438                .iter()
3439                .position(|entry| position_of(entry.offset) == commit_position)
3440                .expect("test operation should succeed")
3441                as u32;
3442            let reachable: Vec<u32> = (0..index.entries.len() as u32).collect();
3443            let bytes = write_bitmap(
3444                ObjectFormat::Sha1,
3445                index.pack_checksum.clone(),
3446                &object_types,
3447                &[(commit_position, commit_index_position, reachable)],
3448                None,
3449            )
3450            .expect("test operation should succeed");
3451            let parsed = PackBitmapIndex::parse(&bytes, ObjectFormat::Sha1, index.entries.len())
3452                .expect("test operation should succeed");
3453            assert_eq!(parsed.pack_checksum, index.pack_checksum);
3454            assert_eq!(parsed.entries.len(), 1);
3455            assert_eq!(
3456                parsed.entries[0]
3457                    .bitmap
3458                    .to_positions()
3459                    .expect("test operation should succeed")
3460                    .len(),
3461                index.entries.len()
3462            );
3463        };
3464        let _ = fs::remove_dir_all(&root);
3465    }
3466}