1use super::*;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct PackIndexBuild {
9 pub index: Vec<u8>,
10 pub pack_checksum: ObjectId,
11 pub entries: Vec<PackIndexEntry>,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct PackStreamIndexBuild {
16 pub index: Vec<u8>,
17 pub pack_checksum: ObjectId,
18 pub entries: Vec<PackIndexEntry>,
19 pub objects: Vec<PackIndexedObject>,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct PackIndexedObject {
24 pub oid: ObjectId,
25 pub object_type: ObjectType,
26 pub size: u64,
27 pub offset: u64,
28}
29
30#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
34pub struct PackStreamProgress {
35 pub received_bytes: u64,
39 pub received_objects: u64,
41 pub total_objects: u64,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct PackIndex {
48 pub version: u32,
49 pub fanout: [u32; 256],
50 pub entries: Vec<PackIndexEntry>,
51 pub pack_checksum: ObjectId,
52 pub index_checksum: ObjectId,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct PackIndexView<'a> {
57 pub version: u32,
58 pub count: usize,
59 pub fanout: [u32; 256],
60 pub pack_checksum: ObjectId,
61 pub index_checksum: ObjectId,
62 bytes: &'a [u8],
63 format: ObjectFormat,
64 tables: PackIndexViewTables,
65}
66
67pub trait PackIndexByteSource: fmt::Debug + Send + Sync {
68 fn as_bytes(&self) -> &[u8];
69}
70
71impl<T> PackIndexByteSource for T
72where
73 T: AsRef<[u8]> + fmt::Debug + Send + Sync + ?Sized,
74{
75 fn as_bytes(&self) -> &[u8] {
76 self.as_ref()
77 }
78}
79
80#[derive(Debug)]
81pub(crate) struct SharedIndexBytes(Arc<[u8]>);
82
83impl PackIndexByteSource for SharedIndexBytes {
84 fn as_bytes(&self) -> &[u8] {
85 self.0.as_ref()
86 }
87}
88
89#[derive(Debug, Clone)]
90pub struct PackIndexViewData {
91 pub version: u32,
92 pub count: usize,
93 pub fanout: [u32; 256],
94 pub pack_checksum: ObjectId,
95 pub index_checksum: ObjectId,
96 bytes: Arc<dyn PackIndexByteSource>,
97 format: ObjectFormat,
98 tables: PackIndexViewTables,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct PackIndexEntry {
103 pub oid: ObjectId,
104 pub crc32: u32,
105 pub offset: u64,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub struct PackIndexLookup {
110 pub crc32: u32,
111 pub offset: u64,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub(crate) enum PackIndexViewTables {
116 V1 {
117 entry_table: Range<usize>,
118 },
119 V2 {
120 oid_table: Range<usize>,
121 crc_table: Range<usize>,
122 small_offset_table: Range<usize>,
123 large_offset_table: Range<usize>,
124 },
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct PackReverseIndex {
129 pub version: u32,
130 pub format: ObjectFormat,
131 pub positions: Vec<u32>,
132 pub pack_checksum: ObjectId,
133 pub index_checksum: ObjectId,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
137pub struct PackMtimes {
138 pub version: u32,
139 pub format: ObjectFormat,
140 pub mtimes: Vec<u32>,
141 pub pack_checksum: ObjectId,
142 pub index_checksum: ObjectId,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct PackBitmapIndex {
147 pub version: u16,
148 pub format: ObjectFormat,
149 pub options: u16,
150 pub pack_checksum: ObjectId,
151 pub index_checksum: ObjectId,
152 pub type_bitmaps: PackBitmapTypeBitmaps,
153 pub entries: Vec<PackBitmapEntry>,
154 pub pseudo_merges: Vec<PackBitmapPseudoMerge>,
155 pub lookup_table: bool,
158 pub name_hash_cache: Option<Vec<u32>>,
159}
160
161#[derive(Debug, Clone, PartialEq, Eq)]
162pub struct PackBitmapTypeBitmaps {
163 pub commits: EwahBitmap,
164 pub trees: EwahBitmap,
165 pub blobs: EwahBitmap,
166 pub tags: EwahBitmap,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct PackBitmapEntry {
171 pub object_position: u32,
176 pub xor_offset: u8,
177 pub flags: u8,
178 pub bitmap: EwahBitmap,
181}
182
183#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct PackBitmapPseudoMerge {
185 pub commits: EwahBitmap,
188 pub bitmap: EwahBitmap,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct EwahBitmap {
195 pub bit_size: u32,
196 pub words: Vec<u64>,
197 pub rlw_position: u32,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct MultiPackIndex {
202 pub version: u8,
203 pub format: ObjectFormat,
204 pub pack_count: u32,
205 pub pack_names: Vec<String>,
206 pub object_count: u32,
207 pub fanout: [u32; 256],
208 pub objects: Vec<MultiPackIndexEntry>,
209 pub reverse_index: Option<Vec<u32>>,
210 pub bitmapped_packs: Option<Vec<MultiPackBitmapPack>>,
211 pub chunks: Vec<MultiPackIndexChunk>,
212 pub checksum: ObjectId,
213}
214
215#[derive(Debug, Clone)]
216pub struct MultiPackIndexOidLookup {
217 format: ObjectFormat,
218 pack_count: u32,
219 pack_names: Vec<String>,
220 fanout: [u32; 256],
221 object_count: usize,
222 oid_lookup_offset: usize,
223 object_offsets_offset: usize,
224 large_offsets_offset: Option<usize>,
225 large_offsets_len: usize,
226 bytes: Arc<dyn PackIndexByteSource>,
227}
228
229#[derive(Debug, Clone, PartialEq, Eq)]
230pub struct MultiPackIndexEntry {
231 pub oid: ObjectId,
232 pub pack_int_id: u32,
233 pub offset: u64,
234 pub force_large_offset: bool,
235}
236
237#[derive(Debug, Clone, PartialEq, Eq)]
238pub struct MultiPackBitmapPack {
239 pub bitmap_pos: u32,
240 pub bitmap_nr: u32,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct MultiPackIndexChunk {
245 pub id: [u8; 4],
246 pub offset: u64,
247 pub len: u64,
248}
249impl<'a> PackIndexView<'a> {
250 pub fn parse_v2_sha1(bytes: &'a [u8]) -> Result<Self> {
251 Self::parse(bytes, ObjectFormat::Sha1)
252 }
253
254 pub fn parse(bytes: &'a [u8], format: ObjectFormat) -> Result<Self> {
255 Self::parse_impl(bytes, format, true, true)
256 }
257
258 pub fn parse_without_checksum(bytes: &'a [u8], format: ObjectFormat) -> Result<Self> {
262 Self::parse_impl(bytes, format, false, true)
263 }
264
265 pub fn parse_trusted_without_checksum(bytes: &'a [u8], format: ObjectFormat) -> Result<Self> {
272 Self::parse_impl(bytes, format, false, false)
273 }
274
275 pub fn count(&self) -> usize {
276 self.count
277 }
278
279 pub fn fanout(&self) -> &[u32; 256] {
280 &self.fanout
281 }
282
283 pub fn find(&self, oid: &ObjectId) -> Option<PackIndexLookup> {
284 if oid.format() != self.format {
285 return None;
286 }
287 let bucket = usize::from(oid.as_bytes()[0]);
288 let mut start = if bucket == 0 {
289 0
290 } else {
291 self.fanout[bucket - 1] as usize
292 };
293 let mut end = self.fanout[bucket] as usize;
294 let target = oid.as_bytes();
295
296 while start < end {
297 let mid = start + (end - start) / 2;
298 match self.oid_bytes_at(mid).cmp(target) {
299 std::cmp::Ordering::Less => start = mid + 1,
300 std::cmp::Ordering::Equal => return self.lookup_at(mid),
301 std::cmp::Ordering::Greater => end = mid,
302 }
303 }
304 None
305 }
306
307 pub(crate) fn parse_impl(
308 bytes: &'a [u8],
309 format: ObjectFormat,
310 verify_checksum: bool,
311 validate_entries: bool,
312 ) -> Result<Self> {
313 let hash_len = format.raw_len();
314 if bytes.len() < 4 {
315 return Err(GitError::InvalidFormat("pack index too short".into()));
316 }
317 if bytes[..4] != [0xff, b't', b'O', b'c'] {
318 return Self::parse_v1_impl(bytes, format, verify_checksum, validate_entries);
319 }
320 if bytes.len() < 8 + 256 * 4 + 2 * hash_len {
321 return Err(GitError::InvalidFormat("pack index too short".into()));
322 }
323 let version = u32_be(&bytes[4..8]);
324 if version != 2 {
325 return Err(GitError::Unsupported(format!(
326 "pack index version {version}"
327 )));
328 }
329 let index_checksum_offset = bytes.len() - hash_len;
330 let index_checksum = ObjectId::from_raw(format, &bytes[index_checksum_offset..])?;
331 if verify_checksum {
332 let actual_index_checksum =
333 sley_core::digest_bytes(format, &bytes[..index_checksum_offset])?;
334 if actual_index_checksum != index_checksum {
335 return Err(GitError::InvalidFormat(format!(
336 "pack index checksum mismatch: expected {index_checksum}, got {actual_index_checksum}"
337 )));
338 }
339 }
340
341 let mut offset = 8usize;
342 let fanout = read_pack_index_fanout(bytes, &mut offset)?;
343 let count = fanout[255] as usize;
344 let oid_table = checked_range(offset, count, hash_len, bytes.len())?;
345 offset = oid_table.end;
346 let crc_table = checked_range(offset, count, 4, bytes.len())?;
347 offset = crc_table.end;
348 let small_offset_table = checked_range(offset, count, 4, bytes.len())?;
349 offset = small_offset_table.end;
350
351 let large_offset_count = (0..count)
352 .filter(|idx| {
353 let start = small_offset_table.start + idx * 4;
354 u32_be(&bytes[start..start + 4]) & 0x8000_0000 != 0
355 })
356 .count();
357 let mut large_offset_table = checked_range(offset, large_offset_count, 8, bytes.len())?;
358 offset = large_offset_table.end;
359
360 let expected_trailer_offset = bytes.len() - hash_len * 2;
361 if offset != expected_trailer_offset {
362 if !verify_checksum && offset < expected_trailer_offset {
363 large_offset_table = large_offset_table.start..expected_trailer_offset;
364 offset = expected_trailer_offset;
365 } else {
366 return Err(GitError::InvalidFormat(format!(
367 "pack index has {} unexpected bytes before trailer",
368 expected_trailer_offset.saturating_sub(offset)
369 )));
370 }
371 }
372 let pack_checksum = ObjectId::from_raw(format, &bytes[offset..offset + hash_len])?;
373
374 let view = Self {
375 version,
376 count,
377 fanout,
378 pack_checksum,
379 index_checksum,
380 bytes,
381 format,
382 tables: PackIndexViewTables::V2 {
383 oid_table,
384 crc_table,
385 small_offset_table,
386 large_offset_table,
387 },
388 };
389 if validate_entries {
390 view.validate_v2_entries()?;
391 }
392 Ok(view)
393 }
394
395 pub(crate) fn parse_v1_impl(
396 bytes: &'a [u8],
397 format: ObjectFormat,
398 verify_checksum: bool,
399 validate_entries: bool,
400 ) -> Result<Self> {
401 let hash_len = format.raw_len();
402 if bytes.len() < 256 * 4 + 2 * hash_len {
403 return Err(GitError::InvalidFormat("pack index too short".into()));
404 }
405 let index_checksum_offset = bytes.len() - hash_len;
406 let index_checksum = ObjectId::from_raw(format, &bytes[index_checksum_offset..])?;
407 if verify_checksum {
408 let actual_index_checksum =
409 sley_core::digest_bytes(format, &bytes[..index_checksum_offset])?;
410 if actual_index_checksum != index_checksum {
411 return Err(GitError::InvalidFormat(format!(
412 "pack index checksum mismatch: expected {index_checksum}, got {actual_index_checksum}"
413 )));
414 }
415 }
416
417 let mut offset = 0usize;
418 let fanout = read_pack_index_fanout(bytes, &mut offset)?;
419 let count = fanout[255] as usize;
420 let entry_len = hash_len
421 .checked_add(4)
422 .ok_or_else(|| GitError::InvalidFormat("pack index entry length overflow".into()))?;
423 let entry_table = checked_range(offset, count, entry_len, bytes.len())?;
424 offset = entry_table.end;
425 let expected_trailer_offset = bytes.len() - hash_len * 2;
426 if offset != expected_trailer_offset {
427 return Err(GitError::InvalidFormat(format!(
428 "pack index has {} unexpected bytes before trailer",
429 expected_trailer_offset.saturating_sub(offset)
430 )));
431 }
432 let pack_checksum = ObjectId::from_raw(format, &bytes[offset..offset + hash_len])?;
433
434 let view = Self {
435 version: 1,
436 count,
437 fanout,
438 pack_checksum,
439 index_checksum,
440 bytes,
441 format,
442 tables: PackIndexViewTables::V1 { entry_table },
443 };
444 if validate_entries {
445 view.validate_v1_entries()?;
446 }
447 Ok(view)
448 }
449
450 pub(crate) fn validate_v2_entries(&self) -> Result<()> {
451 let PackIndexViewTables::V2 {
452 oid_table,
453 small_offset_table,
454 large_offset_table,
455 ..
456 } = &self.tables
457 else {
458 unreachable!("v2 validation only runs for v2 views");
459 };
460 let oid_table = self.slice(oid_table.clone());
461 let small_offset_table = self.slice(small_offset_table.clone());
462 let large_offset_table = self.slice(large_offset_table.clone());
463 let hash_len = self.format.raw_len();
464 for idx in 0..self.count {
465 let oid_start = idx * hash_len;
466 let oid_bytes = &oid_table[oid_start..oid_start + hash_len];
467 if idx > 0 && oid_bytes < &oid_table[oid_start - hash_len..oid_start] {
468 return Err(GitError::InvalidFormat(
469 "pack index object ids are not sorted".into(),
470 ));
471 }
472 validate_pack_index_oid_fanout(idx, oid_bytes, &self.fanout)?;
473
474 let offset_start = idx * 4;
475 let raw_offset = u32_be(&small_offset_table[offset_start..offset_start + 4]);
476 pack_index_v2_offset(raw_offset, large_offset_table)?;
477 }
478 Ok(())
479 }
480
481 pub(crate) fn validate_v1_entries(&self) -> Result<()> {
482 let PackIndexViewTables::V1 { entry_table } = &self.tables else {
483 unreachable!("v1 validation only runs for v1 views");
484 };
485 let entry_table = self.slice(entry_table.clone());
486 let hash_len = self.format.raw_len();
487 let entry_len = hash_len
488 .checked_add(4)
489 .ok_or_else(|| GitError::InvalidFormat("pack index entry length overflow".into()))?;
490 for idx in 0..self.count {
491 let start = idx * entry_len;
492 let oid_start = start + 4;
493 let oid_bytes = &entry_table[oid_start..start + entry_len];
494 if idx > 0 {
495 let previous_oid_start = oid_start - entry_len;
496 let previous_oid = &entry_table[previous_oid_start..previous_oid_start + hash_len];
497 if previous_oid > oid_bytes {
498 return Err(GitError::InvalidFormat(
499 "pack index object ids are not sorted".into(),
500 ));
501 }
502 }
503 validate_pack_index_oid_fanout(idx, oid_bytes, &self.fanout)?;
504 }
505 Ok(())
506 }
507
508 pub(crate) fn oid_bytes_at(&self, idx: usize) -> &'a [u8] {
509 let hash_len = self.format.raw_len();
510 match &self.tables {
511 PackIndexViewTables::V1 { entry_table } => {
512 let entry_table = self.slice(entry_table.clone());
513 let entry_len = hash_len + 4;
514 let start = idx * entry_len + 4;
515 &entry_table[start..start + hash_len]
516 }
517 PackIndexViewTables::V2 { oid_table, .. } => {
518 let oid_table = self.slice(oid_table.clone());
519 let start = idx * hash_len;
520 &oid_table[start..start + hash_len]
521 }
522 }
523 }
524
525 pub(crate) fn lookup_at(&self, idx: usize) -> Option<PackIndexLookup> {
526 if idx >= self.count {
527 return None;
528 }
529 let hash_len = self.format.raw_len();
530 match &self.tables {
531 PackIndexViewTables::V1 { entry_table } => {
532 let entry_table = self.slice(entry_table.clone());
533 let entry_len = hash_len + 4;
534 let start = idx * entry_len;
535 Some(PackIndexLookup {
536 crc32: 0,
537 offset: u64::from(u32_be(&entry_table[start..start + 4])),
538 })
539 }
540 PackIndexViewTables::V2 {
541 crc_table,
542 small_offset_table,
543 large_offset_table,
544 ..
545 } => {
546 let crc_table = self.slice(crc_table.clone());
547 let small_offset_table = self.slice(small_offset_table.clone());
548 let large_offset_table = self.slice(large_offset_table.clone());
549 let crc_start = idx * 4;
550 let raw_offset = u32_be(&small_offset_table[crc_start..crc_start + 4]);
551 Some(PackIndexLookup {
552 crc32: u32_be(&crc_table[crc_start..crc_start + 4]),
553 offset: pack_index_v2_offset(raw_offset, large_offset_table).ok()?,
554 })
555 }
556 }
557 }
558
559 pub(crate) fn slice(&self, range: Range<usize>) -> &'a [u8] {
560 &self.bytes[range]
561 }
562}
563
564impl PackIndexViewData {
565 pub fn parse(bytes: Arc<[u8]>, format: ObjectFormat) -> Result<Self> {
566 Self::parse_source(Arc::new(SharedIndexBytes(bytes)), format)
567 }
568
569 pub fn parse_without_checksum(bytes: Arc<[u8]>, format: ObjectFormat) -> Result<Self> {
573 Self::parse_source_without_checksum(Arc::new(SharedIndexBytes(bytes)), format)
574 }
575
576 pub fn parse_trusted_without_checksum(bytes: Arc<[u8]>, format: ObjectFormat) -> Result<Self> {
579 Self::parse_trusted_source_without_checksum(Arc::new(SharedIndexBytes(bytes)), format)
580 }
581
582 pub fn parse_source(bytes: Arc<dyn PackIndexByteSource>, format: ObjectFormat) -> Result<Self> {
583 Self::parse_impl(bytes, format, true, true)
584 }
585
586 pub fn parse_source_without_checksum(
587 bytes: Arc<dyn PackIndexByteSource>,
588 format: ObjectFormat,
589 ) -> Result<Self> {
590 Self::parse_impl(bytes, format, false, true)
591 }
592
593 pub fn parse_trusted_source_without_checksum(
594 bytes: Arc<dyn PackIndexByteSource>,
595 format: ObjectFormat,
596 ) -> Result<Self> {
597 Self::parse_impl(bytes, format, false, false)
598 }
599
600 pub fn count(&self) -> usize {
601 self.count
602 }
603
604 pub fn fanout(&self) -> &[u32; 256] {
605 &self.fanout
606 }
607
608 pub fn find(&self, oid: &ObjectId) -> Option<PackIndexLookup> {
609 self.as_view().find(oid)
610 }
611
612 pub fn as_view(&self) -> PackIndexView<'_> {
613 PackIndexView {
614 version: self.version,
615 count: self.count,
616 fanout: self.fanout,
617 pack_checksum: self.pack_checksum,
618 index_checksum: self.index_checksum,
619 bytes: self.bytes.as_bytes(),
620 format: self.format,
621 tables: self.tables.clone(),
622 }
623 }
624
625 pub fn lookup_at(&self, idx: usize) -> Option<PackIndexLookup> {
627 self.as_view().lookup_at(idx)
628 }
629
630 pub fn oid_at(&self, idx: usize) -> Result<ObjectId> {
632 if idx >= self.count {
633 return Err(GitError::InvalidFormat(
634 "pack index position out of range".into(),
635 ));
636 }
637 ObjectId::from_raw(self.format, self.as_view().oid_bytes_at(idx))
638 }
639
640 pub fn oid_at_offset_linear(&self, offset: u64) -> Option<ObjectId> {
642 let view = self.as_view();
643 for idx in 0..self.count {
644 let lookup = view.lookup_at(idx)?;
645 if lookup.offset == offset {
646 return self.oid_at(idx).ok();
647 }
648 }
649 None
650 }
651
652 pub(crate) fn parse_impl(
653 bytes: Arc<dyn PackIndexByteSource>,
654 format: ObjectFormat,
655 verify_checksum: bool,
656 validate_entries: bool,
657 ) -> Result<Self> {
658 let (version, count, fanout, pack_checksum, index_checksum, tables) = {
659 let view = PackIndexView::parse_impl(
660 bytes.as_bytes(),
661 format,
662 verify_checksum,
663 validate_entries,
664 )?;
665 (
666 view.version,
667 view.count,
668 view.fanout,
669 view.pack_checksum,
670 view.index_checksum,
671 view.tables,
672 )
673 };
674 Ok(Self {
675 version,
676 count,
677 fanout,
678 pack_checksum,
679 index_checksum,
680 bytes,
681 format,
682 tables,
683 })
684 }
685}
686
687impl PackIndex {
688 pub fn write_v2_for_pack_sha1(pack_bytes: &[u8]) -> Result<PackIndexBuild> {
689 Self::write_v2_for_pack(pack_bytes, ObjectFormat::Sha1)
690 }
691
692 pub fn write_v2_for_pack(pack_bytes: &[u8], format: ObjectFormat) -> Result<PackIndexBuild> {
693 Self::write_v2_for_pack_with_base(pack_bytes, format, |_| Ok(None))
694 }
695
696 pub fn write_v2_for_pack_with_base<F>(
700 pack_bytes: &[u8],
701 format: ObjectFormat,
702 mut external_base: F,
703 ) -> Result<PackIndexBuild>
704 where
705 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
706 {
707 let trailer_len = format.raw_len();
708 if pack_bytes.len() < 12 + trailer_len {
709 return Err(GitError::InvalidFormat("pack file too short".into()));
710 }
711 let trailer_offset = pack_bytes.len() - trailer_len;
712 let pack_checksum = sley_core::digest_bytes(format, &pack_bytes[..trailer_offset])?;
713 let expected = ObjectId::from_raw(format, &pack_bytes[trailer_offset..])?;
714 if pack_checksum != expected {
715 return Err(GitError::InvalidFormat(format!(
716 "pack checksum mismatch: expected {expected}, got {pack_checksum}"
717 )));
718 }
719
720 if &pack_bytes[..4] != b"PACK" {
721 return Err(GitError::InvalidFormat("missing PACK signature".into()));
722 }
723 let version = u32_be(&pack_bytes[4..8]);
724 if version != 2 && version != 3 {
725 return Err(GitError::Unsupported(format!("pack version {version}")));
726 }
727 let count = u32_be(&pack_bytes[8..12]) as usize;
728 let mut offset = 12usize;
729 let mut parsed_entries = Vec::with_capacity(count);
730 let mut raw_entries = Vec::with_capacity(count);
731 for _ in 0..count {
732 let entry_offset = offset;
733 let header = parse_entry_header(pack_bytes, &mut offset)?;
734 let base = match header.kind {
735 PackObjectKind::OfsDelta => Some(DeltaBase::Offset(parse_ofs_delta_base_offset(
736 pack_bytes,
737 &mut offset,
738 entry_offset as u64,
739 )?)),
740 PackObjectKind::RefDelta => {
741 let hash_len = format.raw_len();
742 if offset + hash_len > trailer_offset {
743 return Err(GitError::InvalidFormat(
744 "truncated ref-delta base object id".into(),
745 ));
746 }
747 let oid = ObjectId::from_raw(format, &pack_bytes[offset..offset + hash_len])?;
748 offset += hash_len;
749 Some(DeltaBase::Ref(oid))
750 }
751 _ => None,
752 };
753 let mut body = Vec::new();
754 let consumed = inflate_into(
755 &pack_bytes[offset..trailer_offset],
756 &mut body,
757 header.size.min(usize::MAX as u64) as usize,
758 )?;
759 if body.len() as u64 != header.size {
760 return Err(GitError::InvalidObject(format!(
761 "pack object declared {} bytes, decoded {}",
762 header.size,
763 body.len()
764 )));
765 }
766 if consumed == 0 {
767 return Err(GitError::InvalidFormat(
768 "empty compressed pack entry".into(),
769 ));
770 }
771 offset = offset
772 .checked_add(consumed)
773 .ok_or_else(|| GitError::InvalidFormat("pack offset overflow".into()))?;
774 if offset > trailer_offset {
775 return Err(GitError::InvalidFormat(
776 "pack entry extends past checksum".into(),
777 ));
778 }
779 raw_entries.push((
780 entry_offset as u64,
781 crc32fast::hash(&pack_bytes[entry_offset..offset]),
782 ));
783 if let Some(base) = base {
784 parsed_entries.push(ParsedPackEntry::Delta {
785 base,
786 compressed_size: consumed as u64,
787 delta_size: header.size,
788 offset: entry_offset as u64,
789 delta: body,
790 });
791 } else {
792 let object_type = match header.kind {
793 PackObjectKind::Commit => ObjectType::Commit,
794 PackObjectKind::Tree => ObjectType::Tree,
795 PackObjectKind::Blob => ObjectType::Blob,
796 PackObjectKind::Tag => ObjectType::Tag,
797 PackObjectKind::OfsDelta | PackObjectKind::RefDelta => unreachable!(),
798 };
799 let object = EncodedObject::new(object_type, body);
800 let oid = object.object_id(format)?;
801 parsed_entries.push(ParsedPackEntry::Resolved(PackObject {
802 entry: PackEntry {
803 oid,
804 compressed_size: consumed as u64,
805 uncompressed_size: header.size,
806 offset: entry_offset as u64,
807 },
808 object,
809 }));
810 }
811 }
812 if offset != trailer_offset {
813 return Err(GitError::InvalidFormat(format!(
814 "pack has {} trailing bytes before checksum",
815 trailer_offset - offset
816 )));
817 }
818
819 let resolved = resolve_pack_entries(parsed_entries, format, &mut external_base)?;
820 let entries = resolved
821 .iter()
822 .zip(raw_entries)
823 .map(|(object, (offset, crc32))| PackIndexEntry {
824 oid: object.entry.oid,
825 crc32,
826 offset,
827 })
828 .collect::<Vec<_>>();
829 let index = PackIndex::write_v2(format, &entries, &pack_checksum)?;
830 Ok(PackIndexBuild {
831 index,
832 pack_checksum,
833 entries,
834 })
835 }
836
837 pub fn write_v2_for_pack_reader<R>(
844 reader: &mut R,
845 format: ObjectFormat,
846 ) -> Result<PackStreamIndexBuild>
847 where
848 R: Read + Seek,
849 {
850 let start = reader.stream_position()?;
851 let end = reader.seek(SeekFrom::End(0))?;
852 let pack_len = end
853 .checked_sub(start)
854 .ok_or_else(|| GitError::InvalidFormat("pack stream position overflow".into()))?;
855 reader.seek(SeekFrom::Start(start))?;
856 index_pack_from_reader(reader, format, pack_len)
857 }
858
859 pub fn write_v2_for_pack_reader_to_trailer<R>(
866 reader: &mut R,
867 format: ObjectFormat,
868 ) -> Result<PackStreamIndexBuild>
869 where
870 R: Read,
871 {
872 index_pack_from_reader_to_trailer(reader, format)
873 }
874
875 pub fn write_v2_for_pack_reader_to_trailer_with_progress<R, F>(
880 reader: &mut R,
881 format: ObjectFormat,
882 progress: F,
883 ) -> Result<PackStreamIndexBuild>
884 where
885 R: Read,
886 F: FnMut(PackStreamProgress),
887 {
888 index_pack_from_reader_to_trailer_with_progress(reader, format, progress)
889 }
890
891 pub fn write_v2_for_pack_reader_with_len<R>(
892 reader: &mut R,
893 format: ObjectFormat,
894 pack_len: u64,
895 ) -> Result<PackStreamIndexBuild>
896 where
897 R: Read,
898 {
899 index_pack_from_reader(reader, format, pack_len)
900 }
901
902 pub fn write_v2_for_pack_path(
905 path: impl AsRef<Path>,
906 format: ObjectFormat,
907 ) -> Result<PackStreamIndexBuild> {
908 let mut file = File::open(path)?;
909 Self::write_v2_for_pack_reader(&mut file, format)
910 }
911
912 pub fn parse_v2_sha1(bytes: &[u8]) -> Result<Self> {
913 Self::parse(bytes, ObjectFormat::Sha1)
914 }
915
916 pub fn parse(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
917 Self::parse_impl(bytes, format, true)
918 }
919
920 pub fn parse_without_checksum(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
921 Self::parse_impl(bytes, format, false)
922 }
923
924 pub(crate) fn parse_impl(
925 bytes: &[u8],
926 format: ObjectFormat,
927 verify_checksum: bool,
928 ) -> Result<Self> {
929 let hash_len = format.raw_len();
930 if bytes.len() < 4 {
931 return Err(GitError::InvalidFormat("pack index too short".into()));
932 }
933 if bytes[..4] != [0xff, b't', b'O', b'c'] {
934 return Self::parse_v1_impl(bytes, format, verify_checksum);
935 }
936 if bytes.len() < 8 + 256 * 4 + 2 * hash_len {
937 return Err(GitError::InvalidFormat("pack index too short".into()));
938 }
939 let version = u32_be(&bytes[4..8]);
940 if version != 2 {
941 return Err(GitError::Unsupported(format!(
942 "pack index version {version}"
943 )));
944 }
945 let index_checksum_offset = bytes.len() - hash_len;
946 let index_checksum = ObjectId::from_raw(format, &bytes[index_checksum_offset..])?;
947 if verify_checksum {
948 let actual_index_checksum =
949 sley_core::digest_bytes(format, &bytes[..index_checksum_offset])?;
950 if actual_index_checksum != index_checksum {
951 return Err(GitError::InvalidFormat(format!(
952 "pack index checksum mismatch: expected {index_checksum}, got {actual_index_checksum}"
953 )));
954 }
955 }
956
957 let mut offset = 8usize;
958 let mut fanout = [0u32; 256];
959 let mut previous = 0u32;
960 for slot in &mut fanout {
961 *slot = u32_be(&bytes[offset..offset + 4]);
962 if *slot < previous {
963 return Err(GitError::InvalidFormat(
964 "pack index fanout is not monotonic".into(),
965 ));
966 }
967 previous = *slot;
968 offset += 4;
969 }
970 let count = fanout[255] as usize;
971 let oid_table = checked_range(offset, count, hash_len, bytes.len())?;
972 offset = oid_table.end;
973 let crc_table = checked_range(offset, count, 4, bytes.len())?;
974 offset = crc_table.end;
975 let small_offset_table = checked_range(offset, count, 4, bytes.len())?;
976 offset = small_offset_table.end;
977
978 let large_offset_count = (0..count)
979 .filter(|idx| {
980 let start = small_offset_table.start + idx * 4;
981 u32_be(&bytes[start..start + 4]) & 0x8000_0000 != 0
982 })
983 .count();
984 let mut large_offset_table = checked_range(offset, large_offset_count, 8, bytes.len())?;
985 offset = large_offset_table.end;
986
987 let expected_trailer_offset = bytes.len() - hash_len * 2;
988 if offset != expected_trailer_offset {
989 if !verify_checksum && offset < expected_trailer_offset {
990 large_offset_table = large_offset_table.start..expected_trailer_offset;
991 offset = expected_trailer_offset;
992 } else {
993 return Err(GitError::InvalidFormat(format!(
994 "pack index has {} unexpected bytes before trailer",
995 expected_trailer_offset.saturating_sub(offset)
996 )));
997 }
998 }
999 let pack_checksum = ObjectId::from_raw(format, &bytes[offset..offset + hash_len])?;
1000
1001 let mut entries = Vec::with_capacity(count);
1002 for idx in 0..count {
1003 let oid_start = oid_table.start + idx * hash_len;
1004 let crc_start = crc_table.start + idx * 4;
1005 let offset_start = small_offset_table.start + idx * 4;
1006 let oid_bytes = &bytes[oid_start..oid_start + hash_len];
1007 if idx > 0 && oid_bytes < &bytes[oid_start - hash_len..oid_start] {
1012 return Err(GitError::InvalidFormat(
1013 "pack index object ids are not sorted".into(),
1014 ));
1015 }
1016 let expected_min = if oid_bytes[0] == 0 {
1017 0
1018 } else {
1019 fanout[usize::from(oid_bytes[0] - 1)]
1020 };
1021 if (idx as u32) < expected_min || (idx as u32) >= fanout[usize::from(oid_bytes[0])] {
1022 return Err(GitError::InvalidFormat(
1023 "pack index object id is outside its fanout bucket".into(),
1024 ));
1025 }
1026 let raw_offset = u32_be(&bytes[offset_start..offset_start + 4]);
1027 let offset = if raw_offset & 0x8000_0000 == 0 {
1028 u64::from(raw_offset)
1029 } else {
1030 let large_idx = (raw_offset & 0x7fff_ffff) as usize;
1031 let large_start = large_offset_table.start + large_idx * 8;
1032 if large_idx >= large_offset_table.len() / 8 {
1033 return Err(GitError::InvalidFormat(
1034 "pack index large offset points past table".into(),
1035 ));
1036 }
1037 u64_be(&bytes[large_start..large_start + 8])
1038 };
1039 entries.push(PackIndexEntry {
1040 oid: ObjectId::from_raw(format, oid_bytes)?,
1041 crc32: u32_be(&bytes[crc_start..crc_start + 4]),
1042 offset,
1043 });
1044 }
1045 Ok(Self {
1046 version,
1047 fanout,
1048 entries,
1049 pack_checksum,
1050 index_checksum,
1051 })
1052 }
1053
1054 pub(crate) fn parse_v1_impl(
1055 bytes: &[u8],
1056 format: ObjectFormat,
1057 verify_checksum: bool,
1058 ) -> Result<Self> {
1059 let hash_len = format.raw_len();
1060 if bytes.len() < 256 * 4 + 2 * hash_len {
1061 return Err(GitError::InvalidFormat("pack index too short".into()));
1062 }
1063 let index_checksum_offset = bytes.len() - hash_len;
1064 let index_checksum = ObjectId::from_raw(format, &bytes[index_checksum_offset..])?;
1065 if verify_checksum {
1066 let actual_index_checksum =
1067 sley_core::digest_bytes(format, &bytes[..index_checksum_offset])?;
1068 if actual_index_checksum != index_checksum {
1069 return Err(GitError::InvalidFormat(format!(
1070 "pack index checksum mismatch: expected {index_checksum}, got {actual_index_checksum}"
1071 )));
1072 }
1073 }
1074
1075 let mut offset = 0usize;
1076 let mut fanout = [0u32; 256];
1077 let mut previous = 0u32;
1078 for slot in &mut fanout {
1079 *slot = u32_be(&bytes[offset..offset + 4]);
1080 if *slot < previous {
1081 return Err(GitError::InvalidFormat(
1082 "pack index fanout is not monotonic".into(),
1083 ));
1084 }
1085 previous = *slot;
1086 offset += 4;
1087 }
1088 let count = fanout[255] as usize;
1089 let entry_len = hash_len
1090 .checked_add(4)
1091 .ok_or_else(|| GitError::InvalidFormat("pack index entry length overflow".into()))?;
1092 let entry_table = checked_range(offset, count, entry_len, bytes.len())?;
1093 offset = entry_table.end;
1094 let expected_trailer_offset = bytes.len() - hash_len * 2;
1095 if offset != expected_trailer_offset {
1096 return Err(GitError::InvalidFormat(format!(
1097 "pack index has {} unexpected bytes before trailer",
1098 expected_trailer_offset.saturating_sub(offset)
1099 )));
1100 }
1101 let pack_checksum = ObjectId::from_raw(format, &bytes[offset..offset + hash_len])?;
1102
1103 let mut entries = Vec::with_capacity(count);
1104 let mut previous_oid: Option<ObjectId> = None;
1105 for idx in 0..count {
1106 let start = entry_table.start + idx * entry_len;
1107 let oid = ObjectId::from_raw(format, &bytes[start + 4..start + entry_len])?;
1108 if let Some(previous) = &previous_oid
1109 && previous.as_bytes() > oid.as_bytes()
1110 {
1111 return Err(GitError::InvalidFormat(
1112 "pack index object ids are not sorted".into(),
1113 ));
1114 }
1115 previous_oid = Some(oid);
1116 entries.push(PackIndexEntry {
1117 oid,
1118 crc32: 0,
1119 offset: u64::from(u32_be(&bytes[start..start + 4])),
1120 });
1121 }
1122 Ok(Self {
1123 version: 1,
1124 fanout,
1125 entries,
1126 pack_checksum,
1127 index_checksum,
1128 })
1129 }
1130
1131 pub fn find(&self, oid: &ObjectId) -> Option<&PackIndexEntry> {
1132 self.entries
1133 .binary_search_by(|entry| entry.oid.as_bytes().cmp(oid.as_bytes()))
1134 .ok()
1135 .map(|idx| &self.entries[idx])
1136 }
1137
1138 pub fn write_v2_sha1(entries: &[PackIndexEntry], pack_checksum: &ObjectId) -> Result<Vec<u8>> {
1139 Self::write_v2(ObjectFormat::Sha1, entries, pack_checksum)
1140 }
1141
1142 pub fn write_v2(
1143 format: ObjectFormat,
1144 entries: &[PackIndexEntry],
1145 pack_checksum: &ObjectId,
1146 ) -> Result<Vec<u8>> {
1147 if pack_checksum.format() != format {
1148 return Err(GitError::InvalidObjectId(
1149 "pack checksum format does not match index format".into(),
1150 ));
1151 }
1152 let mut entries = entries.iter().collect::<Vec<_>>();
1153 entries.sort_by(|left, right| left.oid.as_bytes().cmp(right.oid.as_bytes()));
1154 let mut fanout = [0u32; 256];
1155 for entry in &entries {
1156 if entry.oid.format() != format {
1157 return Err(GitError::InvalidObjectId(
1158 "pack index entry format does not match index format".into(),
1159 ));
1160 }
1161 let first = entry.oid.as_bytes()[0] as usize;
1162 fanout[first] = fanout[first]
1163 .checked_add(1)
1164 .ok_or_else(|| GitError::InvalidFormat("pack index fanout overflow".into()))?;
1165 }
1166 let mut running = 0u32;
1167 for slot in &mut fanout {
1168 running = running
1169 .checked_add(*slot)
1170 .ok_or_else(|| GitError::InvalidFormat("pack index fanout overflow".into()))?;
1171 *slot = running;
1172 }
1173
1174 let mut index = Vec::new();
1175 index.extend_from_slice(&[0xff, b't', b'O', b'c']);
1176 index.extend_from_slice(&2u32.to_be_bytes());
1177 for count in fanout {
1178 index.extend_from_slice(&count.to_be_bytes());
1179 }
1180 for entry in &entries {
1181 index.extend_from_slice(entry.oid.as_bytes());
1182 }
1183 for entry in &entries {
1184 index.extend_from_slice(&entry.crc32.to_be_bytes());
1185 }
1186
1187 let mut large_offsets = Vec::new();
1188 for entry in &entries {
1189 if entry.offset < 0x8000_0000 {
1190 index.extend_from_slice(&(entry.offset as u32).to_be_bytes());
1191 } else {
1192 if large_offsets.len() > 0x7fff_ffff {
1193 return Err(GitError::InvalidFormat(
1194 "too many large pack offsets".into(),
1195 ));
1196 }
1197 let large_idx = large_offsets.len() as u32;
1198 index.extend_from_slice(&(0x8000_0000 | large_idx).to_be_bytes());
1199 large_offsets.push(entry.offset);
1200 }
1201 }
1202 for offset in large_offsets {
1203 index.extend_from_slice(&offset.to_be_bytes());
1204 }
1205 index.extend_from_slice(pack_checksum.as_bytes());
1206 let index_checksum = sley_core::digest_bytes(format, &index)?;
1207 index.extend_from_slice(index_checksum.as_bytes());
1208 Ok(index)
1209 }
1210
1211 pub fn write_v1(
1217 format: ObjectFormat,
1218 entries: &[PackIndexEntry],
1219 pack_checksum: &ObjectId,
1220 ) -> Result<Vec<u8>> {
1221 if pack_checksum.format() != format {
1222 return Err(GitError::InvalidObjectId(
1223 "pack checksum format does not match index format".into(),
1224 ));
1225 }
1226 let mut entries = entries.iter().collect::<Vec<_>>();
1227 entries.sort_by(|left, right| left.oid.as_bytes().cmp(right.oid.as_bytes()));
1228 let mut fanout = [0u32; 256];
1229 for entry in &entries {
1230 if entry.oid.format() != format {
1231 return Err(GitError::InvalidObjectId(
1232 "pack index entry format does not match index format".into(),
1233 ));
1234 }
1235 if entry.offset > 0xffff_ffff {
1236 return Err(GitError::InvalidFormat(
1237 "pack offset too large for a version-1 index".into(),
1238 ));
1239 }
1240 let first = entry.oid.as_bytes()[0] as usize;
1241 fanout[first] = fanout[first]
1242 .checked_add(1)
1243 .ok_or_else(|| GitError::InvalidFormat("pack index fanout overflow".into()))?;
1244 }
1245 let mut running = 0u32;
1246 for slot in &mut fanout {
1247 running = running
1248 .checked_add(*slot)
1249 .ok_or_else(|| GitError::InvalidFormat("pack index fanout overflow".into()))?;
1250 *slot = running;
1251 }
1252
1253 let mut index = Vec::new();
1254 for count in fanout {
1255 index.extend_from_slice(&count.to_be_bytes());
1256 }
1257 for entry in &entries {
1258 index.extend_from_slice(&(entry.offset as u32).to_be_bytes());
1259 index.extend_from_slice(entry.oid.as_bytes());
1260 }
1261 index.extend_from_slice(pack_checksum.as_bytes());
1262 let index_checksum = sley_core::digest_bytes(format, &index)?;
1263 index.extend_from_slice(index_checksum.as_bytes());
1264 Ok(index)
1265 }
1266}
1267pub fn pack_order_index_positions(entries: &[PackIndexEntry]) -> Vec<u32> {
1272 let mut oid_sorted: Vec<usize> = (0..entries.len()).collect();
1273 oid_sorted.sort_by(|&a, &b| entries[a].oid.as_bytes().cmp(entries[b].oid.as_bytes()));
1274 let mut index_position = vec![0u32; entries.len()];
1275 for (position, &entry) in oid_sorted.iter().enumerate() {
1276 index_position[entry] = position as u32;
1277 }
1278 let mut by_offset: Vec<usize> = (0..entries.len()).collect();
1279 by_offset.sort_by_key(|&entry| entries[entry].offset);
1280 by_offset
1281 .into_iter()
1282 .map(|entry| index_position[entry])
1283 .collect()
1284}
1285
1286impl PackReverseIndex {
1287 pub fn write(
1288 format: ObjectFormat,
1289 positions: &[u32],
1290 pack_checksum: &ObjectId,
1291 ) -> Result<Vec<u8>> {
1292 if pack_checksum.format() != format {
1293 return Err(GitError::InvalidObjectId(
1294 "pack checksum format does not match reverse index format".into(),
1295 ));
1296 }
1297 validate_position_permutation(positions)?;
1298
1299 let mut out = Vec::new();
1300 out.extend_from_slice(b"RIDX");
1301 out.extend_from_slice(&1u32.to_be_bytes());
1302 out.extend_from_slice(&hash_function_id(format).to_be_bytes());
1303 for position in positions {
1304 out.extend_from_slice(&position.to_be_bytes());
1305 }
1306 out.extend_from_slice(pack_checksum.as_bytes());
1307 let checksum = sley_core::digest_bytes(format, &out)?;
1308 out.extend_from_slice(checksum.as_bytes());
1309 Ok(out)
1310 }
1311
1312 pub fn parse(bytes: &[u8], format: ObjectFormat, object_count: usize) -> Result<Self> {
1313 let hash_len = format.raw_len();
1314 let table_len = object_count
1315 .checked_mul(4)
1316 .ok_or_else(|| GitError::InvalidFormat("reverse index table overflow".into()))?;
1317 let min_len = 12usize
1318 .checked_add(table_len)
1319 .and_then(|len| len.checked_add(hash_len * 2))
1320 .ok_or_else(|| GitError::InvalidFormat("reverse index length overflow".into()))?;
1321 if bytes.len() < min_len {
1322 return Err(GitError::InvalidFormat("reverse index too short".into()));
1323 }
1324 if bytes.len() != min_len {
1325 return Err(GitError::InvalidFormat(format!(
1326 "reverse index has {} trailing bytes",
1327 bytes.len() - min_len
1328 )));
1329 }
1330 if &bytes[..4] != b"RIDX" {
1331 return Err(GitError::InvalidFormat("unknown signature".into()));
1332 }
1333 let version = u32_be(&bytes[4..8]);
1334 if version != 1 {
1335 return Err(GitError::InvalidFormat(format!(
1336 "unsupported version {version}"
1337 )));
1338 }
1339 let hash_id = u32_be(&bytes[8..12]);
1340 if hash_id != hash_function_id(format) {
1341 return Err(GitError::InvalidFormat(format!(
1342 "unsupported hash id {hash_id}"
1343 )));
1344 }
1345
1346 let index_checksum_offset = bytes.len() - hash_len;
1347 let pack_checksum_offset = index_checksum_offset - hash_len;
1348 let pack_checksum =
1349 ObjectId::from_raw(format, &bytes[pack_checksum_offset..index_checksum_offset])?;
1350 let mut positions = Vec::with_capacity(object_count);
1351 let mut offset = 12usize;
1352 for _ in 0..object_count {
1353 let position = u32_be(&bytes[offset..offset + 4]);
1354 positions.push(position);
1355 offset += 4;
1356 }
1357 validate_position_permutation(&positions)?;
1358
1359 let actual_index_checksum =
1362 sley_core::digest_bytes(format, &bytes[..index_checksum_offset])?;
1363 let index_checksum = ObjectId::from_raw(format, &bytes[index_checksum_offset..])?;
1364 if actual_index_checksum != index_checksum {
1365 return Err(GitError::InvalidFormat("invalid checksum".into()));
1366 }
1367
1368 Ok(Self {
1369 version,
1370 format,
1371 positions,
1372 pack_checksum,
1373 index_checksum,
1374 })
1375 }
1376
1377 pub fn oid_at_offset(&self, index: &PackIndexViewData, offset: u64) -> Option<ObjectId> {
1383 if self.pack_checksum != index.pack_checksum {
1384 return None;
1385 }
1386 let view = index.as_view();
1387 let positions = &self.positions;
1388 let mut lo = 0usize;
1389 let mut hi = positions.len();
1390 while lo < hi {
1391 let mid = lo + (hi - lo) / 2;
1392 let idx_pos = positions[mid] as usize;
1393 let entry_offset = view.lookup_at(idx_pos)?.offset;
1394 if entry_offset < offset {
1395 lo = mid + 1;
1396 } else if entry_offset > offset {
1397 hi = mid;
1398 } else {
1399 return index.oid_at(idx_pos).ok();
1400 }
1401 }
1402 None
1403 }
1404}
1405
1406impl PackMtimes {
1407 pub fn write(
1408 format: ObjectFormat,
1409 mtimes: &[u32],
1410 pack_checksum: &ObjectId,
1411 ) -> Result<Vec<u8>> {
1412 if pack_checksum.format() != format {
1413 return Err(GitError::InvalidObjectId(
1414 "pack checksum format does not match mtimes format".into(),
1415 ));
1416 }
1417
1418 let mut out = Vec::new();
1419 out.extend_from_slice(b"MTME");
1420 out.extend_from_slice(&1u32.to_be_bytes());
1421 out.extend_from_slice(&hash_function_id(format).to_be_bytes());
1422 for mtime in mtimes {
1423 out.extend_from_slice(&mtime.to_be_bytes());
1424 }
1425 out.extend_from_slice(pack_checksum.as_bytes());
1426 let checksum = sley_core::digest_bytes(format, &out)?;
1427 out.extend_from_slice(checksum.as_bytes());
1428 Ok(out)
1429 }
1430
1431 pub fn parse(bytes: &[u8], format: ObjectFormat, object_count: usize) -> Result<Self> {
1432 let hash_len = format.raw_len();
1433 let table_len = object_count
1434 .checked_mul(4)
1435 .ok_or_else(|| GitError::InvalidFormat("mtimes table overflow".into()))?;
1436 let expected_len = 12usize
1437 .checked_add(table_len)
1438 .and_then(|len| len.checked_add(hash_len * 2))
1439 .ok_or_else(|| GitError::InvalidFormat("mtimes length overflow".into()))?;
1440 if bytes.len() < expected_len {
1441 return Err(GitError::InvalidFormat("mtimes file too short".into()));
1442 }
1443 if bytes.len() != expected_len {
1444 return Err(GitError::InvalidFormat(format!(
1445 "mtimes file has {} trailing bytes",
1446 bytes.len() - expected_len
1447 )));
1448 }
1449 if &bytes[..4] != b"MTME" {
1450 return Err(GitError::InvalidFormat("missing mtimes signature".into()));
1451 }
1452 let version = u32_be(&bytes[4..8]);
1453 if version != 1 {
1454 return Err(GitError::Unsupported(format!("mtimes version {version}")));
1455 }
1456 let hash_id = u32_be(&bytes[8..12]);
1457 if hash_id != hash_function_id(format) {
1458 return Err(GitError::InvalidFormat(format!(
1459 "mtimes hash id {hash_id} does not match {}",
1460 format.name()
1461 )));
1462 }
1463
1464 let index_checksum_offset = bytes.len() - hash_len;
1465 let actual_index_checksum =
1466 sley_core::digest_bytes(format, &bytes[..index_checksum_offset])?;
1467 let index_checksum = ObjectId::from_raw(format, &bytes[index_checksum_offset..])?;
1468 if actual_index_checksum != index_checksum {
1469 return Err(GitError::InvalidFormat(format!(
1470 "mtimes checksum mismatch: expected {index_checksum}, got {actual_index_checksum}"
1471 )));
1472 }
1473
1474 let pack_checksum_offset = index_checksum_offset - hash_len;
1475 let pack_checksum =
1476 ObjectId::from_raw(format, &bytes[pack_checksum_offset..index_checksum_offset])?;
1477 let mut mtimes = Vec::with_capacity(object_count);
1478 let mut offset = 12usize;
1479 for _ in 0..object_count {
1480 mtimes.push(u32_be(&bytes[offset..offset + 4]));
1481 offset += 4;
1482 }
1483
1484 Ok(Self {
1485 version,
1486 format,
1487 mtimes,
1488 pack_checksum,
1489 index_checksum,
1490 })
1491 }
1492}
1493
1494impl PackBitmapIndex {
1495 pub const OPTION_FULL_DAG: u16 = 0x0001;
1496 pub const OPTION_HASH_CACHE: u16 = 0x0004;
1497 pub const OPTION_LOOKUP_TABLE: u16 = 0x0010;
1498 pub const OPTION_PSEUDO_MERGES: u16 = 0x0020;
1499
1500 pub fn parse(bytes: &[u8], format: ObjectFormat, object_count: usize) -> Result<Self> {
1501 let hash_len = format.raw_len();
1502 let min_len = 12usize
1503 .checked_add(hash_len * 2)
1504 .ok_or_else(|| GitError::InvalidFormat("bitmap index length overflow".into()))?;
1505 if bytes.len() < min_len {
1506 return Err(GitError::InvalidFormat("bitmap index too short".into()));
1507 }
1508 if &bytes[..4] != b"BITM" {
1509 return Err(GitError::InvalidFormat(
1510 "missing bitmap index signature".into(),
1511 ));
1512 }
1513 let version = u16_be(&bytes[4..6]);
1514 if version != 1 {
1515 return Err(GitError::Unsupported(format!(
1516 "bitmap index version {version}"
1517 )));
1518 }
1519 let options = u16_be(&bytes[6..8]);
1520 let known_options = Self::OPTION_FULL_DAG
1521 | Self::OPTION_HASH_CACHE
1522 | Self::OPTION_LOOKUP_TABLE
1523 | Self::OPTION_PSEUDO_MERGES;
1524 if options & !known_options != 0 {
1525 return Err(GitError::Unsupported(format!(
1526 "bitmap index options {:#06x}",
1527 options & !known_options
1528 )));
1529 }
1530 let entry_count = u32_be(&bytes[8..12]) as usize;
1531 let checksum_offset = bytes.len() - hash_len;
1532 let index_checksum = ObjectId::from_raw(format, &bytes[checksum_offset..])?;
1533 let mut extras_end = checksum_offset;
1534 let hash_cache_range = if options & Self::OPTION_HASH_CACHE != 0 {
1535 let cache_len = object_count
1536 .checked_mul(4)
1537 .ok_or_else(|| GitError::InvalidFormat("bitmap hash cache overflow".into()))?;
1538 if cache_len > extras_end {
1539 return Err(GitError::InvalidFormat(
1540 "truncated bitmap hash cache".into(),
1541 ));
1542 }
1543 extras_end -= cache_len;
1544 Some(extras_end..extras_end + cache_len)
1545 } else {
1546 None
1547 };
1548 let lookup_table = options & Self::OPTION_LOOKUP_TABLE != 0;
1549 let lookup_table_range = if lookup_table {
1550 let table_len = entry_count
1551 .checked_mul(16)
1552 .ok_or_else(|| GitError::InvalidFormat("bitmap lookup table overflow".into()))?;
1553 if table_len > extras_end {
1554 return Err(GitError::InvalidFormat(
1555 "truncated bitmap lookup table".into(),
1556 ));
1557 }
1558 extras_end -= table_len;
1559 Some(extras_end..extras_end + table_len)
1560 } else {
1561 None
1562 };
1563 let pseudo_merge_range = if options & Self::OPTION_PSEUDO_MERGES != 0 {
1564 if extras_end < 24 {
1565 return Err(GitError::InvalidFormat(
1566 "truncated bitmap pseudo-merge extension".into(),
1567 ));
1568 }
1569 let extension_size = u64_be(&bytes[extras_end - 8..extras_end]) as usize;
1570 if extension_size > extras_end {
1571 return Err(GitError::InvalidFormat(
1572 "bitmap pseudo-merge extension points before file start".into(),
1573 ));
1574 }
1575 let start = extras_end - extension_size;
1576 Some(start..extras_end)
1577 } else {
1578 None
1579 };
1580 let entries_end = pseudo_merge_range
1581 .as_ref()
1582 .map(|range| range.start)
1583 .unwrap_or(extras_end);
1584
1585 let pack_checksum_end = 12usize
1586 .checked_add(hash_len)
1587 .ok_or_else(|| GitError::InvalidFormat("bitmap index length overflow".into()))?;
1588 let pack_checksum = ObjectId::from_raw(format, &bytes[12..pack_checksum_end])?;
1589 let mut offset = pack_checksum_end;
1590 let commits = parse_bitmap_ewah(bytes, &mut offset, entries_end, object_count)?;
1591 let trees = parse_bitmap_ewah(bytes, &mut offset, entries_end, object_count)?;
1592 let blobs = parse_bitmap_ewah(bytes, &mut offset, entries_end, object_count)?;
1593 let tags = parse_bitmap_ewah(bytes, &mut offset, entries_end, object_count)?;
1594
1595 let mut entries = Vec::with_capacity(entry_count);
1596 for idx in 0..entry_count {
1597 if entries_end.saturating_sub(offset) < 6 {
1598 return Err(GitError::InvalidFormat(
1599 "truncated bitmap index entry".into(),
1600 ));
1601 }
1602 let object_position = u32_be(&bytes[offset..offset + 4]);
1603 offset += 4;
1604 if object_position as usize >= object_count {
1605 return Err(GitError::InvalidFormat(
1606 "bitmap index entry points past object table".into(),
1607 ));
1608 }
1609 let xor_offset = bytes[offset];
1610 offset += 1;
1611 if xor_offset as usize > idx || xor_offset > 160 {
1612 return Err(GitError::InvalidFormat(
1613 "bitmap index entry has invalid XOR offset".into(),
1614 ));
1615 }
1616 let flags = bytes[offset];
1617 offset += 1;
1618 let bitmap = parse_bitmap_ewah(bytes, &mut offset, entries_end, object_count)?;
1619 entries.push(PackBitmapEntry {
1620 object_position,
1621 xor_offset,
1622 flags,
1623 bitmap,
1624 });
1625 }
1626
1627 if offset != entries_end {
1628 return Err(GitError::InvalidFormat(format!(
1629 "bitmap index has {} trailing entry bytes",
1630 entries_end - offset
1631 )));
1632 }
1633
1634 let pseudo_merges = if let Some(range) = pseudo_merge_range {
1635 parse_bitmap_pseudo_merges(bytes, range, object_count)?
1636 } else {
1637 Vec::new()
1638 };
1639
1640 let name_hash_cache = if let Some(range) = hash_cache_range {
1641 let mut cache = Vec::with_capacity(object_count);
1642 let mut offset = range.start;
1643 for _ in 0..object_count {
1644 cache.push(u32_be(&bytes[offset..offset + 4]));
1645 offset += 4;
1646 }
1647 Some(cache)
1648 } else {
1649 None
1650 };
1651 if let Some(range) = lookup_table_range {
1652 for row in bytes[range].chunks_exact(16) {
1653 let commit_position = u32_be(&row[..4]);
1654 let entry_offset = u64_be(&row[4..12]);
1655 let xor_row = u32_be(&row[12..16]);
1656 if commit_position as usize >= object_count
1657 || entry_offset as usize >= entries_end
1658 || (xor_row != u32::MAX && xor_row as usize >= entry_count)
1659 {
1660 return Err(GitError::InvalidFormat(
1661 "corrupt bitmap lookup table".into(),
1662 ));
1663 }
1664 }
1665 }
1666
1667 let actual_index_checksum = sley_core::digest_bytes(format, &bytes[..checksum_offset])?;
1668 if actual_index_checksum != index_checksum {
1669 return Err(GitError::InvalidFormat(format!(
1670 "bitmap index checksum mismatch: expected {index_checksum}, got {actual_index_checksum}"
1671 )));
1672 }
1673
1674 Ok(Self {
1675 version,
1676 format,
1677 options,
1678 pack_checksum,
1679 index_checksum,
1680 type_bitmaps: PackBitmapTypeBitmaps {
1681 commits,
1682 trees,
1683 blobs,
1684 tags,
1685 },
1686 entries,
1687 pseudo_merges,
1688 lookup_table,
1689 name_hash_cache,
1690 })
1691 }
1692
1693 pub fn entry_for_index_position(&self, position: u32) -> Option<&PackBitmapEntry> {
1696 self.entries
1697 .iter()
1698 .find(|entry| entry.object_position == position)
1699 }
1700}
1701
1702pub(crate) fn parse_bitmap_pseudo_merges(
1703 bytes: &[u8],
1704 range: std::ops::Range<usize>,
1705 object_count: usize,
1706) -> Result<Vec<PackBitmapPseudoMerge>> {
1707 if range.end < range.start || range.end > bytes.len() || range.end - range.start < 24 {
1708 return Err(GitError::InvalidFormat(
1709 "truncated bitmap pseudo-merge extension".into(),
1710 ));
1711 }
1712 let trailer_start = range.end - 24;
1713 let pseudo_merge_count = u32_be(&bytes[trailer_start..trailer_start + 4]) as usize;
1714 let commit_count = u32_be(&bytes[trailer_start + 4..trailer_start + 8]) as usize;
1715 let lookup_offset = u64_be(&bytes[trailer_start + 8..trailer_start + 16]) as usize;
1716 let extension_size = u64_be(&bytes[trailer_start + 16..trailer_start + 24]) as usize;
1717 if extension_size != range.end - range.start {
1718 return Err(GitError::InvalidFormat(
1719 "bitmap pseudo-merge extension size mismatch".into(),
1720 ));
1721 }
1722 let lookup_start = range
1723 .start
1724 .checked_add(lookup_offset)
1725 .ok_or_else(|| GitError::InvalidFormat("bitmap pseudo-merge lookup overflow".into()))?;
1726 if lookup_start > trailer_start {
1727 return Err(GitError::InvalidFormat(
1728 "bitmap pseudo-merge lookup points past extension".into(),
1729 ));
1730 }
1731 let lookup_len = commit_count
1732 .checked_mul(12)
1733 .ok_or_else(|| GitError::InvalidFormat("bitmap pseudo-merge lookup overflow".into()))?;
1734 if lookup_start
1735 .checked_add(lookup_len)
1736 .is_none_or(|end| end > trailer_start)
1737 {
1738 return Err(GitError::InvalidFormat(
1739 "truncated bitmap pseudo-merge lookup".into(),
1740 ));
1741 }
1742 let position_table_len = pseudo_merge_count.checked_mul(8).ok_or_else(|| {
1743 GitError::InvalidFormat("bitmap pseudo-merge position table overflow".into())
1744 })?;
1745 let position_table_start = trailer_start
1746 .checked_sub(position_table_len)
1747 .filter(|start| *start >= range.start)
1748 .ok_or_else(|| {
1749 GitError::InvalidFormat("truncated bitmap pseudo-merge position table".into())
1750 })?;
1751
1752 let mut pseudo_merges = Vec::with_capacity(pseudo_merge_count);
1753 let mut cursor = position_table_start;
1754 for _ in 0..pseudo_merge_count {
1755 let pseudo_offset = u64_be(&bytes[cursor..cursor + 8]) as usize;
1756 cursor += 8;
1757 if pseudo_offset < range.start || pseudo_offset >= position_table_start {
1758 return Err(GitError::InvalidFormat(
1759 "bitmap pseudo-merge offset out of range".into(),
1760 ));
1761 }
1762 let mut offset = pseudo_offset;
1763 let commits = parse_bitmap_ewah(bytes, &mut offset, range.end, object_count)?;
1764 let bitmap = parse_bitmap_ewah(bytes, &mut offset, range.end, object_count)?;
1765 pseudo_merges.push(PackBitmapPseudoMerge { commits, bitmap });
1766 }
1767 Ok(pseudo_merges)
1768}
1769
1770pub(crate) fn parse_bitmap_ewah(
1771 bytes: &[u8],
1772 offset: &mut usize,
1773 checksum_offset: usize,
1774 _object_count: usize,
1775) -> Result<EwahBitmap> {
1776 if checksum_offset.saturating_sub(*offset) < 12 {
1777 return Err(GitError::InvalidFormat("truncated EWAH bitmap".into()));
1778 }
1779 let bit_size = u32_be(&bytes[*offset..*offset + 4]);
1780 *offset += 4;
1781 let word_count = u32_be(&bytes[*offset..*offset + 4]) as usize;
1782 *offset += 4;
1783 let words_len = word_count
1784 .checked_mul(8)
1785 .ok_or_else(|| GitError::InvalidFormat("EWAH word table overflow".into()))?;
1786 if checksum_offset.saturating_sub(*offset) < words_len + 4 {
1787 return Err(GitError::InvalidFormat("truncated EWAH word table".into()));
1788 }
1789 let mut words = Vec::with_capacity(word_count);
1790 for _ in 0..word_count {
1791 words.push(u64_be(&bytes[*offset..*offset + 8]));
1792 *offset += 8;
1793 }
1794 let rlw_position = u32_be(&bytes[*offset..*offset + 4]);
1795 *offset += 4;
1796 validate_ewah_words(bit_size, &words, rlw_position)?;
1797 Ok(EwahBitmap {
1798 bit_size,
1799 words,
1800 rlw_position,
1801 })
1802}
1803
1804pub(crate) fn validate_ewah_words(bit_size: u32, words: &[u64], rlw_position: u32) -> Result<()> {
1805 if words.is_empty() {
1806 if rlw_position != 0 || bit_size != 0 {
1807 return Err(GitError::InvalidFormat(
1808 "EWAH bitmap has invalid empty RLW".into(),
1809 ));
1810 }
1811 return Ok(());
1812 }
1813 if rlw_position as usize >= words.len() {
1814 return Err(GitError::InvalidFormat(
1815 "EWAH RLW position points past word table".into(),
1816 ));
1817 }
1818 let mut word_idx = 0usize;
1819 let mut decoded_words = 0u64;
1820 while word_idx < words.len() {
1821 let rlw = words[word_idx];
1822 let run_words = (rlw >> 1) & 0xffff_ffff;
1823 let literal_words = (rlw >> 33) as usize;
1824 word_idx += 1;
1825 word_idx = word_idx
1826 .checked_add(literal_words)
1827 .ok_or_else(|| GitError::InvalidFormat("EWAH literal word overflow".into()))?;
1828 if word_idx > words.len() {
1829 return Err(GitError::InvalidFormat(
1830 "EWAH literal words extend past word table".into(),
1831 ));
1832 }
1833 decoded_words = decoded_words
1834 .checked_add(run_words)
1835 .and_then(|value| value.checked_add(literal_words as u64))
1836 .ok_or_else(|| GitError::InvalidFormat("EWAH decoded size overflow".into()))?;
1837 }
1838 let decoded_bits = decoded_words
1839 .checked_mul(64)
1840 .ok_or_else(|| GitError::InvalidFormat("EWAH decoded bit size overflow".into()))?;
1841 if decoded_bits < u64::from(bit_size) {
1842 return Err(GitError::InvalidFormat(
1843 "EWAH bitmap decodes fewer bits than declared".into(),
1844 ));
1845 }
1846 Ok(())
1847}
1848
1849impl MultiPackIndex {
1850 pub fn write(
1851 format: ObjectFormat,
1852 version: u8,
1853 pack_names: &[String],
1854 objects: &[MultiPackIndexEntry],
1855 ) -> Result<Vec<u8>> {
1856 Self::write_with_reverse_index(format, version, pack_names, objects, None)
1857 }
1858
1859 pub fn write_with_reverse_index(
1868 format: ObjectFormat,
1869 version: u8,
1870 pack_names: &[String],
1871 objects: &[MultiPackIndexEntry],
1872 preferred_pack: Option<u32>,
1873 ) -> Result<Vec<u8>> {
1874 Self::write_with_bitmap_packs(format, version, pack_names, objects, preferred_pack, None)
1875 }
1876
1877 pub fn write_with_bitmap_packs(
1878 format: ObjectFormat,
1879 version: u8,
1880 pack_names: &[String],
1881 objects: &[MultiPackIndexEntry],
1882 preferred_pack: Option<u32>,
1883 bitmapped_packs: Option<&[MultiPackBitmapPack]>,
1884 ) -> Result<Vec<u8>> {
1885 if let Some(preferred) = preferred_pack
1886 && preferred as usize >= pack_names.len()
1887 {
1888 return Err(GitError::InvalidFormat(format!(
1889 "preferred pack {preferred} out of range for {} packs",
1890 pack_names.len()
1891 )));
1892 }
1893 if version != 1 && version != 2 {
1894 return Err(GitError::Unsupported(format!(
1895 "multi-pack-index version {version}"
1896 )));
1897 }
1898 if pack_names.len() > u32::MAX as usize {
1899 return Err(GitError::InvalidFormat(
1900 "too many multi-pack-index packs".into(),
1901 ));
1902 }
1903 if objects.len() > u32::MAX as usize {
1904 return Err(GitError::InvalidFormat(
1905 "too many multi-pack-index objects".into(),
1906 ));
1907 }
1908 if let Some(bitmapped_packs) = bitmapped_packs {
1909 if bitmapped_packs.len() != pack_names.len() {
1910 return Err(GitError::InvalidFormat(
1911 "multi-pack-index BTMP pack count mismatch".into(),
1912 ));
1913 }
1914 for pack in bitmapped_packs {
1915 let bitmap_end = u64::from(pack.bitmap_pos)
1916 .checked_add(u64::from(pack.bitmap_nr))
1917 .ok_or_else(|| {
1918 GitError::InvalidFormat("multi-pack-index BTMP range overflow".into())
1919 })?;
1920 if bitmap_end > objects.len() as u64 {
1921 return Err(GitError::InvalidFormat(
1922 "multi-pack-index BTMP range points past object table".into(),
1923 ));
1924 }
1925 }
1926 }
1927 validate_midx_pack_names(pack_names)?;
1928 if version == 1 && pack_names.windows(2).any(|pair| pair[0] > pair[1]) {
1929 return Err(GitError::InvalidFormat(
1930 "multi-pack-index v1 pack names must be sorted".into(),
1931 ));
1932 }
1933
1934 let mut objects = objects.iter().collect::<Vec<_>>();
1935 objects.sort_by(|left, right| left.oid.as_bytes().cmp(right.oid.as_bytes()));
1936 let mut previous_oid: Option<&ObjectId> = None;
1937 for object in &objects {
1938 if object.oid.format() != format {
1939 return Err(GitError::InvalidObjectId(
1940 "multi-pack-index object format does not match index format".into(),
1941 ));
1942 }
1943 if let Some(previous) = previous_oid
1944 && previous.as_bytes() == object.oid.as_bytes()
1945 {
1946 return Err(GitError::InvalidFormat(
1947 "multi-pack-index contains duplicate object ids".into(),
1948 ));
1949 }
1950 if object.pack_int_id as usize >= pack_names.len() {
1951 return Err(GitError::InvalidFormat(
1952 "multi-pack-index object points past pack table".into(),
1953 ));
1954 }
1955 previous_oid = Some(&object.oid);
1956 }
1957
1958 let mut large_offsets = Vec::new();
1959 let mut chunks = vec![
1960 (*b"PNAM", write_midx_pack_names(pack_names)),
1961 (*b"OIDF", write_midx_oid_fanout(&objects)?),
1962 (*b"OIDL", write_midx_oid_lookup(&objects)),
1963 (
1964 *b"OOFF",
1965 write_midx_object_offsets(&objects, &mut large_offsets)?,
1966 ),
1967 ];
1968 if !large_offsets.is_empty() {
1969 chunks.push((*b"LOFF", large_offsets));
1970 }
1971 if let Some(preferred) = preferred_pack {
1972 let mut pseudo: Vec<u32> = (0..objects.len() as u32).collect();
1975 pseudo.sort_by_key(|&midx_pos| {
1976 let object = objects[midx_pos as usize];
1977 (
1978 object.pack_int_id != preferred,
1979 object.pack_int_id,
1980 object.offset,
1981 )
1982 });
1983 let mut ridx = Vec::with_capacity(pseudo.len() * 4);
1984 for midx_pos in pseudo {
1985 ridx.extend_from_slice(&midx_pos.to_be_bytes());
1986 }
1987 chunks.push((*b"RIDX", ridx));
1988 }
1989 if let Some(bitmapped_packs) = bitmapped_packs {
1990 let mut btmp = Vec::with_capacity(bitmapped_packs.len() * 8);
1991 for pack in bitmapped_packs {
1992 btmp.extend_from_slice(&pack.bitmap_pos.to_be_bytes());
1993 btmp.extend_from_slice(&pack.bitmap_nr.to_be_bytes());
1994 }
1995 chunks.push((*b"BTMP", btmp));
1996 }
1997 write_multi_pack_index_chunks(format, version, pack_names.len() as u32, &chunks)
1998 }
1999
2000 pub fn parse(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
2001 Self::parse_impl(bytes, format, true)
2002 }
2003
2004 pub fn parse_without_checksum(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
2005 Self::parse_impl(bytes, format, false)
2006 }
2007
2008 pub(crate) fn parse_impl(
2009 bytes: &[u8],
2010 format: ObjectFormat,
2011 verify_checksum: bool,
2012 ) -> Result<Self> {
2013 let hash_len = format.raw_len();
2014 if bytes.len() < 12 + 12 + hash_len {
2015 return Err(GitError::InvalidFormat(
2016 "multi-pack-index file too short".into(),
2017 ));
2018 }
2019 if &bytes[..4] != b"MIDX" {
2020 return Err(GitError::InvalidFormat(
2021 "missing multi-pack-index signature".into(),
2022 ));
2023 }
2024 let version = bytes[4];
2025 if version != 1 && version != 2 {
2026 return Err(GitError::Unsupported(format!(
2027 "multi-pack-index version {version}"
2028 )));
2029 }
2030 let hash_id = bytes[5];
2031 if u32::from(hash_id) != hash_function_id(format) {
2032 return Err(GitError::InvalidFormat(format!(
2033 "multi-pack-index hash id {hash_id} does not match {}",
2034 format.name()
2035 )));
2036 }
2037 let chunk_count = bytes[6] as usize;
2038 let base_midx_count = bytes[7];
2039 if base_midx_count != 0 {
2040 return Err(GitError::Unsupported(format!(
2041 "multi-pack-index base count {base_midx_count}"
2042 )));
2043 }
2044 let pack_count = u32_be(&bytes[8..12]);
2045 let lookup_len = (chunk_count + 1)
2046 .checked_mul(12)
2047 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index lookup overflow".into()))?;
2048 let data_start = 12usize
2049 .checked_add(lookup_len)
2050 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index lookup overflow".into()))?;
2051 let checksum_offset = bytes.len() - hash_len;
2052 if data_start > checksum_offset {
2053 return Err(GitError::InvalidFormat(
2054 "truncated multi-pack-index chunk lookup".into(),
2055 ));
2056 }
2057
2058 let checksum = ObjectId::from_raw(format, &bytes[checksum_offset..])?;
2059 if verify_checksum {
2060 let actual_checksum = sley_core::digest_bytes(format, &bytes[..checksum_offset])?;
2061 if actual_checksum != checksum {
2062 return Err(GitError::InvalidFormat(format!(
2063 "multi-pack-index checksum mismatch: expected {checksum}, got {actual_checksum}"
2064 )));
2065 }
2066 }
2067
2068 let mut entries = Vec::with_capacity(chunk_count + 1);
2069 let mut offset = 12usize;
2070 for _ in 0..=chunk_count {
2071 let id = [
2072 bytes[offset],
2073 bytes[offset + 1],
2074 bytes[offset + 2],
2075 bytes[offset + 3],
2076 ];
2077 let chunk_offset = u64_be(&bytes[offset + 4..offset + 12]);
2078 entries.push((id, chunk_offset));
2079 offset += 12;
2080 }
2081 let Some((terminator_id, terminator_offset)) = entries.last().copied() else {
2082 return Err(GitError::InvalidFormat(
2083 "multi-pack-index chunk lookup is empty".into(),
2084 ));
2085 };
2086 if terminator_id != [0, 0, 0, 0] {
2087 return Err(GitError::InvalidFormat(
2088 "multi-pack-index chunk lookup missing terminator".into(),
2089 ));
2090 }
2091 if terminator_offset != checksum_offset as u64 {
2092 return Err(GitError::InvalidFormat(
2093 "multi-pack-index terminator does not point at checksum".into(),
2094 ));
2095 }
2096
2097 let mut chunks = Vec::with_capacity(chunk_count);
2098 let mut previous_offset = data_start as u64;
2099 let mut reported_unaligned = false;
2100 for pair in entries.windows(2) {
2101 let (id, chunk_offset) = pair[0];
2102 let (_next_id, next_offset) = pair[1];
2103 if id == [0, 0, 0, 0] {
2104 return Err(GitError::InvalidFormat(
2105 "multi-pack-index chunk id is zero before terminator".into(),
2106 ));
2107 }
2108 if chunk_offset < data_start as u64 || chunk_offset < previous_offset {
2109 return Err(GitError::InvalidFormat(
2110 "multi-pack-index chunk offsets are not monotonic".into(),
2111 ));
2112 }
2113 if chunk_offset % 4 != 0 && !reported_unaligned {
2114 eprintln!(
2115 "error: chunk id {:08x} not 4-byte aligned",
2116 u32::from_be_bytes(id)
2117 );
2118 reported_unaligned = true;
2119 }
2120 if next_offset < chunk_offset || next_offset > checksum_offset as u64 {
2121 return Err(GitError::InvalidFormat(
2122 "multi-pack-index chunk length is invalid".into(),
2123 ));
2124 }
2125 chunks.push(MultiPackIndexChunk {
2126 id,
2127 offset: chunk_offset,
2128 len: next_offset - chunk_offset,
2129 });
2130 previous_offset = chunk_offset;
2131 }
2132
2133 let pack_names = parse_midx_pack_names(bytes, &chunks, pack_count as usize, version)?;
2134 let (fanout, object_count) = parse_midx_oid_fanout(bytes, &chunks)?;
2135 let object_ids = parse_midx_object_ids(bytes, &chunks, format, object_count, &fanout)?;
2136 let objects = parse_midx_object_offsets(bytes, &chunks, object_ids, pack_count)?;
2137 let reverse_index = parse_midx_reverse_index(bytes, &chunks, object_count)?;
2138 let bitmapped_packs =
2139 parse_midx_bitmapped_packs(bytes, &chunks, pack_count as usize, object_count)?;
2140
2141 Ok(Self {
2142 version,
2143 format,
2144 pack_count,
2145 pack_names,
2146 object_count: object_count as u32,
2147 fanout,
2148 objects,
2149 reverse_index,
2150 bitmapped_packs,
2151 chunks,
2152 checksum,
2153 })
2154 }
2155
2156 pub fn find(&self, oid: &ObjectId) -> Option<&MultiPackIndexEntry> {
2157 self.objects
2158 .binary_search_by(|entry| entry.oid.as_bytes().cmp(oid.as_bytes()))
2159 .ok()
2160 .map(|idx| &self.objects[idx])
2161 }
2162}
2163
2164impl MultiPackIndexOidLookup {
2165 pub fn parse(bytes: Arc<dyn PackIndexByteSource>, format: ObjectFormat) -> Result<Self> {
2166 let raw = bytes.as_bytes();
2167 let hash_len = format.raw_len();
2168 if raw.len() < 12 + 12 + hash_len {
2169 return Err(GitError::InvalidFormat(
2170 "multi-pack-index file too short".into(),
2171 ));
2172 }
2173 if &raw[..4] != b"MIDX" {
2174 return Err(GitError::InvalidFormat(
2175 "missing multi-pack-index signature".into(),
2176 ));
2177 }
2178 let version = raw[4];
2179 if version != 1 && version != 2 {
2180 return Err(GitError::Unsupported(format!(
2181 "multi-pack-index version {version}"
2182 )));
2183 }
2184 let hash_id = raw[5];
2185 if u32::from(hash_id) != hash_function_id(format) {
2186 return Err(GitError::InvalidFormat(format!(
2187 "multi-pack-index hash id {hash_id} does not match {}",
2188 format.name()
2189 )));
2190 }
2191 let chunk_count = raw[6] as usize;
2192 let base_midx_count = raw[7];
2193 if base_midx_count != 0 {
2194 return Err(GitError::Unsupported(format!(
2195 "multi-pack-index base count {base_midx_count}"
2196 )));
2197 }
2198 let pack_count = u32_be(&raw[8..12]);
2199 let lookup_len = (chunk_count + 1)
2200 .checked_mul(12)
2201 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index lookup overflow".into()))?;
2202 let data_start = 12usize
2203 .checked_add(lookup_len)
2204 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index lookup overflow".into()))?;
2205 let checksum_offset = raw.len() - hash_len;
2206 if data_start > checksum_offset {
2207 return Err(GitError::InvalidFormat(
2208 "truncated multi-pack-index chunk lookup".into(),
2209 ));
2210 }
2211
2212 let mut entries = Vec::with_capacity(chunk_count + 1);
2213 let mut offset = 12usize;
2214 for _ in 0..=chunk_count {
2215 let id = [
2216 raw[offset],
2217 raw[offset + 1],
2218 raw[offset + 2],
2219 raw[offset + 3],
2220 ];
2221 let chunk_offset = u64_be(&raw[offset + 4..offset + 12]);
2222 entries.push((id, chunk_offset));
2223 offset += 12;
2224 }
2225 let Some((terminator_id, terminator_offset)) = entries.last().copied() else {
2226 return Err(GitError::InvalidFormat(
2227 "multi-pack-index chunk lookup is empty".into(),
2228 ));
2229 };
2230 if terminator_id != [0, 0, 0, 0] {
2231 return Err(GitError::InvalidFormat(
2232 "multi-pack-index chunk lookup missing terminator".into(),
2233 ));
2234 }
2235 if terminator_offset != checksum_offset as u64 {
2236 return Err(GitError::InvalidFormat(
2237 "multi-pack-index terminator does not point at checksum".into(),
2238 ));
2239 }
2240
2241 let mut chunks = Vec::with_capacity(chunk_count);
2242 let mut previous_offset = data_start as u64;
2243 let mut reported_unaligned = false;
2244 for pair in entries.windows(2) {
2245 let (id, chunk_offset) = pair[0];
2246 let (_next_id, next_offset) = pair[1];
2247 if id == [0, 0, 0, 0] {
2248 return Err(GitError::InvalidFormat(
2249 "multi-pack-index chunk id is zero before terminator".into(),
2250 ));
2251 }
2252 if chunk_offset < data_start as u64 || chunk_offset < previous_offset {
2253 return Err(GitError::InvalidFormat(
2254 "multi-pack-index chunk offsets are not monotonic".into(),
2255 ));
2256 }
2257 if chunk_offset % 4 != 0 && !reported_unaligned {
2258 eprintln!(
2259 "error: chunk id {:08x} not 4-byte aligned",
2260 u32::from_be_bytes(id)
2261 );
2262 reported_unaligned = true;
2263 }
2264 if next_offset < chunk_offset || next_offset > checksum_offset as u64 {
2265 return Err(GitError::InvalidFormat(
2266 "multi-pack-index chunk length is invalid".into(),
2267 ));
2268 }
2269 chunks.push(MultiPackIndexChunk {
2270 id,
2271 offset: chunk_offset,
2272 len: next_offset - chunk_offset,
2273 });
2274 previous_offset = chunk_offset;
2275 }
2276
2277 let pack_names = parse_midx_pack_names(raw, &chunks, pack_count as usize, version)?;
2278 let (fanout, object_count) = parse_midx_oid_fanout(raw, &chunks)?;
2279 let oid_lookup = midx_chunk_data(raw, &chunks, *b"OIDL", true)?
2280 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index missing OIDL chunk".into()))?;
2281 let expected_len = object_count.checked_mul(hash_len).ok_or_else(|| {
2282 GitError::InvalidFormat("multi-pack-index OIDL chunk overflow".into())
2283 })?;
2284 if oid_lookup.len() != expected_len {
2285 return Err(GitError::InvalidFormat(
2286 "error: multi-pack-index OID lookup chunk is the wrong size\nfatal: multi-pack-index required OID lookup chunk missing or corrupted".into(),
2287 ));
2288 }
2289 let object_offsets = midx_chunk_data(raw, &chunks, *b"OOFF", true)?
2290 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index missing OOFF chunk".into()))?;
2291 let expected_offsets_len = object_count.checked_mul(8).ok_or_else(|| {
2292 GitError::InvalidFormat("multi-pack-index OOFF chunk overflow".into())
2293 })?;
2294 if object_offsets.len() != expected_offsets_len {
2295 return Err(GitError::InvalidFormat(
2296 "error: multi-pack-index object offset chunk is the wrong size\nfatal: multi-pack-index required object offsets chunk missing or corrupted".into(),
2297 ));
2298 }
2299 let large_offsets = midx_chunk_data(raw, &chunks, *b"LOFF", false)?;
2300 if let Some(large_offsets) = large_offsets
2301 && large_offsets.len() % 8 != 0
2302 {
2303 return Err(GitError::InvalidFormat(
2304 "multi-pack-index LOFF chunk has invalid length".into(),
2305 ));
2306 }
2307 let oid_lookup_offset = oid_lookup.as_ptr() as usize - raw.as_ptr() as usize;
2308 let object_offsets_offset = object_offsets.as_ptr() as usize - raw.as_ptr() as usize;
2309 let (large_offsets_offset, large_offsets_len) = match large_offsets {
2310 Some(large_offsets) => (
2311 Some(large_offsets.as_ptr() as usize - raw.as_ptr() as usize),
2312 large_offsets.len(),
2313 ),
2314 None => (None, 0),
2315 };
2316 Ok(Self {
2317 format,
2318 pack_count,
2319 pack_names,
2320 fanout,
2321 object_count,
2322 oid_lookup_offset,
2323 object_offsets_offset,
2324 large_offsets_offset,
2325 large_offsets_len,
2326 bytes,
2327 })
2328 }
2329
2330 pub fn contains(&self, oid: &ObjectId) -> bool {
2331 self.find_position(oid).is_some()
2332 }
2333
2334 pub fn find(&self, oid: &ObjectId) -> Result<Option<MultiPackIndexEntry>> {
2335 let Some(position) = self.find_position(oid) else {
2336 return Ok(None);
2337 };
2338 let bytes = self.bytes.as_bytes();
2339 let hash_len = self.format.raw_len();
2340 let oid_start = self
2341 .oid_lookup_offset
2342 .checked_add(position * hash_len)
2343 .ok_or_else(|| {
2344 GitError::InvalidFormat("multi-pack-index OIDL offset overflow".into())
2345 })?;
2346 let oid = ObjectId::from_raw(self.format, &bytes[oid_start..oid_start + hash_len])?;
2347 let offset_start = self
2348 .object_offsets_offset
2349 .checked_add(position * 8)
2350 .ok_or_else(|| {
2351 GitError::InvalidFormat("multi-pack-index OOFF offset overflow".into())
2352 })?;
2353 let data = &bytes[offset_start..offset_start + 8];
2354 let pack_int_id = u32_be(&data[..4]);
2355 if pack_int_id >= self.pack_count {
2356 return Err(GitError::InvalidFormat(
2357 "multi-pack-index object points past pack table".into(),
2358 ));
2359 }
2360 let raw_offset = u32_be(&data[4..8]);
2361 let offset = if raw_offset & 0x8000_0000 == 0 {
2362 u64::from(raw_offset)
2363 } else {
2364 let Some(large_offsets_offset) = self.large_offsets_offset else {
2365 return Err(GitError::InvalidFormat(
2366 "multi-pack-index large offset missing LOFF chunk".into(),
2367 ));
2368 };
2369 let large_idx = (raw_offset & 0x7fff_ffff) as usize;
2370 let large_start = large_idx.checked_mul(8).ok_or_else(|| {
2371 GitError::InvalidFormat("multi-pack-index LOFF index overflow".into())
2372 })?;
2373 let large_end = large_start.checked_add(8).ok_or_else(|| {
2374 GitError::InvalidFormat("multi-pack-index LOFF index overflow".into())
2375 })?;
2376 if large_end > self.large_offsets_len {
2377 return Err(GitError::InvalidFormat(
2378 "fatal: multi-pack-index large offset out of bounds".into(),
2379 ));
2380 }
2381 let start = large_offsets_offset + large_start;
2382 u64_be(&bytes[start..start + 8])
2383 };
2384 Ok(Some(MultiPackIndexEntry {
2385 oid,
2386 pack_int_id,
2387 offset,
2388 force_large_offset: raw_offset & 0x8000_0000 != 0,
2389 }))
2390 }
2391
2392 pub fn pack_name(&self, pack_int_id: u32) -> Option<&str> {
2393 self.pack_names
2394 .get(pack_int_id as usize)
2395 .map(String::as_str)
2396 }
2397
2398 pub(crate) fn find_position(&self, oid: &ObjectId) -> Option<usize> {
2399 if oid.format() != self.format || self.object_count == 0 {
2400 return None;
2401 }
2402 let first = oid.as_bytes()[0] as usize;
2403 let start = if first == 0 {
2404 0
2405 } else {
2406 self.fanout[first - 1] as usize
2407 };
2408 let end = self.fanout[first] as usize;
2409 if start >= end || end > self.object_count {
2410 return None;
2411 }
2412 let hash_len = self.format.raw_len();
2413 let table_start = self.oid_lookup_offset;
2414 let table_end = table_start + self.object_count * hash_len;
2415 let bytes = self.bytes.as_bytes();
2416 let table = &bytes[table_start..table_end];
2417 let needle = oid.as_bytes();
2418 let mut low = start;
2419 let mut high = end;
2420 while low < high {
2421 let mid = low + (high - low) / 2;
2422 let raw = &table[mid * hash_len..(mid + 1) * hash_len];
2423 match raw.cmp(needle) {
2424 std::cmp::Ordering::Less => low = mid + 1,
2425 std::cmp::Ordering::Equal => return Some(mid),
2426 std::cmp::Ordering::Greater => high = mid,
2427 }
2428 }
2429 None
2430 }
2431}
2432
2433pub(crate) fn validate_midx_pack_names(pack_names: &[String]) -> Result<()> {
2434 for name in pack_names {
2435 if name.is_empty() {
2436 return Err(GitError::InvalidFormat(
2437 "multi-pack-index pack name is empty".into(),
2438 ));
2439 }
2440 if name
2441 .bytes()
2442 .any(|byte| byte == 0 || matches!(byte, b'/' | b'\\'))
2443 {
2444 return Err(GitError::InvalidFormat(
2445 "multi-pack-index pack name contains an invalid byte".into(),
2446 ));
2447 }
2448 }
2449 Ok(())
2450}
2451
2452pub(crate) fn write_midx_pack_names(pack_names: &[String]) -> Vec<u8> {
2453 let mut out = Vec::new();
2454 for name in pack_names {
2455 out.extend_from_slice(name.as_bytes());
2456 out.push(0);
2457 }
2458 while out.len() % 4 != 0 {
2459 out.push(0);
2460 }
2461 out
2462}
2463
2464pub(crate) fn write_midx_oid_fanout(objects: &[&MultiPackIndexEntry]) -> Result<Vec<u8>> {
2465 let mut counts = [0u32; 256];
2466 for object in objects {
2467 let first = object.oid.as_bytes()[0] as usize;
2468 counts[first] = counts[first]
2469 .checked_add(1)
2470 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index fanout overflow".into()))?;
2471 }
2472 let mut running = 0u32;
2473 let mut out = Vec::with_capacity(256 * 4);
2474 for count in counts {
2475 running = running
2476 .checked_add(count)
2477 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index fanout overflow".into()))?;
2478 out.extend_from_slice(&running.to_be_bytes());
2479 }
2480 Ok(out)
2481}
2482
2483pub(crate) fn write_midx_oid_lookup(objects: &[&MultiPackIndexEntry]) -> Vec<u8> {
2484 let mut out = Vec::new();
2485 for object in objects {
2486 out.extend_from_slice(object.oid.as_bytes());
2487 }
2488 out
2489}
2490
2491pub(crate) fn write_midx_object_offsets(
2492 objects: &[&MultiPackIndexEntry],
2493 large_offsets: &mut Vec<u8>,
2494) -> Result<Vec<u8>> {
2495 let mut out = Vec::new();
2496 for object in objects {
2497 out.extend_from_slice(&object.pack_int_id.to_be_bytes());
2498 if object.offset < 0x8000_0000 && !object.force_large_offset {
2499 out.extend_from_slice(&(object.offset as u32).to_be_bytes());
2500 } else {
2501 let large_idx = large_offsets.len() / 8;
2502 if large_idx > 0x7fff_ffff {
2503 return Err(GitError::InvalidFormat(
2504 "too many multi-pack-index large offsets".into(),
2505 ));
2506 }
2507 out.extend_from_slice(&(0x8000_0000 | large_idx as u32).to_be_bytes());
2508 large_offsets.extend_from_slice(&object.offset.to_be_bytes());
2509 }
2510 }
2511 Ok(out)
2512}
2513
2514pub(crate) fn write_multi_pack_index_chunks(
2515 format: ObjectFormat,
2516 version: u8,
2517 pack_count: u32,
2518 chunks: &[([u8; 4], Vec<u8>)],
2519) -> Result<Vec<u8>> {
2520 if chunks.len() > u8::MAX as usize {
2521 return Err(GitError::InvalidFormat(
2522 "too many multi-pack-index chunks".into(),
2523 ));
2524 }
2525 let lookup_len = (chunks.len() + 1)
2526 .checked_mul(12)
2527 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index lookup overflow".into()))?;
2528 let mut out = Vec::new();
2529 out.extend_from_slice(b"MIDX");
2530 out.push(version);
2531 out.push(hash_function_id(format) as u8);
2532 out.push(chunks.len() as u8);
2533 out.push(0);
2534 out.extend_from_slice(&pack_count.to_be_bytes());
2535 let mut chunk_offset = (12usize)
2536 .checked_add(lookup_len)
2537 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index lookup overflow".into()))?
2538 as u64;
2539 for (id, data) in chunks {
2540 out.extend_from_slice(id);
2541 out.extend_from_slice(&chunk_offset.to_be_bytes());
2542 chunk_offset = chunk_offset
2543 .checked_add(data.len() as u64)
2544 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index size overflow".into()))?;
2545 }
2546 out.extend_from_slice(&[0, 0, 0, 0]);
2547 out.extend_from_slice(&chunk_offset.to_be_bytes());
2548 for (_id, data) in chunks {
2549 out.extend_from_slice(data);
2550 }
2551 let checksum = sley_core::digest_bytes(format, &out)?;
2552 out.extend_from_slice(checksum.as_bytes());
2553 Ok(out)
2554}
2555pub(crate) fn read_pack_index_fanout(bytes: &[u8], offset: &mut usize) -> Result<[u32; 256]> {
2556 let mut fanout = [0u32; 256];
2557 let mut previous = 0u32;
2558 for slot in &mut fanout {
2559 *slot = u32_be(&bytes[*offset..*offset + 4]);
2560 if *slot < previous {
2561 return Err(GitError::InvalidFormat(
2562 "pack index fanout is not monotonic".into(),
2563 ));
2564 }
2565 previous = *slot;
2566 *offset += 4;
2567 }
2568 Ok(fanout)
2569}
2570
2571pub(crate) fn validate_pack_index_oid_fanout(
2572 idx: usize,
2573 oid_bytes: &[u8],
2574 fanout: &[u32; 256],
2575) -> Result<()> {
2576 let expected_min = if oid_bytes[0] == 0 {
2577 0
2578 } else {
2579 fanout[usize::from(oid_bytes[0] - 1)]
2580 };
2581 if (idx as u32) < expected_min || (idx as u32) >= fanout[usize::from(oid_bytes[0])] {
2582 return Err(GitError::InvalidFormat(
2583 "pack index object id is outside its fanout bucket".into(),
2584 ));
2585 }
2586 Ok(())
2587}
2588
2589pub(crate) fn pack_index_v2_offset(raw_offset: u32, large_offset_table: &[u8]) -> Result<u64> {
2590 if raw_offset & 0x8000_0000 == 0 {
2591 return Ok(u64::from(raw_offset));
2592 }
2593 let large_idx = (raw_offset & 0x7fff_ffff) as usize;
2594 let large_start = large_idx
2595 .checked_mul(8)
2596 .ok_or_else(|| GitError::InvalidFormat("pack index large offset overflow".into()))?;
2597 let large_end = large_start
2598 .checked_add(8)
2599 .ok_or_else(|| GitError::InvalidFormat("pack index large offset overflow".into()))?;
2600 if large_end > large_offset_table.len() {
2601 return Err(GitError::InvalidFormat(
2602 "pack index large offset points past table".into(),
2603 ));
2604 }
2605 Ok(u64_be(&large_offset_table[large_start..large_end]))
2606}
2607pub(crate) fn parse_midx_pack_names(
2608 bytes: &[u8],
2609 chunks: &[MultiPackIndexChunk],
2610 pack_count: usize,
2611 version: u8,
2612) -> Result<Vec<String>> {
2613 let data = midx_chunk_data(bytes, chunks, *b"PNAM", true)?
2614 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index missing PNAM chunk".into()))?;
2615 let mut names = Vec::with_capacity(pack_count);
2616 let mut offset = 0usize;
2617 while names.len() < pack_count {
2618 let Some(relative_end) = data[offset..].iter().position(|byte| *byte == 0) else {
2619 return Err(GitError::InvalidFormat(
2620 "fatal: multi-pack-index pack-name chunk is too short".into(),
2621 ));
2622 };
2623 let name_bytes = &data[offset..offset + relative_end];
2624 if name_bytes.is_empty() {
2625 return Err(GitError::InvalidFormat(
2626 "multi-pack-index PNAM entry is empty".into(),
2627 ));
2628 }
2629 let name = std::str::from_utf8(name_bytes)
2630 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
2631 if name.bytes().any(|byte| matches!(byte, b'/' | b'\\')) {
2632 return Err(GitError::InvalidFormat(
2633 "multi-pack-index PNAM entry contains a path separator".into(),
2634 ));
2635 }
2636 names.push(name.to_string());
2637 offset += relative_end + 1;
2638 }
2639 let padding = &data[offset..];
2640 if padding.len() > 3 || padding.iter().any(|byte| *byte != 0) {
2641 return Err(GitError::InvalidFormat(
2642 "multi-pack-index PNAM padding is invalid".into(),
2643 ));
2644 }
2645 if version == 1 && names.windows(2).any(|pair| pair[0] > pair[1]) {
2646 return Err(GitError::InvalidFormat(
2647 "multi-pack-index v1 PNAM entries are not sorted".into(),
2648 ));
2649 }
2650 Ok(names)
2651}
2652
2653pub(crate) fn parse_midx_oid_fanout(
2654 bytes: &[u8],
2655 chunks: &[MultiPackIndexChunk],
2656) -> Result<([u32; 256], usize)> {
2657 let data = midx_chunk_data(bytes, chunks, *b"OIDF", true)?
2658 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index missing OIDF chunk".into()))?;
2659 if data.len() != 256 * 4 {
2660 return Err(GitError::InvalidFormat(
2661 "error: multi-pack-index OID fanout is of the wrong size\nfatal: multi-pack-index required OID fanout chunk missing or corrupted".into(),
2662 ));
2663 }
2664 let mut fanout = [0u32; 256];
2665 let mut previous = 0u32;
2666 for (idx, slot) in fanout.iter_mut().enumerate() {
2667 let start = idx * 4;
2668 *slot = u32_be(&data[start..start + 4]);
2669 if *slot < previous {
2670 return Err(GitError::InvalidFormat(format!(
2671 "error: oid fanout out of order: fanout[{}] = {:x} > {:x} = fanout[{idx}]\nfatal: multi-pack-index required OID fanout chunk missing or corrupted",
2672 idx - 1,
2673 previous,
2674 *slot
2675 )));
2676 }
2677 previous = *slot;
2678 }
2679 Ok((fanout, fanout[255] as usize))
2680}
2681
2682pub(crate) fn parse_midx_object_ids(
2683 bytes: &[u8],
2684 chunks: &[MultiPackIndexChunk],
2685 format: ObjectFormat,
2686 object_count: usize,
2687 fanout: &[u32; 256],
2688) -> Result<Vec<ObjectId>> {
2689 let data = midx_chunk_data(bytes, chunks, *b"OIDL", true)?
2690 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index missing OIDL chunk".into()))?;
2691 let expected_len = object_count
2692 .checked_mul(format.raw_len())
2693 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index OIDL chunk overflow".into()))?;
2694 if data.len() != expected_len {
2695 return Err(GitError::InvalidFormat(
2696 "error: multi-pack-index OID lookup chunk is the wrong size\nfatal: multi-pack-index required OID lookup chunk missing or corrupted".into(),
2697 ));
2698 }
2699
2700 let mut ids = Vec::with_capacity(object_count);
2701 let mut counts = [0u32; 256];
2702 let mut previous_oid: Option<ObjectId> = None;
2703 for idx in 0..object_count {
2704 let start = idx * format.raw_len();
2705 let oid = ObjectId::from_raw(format, &data[start..start + format.raw_len()])?;
2706 if let Some(previous) = &previous_oid
2707 && previous.as_bytes() >= oid.as_bytes()
2708 {
2709 return Err(GitError::InvalidFormat(
2710 "multi-pack-index OIDL object ids are not strictly sorted".into(),
2711 ));
2712 }
2713 counts[oid.as_bytes()[0] as usize] = counts[oid.as_bytes()[0] as usize]
2714 .checked_add(1)
2715 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index fanout overflow".into()))?;
2716 previous_oid = Some(oid);
2717 ids.push(oid);
2718 }
2719
2720 let mut running = 0u32;
2721 for (idx, count) in counts.iter().enumerate() {
2722 running = running
2723 .checked_add(*count)
2724 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index fanout overflow".into()))?;
2725 if fanout[idx] != running {
2726 return Err(GitError::InvalidFormat(
2727 "multi-pack-index OIDF fanout does not match OIDL".into(),
2728 ));
2729 }
2730 }
2731 Ok(ids)
2732}
2733
2734pub(crate) fn parse_midx_object_offsets(
2735 bytes: &[u8],
2736 chunks: &[MultiPackIndexChunk],
2737 object_ids: Vec<ObjectId>,
2738 pack_count: u32,
2739) -> Result<Vec<MultiPackIndexEntry>> {
2740 let data = midx_chunk_data(bytes, chunks, *b"OOFF", true)?
2741 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index missing OOFF chunk".into()))?;
2742 let expected_len = object_ids
2743 .len()
2744 .checked_mul(8)
2745 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index OOFF chunk overflow".into()))?;
2746 if data.len() != expected_len {
2747 return Err(GitError::InvalidFormat(
2748 "error: multi-pack-index object offset chunk is the wrong size\nfatal: multi-pack-index required object offsets chunk missing or corrupted".into(),
2749 ));
2750 }
2751 let large_offsets = midx_chunk_data(bytes, chunks, *b"LOFF", false)?;
2752 if let Some(large_offsets) = large_offsets
2753 && large_offsets.len() % 8 != 0
2754 {
2755 return Err(GitError::InvalidFormat(
2756 "multi-pack-index LOFF chunk has invalid length".into(),
2757 ));
2758 }
2759
2760 let mut entries = Vec::with_capacity(object_ids.len());
2761 for (idx, oid) in object_ids.into_iter().enumerate() {
2762 let start = idx * 8;
2763 let pack_int_id = u32_be(&data[start..start + 4]);
2764 if pack_int_id >= pack_count {
2765 return Err(GitError::InvalidFormat(
2766 "multi-pack-index object points past pack table".into(),
2767 ));
2768 }
2769 let raw_offset = u32_be(&data[start + 4..start + 8]);
2770 let offset = if raw_offset & 0x8000_0000 == 0 {
2771 u64::from(raw_offset)
2772 } else {
2773 let Some(large_offsets) = large_offsets else {
2774 return Err(GitError::InvalidFormat(
2775 "multi-pack-index large offset missing LOFF chunk".into(),
2776 ));
2777 };
2778 let large_idx = (raw_offset & 0x7fff_ffff) as usize;
2779 let large_start = large_idx.checked_mul(8).ok_or_else(|| {
2780 GitError::InvalidFormat("multi-pack-index LOFF index overflow".into())
2781 })?;
2782 let large_end = large_start.checked_add(8).ok_or_else(|| {
2783 GitError::InvalidFormat("multi-pack-index LOFF index overflow".into())
2784 })?;
2785 if large_end > large_offsets.len() {
2786 return Err(GitError::InvalidFormat(
2787 "fatal: multi-pack-index large offset out of bounds".into(),
2788 ));
2789 }
2790 u64_be(&large_offsets[large_start..large_end])
2791 };
2792 entries.push(MultiPackIndexEntry {
2793 oid,
2794 pack_int_id,
2795 offset,
2796 force_large_offset: raw_offset & 0x8000_0000 != 0,
2797 });
2798 }
2799 Ok(entries)
2800}
2801
2802pub(crate) fn parse_midx_reverse_index(
2803 bytes: &[u8],
2804 chunks: &[MultiPackIndexChunk],
2805 object_count: usize,
2806) -> Result<Option<Vec<u32>>> {
2807 let Some(data) = midx_chunk_data(bytes, chunks, *b"RIDX", false)? else {
2808 return Ok(None);
2809 };
2810 let expected_len = object_count
2811 .checked_mul(4)
2812 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index RIDX chunk overflow".into()))?;
2813 if data.len() != expected_len {
2814 return Err(GitError::InvalidFormat(
2815 "multi-pack-index reverse-index chunk is the wrong size".into(),
2816 ));
2817 }
2818 let mut positions = Vec::with_capacity(object_count);
2819 for idx in 0..object_count {
2820 let start = idx * 4;
2821 positions.push(u32_be(&data[start..start + 4]));
2822 }
2823 validate_position_permutation(&positions)?;
2824 Ok(Some(positions))
2825}
2826
2827pub(crate) fn parse_midx_bitmapped_packs(
2828 bytes: &[u8],
2829 chunks: &[MultiPackIndexChunk],
2830 pack_count: usize,
2831 object_count: usize,
2832) -> Result<Option<Vec<MultiPackBitmapPack>>> {
2833 let Some(data) = midx_chunk_data(bytes, chunks, *b"BTMP", false)? else {
2834 return Ok(None);
2835 };
2836 let expected_len = pack_count
2837 .checked_mul(8)
2838 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index BTMP chunk overflow".into()))?;
2839 if data.len() != expected_len {
2840 return Err(GitError::InvalidFormat(
2841 "multi-pack-index BTMP chunk has invalid length".into(),
2842 ));
2843 }
2844 let mut entries = Vec::with_capacity(pack_count);
2845 for idx in 0..pack_count {
2846 let start = idx * 8;
2847 let bitmap_pos = u32_be(&data[start..start + 4]);
2848 let bitmap_nr = u32_be(&data[start + 4..start + 8]);
2849 let bitmap_end = u64::from(bitmap_pos)
2850 .checked_add(u64::from(bitmap_nr))
2851 .ok_or_else(|| {
2852 GitError::InvalidFormat("multi-pack-index BTMP range overflow".into())
2853 })?;
2854 if bitmap_end > object_count as u64 {
2855 return Err(GitError::InvalidFormat(
2856 "multi-pack-index BTMP range points past object table".into(),
2857 ));
2858 }
2859 entries.push(MultiPackBitmapPack {
2860 bitmap_pos,
2861 bitmap_nr,
2862 });
2863 }
2864 Ok(Some(entries))
2865}
2866
2867pub(crate) fn midx_chunk_data<'a>(
2868 bytes: &'a [u8],
2869 chunks: &[MultiPackIndexChunk],
2870 id: [u8; 4],
2871 required: bool,
2872) -> Result<Option<&'a [u8]>> {
2873 let Some(chunk) = chunks.iter().find(|chunk| chunk.id == id) else {
2874 if required {
2875 return Err(GitError::InvalidFormat(format!(
2876 "multi-pack-index missing {} chunk",
2877 std::str::from_utf8(&id).unwrap_or("required")
2878 )));
2879 }
2880 return Ok(None);
2881 };
2882 let start = usize::try_from(chunk.offset)
2883 .map_err(|_| GitError::InvalidFormat("multi-pack-index chunk offset overflow".into()))?;
2884 let len = usize::try_from(chunk.len)
2885 .map_err(|_| GitError::InvalidFormat("multi-pack-index chunk length overflow".into()))?;
2886 let end = start
2887 .checked_add(len)
2888 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index chunk range overflow".into()))?;
2889 let Some(data) = bytes.get(start..end) else {
2890 return Err(GitError::InvalidFormat(
2891 "multi-pack-index chunk extends past file".into(),
2892 ));
2893 };
2894 Ok(Some(data))
2895}
2896
2897pub(crate) fn hash_function_id(format: ObjectFormat) -> u32 {
2898 match format {
2899 ObjectFormat::Sha1 => 1,
2900 ObjectFormat::Sha256 => 2,
2901 }
2902}
2903
2904pub(crate) const EWAH_MAX_RUNNING_LEN: u64 = 0xffff_ffff;
2907
2908pub(crate) const EWAH_MAX_LITERAL_LEN: u64 = 0x7fff_ffff;
2911
2912pub(crate) const EWAH_ALL_ONES: u64 = u64::MAX;
2914
2915impl EwahBitmap {
2916 pub fn from_words(bit_size: u32, words: &[u64]) -> Result<Self> {
2930 let required_words = bit_size.div_ceil(64) as usize;
2931 if required_words > words.len() {
2932 return Err(GitError::InvalidFormat(format!(
2933 "EWAH bit_size {bit_size} requires {required_words} words but only {} supplied",
2934 words.len()
2935 )));
2936 }
2937 let significant = &words[..required_words];
2940 let mut builder = EwahBuilder::new(bit_size);
2941 for &word in significant {
2942 if word == 0 {
2943 builder.add_empty_words(false, 1);
2944 } else if word == EWAH_ALL_ONES {
2945 builder.add_empty_words(true, 1);
2946 } else {
2947 builder.add_literal(word);
2948 }
2949 }
2950 builder.finish()
2951 }
2952
2953 pub fn from_positions(bit_size: u32, positions: &[u32]) -> Result<Self> {
2959 let word_count = bit_size.div_ceil(64) as usize;
2960 let mut words = vec![0u64; word_count];
2961 for &position in positions {
2962 if position >= bit_size {
2963 return Err(GitError::InvalidFormat(format!(
2964 "EWAH bit position {position} out of range for bit_size {bit_size}"
2965 )));
2966 }
2967 let word_index = (position / 64) as usize;
2968 let bit_index = position % 64;
2969 words[word_index] |= 1u64 << bit_index;
2970 }
2971 Self::from_words(bit_size, &words)
2972 }
2973
2974 pub fn empty() -> Self {
2977 Self {
2978 bit_size: 0,
2979 words: Vec::new(),
2980 rlw_position: 0,
2981 }
2982 }
2983
2984 pub fn to_words(&self) -> Result<Vec<u64>> {
2990 let mut out = Vec::new();
2991 let mut word_idx = 0usize;
2992 while word_idx < self.words.len() {
2993 let rlw = self.words[word_idx];
2994 let run_bit = rlw & 1;
2995 let run_words = (rlw >> 1) & EWAH_MAX_RUNNING_LEN;
2996 let literal_words = (rlw >> 33) as usize;
2997 word_idx += 1;
2998 let fill = if run_bit == 1 { EWAH_ALL_ONES } else { 0 };
2999 for _ in 0..run_words {
3000 out.push(fill);
3001 }
3002 let literal_end = word_idx
3003 .checked_add(literal_words)
3004 .filter(|end| *end <= self.words.len())
3005 .ok_or_else(|| {
3006 GitError::InvalidFormat("EWAH literal words extend past word table".into())
3007 })?;
3008 out.extend_from_slice(&self.words[word_idx..literal_end]);
3009 word_idx = literal_end;
3010 }
3011 let required_words = (self.bit_size as usize).div_ceil(64);
3012 if out.len() < required_words {
3013 out.resize(required_words, 0);
3014 }
3015 out.truncate(required_words);
3016 Ok(out)
3017 }
3018
3019 pub fn to_positions(&self) -> Result<Vec<u32>> {
3021 let words = self.to_words()?;
3022 let mut positions = Vec::new();
3023 for (word_index, word) in words.iter().enumerate() {
3024 let mut remaining = *word;
3025 while remaining != 0 {
3026 let bit = remaining.trailing_zeros();
3027 let position = (word_index as u64) * 64 + u64::from(bit);
3028 if position < u64::from(self.bit_size) {
3029 positions.push(position as u32);
3031 }
3032 remaining &= remaining - 1;
3033 }
3034 }
3035 Ok(positions)
3036 }
3037
3038 pub fn to_bytes(&self) -> Vec<u8> {
3042 let mut out = Vec::with_capacity(12 + self.words.len() * 8);
3043 self.append_bytes(&mut out);
3044 out
3045 }
3046
3047 pub(crate) fn append_bytes(&self, out: &mut Vec<u8>) {
3048 out.extend_from_slice(&self.bit_size.to_be_bytes());
3049 out.extend_from_slice(&(self.words.len() as u32).to_be_bytes());
3050 for word in &self.words {
3051 out.extend_from_slice(&word.to_be_bytes());
3052 }
3053 out.extend_from_slice(&self.rlw_position.to_be_bytes());
3054 }
3055}
3056
3057pub(crate) struct EwahBuilder {
3065 bit_size: u32,
3066 words: Vec<u64>,
3067 rlw_position: usize,
3068}
3069
3070impl EwahBuilder {
3071 pub(crate) fn new(bit_size: u32) -> Self {
3072 Self {
3074 bit_size,
3075 words: vec![0u64],
3076 rlw_position: 0,
3077 }
3078 }
3079
3080 pub(crate) fn rlw(&self) -> u64 {
3081 self.words[self.rlw_position]
3082 }
3083
3084 pub(crate) fn set_rlw(&mut self, value: u64) {
3085 self.words[self.rlw_position] = value;
3086 }
3087
3088 pub(crate) fn rlw_running_len(&self) -> u64 {
3089 (self.rlw() >> 1) & EWAH_MAX_RUNNING_LEN
3090 }
3091
3092 pub(crate) fn rlw_running_bit(&self) -> bool {
3093 self.rlw() & 1 == 1
3094 }
3095
3096 pub(crate) fn rlw_literal_len(&self) -> u64 {
3097 self.rlw() >> 33
3098 }
3099
3100 pub(crate) fn set_running_bit(&mut self, bit: bool) {
3101 let mut value = self.rlw();
3102 value &= !1;
3103 value |= u64::from(bit);
3104 self.set_rlw(value);
3105 }
3106
3107 pub(crate) fn set_running_len(&mut self, len: u64) {
3108 let mut value = self.rlw();
3109 value &= !(EWAH_MAX_RUNNING_LEN << 1);
3110 value |= (len & EWAH_MAX_RUNNING_LEN) << 1;
3111 self.set_rlw(value);
3112 }
3113
3114 pub(crate) fn set_literal_len(&mut self, len: u64) {
3115 let mut value = self.rlw();
3116 value &= (1u64 << 33) - 1;
3117 value |= (len & EWAH_MAX_LITERAL_LEN) << 33;
3118 self.set_rlw(value);
3119 }
3120
3121 pub(crate) fn push_rlw(&mut self) {
3123 self.rlw_position = self.words.len();
3124 self.words.push(0);
3125 }
3126
3127 pub(crate) fn add_empty_words(&mut self, value: bool, mut number: u64) {
3135 while number > 0 {
3136 let can_extend = self.rlw_literal_len() == 0
3140 && (self.rlw_running_len() == 0 || self.rlw_running_bit() == value)
3141 && self.rlw_running_len() < EWAH_MAX_RUNNING_LEN;
3142 if !can_extend {
3143 self.push_rlw();
3144 }
3145 if self.rlw_running_len() == 0 {
3146 self.set_running_bit(value);
3147 }
3148 let available = EWAH_MAX_RUNNING_LEN - self.rlw_running_len();
3149 let take = available.min(number);
3150 self.set_running_len(self.rlw_running_len() + take);
3151 number -= take;
3152 }
3153 }
3154
3155 pub(crate) fn add_literal(&mut self, word: u64) {
3158 if self.rlw_literal_len() >= EWAH_MAX_LITERAL_LEN {
3159 self.push_rlw();
3160 }
3161 let literal_len = self.rlw_literal_len();
3162 self.set_literal_len(literal_len + 1);
3163 self.words.push(word);
3164 }
3165
3166 pub(crate) fn finish(self) -> Result<EwahBitmap> {
3167 let rlw_position = u32::try_from(self.rlw_position)
3168 .map_err(|_| GitError::InvalidFormat("EWAH RLW position overflow".into()))?;
3169 if self.words.len() > u32::MAX as usize {
3170 return Err(GitError::InvalidFormat("EWAH word table overflow".into()));
3171 }
3172 Ok(EwahBitmap {
3173 bit_size: self.bit_size,
3174 words: self.words,
3175 rlw_position,
3176 })
3177 }
3178}