git-zero-core 0.2.0

Allocation-free no_std Git objects, repositories, packs, zlib and delta primitives
Documentation
use crate::{Error, Result, io::Write};

pub const MAX_LENGTH: usize = 65520;

pub fn write(output: &mut impl Write, data: &[u8]) -> Result<()> {
    write_parts(output, &[data])
}

/// Write one packet from borrowed parts without a contiguous scratch buffer.
pub fn write_parts(output: &mut impl Write, parts: &[&[u8]]) -> Result<()> {
    let length = parts
        .iter()
        .try_fold(4_usize, |length, part| length.checked_add(part.len()))
        .filter(|&n| n <= MAX_LENGTH)
        .ok_or(Error::InvalidPacket)?;
    let digits = b"0123456789abcdef";
    output.write_all(&[
        digits[(length >> 12) & 15],
        digits[(length >> 8) & 15],
        digits[(length >> 4) & 15],
        digits[length & 15],
    ])?;
    for part in parts {
        output.write_all(part)?;
    }
    Ok(())
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Packet<'a> {
    Data(&'a [u8]),
    Flush,
}

/// Decode one complete four-byte pkt-line prefix without requiring its body.
/// Zero denotes a flush; reserved control values 1 through 3 are rejected.
pub fn length(prefix: &[u8]) -> Result<usize> {
    if prefix.len() != 4 || !prefix.iter().all(u8::is_ascii_hexdigit) {
        return Err(Error::InvalidPacket);
    }
    let prefix = core::str::from_utf8(prefix).map_err(|_| Error::InvalidPacket)?;
    let length = usize::from_str_radix(prefix, 16).map_err(|_| Error::InvalidPacket)?;
    if length != 0 && !(4..=MAX_LENGTH).contains(&length) {
        return Err(Error::InvalidPacket);
    }
    Ok(length)
}

pub fn read<'a>(input: &mut &'a [u8]) -> Result<Option<Packet<'a>>> {
    if input.is_empty() {
        return Ok(None);
    }
    let prefix = input.get(..4).ok_or(Error::InvalidPacket)?;
    let length = length(prefix)?;
    if length == 0 {
        *input = &input[4..];
        return Ok(Some(Packet::Flush));
    }
    let bytes = input.get(4..length).ok_or(Error::InvalidPacket)?;
    *input = &input[length..];
    Ok(Some(Packet::Data(bytes)))
}