use super::*;
use crate::cache::SegmentBytes;
const OFFSET_BYTES: usize = 8;
const MAXIMA_BYTES: usize = 24;
pub(crate) fn encode_verified_view(
reference: &GraphDescriptorPageRef,
kind: GraphDescriptorKind,
epoch: u64,
mut encoded: Vec<u8>,
limits: GraphDescriptorPageLimits,
) -> Result<Vec<u8>, GraphDescriptorPageError> {
let page =
ImmutableGraphDescriptorPage::decode_bound(reference, kind, epoch, &encoded, limits)?;
let (tag, max_key, max_value, max_child_bytes) = match &page.body {
ImmutableGraphDescriptorPageBody::Leaf(entries) => (
LEAF_ENTRY_FIELD,
entries
.iter()
.map(|entry| entry.key.len())
.max()
.unwrap_or(0),
entries
.iter()
.map(|entry| entry.value.len())
.max()
.unwrap_or(0),
0,
),
ImmutableGraphDescriptorPageBody::Interior(entries) => (
INTERIOR_ENTRY_FIELD,
entries
.iter()
.map(|entry| {
entry
.child
.lower_bound
.len()
.max(entry.child.upper_bound.len())
})
.max()
.unwrap_or(0),
0,
entries
.iter()
.map(|entry| entry.child.length.get())
.max()
.unwrap_or(0),
),
};
let mut offsets = Vec::new();
let mut offset = PAGE_HEADER_BYTES;
for field in Fields::new(&encoded[PAGE_HEADER_BYTES..]) {
let (field_tag, payload) = field?;
if field_tag == tag {
offsets.extend_from_slice(&(offset as u64).to_le_bytes());
}
offset += FIELD_HEADER_BYTES + payload.len();
}
encoded.extend_from_slice(&offsets);
encoded.extend_from_slice(&(max_key as u64).to_le_bytes());
encoded.extend_from_slice(&(max_value as u64).to_le_bytes());
encoded.extend_from_slice(&max_child_bytes.to_le_bytes());
Ok(encoded)
}
pub(crate) struct GraphDescriptorPageView {
bytes: SegmentBytes,
physical_len: usize,
count: usize,
leaf: bool,
}
impl GraphDescriptorPageView {
pub(crate) fn from_verified_cache(
bytes: SegmentBytes,
reference: &GraphDescriptorPageRef,
kind: GraphDescriptorKind,
epoch: u64,
limits: GraphDescriptorPageLimits,
) -> Result<Self, GraphDescriptorPageError> {
reference.validate(limits)?;
let physical_len = usize::try_from(reference.length.get()).map_err(|_| {
GraphDescriptorPageError::Admission(
"descriptor page length exceeds address space".into(),
)
})?;
if bytes.len() < PAGE_HEADER_BYTES || physical_len > bytes.len() {
return Err(corrupt("truncated verified descriptor page"));
}
let count = read_u32(&bytes[36..40]) as usize;
validate_entry_count(count, limits, ErrorClass::Admission)?;
let expected_len = count
.checked_mul(OFFSET_BYTES)
.and_then(|length| length.checked_add(MAXIMA_BYTES))
.and_then(|length| length.checked_add(physical_len))
.ok_or_else(|| corrupt("verified descriptor page length overflow"))?;
if bytes.len() != expected_len
|| &bytes[..8] != PAGE_MAGIC
|| read_u16(&bytes[8..10]) != PAGE_VERSION
|| bytes[11] != kind.tag()
|| read_u64(&bytes[12..20]) != reference.physical_generation
|| read_u64(&bytes[20..28]) != epoch
|| read_u64(&bytes[28..36]) != reference.page_id.get()
|| read_u64(&bytes[40..48]) != (physical_len - PAGE_HEADER_BYTES) as u64
|| read_u32(&bytes[48..52]) != reference.content_crc32c.get()
|| &bytes[52..84] != reference.content_sha256.as_bytes()
{
return Err(corrupt(
"verified descriptor page does not match its reference",
));
}
let maxima = &bytes[expected_len - MAXIMA_BYTES..];
if read_u64(&maxima[..8]) > limits.max_key_bytes.get() as u64
|| read_u64(&maxima[8..16]) > limits.max_value_bytes.get() as u64
|| read_u64(&maxima[16..24]) > limits.max_page_bytes.get() as u64
{
return Err(GraphDescriptorPageError::Admission(
"verified descriptor page exceeds current codec limits".into(),
));
}
let leaf = match GraphDescriptorPageKind::from_tag(bytes[10])? {
GraphDescriptorPageKind::Leaf => true,
GraphDescriptorPageKind::Interior => false,
};
let page = Self {
bytes,
physical_len,
count,
leaf,
};
let (lower, upper) = if leaf {
(page.leaf_entry(0)?.0, page.leaf_entry(count - 1)?.0)
} else {
(page.child_bounds(0)?.0, page.child_bounds(count - 1)?.1)
};
if lower != reference.lower_bound || upper != reference.upper_bound {
return Err(corrupt(
"verified descriptor page key range does not match its reference",
));
}
Ok(page)
}
pub(crate) fn is_leaf(&self) -> bool {
self.leaf
}
pub(crate) fn len(&self) -> usize {
self.count
}
pub(crate) fn lower_bound(
&self,
target: &[u8],
) -> Result<(usize, u64), GraphDescriptorPageError> {
let mut low = 0;
let mut high = self.count;
let mut comparisons = 0;
while low < high {
comparisons += 1;
let middle = low + (high - low) / 2;
let key = if self.leaf {
self.leaf_entry(middle)?.0
} else {
self.child_bounds(middle)?.1
};
if key < target {
low = middle + 1;
} else {
high = middle;
}
}
Ok((low, comparisons))
}
pub(crate) fn leaf_entry(
&self,
index: usize,
) -> Result<(&[u8], &[u8]), GraphDescriptorPageError> {
let entry = self.entry(index)?;
let (key, offset) = decode_bytes(entry, 0, usize::MAX, "verified leaf key")?;
let (value, _) = decode_bytes(entry, offset, usize::MAX, "verified descriptor value")?;
Ok((key, value))
}
pub(crate) fn child_bounds(
&self,
index: usize,
) -> Result<(&[u8], &[u8]), GraphDescriptorPageError> {
let entry = self.entry(index)?;
let (lower, offset) = decode_bytes(entry, 76, usize::MAX, "verified child lower bound")?;
let (upper, _) = decode_bytes(entry, offset, usize::MAX, "verified child upper bound")?;
Ok((lower, upper))
}
pub(crate) fn child(
&self,
index: usize,
limits: GraphDescriptorPageLimits,
) -> Result<GraphDescriptorPageRef, GraphDescriptorPageError> {
decode_page_ref(self.entry(index)?, limits)
}
fn entry(&self, index: usize) -> Result<&[u8], GraphDescriptorPageError> {
if index >= self.count {
return Err(corrupt("verified descriptor entry index out of bounds"));
}
let slot = self.physical_len + index * OFFSET_BYTES;
let offset = usize::try_from(read_u64(&self.bytes[slot..slot + OFFSET_BYTES]))
.map_err(|_| corrupt("verified descriptor entry offset exceeds address space"))?;
let physical = &self.bytes[..self.physical_len];
let header = physical
.get(offset..offset.saturating_add(FIELD_HEADER_BYTES))
.ok_or_else(|| corrupt("truncated verified descriptor field header"))?;
let start = offset + FIELD_HEADER_BYTES;
let end = start
.checked_add(read_u32(&header[2..6]) as usize)
.ok_or_else(|| corrupt("verified descriptor field length overflow"))?;
physical
.get(start..end)
.ok_or_else(|| corrupt("truncated verified descriptor field"))
}
}
fn corrupt(message: &str) -> GraphDescriptorPageError {
GraphDescriptorPageError::Corrupt(message.into())
}