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