1use crate::csv::{CsvIter, Record, trim_ascii_at_null};
16use crate::{Component, ParseError};
17use ascii::AsciiStr;
18use core::ptr;
19
20pub const SBAT_SECTION_NAME: &str = ".sbat";
22
23#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
26pub struct Vendor<'a> {
27 pub name: Option<&'a AsciiStr>,
29
30 pub package_name: Option<&'a AsciiStr>,
32
33 pub version: Option<&'a AsciiStr>,
35
36 pub url: Option<&'a AsciiStr>,
38}
39
40#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
44pub struct Entry<'a> {
45 pub component: Component<'a>,
47
48 pub vendor: Vendor<'a>,
51}
52
53const NUM_ENTRY_FIELDS: usize = 6;
54
55impl<'a> Entry<'a> {
56 #[must_use]
58 pub fn new(component: Component<'a>, vendor: Vendor<'a>) -> Entry<'a> {
59 Entry { component, vendor }
60 }
61
62 fn from_record(
64 record: &Record<'a, NUM_ENTRY_FIELDS>,
65 ) -> Result<Self, ParseError> {
66 Ok(Self::new(
67 Component::from_record(record)?,
68 Vendor {
69 name: record.get_field(2),
70 package_name: record.get_field(3),
71 version: record.get_field(4),
72 url: record.get_field(5),
73 },
74 ))
75 }
76}
77
78pub struct Entries<'a>(CsvIter<'a, NUM_ENTRY_FIELDS>);
82
83impl<'a> Iterator for Entries<'a> {
84 type Item = Entry<'a>;
85
86 fn next(&mut self) -> Option<Self::Item> {
87 let next = self.0.next()?;
88
89 let record = next.unwrap();
92 Some(Entry::from_record(&record).unwrap())
93 }
94}
95
96#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
101#[repr(transparent)]
102pub struct ImageSbat(AsciiStr);
103
104impl ImageSbat {
105 pub fn parse(input: &[u8]) -> Result<&Self, ParseError> {
112 let input = trim_ascii_at_null(input)?;
113
114 let iter = CsvIter::<NUM_ENTRY_FIELDS>::new(input);
116 for record in iter {
117 let record = record?;
118 Component::from_record(&record)?;
121 }
122
123 Ok(Self::from_ascii_str_unchecked(input))
124 }
125
126 #[allow(unsafe_code)]
133 pub(crate) fn from_ascii_str_unchecked(s: &AsciiStr) -> &Self {
134 unsafe { &*(ptr::from_ref(s) as *const Self) }
137 }
138
139 #[must_use]
141 pub fn as_csv(&self) -> &AsciiStr {
142 &self.0
143 }
144
145 #[must_use]
147 pub fn entries(&self) -> Entries<'_> {
148 Entries(CsvIter::new(&self.0))
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::Generation;
156
157 #[cfg(feature = "alloc")]
158 use crate::ImageSbatOwned;
159
160 const VALID_SBAT: &[u8] = b"sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
161shim,1,UEFI shim,shim,1,https://github.com/rhboot/shim";
162
163 fn parse_success_helper(image_sbat: &ImageSbat) {
164 let ascii = |s| AsciiStr::from_ascii(s).unwrap();
165
166 assert_eq!(
167 image_sbat.entries().collect::<Vec<_>>(),
168 [
169 Entry::new(
170 Component {
171 name: ascii("sbat"),
172 generation: Generation::new(1).unwrap(),
173 },
174 Vendor {
175 name: Some(ascii("SBAT Version")),
176 package_name: Some(ascii("sbat")),
177 version: Some(ascii("1")),
178 url: Some(ascii(
179 "https://github.com/rhboot/shim/blob/main/SBAT.md"
180 )),
181 },
182 ),
183 Entry::new(
184 Component {
185 name: ascii("shim"),
186 generation: Generation::new(1).unwrap(),
187 },
188 Vendor {
189 name: Some(ascii("UEFI shim")),
190 package_name: Some(ascii("shim")),
191 version: Some(ascii("1")),
192 url: Some(ascii("https://github.com/rhboot/shim")),
193 }
194 )
195 ]
196 );
197 }
198
199 #[test]
200 fn parse_success_array() {
201 parse_success_helper(ImageSbat::parse(VALID_SBAT).unwrap());
202 }
203
204 #[cfg(feature = "alloc")]
205 #[test]
206 fn parse_success_vec() {
207 parse_success_helper(&ImageSbatOwned::parse(VALID_SBAT).unwrap());
208 }
209
210 #[test]
211 fn invalid_record_array() {
212 assert_eq!(ImageSbat::parse(b"a"), Err(ParseError::TooFewFields));
213 }
214
215 #[cfg(feature = "alloc")]
216 #[test]
217 fn invalid_record_vec() {
218 assert_eq!(ImageSbatOwned::parse(b"a"), Err(ParseError::TooFewFields));
219 }
220}