initial packfile index generation
Diff
Cargo.lock | 23 +++++++++++++++++++++++
Cargo.toml | 4 +++-
src/main.rs | 32 +++++++++++++++++++++++++-------
src/git/packfile.rs | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
4 files changed, 172 insertions(+), 54 deletions(-)
@@ -190,6 +190,8 @@
"async-trait",
"axum",
"bytes",
"const-sha1",
"crc",
"env_logger",
"flate2",
"futures",
@@ -212,13 +214,34 @@
]
[[package]]
name = "const-sha1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb58b6451e8c2a812ad979ed1d83378caa5e927eef2622017a45f251457c2c9d"
[[package]]
name = "cpufeatures"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
dependencies = [
"libc",
]
[[package]]
name = "crc"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10c2722795460108a7872e1cd933a85d6ec38abc4baecad51028f702da28889f"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403"
[[package]]
name = "crc32fast"
@@ -20,4 +20,6 @@
tokio-util = { version = "0.6", features = ["codec"] }
bytes = "1"
flate2 = "1.0"
sha-1 = "0.9"
sha-1 = "0.9"
const-sha1 = "0.2"
crc = "2"
@@ -1,11 +1,11 @@
pub mod git;
use crate::git::PktLine;
use git::codec::GitCodec;
use bytes::BytesMut;
use futures::future::Future;
use git::codec::Encoder;
use git::codec::GitCodec;
use std::{fmt::Write, pin::Pin, sync::Arc};
use thrussh::server::{Auth, Session};
use thrussh::*;
@@ -165,15 +165,27 @@
}
if fetch {
git::packfile::PackFile {
entries: vec![
git::packfile::PackFileEntry {
entry_type: git::packfile::PackFileEntryType::Commit,
data: vec![0, 1, 2, 3, 4],
sha1: [0; 20],
}
]
}.encode_to(&mut self.output_bytes).unwrap();
let packfile = git::packfile::PackFile::new(vec![git::packfile::PackFileEntry::new(
git::packfile::PackFileEntryType::Blob,
b"testing this is a test cool test",
)?]);
{
let mut buf = BytesMut::new();
let packfile_index = git::packfile::PackFileIndex {
packfile: &packfile,
};
packfile_index.encode_to(&mut buf)?;
self.write(PktLine::Data(buf.as_ref()))?;
}
{
let mut buf = BytesMut::new();
packfile.encode_to(&mut buf)?;
self.write(PktLine::Data(buf.as_ref()))?;
}
self.write(PktLine::Flush)?;
self.flush(&mut session, channel);
}
@@ -1,7 +1,7 @@
use bytes::{BufMut, BytesMut};
use const_sha1::{sha1, ConstBuffer};
use flate2::{write::ZlibEncoder, Compression};
use sha1::{Digest, Sha1};
use std::fmt::Write;
use std::convert::TryInto;
use std::io::Write as IoWrite;
@@ -9,24 +9,78 @@
pub struct PackFileIndex<const S: usize> {
pub fanout: [[u8; 4]; 255],
pub size: u16,
pub sha1: [[u8; 20]; S],
pub crc: [[u8; 4]; S],
pub offset: [[u8; 4]; S],
pub packfile_checksum: [u8; 20],
pub idxfiel_checksum: [u8; 20],
pub struct PackFileIndex<'a> {
pub packfile: &'a PackFile,
}
impl<const S: usize> PackFileIndex<S> {
pub fn encode_to(self, buf: &mut BytesMut) -> Result<(), anyhow::Error> {
buf.extend_from_slice(b"\xFFtOc");
buf.put_u8(2);
impl<'a> PackFileIndex<'a> {
pub fn encode_to(self, original_buf: &mut BytesMut) -> Result<(), anyhow::Error> {
use sha1::{Sha1, Digest};
let mut buf = original_buf.split();
buf.extend_from_slice(&[255u8, 116u8, 79u8, 99u8]);
buf.put_u32(2);
let mut totals_by_first_byte = [0u32; 256];
for entry in &self.packfile.entries {
totals_by_first_byte[entry.uncompressed_sha1[0] as usize] += 1;
}
let mut cumulative = 0;
for i in 0..256usize {
cumulative += totals_by_first_byte[i];
buf.put_u32(cumulative);
}
for entry in &self.packfile.entries {
buf.extend_from_slice(&entry.uncompressed_sha1);
}
for entry in &self.packfile.entries {
buf.put_u32(entry.compressed_crc32);
}
let mut offset = PackFile::header_size();
for entry in &self.packfile.entries {
offset += entry.compressed_data.len();
let mut offset_be = offset.to_be();
while offset_be != 0 {
let mut val = (offset_be & 0b1111111) as u8;
offset_be >>= 7;
if offset_be != 0 {
val |= 1 << 7;
}
buf.put_u8(val);
}
}
buf.extend_from_slice(&self.packfile.hash);
let mut hasher = Sha1::new();
hasher.update(&buf);
let result = hasher.finalize();
buf.extend_from_slice(result.as_ref());
original_buf.unsplit(buf);
Ok(())
}
@@ -39,38 +93,50 @@
pub struct PackFile {
pub entries: Vec<PackFileEntry>,
entries: Vec<PackFileEntry>,
hash: [u8; 20],
}
impl PackFile {
pub fn new(mut entries: Vec<PackFileEntry>) -> Self {
entries.sort_unstable_by_key(|v| v.uncompressed_sha1[0]);
let hash_buffer = entries.iter().fold(ConstBuffer::new(), |acc, curr| {
acc.push_slice(&curr.uncompressed_sha1)
});
Self {
entries,
hash: sha1(&hash_buffer).bytes(),
}
}
pub const fn header_size() -> usize {
4 + std::mem::size_of::<u32>() + std::mem::size_of::<u32>()
}
pub fn encode_to(self, buf: &mut BytesMut) -> Result<(), anyhow::Error> {
buf.extend_from_slice(b"PACK");
buf.extend_from_slice(b"0002");
write!(buf, "{:04x}", self.entries.len())?;
buf.put_u32(2);
buf.put_u32(self.entries.len().try_into().unwrap());
for entry in &self.entries {
entry.encode_to(buf)?;
}
let mut hasher = Sha1::new();
for entry in &self.entries {
hasher.update(entry.sha1);
}
let hash = hasher.finalize();
buf.extend_from_slice(&hash.as_slice());
buf.extend_from_slice(&self.hash);
Ok(())
}
}
pub enum PackFileEntryType {
@@ -85,7 +151,7 @@
Commit,
@@ -101,16 +167,34 @@
}
pub struct PackFileEntry {
pub entry_type: PackFileEntryType,
pub data: Vec<u8>,
pub sha1: [u8; 20],
entry_type: PackFileEntryType,
compressed_data: Vec<u8>,
compressed_crc32: u32,
uncompressed_sha1: [u8; 20],
uncompressed_size: usize,
}
impl PackFileEntry {
fn size_of_data(&self) -> usize {
self.data.len() as usize
pub fn new(entry_type: PackFileEntryType, data: &[u8]) -> Result<Self, anyhow::Error> {
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(&data)?;
let compressed_data = e.finish()?;
let compressed_crc32 = crc::Crc::<u32>::new(&crc::CRC_32_CKSUM).checksum(&compressed_data);
Ok(Self {
entry_type,
compressed_data,
compressed_crc32,
uncompressed_sha1: sha1(&ConstBuffer::new().push_slice(data)).bytes(),
uncompressed_size: data.len(),
})
}
fn size_of_data_be(&self) -> usize {
self.uncompressed_size.to_be()
}
@@ -119,7 +203,7 @@
fn write_header(&self, buf: &mut BytesMut) {
let mut size = self.size_of_data();
let mut size = self.size_of_data_be();
{
@@ -134,7 +218,7 @@
} << 4;
val |= (size & 0b1111) as u8;
size >>= 4;
@@ -143,13 +227,13 @@
while size != 0 {
let mut val = (size & 0b1111111) as u8;
size >>= 7;
if size != 0 {
val |= 1 << 7;
}
@@ -159,10 +243,7 @@
pub fn encode_to(&self, buf: &mut BytesMut) -> Result<(), anyhow::Error> {
self.write_header(buf);
let mut e = ZlibEncoder::new(buf.as_mut(), Compression::default());
e.write_all(self.data.as_ref())?;
e.finish()?;
buf.extend_from_slice(&self.compressed_data);
Ok(())
}