1use core::ffi::CStr;
20use thiserror::Error;
21use uuid::Uuid;
22use zerocopy::{FromBytes, IntoBytes};
23
24use crate::{
27 ffa_v1_1::{boot_info_descriptor, boot_info_header},
28 Version,
29};
30
31#[derive(Debug, Error)]
34pub enum Error {
35 #[error("Invalid standard type {0}")]
36 InvalidStdType(u8),
37 #[error("Invalid type {0}")]
38 InvalidType(u8),
39 #[error("Invalid contents format {0}")]
40 InvalidContentsFormat(u16),
41 #[error("Invalid name format {0}")]
42 InvalidNameFormat(u16),
43 #[error("Invalid name")]
44 InvalidName,
45 #[error("Invalid flags {0}")]
46 InvalidFlags(u16),
47 #[error("Invalid header size or alignment")]
48 InvalidHeader,
49 #[error("Invalid buffer size")]
50 InvalidBufferSize,
51 #[error("Invalid signature")]
52 InvalidSignature,
53 #[error("Invalid version {0}")]
54 InvalidVersion(Version),
55 #[error("Malformed descriptor")]
56 MalformedDescriptor,
57}
58
59impl From<Error> for crate::FfaError {
60 fn from(_value: Error) -> Self {
61 Self::InvalidParameters
62 }
63}
64
65#[derive(Clone, Debug, PartialEq, Eq)]
67pub enum BootInfoName<'a> {
68 NullTermString(&'a CStr),
69 Uuid(Uuid),
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74#[repr(u8)]
75pub enum BootInfoStdId {
76 Fdt = Self::FDT,
77 Hob = Self::HOB,
78}
79
80impl TryFrom<u8> for BootInfoStdId {
81 type Error = Error;
82
83 fn try_from(value: u8) -> Result<Self, Self::Error> {
84 match value {
85 Self::FDT => Ok(BootInfoStdId::Fdt),
86 Self::HOB => Ok(BootInfoStdId::Hob),
87 _ => Err(Error::InvalidStdType(value)),
88 }
89 }
90}
91
92impl BootInfoStdId {
93 const FDT: u8 = 0;
94 const HOB: u8 = 1;
95}
96
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
99pub struct BootInfoImpdefId(pub u8);
100
101impl From<u8> for BootInfoImpdefId {
102 fn from(value: u8) -> Self {
103 Self(value)
104 }
105}
106
107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
109pub enum BootInfoType {
110 Std(BootInfoStdId),
111 Impdef(BootInfoImpdefId),
112}
113
114impl TryFrom<u8> for BootInfoType {
115 type Error = Error;
116
117 fn try_from(value: u8) -> Result<Self, Self::Error> {
118 match (value >> Self::TYPE_SHIFT) & Self::TYPE_MASK {
119 Self::TYPE_STANDARD => Ok(BootInfoType::Std((value & Self::ID_MASK).try_into()?)),
120 Self::TYPE_IMPDEF => Ok(BootInfoType::Impdef((value & Self::ID_MASK).into())),
121 _ => Err(Error::InvalidType(value)),
122 }
123 }
124}
125
126impl From<BootInfoType> for u8 {
127 fn from(value: BootInfoType) -> Self {
128 match value {
129 BootInfoType::Std(std_type) => {
130 std_type as u8 | (BootInfoType::TYPE_STANDARD << BootInfoType::TYPE_SHIFT)
131 }
132 BootInfoType::Impdef(impdef_type) => {
133 impdef_type.0 | (BootInfoType::TYPE_IMPDEF << BootInfoType::TYPE_SHIFT)
134 }
135 }
136 }
137}
138
139impl BootInfoType {
140 const TYPE_SHIFT: usize = 7;
142 const TYPE_MASK: u8 = 0b1;
143 const TYPE_STANDARD: u8 = 0b0;
144 const TYPE_IMPDEF: u8 = 0b1;
145 const ID_MASK: u8 = 0b0111_1111;
147}
148
149#[derive(Clone, Copy, Debug, PartialEq, Eq)]
151pub enum BootInfoContents<'a> {
152 Address { content_buf: &'a [u8] },
153 Value { val: u64, len: usize },
154}
155
156#[derive(Clone, Copy, Debug, PartialEq, Eq)]
157#[repr(u16)]
158enum BootInfoContentsFormat {
159 Address = Self::ADDRESS << Self::SHIFT,
160 Value = Self::VALUE << Self::SHIFT,
161}
162
163impl TryFrom<u16> for BootInfoContentsFormat {
164 type Error = Error;
165
166 fn try_from(value: u16) -> Result<Self, Self::Error> {
167 match (value >> Self::SHIFT) & Self::MASK {
168 Self::ADDRESS => Ok(BootInfoContentsFormat::Address),
169 Self::VALUE => Ok(BootInfoContentsFormat::Value),
170 _ => Err(Error::InvalidContentsFormat(value)),
171 }
172 }
173}
174
175impl BootInfoContentsFormat {
176 const SHIFT: usize = 2;
177 const MASK: u16 = 0b11;
178 const ADDRESS: u16 = 0b00;
179 const VALUE: u16 = 0b01;
180}
181
182#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183#[repr(u16)]
184enum BootInfoNameFormat {
185 String = Self::STRING << Self::SHIFT,
186 Uuid = Self::UUID << Self::SHIFT,
187}
188
189impl TryFrom<u16> for BootInfoNameFormat {
190 type Error = Error;
191
192 fn try_from(value: u16) -> Result<Self, Self::Error> {
193 match (value >> Self::SHIFT) & Self::MASK {
194 Self::STRING => Ok(BootInfoNameFormat::String),
195 Self::UUID => Ok(BootInfoNameFormat::Uuid),
196 _ => Err(Error::InvalidNameFormat(value)),
197 }
198 }
199}
200
201impl BootInfoNameFormat {
202 const SHIFT: usize = 0;
203 const MASK: u16 = 0b11;
204 const STRING: u16 = 0b00;
205 const UUID: u16 = 0b01;
206}
207
208#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209struct BootInfoFlags {
210 contents_format: BootInfoContentsFormat,
211 name_format: BootInfoNameFormat,
212}
213
214impl TryFrom<u16> for BootInfoFlags {
215 type Error = Error;
216
217 fn try_from(value: u16) -> Result<Self, Self::Error> {
218 if value >> 4 == 0 {
220 Ok(Self {
221 contents_format: BootInfoContentsFormat::try_from(value)?,
222 name_format: BootInfoNameFormat::try_from(value)?,
223 })
224 } else {
225 Err(Error::InvalidFlags(value))
226 }
227 }
228}
229
230impl From<BootInfoFlags> for u16 {
231 fn from(value: BootInfoFlags) -> Self {
232 value.contents_format as u16 | value.name_format as u16
233 }
234}
235
236#[derive(Clone, Debug, PartialEq, Eq)]
238pub struct BootInfo<'a> {
239 pub name: BootInfoName<'a>,
240 pub typ: BootInfoType,
241 pub contents: BootInfoContents<'a>,
242}
243
244impl BootInfo<'_> {
245 pub fn pack(
251 version: Version,
252 descriptors: &[BootInfo],
253 buf: &mut [u8],
254 mapped_addr: Option<usize>,
255 ) {
256 assert!((Version(1, 1)..=Version(1, 2)).contains(&version));
257
258 const DESC_ARRAY_OFFSET: usize = size_of::<boot_info_header>().next_multiple_of(8);
262 const DESC_SIZE: usize = size_of::<boot_info_descriptor>();
263
264 assert!(buf.len() <= u32::MAX as usize);
265
266 let desc_cnt = descriptors.len();
267
268 let mut total_offset = 0usize;
284
285 total_offset = total_offset.checked_add(DESC_ARRAY_OFFSET).unwrap();
287
288 total_offset = total_offset
290 .checked_add(desc_cnt.checked_mul(DESC_SIZE).unwrap())
291 .unwrap();
292
293 assert!(total_offset <= buf.len());
295
296 let mut desc_array_offset = DESC_ARRAY_OFFSET;
298
299 for desc in descriptors {
300 let mut desc_raw = boot_info_descriptor::default();
301
302 let name_format = match &desc.name {
303 BootInfoName::NullTermString(name) => {
304 let name_len = name.count_bytes().min(15);
306 desc_raw.name[..name_len].copy_from_slice(&name.to_bytes()[..name_len]);
307 desc_raw.name[name_len..].fill(0);
309
310 BootInfoNameFormat::String
311 }
312 BootInfoName::Uuid(uuid) => {
313 desc_raw.name.copy_from_slice(&uuid.to_bytes_le());
314 BootInfoNameFormat::Uuid
315 }
316 };
317
318 let contents_format = match desc.contents {
319 BootInfoContents::Address { content_buf } => {
320 total_offset = total_offset.next_multiple_of(8);
328
329 let buf_addr = mapped_addr.unwrap_or(buf.as_ptr() as usize);
333
334 let content_addr = buf_addr.checked_add(total_offset).unwrap();
338
339 let content_len = content_buf.len();
341 total_offset.checked_add(content_len).unwrap();
342
343 buf[total_offset..total_offset + content_len].copy_from_slice(content_buf);
346 total_offset += content_len;
347
348 desc_raw.contents = content_addr as u64;
349 desc_raw.size = content_len as u32;
350
351 BootInfoContentsFormat::Address
352 }
353 BootInfoContents::Value { val, len } => {
354 assert!((1..=8).contains(&len));
355 desc_raw.contents = val;
356 desc_raw.size = len as u32;
357
358 BootInfoContentsFormat::Value
359 }
360 };
361
362 let flags = BootInfoFlags {
363 contents_format,
364 name_format,
365 };
366
367 desc_raw.flags = flags.into();
368 desc_raw.typ = desc.typ.into();
369
370 desc_raw
371 .write_to_prefix(&mut buf[desc_array_offset..])
372 .unwrap();
373 desc_array_offset += DESC_SIZE;
374 }
375
376 let header_raw = boot_info_header {
377 signature: 0x0ffa,
378 version: version.into(),
379 boot_info_blob_size: total_offset as u32,
380 boot_info_desc_size: DESC_SIZE as u32,
381 boot_info_desc_count: desc_cnt as u32,
382 boot_info_array_offset: DESC_ARRAY_OFFSET as u32,
383 reserved: 0,
384 };
385
386 header_raw.write_to_prefix(buf).unwrap();
387 }
388
389 fn get_header(version: Version, buf: &[u8]) -> Result<&boot_info_header, Error> {
391 let (header_raw, _) =
392 boot_info_header::ref_from_prefix(buf).map_err(|_| Error::InvalidHeader)?;
393
394 if header_raw.signature != 0x0ffa {
395 return Err(Error::InvalidSignature);
396 }
397
398 let header_version = header_raw
399 .version
400 .try_into()
401 .map_err(|_| Error::InvalidHeader)?;
402 if header_version != version {
403 return Err(Error::InvalidVersion(header_version));
404 }
405
406 Ok(header_raw)
407 }
408
409 pub fn get_blob_size(version: Version, buf: &[u8]) -> Result<usize, Error> {
414 if !(Version(1, 1)..=Version(1, 2)).contains(&version) {
415 return Err(Error::InvalidVersion(version));
416 }
417
418 let header_raw = Self::get_header(version, buf)?;
419
420 Ok(header_raw.boot_info_blob_size as usize)
421 }
422}
423
424pub struct BootInfoIterator<'a> {
426 buf: &'a [u8],
427 offset: usize,
428 desc_count: usize,
429 desc_size: usize,
430}
431
432impl<'a> BootInfoIterator<'a> {
433 pub fn new(version: Version, buf: &'a [u8]) -> Result<Self, Error> {
435 let header_raw = BootInfo::get_header(version, buf)?;
436
437 if buf.len() < header_raw.boot_info_blob_size as usize {
438 return Err(Error::InvalidBufferSize);
439 }
440
441 if header_raw.boot_info_desc_size as usize != size_of::<boot_info_descriptor>() {
442 return Err(Error::MalformedDescriptor);
443 }
444
445 let Some(total_desc_size) = header_raw
446 .boot_info_desc_count
447 .checked_mul(header_raw.boot_info_desc_size)
448 .and_then(|x| x.checked_add(header_raw.boot_info_array_offset))
449 else {
450 return Err(Error::InvalidBufferSize);
451 };
452
453 if buf.len() < total_desc_size as usize {
454 return Err(Error::InvalidBufferSize);
455 }
456
457 Ok(Self {
458 buf,
459 offset: header_raw.boot_info_array_offset as usize,
460 desc_count: header_raw.boot_info_desc_count as usize,
461 desc_size: header_raw.boot_info_desc_size as usize,
462 })
463 }
464}
465
466impl<'a> Iterator for BootInfoIterator<'a> {
467 type Item = Result<BootInfo<'a>, Error>;
468
469 fn next(&mut self) -> Option<Self::Item> {
470 if self.desc_count > 0 {
471 let desc_offset = self.offset;
472 self.offset += self.desc_size;
473 self.desc_count -= 1;
474
475 let Ok(desc_raw) = boot_info_descriptor::ref_from_bytes(
476 &self.buf[desc_offset..desc_offset + self.desc_size],
477 ) else {
478 return Some(Err(Error::MalformedDescriptor));
479 };
480
481 if desc_raw.reserved != 0 {
482 return Some(Err(Error::MalformedDescriptor));
483 }
484
485 let typ: BootInfoType = match desc_raw.typ.try_into() {
486 Ok(v) => v,
487 Err(e) => return Some(Err(e)),
488 };
489
490 let flags: BootInfoFlags = match desc_raw.flags.try_into() {
491 Ok(v) => v,
492 Err(e) => return Some(Err(e)),
493 };
494
495 let name = match flags.name_format {
496 BootInfoNameFormat::String => {
497 let Ok(name_str) = CStr::from_bytes_with_nul(desc_raw.name.as_bytes()) else {
498 return Some(Err(Error::InvalidName));
499 };
500 BootInfoName::NullTermString(name_str)
501 }
502 BootInfoNameFormat::Uuid => BootInfoName::Uuid(Uuid::from_bytes_le(desc_raw.name)),
503 };
504
505 let contents = match flags.contents_format {
506 BootInfoContentsFormat::Address => {
507 let contents = desc_raw.contents as usize;
508 let contents_size = desc_raw.size as usize;
509
510 let Some(offset) = contents.checked_sub(self.buf.as_ptr() as usize) else {
511 return Some(Err(Error::InvalidBufferSize));
512 };
513
514 let Some(offset_end) = offset.checked_add(contents_size) else {
515 return Some(Err(Error::InvalidBufferSize));
516 };
517
518 if self.buf.len() < offset_end {
519 return Some(Err(Error::InvalidBufferSize));
520 }
521
522 BootInfoContents::Address {
523 content_buf: &self.buf[offset..offset_end],
524 }
525 }
526
527 BootInfoContentsFormat::Value => {
528 let len = desc_raw.size as usize;
529 if (1..=8).contains(&len) {
530 BootInfoContents::Value {
531 val: desc_raw.contents,
532 len,
533 }
534 } else {
535 return Some(Err(Error::MalformedDescriptor));
536 }
537 }
538 };
539
540 return Some(Ok(BootInfo {
541 name,
542 typ,
543 contents,
544 }));
545 }
546
547 None
548 }
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554 use uuid::uuid;
555
556 #[test]
559 fn boot_info() {
560 let desc1 = BootInfo {
561 name: BootInfoName::NullTermString(c"test1234test123"),
562 typ: BootInfoType::Impdef(BootInfoImpdefId(0x2b)),
563 contents: BootInfoContents::Value {
564 val: 0xdeadbeef,
565 len: 4,
566 },
567 };
568
569 let fdt = [0u8; 0xff];
570 let desc2 = BootInfo {
571 name: BootInfoName::Uuid(uuid!("12345678-abcd-dcba-1234-123456789abc")),
572 typ: BootInfoType::Std(BootInfoStdId::Fdt),
573 contents: BootInfoContents::Address { content_buf: &fdt },
574 };
575
576 let mut buf = [0u8; 0x1ff];
577 let buf_addr = buf.as_ptr() as usize;
578 BootInfo::pack(
579 Version(1, 1),
580 &[desc1.clone(), desc2.clone()],
581 &mut buf,
582 Some(buf_addr),
583 );
584 let mut descriptors = BootInfoIterator::new(Version(1, 1), &buf).unwrap();
585 let desc1_check = descriptors.next().unwrap().unwrap();
586 let desc2_check = descriptors.next().unwrap().unwrap();
587
588 assert_eq!(desc1, desc1_check);
589 assert_eq!(desc2, desc2_check);
590 }
591}