#![no_std]
extern crate alloc;
#[cfg(kani)]
extern crate kani;
use alloc::vec::Vec;
use core::{error::Error, fmt, hash::Hash};
use zerocopy::byteorder::{LE, U16, U32, U64};
mod sealed {
pub trait BuildIndex {}
pub trait ZerocopyWord {}
}
pub trait BuildIndex: sealed::BuildIndex + Copy + Eq + Ord + fmt::Debug + Hash {
fn to_usize(self) -> Option<usize>;
fn from_usize(value: usize) -> Option<Self>;
}
macro_rules! impl_build_index {
($index:ty) => {
impl sealed::BuildIndex for $index {}
impl BuildIndex for $index {
fn to_usize(self) -> Option<usize> {
usize::try_from(self).ok()
}
fn from_usize(value: usize) -> Option<Self> {
Self::try_from(value).ok()
}
}
};
}
impl_build_index!(u16);
impl_build_index!(u32);
impl_build_index!(u64);
impl sealed::BuildIndex for usize {}
impl BuildIndex for usize {
fn to_usize(self) -> Option<usize> {
Some(self)
}
fn from_usize(value: usize) -> Option<Self> {
Some(value)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum IdOutOfBounds {
UsizeOverflow,
OutOfRange {
slot: usize,
count: usize,
},
}
impl fmt::Display for IdOutOfBounds {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UsizeOverflow => formatter.write_str("ID value did not fit in usize"),
Self::OutOfRange { slot, count } => write!(
formatter,
"ID slot {slot} is not less than the dense bound {count}"
),
}
}
}
impl Error for IdOutOfBounds {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum OffsetOverflow {
IndexOverflow {
value: usize,
},
}
impl fmt::Display for OffsetOverflow {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::IndexOverflow { value } => {
write!(
formatter,
"value {value} does not fit in the target index width"
)
}
}
}
}
impl Error for OffsetOverflow {}
#[inline]
pub fn id_to_slot<I: BuildIndex>(id: I, count: usize) -> Result<usize, IdOutOfBounds> {
let slot = id.to_usize().ok_or(IdOutOfBounds::UsizeOverflow)?;
if slot < count {
Ok(slot)
} else {
Err(IdOutOfBounds::OutOfRange { slot, count })
}
}
#[inline]
#[must_use]
pub fn slot_or_max<I: BuildIndex>(id: I) -> usize {
id.to_usize().unwrap_or(usize::MAX)
}
#[inline]
pub fn index_from_usize<O: BuildIndex>(value: usize) -> Result<O, OffsetOverflow> {
O::from_usize(value).ok_or(OffsetOverflow::IndexOverflow { value })
}
pub fn build_offset_index<O, T>(buckets: Vec<Vec<T>>) -> Result<(Vec<O>, Vec<T>), OffsetOverflow>
where
O: BuildIndex,
{
let mut offsets = Vec::with_capacity(buckets.len() + 1);
let mut items: Vec<T> = Vec::new();
offsets.push(index_from_usize::<O>(0)?);
for bucket in buckets {
items.extend(bucket);
offsets.push(index_from_usize::<O>(items.len())?);
}
Ok((offsets, items))
}
pub trait ZerocopyWord: sealed::ZerocopyWord + Copy {
fn read_as_usize(self) -> Option<usize>;
}
macro_rules! impl_native_zerocopy_word {
($word:ty) => {
impl sealed::ZerocopyWord for $word {}
impl ZerocopyWord for $word {
fn read_as_usize(self) -> Option<usize> {
usize::try_from(self).ok()
}
}
};
}
impl_native_zerocopy_word!(u16);
impl_native_zerocopy_word!(u32);
impl_native_zerocopy_word!(u64);
impl_native_zerocopy_word!(usize);
macro_rules! impl_le_zerocopy_word {
($word:ty) => {
impl sealed::ZerocopyWord for $word {}
impl ZerocopyWord for $word {
fn read_as_usize(self) -> Option<usize> {
usize::try_from(<$word>::get(self)).ok()
}
}
};
}
impl_le_zerocopy_word!(U16<LE>);
impl_le_zerocopy_word!(U32<LE>);
impl_le_zerocopy_word!(U64<LE>);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum OffsetIntegrityIssue {
Length {
expected: usize,
actual: usize,
},
FirstNonZero {
actual: usize,
},
NonMonotonic {
index: usize,
previous: usize,
actual: usize,
},
FinalMismatch {
final_offset: usize,
value_len: usize,
},
ValueOutOfRange {
index: usize,
value: usize,
bound: usize,
},
UsizeOverflow {
index: usize,
},
}
impl fmt::Display for OffsetIntegrityIssue {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Length { expected, actual } => write!(
formatter,
"offsets length {actual} does not match expected {expected}"
),
Self::FirstNonZero { actual } => {
write!(formatter, "first offset {actual} must be zero")
}
Self::NonMonotonic {
index,
previous,
actual,
} => write!(
formatter,
"offsets[{index}] = {actual} is less than offsets[{}] = {previous}",
index - 1
),
Self::FinalMismatch {
final_offset,
value_len,
} => write!(
formatter,
"final offset {final_offset} does not match values length {value_len}"
),
Self::ValueOutOfRange {
index,
value,
bound,
} => write!(
formatter,
"values[{index}] = {value} is not less than bound {bound}"
),
Self::UsizeOverflow { index } => write!(
formatter,
"word at slice index {index} did not fit in usize"
),
}
}
}
impl Error for OffsetIntegrityIssue {}
pub fn check_offsets_monotonic<W: ZerocopyWord>(offsets: &[W]) -> Result<(), OffsetIntegrityIssue> {
let mut previous: usize = 0;
for (index, word) in offsets.iter().copied().enumerate() {
let offset = word
.read_as_usize()
.ok_or(OffsetIntegrityIssue::UsizeOverflow { index })?;
if index == 0 {
if offset != 0 {
return Err(OffsetIntegrityIssue::FirstNonZero { actual: offset });
}
} else if offset < previous {
return Err(OffsetIntegrityIssue::NonMonotonic {
index,
previous,
actual: offset,
});
}
previous = offset;
}
Ok(())
}
pub fn check_value_range<W: ZerocopyWord>(
values: &[W],
bound: usize,
) -> Result<(), OffsetIntegrityIssue> {
for (index, word) in values.iter().copied().enumerate() {
let value = word
.read_as_usize()
.ok_or(OffsetIntegrityIssue::UsizeOverflow { index })?;
if value >= bound {
return Err(OffsetIntegrityIssue::ValueOutOfRange {
index,
value,
bound,
});
}
}
Ok(())
}
pub fn check_offset_section<W: ZerocopyWord>(
offsets: &[W],
expected_count: usize,
value_len: usize,
) -> Result<(), OffsetIntegrityIssue> {
let Some(expected) = expected_count.checked_add(1) else {
return Err(OffsetIntegrityIssue::Length {
expected: usize::MAX,
actual: offsets.len(),
});
};
if offsets.len() != expected {
return Err(OffsetIntegrityIssue::Length {
expected,
actual: offsets.len(),
});
}
check_offsets_monotonic(offsets)?;
let final_index = offsets.len() - 1;
let final_offset = offsets[final_index]
.read_as_usize()
.ok_or(OffsetIntegrityIssue::UsizeOverflow { index: final_index })?;
if final_offset != value_len {
return Err(OffsetIntegrityIssue::FinalMismatch {
final_offset,
value_len,
});
}
Ok(())
}
#[cfg(kani)]
mod proofs;