use crate::csv::{CsvIter, Record, trim_ascii_at_null};
use crate::{Component, ParseError};
use ascii::AsciiStr;
use core::ptr;
pub const SBAT_SECTION_NAME: &str = ".sbat";
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Vendor<'a> {
pub name: Option<&'a AsciiStr>,
pub package_name: Option<&'a AsciiStr>,
pub version: Option<&'a AsciiStr>,
pub url: Option<&'a AsciiStr>,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Entry<'a> {
pub component: Component<'a>,
pub vendor: Vendor<'a>,
}
const NUM_ENTRY_FIELDS: usize = 6;
impl<'a> Entry<'a> {
#[must_use]
pub fn new(component: Component<'a>, vendor: Vendor<'a>) -> Entry<'a> {
Entry { component, vendor }
}
fn from_record(
record: &Record<'a, NUM_ENTRY_FIELDS>,
) -> Result<Self, ParseError> {
Ok(Self::new(
Component::from_record(record)?,
Vendor {
name: record.get_field(2),
package_name: record.get_field(3),
version: record.get_field(4),
url: record.get_field(5),
},
))
}
}
pub struct Entries<'a>(CsvIter<'a, NUM_ENTRY_FIELDS>);
impl<'a> Iterator for Entries<'a> {
type Item = Entry<'a>;
fn next(&mut self) -> Option<Self::Item> {
let next = self.0.next()?;
let record = next.unwrap();
Some(Entry::from_record(&record).unwrap())
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
#[repr(transparent)]
pub struct ImageSbat(AsciiStr);
impl ImageSbat {
pub fn parse(input: &[u8]) -> Result<&Self, ParseError> {
let input = trim_ascii_at_null(input)?;
let iter = CsvIter::<NUM_ENTRY_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
}
#[must_use]
pub fn entries(&self) -> Entries<'_> {
Entries(CsvIter::new(&self.0))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Generation;
#[cfg(feature = "alloc")]
use crate::ImageSbatOwned;
const VALID_SBAT: &[u8] = b"sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
shim,1,UEFI shim,shim,1,https://github.com/rhboot/shim";
fn parse_success_helper(image_sbat: &ImageSbat) {
let ascii = |s| AsciiStr::from_ascii(s).unwrap();
assert_eq!(
image_sbat.entries().collect::<Vec<_>>(),
[
Entry::new(
Component {
name: ascii("sbat"),
generation: Generation::new(1).unwrap(),
},
Vendor {
name: Some(ascii("SBAT Version")),
package_name: Some(ascii("sbat")),
version: Some(ascii("1")),
url: Some(ascii(
"https://github.com/rhboot/shim/blob/main/SBAT.md"
)),
},
),
Entry::new(
Component {
name: ascii("shim"),
generation: Generation::new(1).unwrap(),
},
Vendor {
name: Some(ascii("UEFI shim")),
package_name: Some(ascii("shim")),
version: Some(ascii("1")),
url: Some(ascii("https://github.com/rhboot/shim")),
}
)
]
);
}
#[test]
fn parse_success_array() {
parse_success_helper(ImageSbat::parse(VALID_SBAT).unwrap());
}
#[cfg(feature = "alloc")]
#[test]
fn parse_success_vec() {
parse_success_helper(&ImageSbatOwned::parse(VALID_SBAT).unwrap());
}
#[test]
fn invalid_record_array() {
assert_eq!(ImageSbat::parse(b"a"), Err(ParseError::TooFewFields));
}
#[cfg(feature = "alloc")]
#[test]
fn invalid_record_vec() {
assert_eq!(ImageSbatOwned::parse(b"a"), Err(ParseError::TooFewFields));
}
}