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_sha1_with_limits(pack_bytes, PackReadLimits::default())
690 }
691
692 pub fn write_v2_for_pack_sha1_with_limits(
693 pack_bytes: &[u8],
694 limits: PackReadLimits,
695 ) -> Result<PackIndexBuild> {
696 Self::write_v2_for_pack_with_limits(pack_bytes, ObjectFormat::Sha1, limits)
697 }
698
699 pub fn write_v2_for_pack(pack_bytes: &[u8], format: ObjectFormat) -> Result<PackIndexBuild> {
700 Self::write_v2_for_pack_with_limits(pack_bytes, format, PackReadLimits::default())
701 }
702
703 pub fn write_v2_for_pack_with_limits(
704 pack_bytes: &[u8],
705 format: ObjectFormat,
706 limits: PackReadLimits,
707 ) -> Result<PackIndexBuild> {
708 Self::write_v2_for_pack_with_base_and_limits(pack_bytes, format, |_| Ok(None), limits)
709 }
710
711 pub fn write_v2_for_pack_with_base<F>(
715 pack_bytes: &[u8],
716 format: ObjectFormat,
717 external_base: F,
718 ) -> Result<PackIndexBuild>
719 where
720 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
721 {
722 Self::write_v2_for_pack_with_base_and_limits(
723 pack_bytes,
724 format,
725 external_base,
726 PackReadLimits::default(),
727 )
728 }
729
730 pub fn write_v2_for_pack_with_base_and_limits<F>(
731 pack_bytes: &[u8],
732 format: ObjectFormat,
733 mut external_base: F,
734 limits: PackReadLimits,
735 ) -> Result<PackIndexBuild>
736 where
737 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
738 {
739 let trailer_len = format.raw_len();
740 if pack_bytes.len() < 12 + trailer_len {
741 return Err(GitError::InvalidFormat("pack file too short".into()));
742 }
743 let trailer_offset = pack_bytes.len() - trailer_len;
744 let pack_checksum = sley_core::digest_bytes(format, &pack_bytes[..trailer_offset])?;
745 let expected = ObjectId::from_raw(format, &pack_bytes[trailer_offset..])?;
746 if pack_checksum != expected {
747 return Err(GitError::InvalidFormat(format!(
748 "pack checksum mismatch: expected {expected}, got {pack_checksum}"
749 )));
750 }
751
752 if &pack_bytes[..4] != b"PACK" {
753 return Err(GitError::InvalidFormat("missing PACK signature".into()));
754 }
755 let version = u32_be(&pack_bytes[4..8]);
756 if version != 2 && version != 3 {
757 return Err(GitError::Unsupported(format!("pack version {version}")));
758 }
759 let count = checked_pack_object_count(
762 u32_be(&pack_bytes[8..12]),
763 (trailer_offset.saturating_sub(12)) as u64,
764 )?;
765 let mut offset = 12usize;
766 let mut parsed_entries = Vec::with_capacity(pack_entry_prealloc(count));
767 let mut raw_entries = Vec::with_capacity(pack_entry_prealloc(count));
768 for _ in 0..count {
769 let entry_offset = offset;
770 let header = parse_entry_header(pack_bytes, &mut offset)?;
771 let base = match header.kind {
772 PackObjectKind::OfsDelta => Some(DeltaBase::Offset(parse_ofs_delta_base_offset(
773 pack_bytes,
774 &mut offset,
775 entry_offset as u64,
776 )?)),
777 PackObjectKind::RefDelta => {
778 let hash_len = format.raw_len();
779 if offset + hash_len > trailer_offset {
780 return Err(GitError::InvalidFormat(
781 "truncated ref-delta base object id".into(),
782 ));
783 }
784 let oid = ObjectId::from_raw(format, &pack_bytes[offset..offset + hash_len])?;
785 offset += hash_len;
786 Some(DeltaBase::Ref(oid))
787 }
788 _ => None,
789 };
790 let mut body = Vec::new();
791 let consumed = inflate_into(
792 &pack_bytes[offset..trailer_offset],
793 &mut body,
794 header.size.min(usize::MAX as u64) as usize,
795 )?;
796 if body.len() as u64 != header.size {
797 return Err(GitError::InvalidObject(format!(
798 "pack object declared {} bytes, decoded {}",
799 header.size,
800 body.len()
801 )));
802 }
803 if consumed == 0 {
804 return Err(GitError::InvalidFormat(
805 "empty compressed pack entry".into(),
806 ));
807 }
808 offset = offset
809 .checked_add(consumed)
810 .ok_or_else(|| GitError::InvalidFormat("pack offset overflow".into()))?;
811 if offset > trailer_offset {
812 return Err(GitError::InvalidFormat(
813 "pack entry extends past checksum".into(),
814 ));
815 }
816 raw_entries.push((
817 entry_offset as u64,
818 crc32fast::hash(&pack_bytes[entry_offset..offset]),
819 ));
820 if let Some(base) = base {
821 parsed_entries.push(ParsedPackEntry::Delta {
822 base,
823 compressed_size: consumed as u64,
824 delta_size: header.size,
825 offset: entry_offset as u64,
826 delta: body,
827 });
828 } else {
829 let object_type = match header.kind {
830 PackObjectKind::Commit => ObjectType::Commit,
831 PackObjectKind::Tree => ObjectType::Tree,
832 PackObjectKind::Blob => ObjectType::Blob,
833 PackObjectKind::Tag => ObjectType::Tag,
834 PackObjectKind::OfsDelta | PackObjectKind::RefDelta => unreachable!(),
835 };
836 let object = EncodedObject::new(object_type, body);
837 let oid = object.object_id(format)?;
838 parsed_entries.push(ParsedPackEntry::Resolved(PackObject {
839 entry: PackEntry {
840 oid,
841 compressed_size: consumed as u64,
842 uncompressed_size: header.size,
843 offset: entry_offset as u64,
844 },
845 object,
846 }));
847 }
848 }
849 if offset != trailer_offset {
850 return Err(GitError::InvalidFormat(format!(
851 "pack has {} trailing bytes before checksum",
852 trailer_offset - offset
853 )));
854 }
855
856 let resolved = resolve_pack_entries(parsed_entries, format, &mut external_base, limits)?;
857 let entries = resolved
858 .iter()
859 .zip(raw_entries)
860 .map(|(object, (offset, crc32))| PackIndexEntry {
861 oid: object.entry.oid,
862 crc32,
863 offset,
864 })
865 .collect::<Vec<_>>();
866 let index = PackIndex::write_v2(format, &entries, &pack_checksum)?;
867 Ok(PackIndexBuild {
868 index,
869 pack_checksum,
870 entries,
871 })
872 }
873
874 pub fn write_v2_for_pack_reader<R>(
881 reader: &mut R,
882 format: ObjectFormat,
883 ) -> Result<PackStreamIndexBuild>
884 where
885 R: Read + Seek,
886 {
887 Self::write_v2_for_pack_reader_with_limits(reader, format, PackReadLimits::default())
888 }
889
890 pub fn write_v2_for_pack_reader_with_base<R, F>(
892 reader: &mut R,
893 format: ObjectFormat,
894 external_base: F,
895 ) -> Result<PackStreamIndexBuild>
896 where
897 R: Read + Seek,
898 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
899 {
900 Self::write_v2_for_pack_reader_with_base_and_limits(
901 reader,
902 format,
903 external_base,
904 PackReadLimits::default(),
905 )
906 }
907
908 pub fn write_v2_for_pack_reader_with_limits<R>(
909 reader: &mut R,
910 format: ObjectFormat,
911 limits: PackReadLimits,
912 ) -> Result<PackStreamIndexBuild>
913 where
914 R: Read + Seek,
915 {
916 index_pack_from_reader_with_limits(reader, format, limits)
917 }
918
919 pub fn write_v2_for_pack_reader_with_base_and_limits<R, F>(
920 reader: &mut R,
921 format: ObjectFormat,
922 external_base: F,
923 limits: PackReadLimits,
924 ) -> Result<PackStreamIndexBuild>
925 where
926 R: Read + Seek,
927 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
928 {
929 index_pack_from_reader_with_base_and_limits(reader, format, external_base, limits)
930 }
931
932 pub fn write_v2_for_pack_reader_to_trailer<R>(
939 reader: &mut R,
940 format: ObjectFormat,
941 ) -> Result<PackStreamIndexBuild>
942 where
943 R: Read,
944 {
945 Self::write_v2_for_pack_reader_to_trailer_with_limits(
946 reader,
947 format,
948 PackReadLimits::default(),
949 )
950 }
951
952 pub fn write_v2_for_pack_reader_to_trailer_with_base<R, F>(
955 reader: &mut R,
956 format: ObjectFormat,
957 external_base: F,
958 ) -> Result<PackStreamIndexBuild>
959 where
960 R: Read,
961 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
962 {
963 Self::write_v2_for_pack_reader_to_trailer_with_base_and_limits(
964 reader,
965 format,
966 external_base,
967 PackReadLimits::default(),
968 )
969 }
970
971 pub fn write_v2_for_pack_reader_to_trailer_with_limits<R>(
972 reader: &mut R,
973 format: ObjectFormat,
974 limits: PackReadLimits,
975 ) -> Result<PackStreamIndexBuild>
976 where
977 R: Read,
978 {
979 index_pack_from_reader_to_trailer_with_limits(reader, format, limits)
980 }
981
982 pub fn write_v2_for_pack_reader_to_trailer_with_base_and_limits<R, F>(
983 reader: &mut R,
984 format: ObjectFormat,
985 external_base: F,
986 limits: PackReadLimits,
987 ) -> Result<PackStreamIndexBuild>
988 where
989 R: Read,
990 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
991 {
992 index_pack_from_reader_to_trailer_with_base_and_limits(
993 reader,
994 format,
995 external_base,
996 limits,
997 )
998 }
999
1000 pub fn write_v2_for_pack_reader_to_trailer_with_progress<R, F>(
1005 reader: &mut R,
1006 format: ObjectFormat,
1007 progress: F,
1008 ) -> Result<PackStreamIndexBuild>
1009 where
1010 R: Read,
1011 F: FnMut(PackStreamProgress),
1012 {
1013 Self::write_v2_for_pack_reader_to_trailer_with_progress_and_limits(
1014 reader,
1015 format,
1016 PackReadLimits::default(),
1017 progress,
1018 )
1019 }
1020
1021 pub fn write_v2_for_pack_reader_to_trailer_with_progress_and_limits<R, F>(
1022 reader: &mut R,
1023 format: ObjectFormat,
1024 limits: PackReadLimits,
1025 progress: F,
1026 ) -> Result<PackStreamIndexBuild>
1027 where
1028 R: Read,
1029 F: FnMut(PackStreamProgress),
1030 {
1031 index_pack_from_reader_to_trailer_with_progress_and_limits(reader, format, limits, progress)
1032 }
1033
1034 pub fn write_v2_for_pack_reader_to_trailer_with_progress_and_cancel<R, F>(
1042 reader: &mut R,
1043 format: ObjectFormat,
1044 cancel: CancelFlag<'_>,
1045 progress: F,
1046 ) -> Result<PackStreamIndexBuild>
1047 where
1048 R: Read,
1049 F: FnMut(PackStreamProgress),
1050 {
1051 Self::write_v2_for_pack_reader_to_trailer_with_progress_and_cancel_and_limits(
1052 reader,
1053 format,
1054 cancel,
1055 PackReadLimits::default(),
1056 progress,
1057 )
1058 }
1059
1060 pub fn write_v2_for_pack_reader_to_trailer_with_progress_and_cancel_and_limits<R, F>(
1061 reader: &mut R,
1062 format: ObjectFormat,
1063 cancel: CancelFlag<'_>,
1064 limits: PackReadLimits,
1065 progress: F,
1066 ) -> Result<PackStreamIndexBuild>
1067 where
1068 R: Read,
1069 F: FnMut(PackStreamProgress),
1070 {
1071 index_pack_from_reader_to_trailer_with_progress_and_cancel_and_limits(
1072 reader, format, cancel, limits, progress,
1073 )
1074 }
1075
1076 pub fn write_v2_for_pack_reader_with_len<R>(
1077 reader: &mut R,
1078 format: ObjectFormat,
1079 pack_len: u64,
1080 ) -> Result<PackStreamIndexBuild>
1081 where
1082 R: Read,
1083 {
1084 Self::write_v2_for_pack_reader_with_len_and_limits(
1085 reader,
1086 format,
1087 pack_len,
1088 PackReadLimits::default(),
1089 )
1090 }
1091
1092 pub fn write_v2_for_pack_reader_with_len_and_limits<R>(
1093 reader: &mut R,
1094 format: ObjectFormat,
1095 pack_len: u64,
1096 limits: PackReadLimits,
1097 ) -> Result<PackStreamIndexBuild>
1098 where
1099 R: Read,
1100 {
1101 index_pack_from_reader_with_len_and_limits(reader, format, pack_len, limits)
1102 }
1103
1104 pub fn write_v2_for_pack_path(
1107 path: impl AsRef<Path>,
1108 format: ObjectFormat,
1109 ) -> Result<PackStreamIndexBuild> {
1110 Self::write_v2_for_pack_path_with_limits(path, format, PackReadLimits::default())
1111 }
1112
1113 pub fn write_v2_for_pack_path_with_limits(
1114 path: impl AsRef<Path>,
1115 format: ObjectFormat,
1116 limits: PackReadLimits,
1117 ) -> Result<PackStreamIndexBuild> {
1118 let mut file = File::open(path)?;
1119 Self::write_v2_for_pack_reader_with_limits(&mut file, format, limits)
1120 }
1121
1122 pub fn parse_v2_sha1(bytes: &[u8]) -> Result<Self> {
1123 Self::parse(bytes, ObjectFormat::Sha1)
1124 }
1125
1126 pub fn parse(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
1127 Self::parse_impl(bytes, format, true)
1128 }
1129
1130 pub fn parse_without_checksum(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
1131 Self::parse_impl(bytes, format, false)
1132 }
1133
1134 pub(crate) fn parse_impl(
1135 bytes: &[u8],
1136 format: ObjectFormat,
1137 verify_checksum: bool,
1138 ) -> Result<Self> {
1139 let hash_len = format.raw_len();
1140 if bytes.len() < 4 {
1141 return Err(GitError::InvalidFormat("pack index too short".into()));
1142 }
1143 if bytes[..4] != [0xff, b't', b'O', b'c'] {
1144 return Self::parse_v1_impl(bytes, format, verify_checksum);
1145 }
1146 if bytes.len() < 8 + 256 * 4 + 2 * hash_len {
1147 return Err(GitError::InvalidFormat("pack index too short".into()));
1148 }
1149 let version = u32_be(&bytes[4..8]);
1150 if version != 2 {
1151 return Err(GitError::Unsupported(format!(
1152 "pack index version {version}"
1153 )));
1154 }
1155 let index_checksum_offset = bytes.len() - hash_len;
1156 let index_checksum = ObjectId::from_raw(format, &bytes[index_checksum_offset..])?;
1157 if verify_checksum {
1158 let actual_index_checksum =
1159 sley_core::digest_bytes(format, &bytes[..index_checksum_offset])?;
1160 if actual_index_checksum != index_checksum {
1161 return Err(GitError::InvalidFormat(format!(
1162 "pack index checksum mismatch: expected {index_checksum}, got {actual_index_checksum}"
1163 )));
1164 }
1165 }
1166
1167 let mut offset = 8usize;
1168 let mut fanout = [0u32; 256];
1169 let mut previous = 0u32;
1170 for slot in &mut fanout {
1171 *slot = u32_be(&bytes[offset..offset + 4]);
1172 if *slot < previous {
1173 return Err(GitError::InvalidFormat(
1174 "pack index fanout is not monotonic".into(),
1175 ));
1176 }
1177 previous = *slot;
1178 offset += 4;
1179 }
1180 let count = fanout[255] as usize;
1181 let oid_table = checked_range(offset, count, hash_len, bytes.len())?;
1182 offset = oid_table.end;
1183 let crc_table = checked_range(offset, count, 4, bytes.len())?;
1184 offset = crc_table.end;
1185 let small_offset_table = checked_range(offset, count, 4, bytes.len())?;
1186 offset = small_offset_table.end;
1187
1188 let large_offset_count = (0..count)
1189 .filter(|idx| {
1190 let start = small_offset_table.start + idx * 4;
1191 u32_be(&bytes[start..start + 4]) & 0x8000_0000 != 0
1192 })
1193 .count();
1194 let mut large_offset_table = checked_range(offset, large_offset_count, 8, bytes.len())?;
1195 offset = large_offset_table.end;
1196
1197 let expected_trailer_offset = bytes.len() - hash_len * 2;
1198 if offset != expected_trailer_offset {
1199 if !verify_checksum && offset < expected_trailer_offset {
1200 large_offset_table = large_offset_table.start..expected_trailer_offset;
1201 offset = expected_trailer_offset;
1202 } else {
1203 return Err(GitError::InvalidFormat(format!(
1204 "pack index has {} unexpected bytes before trailer",
1205 expected_trailer_offset.saturating_sub(offset)
1206 )));
1207 }
1208 }
1209 let pack_checksum = ObjectId::from_raw(format, &bytes[offset..offset + hash_len])?;
1210
1211 let mut entries = Vec::with_capacity(count);
1212 for idx in 0..count {
1213 let oid_start = oid_table.start + idx * hash_len;
1214 let crc_start = crc_table.start + idx * 4;
1215 let offset_start = small_offset_table.start + idx * 4;
1216 let oid_bytes = &bytes[oid_start..oid_start + hash_len];
1217 if idx > 0 && oid_bytes < &bytes[oid_start - hash_len..oid_start] {
1222 return Err(GitError::InvalidFormat(
1223 "pack index object ids are not sorted".into(),
1224 ));
1225 }
1226 let expected_min = if oid_bytes[0] == 0 {
1227 0
1228 } else {
1229 fanout[usize::from(oid_bytes[0] - 1)]
1230 };
1231 if (idx as u32) < expected_min || (idx as u32) >= fanout[usize::from(oid_bytes[0])] {
1232 return Err(GitError::InvalidFormat(
1233 "pack index object id is outside its fanout bucket".into(),
1234 ));
1235 }
1236 let raw_offset = u32_be(&bytes[offset_start..offset_start + 4]);
1237 let offset = if raw_offset & 0x8000_0000 == 0 {
1238 u64::from(raw_offset)
1239 } else {
1240 let large_idx = (raw_offset & 0x7fff_ffff) as usize;
1241 let large_start = large_offset_table.start + large_idx * 8;
1242 if large_idx >= large_offset_table.len() / 8 {
1243 return Err(GitError::InvalidFormat(
1244 "pack index large offset points past table".into(),
1245 ));
1246 }
1247 u64_be(&bytes[large_start..large_start + 8])
1248 };
1249 entries.push(PackIndexEntry {
1250 oid: ObjectId::from_raw(format, oid_bytes)?,
1251 crc32: u32_be(&bytes[crc_start..crc_start + 4]),
1252 offset,
1253 });
1254 }
1255 Ok(Self {
1256 version,
1257 fanout,
1258 entries,
1259 pack_checksum,
1260 index_checksum,
1261 })
1262 }
1263
1264 pub(crate) fn parse_v1_impl(
1265 bytes: &[u8],
1266 format: ObjectFormat,
1267 verify_checksum: bool,
1268 ) -> Result<Self> {
1269 let hash_len = format.raw_len();
1270 if bytes.len() < 256 * 4 + 2 * hash_len {
1271 return Err(GitError::InvalidFormat("pack index too short".into()));
1272 }
1273 let index_checksum_offset = bytes.len() - hash_len;
1274 let index_checksum = ObjectId::from_raw(format, &bytes[index_checksum_offset..])?;
1275 if verify_checksum {
1276 let actual_index_checksum =
1277 sley_core::digest_bytes(format, &bytes[..index_checksum_offset])?;
1278 if actual_index_checksum != index_checksum {
1279 return Err(GitError::InvalidFormat(format!(
1280 "pack index checksum mismatch: expected {index_checksum}, got {actual_index_checksum}"
1281 )));
1282 }
1283 }
1284
1285 let mut offset = 0usize;
1286 let mut fanout = [0u32; 256];
1287 let mut previous = 0u32;
1288 for slot in &mut fanout {
1289 *slot = u32_be(&bytes[offset..offset + 4]);
1290 if *slot < previous {
1291 return Err(GitError::InvalidFormat(
1292 "pack index fanout is not monotonic".into(),
1293 ));
1294 }
1295 previous = *slot;
1296 offset += 4;
1297 }
1298 let count = fanout[255] as usize;
1299 let entry_len = hash_len
1300 .checked_add(4)
1301 .ok_or_else(|| GitError::InvalidFormat("pack index entry length overflow".into()))?;
1302 let entry_table = checked_range(offset, count, entry_len, bytes.len())?;
1303 offset = entry_table.end;
1304 let expected_trailer_offset = bytes.len() - hash_len * 2;
1305 if offset != expected_trailer_offset {
1306 return Err(GitError::InvalidFormat(format!(
1307 "pack index has {} unexpected bytes before trailer",
1308 expected_trailer_offset.saturating_sub(offset)
1309 )));
1310 }
1311 let pack_checksum = ObjectId::from_raw(format, &bytes[offset..offset + hash_len])?;
1312
1313 let mut entries = Vec::with_capacity(count);
1314 let mut previous_oid: Option<ObjectId> = None;
1315 for idx in 0..count {
1316 let start = entry_table.start + idx * entry_len;
1317 let oid = ObjectId::from_raw(format, &bytes[start + 4..start + entry_len])?;
1318 if let Some(previous) = &previous_oid
1319 && previous.as_bytes() > oid.as_bytes()
1320 {
1321 return Err(GitError::InvalidFormat(
1322 "pack index object ids are not sorted".into(),
1323 ));
1324 }
1325 previous_oid = Some(oid);
1326 entries.push(PackIndexEntry {
1327 oid,
1328 crc32: 0,
1329 offset: u64::from(u32_be(&bytes[start..start + 4])),
1330 });
1331 }
1332 Ok(Self {
1333 version: 1,
1334 fanout,
1335 entries,
1336 pack_checksum,
1337 index_checksum,
1338 })
1339 }
1340
1341 pub fn find(&self, oid: &ObjectId) -> Option<&PackIndexEntry> {
1342 self.entries
1343 .binary_search_by(|entry| entry.oid.as_bytes().cmp(oid.as_bytes()))
1344 .ok()
1345 .map(|idx| &self.entries[idx])
1346 }
1347
1348 pub fn write_v2_sha1(entries: &[PackIndexEntry], pack_checksum: &ObjectId) -> Result<Vec<u8>> {
1349 Self::write_v2(ObjectFormat::Sha1, entries, pack_checksum)
1350 }
1351
1352 pub fn write_v2(
1353 format: ObjectFormat,
1354 entries: &[PackIndexEntry],
1355 pack_checksum: &ObjectId,
1356 ) -> Result<Vec<u8>> {
1357 if pack_checksum.format() != format {
1358 return Err(GitError::InvalidObjectId(
1359 "pack checksum format does not match index format".into(),
1360 ));
1361 }
1362 let mut entries = entries.iter().collect::<Vec<_>>();
1363 entries.sort_by(|left, right| left.oid.as_bytes().cmp(right.oid.as_bytes()));
1364 let mut fanout = [0u32; 256];
1365 for entry in &entries {
1366 if entry.oid.format() != format {
1367 return Err(GitError::InvalidObjectId(
1368 "pack index entry format does not match index format".into(),
1369 ));
1370 }
1371 let first = entry.oid.as_bytes()[0] as usize;
1372 fanout[first] = fanout[first]
1373 .checked_add(1)
1374 .ok_or_else(|| GitError::InvalidFormat("pack index fanout overflow".into()))?;
1375 }
1376 let mut running = 0u32;
1377 for slot in &mut fanout {
1378 running = running
1379 .checked_add(*slot)
1380 .ok_or_else(|| GitError::InvalidFormat("pack index fanout overflow".into()))?;
1381 *slot = running;
1382 }
1383
1384 let mut index = Vec::new();
1385 index.extend_from_slice(&[0xff, b't', b'O', b'c']);
1386 index.extend_from_slice(&2u32.to_be_bytes());
1387 for count in fanout {
1388 index.extend_from_slice(&count.to_be_bytes());
1389 }
1390 for entry in &entries {
1391 index.extend_from_slice(entry.oid.as_bytes());
1392 }
1393 for entry in &entries {
1394 index.extend_from_slice(&entry.crc32.to_be_bytes());
1395 }
1396
1397 let mut large_offsets = Vec::new();
1398 for entry in &entries {
1399 if entry.offset < 0x8000_0000 {
1400 index.extend_from_slice(&(entry.offset as u32).to_be_bytes());
1401 } else {
1402 if large_offsets.len() > 0x7fff_ffff {
1403 return Err(GitError::InvalidFormat(
1404 "too many large pack offsets".into(),
1405 ));
1406 }
1407 let large_idx = large_offsets.len() as u32;
1408 index.extend_from_slice(&(0x8000_0000 | large_idx).to_be_bytes());
1409 large_offsets.push(entry.offset);
1410 }
1411 }
1412 for offset in large_offsets {
1413 index.extend_from_slice(&offset.to_be_bytes());
1414 }
1415 index.extend_from_slice(pack_checksum.as_bytes());
1416 let index_checksum = sley_core::digest_bytes(format, &index)?;
1417 index.extend_from_slice(index_checksum.as_bytes());
1418 Ok(index)
1419 }
1420
1421 pub fn write_v1(
1427 format: ObjectFormat,
1428 entries: &[PackIndexEntry],
1429 pack_checksum: &ObjectId,
1430 ) -> Result<Vec<u8>> {
1431 if pack_checksum.format() != format {
1432 return Err(GitError::InvalidObjectId(
1433 "pack checksum format does not match index format".into(),
1434 ));
1435 }
1436 let mut entries = entries.iter().collect::<Vec<_>>();
1437 entries.sort_by(|left, right| left.oid.as_bytes().cmp(right.oid.as_bytes()));
1438 let mut fanout = [0u32; 256];
1439 for entry in &entries {
1440 if entry.oid.format() != format {
1441 return Err(GitError::InvalidObjectId(
1442 "pack index entry format does not match index format".into(),
1443 ));
1444 }
1445 if entry.offset > 0xffff_ffff {
1446 return Err(GitError::InvalidFormat(
1447 "pack offset too large for a version-1 index".into(),
1448 ));
1449 }
1450 let first = entry.oid.as_bytes()[0] as usize;
1451 fanout[first] = fanout[first]
1452 .checked_add(1)
1453 .ok_or_else(|| GitError::InvalidFormat("pack index fanout overflow".into()))?;
1454 }
1455 let mut running = 0u32;
1456 for slot in &mut fanout {
1457 running = running
1458 .checked_add(*slot)
1459 .ok_or_else(|| GitError::InvalidFormat("pack index fanout overflow".into()))?;
1460 *slot = running;
1461 }
1462
1463 let mut index = Vec::new();
1464 for count in fanout {
1465 index.extend_from_slice(&count.to_be_bytes());
1466 }
1467 for entry in &entries {
1468 index.extend_from_slice(&(entry.offset as u32).to_be_bytes());
1469 index.extend_from_slice(entry.oid.as_bytes());
1470 }
1471 index.extend_from_slice(pack_checksum.as_bytes());
1472 let index_checksum = sley_core::digest_bytes(format, &index)?;
1473 index.extend_from_slice(index_checksum.as_bytes());
1474 Ok(index)
1475 }
1476}
1477pub fn pack_order_index_positions(entries: &[PackIndexEntry]) -> Vec<u32> {
1482 let mut oid_sorted: Vec<usize> = (0..entries.len()).collect();
1483 oid_sorted.sort_by(|&a, &b| entries[a].oid.as_bytes().cmp(entries[b].oid.as_bytes()));
1484 let mut index_position = vec![0u32; entries.len()];
1485 for (position, &entry) in oid_sorted.iter().enumerate() {
1486 index_position[entry] = position as u32;
1487 }
1488 let mut by_offset: Vec<usize> = (0..entries.len()).collect();
1489 by_offset.sort_by_key(|&entry| entries[entry].offset);
1490 by_offset
1491 .into_iter()
1492 .map(|entry| index_position[entry])
1493 .collect()
1494}
1495
1496impl PackReverseIndex {
1497 pub fn write(
1498 format: ObjectFormat,
1499 positions: &[u32],
1500 pack_checksum: &ObjectId,
1501 ) -> Result<Vec<u8>> {
1502 if pack_checksum.format() != format {
1503 return Err(GitError::InvalidObjectId(
1504 "pack checksum format does not match reverse index format".into(),
1505 ));
1506 }
1507 validate_position_permutation(positions)?;
1508
1509 let mut out = Vec::new();
1510 out.extend_from_slice(b"RIDX");
1511 out.extend_from_slice(&1u32.to_be_bytes());
1512 out.extend_from_slice(&hash_function_id(format).to_be_bytes());
1513 for position in positions {
1514 out.extend_from_slice(&position.to_be_bytes());
1515 }
1516 out.extend_from_slice(pack_checksum.as_bytes());
1517 let checksum = sley_core::digest_bytes(format, &out)?;
1518 out.extend_from_slice(checksum.as_bytes());
1519 Ok(out)
1520 }
1521
1522 pub fn parse(bytes: &[u8], format: ObjectFormat, object_count: usize) -> Result<Self> {
1523 let hash_len = format.raw_len();
1524 let table_len = object_count
1525 .checked_mul(4)
1526 .ok_or_else(|| GitError::InvalidFormat("reverse index table overflow".into()))?;
1527 let min_len = 12usize
1528 .checked_add(table_len)
1529 .and_then(|len| len.checked_add(hash_len * 2))
1530 .ok_or_else(|| GitError::InvalidFormat("reverse index length overflow".into()))?;
1531 if bytes.len() < min_len {
1532 return Err(GitError::InvalidFormat("reverse index too short".into()));
1533 }
1534 if bytes.len() != min_len {
1535 return Err(GitError::InvalidFormat(format!(
1536 "reverse index has {} trailing bytes",
1537 bytes.len() - min_len
1538 )));
1539 }
1540 if &bytes[..4] != b"RIDX" {
1541 return Err(GitError::InvalidFormat("unknown signature".into()));
1542 }
1543 let version = u32_be(&bytes[4..8]);
1544 if version != 1 {
1545 return Err(GitError::InvalidFormat(format!(
1546 "unsupported version {version}"
1547 )));
1548 }
1549 let hash_id = u32_be(&bytes[8..12]);
1550 if hash_id != hash_function_id(format) {
1551 return Err(GitError::InvalidFormat(format!(
1552 "unsupported hash id {hash_id}"
1553 )));
1554 }
1555
1556 let index_checksum_offset = bytes.len() - hash_len;
1557 let pack_checksum_offset = index_checksum_offset - hash_len;
1558 let pack_checksum =
1559 ObjectId::from_raw(format, &bytes[pack_checksum_offset..index_checksum_offset])?;
1560 let mut positions = Vec::with_capacity(object_count);
1561 let mut offset = 12usize;
1562 for _ in 0..object_count {
1563 let position = u32_be(&bytes[offset..offset + 4]);
1564 positions.push(position);
1565 offset += 4;
1566 }
1567 validate_position_permutation(&positions)?;
1568
1569 let actual_index_checksum =
1572 sley_core::digest_bytes(format, &bytes[..index_checksum_offset])?;
1573 let index_checksum = ObjectId::from_raw(format, &bytes[index_checksum_offset..])?;
1574 if actual_index_checksum != index_checksum {
1575 return Err(GitError::InvalidFormat("invalid checksum".into()));
1576 }
1577
1578 Ok(Self {
1579 version,
1580 format,
1581 positions,
1582 pack_checksum,
1583 index_checksum,
1584 })
1585 }
1586
1587 pub fn oid_at_offset(&self, index: &PackIndexViewData, offset: u64) -> Option<ObjectId> {
1593 if self.pack_checksum != index.pack_checksum {
1594 return None;
1595 }
1596 let view = index.as_view();
1597 let positions = &self.positions;
1598 let mut lo = 0usize;
1599 let mut hi = positions.len();
1600 while lo < hi {
1601 let mid = lo + (hi - lo) / 2;
1602 let idx_pos = positions[mid] as usize;
1603 let entry_offset = view.lookup_at(idx_pos)?.offset;
1604 if entry_offset < offset {
1605 lo = mid + 1;
1606 } else if entry_offset > offset {
1607 hi = mid;
1608 } else {
1609 return index.oid_at(idx_pos).ok();
1610 }
1611 }
1612 None
1613 }
1614}
1615
1616impl PackMtimes {
1617 pub fn write(
1618 format: ObjectFormat,
1619 mtimes: &[u32],
1620 pack_checksum: &ObjectId,
1621 ) -> Result<Vec<u8>> {
1622 if pack_checksum.format() != format {
1623 return Err(GitError::InvalidObjectId(
1624 "pack checksum format does not match mtimes format".into(),
1625 ));
1626 }
1627
1628 let mut out = Vec::new();
1629 out.extend_from_slice(b"MTME");
1630 out.extend_from_slice(&1u32.to_be_bytes());
1631 out.extend_from_slice(&hash_function_id(format).to_be_bytes());
1632 for mtime in mtimes {
1633 out.extend_from_slice(&mtime.to_be_bytes());
1634 }
1635 out.extend_from_slice(pack_checksum.as_bytes());
1636 let checksum = sley_core::digest_bytes(format, &out)?;
1637 out.extend_from_slice(checksum.as_bytes());
1638 Ok(out)
1639 }
1640
1641 pub fn parse(bytes: &[u8], format: ObjectFormat, object_count: usize) -> Result<Self> {
1642 let hash_len = format.raw_len();
1643 let table_len = object_count
1644 .checked_mul(4)
1645 .ok_or_else(|| GitError::InvalidFormat("mtimes table overflow".into()))?;
1646 let expected_len = 12usize
1647 .checked_add(table_len)
1648 .and_then(|len| len.checked_add(hash_len * 2))
1649 .ok_or_else(|| GitError::InvalidFormat("mtimes length overflow".into()))?;
1650 if bytes.len() < expected_len {
1651 return Err(GitError::InvalidFormat("mtimes file too short".into()));
1652 }
1653 if bytes.len() != expected_len {
1654 return Err(GitError::InvalidFormat(format!(
1655 "mtimes file has {} trailing bytes",
1656 bytes.len() - expected_len
1657 )));
1658 }
1659 if &bytes[..4] != b"MTME" {
1660 return Err(GitError::InvalidFormat("missing mtimes signature".into()));
1661 }
1662 let version = u32_be(&bytes[4..8]);
1663 if version != 1 {
1664 return Err(GitError::Unsupported(format!("mtimes version {version}")));
1665 }
1666 let hash_id = u32_be(&bytes[8..12]);
1667 if hash_id != hash_function_id(format) {
1668 return Err(GitError::InvalidFormat(format!(
1669 "mtimes hash id {hash_id} does not match {}",
1670 format.name()
1671 )));
1672 }
1673
1674 let index_checksum_offset = bytes.len() - hash_len;
1675 let actual_index_checksum =
1676 sley_core::digest_bytes(format, &bytes[..index_checksum_offset])?;
1677 let index_checksum = ObjectId::from_raw(format, &bytes[index_checksum_offset..])?;
1678 if actual_index_checksum != index_checksum {
1679 return Err(GitError::InvalidFormat(format!(
1680 "mtimes checksum mismatch: expected {index_checksum}, got {actual_index_checksum}"
1681 )));
1682 }
1683
1684 let pack_checksum_offset = index_checksum_offset - hash_len;
1685 let pack_checksum =
1686 ObjectId::from_raw(format, &bytes[pack_checksum_offset..index_checksum_offset])?;
1687 let mut mtimes = Vec::with_capacity(object_count);
1688 let mut offset = 12usize;
1689 for _ in 0..object_count {
1690 mtimes.push(u32_be(&bytes[offset..offset + 4]));
1691 offset += 4;
1692 }
1693
1694 Ok(Self {
1695 version,
1696 format,
1697 mtimes,
1698 pack_checksum,
1699 index_checksum,
1700 })
1701 }
1702}
1703
1704impl PackBitmapIndex {
1705 pub const OPTION_FULL_DAG: u16 = 0x0001;
1706 pub const OPTION_HASH_CACHE: u16 = 0x0004;
1707 pub const OPTION_LOOKUP_TABLE: u16 = 0x0010;
1708 pub const OPTION_PSEUDO_MERGES: u16 = 0x0020;
1709
1710 pub fn parse(bytes: &[u8], format: ObjectFormat, object_count: usize) -> Result<Self> {
1711 let hash_len = format.raw_len();
1712 let min_len = 12usize
1713 .checked_add(hash_len * 2)
1714 .ok_or_else(|| GitError::InvalidFormat("bitmap index length overflow".into()))?;
1715 if bytes.len() < min_len {
1716 return Err(GitError::InvalidFormat("bitmap index too short".into()));
1717 }
1718 if &bytes[..4] != b"BITM" {
1719 return Err(GitError::InvalidFormat(
1720 "missing bitmap index signature".into(),
1721 ));
1722 }
1723 let version = u16_be(&bytes[4..6]);
1724 if version != 1 {
1725 return Err(GitError::Unsupported(format!(
1726 "bitmap index version {version}"
1727 )));
1728 }
1729 let options = u16_be(&bytes[6..8]);
1730 let known_options = Self::OPTION_FULL_DAG
1731 | Self::OPTION_HASH_CACHE
1732 | Self::OPTION_LOOKUP_TABLE
1733 | Self::OPTION_PSEUDO_MERGES;
1734 if options & !known_options != 0 {
1735 return Err(GitError::Unsupported(format!(
1736 "bitmap index options {:#06x}",
1737 options & !known_options
1738 )));
1739 }
1740 let entry_count = u32_be(&bytes[8..12]) as usize;
1741 let checksum_offset = bytes.len() - hash_len;
1742 let index_checksum = ObjectId::from_raw(format, &bytes[checksum_offset..])?;
1743 let mut extras_end = checksum_offset;
1744 let hash_cache_range = if options & Self::OPTION_HASH_CACHE != 0 {
1745 let cache_len = object_count
1746 .checked_mul(4)
1747 .ok_or_else(|| GitError::InvalidFormat("bitmap hash cache overflow".into()))?;
1748 if cache_len > extras_end {
1749 return Err(GitError::InvalidFormat(
1750 "truncated bitmap hash cache".into(),
1751 ));
1752 }
1753 extras_end -= cache_len;
1754 Some(extras_end..extras_end + cache_len)
1755 } else {
1756 None
1757 };
1758 let lookup_table = options & Self::OPTION_LOOKUP_TABLE != 0;
1759 let lookup_table_range = if lookup_table {
1760 let table_len = entry_count
1761 .checked_mul(16)
1762 .ok_or_else(|| GitError::InvalidFormat("bitmap lookup table overflow".into()))?;
1763 if table_len > extras_end {
1764 return Err(GitError::InvalidFormat(
1765 "truncated bitmap lookup table".into(),
1766 ));
1767 }
1768 extras_end -= table_len;
1769 Some(extras_end..extras_end + table_len)
1770 } else {
1771 None
1772 };
1773 let pseudo_merge_range = if options & Self::OPTION_PSEUDO_MERGES != 0 {
1774 if extras_end < 24 {
1775 return Err(GitError::InvalidFormat(
1776 "truncated bitmap pseudo-merge extension".into(),
1777 ));
1778 }
1779 let extension_size = u64_be(&bytes[extras_end - 8..extras_end]) as usize;
1780 if extension_size > extras_end {
1781 return Err(GitError::InvalidFormat(
1782 "bitmap pseudo-merge extension points before file start".into(),
1783 ));
1784 }
1785 let start = extras_end - extension_size;
1786 Some(start..extras_end)
1787 } else {
1788 None
1789 };
1790 let entries_end = pseudo_merge_range
1791 .as_ref()
1792 .map(|range| range.start)
1793 .unwrap_or(extras_end);
1794
1795 let pack_checksum_end = 12usize
1796 .checked_add(hash_len)
1797 .ok_or_else(|| GitError::InvalidFormat("bitmap index length overflow".into()))?;
1798 let pack_checksum = ObjectId::from_raw(format, &bytes[12..pack_checksum_end])?;
1799 let mut offset = pack_checksum_end;
1800 let commits = parse_bitmap_ewah(bytes, &mut offset, entries_end, object_count)?;
1801 let trees = parse_bitmap_ewah(bytes, &mut offset, entries_end, object_count)?;
1802 let blobs = parse_bitmap_ewah(bytes, &mut offset, entries_end, object_count)?;
1803 let tags = parse_bitmap_ewah(bytes, &mut offset, entries_end, object_count)?;
1804
1805 let mut entries = Vec::with_capacity(entry_count);
1806 for idx in 0..entry_count {
1807 if entries_end.saturating_sub(offset) < 6 {
1808 return Err(GitError::InvalidFormat(
1809 "truncated bitmap index entry".into(),
1810 ));
1811 }
1812 let object_position = u32_be(&bytes[offset..offset + 4]);
1813 offset += 4;
1814 if object_position as usize >= object_count {
1815 return Err(GitError::InvalidFormat(
1816 "bitmap index entry points past object table".into(),
1817 ));
1818 }
1819 let xor_offset = bytes[offset];
1820 offset += 1;
1821 if xor_offset as usize > idx || xor_offset > 160 {
1822 return Err(GitError::InvalidFormat(
1823 "bitmap index entry has invalid XOR offset".into(),
1824 ));
1825 }
1826 let flags = bytes[offset];
1827 offset += 1;
1828 let bitmap = parse_bitmap_ewah(bytes, &mut offset, entries_end, object_count)?;
1829 entries.push(PackBitmapEntry {
1830 object_position,
1831 xor_offset,
1832 flags,
1833 bitmap,
1834 });
1835 }
1836
1837 if offset != entries_end {
1838 return Err(GitError::InvalidFormat(format!(
1839 "bitmap index has {} trailing entry bytes",
1840 entries_end - offset
1841 )));
1842 }
1843
1844 let pseudo_merges = if let Some(range) = pseudo_merge_range {
1845 parse_bitmap_pseudo_merges(bytes, range, object_count)?
1846 } else {
1847 Vec::new()
1848 };
1849
1850 let name_hash_cache = if let Some(range) = hash_cache_range {
1851 let mut cache = Vec::with_capacity(object_count);
1852 let mut offset = range.start;
1853 for _ in 0..object_count {
1854 cache.push(u32_be(&bytes[offset..offset + 4]));
1855 offset += 4;
1856 }
1857 Some(cache)
1858 } else {
1859 None
1860 };
1861 if let Some(range) = lookup_table_range {
1862 for row in bytes[range].chunks_exact(16) {
1863 let commit_position = u32_be(&row[..4]);
1864 let entry_offset = u64_be(&row[4..12]);
1865 let xor_row = u32_be(&row[12..16]);
1866 if commit_position as usize >= object_count
1867 || entry_offset as usize >= entries_end
1868 || (xor_row != u32::MAX && xor_row as usize >= entry_count)
1869 {
1870 return Err(GitError::InvalidFormat(
1871 "corrupt bitmap lookup table".into(),
1872 ));
1873 }
1874 }
1875 }
1876
1877 let actual_index_checksum = sley_core::digest_bytes(format, &bytes[..checksum_offset])?;
1878 if actual_index_checksum != index_checksum {
1879 return Err(GitError::InvalidFormat(format!(
1880 "bitmap index checksum mismatch: expected {index_checksum}, got {actual_index_checksum}"
1881 )));
1882 }
1883
1884 Ok(Self {
1885 version,
1886 format,
1887 options,
1888 pack_checksum,
1889 index_checksum,
1890 type_bitmaps: PackBitmapTypeBitmaps {
1891 commits,
1892 trees,
1893 blobs,
1894 tags,
1895 },
1896 entries,
1897 pseudo_merges,
1898 lookup_table,
1899 name_hash_cache,
1900 })
1901 }
1902
1903 pub fn entry_for_index_position(&self, position: u32) -> Option<&PackBitmapEntry> {
1906 self.entries
1907 .iter()
1908 .find(|entry| entry.object_position == position)
1909 }
1910}
1911
1912pub(crate) fn parse_bitmap_pseudo_merges(
1913 bytes: &[u8],
1914 range: std::ops::Range<usize>,
1915 object_count: usize,
1916) -> Result<Vec<PackBitmapPseudoMerge>> {
1917 if range.end < range.start || range.end > bytes.len() || range.end - range.start < 24 {
1918 return Err(GitError::InvalidFormat(
1919 "truncated bitmap pseudo-merge extension".into(),
1920 ));
1921 }
1922 let trailer_start = range.end - 24;
1923 let pseudo_merge_count = u32_be(&bytes[trailer_start..trailer_start + 4]) as usize;
1924 let commit_count = u32_be(&bytes[trailer_start + 4..trailer_start + 8]) as usize;
1925 let lookup_offset = u64_be(&bytes[trailer_start + 8..trailer_start + 16]) as usize;
1926 let extension_size = u64_be(&bytes[trailer_start + 16..trailer_start + 24]) as usize;
1927 if extension_size != range.end - range.start {
1928 return Err(GitError::InvalidFormat(
1929 "bitmap pseudo-merge extension size mismatch".into(),
1930 ));
1931 }
1932 let lookup_start = range
1933 .start
1934 .checked_add(lookup_offset)
1935 .ok_or_else(|| GitError::InvalidFormat("bitmap pseudo-merge lookup overflow".into()))?;
1936 if lookup_start > trailer_start {
1937 return Err(GitError::InvalidFormat(
1938 "bitmap pseudo-merge lookup points past extension".into(),
1939 ));
1940 }
1941 let lookup_len = commit_count
1942 .checked_mul(12)
1943 .ok_or_else(|| GitError::InvalidFormat("bitmap pseudo-merge lookup overflow".into()))?;
1944 if lookup_start
1945 .checked_add(lookup_len)
1946 .is_none_or(|end| end > trailer_start)
1947 {
1948 return Err(GitError::InvalidFormat(
1949 "truncated bitmap pseudo-merge lookup".into(),
1950 ));
1951 }
1952 let position_table_len = pseudo_merge_count.checked_mul(8).ok_or_else(|| {
1953 GitError::InvalidFormat("bitmap pseudo-merge position table overflow".into())
1954 })?;
1955 let position_table_start = trailer_start
1956 .checked_sub(position_table_len)
1957 .filter(|start| *start >= range.start)
1958 .ok_or_else(|| {
1959 GitError::InvalidFormat("truncated bitmap pseudo-merge position table".into())
1960 })?;
1961
1962 let mut pseudo_merges = Vec::with_capacity(pseudo_merge_count);
1963 let mut cursor = position_table_start;
1964 for _ in 0..pseudo_merge_count {
1965 let pseudo_offset = u64_be(&bytes[cursor..cursor + 8]) as usize;
1966 cursor += 8;
1967 if pseudo_offset < range.start || pseudo_offset >= position_table_start {
1968 return Err(GitError::InvalidFormat(
1969 "bitmap pseudo-merge offset out of range".into(),
1970 ));
1971 }
1972 let mut offset = pseudo_offset;
1973 let commits = parse_bitmap_ewah(bytes, &mut offset, range.end, object_count)?;
1974 let bitmap = parse_bitmap_ewah(bytes, &mut offset, range.end, object_count)?;
1975 pseudo_merges.push(PackBitmapPseudoMerge { commits, bitmap });
1976 }
1977 Ok(pseudo_merges)
1978}
1979
1980pub(crate) fn parse_bitmap_ewah(
1981 bytes: &[u8],
1982 offset: &mut usize,
1983 checksum_offset: usize,
1984 _object_count: usize,
1985) -> Result<EwahBitmap> {
1986 if checksum_offset.saturating_sub(*offset) < 12 {
1987 return Err(GitError::InvalidFormat("truncated EWAH bitmap".into()));
1988 }
1989 let bit_size = u32_be(&bytes[*offset..*offset + 4]);
1990 *offset += 4;
1991 let word_count = u32_be(&bytes[*offset..*offset + 4]) as usize;
1992 *offset += 4;
1993 let words_len = word_count
1994 .checked_mul(8)
1995 .ok_or_else(|| GitError::InvalidFormat("EWAH word table overflow".into()))?;
1996 if checksum_offset.saturating_sub(*offset) < words_len + 4 {
1997 return Err(GitError::InvalidFormat("truncated EWAH word table".into()));
1998 }
1999 let mut words = Vec::with_capacity(word_count);
2000 for _ in 0..word_count {
2001 words.push(u64_be(&bytes[*offset..*offset + 8]));
2002 *offset += 8;
2003 }
2004 let rlw_position = u32_be(&bytes[*offset..*offset + 4]);
2005 *offset += 4;
2006 validate_ewah_words(bit_size, &words, rlw_position)?;
2007 Ok(EwahBitmap {
2008 bit_size,
2009 words,
2010 rlw_position,
2011 })
2012}
2013
2014pub(crate) fn validate_ewah_words(bit_size: u32, words: &[u64], rlw_position: u32) -> Result<()> {
2015 if words.is_empty() {
2016 if rlw_position != 0 || bit_size != 0 {
2017 return Err(GitError::InvalidFormat(
2018 "EWAH bitmap has invalid empty RLW".into(),
2019 ));
2020 }
2021 return Ok(());
2022 }
2023 if rlw_position as usize >= words.len() {
2024 return Err(GitError::InvalidFormat(
2025 "EWAH RLW position points past word table".into(),
2026 ));
2027 }
2028 let mut word_idx = 0usize;
2029 let mut decoded_words = 0u64;
2030 while word_idx < words.len() {
2031 let rlw = words[word_idx];
2032 let run_words = (rlw >> 1) & 0xffff_ffff;
2033 let literal_words = (rlw >> 33) as usize;
2034 word_idx += 1;
2035 word_idx = word_idx
2036 .checked_add(literal_words)
2037 .ok_or_else(|| GitError::InvalidFormat("EWAH literal word overflow".into()))?;
2038 if word_idx > words.len() {
2039 return Err(GitError::InvalidFormat(
2040 "EWAH literal words extend past word table".into(),
2041 ));
2042 }
2043 decoded_words = decoded_words
2044 .checked_add(run_words)
2045 .and_then(|value| value.checked_add(literal_words as u64))
2046 .ok_or_else(|| GitError::InvalidFormat("EWAH decoded size overflow".into()))?;
2047 }
2048 let decoded_bits = decoded_words
2049 .checked_mul(64)
2050 .ok_or_else(|| GitError::InvalidFormat("EWAH decoded bit size overflow".into()))?;
2051 if decoded_bits < u64::from(bit_size) {
2052 return Err(GitError::InvalidFormat(
2053 "EWAH bitmap decodes fewer bits than declared".into(),
2054 ));
2055 }
2056 Ok(())
2057}
2058
2059impl MultiPackIndex {
2060 pub fn write(
2061 format: ObjectFormat,
2062 version: u8,
2063 pack_names: &[String],
2064 objects: &[MultiPackIndexEntry],
2065 ) -> Result<Vec<u8>> {
2066 Self::write_with_reverse_index(format, version, pack_names, objects, None)
2067 }
2068
2069 pub fn write_with_reverse_index(
2078 format: ObjectFormat,
2079 version: u8,
2080 pack_names: &[String],
2081 objects: &[MultiPackIndexEntry],
2082 preferred_pack: Option<u32>,
2083 ) -> Result<Vec<u8>> {
2084 Self::write_with_bitmap_packs(format, version, pack_names, objects, preferred_pack, None)
2085 }
2086
2087 pub fn write_with_bitmap_packs(
2088 format: ObjectFormat,
2089 version: u8,
2090 pack_names: &[String],
2091 objects: &[MultiPackIndexEntry],
2092 preferred_pack: Option<u32>,
2093 bitmapped_packs: Option<&[MultiPackBitmapPack]>,
2094 ) -> Result<Vec<u8>> {
2095 if let Some(preferred) = preferred_pack
2096 && preferred as usize >= pack_names.len()
2097 {
2098 return Err(GitError::InvalidFormat(format!(
2099 "preferred pack {preferred} out of range for {} packs",
2100 pack_names.len()
2101 )));
2102 }
2103 if version != 1 && version != 2 {
2104 return Err(GitError::Unsupported(format!(
2105 "multi-pack-index version {version}"
2106 )));
2107 }
2108 if pack_names.len() > u32::MAX as usize {
2109 return Err(GitError::InvalidFormat(
2110 "too many multi-pack-index packs".into(),
2111 ));
2112 }
2113 if objects.len() > u32::MAX as usize {
2114 return Err(GitError::InvalidFormat(
2115 "too many multi-pack-index objects".into(),
2116 ));
2117 }
2118 if let Some(bitmapped_packs) = bitmapped_packs {
2119 if bitmapped_packs.len() != pack_names.len() {
2120 return Err(GitError::InvalidFormat(
2121 "multi-pack-index BTMP pack count mismatch".into(),
2122 ));
2123 }
2124 for pack in bitmapped_packs {
2125 let bitmap_end = u64::from(pack.bitmap_pos)
2126 .checked_add(u64::from(pack.bitmap_nr))
2127 .ok_or_else(|| {
2128 GitError::InvalidFormat("multi-pack-index BTMP range overflow".into())
2129 })?;
2130 if bitmap_end > objects.len() as u64 {
2131 return Err(GitError::InvalidFormat(
2132 "multi-pack-index BTMP range points past object table".into(),
2133 ));
2134 }
2135 }
2136 }
2137 validate_midx_pack_names(pack_names)?;
2138 if version == 1 && pack_names.windows(2).any(|pair| pair[0] > pair[1]) {
2139 return Err(GitError::InvalidFormat(
2140 "multi-pack-index v1 pack names must be sorted".into(),
2141 ));
2142 }
2143
2144 let mut objects = objects.iter().collect::<Vec<_>>();
2145 objects.sort_by(|left, right| left.oid.as_bytes().cmp(right.oid.as_bytes()));
2146 let mut previous_oid: Option<&ObjectId> = None;
2147 for object in &objects {
2148 if object.oid.format() != format {
2149 return Err(GitError::InvalidObjectId(
2150 "multi-pack-index object format does not match index format".into(),
2151 ));
2152 }
2153 if let Some(previous) = previous_oid
2154 && previous.as_bytes() == object.oid.as_bytes()
2155 {
2156 return Err(GitError::InvalidFormat(
2157 "multi-pack-index contains duplicate object ids".into(),
2158 ));
2159 }
2160 if object.pack_int_id as usize >= pack_names.len() {
2161 return Err(GitError::InvalidFormat(
2162 "multi-pack-index object points past pack table".into(),
2163 ));
2164 }
2165 previous_oid = Some(&object.oid);
2166 }
2167
2168 let mut large_offsets = Vec::new();
2169 let mut chunks = vec![
2170 (*b"PNAM", write_midx_pack_names(pack_names)),
2171 (*b"OIDF", write_midx_oid_fanout(&objects)?),
2172 (*b"OIDL", write_midx_oid_lookup(&objects)),
2173 (
2174 *b"OOFF",
2175 write_midx_object_offsets(&objects, &mut large_offsets)?,
2176 ),
2177 ];
2178 if !large_offsets.is_empty() {
2179 chunks.push((*b"LOFF", large_offsets));
2180 }
2181 if let Some(preferred) = preferred_pack {
2182 let mut pseudo: Vec<u32> = (0..objects.len() as u32).collect();
2185 pseudo.sort_by_key(|&midx_pos| {
2186 let object = objects[midx_pos as usize];
2187 (
2188 object.pack_int_id != preferred,
2189 object.pack_int_id,
2190 object.offset,
2191 )
2192 });
2193 let mut ridx = Vec::with_capacity(pseudo.len() * 4);
2194 for midx_pos in pseudo {
2195 ridx.extend_from_slice(&midx_pos.to_be_bytes());
2196 }
2197 chunks.push((*b"RIDX", ridx));
2198 }
2199 if let Some(bitmapped_packs) = bitmapped_packs {
2200 let mut btmp = Vec::with_capacity(bitmapped_packs.len() * 8);
2201 for pack in bitmapped_packs {
2202 btmp.extend_from_slice(&pack.bitmap_pos.to_be_bytes());
2203 btmp.extend_from_slice(&pack.bitmap_nr.to_be_bytes());
2204 }
2205 chunks.push((*b"BTMP", btmp));
2206 }
2207 write_multi_pack_index_chunks(format, version, pack_names.len() as u32, &chunks)
2208 }
2209
2210 pub fn parse(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
2211 Self::parse_impl(bytes, format, true)
2212 }
2213
2214 pub fn parse_without_checksum(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
2215 Self::parse_impl(bytes, format, false)
2216 }
2217
2218 pub(crate) fn parse_impl(
2219 bytes: &[u8],
2220 format: ObjectFormat,
2221 verify_checksum: bool,
2222 ) -> Result<Self> {
2223 let hash_len = format.raw_len();
2224 if bytes.len() < 12 + 12 + hash_len {
2225 return Err(GitError::InvalidFormat(
2226 "multi-pack-index file too short".into(),
2227 ));
2228 }
2229 if &bytes[..4] != b"MIDX" {
2230 return Err(GitError::InvalidFormat(
2231 "missing multi-pack-index signature".into(),
2232 ));
2233 }
2234 let version = bytes[4];
2235 if version != 1 && version != 2 {
2236 return Err(GitError::Unsupported(format!(
2237 "multi-pack-index version {version}"
2238 )));
2239 }
2240 let hash_id = bytes[5];
2241 if u32::from(hash_id) != hash_function_id(format) {
2242 return Err(GitError::InvalidFormat(format!(
2243 "multi-pack-index hash id {hash_id} does not match {}",
2244 format.name()
2245 )));
2246 }
2247 let chunk_count = bytes[6] as usize;
2248 let base_midx_count = bytes[7];
2249 if base_midx_count != 0 {
2250 return Err(GitError::Unsupported(format!(
2251 "multi-pack-index base count {base_midx_count}"
2252 )));
2253 }
2254 let pack_count = u32_be(&bytes[8..12]);
2255 let lookup_len = (chunk_count + 1)
2256 .checked_mul(12)
2257 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index lookup overflow".into()))?;
2258 let data_start = 12usize
2259 .checked_add(lookup_len)
2260 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index lookup overflow".into()))?;
2261 let checksum_offset = bytes.len() - hash_len;
2262 if data_start > checksum_offset {
2263 return Err(GitError::InvalidFormat(
2264 "truncated multi-pack-index chunk lookup".into(),
2265 ));
2266 }
2267
2268 let checksum = ObjectId::from_raw(format, &bytes[checksum_offset..])?;
2269 if verify_checksum {
2270 let actual_checksum = sley_core::digest_bytes(format, &bytes[..checksum_offset])?;
2271 if actual_checksum != checksum {
2272 return Err(GitError::InvalidFormat(format!(
2273 "multi-pack-index checksum mismatch: expected {checksum}, got {actual_checksum}"
2274 )));
2275 }
2276 }
2277
2278 let mut entries = Vec::with_capacity(chunk_count + 1);
2279 let mut offset = 12usize;
2280 for _ in 0..=chunk_count {
2281 let id = [
2282 bytes[offset],
2283 bytes[offset + 1],
2284 bytes[offset + 2],
2285 bytes[offset + 3],
2286 ];
2287 let chunk_offset = u64_be(&bytes[offset + 4..offset + 12]);
2288 entries.push((id, chunk_offset));
2289 offset += 12;
2290 }
2291 let Some((terminator_id, terminator_offset)) = entries.last().copied() else {
2292 return Err(GitError::InvalidFormat(
2293 "multi-pack-index chunk lookup is empty".into(),
2294 ));
2295 };
2296 if terminator_id != [0, 0, 0, 0] {
2297 return Err(GitError::InvalidFormat(
2298 "multi-pack-index chunk lookup missing terminator".into(),
2299 ));
2300 }
2301 if terminator_offset != checksum_offset as u64 {
2302 return Err(GitError::InvalidFormat(
2303 "multi-pack-index terminator does not point at checksum".into(),
2304 ));
2305 }
2306
2307 let mut chunks = Vec::with_capacity(chunk_count);
2308 let mut previous_offset = data_start as u64;
2309 let mut reported_unaligned = false;
2310 for pair in entries.windows(2) {
2311 let (id, chunk_offset) = pair[0];
2312 let (_next_id, next_offset) = pair[1];
2313 if id == [0, 0, 0, 0] {
2314 return Err(GitError::InvalidFormat(
2315 "multi-pack-index chunk id is zero before terminator".into(),
2316 ));
2317 }
2318 if chunk_offset < data_start as u64 || chunk_offset < previous_offset {
2319 return Err(GitError::InvalidFormat(
2320 "multi-pack-index chunk offsets are not monotonic".into(),
2321 ));
2322 }
2323 if chunk_offset % 4 != 0 && !reported_unaligned {
2324 eprintln!(
2325 "error: chunk id {:08x} not 4-byte aligned",
2326 u32::from_be_bytes(id)
2327 );
2328 reported_unaligned = true;
2329 }
2330 if next_offset < chunk_offset || next_offset > checksum_offset as u64 {
2331 return Err(GitError::InvalidFormat(
2332 "multi-pack-index chunk length is invalid".into(),
2333 ));
2334 }
2335 chunks.push(MultiPackIndexChunk {
2336 id,
2337 offset: chunk_offset,
2338 len: next_offset - chunk_offset,
2339 });
2340 previous_offset = chunk_offset;
2341 }
2342
2343 let pack_names = parse_midx_pack_names(bytes, &chunks, pack_count as usize, version)?;
2344 let (fanout, object_count) = parse_midx_oid_fanout(bytes, &chunks)?;
2345 let object_ids = parse_midx_object_ids(bytes, &chunks, format, object_count, &fanout)?;
2346 let objects = parse_midx_object_offsets(bytes, &chunks, object_ids, pack_count)?;
2347 let reverse_index = parse_midx_reverse_index(bytes, &chunks, object_count)?;
2348 let bitmapped_packs =
2349 parse_midx_bitmapped_packs(bytes, &chunks, pack_count as usize, object_count)?;
2350
2351 Ok(Self {
2352 version,
2353 format,
2354 pack_count,
2355 pack_names,
2356 object_count: object_count as u32,
2357 fanout,
2358 objects,
2359 reverse_index,
2360 bitmapped_packs,
2361 chunks,
2362 checksum,
2363 })
2364 }
2365
2366 pub fn find(&self, oid: &ObjectId) -> Option<&MultiPackIndexEntry> {
2367 self.objects
2368 .binary_search_by(|entry| entry.oid.as_bytes().cmp(oid.as_bytes()))
2369 .ok()
2370 .map(|idx| &self.objects[idx])
2371 }
2372}
2373
2374impl MultiPackIndexOidLookup {
2375 pub fn parse(bytes: Arc<dyn PackIndexByteSource>, format: ObjectFormat) -> Result<Self> {
2376 let raw = bytes.as_bytes();
2377 let hash_len = format.raw_len();
2378 if raw.len() < 12 + 12 + hash_len {
2379 return Err(GitError::InvalidFormat(
2380 "multi-pack-index file too short".into(),
2381 ));
2382 }
2383 if &raw[..4] != b"MIDX" {
2384 return Err(GitError::InvalidFormat(
2385 "missing multi-pack-index signature".into(),
2386 ));
2387 }
2388 let version = raw[4];
2389 if version != 1 && version != 2 {
2390 return Err(GitError::Unsupported(format!(
2391 "multi-pack-index version {version}"
2392 )));
2393 }
2394 let hash_id = raw[5];
2395 if u32::from(hash_id) != hash_function_id(format) {
2396 return Err(GitError::InvalidFormat(format!(
2397 "multi-pack-index hash id {hash_id} does not match {}",
2398 format.name()
2399 )));
2400 }
2401 let chunk_count = raw[6] as usize;
2402 let base_midx_count = raw[7];
2403 if base_midx_count != 0 {
2404 return Err(GitError::Unsupported(format!(
2405 "multi-pack-index base count {base_midx_count}"
2406 )));
2407 }
2408 let pack_count = u32_be(&raw[8..12]);
2409 let lookup_len = (chunk_count + 1)
2410 .checked_mul(12)
2411 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index lookup overflow".into()))?;
2412 let data_start = 12usize
2413 .checked_add(lookup_len)
2414 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index lookup overflow".into()))?;
2415 let checksum_offset = raw.len() - hash_len;
2416 if data_start > checksum_offset {
2417 return Err(GitError::InvalidFormat(
2418 "truncated multi-pack-index chunk lookup".into(),
2419 ));
2420 }
2421
2422 let mut entries = Vec::with_capacity(chunk_count + 1);
2423 let mut offset = 12usize;
2424 for _ in 0..=chunk_count {
2425 let id = [
2426 raw[offset],
2427 raw[offset + 1],
2428 raw[offset + 2],
2429 raw[offset + 3],
2430 ];
2431 let chunk_offset = u64_be(&raw[offset + 4..offset + 12]);
2432 entries.push((id, chunk_offset));
2433 offset += 12;
2434 }
2435 let Some((terminator_id, terminator_offset)) = entries.last().copied() else {
2436 return Err(GitError::InvalidFormat(
2437 "multi-pack-index chunk lookup is empty".into(),
2438 ));
2439 };
2440 if terminator_id != [0, 0, 0, 0] {
2441 return Err(GitError::InvalidFormat(
2442 "multi-pack-index chunk lookup missing terminator".into(),
2443 ));
2444 }
2445 if terminator_offset != checksum_offset as u64 {
2446 return Err(GitError::InvalidFormat(
2447 "multi-pack-index terminator does not point at checksum".into(),
2448 ));
2449 }
2450
2451 let mut chunks = Vec::with_capacity(chunk_count);
2452 let mut previous_offset = data_start as u64;
2453 let mut reported_unaligned = false;
2454 for pair in entries.windows(2) {
2455 let (id, chunk_offset) = pair[0];
2456 let (_next_id, next_offset) = pair[1];
2457 if id == [0, 0, 0, 0] {
2458 return Err(GitError::InvalidFormat(
2459 "multi-pack-index chunk id is zero before terminator".into(),
2460 ));
2461 }
2462 if chunk_offset < data_start as u64 || chunk_offset < previous_offset {
2463 return Err(GitError::InvalidFormat(
2464 "multi-pack-index chunk offsets are not monotonic".into(),
2465 ));
2466 }
2467 if chunk_offset % 4 != 0 && !reported_unaligned {
2468 eprintln!(
2469 "error: chunk id {:08x} not 4-byte aligned",
2470 u32::from_be_bytes(id)
2471 );
2472 reported_unaligned = true;
2473 }
2474 if next_offset < chunk_offset || next_offset > checksum_offset as u64 {
2475 return Err(GitError::InvalidFormat(
2476 "multi-pack-index chunk length is invalid".into(),
2477 ));
2478 }
2479 chunks.push(MultiPackIndexChunk {
2480 id,
2481 offset: chunk_offset,
2482 len: next_offset - chunk_offset,
2483 });
2484 previous_offset = chunk_offset;
2485 }
2486
2487 let pack_names = parse_midx_pack_names(raw, &chunks, pack_count as usize, version)?;
2488 let (fanout, object_count) = parse_midx_oid_fanout(raw, &chunks)?;
2489 let oid_lookup = midx_chunk_data(raw, &chunks, *b"OIDL", true)?
2490 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index missing OIDL chunk".into()))?;
2491 let expected_len = object_count.checked_mul(hash_len).ok_or_else(|| {
2492 GitError::InvalidFormat("multi-pack-index OIDL chunk overflow".into())
2493 })?;
2494 if oid_lookup.len() != expected_len {
2495 return Err(GitError::InvalidFormat(
2496 "error: multi-pack-index OID lookup chunk is the wrong size\nfatal: multi-pack-index required OID lookup chunk missing or corrupted".into(),
2497 ));
2498 }
2499 let object_offsets = midx_chunk_data(raw, &chunks, *b"OOFF", true)?
2500 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index missing OOFF chunk".into()))?;
2501 let expected_offsets_len = object_count.checked_mul(8).ok_or_else(|| {
2502 GitError::InvalidFormat("multi-pack-index OOFF chunk overflow".into())
2503 })?;
2504 if object_offsets.len() != expected_offsets_len {
2505 return Err(GitError::InvalidFormat(
2506 "error: multi-pack-index object offset chunk is the wrong size\nfatal: multi-pack-index required object offsets chunk missing or corrupted".into(),
2507 ));
2508 }
2509 let large_offsets = midx_chunk_data(raw, &chunks, *b"LOFF", false)?;
2510 if let Some(large_offsets) = large_offsets
2511 && large_offsets.len() % 8 != 0
2512 {
2513 return Err(GitError::InvalidFormat(
2514 "multi-pack-index LOFF chunk has invalid length".into(),
2515 ));
2516 }
2517 let oid_lookup_offset = oid_lookup.as_ptr() as usize - raw.as_ptr() as usize;
2518 let object_offsets_offset = object_offsets.as_ptr() as usize - raw.as_ptr() as usize;
2519 let (large_offsets_offset, large_offsets_len) = match large_offsets {
2520 Some(large_offsets) => (
2521 Some(large_offsets.as_ptr() as usize - raw.as_ptr() as usize),
2522 large_offsets.len(),
2523 ),
2524 None => (None, 0),
2525 };
2526 Ok(Self {
2527 format,
2528 pack_count,
2529 pack_names,
2530 fanout,
2531 object_count,
2532 oid_lookup_offset,
2533 object_offsets_offset,
2534 large_offsets_offset,
2535 large_offsets_len,
2536 bytes,
2537 })
2538 }
2539
2540 pub fn contains(&self, oid: &ObjectId) -> bool {
2541 self.find_position(oid).is_some()
2542 }
2543
2544 pub fn find(&self, oid: &ObjectId) -> Result<Option<MultiPackIndexEntry>> {
2545 let Some(position) = self.find_position(oid) else {
2546 return Ok(None);
2547 };
2548 let bytes = self.bytes.as_bytes();
2549 let hash_len = self.format.raw_len();
2550 let oid_start = self
2551 .oid_lookup_offset
2552 .checked_add(position * hash_len)
2553 .ok_or_else(|| {
2554 GitError::InvalidFormat("multi-pack-index OIDL offset overflow".into())
2555 })?;
2556 let oid = ObjectId::from_raw(self.format, &bytes[oid_start..oid_start + hash_len])?;
2557 let offset_start = self
2558 .object_offsets_offset
2559 .checked_add(position * 8)
2560 .ok_or_else(|| {
2561 GitError::InvalidFormat("multi-pack-index OOFF offset overflow".into())
2562 })?;
2563 let data = &bytes[offset_start..offset_start + 8];
2564 let pack_int_id = u32_be(&data[..4]);
2565 if pack_int_id >= self.pack_count {
2566 return Err(GitError::InvalidFormat(
2567 "multi-pack-index object points past pack table".into(),
2568 ));
2569 }
2570 let raw_offset = u32_be(&data[4..8]);
2571 let offset = if raw_offset & 0x8000_0000 == 0 {
2572 u64::from(raw_offset)
2573 } else {
2574 let Some(large_offsets_offset) = self.large_offsets_offset else {
2575 return Err(GitError::InvalidFormat(
2576 "multi-pack-index large offset missing LOFF chunk".into(),
2577 ));
2578 };
2579 let large_idx = (raw_offset & 0x7fff_ffff) as usize;
2580 let large_start = large_idx.checked_mul(8).ok_or_else(|| {
2581 GitError::InvalidFormat("multi-pack-index LOFF index overflow".into())
2582 })?;
2583 let large_end = large_start.checked_add(8).ok_or_else(|| {
2584 GitError::InvalidFormat("multi-pack-index LOFF index overflow".into())
2585 })?;
2586 if large_end > self.large_offsets_len {
2587 return Err(GitError::InvalidFormat(
2588 "fatal: multi-pack-index large offset out of bounds".into(),
2589 ));
2590 }
2591 let start = large_offsets_offset + large_start;
2592 u64_be(&bytes[start..start + 8])
2593 };
2594 Ok(Some(MultiPackIndexEntry {
2595 oid,
2596 pack_int_id,
2597 offset,
2598 force_large_offset: raw_offset & 0x8000_0000 != 0,
2599 }))
2600 }
2601
2602 pub fn pack_name(&self, pack_int_id: u32) -> Option<&str> {
2603 self.pack_names
2604 .get(pack_int_id as usize)
2605 .map(String::as_str)
2606 }
2607
2608 pub(crate) fn find_position(&self, oid: &ObjectId) -> Option<usize> {
2609 if oid.format() != self.format || self.object_count == 0 {
2610 return None;
2611 }
2612 let first = oid.as_bytes()[0] as usize;
2613 let start = if first == 0 {
2614 0
2615 } else {
2616 self.fanout[first - 1] as usize
2617 };
2618 let end = self.fanout[first] as usize;
2619 if start >= end || end > self.object_count {
2620 return None;
2621 }
2622 let hash_len = self.format.raw_len();
2623 let table_start = self.oid_lookup_offset;
2624 let table_end = table_start + self.object_count * hash_len;
2625 let bytes = self.bytes.as_bytes();
2626 let table = &bytes[table_start..table_end];
2627 let needle = oid.as_bytes();
2628 let mut low = start;
2629 let mut high = end;
2630 while low < high {
2631 let mid = low + (high - low) / 2;
2632 let raw = &table[mid * hash_len..(mid + 1) * hash_len];
2633 match raw.cmp(needle) {
2634 std::cmp::Ordering::Less => low = mid + 1,
2635 std::cmp::Ordering::Equal => return Some(mid),
2636 std::cmp::Ordering::Greater => high = mid,
2637 }
2638 }
2639 None
2640 }
2641}
2642
2643pub(crate) fn validate_midx_pack_names(pack_names: &[String]) -> Result<()> {
2644 for name in pack_names {
2645 if name.is_empty() {
2646 return Err(GitError::InvalidFormat(
2647 "multi-pack-index pack name is empty".into(),
2648 ));
2649 }
2650 if name
2651 .bytes()
2652 .any(|byte| byte == 0 || matches!(byte, b'/' | b'\\'))
2653 {
2654 return Err(GitError::InvalidFormat(
2655 "multi-pack-index pack name contains an invalid byte".into(),
2656 ));
2657 }
2658 }
2659 Ok(())
2660}
2661
2662pub(crate) fn write_midx_pack_names(pack_names: &[String]) -> Vec<u8> {
2663 let mut out = Vec::new();
2664 for name in pack_names {
2665 out.extend_from_slice(name.as_bytes());
2666 out.push(0);
2667 }
2668 while out.len() % 4 != 0 {
2669 out.push(0);
2670 }
2671 out
2672}
2673
2674pub(crate) fn write_midx_oid_fanout(objects: &[&MultiPackIndexEntry]) -> Result<Vec<u8>> {
2675 let mut counts = [0u32; 256];
2676 for object in objects {
2677 let first = object.oid.as_bytes()[0] as usize;
2678 counts[first] = counts[first]
2679 .checked_add(1)
2680 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index fanout overflow".into()))?;
2681 }
2682 let mut running = 0u32;
2683 let mut out = Vec::with_capacity(256 * 4);
2684 for count in counts {
2685 running = running
2686 .checked_add(count)
2687 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index fanout overflow".into()))?;
2688 out.extend_from_slice(&running.to_be_bytes());
2689 }
2690 Ok(out)
2691}
2692
2693pub(crate) fn write_midx_oid_lookup(objects: &[&MultiPackIndexEntry]) -> Vec<u8> {
2694 let mut out = Vec::new();
2695 for object in objects {
2696 out.extend_from_slice(object.oid.as_bytes());
2697 }
2698 out
2699}
2700
2701pub(crate) fn write_midx_object_offsets(
2702 objects: &[&MultiPackIndexEntry],
2703 large_offsets: &mut Vec<u8>,
2704) -> Result<Vec<u8>> {
2705 let mut out = Vec::new();
2706 for object in objects {
2707 out.extend_from_slice(&object.pack_int_id.to_be_bytes());
2708 if object.offset < 0x8000_0000 && !object.force_large_offset {
2709 out.extend_from_slice(&(object.offset as u32).to_be_bytes());
2710 } else {
2711 let large_idx = large_offsets.len() / 8;
2712 if large_idx > 0x7fff_ffff {
2713 return Err(GitError::InvalidFormat(
2714 "too many multi-pack-index large offsets".into(),
2715 ));
2716 }
2717 out.extend_from_slice(&(0x8000_0000 | large_idx as u32).to_be_bytes());
2718 large_offsets.extend_from_slice(&object.offset.to_be_bytes());
2719 }
2720 }
2721 Ok(out)
2722}
2723
2724pub(crate) fn write_multi_pack_index_chunks(
2725 format: ObjectFormat,
2726 version: u8,
2727 pack_count: u32,
2728 chunks: &[([u8; 4], Vec<u8>)],
2729) -> Result<Vec<u8>> {
2730 if chunks.len() > u8::MAX as usize {
2731 return Err(GitError::InvalidFormat(
2732 "too many multi-pack-index chunks".into(),
2733 ));
2734 }
2735 let lookup_len = (chunks.len() + 1)
2736 .checked_mul(12)
2737 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index lookup overflow".into()))?;
2738 let mut out = Vec::new();
2739 out.extend_from_slice(b"MIDX");
2740 out.push(version);
2741 out.push(hash_function_id(format) as u8);
2742 out.push(chunks.len() as u8);
2743 out.push(0);
2744 out.extend_from_slice(&pack_count.to_be_bytes());
2745 let mut chunk_offset = (12usize)
2746 .checked_add(lookup_len)
2747 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index lookup overflow".into()))?
2748 as u64;
2749 for (id, data) in chunks {
2750 out.extend_from_slice(id);
2751 out.extend_from_slice(&chunk_offset.to_be_bytes());
2752 chunk_offset = chunk_offset
2753 .checked_add(data.len() as u64)
2754 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index size overflow".into()))?;
2755 }
2756 out.extend_from_slice(&[0, 0, 0, 0]);
2757 out.extend_from_slice(&chunk_offset.to_be_bytes());
2758 for (_id, data) in chunks {
2759 out.extend_from_slice(data);
2760 }
2761 let checksum = sley_core::digest_bytes(format, &out)?;
2762 out.extend_from_slice(checksum.as_bytes());
2763 Ok(out)
2764}
2765pub(crate) fn read_pack_index_fanout(bytes: &[u8], offset: &mut usize) -> Result<[u32; 256]> {
2766 let mut fanout = [0u32; 256];
2767 let mut previous = 0u32;
2768 for slot in &mut fanout {
2769 *slot = u32_be(&bytes[*offset..*offset + 4]);
2770 if *slot < previous {
2771 return Err(GitError::InvalidFormat(
2772 "pack index fanout is not monotonic".into(),
2773 ));
2774 }
2775 previous = *slot;
2776 *offset += 4;
2777 }
2778 Ok(fanout)
2779}
2780
2781pub(crate) fn validate_pack_index_oid_fanout(
2782 idx: usize,
2783 oid_bytes: &[u8],
2784 fanout: &[u32; 256],
2785) -> Result<()> {
2786 let expected_min = if oid_bytes[0] == 0 {
2787 0
2788 } else {
2789 fanout[usize::from(oid_bytes[0] - 1)]
2790 };
2791 if (idx as u32) < expected_min || (idx as u32) >= fanout[usize::from(oid_bytes[0])] {
2792 return Err(GitError::InvalidFormat(
2793 "pack index object id is outside its fanout bucket".into(),
2794 ));
2795 }
2796 Ok(())
2797}
2798
2799pub(crate) fn pack_index_v2_offset(raw_offset: u32, large_offset_table: &[u8]) -> Result<u64> {
2800 if raw_offset & 0x8000_0000 == 0 {
2801 return Ok(u64::from(raw_offset));
2802 }
2803 let large_idx = (raw_offset & 0x7fff_ffff) as usize;
2804 let large_start = large_idx
2805 .checked_mul(8)
2806 .ok_or_else(|| GitError::InvalidFormat("pack index large offset overflow".into()))?;
2807 let large_end = large_start
2808 .checked_add(8)
2809 .ok_or_else(|| GitError::InvalidFormat("pack index large offset overflow".into()))?;
2810 if large_end > large_offset_table.len() {
2811 return Err(GitError::InvalidFormat(
2812 "pack index large offset points past table".into(),
2813 ));
2814 }
2815 Ok(u64_be(&large_offset_table[large_start..large_end]))
2816}
2817pub(crate) fn parse_midx_pack_names(
2818 bytes: &[u8],
2819 chunks: &[MultiPackIndexChunk],
2820 pack_count: usize,
2821 version: u8,
2822) -> Result<Vec<String>> {
2823 let data = midx_chunk_data(bytes, chunks, *b"PNAM", true)?
2824 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index missing PNAM chunk".into()))?;
2825 let mut names = Vec::with_capacity(pack_count);
2826 let mut offset = 0usize;
2827 while names.len() < pack_count {
2828 let Some(relative_end) = data[offset..].iter().position(|byte| *byte == 0) else {
2829 return Err(GitError::InvalidFormat(
2830 "fatal: multi-pack-index pack-name chunk is too short".into(),
2831 ));
2832 };
2833 let name_bytes = &data[offset..offset + relative_end];
2834 if name_bytes.is_empty() {
2835 return Err(GitError::InvalidFormat(
2836 "multi-pack-index PNAM entry is empty".into(),
2837 ));
2838 }
2839 let name = std::str::from_utf8(name_bytes)
2840 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
2841 if name.bytes().any(|byte| matches!(byte, b'/' | b'\\')) {
2842 return Err(GitError::InvalidFormat(
2843 "multi-pack-index PNAM entry contains a path separator".into(),
2844 ));
2845 }
2846 names.push(name.to_string());
2847 offset += relative_end + 1;
2848 }
2849 let padding = &data[offset..];
2850 if padding.len() > 3 || padding.iter().any(|byte| *byte != 0) {
2851 return Err(GitError::InvalidFormat(
2852 "multi-pack-index PNAM padding is invalid".into(),
2853 ));
2854 }
2855 if version == 1 && names.windows(2).any(|pair| pair[0] > pair[1]) {
2856 return Err(GitError::InvalidFormat(
2857 "multi-pack-index v1 PNAM entries are not sorted".into(),
2858 ));
2859 }
2860 Ok(names)
2861}
2862
2863pub(crate) fn parse_midx_oid_fanout(
2864 bytes: &[u8],
2865 chunks: &[MultiPackIndexChunk],
2866) -> Result<([u32; 256], usize)> {
2867 let data = midx_chunk_data(bytes, chunks, *b"OIDF", true)?
2868 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index missing OIDF chunk".into()))?;
2869 if data.len() != 256 * 4 {
2870 return Err(GitError::InvalidFormat(
2871 "error: multi-pack-index OID fanout is of the wrong size\nfatal: multi-pack-index required OID fanout chunk missing or corrupted".into(),
2872 ));
2873 }
2874 let mut fanout = [0u32; 256];
2875 let mut previous = 0u32;
2876 for (idx, slot) in fanout.iter_mut().enumerate() {
2877 let start = idx * 4;
2878 *slot = u32_be(&data[start..start + 4]);
2879 if *slot < previous {
2880 return Err(GitError::InvalidFormat(format!(
2881 "error: oid fanout out of order: fanout[{}] = {:x} > {:x} = fanout[{idx}]\nfatal: multi-pack-index required OID fanout chunk missing or corrupted",
2882 idx - 1,
2883 previous,
2884 *slot
2885 )));
2886 }
2887 previous = *slot;
2888 }
2889 Ok((fanout, fanout[255] as usize))
2890}
2891
2892pub(crate) fn parse_midx_object_ids(
2893 bytes: &[u8],
2894 chunks: &[MultiPackIndexChunk],
2895 format: ObjectFormat,
2896 object_count: usize,
2897 fanout: &[u32; 256],
2898) -> Result<Vec<ObjectId>> {
2899 let data = midx_chunk_data(bytes, chunks, *b"OIDL", true)?
2900 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index missing OIDL chunk".into()))?;
2901 let expected_len = object_count
2902 .checked_mul(format.raw_len())
2903 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index OIDL chunk overflow".into()))?;
2904 if data.len() != expected_len {
2905 return Err(GitError::InvalidFormat(
2906 "error: multi-pack-index OID lookup chunk is the wrong size\nfatal: multi-pack-index required OID lookup chunk missing or corrupted".into(),
2907 ));
2908 }
2909
2910 let mut ids = Vec::with_capacity(object_count);
2911 let mut counts = [0u32; 256];
2912 let mut previous_oid: Option<ObjectId> = None;
2913 for idx in 0..object_count {
2914 let start = idx * format.raw_len();
2915 let oid = ObjectId::from_raw(format, &data[start..start + format.raw_len()])?;
2916 if let Some(previous) = &previous_oid
2917 && previous.as_bytes() >= oid.as_bytes()
2918 {
2919 return Err(GitError::InvalidFormat(
2920 "multi-pack-index OIDL object ids are not strictly sorted".into(),
2921 ));
2922 }
2923 counts[oid.as_bytes()[0] as usize] = counts[oid.as_bytes()[0] as usize]
2924 .checked_add(1)
2925 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index fanout overflow".into()))?;
2926 previous_oid = Some(oid);
2927 ids.push(oid);
2928 }
2929
2930 let mut running = 0u32;
2931 for (idx, count) in counts.iter().enumerate() {
2932 running = running
2933 .checked_add(*count)
2934 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index fanout overflow".into()))?;
2935 if fanout[idx] != running {
2936 return Err(GitError::InvalidFormat(
2937 "multi-pack-index OIDF fanout does not match OIDL".into(),
2938 ));
2939 }
2940 }
2941 Ok(ids)
2942}
2943
2944pub(crate) fn parse_midx_object_offsets(
2945 bytes: &[u8],
2946 chunks: &[MultiPackIndexChunk],
2947 object_ids: Vec<ObjectId>,
2948 pack_count: u32,
2949) -> Result<Vec<MultiPackIndexEntry>> {
2950 let data = midx_chunk_data(bytes, chunks, *b"OOFF", true)?
2951 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index missing OOFF chunk".into()))?;
2952 let expected_len = object_ids
2953 .len()
2954 .checked_mul(8)
2955 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index OOFF chunk overflow".into()))?;
2956 if data.len() != expected_len {
2957 return Err(GitError::InvalidFormat(
2958 "error: multi-pack-index object offset chunk is the wrong size\nfatal: multi-pack-index required object offsets chunk missing or corrupted".into(),
2959 ));
2960 }
2961 let large_offsets = midx_chunk_data(bytes, chunks, *b"LOFF", false)?;
2962 if let Some(large_offsets) = large_offsets
2963 && large_offsets.len() % 8 != 0
2964 {
2965 return Err(GitError::InvalidFormat(
2966 "multi-pack-index LOFF chunk has invalid length".into(),
2967 ));
2968 }
2969
2970 let mut entries = Vec::with_capacity(object_ids.len());
2971 for (idx, oid) in object_ids.into_iter().enumerate() {
2972 let start = idx * 8;
2973 let pack_int_id = u32_be(&data[start..start + 4]);
2974 if pack_int_id >= pack_count {
2975 return Err(GitError::InvalidFormat(
2976 "multi-pack-index object points past pack table".into(),
2977 ));
2978 }
2979 let raw_offset = u32_be(&data[start + 4..start + 8]);
2980 let offset = if raw_offset & 0x8000_0000 == 0 {
2981 u64::from(raw_offset)
2982 } else {
2983 let Some(large_offsets) = large_offsets else {
2984 return Err(GitError::InvalidFormat(
2985 "multi-pack-index large offset missing LOFF chunk".into(),
2986 ));
2987 };
2988 let large_idx = (raw_offset & 0x7fff_ffff) as usize;
2989 let large_start = large_idx.checked_mul(8).ok_or_else(|| {
2990 GitError::InvalidFormat("multi-pack-index LOFF index overflow".into())
2991 })?;
2992 let large_end = large_start.checked_add(8).ok_or_else(|| {
2993 GitError::InvalidFormat("multi-pack-index LOFF index overflow".into())
2994 })?;
2995 if large_end > large_offsets.len() {
2996 return Err(GitError::InvalidFormat(
2997 "fatal: multi-pack-index large offset out of bounds".into(),
2998 ));
2999 }
3000 u64_be(&large_offsets[large_start..large_end])
3001 };
3002 entries.push(MultiPackIndexEntry {
3003 oid,
3004 pack_int_id,
3005 offset,
3006 force_large_offset: raw_offset & 0x8000_0000 != 0,
3007 });
3008 }
3009 Ok(entries)
3010}
3011
3012pub(crate) fn parse_midx_reverse_index(
3013 bytes: &[u8],
3014 chunks: &[MultiPackIndexChunk],
3015 object_count: usize,
3016) -> Result<Option<Vec<u32>>> {
3017 let Some(data) = midx_chunk_data(bytes, chunks, *b"RIDX", false)? else {
3018 return Ok(None);
3019 };
3020 let expected_len = object_count
3021 .checked_mul(4)
3022 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index RIDX chunk overflow".into()))?;
3023 if data.len() != expected_len {
3024 return Err(GitError::InvalidFormat(
3025 "multi-pack-index reverse-index chunk is the wrong size".into(),
3026 ));
3027 }
3028 let mut positions = Vec::with_capacity(object_count);
3029 for idx in 0..object_count {
3030 let start = idx * 4;
3031 positions.push(u32_be(&data[start..start + 4]));
3032 }
3033 validate_position_permutation(&positions)?;
3034 Ok(Some(positions))
3035}
3036
3037pub(crate) fn parse_midx_bitmapped_packs(
3038 bytes: &[u8],
3039 chunks: &[MultiPackIndexChunk],
3040 pack_count: usize,
3041 object_count: usize,
3042) -> Result<Option<Vec<MultiPackBitmapPack>>> {
3043 let Some(data) = midx_chunk_data(bytes, chunks, *b"BTMP", false)? else {
3044 return Ok(None);
3045 };
3046 let expected_len = pack_count
3047 .checked_mul(8)
3048 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index BTMP chunk overflow".into()))?;
3049 if data.len() != expected_len {
3050 return Err(GitError::InvalidFormat(
3051 "multi-pack-index BTMP chunk has invalid length".into(),
3052 ));
3053 }
3054 let mut entries = Vec::with_capacity(pack_count);
3055 for idx in 0..pack_count {
3056 let start = idx * 8;
3057 let bitmap_pos = u32_be(&data[start..start + 4]);
3058 let bitmap_nr = u32_be(&data[start + 4..start + 8]);
3059 let bitmap_end = u64::from(bitmap_pos)
3060 .checked_add(u64::from(bitmap_nr))
3061 .ok_or_else(|| {
3062 GitError::InvalidFormat("multi-pack-index BTMP range overflow".into())
3063 })?;
3064 if bitmap_end > object_count as u64 {
3065 return Err(GitError::InvalidFormat(
3066 "multi-pack-index BTMP range points past object table".into(),
3067 ));
3068 }
3069 entries.push(MultiPackBitmapPack {
3070 bitmap_pos,
3071 bitmap_nr,
3072 });
3073 }
3074 Ok(Some(entries))
3075}
3076
3077pub(crate) fn midx_chunk_data<'a>(
3078 bytes: &'a [u8],
3079 chunks: &[MultiPackIndexChunk],
3080 id: [u8; 4],
3081 required: bool,
3082) -> Result<Option<&'a [u8]>> {
3083 let Some(chunk) = chunks.iter().find(|chunk| chunk.id == id) else {
3084 if required {
3085 return Err(GitError::InvalidFormat(format!(
3086 "multi-pack-index missing {} chunk",
3087 std::str::from_utf8(&id).unwrap_or("required")
3088 )));
3089 }
3090 return Ok(None);
3091 };
3092 let start = usize::try_from(chunk.offset)
3093 .map_err(|_| GitError::InvalidFormat("multi-pack-index chunk offset overflow".into()))?;
3094 let len = usize::try_from(chunk.len)
3095 .map_err(|_| GitError::InvalidFormat("multi-pack-index chunk length overflow".into()))?;
3096 let end = start
3097 .checked_add(len)
3098 .ok_or_else(|| GitError::InvalidFormat("multi-pack-index chunk range overflow".into()))?;
3099 let Some(data) = bytes.get(start..end) else {
3100 return Err(GitError::InvalidFormat(
3101 "multi-pack-index chunk extends past file".into(),
3102 ));
3103 };
3104 Ok(Some(data))
3105}
3106
3107pub(crate) fn hash_function_id(format: ObjectFormat) -> u32 {
3108 match format {
3109 ObjectFormat::Sha1 => 1,
3110 ObjectFormat::Sha256 => 2,
3111 }
3112}
3113
3114pub(crate) const EWAH_MAX_RUNNING_LEN: u64 = 0xffff_ffff;
3117
3118pub(crate) const EWAH_MAX_LITERAL_LEN: u64 = 0x7fff_ffff;
3121
3122pub(crate) const EWAH_ALL_ONES: u64 = u64::MAX;
3124
3125impl EwahBitmap {
3126 pub fn from_words(bit_size: u32, words: &[u64]) -> Result<Self> {
3140 let required_words = bit_size.div_ceil(64) as usize;
3141 if required_words > words.len() {
3142 return Err(GitError::InvalidFormat(format!(
3143 "EWAH bit_size {bit_size} requires {required_words} words but only {} supplied",
3144 words.len()
3145 )));
3146 }
3147 let significant = &words[..required_words];
3150 let mut builder = EwahBuilder::new(bit_size);
3151 for &word in significant {
3152 if word == 0 {
3153 builder.add_empty_words(false, 1);
3154 } else if word == EWAH_ALL_ONES {
3155 builder.add_empty_words(true, 1);
3156 } else {
3157 builder.add_literal(word);
3158 }
3159 }
3160 builder.finish()
3161 }
3162
3163 pub fn from_positions(bit_size: u32, positions: &[u32]) -> Result<Self> {
3169 let word_count = bit_size.div_ceil(64) as usize;
3170 let mut words = vec![0u64; word_count];
3171 for &position in positions {
3172 if position >= bit_size {
3173 return Err(GitError::InvalidFormat(format!(
3174 "EWAH bit position {position} out of range for bit_size {bit_size}"
3175 )));
3176 }
3177 let word_index = (position / 64) as usize;
3178 let bit_index = position % 64;
3179 words[word_index] |= 1u64 << bit_index;
3180 }
3181 Self::from_words(bit_size, &words)
3182 }
3183
3184 pub fn empty() -> Self {
3187 Self {
3188 bit_size: 0,
3189 words: Vec::new(),
3190 rlw_position: 0,
3191 }
3192 }
3193
3194 pub fn to_words(&self) -> Result<Vec<u64>> {
3200 let mut out = Vec::new();
3201 let mut word_idx = 0usize;
3202 while word_idx < self.words.len() {
3203 let rlw = self.words[word_idx];
3204 let run_bit = rlw & 1;
3205 let run_words = (rlw >> 1) & EWAH_MAX_RUNNING_LEN;
3206 let literal_words = (rlw >> 33) as usize;
3207 word_idx += 1;
3208 let fill = if run_bit == 1 { EWAH_ALL_ONES } else { 0 };
3209 for _ in 0..run_words {
3210 out.push(fill);
3211 }
3212 let literal_end = word_idx
3213 .checked_add(literal_words)
3214 .filter(|end| *end <= self.words.len())
3215 .ok_or_else(|| {
3216 GitError::InvalidFormat("EWAH literal words extend past word table".into())
3217 })?;
3218 out.extend_from_slice(&self.words[word_idx..literal_end]);
3219 word_idx = literal_end;
3220 }
3221 let required_words = (self.bit_size as usize).div_ceil(64);
3222 if out.len() < required_words {
3223 out.resize(required_words, 0);
3224 }
3225 out.truncate(required_words);
3226 Ok(out)
3227 }
3228
3229 pub fn to_positions(&self) -> Result<Vec<u32>> {
3231 let words = self.to_words()?;
3232 let mut positions = Vec::new();
3233 for (word_index, word) in words.iter().enumerate() {
3234 let mut remaining = *word;
3235 while remaining != 0 {
3236 let bit = remaining.trailing_zeros();
3237 let position = (word_index as u64) * 64 + u64::from(bit);
3238 if position < u64::from(self.bit_size) {
3239 positions.push(position as u32);
3241 }
3242 remaining &= remaining - 1;
3243 }
3244 }
3245 Ok(positions)
3246 }
3247
3248 pub fn to_bytes(&self) -> Vec<u8> {
3252 let mut out = Vec::with_capacity(12 + self.words.len() * 8);
3253 self.append_bytes(&mut out);
3254 out
3255 }
3256
3257 pub(crate) fn append_bytes(&self, out: &mut Vec<u8>) {
3258 out.extend_from_slice(&self.bit_size.to_be_bytes());
3259 out.extend_from_slice(&(self.words.len() as u32).to_be_bytes());
3260 for word in &self.words {
3261 out.extend_from_slice(&word.to_be_bytes());
3262 }
3263 out.extend_from_slice(&self.rlw_position.to_be_bytes());
3264 }
3265}
3266
3267pub(crate) struct EwahBuilder {
3275 bit_size: u32,
3276 words: Vec<u64>,
3277 rlw_position: usize,
3278}
3279
3280impl EwahBuilder {
3281 pub(crate) fn new(bit_size: u32) -> Self {
3282 Self {
3284 bit_size,
3285 words: vec![0u64],
3286 rlw_position: 0,
3287 }
3288 }
3289
3290 pub(crate) fn rlw(&self) -> u64 {
3291 self.words[self.rlw_position]
3292 }
3293
3294 pub(crate) fn set_rlw(&mut self, value: u64) {
3295 self.words[self.rlw_position] = value;
3296 }
3297
3298 pub(crate) fn rlw_running_len(&self) -> u64 {
3299 (self.rlw() >> 1) & EWAH_MAX_RUNNING_LEN
3300 }
3301
3302 pub(crate) fn rlw_running_bit(&self) -> bool {
3303 self.rlw() & 1 == 1
3304 }
3305
3306 pub(crate) fn rlw_literal_len(&self) -> u64 {
3307 self.rlw() >> 33
3308 }
3309
3310 pub(crate) fn set_running_bit(&mut self, bit: bool) {
3311 let mut value = self.rlw();
3312 value &= !1;
3313 value |= u64::from(bit);
3314 self.set_rlw(value);
3315 }
3316
3317 pub(crate) fn set_running_len(&mut self, len: u64) {
3318 let mut value = self.rlw();
3319 value &= !(EWAH_MAX_RUNNING_LEN << 1);
3320 value |= (len & EWAH_MAX_RUNNING_LEN) << 1;
3321 self.set_rlw(value);
3322 }
3323
3324 pub(crate) fn set_literal_len(&mut self, len: u64) {
3325 let mut value = self.rlw();
3326 value &= (1u64 << 33) - 1;
3327 value |= (len & EWAH_MAX_LITERAL_LEN) << 33;
3328 self.set_rlw(value);
3329 }
3330
3331 pub(crate) fn push_rlw(&mut self) {
3333 self.rlw_position = self.words.len();
3334 self.words.push(0);
3335 }
3336
3337 pub(crate) fn add_empty_words(&mut self, value: bool, mut number: u64) {
3345 while number > 0 {
3346 let can_extend = self.rlw_literal_len() == 0
3350 && (self.rlw_running_len() == 0 || self.rlw_running_bit() == value)
3351 && self.rlw_running_len() < EWAH_MAX_RUNNING_LEN;
3352 if !can_extend {
3353 self.push_rlw();
3354 }
3355 if self.rlw_running_len() == 0 {
3356 self.set_running_bit(value);
3357 }
3358 let available = EWAH_MAX_RUNNING_LEN - self.rlw_running_len();
3359 let take = available.min(number);
3360 self.set_running_len(self.rlw_running_len() + take);
3361 number -= take;
3362 }
3363 }
3364
3365 pub(crate) fn add_literal(&mut self, word: u64) {
3368 if self.rlw_literal_len() >= EWAH_MAX_LITERAL_LEN {
3369 self.push_rlw();
3370 }
3371 let literal_len = self.rlw_literal_len();
3372 self.set_literal_len(literal_len + 1);
3373 self.words.push(word);
3374 }
3375
3376 pub(crate) fn finish(self) -> Result<EwahBitmap> {
3377 let rlw_position = u32::try_from(self.rlw_position)
3378 .map_err(|_| GitError::InvalidFormat("EWAH RLW position overflow".into()))?;
3379 if self.words.len() > u32::MAX as usize {
3380 return Err(GitError::InvalidFormat("EWAH word table overflow".into()));
3381 }
3382 Ok(EwahBitmap {
3383 bit_size: self.bit_size,
3384 words: self.words,
3385 rlw_position,
3386 })
3387 }
3388}