1use super::*;
8use flate2::{Decompress, FlushDecompress, Status};
9use std::collections::{HashMap, HashSet};
10use std::io;
11
12const ENTRY_PREFIX_BYTES: usize = 64;
13const INFLATE_CHUNK_BYTES: usize = 8 * 1024;
14
15pub trait PackReadSource {
18 fn len(&self) -> io::Result<u64>;
20
21 fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<usize>;
24
25 fn is_empty(&self) -> io::Result<bool> {
27 self.len().map(|len| len == 0)
28 }
29}
30
31#[derive(Debug, Clone, Copy)]
34pub struct SlicePackSource<'a> {
35 bytes: &'a [u8],
36}
37
38impl<'a> SlicePackSource<'a> {
39 pub const fn new(bytes: &'a [u8]) -> Self {
40 Self { bytes }
41 }
42}
43
44impl PackReadSource for SlicePackSource<'_> {
45 fn len(&self) -> io::Result<u64> {
46 Ok(self.bytes.len() as u64)
47 }
48
49 fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<usize> {
50 let start = usize::try_from(offset).unwrap_or(usize::MAX);
51 let Some(remaining) = self.bytes.get(start..) else {
52 return Ok(0);
53 };
54 let count = remaining.len().min(buf.len());
55 buf[..count].copy_from_slice(&remaining[..count]);
56 Ok(count)
57 }
58}
59
60impl PackReadSource for Vec<u8> {
61 fn len(&self) -> io::Result<u64> {
62 Ok(self.len() as u64)
63 }
64
65 fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<usize> {
66 let start = usize::try_from(offset).unwrap_or(usize::MAX);
67 let Some(remaining) = self.get(start..) else {
68 return Ok(0);
69 };
70 let count = remaining.len().min(buf.len());
71 buf[..count].copy_from_slice(&remaining[..count]);
72 Ok(count)
73 }
74}
75
76#[cfg(unix)]
77impl PackReadSource for std::fs::File {
78 fn len(&self) -> io::Result<u64> {
79 self.metadata().map(|metadata| metadata.len())
80 }
81
82 fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<usize> {
83 std::os::unix::fs::FileExt::read_at(self, buf, offset)
84 }
85}
86
87#[cfg(windows)]
88impl PackReadSource for std::fs::File {
89 fn len(&self) -> io::Result<u64> {
90 self.metadata().map(|metadata| metadata.len())
91 }
92
93 fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<usize> {
94 std::os::windows::fs::FileExt::seek_read(self, buf, offset)
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct PackReadLimits {
105 pub max_delta_depth: usize,
107 pub max_materialized_bytes: usize,
116 pub max_cached_bytes: usize,
119}
120
121impl Default for PackReadLimits {
122 fn default() -> Self {
123 Self {
124 max_delta_depth: MAX_READ_DELTA_CHAIN_DEPTH,
125 max_materialized_bytes: 64 * 1024 * 1024,
126 max_cached_bytes: 16 * 1024 * 1024,
127 }
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum PackLimitKind {
134 DeltaDepth,
135 MaterializedBytes,
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub struct PackLimitError {
141 pub kind: PackLimitKind,
142 pub limit: usize,
143 pub attempted: usize,
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub struct PackAllocationError {
153 pub requested: usize,
155 pub active: usize,
157 pub cached: usize,
159}
160
161#[derive(Debug)]
163pub enum PackReadError {
164 Limit(PackLimitError),
165 Allocation(PackAllocationError),
166 Source(io::Error),
167 Pack(GitError),
168}
169
170impl fmt::Display for PackReadError {
171 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
172 match self {
173 Self::Limit(error) => write!(
174 formatter,
175 "pack {:?} limit exceeded: limit {}, attempted {}",
176 error.kind, error.limit, error.attempted
177 ),
178 Self::Allocation(error) => write!(
179 formatter,
180 "pack body allocation failed: requested {}, active {}, cached {}",
181 error.requested, error.active, error.cached
182 ),
183 Self::Source(error) => write!(formatter, "pack source read failed: {error}"),
184 Self::Pack(error) => error.fmt(formatter),
185 }
186 }
187}
188
189impl std::error::Error for PackReadError {
190 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
191 match self {
192 Self::Source(error) => Some(error),
193 Self::Pack(error) => Some(error),
194 Self::Limit(_) | Self::Allocation(_) => None,
195 }
196 }
197}
198
199impl From<io::Error> for PackReadError {
200 fn from(error: io::Error) -> Self {
201 Self::Source(error)
202 }
203}
204
205impl From<GitError> for PackReadError {
206 fn from(error: GitError) -> Self {
207 Self::Pack(error)
208 }
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
213pub struct PackSourceId(usize);
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
217pub struct PackObjectLocation {
218 source: PackSourceId,
219 offset: u64,
220}
221
222impl PackObjectLocation {
223 pub const fn new(source: PackSourceId, offset: u64) -> Self {
224 Self { source, offset }
225 }
226
227 pub const fn source(self) -> PackSourceId {
228 self.source
229 }
230
231 pub const fn offset(self) -> u64 {
232 self.offset
233 }
234}
235
236#[derive(Debug, Clone)]
254pub struct ResolvedPackObject {
255 object: Arc<EncodedObject>,
256 oid: ObjectId,
257 depth: usize,
258 origin: Option<PackObjectLocation>,
259}
260
261impl ResolvedPackObject {
262 pub fn object(&self) -> &EncodedObject {
263 &self.object
264 }
265
266 pub const fn object_id(&self) -> ObjectId {
267 self.oid
268 }
269
270 pub const fn delta_depth(&self) -> usize {
271 self.depth
272 }
273}
274
275#[derive(Debug, Clone, Default)]
282pub struct RefDeltaBases {
283 entries: HashMap<ObjectId, RefDeltaBase>,
284}
285
286#[derive(Debug, Clone)]
287enum RefDeltaBase {
288 Location(PackObjectLocation),
289 Resolved(ResolvedPackObject),
290}
291
292impl RefDeltaBases {
293 pub fn new() -> Self {
294 Self::default()
295 }
296
297 pub fn insert_location(&mut self, oid: ObjectId, location: PackObjectLocation) {
298 self.entries.insert(oid, RefDeltaBase::Location(location));
299 }
300
301 pub fn insert_loose(
305 &mut self,
306 object: Arc<EncodedObject>,
307 format: ObjectFormat,
308 ) -> Result<ObjectId> {
309 self.insert_loose_with_cancel(object, format, CancelFlag::never())
310 }
311
312 pub fn insert_loose_with_cancel(
313 &mut self,
314 object: Arc<EncodedObject>,
315 format: ObjectFormat,
316 cancel: CancelFlag<'_>,
317 ) -> Result<ObjectId> {
318 let oid = cancellable_object_id(&object, format, cancel)?;
319 cancel.check()?;
320 let resolved = RefDeltaBase::Resolved(ResolvedPackObject {
321 object,
322 oid,
323 depth: 0,
324 origin: None,
325 });
326 self.replace_transactionally(oid, resolved, || cancel.check())?;
327 Ok(oid)
328 }
329
330 fn replace_transactionally<F>(
331 &mut self,
332 oid: ObjectId,
333 replacement: RefDeltaBase,
334 post_insert: F,
335 ) -> Result<()>
336 where
337 F: FnOnce() -> Result<()>,
338 {
339 let previous = self.entries.insert(oid, replacement);
340 if let Err(error) = post_insert() {
341 match previous {
342 Some(previous) => {
343 self.entries.insert(oid, previous);
344 }
345 None => {
346 self.entries.remove(&oid);
347 }
348 }
349 return Err(error);
350 }
351 Ok(())
352 }
353
354 pub fn insert_resolved(&mut self, resolved: ResolvedPackObject) -> ObjectId {
355 let oid = resolved.oid;
356 self.entries.insert(oid, RefDeltaBase::Resolved(resolved));
357 oid
358 }
359
360 fn get(&self, oid: &ObjectId) -> Option<&RefDeltaBase> {
361 self.entries.get(oid)
362 }
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
367pub struct PackReadStats {
368 source_bytes_read: u64,
372 compressed_bytes_read: u64,
376 peak_materialized_bytes: usize,
379 cached_bytes: usize,
381 cached_objects: usize,
382 cache_evictions: u64,
383 delta_depth: usize,
385}
386
387impl PackReadStats {
388 fn start(cached_bytes: usize) -> Self {
389 Self {
390 source_bytes_read: 0,
391 compressed_bytes_read: 0,
392 peak_materialized_bytes: cached_bytes,
393 cached_bytes: 0,
394 cached_objects: 0,
395 cache_evictions: 0,
396 delta_depth: 0,
397 }
398 }
399
400 pub const fn source_bytes_read(&self) -> u64 {
401 self.source_bytes_read
402 }
403
404 pub const fn compressed_bytes_read(&self) -> u64 {
405 self.compressed_bytes_read
406 }
407
408 pub const fn peak_materialized_bytes(&self) -> usize {
409 self.peak_materialized_bytes
410 }
411
412 pub const fn cached_bytes(&self) -> usize {
413 self.cached_bytes
414 }
415
416 pub const fn cached_objects(&self) -> usize {
417 self.cached_objects
418 }
419
420 pub const fn cache_evictions(&self) -> u64 {
421 self.cache_evictions
422 }
423
424 pub const fn delta_depth(&self) -> usize {
425 self.delta_depth
426 }
427}
428
429#[derive(Debug, Clone)]
452pub struct PackReadOutcome {
453 object: Arc<EncodedObject>,
454 stats: PackReadStats,
455 oid: ObjectId,
456 depth: usize,
457 origin: PackObjectLocation,
458}
459
460impl PackReadOutcome {
461 pub fn object(&self) -> &EncodedObject {
462 &self.object
463 }
464
465 pub const fn stats(&self) -> &PackReadStats {
466 &self.stats
467 }
468
469 pub const fn object_id(&self) -> ObjectId {
470 self.oid
471 }
472
473 pub fn resolved_base(&self) -> ResolvedPackObject {
474 ResolvedPackObject {
475 object: Arc::clone(&self.object),
476 oid: self.oid,
477 depth: self.depth,
478 origin: Some(self.origin),
479 }
480 }
481}
482
483#[derive(Debug)]
484struct CachedObject {
485 object: Arc<EncodedObject>,
486 oid: ObjectId,
487 bytes: usize,
488 depth: usize,
489 less_recent: Option<PackObjectLocation>,
490 more_recent: Option<PackObjectLocation>,
491}
492
493#[derive(Debug)]
494struct ByteCache {
495 budget: usize,
496 used: usize,
497 entries: HashMap<PackObjectLocation, CachedObject>,
498 least_recent: Option<PackObjectLocation>,
499 most_recent: Option<PackObjectLocation>,
500 evictions: u64,
501}
502
503impl ByteCache {
504 fn new(budget: usize) -> Self {
505 Self {
506 budget,
507 used: 0,
508 entries: HashMap::new(),
509 least_recent: None,
510 most_recent: None,
511 evictions: 0,
512 }
513 }
514
515 fn get(
516 &mut self,
517 location: PackObjectLocation,
518 cancel: CancelFlag<'_>,
519 ) -> Result<Option<(Arc<EncodedObject>, ObjectId, usize)>> {
520 cancel.check()?;
521 let Some(entry) = self.entries.get(&location) else {
522 cancel.check()?;
523 return Ok(None);
524 };
525 let found = (Arc::clone(&entry.object), entry.oid, entry.depth);
526 self.touch(location);
527 cancel.check()?;
528 Ok(Some(found))
529 }
530
531 fn peek(&self, location: PackObjectLocation) -> Option<(Arc<EncodedObject>, ObjectId, usize)> {
532 let entry = self.entries.get(&location)?;
533 Some((Arc::clone(&entry.object), entry.oid, entry.depth))
534 }
535
536 fn contains_same(&self, location: PackObjectLocation, object: &Arc<EncodedObject>) -> bool {
537 self.entries
538 .get(&location)
539 .is_some_and(|cached| Arc::ptr_eq(&cached.object, object))
540 }
541
542 fn insert(
543 &mut self,
544 location: PackObjectLocation,
545 object: Arc<EncodedObject>,
546 oid: ObjectId,
547 depth: usize,
548 cancel: CancelFlag<'_>,
549 ) -> Result<()> {
550 cancel.check()?;
551 let bytes = object.body.len();
552 if bytes == 0 || bytes > self.budget || self.budget == 0 {
555 cancel.check()?;
556 return Ok(());
557 }
558 let previous_bytes = self
559 .entries
560 .get(&location)
561 .map_or(0, |previous| previous.bytes);
562 while self
563 .used
564 .checked_sub(previous_bytes)
565 .and_then(|retained| retained.checked_add(bytes))
566 .is_none_or(|projected| projected > self.budget)
567 {
568 cancel.check()?;
569 if !self.evict_one_except(Some(location), cancel)? {
570 return Ok(());
571 }
572 }
573 cancel.check()?;
574 if self.entries.contains_key(&location) {
575 self.remove_entry(location);
576 }
577 self.used += bytes;
578 self.entries.insert(
579 location,
580 CachedObject {
581 object,
582 oid,
583 bytes,
584 depth,
585 less_recent: None,
586 more_recent: None,
587 },
588 );
589 self.link_as_most_recent(location);
590 Ok(())
591 }
592
593 fn evict_one_except(
594 &mut self,
595 pinned: Option<PackObjectLocation>,
596 cancel: CancelFlag<'_>,
597 ) -> Result<bool> {
598 self.evict_one_except_with(pinned, || cancel.check())
599 }
600
601 fn evict_one_except_with<F>(
602 &mut self,
603 pinned: Option<PackObjectLocation>,
604 mut poll: F,
605 ) -> Result<bool>
606 where
607 F: FnMut() -> Result<()>,
608 {
609 poll()?;
610 let location = match self.least_recent {
611 Some(location) if Some(location) != pinned => Some(location),
612 Some(location) => self
613 .entries
614 .get(&location)
615 .and_then(|cached| cached.more_recent),
616 None => None,
617 };
618 let Some(location) = location else {
619 poll()?;
620 return Ok(false);
621 };
622 self.remove_entry(location);
623 self.evictions += 1;
624 poll()?;
625 Ok(true)
626 }
627
628 fn clear(&mut self, cancel: CancelFlag<'_>) -> Result<()> {
629 self.clear_with(|| cancel.check())
630 }
631
632 fn clear_with<F>(&mut self, mut poll: F) -> Result<()>
633 where
634 F: FnMut() -> Result<()>,
635 {
636 poll()?;
637 let mut removed = 0usize;
638 while let Some(location) = self.least_recent {
639 self.remove_entry(location);
640 removed += 1;
641 if removed.is_multiple_of(64) {
642 poll()?;
643 }
644 }
645 poll()?;
646 Ok(())
647 }
648
649 fn touch(&mut self, location: PackObjectLocation) {
650 if self.most_recent == Some(location) {
651 return;
652 }
653 self.unlink(location);
654 self.link_as_most_recent(location);
655 }
656
657 fn remove_entry(&mut self, location: PackObjectLocation) -> Option<CachedObject> {
658 self.unlink(location);
659 let cached = self.entries.remove(&location)?;
660 debug_assert!(self.used >= cached.bytes);
661 self.used -= cached.bytes;
662 Some(cached)
663 }
664
665 fn unlink(&mut self, location: PackObjectLocation) {
666 let Some((less_recent, more_recent)) = self
667 .entries
668 .get(&location)
669 .map(|cached| (cached.less_recent, cached.more_recent))
670 else {
671 return;
672 };
673 if let Some(less_recent) = less_recent {
674 if let Some(cached) = self.entries.get_mut(&less_recent) {
675 cached.more_recent = more_recent;
676 }
677 } else {
678 self.least_recent = more_recent;
679 }
680 if let Some(more_recent) = more_recent {
681 if let Some(cached) = self.entries.get_mut(&more_recent) {
682 cached.less_recent = less_recent;
683 }
684 } else {
685 self.most_recent = less_recent;
686 }
687 if let Some(cached) = self.entries.get_mut(&location) {
688 cached.less_recent = None;
689 cached.more_recent = None;
690 }
691 }
692
693 fn link_as_most_recent(&mut self, location: PackObjectLocation) {
694 let previous = self.most_recent;
695 if let Some(cached) = self.entries.get_mut(&location) {
696 cached.less_recent = previous;
697 cached.more_recent = None;
698 } else {
699 return;
700 }
701 if let Some(previous) = previous {
702 if let Some(cached) = self.entries.get_mut(&previous) {
703 cached.more_recent = Some(location);
704 }
705 } else {
706 self.least_recent = Some(location);
707 }
708 self.most_recent = Some(location);
709 }
710}
711
712#[derive(Debug)]
713struct EntryPlan {
714 location: PackObjectLocation,
715 header: EntryHeader,
716 data_offset: u64,
717 base: Option<DeltaBase>,
718}
719
720struct PackSourceState<S> {
721 source: S,
722 format: ObjectFormat,
723 trailer_offset: u64,
724}
725
726pub struct BoundedPackDecoder<S> {
738 sources: Vec<PackSourceState<S>>,
739 limits: PackReadLimits,
740 cache: ByteCache,
741}
742
743impl<S: PackReadSource> BoundedPackDecoder<S> {
744 pub fn new(
745 source: S,
746 format: ObjectFormat,
747 limits: PackReadLimits,
748 ) -> std::result::Result<Self, PackReadError> {
749 let source = Self::open_source(source, format)?;
750 Ok(Self {
751 sources: vec![source],
752 limits,
753 cache: ByteCache::new(limits.max_cached_bytes.min(limits.max_materialized_bytes)),
754 })
755 }
756
757 fn open_source(
758 source: S,
759 format: ObjectFormat,
760 ) -> std::result::Result<PackSourceState<S>, PackReadError> {
761 let source_len = source.len()?;
762 let trailer_len = format.raw_len() as u64;
763 let trailer_offset = source_len
764 .checked_sub(trailer_len)
765 .ok_or_else(|| GitError::InvalidFormat("pack smaller than its trailer".into()))?;
766 Ok(PackSourceState {
767 source,
768 format,
769 trailer_offset,
770 })
771 }
772
773 pub const fn primary_source(&self) -> PackSourceId {
774 PackSourceId(0)
775 }
776
777 pub fn add_source(
778 &mut self,
779 source: S,
780 format: ObjectFormat,
781 ) -> std::result::Result<PackSourceId, PackReadError> {
782 let source = Self::open_source(source, format)?;
783 let id = PackSourceId(self.sources.len());
784 self.sources.push(source);
785 Ok(id)
786 }
787
788 pub const fn limits(&self) -> PackReadLimits {
789 self.limits
790 }
791
792 pub fn cached_bytes(&self) -> usize {
793 self.cache.used
794 }
795
796 pub fn cached_objects(&self) -> usize {
797 self.cache.entries.len()
798 }
799
800 pub fn clear_cache(
804 &mut self,
805 cancel: CancelFlag<'_>,
806 ) -> std::result::Result<(), PackReadError> {
807 self.cache.clear(cancel)?;
808 Ok(())
809 }
810
811 pub fn read_object_at(
813 &mut self,
814 offset: u64,
815 ref_bases: &RefDeltaBases,
816 ) -> std::result::Result<PackReadOutcome, PackReadError> {
817 self.read_object_at_with_cancel(offset, ref_bases, CancelFlag::never())
818 }
819
820 pub fn read_object_at_with_cancel(
821 &mut self,
822 offset: u64,
823 ref_bases: &RefDeltaBases,
824 cancel: CancelFlag<'_>,
825 ) -> std::result::Result<PackReadOutcome, PackReadError> {
826 self.read_object_at_location_with_cancel(
827 PackObjectLocation::new(self.primary_source(), offset),
828 ref_bases,
829 cancel,
830 )
831 }
832
833 pub fn read_object_at_location(
837 &mut self,
838 location: PackObjectLocation,
839 ref_bases: &RefDeltaBases,
840 ) -> std::result::Result<PackReadOutcome, PackReadError> {
841 self.read_object_at_location_with_cancel(location, ref_bases, CancelFlag::never())
842 }
843
844 pub fn read_object_at_location_with_cancel(
848 &mut self,
849 location: PackObjectLocation,
850 ref_bases: &RefDeltaBases,
851 cancel: CancelFlag<'_>,
852 ) -> std::result::Result<PackReadOutcome, PackReadError> {
853 cancel.check()?;
854 let target_format = self.source_state(location)?.format;
855 let evictions_before = self.cache.evictions;
856 let mut stats = PackReadStats::start(self.cache.used);
857 if let Some((object, oid, depth)) = self.cache.get(location, cancel)? {
858 stats.delta_depth = depth;
859 self.finish_stats(&mut stats, evictions_before);
860 return Ok(PackReadOutcome {
861 object,
862 stats,
863 oid,
864 depth,
865 origin: location,
866 });
867 }
868
869 let mut visited = HashSet::new();
870 let mut deltas = Vec::new();
871 let mut current_location = location;
872 let mut base_object: Option<(
873 Arc<EncodedObject>,
874 ObjectId,
875 Option<PackObjectLocation>,
876 usize,
877 )> = None;
878 let mut base_entry = None;
879
880 loop {
881 cancel.check()?;
882 self.source_state(current_location)?;
883 if !visited.insert(current_location) {
884 return Err(GitError::InvalidFormat("pack delta cycle detected".into()).into());
885 }
886 if current_location != location
887 && let Some((object, oid, cached_depth)) = self.cache.peek(current_location)
888 {
889 let full_depth = deltas.len().saturating_add(cached_depth);
890 self.enforce_depth(full_depth)?;
891 base_object = Some((object, oid, Some(current_location), cached_depth));
892 break;
893 }
894 let entry = self.read_entry_plan(current_location, cancel, &mut stats)?;
895 cancel.check()?;
896 match entry.base.clone() {
897 None => {
898 base_entry = Some(entry);
899 break;
900 }
901 Some(base) => {
902 let depth = deltas.len().saturating_add(1);
903 self.enforce_depth(depth)?;
904 deltas.push(entry);
905 match base {
906 DeltaBase::Offset(base_offset) => {
907 current_location =
908 PackObjectLocation::new(current_location.source, base_offset);
909 }
910 DeltaBase::Ref(base_oid) => {
911 cancel.check()?;
912 match ref_bases.get(&base_oid) {
913 Some(RefDeltaBase::Location(base_location)) => {
914 current_location = *base_location;
915 }
916 Some(RefDeltaBase::Resolved(resolved)) => {
917 if resolved.oid != base_oid {
918 return Err(GitError::InvalidObject(format!(
919 "resolved REF base identity mismatch: expected {base_oid}, got {}",
920 resolved.oid
921 ))
922 .into());
923 }
924 let full_depth = deltas.len().saturating_add(resolved.depth);
925 self.enforce_depth(full_depth)?;
926 let pinned = resolved.origin.filter(|origin| {
927 self.cache.contains_same(*origin, &resolved.object)
928 });
929 let active = if pinned.is_some() {
930 0
931 } else {
932 resolved.object.body.len()
933 };
934 self.ensure_materialized(
935 0, active, pinned, cancel, &mut stats,
936 )?;
937 base_object = Some((
938 Arc::clone(&resolved.object),
939 resolved.oid,
940 pinned,
941 resolved.depth,
942 ));
943 break;
944 }
945 None => {
946 return Err(GitError::not_found(format!(
947 "ref-delta base object {base_oid}"
948 ))
949 .into());
950 }
951 }
952 }
953 }
954 }
955 }
956 }
957
958 let cached_base_depth = base_object.as_ref().map_or(0, |(_, _, _, depth)| *depth);
959 stats.delta_depth = deltas.len().saturating_add(cached_base_depth);
960 self.enforce_depth(stats.delta_depth)?;
961 let (mut object, mut object_oid, mut pinned_cache) = match (base_object, base_entry) {
962 (Some((object, oid, pinned, _)), None) => (object, Some(oid), pinned),
963 (None, Some(entry)) => {
964 let object_type = object_type_for_entry(entry.header.kind)?;
965 let body = self.inflate_entry(&entry, 0, None, cancel, &mut stats)?;
966 let object = Arc::new(EncodedObject::new(object_type, body));
967 (object, None, None)
968 }
969 _ => {
970 return Err(
971 GitError::InvalidFormat("pack delta base planning failed".into()).into(),
972 );
973 }
974 };
975
976 for delta_entry in deltas.iter().rev() {
977 cancel.check()?;
978 if let Some(DeltaBase::Ref(expected_oid)) = delta_entry.base.as_ref() {
979 let actual_oid = match object_oid {
980 Some(oid) if oid.format() == expected_oid.format() => oid,
981 _ => cancellable_object_id(&object, expected_oid.format(), cancel)?,
982 };
983 if actual_oid != *expected_oid {
984 return Err(GitError::InvalidObject(format!(
985 "resolved REF base identity mismatch: expected {expected_oid}, got {actual_oid}"
986 ))
987 .into());
988 }
989 }
990 let base_bytes = if pinned_cache.is_some() {
991 0
992 } else {
993 object.body.len()
994 };
995 let delta =
996 self.inflate_entry(delta_entry, base_bytes, pinned_cache, cancel, &mut stats)?;
997 let plan = plan_pack_delta(&object.body, &delta)?;
998 let result_size_u64 = plan.result_size;
999 let result_size = usize::try_from(result_size_u64).map_err(|_| {
1000 PackReadError::Limit(PackLimitError {
1001 kind: PackLimitKind::MaterializedBytes,
1002 limit: self.limits.max_materialized_bytes,
1003 attempted: usize::MAX,
1004 })
1005 })?;
1006 let active_bytes = self.checked_materialized_add(base_bytes, delta.len())?;
1007 let mut resolved =
1008 self.allocate_body(result_size, active_bytes, pinned_cache, cancel, &mut stats)?;
1009 apply_pack_delta_exact(&object.body, &delta, plan, &mut resolved, cancel)?;
1010 object = Arc::new(EncodedObject::new(object.object_type, resolved));
1011 object_oid = None;
1012 pinned_cache = None;
1013 }
1014
1015 cancel.check()?;
1016 let oid = cancellable_object_id(&object, target_format, cancel)?;
1017 self.cache.insert(
1018 location,
1019 Arc::clone(&object),
1020 oid,
1021 stats.delta_depth,
1022 cancel,
1023 )?;
1024 self.finish_stats(&mut stats, evictions_before);
1025 let depth = stats.delta_depth;
1026 Ok(PackReadOutcome {
1027 object,
1028 stats,
1029 oid,
1030 depth,
1031 origin: location,
1032 })
1033 }
1034
1035 fn finish_stats(&self, stats: &mut PackReadStats, evictions_before: u64) {
1036 stats.cached_bytes = self.cache.used;
1037 stats.cached_objects = self.cache.entries.len();
1038 stats.cache_evictions = self.cache.evictions.saturating_sub(evictions_before);
1039 stats.peak_materialized_bytes = stats.peak_materialized_bytes.max(self.cache.used);
1040 }
1041
1042 fn enforce_depth(&self, depth: usize) -> std::result::Result<(), PackReadError> {
1043 if depth > self.limits.max_delta_depth {
1044 return Err(PackReadError::Limit(PackLimitError {
1045 kind: PackLimitKind::DeltaDepth,
1046 limit: self.limits.max_delta_depth,
1047 attempted: depth,
1048 }));
1049 }
1050 Ok(())
1051 }
1052
1053 fn checked_materialized_add(
1054 &self,
1055 left: usize,
1056 right: usize,
1057 ) -> std::result::Result<usize, PackReadError> {
1058 left.checked_add(right)
1059 .ok_or(PackReadError::Limit(PackLimitError {
1060 kind: PackLimitKind::MaterializedBytes,
1061 limit: self.limits.max_materialized_bytes,
1062 attempted: usize::MAX,
1063 }))
1064 }
1065
1066 fn read_entry_plan(
1067 &self,
1068 location: PackObjectLocation,
1069 cancel: CancelFlag<'_>,
1070 stats: &mut PackReadStats,
1071 ) -> std::result::Result<EntryPlan, PackReadError> {
1072 let source = self.source_state(location)?;
1073 let offset = location.offset;
1074 if offset >= source.trailer_offset {
1075 return Err(GitError::InvalidFormat("pack object offset out of range".into()).into());
1076 }
1077 let available =
1078 usize::try_from((source.trailer_offset - offset).min(ENTRY_PREFIX_BYTES as u64))
1079 .unwrap_or(ENTRY_PREFIX_BYTES);
1080 let mut prefix = [0u8; ENTRY_PREFIX_BYTES];
1081 self.read_exact_at(
1082 location.source,
1083 offset,
1084 &mut prefix[..available],
1085 cancel,
1086 stats,
1087 )?;
1088 let bytes = &prefix[..available];
1089 let mut cursor = 0usize;
1090 let header = parse_entry_header(bytes, &mut cursor)?;
1091 let base = match header.kind {
1092 PackObjectKind::OfsDelta => Some(DeltaBase::Offset(parse_ofs_delta_base_offset(
1093 bytes,
1094 &mut cursor,
1095 offset,
1096 )?)),
1097 PackObjectKind::RefDelta => {
1098 let raw_len = source.format.raw_len();
1099 let end = cursor.checked_add(raw_len).ok_or_else(|| {
1100 GitError::InvalidFormat("ref-delta base offset overflow".into())
1101 })?;
1102 let raw = bytes.get(cursor..end).ok_or_else(|| {
1103 GitError::InvalidFormat("truncated ref-delta base object id".into())
1104 })?;
1105 cursor = end;
1106 Some(DeltaBase::Ref(ObjectId::from_raw(source.format, raw)?))
1107 }
1108 _ => None,
1109 };
1110 let data_offset = offset
1111 .checked_add(cursor as u64)
1112 .ok_or_else(|| GitError::InvalidFormat("pack object offset overflow".into()))?;
1113 Ok(EntryPlan {
1114 location,
1115 header,
1116 data_offset,
1117 base,
1118 })
1119 }
1120
1121 fn inflate_entry(
1122 &mut self,
1123 entry: &EntryPlan,
1124 active_bytes: usize,
1125 pinned_cache: Option<PackObjectLocation>,
1126 cancel: CancelFlag<'_>,
1127 stats: &mut PackReadStats,
1128 ) -> std::result::Result<Vec<u8>, PackReadError> {
1129 cancel.check()?;
1130 let expected = usize::try_from(entry.header.size).map_err(|_| {
1131 PackReadError::Limit(PackLimitError {
1132 kind: PackLimitKind::MaterializedBytes,
1133 limit: self.limits.max_materialized_bytes,
1134 attempted: usize::MAX,
1135 })
1136 })?;
1137 let source = self.source_state(entry.location)?;
1138 let source_id = entry.location.source;
1139 let trailer_offset = source.trailer_offset;
1140 let mut body = self.allocate_body(expected, active_bytes, pinned_cache, cancel, stats)?;
1141
1142 let mut decompressor = Decompress::new(true);
1143 let mut input = [0u8; INFLATE_CHUNK_BYTES];
1144 let mut output = [0u8; INFLATE_CHUNK_BYTES];
1145 let mut input_start = 0usize;
1146 let mut input_end = 0usize;
1147 let mut source_offset = entry.data_offset;
1148
1149 loop {
1150 cancel.check()?;
1151 if input_start == input_end {
1152 if source_offset >= trailer_offset {
1153 return Err(GitError::InvalidObject("truncated zlib stream".into()).into());
1154 }
1155 let wanted = usize::try_from(
1156 (trailer_offset - source_offset).min(INFLATE_CHUNK_BYTES as u64),
1157 )
1158 .unwrap_or(INFLATE_CHUNK_BYTES);
1159 let read = self.read_source_at(
1160 source_id,
1161 source_offset,
1162 &mut input[..wanted],
1163 cancel,
1164 stats,
1165 )?;
1166 if read == 0 {
1167 return Err(GitError::InvalidObject("truncated zlib stream".into()).into());
1168 }
1169 source_offset = source_offset
1170 .checked_add(read as u64)
1171 .ok_or_else(|| GitError::InvalidFormat("pack source offset overflow".into()))?;
1172 input_start = 0;
1173 input_end = read;
1174 }
1175
1176 let before_in = decompressor.total_in();
1177 let before_out = decompressor.total_out();
1178 let status = decompressor
1179 .decompress(
1180 &input[input_start..input_end],
1181 &mut output,
1182 FlushDecompress::None,
1183 )
1184 .map_err(|error| {
1185 GitError::InvalidObject(format!("zlib inflate failed: {error}"))
1186 })?;
1187 let consumed =
1188 usize::try_from(decompressor.total_in() - before_in).unwrap_or(usize::MAX);
1189 let produced =
1190 usize::try_from(decompressor.total_out() - before_out).unwrap_or(usize::MAX);
1191 input_start = input_start.saturating_add(consumed);
1192 stats.compressed_bytes_read =
1193 stats.compressed_bytes_read.saturating_add(consumed as u64);
1194 let attempted = self.checked_materialized_add(body.len(), produced)?;
1195 if attempted > expected {
1196 return Err(GitError::InvalidObject(format!(
1197 "pack object declared {} bytes, decoded more than {}",
1198 entry.header.size, expected
1199 ))
1200 .into());
1201 }
1202 body.extend_from_slice(&output[..produced]);
1203
1204 if status == Status::StreamEnd {
1205 if body.len() != expected {
1206 return Err(GitError::InvalidObject(format!(
1207 "pack object declared {} bytes, decoded {}",
1208 entry.header.size,
1209 body.len()
1210 ))
1211 .into());
1212 }
1213 return Ok(body);
1214 }
1215 if consumed == 0 && produced == 0 && input_start < input_end {
1216 return Err(GitError::InvalidObject("zlib inflate made no progress".into()).into());
1217 }
1218 }
1219 }
1220
1221 fn ensure_materialized(
1222 &mut self,
1223 active_bytes: usize,
1224 additional_bytes: usize,
1225 pinned_cache: Option<PackObjectLocation>,
1226 cancel: CancelFlag<'_>,
1227 stats: &mut PackReadStats,
1228 ) -> std::result::Result<(), PackReadError> {
1229 cancel.check()?;
1230 let working = self.checked_materialized_add(active_bytes, additional_bytes)?;
1231 if working > self.limits.max_materialized_bytes {
1232 return Err(PackReadError::Limit(PackLimitError {
1233 kind: PackLimitKind::MaterializedBytes,
1234 limit: self.limits.max_materialized_bytes,
1235 attempted: working,
1236 }));
1237 }
1238 while self.checked_materialized_add(self.cache.used, working)?
1239 > self.limits.max_materialized_bytes
1240 {
1241 cancel.check()?;
1242 if !self.cache.evict_one_except(pinned_cache, cancel)? {
1243 break;
1244 }
1245 }
1246 let total = self.checked_materialized_add(self.cache.used, working)?;
1247 if total > self.limits.max_materialized_bytes {
1248 return Err(PackReadError::Limit(PackLimitError {
1249 kind: PackLimitKind::MaterializedBytes,
1250 limit: self.limits.max_materialized_bytes,
1251 attempted: total,
1252 }));
1253 }
1254 stats.peak_materialized_bytes = stats.peak_materialized_bytes.max(total);
1255 Ok(())
1256 }
1257
1258 fn allocate_body(
1259 &mut self,
1260 requested: usize,
1261 active_bytes: usize,
1262 pinned_cache: Option<PackObjectLocation>,
1263 cancel: CancelFlag<'_>,
1264 stats: &mut PackReadStats,
1265 ) -> std::result::Result<Vec<u8>, PackReadError> {
1266 cancel.check()?;
1267 self.ensure_materialized(active_bytes, requested, pinned_cache, cancel, stats)?;
1268 cancel.check()?;
1269 let mut body = Vec::new();
1270 let allocation = body.try_reserve_exact(requested);
1271 cancel.check()?;
1272 allocation.map_err(|_| {
1273 PackReadError::Allocation(PackAllocationError {
1274 requested,
1275 active: active_bytes,
1276 cached: self.cache.used,
1277 })
1278 })?;
1279 Ok(body)
1280 }
1281
1282 fn read_source_at(
1283 &self,
1284 source_id: PackSourceId,
1285 offset: u64,
1286 buf: &mut [u8],
1287 cancel: CancelFlag<'_>,
1288 stats: &mut PackReadStats,
1289 ) -> std::result::Result<usize, PackReadError> {
1290 cancel.check()?;
1291 let source = self
1292 .sources
1293 .get(source_id.0)
1294 .ok_or_else(|| GitError::InvalidFormat("unknown pack source id".into()))?;
1295 let read_result = source.source.read_at(offset, buf);
1296 cancel.check()?;
1297 let read = read_result?;
1298 if read > buf.len() {
1299 return Err(GitError::InvalidFormat(
1300 "pack source returned more bytes than requested".into(),
1301 )
1302 .into());
1303 }
1304 stats.source_bytes_read = stats.source_bytes_read.saturating_add(read as u64);
1305 Ok(read)
1306 }
1307
1308 fn read_exact_at(
1309 &self,
1310 source_id: PackSourceId,
1311 mut offset: u64,
1312 mut buf: &mut [u8],
1313 cancel: CancelFlag<'_>,
1314 stats: &mut PackReadStats,
1315 ) -> std::result::Result<(), PackReadError> {
1316 while !buf.is_empty() {
1317 let read = self.read_source_at(source_id, offset, buf, cancel, stats)?;
1318 if read == 0 {
1319 return Err(GitError::InvalidFormat("truncated pack entry header".into()).into());
1320 }
1321 offset = offset
1322 .checked_add(read as u64)
1323 .ok_or_else(|| GitError::InvalidFormat("pack source offset overflow".into()))?;
1324 buf = &mut buf[read..];
1325 }
1326 Ok(())
1327 }
1328
1329 fn source_state(
1330 &self,
1331 location: PackObjectLocation,
1332 ) -> std::result::Result<&PackSourceState<S>, PackReadError> {
1333 self.sources
1334 .get(location.source.0)
1335 .ok_or_else(|| GitError::InvalidFormat("unknown pack source id".into()).into())
1336 }
1337}
1338
1339fn cancellable_object_id(
1340 object: &EncodedObject,
1341 format: ObjectFormat,
1342 cancel: CancelFlag<'_>,
1343) -> Result<ObjectId> {
1344 cancel.check()?;
1345 let mut digest = StreamingDigest::new(format);
1346 digest.update(object.object_type.as_str().as_bytes());
1347 digest.update(b" ");
1348 let body_len = object.body.len().to_string();
1349 digest.update(body_len.as_bytes());
1350 digest.update(b"\0");
1351 for chunk in object.body.chunks(INFLATE_CHUNK_BYTES) {
1352 cancel.check()?;
1353 digest.update(chunk);
1354 }
1355 cancel.check()?;
1356 digest.finalize()
1357}
1358
1359fn object_type_for_entry(kind: PackObjectKind) -> Result<ObjectType> {
1360 match kind {
1361 PackObjectKind::Commit => Ok(ObjectType::Commit),
1362 PackObjectKind::Tree => Ok(ObjectType::Tree),
1363 PackObjectKind::Blob => Ok(ObjectType::Blob),
1364 PackObjectKind::Tag => Ok(ObjectType::Tag),
1365 PackObjectKind::OfsDelta | PackObjectKind::RefDelta => Err(GitError::InvalidFormat(
1366 "delta pack entry decoded without a base".into(),
1367 )),
1368 }
1369}
1370
1371#[cfg(test)]
1372mod tests {
1373 use super::*;
1374
1375 fn test_oid() -> ObjectId {
1376 ObjectId::from_raw(ObjectFormat::Sha1, &[0; 20]).expect("object id")
1377 }
1378
1379 fn location(offset: u64) -> PackObjectLocation {
1380 PackObjectLocation::new(PackSourceId(0), offset)
1381 }
1382
1383 fn insert_cached(cache: &mut ByteCache, offset: u64, bytes: usize) {
1384 cache
1385 .insert(
1386 location(offset),
1387 Arc::new(EncodedObject::new(
1388 ObjectType::Blob,
1389 vec![offset as u8; bytes],
1390 )),
1391 test_oid(),
1392 0,
1393 CancelFlag::never(),
1394 )
1395 .expect("cache insert");
1396 }
1397
1398 #[test]
1399 fn cache_eviction_is_constant_work_and_preserves_lru_accounting() {
1400 let mut cache = ByteCache::new(512);
1401 for offset in 0..256 {
1402 insert_cached(&mut cache, offset, 1);
1403 }
1404 cache
1405 .get(location(0), CancelFlag::never())
1406 .expect("cache get")
1407 .expect("cached entry");
1408 assert_eq!(cache.least_recent, Some(location(1)));
1409 assert_eq!(cache.most_recent, Some(location(0)));
1410
1411 let mut polls = 0;
1412 assert!(
1413 cache
1414 .evict_one_except_with(None, || {
1415 polls += 1;
1416 Ok(())
1417 })
1418 .expect("eviction")
1419 );
1420 assert_eq!(polls, 2, "eviction work must not grow with cache size");
1421 assert!(!cache.entries.contains_key(&location(1)));
1422 assert_eq!(cache.least_recent, Some(location(2)));
1423 assert_eq!(cache.most_recent, Some(location(0)));
1424 assert_eq!(cache.entries.len(), 255);
1425 assert_eq!(cache.used, 255);
1426 assert_eq!(cache.evictions, 1);
1427 }
1428
1429 #[test]
1430 fn cache_replacement_and_pinned_eviction_keep_exact_order_and_bytes() {
1431 let mut cache = ByteCache::new(8);
1432 insert_cached(&mut cache, 1, 1);
1433 insert_cached(&mut cache, 2, 2);
1434 insert_cached(&mut cache, 3, 3);
1435 assert_eq!(cache.used, 6);
1436
1437 insert_cached(&mut cache, 1, 5);
1439 assert_eq!(cache.used, 8);
1440 assert!(!cache.entries.contains_key(&location(2)));
1441 assert!(cache.entries.contains_key(&location(3)));
1442 assert!(cache.entries.contains_key(&location(1)));
1443 assert_eq!(cache.least_recent, Some(location(3)));
1444 assert_eq!(cache.most_recent, Some(location(1)));
1445 assert_eq!(cache.evictions, 1);
1446 }
1447
1448 #[test]
1449 fn cancelled_clear_leaves_exact_partially_cleared_lru_state() {
1450 let mut cache = ByteCache::new(256);
1451 for offset in 0..256 {
1452 insert_cached(&mut cache, offset, 1);
1453 }
1454 let mut polls = 0;
1455 let error = cache
1456 .clear_with(|| {
1457 polls += 1;
1458 if polls == 3 {
1459 return Err(GitError::Cancelled);
1460 }
1461 Ok(())
1462 })
1463 .expect_err("clear must observe cancellation between batches");
1464 assert!(matches!(error, GitError::Cancelled));
1465 assert_eq!(cache.entries.len(), 128);
1466 assert_eq!(cache.used, 128);
1467 assert_eq!(cache.least_recent, Some(location(128)));
1468 assert_eq!(cache.most_recent, Some(location(255)));
1469 assert_eq!(
1470 cache.entries[&location(128)].less_recent,
1471 None,
1472 "remaining head must be detached from removed entries"
1473 );
1474
1475 cache.clear_with(|| Ok(())).expect("finish clear");
1476 assert!(cache.entries.is_empty());
1477 assert_eq!(cache.used, 0);
1478 assert_eq!(cache.least_recent, None);
1479 assert_eq!(cache.most_recent, None);
1480 }
1481
1482 #[test]
1483 fn cancelled_loose_replacement_restores_location_and_resolved_bases() {
1484 let oid = test_oid();
1485 let prior_location = location(41);
1486 let replacement = || {
1487 RefDeltaBase::Resolved(ResolvedPackObject {
1488 object: Arc::new(EncodedObject::new(ObjectType::Blob, vec![9])),
1489 oid,
1490 depth: 0,
1491 origin: None,
1492 })
1493 };
1494
1495 let mut bases = RefDeltaBases::new();
1496 bases.insert_location(oid, prior_location);
1497 assert!(matches!(
1498 bases.replace_transactionally(oid, replacement(), || Err(GitError::Cancelled)),
1499 Err(GitError::Cancelled)
1500 ));
1501 assert!(matches!(
1502 bases.get(&oid),
1503 Some(RefDeltaBase::Location(location)) if *location == prior_location
1504 ));
1505
1506 let prior_object = Arc::new(EncodedObject::new(ObjectType::Blob, vec![7]));
1507 bases.entries.insert(
1508 oid,
1509 RefDeltaBase::Resolved(ResolvedPackObject {
1510 object: Arc::clone(&prior_object),
1511 oid,
1512 depth: 7,
1513 origin: Some(prior_location),
1514 }),
1515 );
1516 assert!(matches!(
1517 bases.replace_transactionally(oid, replacement(), || Err(GitError::Cancelled)),
1518 Err(GitError::Cancelled)
1519 ));
1520 let Some(RefDeltaBase::Resolved(restored)) = bases.get(&oid) else {
1521 panic!("resolved base must be restored");
1522 };
1523 assert!(Arc::ptr_eq(&restored.object, &prior_object));
1524 assert_eq!(restored.depth, 7);
1525 assert_eq!(restored.origin, Some(prior_location));
1526 }
1527
1528 #[test]
1529 fn allocator_failure_below_limit_is_not_reported_as_limit_rejection() {
1530 let mut decoder: BoundedPackDecoder<Vec<u8>> = BoundedPackDecoder {
1531 sources: Vec::new(),
1532 limits: PackReadLimits {
1533 max_delta_depth: 0,
1534 max_materialized_bytes: usize::MAX,
1535 max_cached_bytes: 0,
1536 },
1537 cache: ByteCache::new(0),
1538 };
1539 let mut stats = PackReadStats::start(0);
1540 let requested = (isize::MAX as usize) + 1;
1541 let error = decoder
1542 .allocate_body(requested, 0, None, CancelFlag::never(), &mut stats)
1543 .expect_err("Vec capacity overflow must be an allocation error");
1544 assert!(requested < decoder.limits.max_materialized_bytes);
1545 assert!(matches!(
1546 error,
1547 PackReadError::Allocation(PackAllocationError {
1548 requested: actual,
1549 active: 0,
1550 cached: 0,
1551 }) if actual == requested
1552 ));
1553 }
1554
1555 #[test]
1556 fn active_plus_requested_overflow_is_a_limit_without_allocating() {
1557 let mut decoder: BoundedPackDecoder<Vec<u8>> = BoundedPackDecoder {
1558 sources: Vec::new(),
1559 limits: PackReadLimits {
1560 max_delta_depth: 0,
1561 max_materialized_bytes: usize::MAX,
1562 max_cached_bytes: 0,
1563 },
1564 cache: ByteCache::new(0),
1565 };
1566 let mut stats = PackReadStats::start(0);
1567 let error = decoder
1568 .allocate_body(1, usize::MAX, None, CancelFlag::never(), &mut stats)
1569 .expect_err("overflowing active plus requested bytes must be a limit");
1570 assert!(matches!(
1571 error,
1572 PackReadError::Limit(PackLimitError {
1573 kind: PackLimitKind::MaterializedBytes,
1574 limit: usize::MAX,
1575 attempted: usize::MAX,
1576 })
1577 ));
1578 }
1579
1580 #[test]
1581 fn cached_plus_working_overflow_is_a_limit_before_body_allocation() {
1582 let mut decoder: BoundedPackDecoder<Vec<u8>> = BoundedPackDecoder {
1583 sources: Vec::new(),
1584 limits: PackReadLimits {
1585 max_delta_depth: 0,
1586 max_materialized_bytes: usize::MAX,
1587 max_cached_bytes: usize::MAX,
1588 },
1589 cache: ByteCache::new(usize::MAX),
1590 };
1591 insert_cached(&mut decoder.cache, 1, 1);
1592 let mut stats = PackReadStats::start(decoder.cache.used);
1593 let error = decoder
1594 .ensure_materialized(usize::MAX, 0, None, CancelFlag::never(), &mut stats)
1595 .expect_err("overflowing cached plus working bytes must be a limit");
1596 assert!(matches!(
1597 error,
1598 PackReadError::Limit(PackLimitError {
1599 kind: PackLimitKind::MaterializedBytes,
1600 limit: usize::MAX,
1601 attempted: usize::MAX,
1602 })
1603 ));
1604 }
1605}