use core::{error::Error, fmt};
use crate::{LayoutIndex, ZerocopyWord};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum OffsetIntegrityIssue {
Length {
expected: usize,
actual: usize,
},
CountOverflow {
count: 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::CountOverflow { count } => {
write!(formatter, "element count {count} + 1 overflows usize")
}
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 {}
#[inline]
#[must_use]
pub fn index_to_usize_validated<I: LayoutIndex>(value: I) -> Option<usize> {
value.to_usize()
}
#[inline]
#[must_use]
pub fn usize_to_index_validated<I: LayoutIndex>(value: usize) -> Option<I> {
I::from_usize(value)
}
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::CountOverflow {
count: expected_count,
});
};
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(())
}