use std::cmp::Ordering;
use std::ops::Range;
use super::bloom::{BloomFilter, BloomLayout};
use super::{Chunks, GraphCommit, GraphError, GraphLimits, bad, id, long, word};
use crate::{ObjectFormat, ObjectId};
const NO_PARENT: u32 = 0x7000_0000;
const EXTRA_EDGES: u32 = 0x8000_0000;
const LARGE_OFFSET: u32 = 0x8000_0000;
#[derive(Clone, Debug)]
pub(super) struct Layer<B = Vec<u8>> {
bytes: B,
format: ObjectFormat,
start: usize,
count: usize,
fanout: Range<usize>,
ids: Range<usize>,
data: Range<usize>,
edges: Range<usize>,
offsets: Option<Range<usize>>,
overflow: Range<usize>,
bloom: Option<BloomLayout>,
}
impl<'a> Layer<&'a [u8]> {
pub(super) fn parse(
bytes: &'a [u8],
format: ObjectFormat,
limits: GraphLimits,
start: usize,
hashes: &[ObjectId],
parents_left: &mut usize,
) -> Result<Self, GraphError> {
let chunks = Chunks::table(bytes, format)?;
if usize::from(bytes[7]) != hashes.len() {
return Err(bad("base graph count"));
}
let base_bytes = chunks.optional(b"BASE").unwrap_or_default();
if !base_bytes
.chunks(format.digest_len())
.map(Some)
.eq(hashes.iter().map(|hash| Some(hash.as_bytes())))
{
return Err(bad("base graph identities"));
}
let fanout = chunks.required(b"OIDF")?;
if fanout.len() != 1024 {
return Err(bad("fanout length"));
}
let count = word(fanout, 1020)? as usize;
if count > limits.max_commits {
return Err(GraphError::Limit("commits"));
}
let ids = chunks.required(b"OIDL")?;
let data = chunks.required(b"CDAT")?;
let width = format.digest_len();
if ids.len() / width != count
|| ids.len() % width != 0
|| Some(data.len()) != count.checked_mul(width + 16)
{
return Err(bad("commit table length"));
}
validate_fanout(fanout, ids, width)?;
let offsets = chunks.optional(b"GDA2");
if offsets.is_some_and(|g| g.len() != count * 4) {
return Err(bad("generation table length"));
}
let ranges = |tag| chunks.range(tag).unwrap_or_default();
let (fanout, ids, data, edges) = (
ranges(b"OIDF"),
ranges(b"OIDL"),
ranges(b"CDAT"),
ranges(b"EDGE"),
);
let (offsets, overflow) = (chunks.range(b"GDA2"), ranges(b"GDO2"));
let has_edges = chunks.range(b"EDGE").is_some();
let bloom = BloomLayout::parse(&chunks, count);
let layer = Self {
bytes,
format,
start,
count,
fanout,
ids,
data,
edges,
offsets,
overflow,
bloom,
};
layer.check_records(has_edges, parents_left)?;
Ok(layer)
}
pub(super) fn detach(self) -> Layer<()> {
Layer {
bytes: (),
format: self.format,
start: self.start,
count: self.count,
fanout: self.fanout,
ids: self.ids,
data: self.data,
edges: self.edges,
offsets: self.offsets,
overflow: self.overflow,
bloom: self.bloom,
}
}
}
impl Layer<()> {
pub(super) fn attach(self, bytes: Vec<u8>) -> Layer {
Layer {
bytes,
format: self.format,
start: self.start,
count: self.count,
fanout: self.fanout,
ids: self.ids,
data: self.data,
edges: self.edges,
offsets: self.offsets,
overflow: self.overflow,
bloom: self.bloom,
}
}
}
impl<B: AsRef<[u8]>> Layer<B> {
fn check_records(&self, has_edges: bool, parents_left: &mut usize) -> Result<(), GraphError> {
let encoded_parents = self
.count
.checked_mul(2)
.and_then(|inline| inline.checked_add(self.edges.len() / 4))
.ok_or(GraphError::Limit("parents"))?;
let budget = (*parents_left).min(encoded_parents);
let mut left = budget;
let limit = self.start + self.count;
let mut charge = |parent: u32| {
left = left.checked_sub(1).ok_or(GraphError::Limit("parents"))?;
if parent as usize >= limit {
return Err(bad("parent position"));
}
Ok(())
};
let edges = &self.bytes.as_ref()[self.edges.clone()];
for index in 0..self.count {
let (first, second) = self.parent_fields(index);
if first == NO_PARENT {
if second != NO_PARENT {
return Err(bad("second parent without first"));
}
} else {
charge(first)?;
if second & EXTRA_EDGES != 0 {
if !has_edges {
return Err(bad("missing required chunk"));
}
if edges.len() % 4 != 0 {
return Err(bad("extra edge length"));
}
let mut at = ((second & !EXTRA_EDGES) as usize)
.checked_mul(4)
.ok_or(bad("extra edge offset"))?;
loop {
let edge = word(edges, at)?;
charge(edge & !EXTRA_EDGES)?;
if edge & EXTRA_EDGES != 0 {
break;
}
at += 4;
}
} else if second != NO_PARENT {
charge(second)?;
}
}
if let Some(offsets) = &self.offsets {
corrected_date(
&self.bytes.as_ref()[offsets.clone()],
&self.bytes.as_ref()[self.overflow.clone()],
index,
self.commit_time(index),
)?;
}
}
*parents_left -= budget - left;
Ok(())
}
pub(super) fn start(&self) -> usize {
self.start
}
pub(super) fn count(&self) -> usize {
self.count
}
pub(super) fn checksum(&self) -> ObjectId {
let bytes = self.bytes.as_ref();
let trailer = bytes.len() - self.format.digest_len();
ObjectId::from_bytes(self.format, &bytes[trailer..])
.expect("trailer has the format's digest width")
}
pub(super) fn has_corrected_dates(&self) -> bool {
self.offsets.is_some()
}
pub(super) fn find(&self, id: ObjectId) -> Option<usize> {
let id = id.as_bytes();
let width = self.format.digest_len();
if id.len() != width {
return None;
}
let bucket = usize::from(id[0]);
let fanout = &self.bytes.as_ref()[self.fanout.clone()];
let mut low = if bucket == 0 {
0
} else {
be32(fanout, (bucket - 1) * 4) as usize
};
let mut high = be32(fanout, bucket * 4) as usize;
let ids = &self.bytes.as_ref()[self.ids.clone()];
while low < high {
let middle = low + (high - low) / 2;
match ids[middle * width..(middle + 1) * width].cmp(id) {
Ordering::Less => low = middle + 1,
Ordering::Greater => high = middle,
Ordering::Equal => return Some(middle),
}
}
None
}
pub(super) fn id(&self, index: usize) -> ObjectId {
let width = self.format.digest_len();
let at = self.ids.start + index * width;
ObjectId::from_bytes(self.format, &self.bytes.as_ref()[at..at + width])
.expect("ID table entries have the format's digest width")
}
fn record(&self, index: usize) -> &[u8] {
let width = self.format.digest_len() + 16;
let at = self.data.start + index * width;
&self.bytes.as_ref()[at..at + width]
}
fn parent_fields(&self, index: usize) -> (u32, u32) {
let record = self.record(index);
let width = self.format.digest_len();
(be32(record, width), be32(record, width + 4))
}
pub(super) fn parents_into(&self, index: usize, parents: &mut Vec<u32>) {
let (first, second) = self.parent_fields(index);
if first == NO_PARENT {
return;
}
parents.push(first);
if second & EXTRA_EDGES != 0 {
let edges = &self.bytes.as_ref()[self.edges.clone()];
let mut at = (second & !EXTRA_EDGES) as usize * 4;
loop {
let edge = be32(edges, at);
parents.push(edge & !EXTRA_EDGES);
if edge & EXTRA_EDGES != 0 {
break;
}
at += 4;
}
} else if second != NO_PARENT {
parents.push(second);
}
}
pub(super) fn commit_time(&self, index: usize) -> u64 {
let record = self.record(index);
let width = self.format.digest_len();
let high = be32(record, width + 8);
(u64::from(high & 3) << 32) | u64::from(be32(record, width + 12))
}
pub(super) fn level(&self, index: usize) -> u32 {
be32(self.record(index), self.format.digest_len() + 8) >> 2
}
pub(super) fn corrected_date(&self, index: usize) -> Option<u64> {
let offsets = self.offsets.as_ref()?;
let date = corrected_date(
&self.bytes.as_ref()[offsets.clone()],
&self.bytes.as_ref()[self.overflow.clone()],
index,
self.commit_time(index),
);
Some(date.expect("corrected dates were checked while parsing"))
}
pub(super) fn bloom(&self, index: usize) -> Option<BloomFilter<'_>> {
self.bloom.as_ref()?.filter(self.bytes.as_ref(), index)
}
pub(super) fn decode(&self, index: usize, id_at: impl Fn(u32) -> ObjectId) -> GraphCommit {
let mut positions = Vec::new();
self.parents_into(index, &mut positions);
let width = self.format.digest_len();
GraphCommit {
id: self.id(index),
tree: id(self.format, &self.record(index)[..width])
.expect("records hold a tree ID of the format's digest width"),
parents: positions.into_iter().map(id_at).collect(),
commit_time: self.commit_time(index),
generation: self.level(index),
corrected_commit_date: self.corrected_date(index),
}
}
}
fn validate_fanout(fanout: &[u8], ids: &[u8], width: usize) -> Result<(), GraphError> {
let mut position = 0;
for bucket in 0..256 {
while position < ids.len() / width && usize::from(ids[position * width]) <= bucket {
position += 1;
}
if word(fanout, bucket * 4)? as usize != position {
return Err(bad("fanout contents"));
}
}
if ids
.chunks_exact(width)
.zip(ids.chunks_exact(width).skip(1))
.any(|(a, b)| a >= b)
{
return Err(bad("object ID ordering"));
}
Ok(())
}
fn corrected_date(
offsets: &[u8],
overflow: &[u8],
index: usize,
time: u64,
) -> Result<u64, GraphError> {
let offset = word(offsets, index * 4)?;
let offset = if offset & LARGE_OFFSET == 0 {
u64::from(offset)
} else {
if overflow.is_empty() {
return Err(bad("missing required chunk"));
}
long(
overflow,
((offset & !LARGE_OFFSET) as usize)
.checked_mul(8)
.ok_or(bad("generation overflow offset"))?,
)?
};
time.checked_add(offset)
.ok_or(bad("corrected date overflow"))
}
fn be32(bytes: &[u8], at: usize) -> u32 {
let mut word = [0; 4];
word.copy_from_slice(&bytes[at..at + 4]);
u32::from_be_bytes(word)
}