use core::fmt::{self, Display, Formatter};
use core::mem;
pub const REVOCATION_SECTION_NAME: &str = ".sbatlevel";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RevocationSectionError {
MissingVersion,
InvalidVersion(u32),
MissingHeader,
InvalidPreviousOffset(u32),
InvalidLatestOffset(u32),
MissingPreviousNull,
MissingLatestNull,
}
impl Display for RevocationSectionError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::MissingVersion => {
write!(f, "missing version field")
}
Self::InvalidVersion(version) => {
write!(f, "invalid version: {version}")
}
Self::MissingHeader => {
write!(f, "missing payload header")
}
Self::InvalidPreviousOffset(offset) => {
write!(f, "invalid previous offset: {offset}")
}
Self::InvalidLatestOffset(offset) => {
write!(f, "invalid latest offset: {offset}")
}
Self::MissingPreviousNull => {
write!(f, "missing null terminator for previous data")
}
Self::MissingLatestNull => {
write!(f, "missing null terminator for latest data")
}
}
}
}
impl core::error::Error for RevocationSectionError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RevocationSection<'a> {
previous: &'a [u8],
latest: &'a [u8],
}
impl<'a> RevocationSection<'a> {
#[allow(clippy::missing_panics_doc)]
pub fn parse(
mut data: &'a [u8],
) -> Result<RevocationSection<'a>, RevocationSectionError> {
use RevocationSectionError::*;
const PAYLOAD_HEADER_SIZE: usize = mem::size_of::<u32>() * 2;
let version_size = mem::size_of::<u32>();
if data.len() < version_size {
return Err(MissingVersion);
}
let version =
u32::from_le_bytes(data[..version_size].try_into().unwrap());
if version != 0 {
return Err(InvalidVersion(version));
}
data = &data[version_size..];
if data.len() < PAYLOAD_HEADER_SIZE {
return Err(MissingHeader);
}
let previous_offset = u32::from_le_bytes(data[..4].try_into().unwrap());
let latest_offset = u32::from_le_bytes(data[4..8].try_into().unwrap());
let previous_start = usize::try_from(previous_offset)
.map_err(|_| InvalidPreviousOffset(previous_offset))?;
let latest_start = usize::try_from(latest_offset)
.map_err(|_| InvalidLatestOffset(latest_offset))?;
if previous_start >= data.len() {
return Err(InvalidPreviousOffset(previous_offset));
}
if latest_start >= data.len() {
return Err(InvalidLatestOffset(latest_offset));
}
let previous_len = data[previous_start..]
.iter()
.position(|b| *b == 0)
.ok_or(MissingPreviousNull)?;
let latest_len = data[latest_start..]
.iter()
.position(|b| *b == 0)
.ok_or(MissingLatestNull)?;
let previous = &data
[previous_start..previous_start.checked_add(previous_len).unwrap()];
let latest =
&data[latest_start..latest_start.checked_add(latest_len).unwrap()];
Ok(Self { previous, latest })
}
#[must_use]
pub fn previous(&self) -> &[u8] {
self.previous
}
#[must_use]
pub fn latest(&self) -> &[u8] {
self.latest
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let _ = format!("{}", RevocationSectionError::MissingVersion);
let _ = format!("{}", RevocationSectionError::InvalidVersion(1));
let _ = format!("{}", RevocationSectionError::MissingHeader);
let _ = format!("{}", RevocationSectionError::InvalidPreviousOffset(0));
let _ = format!("{}", RevocationSectionError::InvalidLatestOffset(0));
let _ = format!("{}", RevocationSectionError::MissingPreviousNull);
let _ = format!("{}", RevocationSectionError::MissingLatestNull);
}
}