use tokio::sync::mpsc;
pub use crate::internal::pack::index_entry::IndexEntry;
use crate::{errors::GitError, hash::ObjectHash, utils::HashAlgorithm};
pub struct IdxBuilder {
sender: Option<mpsc::Sender<Vec<u8>>>,
inner_hash: HashAlgorithm, object_number: usize,
pack_hash: ObjectHash,
}
impl IdxBuilder {
pub fn new(object_number: usize, sender: mpsc::Sender<Vec<u8>>, pack_hash: ObjectHash) -> Self {
Self {
sender: Some(sender),
inner_hash: HashAlgorithm::new(),
object_number,
pack_hash,
}
}
pub fn drop_sender(&mut self) {
self.sender.take(); }
async fn send_data(&mut self, data: Vec<u8>) -> Result<(), GitError> {
if let Some(sender) = &self.sender {
self.inner_hash.update(&data);
sender.send(data).await.map_err(|e| {
GitError::IOError(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
format!("Failed to send idx data: {e}"),
))
})?;
}
Ok(())
}
async fn send_data_without_update_hash(&mut self, data: Vec<u8>) -> Result<(), GitError> {
if let Some(sender) = &self.sender {
sender.send(data).await.map_err(|e| {
GitError::IOError(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
format!("Failed to send idx data: {e}"),
))
})?;
}
Ok(())
}
async fn send_u32(&mut self, v: u32) -> Result<(), GitError> {
self.send_data(v.to_be_bytes().to_vec()).await
}
async fn send_u64(&mut self, v: u64) -> Result<(), GitError> {
self.send_data(v.to_be_bytes().to_vec()).await
}
async fn write_header(&mut self) -> Result<(), GitError> {
let header: [u8; 8] = [0xFF, 0x74, 0x4F, 0x63, 0, 0, 0, 2];
self.send_data(header.to_vec()).await
}
async fn write_fanout(&mut self, entries: &mut [IndexEntry]) -> Result<(), GitError> {
entries.sort_by_key(|entry| entry.hash);
let mut fanout = [0u32; 256];
for entry in entries.iter() {
fanout[entry.hash.to_data()[0] as usize] += 1;
}
for i in 1..fanout.len() {
fanout[i] += fanout[i - 1];
}
for &count in fanout.iter() {
self.send_u32(count).await?;
}
Ok(())
}
async fn write_names(&mut self, entries: &Vec<IndexEntry>) -> Result<(), GitError> {
for e in entries {
self.send_data(e.hash.to_data().clone()).await?;
}
Ok(())
}
async fn write_crc32(&mut self, entries: &Vec<IndexEntry>) -> Result<(), GitError> {
for e in entries {
self.send_u32(e.crc32).await?;
}
Ok(())
}
async fn write_offsets(&mut self, entries: &Vec<IndexEntry>) -> Result<(), GitError> {
let mut large = vec![];
for e in entries {
if e.offset <= 0x7FFF_FFFF {
self.send_u32(e.offset as u32).await?;
} else {
let marker = 0x8000_0000 | large.len() as u32;
self.send_u32(marker).await?;
large.push(e.offset);
}
}
for v in large {
self.send_u64(v).await?;
}
Ok(())
}
async fn write_trailer(&mut self) -> Result<(), GitError> {
self.send_data_without_update_hash(self.pack_hash.to_data().clone())
.await?;
let idx_hash = self.inner_hash.clone().finalize();
self.send_data(idx_hash).await?;
Ok(())
}
pub async fn write_idx(&mut self, mut entries: Vec<IndexEntry>) -> Result<(), GitError> {
if entries.len() != self.object_number {
return Err(GitError::ConversionError(format!(
"entries length {} != object_number {}",
entries.len(),
self.object_number
)));
}
self.write_header().await?;
self.write_fanout(&mut entries).await?;
self.write_names(&entries).await?;
self.write_crc32(&entries).await?;
self.write_offsets(&entries).await?;
self.write_trailer().await?;
self.drop_sender();
Ok(())
}
}
#[cfg(test)]
mod tests {
use tokio::sync::mpsc;
use crate::{
errors::GitError,
hash::ObjectHash,
internal::pack::{index_entry::IndexEntry, pack_index::IdxBuilder},
};
fn fake_sha1(n: u8) -> ObjectHash {
ObjectHash::Sha1([n; 20])
}
fn build_entries_sha1(n: usize) -> Vec<IndexEntry> {
(0..n)
.map(|i| IndexEntry {
hash: fake_sha1(i as u8),
crc32: 0x12345678 + i as u32,
offset: 0x10 + (i as u64) * 3,
})
.collect()
}
#[tokio::test]
async fn test_idx_builder_sha1_basic() -> Result<(), GitError> {
let (tx, mut rx) = mpsc::channel::<Vec<u8>>(4096);
let object_number = 3;
let pack_hash = fake_sha1(0xAA);
let mut builder = IdxBuilder::new(object_number, tx, pack_hash);
let entries = build_entries_sha1(object_number);
builder.write_idx(entries).await?;
let mut out: Vec<u8> = Vec::new();
while let Some(chunk) = rx.recv().await {
out.extend_from_slice(&chunk);
}
assert_eq!(&out[0..8], &[0xFF, 0x74, 0x4F, 0x63, 0, 0, 0, 2]);
let fanout_start = 8;
let fanout_end = fanout_start + 256 * 4;
let fanout_bytes = &out[fanout_start..fanout_end];
let mut fanout = [0u32; 256];
fanout[0] = 1;
fanout[1] = 2;
fanout[2] = 3;
for item in fanout.iter_mut().skip(3) {
*item = 3;
}
for (i, val) in fanout.iter().enumerate() {
let idx = i * 4;
let v = u32::from_be_bytes([
fanout_bytes[idx],
fanout_bytes[idx + 1],
fanout_bytes[idx + 2],
fanout_bytes[idx + 3],
]);
assert_eq!(v, *val, "fanout mismatch at index {i}");
}
let names_start = fanout_end;
let names_end = names_start + object_number * 20; let names_bytes = &out[names_start..names_end];
for i in 0..object_number {
let name = &names_bytes[i * 20..i * 20 + 20];
assert!(name.iter().all(|b| *b == i as u8));
}
let crc_start = names_end;
let crc_end = crc_start + object_number * 4;
let crc_bytes = &out[crc_start..crc_end];
for i in 0..object_number {
let expected = 0x12345678 + i as u32;
let actual = u32::from_be_bytes([
crc_bytes[4 * i],
crc_bytes[4 * i + 1],
crc_bytes[4 * i + 2],
crc_bytes[4 * i + 3],
]);
assert_eq!(expected, actual);
}
let offset_start = crc_end;
let offset_end = offset_start + object_number * 4;
let offsets_bytes = &out[offset_start..offset_end];
for i in 0..object_number {
let expected = 0x10 + (i as u64) * 3;
let actual = u32::from_be_bytes([
offsets_bytes[i * 4],
offsets_bytes[i * 4 + 1],
offsets_bytes[i * 4 + 2],
offsets_bytes[i * 4 + 3],
]);
assert_eq!(expected as u32, actual);
}
let trailer_pack_hash_start = offset_end;
let trailer_pack_hash_end = trailer_pack_hash_start + 20;
let pack_hash_bytes = &out[trailer_pack_hash_start..trailer_pack_hash_end];
assert!(pack_hash_bytes.iter().all(|b| *b == 0xAA));
let idx_hash = &out[trailer_pack_hash_end..trailer_pack_hash_end + 20];
assert_eq!(idx_hash.len(), 20);
Ok(())
}
}