mod delta;
mod index;
use index::CachedObject;
pub(crate) use index::PackIndex;
use crate::{GitError, Object, ObjectId, ObjectKind, Result, error::invalid, inflate};
impl PackIndex {
pub(crate) fn read(
&self,
id: ObjectId,
max_object_bytes: usize,
max_delta_depth: usize,
external: &dyn Fn(ObjectId, usize) -> Result<Object>,
depth: usize,
) -> Result<Option<Object>> {
let Some(offset) = self.offset(id) else {
return Ok(None);
};
self.read_at(
id,
offset,
max_object_bytes,
max_delta_depth,
external,
depth,
)
.map(Some)
}
pub(crate) fn read_at(
&self,
id: ObjectId,
offset: u64,
max_object_bytes: usize,
max_delta_depth: usize,
external: &dyn Fn(ObjectId, usize) -> Result<Object>,
depth: usize,
) -> Result<Object> {
let (kind, data) =
self.decode(offset, max_object_bytes, max_delta_depth, external, depth)?;
Ok(Object { id, kind, data })
}
fn decode(
&self,
offset: u64,
max_object_bytes: usize,
max_delta_depth: usize,
external: &dyn Fn(ObjectId, usize) -> Result<Object>,
depth: usize,
) -> Result<(ObjectKind, Vec<u8>)> {
if depth > max_delta_depth {
return Err(GitError::LimitExceeded {
resource: "pack delta depth",
limit: max_delta_depth,
});
}
if let Some(object) = self.cached(offset) {
return Ok((object.kind, object.data));
}
let pack = self.pack_bytes()?;
validate_pack(pack, self.object_count())?;
let mut cursor = usize::try_from(offset).map_err(|_| invalid("pack offset overflow"))?;
let (kind, declared) = entry_header(pack, &mut cursor)?;
let base = match kind {
6 => Some(Base::Offset(ofs_base(pack, &mut cursor, offset)?)),
7 => {
let hash_len = self
.id_at_offset(offset)
.ok_or_else(|| invalid("pack index has no object for offset"))?
.kind()
.bytes();
let end = cursor
.checked_add(hash_len)
.ok_or_else(|| invalid("pack base id overflow"))?;
let bytes = pack
.get(cursor..end)
.ok_or_else(|| invalid("truncated REF_DELTA base id"))?;
cursor = end;
Some(Base::Id(ObjectId::from_bytes(bytes)?))
}
_ => None,
};
let inflate_limit = max_object_bytes.max(declared);
let (payload, _) = inflate::zlib(
pack.get(cursor..)
.ok_or_else(|| invalid("pack entry offset is out of bounds"))?,
inflate_limit,
)?;
if payload.len() != declared {
return Err(invalid("pack entry inflated size mismatch"));
}
if let Some(base) = base {
let (base_kind, base_data) = match base {
Base::Offset(base_offset) => self.decode(
base_offset,
max_object_bytes,
max_delta_depth,
external,
depth + 1,
)?,
Base::Id(base_id) => {
if let Some(base_offset) = self.offset(base_id) {
self.decode(
base_offset,
max_object_bytes,
max_delta_depth,
external,
depth + 1,
)?
} else {
let object = external(base_id, depth + 1)?;
(object.kind, object.data)
}
}
};
let object = (
base_kind,
delta::apply(&base_data, &payload, max_object_bytes)?,
);
self.cache(
offset,
CachedObject {
kind: object.0,
data: object.1.clone(),
},
);
return Ok(object);
}
let object_kind = match kind {
1 => ObjectKind::Commit,
2 => ObjectKind::Tree,
3 => ObjectKind::Blob,
4 => ObjectKind::Tag,
_ => return Err(invalid("invalid non-delta pack object kind")),
};
self.cache(
offset,
CachedObject {
kind: object_kind,
data: payload.clone(),
},
);
Ok((object_kind, payload))
}
}
enum Base {
Offset(u64),
Id(ObjectId),
}
fn validate_pack(pack: &[u8], expected_count: usize) -> Result<()> {
if pack.get(..4) != Some(b"PACK") {
return Err(invalid("invalid pack signature"));
}
let version = read_u32(pack, 4)?;
if version != 2 && version != 3 {
return Err(invalid("unsupported pack version"));
}
let count =
usize::try_from(read_u32(pack, 8)?).map_err(|_| invalid("pack object count overflow"))?;
if count != expected_count {
return Err(invalid("pack and index object counts differ"));
}
Ok(())
}
fn entry_header(input: &[u8], cursor: &mut usize) -> Result<(u8, usize)> {
let first = take(input, cursor)?;
let kind = (first >> 4) & 7;
let mut size = usize::from(first & 0x0f);
let mut shift = 4;
let mut byte = first;
while byte & 0x80 != 0 {
byte = take(input, cursor)?;
size = size
.checked_add(usize::from(byte & 0x7f) << shift)
.ok_or_else(|| invalid("pack entry size overflow"))?;
shift += 7;
if shift >= usize::BITS {
return Err(invalid("pack entry size is too long"));
}
}
Ok((kind, size))
}
fn ofs_base(input: &[u8], cursor: &mut usize, offset: u64) -> Result<u64> {
let mut byte = take(input, cursor)?;
let mut distance = u64::from(byte & 0x7f);
while byte & 0x80 != 0 {
byte = take(input, cursor)?;
distance = distance
.checked_add(1)
.and_then(|value| value.checked_shl(7))
.and_then(|value| value.checked_add(u64::from(byte & 0x7f)))
.ok_or_else(|| invalid("OFS_DELTA distance overflow"))?;
}
offset
.checked_sub(distance)
.ok_or_else(|| invalid("OFS_DELTA base precedes pack"))
}
fn take(input: &[u8], cursor: &mut usize) -> Result<u8> {
let value = *input
.get(*cursor)
.ok_or_else(|| invalid("truncated pack entry"))?;
*cursor += 1;
Ok(value)
}
fn read_u32(input: &[u8], offset: usize) -> Result<u32> {
let bytes = input
.get(offset..offset + 4)
.ok_or_else(|| invalid("truncated pack header"))?;
Ok(u32::from_be_bytes(bytes.try_into().expect("four bytes")))
}