1use super::*;
6
7impl PackFile {
8 pub fn parse_sha1(bytes: &[u8]) -> Result<Self> {
9 Self::parse(bytes, ObjectFormat::Sha1)
10 }
11
12 pub fn parse(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
13 Self::parse_with_base(bytes, format, |_| Ok(None))
14 }
15
16 pub fn parse_bundle(bundle: &Bundle) -> Result<Self> {
17 Self::parse(&bundle.pack, bundle.format)
18 }
19
20 pub fn index_pack(bytes: &[u8], format: ObjectFormat) -> Result<PackWrite> {
21 let PackIndexBuild {
22 index,
23 pack_checksum,
24 entries,
25 } = PackIndex::write_v2_for_pack(bytes, format)?;
26 Ok(PackWrite {
27 pack: bytes.to_vec(),
28 index,
29 checksum: pack_checksum,
30 entries,
31 delta_count: 0,
32 })
33 }
34
35 pub fn parse_thin<F>(bytes: &[u8], format: ObjectFormat, external_base: F) -> Result<Self>
36 where
37 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
38 {
39 Self::parse_with_base(bytes, format, external_base)
40 }
41
42 pub(crate) fn parse_with_base<F>(bytes: &[u8], format: ObjectFormat, mut external_base: F) -> Result<Self>
43 where
44 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
45 {
46 let trailer_len = format.raw_len();
47 if bytes.len() < 12 + trailer_len {
48 return Err(GitError::InvalidFormat("pack file too short".into()));
49 }
50 let trailer_offset = bytes.len() - trailer_len;
51 let checksum = sley_core::digest_bytes(format, &bytes[..trailer_offset])?;
52 let expected = ObjectId::from_raw(format, &bytes[trailer_offset..])?;
53 if checksum != expected {
54 return Err(GitError::InvalidFormat(format!(
55 "pack checksum mismatch: expected {expected}, got {checksum}"
56 )));
57 }
58
59 if &bytes[..4] != b"PACK" {
60 return Err(GitError::InvalidFormat("missing PACK signature".into()));
61 }
62 let version = u32_be(&bytes[4..8]);
63 if version != 2 && version != 3 {
64 return Err(GitError::Unsupported(format!("pack version {version}")));
65 }
66 let count = u32_be(&bytes[8..12]) as usize;
67 let mut offset = 12usize;
68 let mut entries = Vec::with_capacity(count);
69 for _ in 0..count {
70 let entry_offset = offset;
71 let header = parse_entry_header(bytes, &mut offset)?;
72 let base =
73 match header.kind {
74 PackObjectKind::OfsDelta => Some(DeltaBase::Offset(
75 parse_ofs_delta_base_offset(bytes, &mut offset, entry_offset as u64)?,
76 )),
77 PackObjectKind::RefDelta => {
78 let hash_len = format.raw_len();
79 if offset + hash_len > trailer_offset {
80 return Err(GitError::InvalidFormat(
81 "truncated ref-delta base object id".into(),
82 ));
83 }
84 let oid = ObjectId::from_raw(format, &bytes[offset..offset + hash_len])?;
85 offset += hash_len;
86 Some(DeltaBase::Ref(oid))
87 }
88 _ => None,
89 };
90 let mut body = Vec::new();
91 let consumed = inflate_into(
92 &bytes[offset..trailer_offset],
93 &mut body,
94 header.size.min(usize::MAX as u64) as usize,
95 )?;
96 if body.len() as u64 != header.size {
97 return Err(GitError::InvalidObject(format!(
98 "pack object declared {} bytes, decoded {}",
99 header.size,
100 body.len()
101 )));
102 }
103 if consumed == 0 {
104 return Err(GitError::InvalidFormat(
105 "empty compressed pack entry".into(),
106 ));
107 }
108 offset = offset
109 .checked_add(consumed)
110 .ok_or_else(|| GitError::InvalidFormat("pack offset overflow".into()))?;
111 if offset > trailer_offset {
112 return Err(GitError::InvalidFormat(
113 "pack entry extends past checksum".into(),
114 ));
115 }
116 if let Some(base) = base {
117 entries.push(ParsedPackEntry::Delta {
118 base,
119 compressed_size: consumed as u64,
120 delta_size: header.size,
121 offset: entry_offset as u64,
122 delta: body,
123 });
124 } else {
125 let object_type = match header.kind {
126 PackObjectKind::Commit => ObjectType::Commit,
127 PackObjectKind::Tree => ObjectType::Tree,
128 PackObjectKind::Blob => ObjectType::Blob,
129 PackObjectKind::Tag => ObjectType::Tag,
130 PackObjectKind::OfsDelta | PackObjectKind::RefDelta => unreachable!(),
131 };
132 let object = EncodedObject::new(object_type, body);
133 let oid = object.object_id(format)?;
134 entries.push(ParsedPackEntry::Resolved(PackObject {
135 entry: PackEntry {
136 oid,
137 compressed_size: consumed as u64,
138 uncompressed_size: header.size,
139 offset: entry_offset as u64,
140 },
141 object,
142 }));
143 }
144 }
145 if offset != trailer_offset {
146 return Err(GitError::InvalidFormat(format!(
147 "pack has {} trailing bytes before checksum",
148 trailer_offset - offset
149 )));
150 }
151 Ok(Self {
152 version,
153 entries: resolve_pack_entries(entries, format, &mut external_base)?,
154 checksum,
155 })
156 }
157
158 pub fn verify_pack_stats(bytes: &[u8], format: ObjectFormat) -> Result<PackVerifyStats> {
169 let pack = Self::parse(bytes, format)?;
173
174 let trailer_len = format.raw_len();
178 let trailer_offset = bytes.len() - trailer_len;
179 let count = u32_be(&bytes[8..12]) as usize;
180 let mut offset = 12usize;
181 let mut on_disk: Vec<OnDiskEntry> = Vec::with_capacity(count);
187 for _ in 0..count {
188 let entry_offset = offset as u64;
189 let header = parse_entry_header(bytes, &mut offset)?;
190 let stream_size = header.size;
191 let base =
192 match header.kind {
193 PackObjectKind::OfsDelta => Some(DeltaBase::Offset(
194 parse_ofs_delta_base_offset(bytes, &mut offset, entry_offset)?,
195 )),
196 PackObjectKind::RefDelta => {
197 let hash_len = format.raw_len();
198 if offset + hash_len > trailer_offset {
199 return Err(GitError::InvalidFormat(
200 "truncated ref-delta base object id".into(),
201 ));
202 }
203 let oid = ObjectId::from_raw(format, &bytes[offset..offset + hash_len])?;
204 offset += hash_len;
205 Some(DeltaBase::Ref(oid))
206 }
207 _ => None,
208 };
209 let mut body = Vec::new();
211 let consumed = inflate_into(
212 &bytes[offset..trailer_offset],
213 &mut body,
214 header.size.min(usize::MAX as u64) as usize,
215 )?;
216 offset = offset
217 .checked_add(consumed)
218 .ok_or_else(|| GitError::InvalidFormat("pack offset overflow".into()))?;
219 on_disk.push(OnDiskEntry {
220 offset: entry_offset,
221 base,
222 stream_size,
223 });
224 }
225
226 let mut resolved_by_offset: HashMap<u64, &PackObject> =
228 HashMap::with_capacity(pack.entries.len());
229 for object in &pack.entries {
230 resolved_by_offset.insert(object.entry.offset, object);
231 }
232 let mut oid_by_offset: HashMap<u64, ObjectId> = HashMap::with_capacity(on_disk.len());
234 for entry in &on_disk {
235 if let Some(object) = resolved_by_offset.get(&entry.offset) {
236 oid_by_offset.insert(entry.offset, object.entry.oid);
237 }
238 }
239 let mut index_by_offset: HashMap<u64, usize> = HashMap::with_capacity(on_disk.len());
241 for (idx, entry) in on_disk.iter().enumerate() {
242 index_by_offset.insert(entry.offset, idx);
243 }
244
245 let mut sorted_offsets: Vec<u64> = on_disk.iter().map(|entry| entry.offset).collect();
248 sorted_offsets.sort_unstable();
249 let mut next_offset: HashMap<u64, u64> = HashMap::with_capacity(sorted_offsets.len());
250 for window in sorted_offsets.windows(2) {
251 next_offset.insert(window[0], window[1]);
252 }
253 if let Some(last) = sorted_offsets.last() {
254 next_offset.insert(*last, trailer_offset as u64);
255 }
256
257 let mut depth = vec![None; on_disk.len()];
263 fn resolve_depth(
264 idx: usize,
265 on_disk: &[OnDiskEntry],
266 index_by_offset: &HashMap<u64, usize>,
267 offset_of_oid: &HashMap<ObjectId, u64>,
268 depth: &mut [Option<u32>],
269 ) -> u32 {
270 if let Some(d) = depth[idx] {
271 return d;
272 }
273 let computed = match &on_disk[idx].base {
274 None => 0,
275 Some(base) => {
276 let base_idx = match base {
277 DeltaBase::Offset(off) => index_by_offset.get(off).copied(),
278 DeltaBase::Ref(oid) => offset_of_oid
279 .get(oid)
280 .and_then(|off| index_by_offset.get(off).copied()),
281 };
282 match base_idx {
283 Some(bi) => {
284 resolve_depth(bi, on_disk, index_by_offset, offset_of_oid, depth) + 1
285 }
286 None => 1,
288 }
289 }
290 };
291 depth[idx] = Some(computed);
292 computed
293 }
294 let mut offset_of_oid: HashMap<ObjectId, u64> = HashMap::with_capacity(oid_by_offset.len());
295 for (off, oid) in &oid_by_offset {
296 offset_of_oid.insert(*oid, *off);
297 }
298 for idx in 0..on_disk.len() {
299 resolve_depth(idx, &on_disk, &index_by_offset, &offset_of_oid, &mut depth);
300 }
301
302 let mut stats = Vec::with_capacity(on_disk.len());
303 for (idx, entry) in on_disk.iter().enumerate() {
304 let off = entry.offset;
305 let object = resolved_by_offset.get(&off).ok_or_else(|| {
306 GitError::InvalidFormat("pack offset missing from resolved set".into())
307 })?;
308 let size_in_pack = next_offset
309 .get(&off)
310 .copied()
311 .unwrap_or(trailer_offset as u64)
312 .saturating_sub(off);
313 let base_oid = match &entry.base {
314 None => None,
315 Some(DeltaBase::Offset(base_off)) => oid_by_offset.get(base_off).copied(),
316 Some(DeltaBase::Ref(oid)) => Some(*oid),
317 };
318 stats.push(PackVerifyStat {
319 oid: object.entry.oid,
320 object_type: object.object.object_type,
321 size: entry.stream_size,
324 size_in_pack,
325 offset: off,
326 delta_depth: depth[idx].unwrap_or(0),
327 base_oid,
328 });
329 }
330 stats.sort_by_key(|stat| stat.offset);
332
333 Ok(PackVerifyStats {
334 objects: stats,
335 checksum: pack.checksum,
336 })
337 }
338}
339
340pub trait PackDeltaCache {
354 fn get(&self, offset: u64) -> Option<Arc<EncodedObject>>;
356 fn insert(&self, offset: u64, object: Arc<EncodedObject>);
358}
359
360pub(crate) struct NoopDeltaCache;
363
364impl PackDeltaCache for NoopDeltaCache {
365 fn get(&self, _offset: u64) -> Option<Arc<EncodedObject>> {
366 None
367 }
368 fn insert(&self, _offset: u64, _object: Arc<EncodedObject>) {}
369}
370
371thread_local! {
377 static INFLATE: RefCell<flate2::Decompress> = RefCell::new(flate2::Decompress::new(true));
378}
379
380pub(crate) fn inflate_into(compressed: &[u8], out: &mut Vec<u8>, size_hint: usize) -> Result<usize> {
400 INFLATE.with(|cell| {
401 let mut decompress = cell.borrow_mut();
402 decompress.reset(true);
403 out.reserve(inflate::bounded_inflate_reserve(size_hint, compressed.len()));
404 let mut input = compressed;
405 let mut consumed_total = 0usize;
406 loop {
407 if out.len() == out.capacity() {
410 out.reserve(out.len().max(64));
411 }
412 let before_in = decompress.total_in();
413 let before_out = decompress.total_out();
414 let status = decompress
415 .decompress_vec(input, out, flate2::FlushDecompress::None)
416 .map_err(|err| GitError::InvalidObject(format!("zlib inflate failed: {err}")))?;
417 let consumed = (decompress.total_in() - before_in) as usize;
418 let produced = decompress.total_out() - before_out;
419 input = &input[consumed..];
420 consumed_total += consumed;
421 match status {
422 flate2::Status::StreamEnd => return Ok(consumed_total),
423 _ if consumed == 0 && produced == 0 => {
424 return Err(GitError::InvalidObject("truncated zlib stream".into()));
425 }
426 _ => {}
427 }
428 }
429 })
430}
431
432pub(crate) fn inflate_prefix(compressed: &[u8], max_out: usize, out: &mut Vec<u8>) -> Result<()> {
436 INFLATE.with(|cell| {
437 let mut decompress = cell.borrow_mut();
438 decompress.reset(true);
439 out.reserve(max_out.max(16));
440 let mut input = compressed;
441 while out.len() < max_out {
442 if out.len() == out.capacity() {
443 out.reserve(out.len().max(16));
444 }
445 let before_in = decompress.total_in();
446 let before_out = decompress.total_out();
447 let status = decompress
448 .decompress_vec(input, out, flate2::FlushDecompress::None)
449 .map_err(|err| GitError::InvalidObject(format!("zlib inflate failed: {err}")))?;
450 let consumed = (decompress.total_in() - before_in) as usize;
451 let produced = decompress.total_out() - before_out;
452 input = &input[consumed..];
453 if status == flate2::Status::StreamEnd || (consumed == 0 && produced == 0) {
454 break;
455 }
456 }
457 Ok(())
458 })
459}
460pub fn read_object_at_arc<F>(
468 pack_bytes: &[u8],
469 offset: u64,
470 format: ObjectFormat,
471 resolve_ref_base: F,
472) -> Result<Arc<EncodedObject>>
473where
474 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
475{
476 read_object_at_with_cache_arc(
477 pack_bytes,
478 offset,
479 format,
480 resolve_ref_base,
481 &NoopDeltaCache,
482 )
483}
484
485pub fn read_object_at_with_cache_arc<F, C>(
494 pack_bytes: &[u8],
495 offset: u64,
496 format: ObjectFormat,
497 mut resolve_ref_base: F,
498 cache: &C,
499) -> Result<Arc<EncodedObject>>
500where
501 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
502 C: PackDeltaCache + ?Sized,
503{
504 read_object_at_with_cache_and_ofs_base_arc(
505 pack_bytes,
506 offset,
507 format,
508 &mut resolve_ref_base,
509 |_offset| Ok(None),
510 cache,
511 )
512}
513
514pub fn read_object_at_with_cache_and_ofs_base_arc<F, G, C>(
520 pack_bytes: &[u8],
521 offset: u64,
522 format: ObjectFormat,
523 mut resolve_ref_base: F,
524 mut resolve_ofs_base: G,
525 cache: &C,
526) -> Result<Arc<EncodedObject>>
527where
528 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
529 G: FnMut(u64) -> Result<Option<Arc<EncodedObject>>>,
530 C: PackDeltaCache + ?Sized,
531{
532 read_object_at_inner(
533 pack_bytes,
534 offset,
535 format,
536 &mut resolve_ref_base,
537 &mut resolve_ofs_base,
538 cache,
539 )
540}
541
542pub fn read_object_at_with_ofs_base_arc<F, G>(
544 pack_bytes: &[u8],
545 offset: u64,
546 format: ObjectFormat,
547 resolve_ref_base: F,
548 resolve_ofs_base: G,
549) -> Result<Arc<EncodedObject>>
550where
551 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
552 G: FnMut(u64) -> Result<Option<Arc<EncodedObject>>>,
553{
554 read_object_at_with_cache_and_ofs_base_arc(
555 pack_bytes,
556 offset,
557 format,
558 resolve_ref_base,
559 resolve_ofs_base,
560 &NoopDeltaCache,
561 )
562}
563
564pub(crate) fn read_object_at_inner<F, G, C>(
565 pack_bytes: &[u8],
566 offset: u64,
567 format: ObjectFormat,
568 resolve_ref_base: &mut F,
569 resolve_ofs_base: &mut G,
570 cache: &C,
571) -> Result<Arc<EncodedObject>>
572where
573 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
574 G: FnMut(u64) -> Result<Option<Arc<EncodedObject>>>,
575 C: PackDeltaCache + ?Sized,
576{
577 if let Some(object) = cache.get(offset) {
580 return Ok(object);
581 }
582 let trailer_offset = pack_bytes
583 .len()
584 .checked_sub(format.raw_len())
585 .ok_or_else(|| GitError::InvalidFormat("pack smaller than its trailer".into()))?;
586 let mut cursor = usize::try_from(offset)
587 .ok()
588 .filter(|&value| value < trailer_offset)
589 .ok_or_else(|| GitError::InvalidFormat("pack object offset out of range".into()))?;
590 let header = parse_entry_header(pack_bytes, &mut cursor)?;
591 let base = match header.kind {
592 PackObjectKind::OfsDelta => Some(DeltaBase::Offset(parse_ofs_delta_base_offset(
593 pack_bytes,
594 &mut cursor,
595 offset,
596 )?)),
597 PackObjectKind::RefDelta => {
598 let hash_len = format.raw_len();
599 if cursor + hash_len > trailer_offset {
600 return Err(GitError::InvalidFormat(
601 "truncated ref-delta base object id".into(),
602 ));
603 }
604 let oid = ObjectId::from_raw(format, &pack_bytes[cursor..cursor + hash_len])?;
605 cursor += hash_len;
606 Some(DeltaBase::Ref(oid))
607 }
608 _ => None,
609 };
610 let mut body = Vec::new();
611 inflate_into(
612 &pack_bytes[cursor..trailer_offset],
613 &mut body,
614 header.size.min(usize::MAX as u64) as usize,
615 )?;
616 if body.len() as u64 != header.size {
617 return Err(GitError::InvalidObject(format!(
618 "pack object declared {} bytes, decoded {}",
619 header.size,
620 body.len()
621 )));
622 }
623 let object = match base {
624 None => {
625 let object_type = match header.kind {
626 PackObjectKind::Commit => ObjectType::Commit,
627 PackObjectKind::Tree => ObjectType::Tree,
628 PackObjectKind::Blob => ObjectType::Blob,
629 PackObjectKind::Tag => ObjectType::Tag,
630 PackObjectKind::OfsDelta | PackObjectKind::RefDelta => {
631 return Err(GitError::InvalidFormat(
632 "delta pack entry decoded without a base".into(),
633 ));
634 }
635 };
636 Arc::new(EncodedObject::new(object_type, body))
637 }
638 Some(DeltaBase::Offset(base_offset)) => {
639 let base = match read_object_at_inner(
640 pack_bytes,
641 base_offset,
642 format,
643 resolve_ref_base,
644 resolve_ofs_base,
645 cache,
646 ) {
647 Ok(base) => base,
648 Err(pack_err) => match resolve_ofs_base(base_offset)? {
649 Some(base) => base,
650 None => return Err(pack_err),
651 },
652 };
653 let resolved = apply_pack_delta(&base.body, &body)?;
654 Arc::new(EncodedObject::new(base.object_type, resolved))
655 }
656 Some(DeltaBase::Ref(base_oid)) => {
657 let base = resolve_ref_base(&base_oid)?
658 .ok_or_else(|| GitError::not_found(format!("ref-delta base object {base_oid}")))?;
659 let resolved = apply_pack_delta(&base.body, &body)?;
660 Arc::new(EncodedObject::new(base.object_type, resolved))
661 }
662 };
663 cache.insert(offset, Arc::clone(&object));
667 Ok(object)
668}
669
670pub fn read_object_header_at<F>(
680 pack_bytes: &[u8],
681 offset: u64,
682 format: ObjectFormat,
683 mut resolve_ref_base_type: F,
684) -> Result<(ObjectType, u64)>
685where
686 F: FnMut(&ObjectId) -> Result<Option<ObjectType>>,
687{
688 read_object_header_at_inner(
689 pack_bytes,
690 offset,
691 format,
692 &mut resolve_ref_base_type,
693 &mut NoopHeaderTypeCache,
694 )
695}
696
697pub trait HeaderTypeCache {
714 fn get(&self, pack_offset: u64) -> Option<(ObjectType, u64)>;
716 fn put(&mut self, pack_offset: u64, header: (ObjectType, u64));
718}
719
720pub(crate) struct NoopHeaderTypeCache;
721
722impl HeaderTypeCache for NoopHeaderTypeCache {
723 fn get(&self, _pack_offset: u64) -> Option<(ObjectType, u64)> {
724 None
725 }
726 fn put(&mut self, _pack_offset: u64, _header: (ObjectType, u64)) {}
727}
728
729pub fn read_object_header_at_with_cache<F, C>(
735 pack_bytes: &[u8],
736 offset: u64,
737 format: ObjectFormat,
738 mut resolve_ref_base_type: F,
739 type_cache: &mut C,
740) -> Result<(ObjectType, u64)>
741where
742 F: FnMut(&ObjectId) -> Result<Option<ObjectType>>,
743 C: HeaderTypeCache + ?Sized,
744{
745 if let Some(header) = type_cache.get(offset) {
746 return Ok(header);
747 }
748 read_object_header_at_inner(
749 pack_bytes,
750 offset,
751 format,
752 &mut resolve_ref_base_type,
753 type_cache,
754 )
755}
756
757pub(crate) fn read_object_header_at_inner<F, C>(
758 pack_bytes: &[u8],
759 offset: u64,
760 format: ObjectFormat,
761 resolve_ref_base_type: &mut F,
762 type_cache: &mut C,
763) -> Result<(ObjectType, u64)>
764where
765 F: FnMut(&ObjectId) -> Result<Option<ObjectType>>,
766 C: HeaderTypeCache + ?Sized,
767{
768 let trailer_offset = pack_bytes
769 .len()
770 .checked_sub(format.raw_len())
771 .ok_or_else(|| GitError::InvalidFormat("pack smaller than its trailer".into()))?;
772 let mut cursor = usize::try_from(offset)
773 .ok()
774 .filter(|&value| value < trailer_offset)
775 .ok_or_else(|| GitError::InvalidFormat("pack object offset out of range".into()))?;
776 let header = parse_entry_header(pack_bytes, &mut cursor)?;
777 let resolved = match header.kind {
778 PackObjectKind::Commit => (ObjectType::Commit, header.size),
779 PackObjectKind::Tree => (ObjectType::Tree, header.size),
780 PackObjectKind::Blob => (ObjectType::Blob, header.size),
781 PackObjectKind::Tag => (ObjectType::Tag, header.size),
782 PackObjectKind::OfsDelta => {
783 let base_offset = parse_ofs_delta_base_offset(pack_bytes, &mut cursor, offset)?;
784 let size = delta_result_size_from_stream(&pack_bytes[cursor..trailer_offset])?;
785 let base_type = match type_cache.get(base_offset) {
788 Some((base_type, _)) => base_type,
789 None => {
790 let (base_type, _) = read_object_header_at_inner(
791 pack_bytes,
792 base_offset,
793 format,
794 resolve_ref_base_type,
795 type_cache,
796 )?;
797 base_type
798 }
799 };
800 (base_type, size)
801 }
802 PackObjectKind::RefDelta => {
803 let hash_len = format.raw_len();
804 if cursor + hash_len > trailer_offset {
805 return Err(GitError::InvalidFormat(
806 "truncated ref-delta base object id".into(),
807 ));
808 }
809 let oid = ObjectId::from_raw(format, &pack_bytes[cursor..cursor + hash_len])?;
810 cursor += hash_len;
811 let size = delta_result_size_from_stream(&pack_bytes[cursor..trailer_offset])?;
812 let base_type = resolve_ref_base_type(&oid)?
813 .ok_or_else(|| GitError::not_found(format!("ref-delta base object {oid}")))?;
814 (base_type, size)
815 }
816 };
817 type_cache.put(offset, resolved);
820 Ok(resolved)
821}
822
823pub(crate) const DELTA_HEADER_PREFIX_LEN: usize = 32;
827
828pub(crate) fn delta_result_size_from_stream(compressed: &[u8]) -> Result<u64> {
831 let mut prefix = Vec::new();
832 inflate_prefix(compressed, DELTA_HEADER_PREFIX_LEN, &mut prefix)?;
833 decoded_delta_result_size(&prefix)
834}
835
836pub(crate) fn parse_entry_header(bytes: &[u8], offset: &mut usize) -> Result<EntryHeader> {
837 let first = next_byte(bytes, offset)?;
838 let mut size = u64::from(first & 0x0f);
839 let kind = match (first >> 4) & 0x07 {
840 1 => PackObjectKind::Commit,
841 2 => PackObjectKind::Tree,
842 3 => PackObjectKind::Blob,
843 4 => PackObjectKind::Tag,
844 6 => PackObjectKind::OfsDelta,
845 7 => PackObjectKind::RefDelta,
846 other => {
847 return Err(GitError::InvalidFormat(format!(
848 "invalid pack object type {other}"
849 )));
850 }
851 };
852 let mut shift = 4;
853 let mut byte = first;
854 while byte & 0x80 != 0 {
855 byte = next_byte(bytes, offset)?;
856 let part = u64::from(byte & 0x7f);
857 size = size
858 .checked_add(
859 part.checked_shl(shift)
860 .ok_or_else(|| GitError::InvalidFormat("pack size overflow".into()))?,
861 )
862 .ok_or_else(|| GitError::InvalidFormat("pack size overflow".into()))?;
863 shift += 7;
864 }
865 Ok(EntryHeader { kind, size })
866}
867
868pub(crate) fn parse_ofs_delta_base_offset(bytes: &[u8], offset: &mut usize, entry_offset: u64) -> Result<u64> {
869 let mut byte = next_byte(bytes, offset)?;
870 let mut relative = u64::from(byte & 0x7f);
871 while byte & 0x80 != 0 {
872 byte = next_byte(bytes, offset)?;
873 relative = relative
874 .checked_add(1)
875 .and_then(|value| value.checked_shl(7))
876 .and_then(|value| value.checked_add(u64::from(byte & 0x7f)))
877 .ok_or_else(|| GitError::InvalidFormat("ofs-delta offset overflow".into()))?;
878 }
879 entry_offset
880 .checked_sub(relative)
881 .ok_or_else(|| GitError::InvalidFormat("ofs-delta points before pack start".into()))
882}
883
884pub(crate) fn resolve_pack_entries<F>(
885 parsed: Vec<ParsedPackEntry>,
886 format: ObjectFormat,
887 external_base: &mut F,
888) -> Result<Vec<PackObject>>
889where
890 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
891{
892 let mut offset_to_index = HashMap::with_capacity(parsed.len());
893 for (idx, entry) in parsed.iter().enumerate() {
894 offset_to_index.insert(parsed_entry_offset(entry), idx);
895 }
896
897 let mut resolved = vec![None; parsed.len()];
898 let mut oid_to_index = HashMap::new();
899 let mut unresolved = 0usize;
900 for (idx, entry) in parsed.iter().enumerate() {
901 match entry {
902 ParsedPackEntry::Resolved(object) => {
903 oid_to_index.insert(object.entry.oid, idx);
904 resolved[idx] = Some(object.clone());
905 }
906 ParsedPackEntry::Delta { .. } => unresolved += 1,
907 }
908 }
909
910 while unresolved != 0 {
911 let mut progress = false;
912 for idx in 0..parsed.len() {
913 if resolved[idx].is_some() {
914 continue;
915 }
916 let ParsedPackEntry::Delta {
917 base,
918 compressed_size,
919 delta_size,
920 offset,
921 delta,
922 } = &parsed[idx]
923 else {
924 continue;
925 };
926 let Some(base_object) = delta_base_object(
927 base,
928 &offset_to_index,
929 &oid_to_index,
930 &resolved,
931 external_base,
932 )?
933 else {
934 continue;
935 };
936 let body = apply_pack_delta(base_object.body(), delta)?;
937 let object = EncodedObject::new(base_object.object_type(), body);
938 let oid = object.object_id(format)?;
939 let pack_object = PackObject {
940 entry: PackEntry {
941 oid,
942 compressed_size: *compressed_size,
943 uncompressed_size: object.body.len() as u64,
944 offset: *offset,
945 },
946 object,
947 };
948 if pack_object.entry.uncompressed_size != decoded_delta_result_size(delta)? {
949 return Err(GitError::InvalidObject(
950 "resolved delta size does not match delta header".into(),
951 ));
952 }
953 if *delta_size != delta.len() as u64 {
954 return Err(GitError::InvalidObject(format!(
955 "pack delta declared {delta_size} bytes, decoded {}",
956 delta.len()
957 )));
958 }
959 oid_to_index.insert(oid, idx);
960 resolved[idx] = Some(pack_object);
961 unresolved -= 1;
962 progress = true;
963 }
964 if !progress {
965 return Err(GitError::Unsupported("unresolved delta base".into()));
966 }
967 }
968
969 resolved
970 .into_iter()
971 .map(|entry| entry.ok_or_else(|| GitError::InvalidFormat("unresolved pack entry".into())))
972 .collect()
973}
974
975pub(crate) fn parsed_entry_offset(entry: &ParsedPackEntry) -> u64 {
976 match entry {
977 ParsedPackEntry::Resolved(object) => object.entry.offset,
978 ParsedPackEntry::Delta { offset, .. } => *offset,
979 }
980}
981
982pub(crate) enum DeltaBaseObject<'a> {
983 Borrowed(&'a EncodedObject),
984 Owned(EncodedObject),
985}
986
987impl DeltaBaseObject<'_> {
988 pub(crate) fn object_type(&self) -> ObjectType {
989 match self {
990 Self::Borrowed(object) => object.object_type,
991 Self::Owned(object) => object.object_type,
992 }
993 }
994
995 pub(crate) fn body(&self) -> &[u8] {
996 match self {
997 Self::Borrowed(object) => &object.body,
998 Self::Owned(object) => &object.body,
999 }
1000 }
1001}
1002
1003pub(crate) fn delta_base_object<'a, F>(
1004 base: &DeltaBase,
1005 offset_to_index: &HashMap<u64, usize>,
1006 oid_to_index: &HashMap<ObjectId, usize>,
1007 resolved: &'a [Option<PackObject>],
1008 external_base: &mut F,
1009) -> Result<Option<DeltaBaseObject<'a>>>
1010where
1011 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
1012{
1013 match base {
1014 DeltaBase::Offset(offset) => {
1015 let Some(index) = offset_to_index.get(offset).copied() else {
1016 return Err(GitError::InvalidFormat(format!(
1017 "ofs-delta base offset {offset} not found"
1018 )));
1019 };
1020 Ok(resolved[index]
1021 .as_ref()
1022 .map(|object| DeltaBaseObject::Borrowed(&object.object)))
1023 }
1024 DeltaBase::Ref(oid) => {
1025 if let Some(index) = oid_to_index.get(oid).copied() {
1026 return Ok(resolved[index]
1027 .as_ref()
1028 .map(|object| DeltaBaseObject::Borrowed(&object.object)));
1029 }
1030 external_base(oid).map(|object| object.map(DeltaBaseObject::Owned))
1031 }
1032 }
1033}
1034
1035pub(crate) fn apply_pack_delta(base: &[u8], delta: &[u8]) -> Result<Vec<u8>> {
1036 let mut cursor = 0usize;
1037 let base_size = read_delta_varint(delta, &mut cursor)?;
1038 if base_size != base.len() as u64 {
1039 return Err(GitError::InvalidObject(format!(
1040 "delta base size mismatch: expected {base_size}, got {}",
1041 base.len()
1042 )));
1043 }
1044 let result_size = read_delta_varint(delta, &mut cursor)?;
1045 let result_size_hint = usize::try_from(result_size).unwrap_or(usize::MAX);
1054 let mut result = Vec::with_capacity(inflate::bounded_inflate_reserve(
1055 result_size_hint,
1056 delta.len(),
1057 ));
1058 while cursor < delta.len() {
1059 let command = delta[cursor];
1060 cursor += 1;
1061 if command & 0x80 != 0 {
1062 let copy_offset =
1063 read_delta_copy_value(delta, &mut cursor, command, &[0x01, 0x02, 0x04, 0x08])?;
1064 let mut copy_size =
1065 read_delta_copy_value(delta, &mut cursor, command, &[0x10, 0x20, 0x40])?;
1066 if copy_size == 0 {
1067 copy_size = 0x10000;
1068 }
1069 let start = usize::try_from(copy_offset)
1070 .map_err(|_| GitError::InvalidObject("delta copy offset overflows usize".into()))?;
1071 let len = usize::try_from(copy_size)
1072 .map_err(|_| GitError::InvalidObject("delta copy size overflows usize".into()))?;
1073 let end = start
1074 .checked_add(len)
1075 .ok_or_else(|| GitError::InvalidObject("delta copy range overflow".into()))?;
1076 let Some(slice) = base.get(start..end) else {
1077 return Err(GitError::InvalidObject(
1078 "delta copy range exceeds base object".into(),
1079 ));
1080 };
1081 result.extend_from_slice(slice);
1082 } else if command != 0 {
1083 let len = usize::from(command);
1084 let end = cursor
1085 .checked_add(len)
1086 .ok_or_else(|| GitError::InvalidObject("delta insert range overflow".into()))?;
1087 let Some(slice) = delta.get(cursor..end) else {
1088 return Err(GitError::InvalidObject(
1089 "delta insert range exceeds delta data".into(),
1090 ));
1091 };
1092 result.extend_from_slice(slice);
1093 cursor = end;
1094 } else {
1095 return Err(GitError::InvalidObject(
1096 "delta contains reserved zero command".into(),
1097 ));
1098 }
1099 }
1100 if result.len() as u64 != result_size {
1101 return Err(GitError::InvalidObject(format!(
1102 "delta result size mismatch: expected {result_size}, got {}",
1103 result.len()
1104 )));
1105 }
1106 Ok(result)
1107}
1108
1109pub(crate) fn decoded_delta_result_size(delta: &[u8]) -> Result<u64> {
1110 let mut cursor = 0usize;
1111 let _ = read_delta_varint(delta, &mut cursor)?;
1112 read_delta_varint(delta, &mut cursor)
1113}
1114pub(crate) fn read_delta_varint(delta: &[u8], cursor: &mut usize) -> Result<u64> {
1115 let mut value = 0u64;
1116 let mut shift = 0u32;
1117 loop {
1118 let Some(byte) = delta.get(*cursor).copied() else {
1119 return Err(GitError::InvalidObject("truncated delta size".into()));
1120 };
1121 *cursor += 1;
1122 value = value
1123 .checked_add(
1124 u64::from(byte & 0x7f)
1125 .checked_shl(shift)
1126 .ok_or_else(|| GitError::InvalidObject("delta size overflow".into()))?,
1127 )
1128 .ok_or_else(|| GitError::InvalidObject("delta size overflow".into()))?;
1129 if byte & 0x80 == 0 {
1130 return Ok(value);
1131 }
1132 shift = shift
1133 .checked_add(7)
1134 .ok_or_else(|| GitError::InvalidObject("delta size overflow".into()))?;
1135 }
1136}
1137
1138pub(crate) fn read_delta_copy_value(
1139 delta: &[u8],
1140 cursor: &mut usize,
1141 command: u8,
1142 masks: &[u8],
1143) -> Result<u64> {
1144 let mut value = 0u64;
1145 for (shift, mask) in masks.iter().enumerate() {
1146 if command & mask != 0 {
1147 let Some(byte) = delta.get(*cursor).copied() else {
1148 return Err(GitError::InvalidObject(
1149 "truncated delta copy command".into(),
1150 ));
1151 };
1152 *cursor += 1;
1153 value |= u64::from(byte) << (shift * 8);
1154 }
1155 }
1156 Ok(value)
1157}