use crate::csv::{CsvIter, trim_ascii_at_null};
use crate::{Component, Entry, ImageSbat, ParseError};
use ascii::AsciiStr;
use core::ptr;
const MAX_HEADER_FIELDS: usize = 3;
#[must_use]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ValidationResult<'a> {
Allowed,
Revoked(Entry<'a>),
}
pub struct RevokedComponents<'a>(CsvIter<'a, MAX_HEADER_FIELDS>);
impl<'a> Iterator for RevokedComponents<'a> {
type Item = Component<'a>;
fn next(&mut self) -> Option<Self::Item> {
let next = self.0.next()?;
let record = next.unwrap();
Some(Component::from_record(&record).unwrap())
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
#[repr(transparent)]
pub struct RevocationSbat(AsciiStr);
impl RevocationSbat {
pub fn parse(input: &[u8]) -> Result<&Self, ParseError> {
let input = trim_ascii_at_null(input)?;
let iter = CsvIter::<{ MAX_HEADER_FIELDS }>::new(input);
for record in iter {
let record = record?;
Component::from_record(&record)?;
}
Ok(Self::from_ascii_str_unchecked(input))
}
#[allow(unsafe_code)]
pub(crate) fn from_ascii_str_unchecked(s: &AsciiStr) -> &Self {
unsafe { &*(ptr::from_ref(s) as *const Self) }
}
#[must_use]
pub fn as_csv(&self) -> &AsciiStr {
&self.0
}
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn date(&self) -> Option<&AsciiStr> {
let mut iter = CsvIter::<{ MAX_HEADER_FIELDS }>::new(&self.0);
let record = iter.next()?.unwrap();
record.get_field(2)
}
#[must_use]
pub fn revoked_components(&self) -> RevokedComponents<'_> {
RevokedComponents(CsvIter::new(&self.0))
}
#[must_use]
pub fn is_component_revoked(&self, input: &Component) -> bool {
self.revoked_components().any(|revoked_component| {
input.name == revoked_component.name
&& input.generation < revoked_component.generation
})
}
pub fn validate_image<'i>(
&self,
image_sbat: &'i ImageSbat,
) -> ValidationResult<'i> {
if let Some(revoked_entry) = image_sbat
.entries()
.find(|entry| self.is_component_revoked(&entry.component))
{
ValidationResult::Revoked(revoked_entry)
} else {
ValidationResult::Allowed
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Generation, RevocationSbat, Vendor};
#[cfg(feature = "alloc")]
use crate::RevocationSbatOwned;
const VALID_SBAT: &[u8] = b"sbat,1,2021030218\ncompA,1\ncompB,2";
fn ascii(s: &str) -> &AsciiStr {
AsciiStr::from_ascii(s).unwrap()
}
fn make_component(name: &str, generation: u32) -> Component<'_> {
Component::new(ascii(name), Generation::new(generation).unwrap())
}
fn make_entry(name: &str, generation: u32) -> Entry<'_> {
Entry::new(make_component(name, generation), Vendor::default())
}
fn parse_success_helper(revocations: &RevocationSbat) {
assert_eq!(revocations.date(), Some(ascii("2021030218")));
assert_eq!(
revocations.revoked_components().collect::<Vec<_>>(),
[
make_component("sbat", 1),
make_component("compA", 1),
make_component("compB", 2)
],
);
}
#[test]
fn parse_success_array() {
parse_success_helper(RevocationSbat::parse(VALID_SBAT).unwrap());
}
#[cfg(feature = "alloc")]
#[test]
fn parse_success_vec() {
parse_success_helper(&RevocationSbatOwned::parse(VALID_SBAT).unwrap());
}
#[test]
fn too_few_fields() {
let input = b"sbat";
assert_eq!(RevocationSbat::parse(input), Err(ParseError::TooFewFields));
}
#[test]
fn no_date_field() {
let input = b"sbat,1";
let revocations = RevocationSbat::parse(input).unwrap();
assert!(revocations.date().is_none());
assert_eq!(
revocations.revoked_components().collect::<Vec<_>>(),
[make_component("sbat", 1)]
);
}
#[test]
fn is_component_revoked() {
let csv = b"compA,2\ncompB,3";
let revocations = RevocationSbat::parse(csv).unwrap();
assert!(revocations.is_component_revoked(&make_component("compA", 1)));
assert!(!revocations.is_component_revoked(&make_component("compA", 2)));
assert!(!revocations.is_component_revoked(&make_component("compA", 3)));
assert!(revocations.is_component_revoked(&make_component("compB", 2)));
assert!(!revocations.is_component_revoked(&make_component("compB", 3)));
assert!(!revocations.is_component_revoked(&make_component("compB", 4)));
assert!(!revocations.is_component_revoked(&make_component("compC", 1)));
assert!(!revocations.is_component_revoked(&make_component("compC", 2)));
assert!(!revocations.is_component_revoked(&make_component("compC", 3)));
}
#[test]
fn validate_image() {
use ValidationResult::{Allowed, Revoked};
let revocations = RevocationSbat::parse(b"compA,2\ncompB,3").unwrap();
let image = ImageSbat::parse(b"compA,1").unwrap();
assert_eq!(
revocations.validate_image(image),
Revoked(make_entry("compA", 1))
);
let image = ImageSbat::parse(b"compA,2\ncompB,2").unwrap();
assert_eq!(
revocations.validate_image(image),
Revoked(make_entry("compB", 2))
);
let image = ImageSbat::parse(b"compA,1\ncompB,3").unwrap();
assert_eq!(
revocations.validate_image(image),
Revoked(make_entry("compA", 1))
);
let image = ImageSbat::parse(b"compA,2\ncompB,3").unwrap();
assert_eq!(revocations.validate_image(image), Allowed);
let image = ImageSbat::parse(b"compC,1").unwrap();
assert_eq!(revocations.validate_image(image), Allowed);
let image = ImageSbat::parse(b"compC,1\ncompA,1").unwrap();
assert_eq!(
revocations.validate_image(image),
Revoked(make_entry("compA", 1))
);
}
}