1use super::*;
6use sley_core::primitives::BiasedVarintError;
7
8impl PackFile {
9 pub fn parse_sha1(bytes: &[u8]) -> Result<Self> {
10 Self::parse_sha1_with_limits(bytes, PackReadLimits::default())
11 }
12
13 pub fn parse_sha1_with_limits(bytes: &[u8], limits: PackReadLimits) -> Result<Self> {
14 Self::parse_with_limits(bytes, ObjectFormat::Sha1, limits)
15 }
16
17 pub fn parse(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
18 Self::parse_with_limits(bytes, format, PackReadLimits::default())
19 }
20
21 pub fn parse_with_limits(
23 bytes: &[u8],
24 format: ObjectFormat,
25 limits: PackReadLimits,
26 ) -> Result<Self> {
27 Self::parse_with_base_and_limits(bytes, format, |_| Ok(None), limits)
28 }
29
30 pub fn parse_bundle(bundle: &Bundle) -> Result<Self> {
31 Self::parse_bundle_with_limits(bundle, PackReadLimits::default())
32 }
33
34 pub fn parse_bundle_with_limits(bundle: &Bundle, limits: PackReadLimits) -> Result<Self> {
35 Self::parse_with_limits(&bundle.pack, bundle.format, limits)
36 }
37
38 pub fn index_pack(bytes: &[u8], format: ObjectFormat) -> Result<PackWrite> {
39 Self::index_pack_with_limits(bytes, format, PackReadLimits::default())
40 }
41
42 pub fn index_pack_with_limits(
43 bytes: &[u8],
44 format: ObjectFormat,
45 limits: PackReadLimits,
46 ) -> Result<PackWrite> {
47 let PackIndexBuild {
48 index,
49 pack_checksum,
50 entries,
51 objects: _,
52 } = PackIndex::write_v2_for_pack_with_limits(bytes, format, limits)?;
53 Ok(PackWrite {
54 pack: bytes.to_vec(),
55 index,
56 checksum: pack_checksum,
57 entries,
58 delta_count: 0,
59 })
60 }
61
62 pub fn parse_thin<F>(bytes: &[u8], format: ObjectFormat, external_base: F) -> Result<Self>
63 where
64 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
65 {
66 Self::parse_thin_with_limits(bytes, format, external_base, PackReadLimits::default())
67 }
68
69 pub fn parse_thin_with_limits<F>(
70 bytes: &[u8],
71 format: ObjectFormat,
72 external_base: F,
73 limits: PackReadLimits,
74 ) -> Result<Self>
75 where
76 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
77 {
78 Self::parse_with_base_and_limits(bytes, format, external_base, limits)
79 }
80
81 pub(crate) fn parse_with_base_and_limits<F>(
82 bytes: &[u8],
83 format: ObjectFormat,
84 mut external_base: F,
85 limits: PackReadLimits,
86 ) -> Result<Self>
87 where
88 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
89 {
90 parse_parallel_pack(
91 bytes,
92 format,
93 &mut external_base,
94 PackIndexOptions::new(limits),
95 CancelFlag::never(),
96 )
97 }
98
99 pub fn verify_pack_stats(bytes: &[u8], format: ObjectFormat) -> Result<PackVerifyStats> {
110 Self::verify_pack_stats_with_limits(bytes, format, PackReadLimits::default())
111 }
112
113 pub fn verify_pack_stats_with_limits(
114 bytes: &[u8],
115 format: ObjectFormat,
116 limits: PackReadLimits,
117 ) -> Result<PackVerifyStats> {
118 let pack = Self::parse_with_limits(bytes, format, limits)?;
122
123 let trailer_len = format.raw_len();
127 let trailer_offset = bytes.len() - trailer_len;
128 let entry_region = pack_entry_region(bytes, trailer_offset)?;
129 let count = checked_pack_object_count(
130 u32_be(&bytes[8..12]),
131 (trailer_offset.saturating_sub(12)) as u64,
132 )?;
133 let mut offset = 12usize;
134 let mut on_disk: Vec<OnDiskEntry> = Vec::with_capacity(pack_entry_prealloc(count));
140 for _ in 0..count {
141 let entry_offset = offset as u64;
142 let header = parse_entry_header(entry_region, &mut offset)?;
143 let stream_size = header.size;
144 let base = match header.kind {
145 PackObjectKind::OfsDelta => Some(DeltaBase::Offset(parse_ofs_delta_base_offset(
146 entry_region,
147 &mut offset,
148 entry_offset,
149 )?)),
150 PackObjectKind::RefDelta => {
151 let hash_len = format.raw_len();
152 if offset + hash_len > trailer_offset {
153 return Err(GitError::InvalidFormat(
154 "truncated ref-delta base object id".into(),
155 ));
156 }
157 let oid = ObjectId::from_raw(format, &entry_region[offset..offset + hash_len])?;
158 offset += hash_len;
159 Some(DeltaBase::Ref(oid))
160 }
161 _ => None,
162 };
163 let mut body = Vec::new();
165 let consumed = inflate_into(
166 &entry_region[offset..],
167 &mut body,
168 header.size.min(usize::MAX as u64) as usize,
169 )?;
170 offset = offset
171 .checked_add(consumed)
172 .ok_or_else(|| GitError::InvalidFormat("pack offset overflow".into()))?;
173 on_disk.push(OnDiskEntry {
174 offset: entry_offset,
175 base,
176 stream_size,
177 });
178 }
179
180 let mut resolved_by_offset: HashMap<u64, &PackObject> =
182 HashMap::with_capacity(pack.entries.len());
183 for object in &pack.entries {
184 resolved_by_offset.insert(object.entry.offset, object);
185 }
186 let mut oid_by_offset: HashMap<u64, ObjectId> = HashMap::with_capacity(on_disk.len());
188 for entry in &on_disk {
189 if let Some(object) = resolved_by_offset.get(&entry.offset) {
190 oid_by_offset.insert(entry.offset, object.entry.oid);
191 }
192 }
193 let mut index_by_offset: HashMap<u64, usize> = HashMap::with_capacity(on_disk.len());
195 for (idx, entry) in on_disk.iter().enumerate() {
196 index_by_offset.insert(entry.offset, idx);
197 }
198
199 let mut sorted_offsets: Vec<u64> = on_disk.iter().map(|entry| entry.offset).collect();
202 sorted_offsets.sort_unstable();
203 let mut next_offset: HashMap<u64, u64> = HashMap::with_capacity(sorted_offsets.len());
204 for window in sorted_offsets.windows(2) {
205 next_offset.insert(window[0], window[1]);
206 }
207 if let Some(last) = sorted_offsets.last() {
208 next_offset.insert(*last, trailer_offset as u64);
209 }
210
211 let mut depth = vec![None; on_disk.len()];
217 fn resolve_depth(
218 idx: usize,
219 on_disk: &[OnDiskEntry],
220 index_by_offset: &HashMap<u64, usize>,
221 offset_of_oid: &HashMap<ObjectId, u64>,
222 depth: &mut [Option<u32>],
223 ) -> u32 {
224 if let Some(d) = depth[idx] {
225 return d;
226 }
227 let computed = match &on_disk[idx].base {
228 None => 0,
229 Some(base) => {
230 let base_idx = match base {
231 DeltaBase::Offset(off) => index_by_offset.get(off).copied(),
232 DeltaBase::Ref(oid) => offset_of_oid
233 .get(oid)
234 .and_then(|off| index_by_offset.get(off).copied()),
235 };
236 match base_idx {
237 Some(bi) => {
238 resolve_depth(bi, on_disk, index_by_offset, offset_of_oid, depth) + 1
239 }
240 None => 1,
242 }
243 }
244 };
245 depth[idx] = Some(computed);
246 computed
247 }
248 let mut offset_of_oid: HashMap<ObjectId, u64> = HashMap::with_capacity(oid_by_offset.len());
249 for (off, oid) in &oid_by_offset {
250 offset_of_oid.insert(*oid, *off);
251 }
252 for idx in 0..on_disk.len() {
253 resolve_depth(idx, &on_disk, &index_by_offset, &offset_of_oid, &mut depth);
254 }
255
256 let mut stats = Vec::with_capacity(on_disk.len());
257 for (idx, entry) in on_disk.iter().enumerate() {
258 let off = entry.offset;
259 let object = resolved_by_offset.get(&off).ok_or_else(|| {
260 GitError::InvalidFormat("pack offset missing from resolved set".into())
261 })?;
262 let size_in_pack = next_offset
263 .get(&off)
264 .copied()
265 .unwrap_or(trailer_offset as u64)
266 .saturating_sub(off);
267 let base_oid = match &entry.base {
268 None => None,
269 Some(DeltaBase::Offset(base_off)) => oid_by_offset.get(base_off).copied(),
270 Some(DeltaBase::Ref(oid)) => Some(*oid),
271 };
272 stats.push(PackVerifyStat {
273 oid: object.entry.oid,
274 object_type: object.object.object_type,
275 size: entry.stream_size,
278 size_in_pack,
279 offset: off,
280 delta_depth: depth[idx].unwrap_or(0),
281 base_oid,
282 });
283 }
284 stats.sort_by_key(|stat| stat.offset);
286
287 Ok(PackVerifyStats {
288 objects: stats,
289 checksum: pack.checksum,
290 })
291 }
292}
293
294pub trait PackDeltaCache {
308 fn get(&self, offset: u64) -> Option<Arc<EncodedObject>>;
310 fn insert(&self, offset: u64, object: Arc<EncodedObject>);
312}
313
314pub(crate) struct NoopDeltaCache;
317
318impl PackDeltaCache for NoopDeltaCache {
319 fn get(&self, _offset: u64) -> Option<Arc<EncodedObject>> {
320 None
321 }
322 fn insert(&self, _offset: u64, _object: Arc<EncodedObject>) {}
323}
324
325thread_local! {
331 static INFLATE: RefCell<flate2::Decompress> = RefCell::new(flate2::Decompress::new(true));
332}
333
334pub(crate) fn inflate_into(
354 compressed: &[u8],
355 out: &mut Vec<u8>,
356 size_hint: usize,
357) -> Result<usize> {
358 INFLATE.with(|cell| {
359 let mut decompress = cell.borrow_mut();
360 decompress.reset(true);
361 out.reserve(inflate::bounded_inflate_reserve(
362 size_hint,
363 compressed.len(),
364 ));
365 let mut input = compressed;
366 let mut consumed_total = 0usize;
367 loop {
368 if out.len() == out.capacity() {
371 out.reserve(out.len().max(64));
372 }
373 let before_in = decompress.total_in();
374 let before_out = decompress.total_out();
375 let status = decompress
376 .decompress_vec(input, out, flate2::FlushDecompress::None)
377 .map_err(|err| GitError::InvalidObject(format!("zlib inflate failed: {err}")))?;
378 let consumed = (decompress.total_in() - before_in) as usize;
379 let produced = decompress.total_out() - before_out;
380 input = &input[consumed..];
381 consumed_total += consumed;
382 match status {
383 flate2::Status::StreamEnd => return Ok(consumed_total),
384 _ if consumed == 0 && produced == 0 => {
385 return Err(GitError::InvalidObject("truncated zlib stream".into()));
386 }
387 _ => {}
388 }
389 }
390 })
391}
392
393pub(crate) fn inflate_prefix(compressed: &[u8], out: &mut [u8]) -> Result<usize> {
398 INFLATE.with(|cell| {
399 let mut decompress = cell.borrow_mut();
400 decompress.reset(true);
401 let mut input = compressed;
402 let mut written = 0usize;
403 while written < out.len() {
404 let before_in = decompress.total_in();
405 let before_out = decompress.total_out();
406 let status = decompress
407 .decompress(input, &mut out[written..], flate2::FlushDecompress::None)
408 .map_err(|err| GitError::InvalidObject(format!("zlib inflate failed: {err}")))?;
409 let consumed = (decompress.total_in() - before_in) as usize;
410 let produced = (decompress.total_out() - before_out) as usize;
411 input = &input[consumed..];
412 written = written.saturating_add(produced);
413 if status == flate2::Status::StreamEnd || (consumed == 0 && produced == 0) {
414 break;
415 }
416 }
417 Ok(written)
418 })
419}
420pub fn read_object_at_arc<F>(
428 pack_bytes: &[u8],
429 offset: u64,
430 format: ObjectFormat,
431 resolve_ref_base: F,
432) -> Result<Arc<EncodedObject>>
433where
434 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
435{
436 read_object_at_with_cache_arc(
437 pack_bytes,
438 offset,
439 format,
440 resolve_ref_base,
441 &NoopDeltaCache,
442 )
443}
444
445pub fn read_object_at_with_cache_arc<F, C>(
454 pack_bytes: &[u8],
455 offset: u64,
456 format: ObjectFormat,
457 mut resolve_ref_base: F,
458 cache: &C,
459) -> Result<Arc<EncodedObject>>
460where
461 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
462 C: PackDeltaCache + ?Sized,
463{
464 read_object_at_with_cache_and_ofs_base_arc(
465 pack_bytes,
466 offset,
467 format,
468 &mut resolve_ref_base,
469 |_offset| Ok(None),
470 cache,
471 )
472}
473
474pub fn read_object_at_with_cache_and_ofs_base_arc<F, G, C>(
480 pack_bytes: &[u8],
481 offset: u64,
482 format: ObjectFormat,
483 mut resolve_ref_base: F,
484 mut resolve_ofs_base: G,
485 cache: &C,
486) -> Result<Arc<EncodedObject>>
487where
488 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
489 G: FnMut(u64) -> Result<Option<Arc<EncodedObject>>>,
490 C: PackDeltaCache + ?Sized,
491{
492 read_object_at_inner(
493 pack_bytes,
494 offset,
495 format,
496 &mut resolve_ref_base,
497 &mut resolve_ofs_base,
498 cache,
499 )
500}
501
502pub fn read_object_at_with_ofs_base_arc<F, G>(
504 pack_bytes: &[u8],
505 offset: u64,
506 format: ObjectFormat,
507 resolve_ref_base: F,
508 resolve_ofs_base: G,
509) -> Result<Arc<EncodedObject>>
510where
511 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
512 G: FnMut(u64) -> Result<Option<Arc<EncodedObject>>>,
513{
514 read_object_at_with_cache_and_ofs_base_arc(
515 pack_bytes,
516 offset,
517 format,
518 resolve_ref_base,
519 resolve_ofs_base,
520 &NoopDeltaCache,
521 )
522}
523
524pub(crate) fn read_object_at_inner<F, G, C>(
525 pack_bytes: &[u8],
526 offset: u64,
527 format: ObjectFormat,
528 resolve_ref_base: &mut F,
529 resolve_ofs_base: &mut G,
530 cache: &C,
531) -> Result<Arc<EncodedObject>>
532where
533 F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
534 G: FnMut(u64) -> Result<Option<Arc<EncodedObject>>>,
535 C: PackDeltaCache + ?Sized,
536{
537 if let Some(object) = cache.get(offset) {
540 return Ok(object);
541 }
542 let trailer_offset = pack_bytes
543 .len()
544 .checked_sub(format.raw_len())
545 .ok_or_else(|| GitError::InvalidFormat("pack smaller than its trailer".into()))?;
546 let entry_region = pack_entry_region(pack_bytes, trailer_offset)?;
547 let mut cursor = usize::try_from(offset)
548 .ok()
549 .filter(|&value| value < trailer_offset)
550 .ok_or_else(|| GitError::InvalidFormat("pack object offset out of range".into()))?;
551 let header = parse_entry_header(entry_region, &mut cursor)?;
552 let base = match header.kind {
553 PackObjectKind::OfsDelta => Some(DeltaBase::Offset(parse_ofs_delta_base_offset(
554 entry_region,
555 &mut cursor,
556 offset,
557 )?)),
558 PackObjectKind::RefDelta => {
559 let hash_len = format.raw_len();
560 if cursor + hash_len > trailer_offset {
561 return Err(GitError::InvalidFormat(
562 "truncated ref-delta base object id".into(),
563 ));
564 }
565 let oid = ObjectId::from_raw(format, &entry_region[cursor..cursor + hash_len])?;
566 cursor += hash_len;
567 Some(DeltaBase::Ref(oid))
568 }
569 _ => None,
570 };
571 let mut body = Vec::new();
572 inflate_into(
573 &entry_region[cursor..],
574 &mut body,
575 header.size.min(usize::MAX as u64) as usize,
576 )?;
577 if body.len() as u64 != header.size {
578 return Err(GitError::InvalidObject(format!(
579 "pack object declared {} bytes, decoded {}",
580 header.size,
581 body.len()
582 )));
583 }
584 let object = match base {
585 None => {
586 let object_type = match header.kind {
587 PackObjectKind::Commit => ObjectType::Commit,
588 PackObjectKind::Tree => ObjectType::Tree,
589 PackObjectKind::Blob => ObjectType::Blob,
590 PackObjectKind::Tag => ObjectType::Tag,
591 PackObjectKind::OfsDelta | PackObjectKind::RefDelta => {
592 return Err(GitError::InvalidFormat(
593 "delta pack entry decoded without a base".into(),
594 ));
595 }
596 };
597 Arc::new(EncodedObject::new(object_type, body))
598 }
599 Some(DeltaBase::Offset(base_offset)) => {
600 let base = match read_object_at_inner(
601 pack_bytes,
602 base_offset,
603 format,
604 resolve_ref_base,
605 resolve_ofs_base,
606 cache,
607 ) {
608 Ok(base) => base,
609 Err(pack_err) => match resolve_ofs_base(base_offset)? {
610 Some(base) => base,
611 None => return Err(pack_err),
612 },
613 };
614 let resolved = apply_pack_delta(&base.body, &body)?;
615 Arc::new(EncodedObject::new(base.object_type, resolved))
616 }
617 Some(DeltaBase::Ref(base_oid)) => {
618 let base = resolve_ref_base(&base_oid)?
619 .ok_or_else(|| GitError::not_found(format!("ref-delta base object {base_oid}")))?;
620 let resolved = apply_pack_delta(&base.body, &body)?;
621 Arc::new(EncodedObject::new(base.object_type, resolved))
622 }
623 };
624 cache.insert(offset, Arc::clone(&object));
628 Ok(object)
629}
630
631pub fn read_object_header_at<F>(
644 pack_bytes: &[u8],
645 offset: u64,
646 format: ObjectFormat,
647 initial_delta_depth: usize,
648 mut resolve_ref_base_type: F,
649) -> Result<PackObjectHeader>
650where
651 F: FnMut(&ObjectId, usize) -> Result<Option<PackObjectHeader>>,
652{
653 read_object_header_at_inner(
654 pack_bytes,
655 offset,
656 format,
657 initial_delta_depth,
658 &mut resolve_ref_base_type,
659 &mut NoopHeaderTypeCache,
660 )
661}
662
663#[derive(Clone, Copy, Debug, PartialEq, Eq)]
666pub struct PackObjectHeader {
667 pub object_type: ObjectType,
668 pub size: u64,
669 pub delta_depth: usize,
671}
672
673impl PackObjectHeader {
674 pub const fn undeltified(object_type: ObjectType, size: u64) -> Self {
675 Self {
676 object_type,
677 size,
678 delta_depth: 0,
679 }
680 }
681
682 pub const fn type_and_size(self) -> (ObjectType, u64) {
683 (self.object_type, self.size)
684 }
685}
686
687pub trait HeaderTypeCache {
704 fn get(&self, pack_offset: u64) -> Option<PackObjectHeader>;
706 fn put(&mut self, pack_offset: u64, header: PackObjectHeader);
708}
709
710pub(crate) struct NoopHeaderTypeCache;
711
712impl HeaderTypeCache for NoopHeaderTypeCache {
713 fn get(&self, _pack_offset: u64) -> Option<PackObjectHeader> {
714 None
715 }
716 fn put(&mut self, _pack_offset: u64, _header: PackObjectHeader) {}
717}
718
719pub fn read_object_header_at_with_cache<F, C>(
726 pack_bytes: &[u8],
727 offset: u64,
728 format: ObjectFormat,
729 initial_delta_depth: usize,
730 mut resolve_ref_base_type: F,
731 type_cache: &mut C,
732) -> Result<PackObjectHeader>
733where
734 F: FnMut(&ObjectId, usize) -> Result<Option<PackObjectHeader>>,
735 C: HeaderTypeCache + ?Sized,
736{
737 if let Some(header) = type_cache.get(offset) {
738 checked_cached_header_depth(offset, initial_delta_depth, header.delta_depth)?;
739 return Ok(header);
740 }
741 read_object_header_at_inner(
742 pack_bytes,
743 offset,
744 format,
745 initial_delta_depth,
746 &mut resolve_ref_base_type,
747 type_cache,
748 )
749}
750
751pub(crate) fn read_object_header_at_inner<F, C>(
752 pack_bytes: &[u8],
753 offset: u64,
754 format: ObjectFormat,
755 delta_depth: usize,
756 resolve_ref_base_type: &mut F,
757 type_cache: &mut C,
758) -> Result<PackObjectHeader>
759where
760 F: FnMut(&ObjectId, usize) -> Result<Option<PackObjectHeader>>,
761 C: HeaderTypeCache + ?Sized,
762{
763 let trailer_offset = pack_bytes
764 .len()
765 .checked_sub(format.raw_len())
766 .ok_or_else(|| GitError::InvalidFormat("pack smaller than its trailer".into()))?;
767 let entry_region = pack_entry_region(pack_bytes, trailer_offset)?;
768 let mut cursor = usize::try_from(offset)
769 .ok()
770 .filter(|&value| value < trailer_offset)
771 .ok_or_else(|| GitError::InvalidFormat("pack object offset out of range".into()))?;
772 let header = parse_entry_header(entry_region, &mut cursor)?;
773 let resolved = match header.kind {
774 PackObjectKind::Commit => PackObjectHeader::undeltified(ObjectType::Commit, header.size),
775 PackObjectKind::Tree => PackObjectHeader::undeltified(ObjectType::Tree, header.size),
776 PackObjectKind::Blob => PackObjectHeader::undeltified(ObjectType::Blob, header.size),
777 PackObjectKind::Tag => PackObjectHeader::undeltified(ObjectType::Tag, header.size),
778 PackObjectKind::OfsDelta => {
779 let next_delta_depth = checked_header_delta_depth(offset, delta_depth)?;
780 let base_offset = parse_ofs_delta_base_offset(entry_region, &mut cursor, offset)?;
781 let size = delta_result_size_from_stream(&entry_region[cursor..])?;
782 let base_header = match type_cache.get(base_offset) {
785 Some(base_header) => {
786 checked_cached_header_depth(
787 base_offset,
788 next_delta_depth,
789 base_header.delta_depth,
790 )?;
791 base_header
792 }
793 None => read_object_header_at_inner(
794 pack_bytes,
795 base_offset,
796 format,
797 next_delta_depth,
798 resolve_ref_base_type,
799 type_cache,
800 )?,
801 };
802 let resolved_delta_depth = checked_header_delta_depth(offset, base_header.delta_depth)?;
803 PackObjectHeader {
804 object_type: base_header.object_type,
805 size,
806 delta_depth: resolved_delta_depth,
807 }
808 }
809 PackObjectKind::RefDelta => {
810 let next_delta_depth = checked_header_delta_depth(offset, delta_depth)?;
811 let hash_len = format.raw_len();
812 if cursor + hash_len > trailer_offset {
813 return Err(GitError::InvalidFormat(
814 "truncated ref-delta base object id".into(),
815 ));
816 }
817 let oid = ObjectId::from_raw(format, &entry_region[cursor..cursor + hash_len])?;
818 cursor += hash_len;
819 let size = delta_result_size_from_stream(&entry_region[cursor..])?;
820 let base_header = resolve_ref_base_type(&oid, next_delta_depth)?
821 .ok_or_else(|| GitError::not_found(format!("ref-delta base object {oid}")))?;
822 let resolved_delta_depth = checked_header_delta_depth(offset, base_header.delta_depth)?;
823 checked_cached_header_depth(offset, delta_depth, resolved_delta_depth)?;
824 PackObjectHeader {
825 object_type: base_header.object_type,
826 size,
827 delta_depth: resolved_delta_depth,
828 }
829 }
830 };
831 type_cache.put(offset, resolved);
834 Ok(resolved)
835}
836
837fn checked_header_delta_depth(offset: u64, delta_depth: usize) -> Result<usize> {
838 let observed_depth = delta_depth.checked_add(1).ok_or_else(|| {
839 GitError::InvalidFormat(format!(
840 "pack delta chain depth overflows at offset {offset}"
841 ))
842 })?;
843 if observed_depth > MAX_READ_DELTA_CHAIN_DEPTH {
844 return Err(GitError::InvalidFormat(format!(
845 "pack delta chain at offset {offset} has observed depth {observed_depth}, which \
846 exceeds maximum depth (configured limit {MAX_READ_DELTA_CHAIN_DEPTH})"
847 )));
848 }
849 Ok(observed_depth)
850}
851
852fn checked_cached_header_depth(
853 offset: u64,
854 initial_delta_depth: usize,
855 cached_delta_depth: usize,
856) -> Result<()> {
857 let observed_depth = initial_delta_depth
858 .checked_add(cached_delta_depth)
859 .ok_or_else(|| {
860 GitError::InvalidFormat(format!(
861 "pack delta chain depth overflows at offset {offset}"
862 ))
863 })?;
864 if observed_depth > MAX_READ_DELTA_CHAIN_DEPTH {
865 return Err(GitError::InvalidFormat(format!(
866 "pack delta chain at offset {offset} has observed depth {observed_depth}, which \
867 exceeds maximum depth (configured limit {MAX_READ_DELTA_CHAIN_DEPTH})"
868 )));
869 }
870 Ok(())
871}
872
873pub(crate) const DELTA_HEADER_PREFIX_LEN: usize = 32;
877
878pub(crate) fn delta_result_size_from_stream(compressed: &[u8]) -> Result<u64> {
881 let mut prefix = [0u8; DELTA_HEADER_PREFIX_LEN];
882 let written = inflate_prefix(compressed, &mut prefix)?;
883 decoded_delta_result_size(&prefix[..written])
884}
885
886fn pack_entry_region(bytes: &[u8], trailer_offset: usize) -> Result<&[u8]> {
900 bytes
901 .get(..trailer_offset)
902 .ok_or_else(|| GitError::InvalidFormat("pack smaller than its trailer".into()))
903}
904
905pub(crate) fn parse_entry_header(bytes: &[u8], offset: &mut usize) -> Result<EntryHeader> {
906 let first = next_byte(bytes, offset)?;
907 let mut size = u64::from(first & 0x0f);
908 let kind = match (first >> 4) & 0x07 {
909 1 => PackObjectKind::Commit,
910 2 => PackObjectKind::Tree,
911 3 => PackObjectKind::Blob,
912 4 => PackObjectKind::Tag,
913 6 => PackObjectKind::OfsDelta,
914 7 => PackObjectKind::RefDelta,
915 other => {
916 return Err(GitError::InvalidFormat(format!(
917 "invalid pack object type {other}"
918 )));
919 }
920 };
921 let mut shift = 4;
922 let mut byte = first;
923 while byte & 0x80 != 0 {
924 byte = next_byte(bytes, offset)?;
925 let part = u64::from(byte & 0x7f);
926 size = size
927 .checked_add(
928 part.checked_shl(shift)
929 .ok_or_else(|| GitError::InvalidFormat("pack size overflow".into()))?,
930 )
931 .ok_or_else(|| GitError::InvalidFormat("pack size overflow".into()))?;
932 shift += 7;
933 }
934 Ok(EntryHeader { kind, size })
935}
936
937pub(crate) fn parse_ofs_delta_base_offset(
938 bytes: &[u8],
939 offset: &mut usize,
940 entry_offset: u64,
941) -> Result<u64> {
942 let relative = sley_core::primitives::read_biased_varint(bytes, offset).map_err(|err| {
943 GitError::InvalidFormat(
944 match err {
945 BiasedVarintError::Truncated => "truncated pack entry header",
946 BiasedVarintError::Overflow => "ofs-delta offset overflow",
947 }
948 .into(),
949 )
950 })?;
951 entry_offset
952 .checked_sub(relative)
953 .ok_or_else(|| GitError::InvalidFormat("ofs-delta points before pack start".into()))
954}
955
956pub(crate) fn apply_pack_delta(base: &[u8], delta: &[u8]) -> Result<Vec<u8>> {
957 let plan = plan_pack_delta(base, delta)?;
958 let result_size = plan.result_size;
959 let result_size_hint = usize::try_from(result_size).unwrap_or(usize::MAX);
960 let mut result = Vec::with_capacity(inflate::bounded_inflate_reserve(
964 result_size_hint,
965 delta.len(),
966 ));
967 walk_pack_delta(base, delta, plan, CancelFlag::never(), |slice| {
968 result.extend_from_slice(slice);
969 Ok(())
970 })?;
971 if result.len() as u64 != result_size {
972 return Err(GitError::InvalidObject(format!(
973 "delta result size mismatch: expected {result_size}, got {}",
974 result.len()
975 )));
976 }
977 Ok(result)
978}
979
980#[derive(Debug, Clone, Copy)]
981pub(crate) struct PackDeltaPlan {
982 instructions_offset: usize,
983 pub(crate) result_size: u64,
984}
985
986pub(crate) fn plan_pack_delta(base: &[u8], delta: &[u8]) -> Result<PackDeltaPlan> {
987 let mut instructions_offset = 0usize;
988 let base_size = read_delta_varint(delta, &mut instructions_offset)?;
989 if base_size != base.len() as u64 {
990 return Err(GitError::InvalidObject(format!(
991 "delta base size mismatch: expected {base_size}, got {}",
992 base.len()
993 )));
994 }
995 let result_size = read_delta_varint(delta, &mut instructions_offset)?;
996 Ok(PackDeltaPlan {
997 instructions_offset,
998 result_size,
999 })
1000}
1001
1002pub(crate) fn apply_pack_delta_exact(
1003 base: &[u8],
1004 delta: &[u8],
1005 plan: PackDeltaPlan,
1006 result: &mut Vec<u8>,
1007 cancel: CancelFlag<'_>,
1008) -> Result<()> {
1009 if !result.is_empty() || u64::try_from(result.capacity()).unwrap_or(u64::MAX) < plan.result_size
1010 {
1011 return Err(GitError::InvalidObject(
1012 "delta output buffer is not empty and preallocated to the declared result size".into(),
1013 ));
1014 }
1015 walk_pack_delta(base, delta, plan, cancel, |slice| {
1016 let end = result
1017 .len()
1018 .checked_add(slice.len())
1019 .ok_or_else(|| GitError::InvalidObject("delta output range overflow".into()))?;
1020 if u64::try_from(end).unwrap_or(u64::MAX) > plan.result_size {
1021 return Err(GitError::InvalidObject(
1022 "delta instructions exceed declared result size".into(),
1023 ));
1024 }
1025 result.extend_from_slice(slice);
1026 Ok(())
1027 })?;
1028 cancel.check()?;
1029 if result.len() as u64 != plan.result_size {
1030 return Err(GitError::InvalidObject(format!(
1031 "delta result size mismatch: expected {}, got {}",
1032 plan.result_size,
1033 result.len()
1034 )));
1035 }
1036 Ok(())
1037}
1038
1039fn walk_pack_delta<F>(
1040 base: &[u8],
1041 delta: &[u8],
1042 plan: PackDeltaPlan,
1043 cancel: CancelFlag<'_>,
1044 mut emit: F,
1045) -> Result<()>
1046where
1047 F: FnMut(&[u8]) -> Result<()>,
1048{
1049 let mut cursor = plan.instructions_offset;
1050 while cursor < delta.len() {
1051 cancel.check()?;
1052 let command = delta[cursor];
1053 cursor += 1;
1054 if command & 0x80 != 0 {
1055 let copy_offset =
1056 read_delta_copy_value(delta, &mut cursor, command, &[0x01, 0x02, 0x04, 0x08])?;
1057 let mut copy_size =
1058 read_delta_copy_value(delta, &mut cursor, command, &[0x10, 0x20, 0x40])?;
1059 if copy_size == 0 {
1060 copy_size = 0x10000;
1061 }
1062 let start = usize::try_from(copy_offset)
1063 .map_err(|_| GitError::InvalidObject("delta copy offset overflows usize".into()))?;
1064 let len = usize::try_from(copy_size)
1065 .map_err(|_| GitError::InvalidObject("delta copy size overflows usize".into()))?;
1066 let end = start
1067 .checked_add(len)
1068 .ok_or_else(|| GitError::InvalidObject("delta copy range overflow".into()))?;
1069 let Some(slice) = base.get(start..end) else {
1070 return Err(GitError::InvalidObject(
1071 "delta copy range exceeds base object".into(),
1072 ));
1073 };
1074 emit(slice)?;
1075 } else if command != 0 {
1076 let len = usize::from(command);
1077 let end = cursor
1078 .checked_add(len)
1079 .ok_or_else(|| GitError::InvalidObject("delta insert range overflow".into()))?;
1080 let Some(slice) = delta.get(cursor..end) else {
1081 return Err(GitError::InvalidObject(
1082 "delta insert range exceeds delta data".into(),
1083 ));
1084 };
1085 emit(slice)?;
1086 cursor = end;
1087 } else {
1088 return Err(GitError::InvalidObject(
1089 "delta contains reserved zero command".into(),
1090 ));
1091 }
1092 }
1093 cancel.check()?;
1094 Ok(())
1095}
1096
1097pub(crate) fn decoded_delta_result_size(delta: &[u8]) -> Result<u64> {
1098 let mut cursor = 0usize;
1099 let _ = read_delta_varint(delta, &mut cursor)?;
1100 read_delta_varint(delta, &mut cursor)
1101}
1102
1103pub(crate) fn read_delta_varint(delta: &[u8], cursor: &mut usize) -> Result<u64> {
1104 let mut value = 0u64;
1105 let mut shift = 0u32;
1106 loop {
1107 let Some(byte) = delta.get(*cursor).copied() else {
1108 return Err(GitError::InvalidObject("truncated delta size".into()));
1109 };
1110 *cursor += 1;
1111 value = value
1112 .checked_add(
1113 u64::from(byte & 0x7f)
1114 .checked_shl(shift)
1115 .ok_or_else(|| GitError::InvalidObject("delta size overflow".into()))?,
1116 )
1117 .ok_or_else(|| GitError::InvalidObject("delta size overflow".into()))?;
1118 if byte & 0x80 == 0 {
1119 return Ok(value);
1120 }
1121 shift = shift
1122 .checked_add(7)
1123 .ok_or_else(|| GitError::InvalidObject("delta size overflow".into()))?;
1124 }
1125}
1126
1127pub(crate) fn read_delta_copy_value(
1128 delta: &[u8],
1129 cursor: &mut usize,
1130 command: u8,
1131 masks: &[u8],
1132) -> Result<u64> {
1133 let mut value = 0u64;
1134 for (shift, mask) in masks.iter().enumerate() {
1135 if command & mask != 0 {
1136 let Some(byte) = delta.get(*cursor).copied() else {
1137 return Err(GitError::InvalidObject(
1138 "truncated delta copy command".into(),
1139 ));
1140 };
1141 *cursor += 1;
1142 value |= u64::from(byte) << (shift * 8);
1143 }
1144 }
1145 Ok(value)
1146}