use alloc::{borrow::Cow, boxed::Box, vec::Vec};
use crate::BinaryGuid;
use core::{any::Any, ops::Deref};
use super::{
metadata::MetaData,
params::Param,
storage::{Storage, UnsafeStorageCell},
};
pub trait FromHob: Sized + 'static {
const HOB_GUID: BinaryGuid;
fn register(bytes: &[u8], storage: &mut Storage) {
storage.add_hob(Self::parse(bytes));
}
fn parse(bytes: &[u8]) -> Self;
}
pub use patina_macro::FromHob;
pub struct Hob<'h, T: FromHob + 'static> {
value: &'h [Box<dyn Any>],
_marker: core::marker::PhantomData<T>,
}
impl<'h, T: FromHob + 'static> Hob<'h, T> {
#[allow(clippy::test_attr_in_doctest)]
pub fn mock(value: Vec<T>) -> Self {
let value = value.into_iter().map(|v| Box::new(v) as Box<dyn Any>).collect::<Vec<_>>();
let static_value = Box::leak(value.into_boxed_slice());
Self { value: static_value, _marker: core::marker::PhantomData }
}
pub fn iter(&self) -> HobIter<'h, T> {
HobIter { inner: self.value.iter(), _marker: core::marker::PhantomData }
}
}
impl<'h, T: FromHob + 'static> From<&'h [Box<dyn Any>]> for Hob<'h, T> {
fn from(value: &'h [Box<dyn Any>]) -> Self {
Self { value, _marker: core::marker::PhantomData }
}
}
impl<T: FromHob + 'static> Deref for Hob<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value[0].downcast_ref::<T>().expect("Failed to downcast Hob value")
}
}
unsafe impl<T: FromHob + 'static> Param for Hob<'_, T> {
type State = usize;
type Item<'storage, 'state> = Hob<'storage, T>;
unsafe fn get_param<'storage, 'state>(
lookup_id: &'state Self::State,
storage: UnsafeStorageCell<'storage>,
) -> Self::Item<'storage, 'state> {
Hob::from(unsafe { storage.storage().get_raw_hob(*lookup_id) })
}
fn validate(state: &Self::State, storage: UnsafeStorageCell) -> bool {
!unsafe { storage.storage() }.get_raw_hob(*state).is_empty()
}
fn init_state(storage: &mut Storage, _meta: &mut MetaData) -> Result<Self::State, Cow<'static, str>> {
storage.add_hob_parser::<T>();
Ok(storage.register_hob::<T>())
}
}
pub struct HobIter<'h, T> {
inner: core::slice::Iter<'h, Box<dyn Any>>,
_marker: core::marker::PhantomData<T>,
}
impl<'h, T: FromHob + 'static> Iterator for HobIter<'h, T> {
type Item = &'h T;
fn next(&mut self) -> Option<Self::Item> {
if let Some(any_box) = self.inner.next() {
return Some(
any_box
.downcast_ref::<T>()
.unwrap_or_else(|| panic!("Hob should be of type {}", super::type_name::normalized::<T>())),
);
}
None
}
}
impl<'h, T: FromHob + 'static> IntoIterator for &Hob<'h, T> {
type Item = &'h T;
type IntoIter = HobIter<'h, T>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[cfg(test)]
#[coverage(off)]
mod tests {
use crate as patina;
use crate::{
BinaryGuid,
component::{IntoComponent, component},
error::{EfiError, Result},
};
use super::*;
#[derive(Default)]
struct MyStruct {
unused: u32,
}
impl FromHob for MyStruct {
const HOB_GUID: BinaryGuid = patina::guids::ZERO;
fn parse(_bytes: &[u8]) -> Self {
MyStruct::default()
}
}
#[test]
fn test_single_hob() {
let mut storage = Storage::new();
let id = storage.register_hob::<MyStruct>();
storage.add_hob(MyStruct { unused: 5 });
let hob: Hob<MyStruct> = Hob::from(storage.get_raw_hob(id));
assert_eq!(hob.unused, 5);
}
#[test]
fn test_multiple_hobs() {
let mut storage = Storage::new();
let id1 = storage.register_hob::<MyStruct>();
storage.add_hob(MyStruct { unused: 5 });
storage.add_hob(MyStruct { unused: 10 });
let hobs: Hob<MyStruct> = Hob::from(storage.get_raw_hob(id1));
{
let mut iter = hobs.iter();
assert_eq!(iter.next().unwrap().unused, 5);
assert_eq!(iter.next().unwrap().unused, 10);
}
for hob in &hobs {
assert!([5, 10].contains(&hob.unused))
}
}
#[test]
fn test_iter_next_function() {
let hobs = Hob::mock(vec![MyStruct { unused: 5 }, MyStruct { unused: 10 }]);
let mut iter = hobs.iter();
assert_eq!(iter.next().unwrap().unused, 5);
assert_eq!(iter.next().unwrap().unused, 10);
assert!(iter.next().is_none()); }
#[test]
fn test_component_flow() {
struct MyComponent;
#[component]
impl MyComponent {
fn entry_point(self, hob: Hob<MyStruct>) -> Result<()> {
if hob.unused == 0 {
return Err(EfiError::NotReady);
}
if hob.unused != 5 {
return Err(EfiError::InvalidParameter);
}
Ok(())
}
}
let mut storage = Storage::new();
let mut comp = MyComponent.into_component();
comp.initialize(&mut storage);
assert!(!Hob::<MyStruct>::validate(&0, UnsafeStorageCell::from(&storage)));
MyStruct::register(&[0], &mut storage);
assert!(Hob::<MyStruct>::validate(&0, UnsafeStorageCell::from(&storage)));
let x = unsafe { Hob::<MyStruct>::get_param(&0, UnsafeStorageCell::from(&storage)) };
assert!(MyComponent::entry_point(MyComponent, x).is_err_and(|e| e == EfiError::NotReady));
assert!(MyComponent::entry_point(MyComponent, Hob::mock(vec![MyStruct { unused: 5 }])).is_ok());
assert!(
MyComponent::entry_point(MyComponent, Hob::mock(vec![MyStruct { unused: 10 }]))
.is_err_and(|e| e == EfiError::InvalidParameter)
);
}
}