1use super::*;
6
7impl PackFile {
8 pub fn parse_sha1(bytes: &[u8]) -> Result<Self> {
9 Self::parse_sha1_with_limits(bytes, PackReadLimits::default())
10 }
11
12 pub fn parse_sha1_with_limits(bytes: &[u8], limits: PackReadLimits) -> Result<Self> {
13 Self::parse_with_limits(bytes, ObjectFormat::Sha1, limits)
14 }
15
16 pub fn parse(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
17 Self::parse_with_limits(bytes, format, PackReadLimits::default())
18 }
19
20 pub fn parse_with_limits(
22 bytes: &[u8],
23 format: ObjectFormat,
24 limits: PackReadLimits,
25 ) -> Result<Self> {
26 Self::parse_with_base_and_limits(bytes, format, |_| Ok(None), limits)
27 }
28
29 pub fn parse_bundle(bundle: &Bundle) -> Result<Self> {
30 Self::parse_bundle_with_limits(bundle, PackReadLimits::default())
31 }
32
33 pub fn parse_bundle_with_limits(bundle: &Bundle, limits: PackReadLimits) -> Result<Self> {
34 Self::parse_with_limits(&bundle.pack, bundle.format, limits)
35 }
36
37 pub fn index_pack(bytes: &[u8], format: ObjectFormat) -> Result<PackWrite> {
38 Self::index_pack_with_limits(bytes, format, PackReadLimits::default())
39 }
40
41 pub fn index_pack_with_limits(
42 bytes: &[u8],
43 format: ObjectFormat,
44 limits: PackReadLimits,
45 ) -> Result<PackWrite> {
46 let PackIndexBuild {
47 index,
48 pack_checksum,
49 entries,
50 } = PackIndex::write_v2_for_pack_with_limits(bytes, format, limits)?;
51 Ok(PackWrite {
52 pack: bytes.to_vec(),
53 index,
54 checksum: pack_checksum,
55 entries,
56 delta_count: 0,
57 })
58 }
59
60 pub fn parse_thin<F>(bytes: &[u8], format: ObjectFormat, external_base: F) -> Result<Self>
61 where
62 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
63 {
64 Self::parse_thin_with_limits(bytes, format, external_base, PackReadLimits::default())
65 }
66
67 pub fn parse_thin_with_limits<F>(
68 bytes: &[u8],
69 format: ObjectFormat,
70 external_base: F,
71 limits: PackReadLimits,
72 ) -> Result<Self>
73 where
74 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
75 {
76 Self::parse_with_base_and_limits(bytes, format, external_base, limits)
77 }
78
79 pub(crate) fn parse_with_base_and_limits<F>(
80 bytes: &[u8],
81 format: ObjectFormat,
82 mut external_base: F,
83 limits: PackReadLimits,
84 ) -> Result<Self>
85 where
86 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
87 {
88 let trailer_len = format.raw_len();
89 if bytes.len() < 12 + trailer_len {
90 return Err(GitError::InvalidFormat("pack file too short".into()));
91 }
92 let trailer_offset = bytes.len() - trailer_len;
93 let entry_region = pack_entry_region(bytes, trailer_offset)?;
94 let checksum = sley_core::digest_bytes(format, entry_region)?;
95 let expected = ObjectId::from_raw(format, &bytes[trailer_offset..])?;
96 if checksum != expected {
97 return Err(GitError::InvalidFormat(format!(
98 "pack checksum mismatch: expected {expected}, got {checksum}"
99 )));
100 }
101
102 if &bytes[..4] != b"PACK" {
103 return Err(GitError::InvalidFormat("missing PACK signature".into()));
104 }
105 let version = u32_be(&bytes[4..8]);
106 if version != 2 && version != 3 {
107 return Err(GitError::Unsupported(format!("pack version {version}")));
108 }
109 let count = checked_pack_object_count(
112 u32_be(&bytes[8..12]),
113 (trailer_offset.saturating_sub(12)) as u64,
114 )?;
115 let mut offset = 12usize;
116 let mut entries = Vec::with_capacity(pack_entry_prealloc(count));
117 for _ in 0..count {
118 let entry_offset = offset;
119 let header = parse_entry_header(entry_region, &mut offset)?;
120 let base = match header.kind {
121 PackObjectKind::OfsDelta => Some(DeltaBase::Offset(parse_ofs_delta_base_offset(
122 entry_region,
123 &mut offset,
124 entry_offset as u64,
125 )?)),
126 PackObjectKind::RefDelta => {
127 let hash_len = format.raw_len();
128 if offset + hash_len > trailer_offset {
129 return Err(GitError::InvalidFormat(
130 "truncated ref-delta base object id".into(),
131 ));
132 }
133 let oid = ObjectId::from_raw(format, &entry_region[offset..offset + hash_len])?;
134 offset += hash_len;
135 Some(DeltaBase::Ref(oid))
136 }
137 _ => None,
138 };
139 let mut body = Vec::new();
140 let consumed = inflate_into(
141 &entry_region[offset..],
142 &mut body,
143 header.size.min(usize::MAX as u64) as usize,
144 )?;
145 if body.len() as u64 != header.size {
146 return Err(GitError::InvalidObject(format!(
147 "pack object declared {} bytes, decoded {}",
148 header.size,
149 body.len()
150 )));
151 }
152 if consumed == 0 {
153 return Err(GitError::InvalidFormat(
154 "empty compressed pack entry".into(),
155 ));
156 }
157 offset = offset
158 .checked_add(consumed)
159 .ok_or_else(|| GitError::InvalidFormat("pack offset overflow".into()))?;
160 if offset > trailer_offset {
161 return Err(GitError::InvalidFormat(
162 "pack entry extends past checksum".into(),
163 ));
164 }
165 if let Some(base) = base {
166 entries.push(ParsedPackEntry::Delta {
167 base,
168 compressed_size: consumed as u64,
169 delta_size: header.size,
170 offset: entry_offset as u64,
171 delta: body,
172 });
173 } else {
174 let object_type = match header.kind {
175 PackObjectKind::Commit => ObjectType::Commit,
176 PackObjectKind::Tree => ObjectType::Tree,
177 PackObjectKind::Blob => ObjectType::Blob,
178 PackObjectKind::Tag => ObjectType::Tag,
179 PackObjectKind::OfsDelta | PackObjectKind::RefDelta => unreachable!(),
180 };
181 let object = EncodedObject::new(object_type, body);
182 let oid = object.object_id(format)?;
183 entries.push(ParsedPackEntry::Resolved(PackObject {
184 entry: PackEntry {
185 oid,
186 compressed_size: consumed as u64,
187 uncompressed_size: header.size,
188 offset: entry_offset as u64,
189 },
190 object,
191 }));
192 }
193 }
194 if offset != trailer_offset {
195 return Err(GitError::InvalidFormat(format!(
196 "pack has {} trailing bytes before checksum",
197 trailer_offset - offset
198 )));
199 }
200 Ok(Self {
201 version,
202 entries: resolve_pack_entries(entries, format, &mut external_base, limits)?,
203 checksum,
204 })
205 }
206
207 pub fn verify_pack_stats(bytes: &[u8], format: ObjectFormat) -> Result<PackVerifyStats> {
218 Self::verify_pack_stats_with_limits(bytes, format, PackReadLimits::default())
219 }
220
221 pub fn verify_pack_stats_with_limits(
222 bytes: &[u8],
223 format: ObjectFormat,
224 limits: PackReadLimits,
225 ) -> Result<PackVerifyStats> {
226 let pack = Self::parse_with_limits(bytes, format, limits)?;
230
231 let trailer_len = format.raw_len();
235 let trailer_offset = bytes.len() - trailer_len;
236 let entry_region = pack_entry_region(bytes, trailer_offset)?;
237 let count = checked_pack_object_count(
238 u32_be(&bytes[8..12]),
239 (trailer_offset.saturating_sub(12)) as u64,
240 )?;
241 let mut offset = 12usize;
242 let mut on_disk: Vec<OnDiskEntry> = Vec::with_capacity(pack_entry_prealloc(count));
248 for _ in 0..count {
249 let entry_offset = offset as u64;
250 let header = parse_entry_header(entry_region, &mut offset)?;
251 let stream_size = header.size;
252 let base = match header.kind {
253 PackObjectKind::OfsDelta => Some(DeltaBase::Offset(parse_ofs_delta_base_offset(
254 entry_region,
255 &mut offset,
256 entry_offset,
257 )?)),
258 PackObjectKind::RefDelta => {
259 let hash_len = format.raw_len();
260 if offset + hash_len > trailer_offset {
261 return Err(GitError::InvalidFormat(
262 "truncated ref-delta base object id".into(),
263 ));
264 }
265 let oid = ObjectId::from_raw(format, &entry_region[offset..offset + hash_len])?;
266 offset += hash_len;
267 Some(DeltaBase::Ref(oid))
268 }
269 _ => None,
270 };
271 let mut body = Vec::new();
273 let consumed = inflate_into(
274 &entry_region[offset..],
275 &mut body,
276 header.size.min(usize::MAX as u64) as usize,
277 )?;
278 offset = offset
279 .checked_add(consumed)
280 .ok_or_else(|| GitError::InvalidFormat("pack offset overflow".into()))?;
281 on_disk.push(OnDiskEntry {
282 offset: entry_offset,
283 base,
284 stream_size,
285 });
286 }
287
288 let mut resolved_by_offset: HashMap<u64, &PackObject> =
290 HashMap::with_capacity(pack.entries.len());
291 for object in &pack.entries {
292 resolved_by_offset.insert(object.entry.offset, object);
293 }
294 let mut oid_by_offset: HashMap<u64, ObjectId> = HashMap::with_capacity(on_disk.len());
296 for entry in &on_disk {
297 if let Some(object) = resolved_by_offset.get(&entry.offset) {
298 oid_by_offset.insert(entry.offset, object.entry.oid);
299 }
300 }
301 let mut index_by_offset: HashMap<u64, usize> = HashMap::with_capacity(on_disk.len());
303 for (idx, entry) in on_disk.iter().enumerate() {
304 index_by_offset.insert(entry.offset, idx);
305 }
306
307 let mut sorted_offsets: Vec<u64> = on_disk.iter().map(|entry| entry.offset).collect();
310 sorted_offsets.sort_unstable();
311 let mut next_offset: HashMap<u64, u64> = HashMap::with_capacity(sorted_offsets.len());
312 for window in sorted_offsets.windows(2) {
313 next_offset.insert(window[0], window[1]);
314 }
315 if let Some(last) = sorted_offsets.last() {
316 next_offset.insert(*last, trailer_offset as u64);
317 }
318
319 let mut depth = vec![None; on_disk.len()];
325 fn resolve_depth(
326 idx: usize,
327 on_disk: &[OnDiskEntry],
328 index_by_offset: &HashMap<u64, usize>,
329 offset_of_oid: &HashMap<ObjectId, u64>,
330 depth: &mut [Option<u32>],
331 ) -> u32 {
332 if let Some(d) = depth[idx] {
333 return d;
334 }
335 let computed = match &on_disk[idx].base {
336 None => 0,
337 Some(base) => {
338 let base_idx = match base {
339 DeltaBase::Offset(off) => index_by_offset.get(off).copied(),
340 DeltaBase::Ref(oid) => offset_of_oid
341 .get(oid)
342 .and_then(|off| index_by_offset.get(off).copied()),
343 };
344 match base_idx {
345 Some(bi) => {
346 resolve_depth(bi, on_disk, index_by_offset, offset_of_oid, depth) + 1
347 }
348 None => 1,
350 }
351 }
352 };
353 depth[idx] = Some(computed);
354 computed
355 }
356 let mut offset_of_oid: HashMap<ObjectId, u64> = HashMap::with_capacity(oid_by_offset.len());
357 for (off, oid) in &oid_by_offset {
358 offset_of_oid.insert(*oid, *off);
359 }
360 for idx in 0..on_disk.len() {
361 resolve_depth(idx, &on_disk, &index_by_offset, &offset_of_oid, &mut depth);
362 }
363
364 let mut stats = Vec::with_capacity(on_disk.len());
365 for (idx, entry) in on_disk.iter().enumerate() {
366 let off = entry.offset;
367 let object = resolved_by_offset.get(&off).ok_or_else(|| {
368 GitError::InvalidFormat("pack offset missing from resolved set".into())
369 })?;
370 let size_in_pack = next_offset
371 .get(&off)
372 .copied()
373 .unwrap_or(trailer_offset as u64)
374 .saturating_sub(off);
375 let base_oid = match &entry.base {
376 None => None,
377 Some(DeltaBase::Offset(base_off)) => oid_by_offset.get(base_off).copied(),
378 Some(DeltaBase::Ref(oid)) => Some(*oid),
379 };
380 stats.push(PackVerifyStat {
381 oid: object.entry.oid,
382 object_type: object.object.object_type,
383 size: entry.stream_size,
386 size_in_pack,
387 offset: off,
388 delta_depth: depth[idx].unwrap_or(0),
389 base_oid,
390 });
391 }
392 stats.sort_by_key(|stat| stat.offset);
394
395 Ok(PackVerifyStats {
396 objects: stats,
397 checksum: pack.checksum,
398 })
399 }
400}
401
402pub trait PackDeltaCache {
416 fn get(&self, offset: u64) -> Option<Arc<EncodedObject>>;
418 fn insert(&self, offset: u64, object: Arc<EncodedObject>);
420}
421
422pub(crate) struct NoopDeltaCache;
425
426impl PackDeltaCache for NoopDeltaCache {
427 fn get(&self, _offset: u64) -> Option<Arc<EncodedObject>> {
428 None
429 }
430 fn insert(&self, _offset: u64, _object: Arc<EncodedObject>) {}
431}
432
433thread_local! {
439 static INFLATE: RefCell<flate2::Decompress> = RefCell::new(flate2::Decompress::new(true));
440}
441
442pub(crate) fn inflate_into(
462 compressed: &[u8],
463 out: &mut Vec<u8>,
464 size_hint: usize,
465) -> Result<usize> {
466 INFLATE.with(|cell| {
467 let mut decompress = cell.borrow_mut();
468 decompress.reset(true);
469 out.reserve(inflate::bounded_inflate_reserve(
470 size_hint,
471 compressed.len(),
472 ));
473 let mut input = compressed;
474 let mut consumed_total = 0usize;
475 loop {
476 if out.len() == out.capacity() {
479 out.reserve(out.len().max(64));
480 }
481 let before_in = decompress.total_in();
482 let before_out = decompress.total_out();
483 let status = decompress
484 .decompress_vec(input, out, flate2::FlushDecompress::None)
485 .map_err(|err| GitError::InvalidObject(format!("zlib inflate failed: {err}")))?;
486 let consumed = (decompress.total_in() - before_in) as usize;
487 let produced = decompress.total_out() - before_out;
488 input = &input[consumed..];
489 consumed_total += consumed;
490 match status {
491 flate2::Status::StreamEnd => return Ok(consumed_total),
492 _ if consumed == 0 && produced == 0 => {
493 return Err(GitError::InvalidObject("truncated zlib stream".into()));
494 }
495 _ => {}
496 }
497 }
498 })
499}
500
501pub(crate) fn inflate_prefix(compressed: &[u8], max_out: usize, out: &mut Vec<u8>) -> Result<()> {
505 INFLATE.with(|cell| {
506 let mut decompress = cell.borrow_mut();
507 decompress.reset(true);
508 out.reserve(max_out.max(16));
509 let mut input = compressed;
510 while out.len() < max_out {
511 if out.len() == out.capacity() {
512 out.reserve(out.len().max(16));
513 }
514 let before_in = decompress.total_in();
515 let before_out = decompress.total_out();
516 let status = decompress
517 .decompress_vec(input, out, flate2::FlushDecompress::None)
518 .map_err(|err| GitError::InvalidObject(format!("zlib inflate failed: {err}")))?;
519 let consumed = (decompress.total_in() - before_in) as usize;
520 let produced = decompress.total_out() - before_out;
521 input = &input[consumed..];
522 if status == flate2::Status::StreamEnd || (consumed == 0 && produced == 0) {
523 break;
524 }
525 }
526 Ok(())
527 })
528}
529pub fn read_object_at_arc<F>(
537 pack_bytes: &[u8],
538 offset: u64,
539 format: ObjectFormat,
540 resolve_ref_base: F,
541) -> Result<Arc<EncodedObject>>
542where
543 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
544{
545 read_object_at_with_cache_arc(
546 pack_bytes,
547 offset,
548 format,
549 resolve_ref_base,
550 &NoopDeltaCache,
551 )
552}
553
554pub fn read_object_at_with_cache_arc<F, C>(
563 pack_bytes: &[u8],
564 offset: u64,
565 format: ObjectFormat,
566 mut resolve_ref_base: F,
567 cache: &C,
568) -> Result<Arc<EncodedObject>>
569where
570 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
571 C: PackDeltaCache + ?Sized,
572{
573 read_object_at_with_cache_and_ofs_base_arc(
574 pack_bytes,
575 offset,
576 format,
577 &mut resolve_ref_base,
578 |_offset| Ok(None),
579 cache,
580 )
581}
582
583pub fn read_object_at_with_cache_and_ofs_base_arc<F, G, C>(
589 pack_bytes: &[u8],
590 offset: u64,
591 format: ObjectFormat,
592 mut resolve_ref_base: F,
593 mut resolve_ofs_base: G,
594 cache: &C,
595) -> Result<Arc<EncodedObject>>
596where
597 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
598 G: FnMut(u64) -> Result<Option<Arc<EncodedObject>>>,
599 C: PackDeltaCache + ?Sized,
600{
601 read_object_at_inner(
602 pack_bytes,
603 offset,
604 format,
605 &mut resolve_ref_base,
606 &mut resolve_ofs_base,
607 cache,
608 )
609}
610
611pub fn read_object_at_with_ofs_base_arc<F, G>(
613 pack_bytes: &[u8],
614 offset: u64,
615 format: ObjectFormat,
616 resolve_ref_base: F,
617 resolve_ofs_base: G,
618) -> Result<Arc<EncodedObject>>
619where
620 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
621 G: FnMut(u64) -> Result<Option<Arc<EncodedObject>>>,
622{
623 read_object_at_with_cache_and_ofs_base_arc(
624 pack_bytes,
625 offset,
626 format,
627 resolve_ref_base,
628 resolve_ofs_base,
629 &NoopDeltaCache,
630 )
631}
632
633pub(crate) fn read_object_at_inner<F, G, C>(
634 pack_bytes: &[u8],
635 offset: u64,
636 format: ObjectFormat,
637 resolve_ref_base: &mut F,
638 resolve_ofs_base: &mut G,
639 cache: &C,
640) -> Result<Arc<EncodedObject>>
641where
642 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
643 G: FnMut(u64) -> Result<Option<Arc<EncodedObject>>>,
644 C: PackDeltaCache + ?Sized,
645{
646 if let Some(object) = cache.get(offset) {
649 return Ok(object);
650 }
651 let trailer_offset = pack_bytes
652 .len()
653 .checked_sub(format.raw_len())
654 .ok_or_else(|| GitError::InvalidFormat("pack smaller than its trailer".into()))?;
655 let entry_region = pack_entry_region(pack_bytes, trailer_offset)?;
656 let mut cursor = usize::try_from(offset)
657 .ok()
658 .filter(|&value| value < trailer_offset)
659 .ok_or_else(|| GitError::InvalidFormat("pack object offset out of range".into()))?;
660 let header = parse_entry_header(entry_region, &mut cursor)?;
661 let base = match header.kind {
662 PackObjectKind::OfsDelta => Some(DeltaBase::Offset(parse_ofs_delta_base_offset(
663 entry_region,
664 &mut cursor,
665 offset,
666 )?)),
667 PackObjectKind::RefDelta => {
668 let hash_len = format.raw_len();
669 if cursor + hash_len > trailer_offset {
670 return Err(GitError::InvalidFormat(
671 "truncated ref-delta base object id".into(),
672 ));
673 }
674 let oid = ObjectId::from_raw(format, &entry_region[cursor..cursor + hash_len])?;
675 cursor += hash_len;
676 Some(DeltaBase::Ref(oid))
677 }
678 _ => None,
679 };
680 let mut body = Vec::new();
681 inflate_into(
682 &entry_region[cursor..],
683 &mut body,
684 header.size.min(usize::MAX as u64) as usize,
685 )?;
686 if body.len() as u64 != header.size {
687 return Err(GitError::InvalidObject(format!(
688 "pack object declared {} bytes, decoded {}",
689 header.size,
690 body.len()
691 )));
692 }
693 let object = match base {
694 None => {
695 let object_type = match header.kind {
696 PackObjectKind::Commit => ObjectType::Commit,
697 PackObjectKind::Tree => ObjectType::Tree,
698 PackObjectKind::Blob => ObjectType::Blob,
699 PackObjectKind::Tag => ObjectType::Tag,
700 PackObjectKind::OfsDelta | PackObjectKind::RefDelta => {
701 return Err(GitError::InvalidFormat(
702 "delta pack entry decoded without a base".into(),
703 ));
704 }
705 };
706 Arc::new(EncodedObject::new(object_type, body))
707 }
708 Some(DeltaBase::Offset(base_offset)) => {
709 let base = match read_object_at_inner(
710 pack_bytes,
711 base_offset,
712 format,
713 resolve_ref_base,
714 resolve_ofs_base,
715 cache,
716 ) {
717 Ok(base) => base,
718 Err(pack_err) => match resolve_ofs_base(base_offset)? {
719 Some(base) => base,
720 None => return Err(pack_err),
721 },
722 };
723 let resolved = apply_pack_delta(&base.body, &body)?;
724 Arc::new(EncodedObject::new(base.object_type, resolved))
725 }
726 Some(DeltaBase::Ref(base_oid)) => {
727 let base = resolve_ref_base(&base_oid)?
728 .ok_or_else(|| GitError::not_found(format!("ref-delta base object {base_oid}")))?;
729 let resolved = apply_pack_delta(&base.body, &body)?;
730 Arc::new(EncodedObject::new(base.object_type, resolved))
731 }
732 };
733 cache.insert(offset, Arc::clone(&object));
737 Ok(object)
738}
739
740pub fn read_object_header_at<F>(
750 pack_bytes: &[u8],
751 offset: u64,
752 format: ObjectFormat,
753 mut resolve_ref_base_type: F,
754) -> Result<(ObjectType, u64)>
755where
756 F: FnMut(&ObjectId) -> Result<Option<ObjectType>>,
757{
758 read_object_header_at_inner(
759 pack_bytes,
760 offset,
761 format,
762 &mut resolve_ref_base_type,
763 &mut NoopHeaderTypeCache,
764 )
765}
766
767pub trait HeaderTypeCache {
784 fn get(&self, pack_offset: u64) -> Option<(ObjectType, u64)>;
786 fn put(&mut self, pack_offset: u64, header: (ObjectType, u64));
788}
789
790pub(crate) struct NoopHeaderTypeCache;
791
792impl HeaderTypeCache for NoopHeaderTypeCache {
793 fn get(&self, _pack_offset: u64) -> Option<(ObjectType, u64)> {
794 None
795 }
796 fn put(&mut self, _pack_offset: u64, _header: (ObjectType, u64)) {}
797}
798
799pub fn read_object_header_at_with_cache<F, C>(
805 pack_bytes: &[u8],
806 offset: u64,
807 format: ObjectFormat,
808 mut resolve_ref_base_type: F,
809 type_cache: &mut C,
810) -> Result<(ObjectType, u64)>
811where
812 F: FnMut(&ObjectId) -> Result<Option<ObjectType>>,
813 C: HeaderTypeCache + ?Sized,
814{
815 if let Some(header) = type_cache.get(offset) {
816 return Ok(header);
817 }
818 read_object_header_at_inner(
819 pack_bytes,
820 offset,
821 format,
822 &mut resolve_ref_base_type,
823 type_cache,
824 )
825}
826
827pub(crate) fn read_object_header_at_inner<F, C>(
828 pack_bytes: &[u8],
829 offset: u64,
830 format: ObjectFormat,
831 resolve_ref_base_type: &mut F,
832 type_cache: &mut C,
833) -> Result<(ObjectType, u64)>
834where
835 F: FnMut(&ObjectId) -> Result<Option<ObjectType>>,
836 C: HeaderTypeCache + ?Sized,
837{
838 let trailer_offset = pack_bytes
839 .len()
840 .checked_sub(format.raw_len())
841 .ok_or_else(|| GitError::InvalidFormat("pack smaller than its trailer".into()))?;
842 let entry_region = pack_entry_region(pack_bytes, trailer_offset)?;
843 let mut cursor = usize::try_from(offset)
844 .ok()
845 .filter(|&value| value < trailer_offset)
846 .ok_or_else(|| GitError::InvalidFormat("pack object offset out of range".into()))?;
847 let header = parse_entry_header(entry_region, &mut cursor)?;
848 let resolved = match header.kind {
849 PackObjectKind::Commit => (ObjectType::Commit, header.size),
850 PackObjectKind::Tree => (ObjectType::Tree, header.size),
851 PackObjectKind::Blob => (ObjectType::Blob, header.size),
852 PackObjectKind::Tag => (ObjectType::Tag, header.size),
853 PackObjectKind::OfsDelta => {
854 let base_offset = parse_ofs_delta_base_offset(entry_region, &mut cursor, offset)?;
855 let size = delta_result_size_from_stream(&entry_region[cursor..])?;
856 let base_type = match type_cache.get(base_offset) {
859 Some((base_type, _)) => base_type,
860 None => {
861 let (base_type, _) = read_object_header_at_inner(
862 pack_bytes,
863 base_offset,
864 format,
865 resolve_ref_base_type,
866 type_cache,
867 )?;
868 base_type
869 }
870 };
871 (base_type, size)
872 }
873 PackObjectKind::RefDelta => {
874 let hash_len = format.raw_len();
875 if cursor + hash_len > trailer_offset {
876 return Err(GitError::InvalidFormat(
877 "truncated ref-delta base object id".into(),
878 ));
879 }
880 let oid = ObjectId::from_raw(format, &entry_region[cursor..cursor + hash_len])?;
881 cursor += hash_len;
882 let size = delta_result_size_from_stream(&entry_region[cursor..])?;
883 let base_type = resolve_ref_base_type(&oid)?
884 .ok_or_else(|| GitError::not_found(format!("ref-delta base object {oid}")))?;
885 (base_type, size)
886 }
887 };
888 type_cache.put(offset, resolved);
891 Ok(resolved)
892}
893
894pub(crate) const DELTA_HEADER_PREFIX_LEN: usize = 32;
898
899pub(crate) fn delta_result_size_from_stream(compressed: &[u8]) -> Result<u64> {
902 let mut prefix = Vec::new();
903 inflate_prefix(compressed, DELTA_HEADER_PREFIX_LEN, &mut prefix)?;
904 decoded_delta_result_size(&prefix)
905}
906
907fn pack_entry_region(bytes: &[u8], trailer_offset: usize) -> Result<&[u8]> {
921 bytes
922 .get(..trailer_offset)
923 .ok_or_else(|| GitError::InvalidFormat("pack smaller than its trailer".into()))
924}
925
926pub(crate) fn parse_entry_header(bytes: &[u8], offset: &mut usize) -> Result<EntryHeader> {
927 let first = next_byte(bytes, offset)?;
928 let mut size = u64::from(first & 0x0f);
929 let kind = match (first >> 4) & 0x07 {
930 1 => PackObjectKind::Commit,
931 2 => PackObjectKind::Tree,
932 3 => PackObjectKind::Blob,
933 4 => PackObjectKind::Tag,
934 6 => PackObjectKind::OfsDelta,
935 7 => PackObjectKind::RefDelta,
936 other => {
937 return Err(GitError::InvalidFormat(format!(
938 "invalid pack object type {other}"
939 )));
940 }
941 };
942 let mut shift = 4;
943 let mut byte = first;
944 while byte & 0x80 != 0 {
945 byte = next_byte(bytes, offset)?;
946 let part = u64::from(byte & 0x7f);
947 size = size
948 .checked_add(
949 part.checked_shl(shift)
950 .ok_or_else(|| GitError::InvalidFormat("pack size overflow".into()))?,
951 )
952 .ok_or_else(|| GitError::InvalidFormat("pack size overflow".into()))?;
953 shift += 7;
954 }
955 Ok(EntryHeader { kind, size })
956}
957
958pub(crate) fn parse_ofs_delta_base_offset(
959 bytes: &[u8],
960 offset: &mut usize,
961 entry_offset: u64,
962) -> Result<u64> {
963 let mut byte = next_byte(bytes, offset)?;
964 let mut relative = u64::from(byte & 0x7f);
965 while byte & 0x80 != 0 {
966 byte = next_byte(bytes, offset)?;
967 relative = relative
968 .checked_add(1)
969 .and_then(|value| value.checked_shl(7))
970 .and_then(|value| value.checked_add(u64::from(byte & 0x7f)))
971 .ok_or_else(|| GitError::InvalidFormat("ofs-delta offset overflow".into()))?;
972 }
973 entry_offset
974 .checked_sub(relative)
975 .ok_or_else(|| GitError::InvalidFormat("ofs-delta points before pack start".into()))
976}
977
978pub(crate) fn resolve_pack_entries<F>(
979 parsed: Vec<ParsedPackEntry>,
980 format: ObjectFormat,
981 external_base: &mut F,
982 limits: PackReadLimits,
983) -> Result<Vec<PackObject>>
984where
985 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
986{
987 let mut offset_to_index = HashMap::with_capacity(parsed.len());
988 for (idx, entry) in parsed.iter().enumerate() {
989 offset_to_index.insert(parsed_entry_offset(entry), idx);
990 }
991
992 let mut resolved = vec![None; parsed.len()];
993 let mut depths = vec![0usize; parsed.len()];
997 let mut oid_to_index = HashMap::new();
998 let mut unresolved = 0usize;
999 for (idx, entry) in parsed.iter().enumerate() {
1000 match entry {
1001 ParsedPackEntry::Resolved(object) => {
1002 oid_to_index.insert(object.entry.oid, idx);
1003 resolved[idx] = Some(object.clone());
1004 }
1005 ParsedPackEntry::Delta { .. } => unresolved += 1,
1006 }
1007 }
1008
1009 while unresolved != 0 {
1010 let mut progress = false;
1011 for idx in 0..parsed.len() {
1012 if resolved[idx].is_some() {
1013 continue;
1014 }
1015 let ParsedPackEntry::Delta {
1016 base,
1017 compressed_size,
1018 delta_size,
1019 offset,
1020 delta,
1021 } = &parsed[idx]
1022 else {
1023 continue;
1024 };
1025 let Some(base_object) = delta_base_object(
1026 base,
1027 &offset_to_index,
1028 &oid_to_index,
1029 &resolved,
1030 external_base,
1031 )?
1032 else {
1033 continue;
1034 };
1035 let base_depth = match base {
1040 DeltaBase::Offset(base_offset) => {
1041 offset_to_index.get(base_offset).map(|idx| depths[*idx])
1042 }
1043 DeltaBase::Ref(base_oid) => oid_to_index.get(base_oid).map(|idx| depths[*idx]),
1044 }
1045 .unwrap_or(0);
1046 let depth = base_depth + 1;
1047 if depth > limits.max_delta_depth {
1048 return Err(GitError::InvalidFormat(format!(
1049 "pack delta chain at offset {offset} has observed depth {depth}, which \
1050 exceeds maximum depth (configured limit {}); raise \
1051 PackReadLimits::max_delta_depth or run `git repack --depth={}`",
1052 limits.max_delta_depth, limits.max_delta_depth
1053 )));
1054 }
1055 let body = apply_pack_delta(base_object.body(), delta)?;
1056 let object = EncodedObject::new(base_object.object_type(), body);
1057 let oid = object.object_id(format)?;
1058 let pack_object = PackObject {
1059 entry: PackEntry {
1060 oid,
1061 compressed_size: *compressed_size,
1062 uncompressed_size: object.body.len() as u64,
1063 offset: *offset,
1064 },
1065 object,
1066 };
1067 if pack_object.entry.uncompressed_size != decoded_delta_result_size(delta)? {
1068 return Err(GitError::InvalidObject(
1069 "resolved delta size does not match delta header".into(),
1070 ));
1071 }
1072 if *delta_size != delta.len() as u64 {
1073 return Err(GitError::InvalidObject(format!(
1074 "pack delta declared {delta_size} bytes, decoded {}",
1075 delta.len()
1076 )));
1077 }
1078 oid_to_index.insert(oid, idx);
1079 depths[idx] = depth;
1080 resolved[idx] = Some(pack_object);
1081 unresolved -= 1;
1082 progress = true;
1083 }
1084 if !progress {
1085 return Err(GitError::Unsupported("unresolved delta base".into()));
1086 }
1087 }
1088
1089 resolved
1090 .into_iter()
1091 .map(|entry| entry.ok_or_else(|| GitError::InvalidFormat("unresolved pack entry".into())))
1092 .collect()
1093}
1094
1095pub(crate) fn parsed_entry_offset(entry: &ParsedPackEntry) -> u64 {
1096 match entry {
1097 ParsedPackEntry::Resolved(object) => object.entry.offset,
1098 ParsedPackEntry::Delta { offset, .. } => *offset,
1099 }
1100}
1101
1102pub(crate) enum DeltaBaseObject<'a> {
1103 Borrowed(&'a EncodedObject),
1104 Owned(EncodedObject),
1105}
1106
1107impl DeltaBaseObject<'_> {
1108 pub(crate) fn object_type(&self) -> ObjectType {
1109 match self {
1110 Self::Borrowed(object) => object.object_type,
1111 Self::Owned(object) => object.object_type,
1112 }
1113 }
1114
1115 pub(crate) fn body(&self) -> &[u8] {
1116 match self {
1117 Self::Borrowed(object) => &object.body,
1118 Self::Owned(object) => &object.body,
1119 }
1120 }
1121}
1122
1123pub(crate) fn delta_base_object<'a, F>(
1124 base: &DeltaBase,
1125 offset_to_index: &HashMap<u64, usize>,
1126 oid_to_index: &HashMap<ObjectId, usize>,
1127 resolved: &'a [Option<PackObject>],
1128 external_base: &mut F,
1129) -> Result<Option<DeltaBaseObject<'a>>>
1130where
1131 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
1132{
1133 match base {
1134 DeltaBase::Offset(offset) => {
1135 let Some(index) = offset_to_index.get(offset).copied() else {
1136 return Err(GitError::InvalidFormat(format!(
1137 "ofs-delta base offset {offset} not found"
1138 )));
1139 };
1140 Ok(resolved[index]
1141 .as_ref()
1142 .map(|object| DeltaBaseObject::Borrowed(&object.object)))
1143 }
1144 DeltaBase::Ref(oid) => {
1145 if let Some(index) = oid_to_index.get(oid).copied() {
1146 return Ok(resolved[index]
1147 .as_ref()
1148 .map(|object| DeltaBaseObject::Borrowed(&object.object)));
1149 }
1150 external_base(oid).map(|object| object.map(DeltaBaseObject::Owned))
1151 }
1152 }
1153}
1154
1155pub(crate) fn apply_pack_delta(base: &[u8], delta: &[u8]) -> Result<Vec<u8>> {
1156 let plan = plan_pack_delta(base, delta)?;
1157 let result_size = plan.result_size;
1158 let result_size_hint = usize::try_from(result_size).unwrap_or(usize::MAX);
1159 let mut result = Vec::with_capacity(inflate::bounded_inflate_reserve(
1163 result_size_hint,
1164 delta.len(),
1165 ));
1166 walk_pack_delta(base, delta, plan, CancelFlag::never(), |slice| {
1167 result.extend_from_slice(slice);
1168 Ok(())
1169 })?;
1170 if result.len() as u64 != result_size {
1171 return Err(GitError::InvalidObject(format!(
1172 "delta result size mismatch: expected {result_size}, got {}",
1173 result.len()
1174 )));
1175 }
1176 Ok(result)
1177}
1178
1179#[derive(Debug, Clone, Copy)]
1180pub(crate) struct PackDeltaPlan {
1181 instructions_offset: usize,
1182 pub(crate) result_size: u64,
1183}
1184
1185pub(crate) fn plan_pack_delta(base: &[u8], delta: &[u8]) -> Result<PackDeltaPlan> {
1186 let mut instructions_offset = 0usize;
1187 let base_size = read_delta_varint(delta, &mut instructions_offset)?;
1188 if base_size != base.len() as u64 {
1189 return Err(GitError::InvalidObject(format!(
1190 "delta base size mismatch: expected {base_size}, got {}",
1191 base.len()
1192 )));
1193 }
1194 let result_size = read_delta_varint(delta, &mut instructions_offset)?;
1195 Ok(PackDeltaPlan {
1196 instructions_offset,
1197 result_size,
1198 })
1199}
1200
1201pub(crate) fn apply_pack_delta_exact(
1202 base: &[u8],
1203 delta: &[u8],
1204 plan: PackDeltaPlan,
1205 result: &mut Vec<u8>,
1206 cancel: CancelFlag<'_>,
1207) -> Result<()> {
1208 if !result.is_empty() || u64::try_from(result.capacity()).unwrap_or(u64::MAX) < plan.result_size
1209 {
1210 return Err(GitError::InvalidObject(
1211 "delta output buffer is not empty and preallocated to the declared result size".into(),
1212 ));
1213 }
1214 walk_pack_delta(base, delta, plan, cancel, |slice| {
1215 let end = result
1216 .len()
1217 .checked_add(slice.len())
1218 .ok_or_else(|| GitError::InvalidObject("delta output range overflow".into()))?;
1219 if u64::try_from(end).unwrap_or(u64::MAX) > plan.result_size {
1220 return Err(GitError::InvalidObject(
1221 "delta instructions exceed declared result size".into(),
1222 ));
1223 }
1224 result.extend_from_slice(slice);
1225 Ok(())
1226 })?;
1227 cancel.check()?;
1228 if result.len() as u64 != plan.result_size {
1229 return Err(GitError::InvalidObject(format!(
1230 "delta result size mismatch: expected {}, got {}",
1231 plan.result_size,
1232 result.len()
1233 )));
1234 }
1235 Ok(())
1236}
1237
1238fn walk_pack_delta<F>(
1239 base: &[u8],
1240 delta: &[u8],
1241 plan: PackDeltaPlan,
1242 cancel: CancelFlag<'_>,
1243 mut emit: F,
1244) -> Result<()>
1245where
1246 F: FnMut(&[u8]) -> Result<()>,
1247{
1248 let mut cursor = plan.instructions_offset;
1249 while cursor < delta.len() {
1250 cancel.check()?;
1251 let command = delta[cursor];
1252 cursor += 1;
1253 if command & 0x80 != 0 {
1254 let copy_offset =
1255 read_delta_copy_value(delta, &mut cursor, command, &[0x01, 0x02, 0x04, 0x08])?;
1256 let mut copy_size =
1257 read_delta_copy_value(delta, &mut cursor, command, &[0x10, 0x20, 0x40])?;
1258 if copy_size == 0 {
1259 copy_size = 0x10000;
1260 }
1261 let start = usize::try_from(copy_offset)
1262 .map_err(|_| GitError::InvalidObject("delta copy offset overflows usize".into()))?;
1263 let len = usize::try_from(copy_size)
1264 .map_err(|_| GitError::InvalidObject("delta copy size overflows usize".into()))?;
1265 let end = start
1266 .checked_add(len)
1267 .ok_or_else(|| GitError::InvalidObject("delta copy range overflow".into()))?;
1268 let Some(slice) = base.get(start..end) else {
1269 return Err(GitError::InvalidObject(
1270 "delta copy range exceeds base object".into(),
1271 ));
1272 };
1273 emit(slice)?;
1274 } else if command != 0 {
1275 let len = usize::from(command);
1276 let end = cursor
1277 .checked_add(len)
1278 .ok_or_else(|| GitError::InvalidObject("delta insert range overflow".into()))?;
1279 let Some(slice) = delta.get(cursor..end) else {
1280 return Err(GitError::InvalidObject(
1281 "delta insert range exceeds delta data".into(),
1282 ));
1283 };
1284 emit(slice)?;
1285 cursor = end;
1286 } else {
1287 return Err(GitError::InvalidObject(
1288 "delta contains reserved zero command".into(),
1289 ));
1290 }
1291 }
1292 cancel.check()?;
1293 Ok(())
1294}
1295
1296pub(crate) fn decoded_delta_result_size(delta: &[u8]) -> Result<u64> {
1297 let mut cursor = 0usize;
1298 let _ = read_delta_varint(delta, &mut cursor)?;
1299 read_delta_varint(delta, &mut cursor)
1300}
1301
1302pub(crate) fn read_delta_varint(delta: &[u8], cursor: &mut usize) -> Result<u64> {
1303 let mut value = 0u64;
1304 let mut shift = 0u32;
1305 loop {
1306 let Some(byte) = delta.get(*cursor).copied() else {
1307 return Err(GitError::InvalidObject("truncated delta size".into()));
1308 };
1309 *cursor += 1;
1310 value = value
1311 .checked_add(
1312 u64::from(byte & 0x7f)
1313 .checked_shl(shift)
1314 .ok_or_else(|| GitError::InvalidObject("delta size overflow".into()))?,
1315 )
1316 .ok_or_else(|| GitError::InvalidObject("delta size overflow".into()))?;
1317 if byte & 0x80 == 0 {
1318 return Ok(value);
1319 }
1320 shift = shift
1321 .checked_add(7)
1322 .ok_or_else(|| GitError::InvalidObject("delta size overflow".into()))?;
1323 }
1324}
1325
1326pub(crate) fn read_delta_copy_value(
1327 delta: &[u8],
1328 cursor: &mut usize,
1329 command: u8,
1330 masks: &[u8],
1331) -> Result<u64> {
1332 let mut value = 0u64;
1333 for (shift, mask) in masks.iter().enumerate() {
1334 if command & mask != 0 {
1335 let Some(byte) = delta.get(*cursor).copied() else {
1336 return Err(GitError::InvalidObject(
1337 "truncated delta copy command".into(),
1338 ));
1339 };
1340 *cursor += 1;
1341 value |= u64::from(byte) << (shift * 8);
1342 }
1343 }
1344 Ok(value)
1345}