git-zero-core 0.2.0

Allocation-free no_std Git objects, repositories, packs, zlib and delta primitives
Documentation
use crate::{Error, ObjectFormat, ObjectId, ObjectInfo, ObjectKind, Result, zlib::adler::Adler32};

pub(super) fn read(bytes: &[u8], format: ObjectFormat, ids: &mut [ObjectId]) -> Result<()> {
    let end = bytes
        .len()
        .checked_sub(format.id_length())
        .ok_or(Error::CorruptPack)?;
    let (payload, trailer) = bytes.split_at(end);
    let mut hash = format.checked_hash();
    hash.update(payload);
    if hash.finish().map_err(|_| Error::CorruptPack)?.as_bytes() != trailer {
        return Err(Error::CorruptPack);
    }
    let mut input = payload;
    if take(&mut input, 8)? != b"PACK\0\0\0\x02"
        || u32::from_be_bytes(take(&mut input, 4)?.try_into().unwrap()) as u64 != ids.len() as u64
    {
        return Err(Error::CorruptPack);
    }
    for id in ids {
        *id = object(&mut input, format)?;
    }
    if !input.is_empty() {
        return Err(Error::CorruptPack);
    }
    Ok(())
}

fn object(input: &mut &[u8], format: ObjectFormat) -> Result<ObjectId> {
    let info = entry(input)?;
    let mut hash = format.checked_hash();
    hash.update(info.header(&mut [0; 32])?);
    let zlib = take(input, 2)?;
    if zlib[0] & 15 != 8
        || zlib[0] >> 4 > 7
        || zlib[1] & 32 != 0
        || u16::from_be_bytes(zlib.try_into().unwrap()) % 31 != 0
    {
        return Err(Error::InvalidZlib);
    }
    let mut adler = Adler32::new();
    let mut remaining = info.size;
    loop {
        let flags = take(input, 1)?[0];
        if flags & 6 != 0 {
            return Err(Error::Unsupported);
        }
        let lengths = take(input, 4)?;
        let length = u16::from_le_bytes(lengths[..2].try_into().unwrap());
        if length != !u16::from_le_bytes(lengths[2..].try_into().unwrap()) {
            return Err(Error::InvalidZlib);
        }
        remaining = remaining
            .checked_sub(u64::from(length))
            .ok_or(Error::CorruptPack)?;
        let body = take(input, usize::from(length))?;
        hash.update(body);
        adler.update(body);
        if flags & 1 != 0 {
            break;
        }
    }
    if remaining != 0 || u32::from_be_bytes(take(input, 4)?.try_into().unwrap()) != adler.value() {
        return Err(Error::CorruptPack);
    }
    hash.finish().map_err(|_| Error::CorruptPack)
}

fn entry(input: &mut &[u8]) -> Result<ObjectInfo> {
    let first = take(input, 1)?[0];
    let kind = match (first >> 4) & 7 {
        1 => ObjectKind::Commit,
        2 => ObjectKind::Tree,
        3 => ObjectKind::Blob,
        4 => ObjectKind::Tag,
        _ => return Err(Error::Unsupported),
    };
    let mut size = u64::from(first & 15);
    let mut byte = first;
    let mut shift = 4;
    while byte & 128 != 0 {
        byte = take(input, 1)?[0];
        let bits = u64::from(byte & 127);
        if shift >= 64 || bits > u64::MAX >> shift {
            return Err(Error::CorruptPack);
        }
        size |= bits << shift;
        shift += 7;
    }
    Ok(ObjectInfo { kind, size })
}

fn take<'a>(input: &mut &'a [u8], count: usize) -> Result<&'a [u8]> {
    let bytes = input.get(..count).ok_or(Error::CorruptPack)?;
    *input = &input[count..];
    Ok(bytes)
}