use crate::{GitError, Result};
pub fn u16_be(bytes: &[u8]) -> u16 {
u16::from_be_bytes([bytes[0], bytes[1]])
}
pub fn u32_be(bytes: &[u8]) -> u32 {
u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}
pub fn u64_be(bytes: &[u8]) -> u64 {
u64::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])
}
pub fn read_u16(bytes: &[u8], offset: usize) -> Result<u16> {
let raw = bytes
.get(offset..offset + 2)
.ok_or_else(|| GitError::InvalidFormat("truncated uint16".into()))?;
Ok(u16::from_be_bytes([raw[0], raw[1]]))
}
pub fn read_u24(bytes: &[u8], offset: usize) -> Result<u32> {
let raw = bytes
.get(offset..offset + 3)
.ok_or_else(|| GitError::InvalidFormat("truncated uint24".into()))?;
Ok((u32::from(raw[0]) << 16) | (u32::from(raw[1]) << 8) | u32::from(raw[2]))
}
pub fn read_u32(bytes: &[u8], offset: usize) -> Result<u32> {
let raw = bytes
.get(offset..offset + 4)
.ok_or_else(|| GitError::InvalidFormat("truncated uint32".into()))?;
Ok(u32::from_be_bytes([raw[0], raw[1], raw[2], raw[3]]))
}
pub fn read_u64(bytes: &[u8], offset: usize) -> Result<u64> {
let raw = bytes
.get(offset..offset + 8)
.ok_or_else(|| GitError::InvalidFormat("truncated uint64".into()))?;
Ok(u64::from_be_bytes([
raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6], raw[7],
]))
}
pub fn get_u16_le(bytes: &[u8], offset: usize) -> Option<u16> {
Some(u16::from_le_bytes(
bytes.get(offset..offset + 2)?.try_into().ok()?,
))
}
pub fn get_u32_le(bytes: &[u8], offset: usize) -> Option<u32> {
Some(u32::from_le_bytes(
bytes.get(offset..offset + 4)?.try_into().ok()?,
))
}
pub fn get_u64_le(bytes: &[u8], offset: usize) -> Option<u64> {
Some(u64::from_le_bytes(
bytes.get(offset..offset + 8)?.try_into().ok()?,
))
}
pub fn get_u32_be(bytes: &[u8], offset: usize) -> Option<u32> {
Some(u32::from_be_bytes(
bytes.get(offset..offset + 4)?.try_into().ok()?,
))
}
pub fn write_u24(out: &mut Vec<u8>, value: u32) {
out.push((value >> 16) as u8);
out.push((value >> 8) as u8);
out.push(value as u8);
}
pub fn write_u24_at(out: &mut [u8], offset: usize, value: u32) -> Result<()> {
let target = out
.get_mut(offset..offset + 3)
.ok_or_else(|| GitError::InvalidFormat("uint24 write is out of bounds".into()))?;
target[0] = (value >> 16) as u8;
target[1] = (value >> 8) as u8;
target[2] = value as u8;
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BiasedVarintError {
Truncated,
Overflow,
}
pub fn read_biased_varint(
bytes: &[u8],
cursor: &mut usize,
) -> std::result::Result<u64, BiasedVarintError> {
let Some(mut byte) = bytes.get(*cursor).copied() else {
return Err(BiasedVarintError::Truncated);
};
*cursor += 1;
let mut value = u64::from(byte & 0x7f);
while byte & 0x80 != 0 {
let Some(next) = bytes.get(*cursor).copied() else {
return Err(BiasedVarintError::Truncated);
};
byte = next;
*cursor += 1;
value = value
.checked_add(1)
.and_then(|value| value.checked_shl(7))
.and_then(|value| value.checked_add(u64::from(byte & 0x7f)))
.ok_or(BiasedVarintError::Overflow)?;
}
Ok(value)
}
pub fn write_biased_varint(mut value: u64, out: &mut Vec<u8>) {
let mut groups = [0u8; 10];
let mut len = 0;
groups[len] = (value & 0x7f) as u8;
len += 1;
value >>= 7;
while value != 0 {
value -= 1;
groups[len] = 0x80 | (value & 0x7f) as u8;
len += 1;
value >>= 7;
}
out.extend(groups[..len].iter().rev());
}
pub fn common_prefix_len<T: PartialEq>(left: &[T], right: &[T]) -> usize {
left.iter()
.zip(right.iter())
.take_while(|(left, right)| left == right)
.count()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixed_width_be_reads_round_trip() {
assert_eq!(u16_be(&[0x12, 0x34]), 0x1234);
assert_eq!(u32_be(&[0x12, 0x34, 0x56, 0x78]), 0x1234_5678);
assert_eq!(
u64_be(&[0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78]),
0x1234_5678_u64
);
}
fn err_string<T>(result: crate::Result<T>) -> String {
match result {
Ok(_) => panic!("expected Err"),
Err(err) => err.to_string(),
}
}
#[test]
fn offset_readers_report_truncation() {
assert_eq!(err_string(read_u16(&[0x12], 0)), "invalid format: truncated uint16");
assert_eq!(read_u16(&[0x12, 0x34], 0).expect("in bounds"), 0x1234);
assert_eq!(err_string(read_u24(&[1, 2], 0)), "invalid format: truncated uint24");
assert_eq!(read_u24(&[0, 0xab, 0xcd, 0xef], 1).expect("in bounds"), 0xab_cdef);
assert_eq!(err_string(read_u32(&[0; 3], 0)), "invalid format: truncated uint32");
assert_eq!(err_string(read_u64(&[0; 7], 0)), "invalid format: truncated uint64");
}
#[test]
fn offset_getters_return_none_when_short() {
assert_eq!(get_u16_le(&[0x01, 0x02], 0), Some(0x0201));
assert_eq!(get_u16_le(&[0x01], 0), None);
assert_eq!(get_u32_le(&[1, 2, 3, 4], 0), Some(0x0403_0201));
assert_eq!(get_u32_le(&[1, 2, 3, 4], 1), None);
assert_eq!(get_u64_le(&[1; 8], 0), Some(0x0101_0101_0101_0101));
assert_eq!(get_u32_be(&[1, 2, 3, 4], 0), Some(0x0102_0304));
assert_eq!(get_u32_be(&[], 0), None);
}
#[test]
fn u24_writers_round_trip() {
let mut buf = Vec::new();
write_u24(&mut buf, 0x0a_bc_de);
assert_eq!(buf, vec![0x0a, 0xbc, 0xde]);
let mut scratch = [0xff_u8; 5];
write_u24_at(&mut scratch, 1, 0x01_02_03).expect("in bounds");
assert_eq!(&scratch, &[0xff, 0x01, 0x02, 0x03, 0xff]);
assert!(write_u24_at(&mut scratch, 4, 0).is_err());
}
#[test]
fn biased_varint_matches_ofs_delta_encoding() {
let mut out = Vec::new();
write_biased_varint(0, &mut out);
assert_eq!(out, vec![0x00]);
out.clear();
write_biased_varint(127, &mut out);
assert_eq!(out, vec![0x7f]);
out.clear();
write_biased_varint(128, &mut out);
assert_eq!(out, vec![0x80, 0x00]);
out.clear();
write_biased_varint(0xffff_ffff_ffff_ffff, &mut out);
let mut cursor = 0usize;
let decoded =
read_biased_varint(&out, &mut cursor).expect("test operation should succeed");
assert_eq!(decoded, 0xffff_ffff_ffff_ffff);
assert_eq!(cursor, out.len());
let mut cursor = 0usize;
assert_eq!(
read_biased_varint(&[0x81, 0x81], &mut cursor),
Err(BiasedVarintError::Truncated)
);
assert_eq!(cursor, 2);
}
#[test]
fn biased_varint_rejects_overflow() {
let mut hostile = Vec::new();
write_biased_varint(u64::MAX, &mut hostile);
let last = hostile.len() - 1;
hostile[last] = 0xff; hostile.push(0x00); let mut cursor = 0usize;
assert_eq!(
read_biased_varint(&hostile, &mut cursor),
Err(BiasedVarintError::Overflow)
);
}
#[test]
fn common_prefix_counts_shared_elements() {
assert_eq!(common_prefix_len(b"refs/heads/main", b"refs/heads/next"), 11);
assert_eq!(common_prefix_len(b"", b""), 0);
assert_eq!(common_prefix_len(b"abc", b"abd"), 2);
assert_eq!(common_prefix_len(b"ab", b"abcd"), 2);
assert_eq!(common_prefix_len(&[1u16, 2, 3], &[1u16, 9]), 1);
}
}