use nom::{
IResult, Parser,
bytes::complete::{tag, take},
error::{Error, ErrorKind},
multi::count,
number::complete::{be_u32, be_u64},
};
use std::fmt;
use crate::git::pack::PackError;
#[derive(Debug, Clone)]
pub struct PackIndex {
pub version: u32,
pub fan_out: [u32; 256],
pub object_names: Vec<[u8; 20]>,
pub crc32_checksums: Vec<u32>,
pub offsets: Vec<u32>,
pub large_offsets: Option<Vec<u64>>,
pub pack_checksum: [u8; 20],
pub index_checksum: [u8; 20],
pub raw_data: Vec<u8>,
}
impl PackIndex {
pub fn parse(input: &[u8]) -> IResult<&[u8], Self> {
let original_input = input;
let (input, _) = Self::parse_header(input)?;
let (input, fan_out) = Self::parse_fan_out_table(input)?;
let total_objects = fan_out[255] as usize;
let (input, object_names) = count(Self::parse_object_name, total_objects).parse(input)?;
let (input, crc32_checksums) = count(be_u32, total_objects).parse(input)?;
let (input, offsets) = count(be_u32, total_objects).parse(input)?;
let needs_large_offsets = offsets.iter().any(|&offset| offset & 0x80000000 != 0);
let large_offset_count = offsets
.iter()
.filter(|&&offset| offset & 0x80000000 != 0)
.count();
let (input, large_offsets) = if needs_large_offsets {
let (input, large_offsets) = count(be_u64, large_offset_count).parse(input)?;
(input, Some(large_offsets))
} else {
(input, None)
};
let (input, pack_checksum_bytes) = take(20usize)(input)?;
let mut pack_checksum = [0u8; 20];
pack_checksum.copy_from_slice(pack_checksum_bytes);
let (input, index_checksum_bytes) = take(20usize)(input)?;
let mut index_checksum = [0u8; 20];
index_checksum.copy_from_slice(index_checksum_bytes);
let consumed = original_input.len() - input.len();
let raw_data = original_input[..consumed].to_vec();
Ok((
input,
PackIndex {
version: 2, fan_out,
object_names,
crc32_checksums,
offsets,
large_offsets,
pack_checksum,
index_checksum,
raw_data,
},
))
}
fn parse_header(input: &[u8]) -> IResult<&[u8], ()> {
let magic_bytes: &[u8] = &[0xff, 0x74, 0x4f, 0x63];
let (input, _) = tag(magic_bytes)(input)?;
let (input, version) = be_u32(input)?;
if version != 2 {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Tag)));
}
Ok((input, ()))
}
fn parse_fan_out_table(input: &[u8]) -> IResult<&[u8], [u32; 256]> {
let (input, fan_out_vec) = count(be_u32, 256).parse(input)?;
let mut fan_out = [0u32; 256];
for (i, &value) in fan_out_vec.iter().enumerate() {
fan_out[i] = value;
}
for i in 1..256 {
if fan_out[i] < fan_out[i - 1] {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Verify)));
}
}
Ok((input, fan_out))
}
fn parse_object_name(input: &[u8]) -> IResult<&[u8], [u8; 20]> {
let (input, name_bytes) = take(20usize)(input)?;
let mut name = [0u8; 20];
name.copy_from_slice(name_bytes);
Ok((input, name))
}
pub fn object_count(&self) -> usize {
self.object_names.len()
}
pub fn lookup_object(&self, sha1: &[u8; 20]) -> Option<u64> {
let first_byte = sha1[0] as usize;
let start_idx = if first_byte == 0 {
0
} else {
self.fan_out[first_byte - 1] as usize
};
let end_idx = self.fan_out[first_byte] as usize;
let search_slice = &self.object_names[start_idx..end_idx];
match search_slice.binary_search(sha1) {
Ok(relative_idx) => {
let absolute_idx = start_idx + relative_idx;
Some(self.get_object_offset(absolute_idx))
}
Err(_) => None,
}
}
pub fn get_object_offset(&self, index: usize) -> u64 {
if index >= self.offsets.len() {
return 0;
}
let offset = self.offsets[index];
if offset & 0x80000000 != 0 {
let large_offset_index = (offset & 0x7fffffff) as usize;
if let Some(ref large_offsets) = self.large_offsets
&& large_offset_index < large_offsets.len()
{
return large_offsets[large_offset_index];
}
}
offset as u64
}
pub fn get_object_crc32(&self, index: usize) -> Option<u32> {
self.crc32_checksums.get(index).copied()
}
pub fn verify_checksum(&self) -> Result<(), PackError> {
Ok(())
}
}
impl fmt::Display for PackIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Pack Index (version {})", self.version)?;
writeln!(f, "Total objects: {}", self.object_count())?;
writeln!(f, "Pack checksum: {}", hex::encode(self.pack_checksum))?;
writeln!(f, "Index checksum: {}", hex::encode(self.index_checksum))?;
if let Some(ref large_offsets) = self.large_offsets {
writeln!(f, "Large offsets: {} entries", large_offsets.len())?;
}
writeln!(f, "\nObject distribution by first byte:")?;
let mut prev_count = 0;
for (byte, &count) in self.fan_out.iter().enumerate() {
let objects_for_byte = count - prev_count;
if objects_for_byte > 0 {
writeln!(f, " 0x{:02x}: {} objects", byte, objects_for_byte)?;
}
prev_count = count;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_header() {
let header_data = [
0xff, 0x74, 0x4f, 0x63, 0x00, 0x00, 0x00, 0x02, ];
let (remaining, _) = PackIndex::parse_header(&header_data).unwrap();
assert!(remaining.is_empty());
}
#[test]
fn test_parse_object_name() {
let name_data = [0x01; 20];
let (remaining, name) = PackIndex::parse_object_name(&name_data).unwrap();
assert!(remaining.is_empty());
assert_eq!(name, [0x01; 20]);
}
#[test]
fn test_fan_out_validation() {
let mut fan_out_data = Vec::new();
for i in 0..256 {
fan_out_data.extend_from_slice(&(i as u32).to_be_bytes());
}
let (_, fan_out) = PackIndex::parse_fan_out_table(&fan_out_data).unwrap();
assert_eq!(fan_out[0], 0);
assert_eq!(fan_out[255], 255);
}
#[test]
fn test_lookup_object() {
let mut fan_out = [0u32; 256];
fan_out[0] = 1; for i in 1..256 {
fan_out[i] = 1; }
let object_names = vec![[0x00; 20]]; let crc32_checksums = vec![0x12345678];
let offsets = vec![100];
let index = PackIndex {
version: 2,
fan_out,
object_names,
crc32_checksums,
offsets,
large_offsets: None,
pack_checksum: [0; 20],
index_checksum: [0; 20],
raw_data: vec![],
};
assert_eq!(index.lookup_object(&[0x00; 20]), Some(100));
assert_eq!(index.lookup_object(&[0xff; 20]), None);
}
#[test]
fn test_multiple_objects_lookup() {
let mut fan_out = [0u32; 256];
fan_out[0] = 2; fan_out[1] = 3; for i in 2..0xaa {
fan_out[i] = 3; }
fan_out[0xaa] = 4; for i in 0xab..0xff {
fan_out[i] = 4; }
fan_out[0xff] = 5;
let mut obj1 = [0u8; 20];
obj1[0] = 0x00;
let mut obj2 = [0u8; 20];
obj2[0] = 0x00;
obj2[1] = 0x01; let mut obj3 = [0u8; 20];
obj3[0] = 0x01;
let mut obj4 = [0u8; 20];
obj4[0] = 0xaa;
let mut obj5 = [0u8; 20];
obj5[0] = 0xff;
let object_names = vec![obj1, obj2, obj3, obj4, obj5];
let crc32_checksums = vec![0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555];
let offsets = vec![100, 200, 300, 400, 500];
let index = PackIndex {
version: 2,
fan_out,
object_names,
crc32_checksums,
offsets,
large_offsets: None,
pack_checksum: [0; 20],
index_checksum: [0; 20],
raw_data: vec![],
};
assert_eq!(index.lookup_object(&obj1), Some(100));
assert_eq!(index.lookup_object(&obj2), Some(200));
assert_eq!(index.lookup_object(&obj3), Some(300));
assert_eq!(index.lookup_object(&obj4), Some(400));
assert_eq!(index.lookup_object(&obj5), Some(500));
let mut nonexistent = [0u8; 20];
nonexistent[0] = 0x80; assert_eq!(index.lookup_object(&nonexistent), None);
assert_eq!(index.get_object_crc32(0), Some(0x11111111));
assert_eq!(index.get_object_crc32(4), Some(0x55555555));
assert_eq!(index.get_object_crc32(10), None); }
#[test]
fn test_large_offsets() {
let mut fan_out = [0u32; 256];
fan_out[0] = 1;
for i in 1..256 {
fan_out[i] = 1;
}
let object_names = vec![[0x00; 20]];
let crc32_checksums = vec![0x12345678];
let offsets = vec![0x80000000]; let large_offsets = Some(vec![0x123456789abcdef0]);
let index = PackIndex {
version: 2,
fan_out,
object_names,
crc32_checksums,
offsets,
large_offsets,
pack_checksum: [0; 20],
index_checksum: [0; 20],
raw_data: vec![],
};
assert_eq!(index.get_object_offset(0), 0x123456789abcdef0);
assert_eq!(index.lookup_object(&[0x00; 20]), Some(0x123456789abcdef0));
}
}