diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 1ceae0d2d..397aa4672 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -4,6 +4,9 @@ - Added `ConfigTableEntry::MEMORY_ATTRIBUTES_GUID` and `ConfigTableEntry::IMAGE_SECURITY_DATABASE_GUID`. - Added `proto::usb::io::UsbIo`. - Added `proto::pci::PciRootBridgeIo`. +- Added `proto::hii::config::ConfigKeywordHandler`. +- Added `proto::hii::config::HiiConfigAccess`. +- Added `proto::hii::config_str::ConfigurationString`. ## Changed - **Breaking:** `boot::stall` now take `core::time::Duration` instead of `usize`. diff --git a/uefi/src/proto/hii/config.rs b/uefi/src/proto/hii/config.rs new file mode 100644 index 000000000..1b1d9aae5 --- /dev/null +++ b/uefi/src/proto/hii/config.rs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! HII Configuration protocols. + +use uefi_macros::unsafe_protocol; +use uefi_raw::protocol::hii::config::{ConfigKeywordHandlerProtocol, HiiConfigAccessProtocol}; + +/// The HII Keyword Handler Protocol. +/// +/// # UEFI Spec Description +/// +/// This protocol provides the mechanism to set and get the values associated +/// with a keyword exposed through a x-UEFI- prefixed configuration language namespace. +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(ConfigKeywordHandlerProtocol::GUID)] +pub struct ConfigKeywordHandler(ConfigKeywordHandlerProtocol); + +/// The HII Configuration Access Protocol. +/// +/// # UEFI Spec Description +/// +/// This protocol is responsible for facilitating access to configuration data from HII. +/// It is typically invoked by the HII Configuration Routing Protocol for handling +/// configuration requests. Forms browsers also interact with this protocol through +/// the `Callback()` function. +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(HiiConfigAccessProtocol::GUID)] +pub struct HiiConfigAccess(HiiConfigAccessProtocol); diff --git a/uefi/src/proto/hii/config_str.rs b/uefi/src/proto/hii/config_str.rs new file mode 100644 index 000000000..91040000d --- /dev/null +++ b/uefi/src/proto/hii/config_str.rs @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! UEFI Configuration String parsing according to Spec 35.2.1 + +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::slice; +use core::str::{self, FromStr}; +use uguid::Guid; + +use crate::proto::device_path::DevicePath; +use crate::{CStr16, Char16}; + +/// A helper struct to split and parse a UEFI Configuration String. +/// +/// Configuration strings consist of key-value pairs separated by `&`. Keys +/// and values are separated by `=`. This struct provides an iterator for +/// easy traversal of the key-value pairs. +/// +/// For reasons of developer sanity, this is operating on &str instead of &CStr16. +#[derive(Debug)] +pub struct ConfigurationStringSplitter<'a> { + bfr: &'a str, +} + +impl<'a> ConfigurationStringSplitter<'a> { + /// Creates a new splitter instance for a given configuration string buffer. + #[must_use] + pub const fn new(bfr: &'a str) -> Self { + Self { bfr } + } +} + +impl<'a> Iterator for ConfigurationStringSplitter<'a> { + type Item = (&'a str, Option<&'a str>); + + fn next(&mut self) -> Option { + if self.bfr.is_empty() { + return None; + } + let (keyval, remainder) = self + .bfr + .split_once('&') + .unwrap_or((self.bfr, &self.bfr[0..0])); + self.bfr = remainder; + let (key, value) = keyval + .split_once('=') + .map(|(key, value)| (key, Some(value))) + .unwrap_or((keyval, None)); + Some((key, value)) + } +} + +/// Enum representing different sections of a UEFI Configuration Header. +/// +/// These sections include GUID, Name, and Path elements, which provide +/// routing and identification information for UEFI components. +#[derive(Debug, PartialEq, Eq)] +pub enum ConfigHdrSection { + /// UEFI ConfigurationString {GuidHdr} element + Guid, + /// UEFI ConfigurationString {NameHdr} element + Name, + /// UEFI ConfigurationString {PathHdr} element + Path, +} + +/// Enum representing possible parsing errors encountered when processing +/// UEFI Configuration Strings. +#[derive(Debug)] +pub enum ParseError { + /// Error while parsing the UEFI {ConfigHdr} configuration string section. + ConfigHdr(ConfigHdrSection), + /// Error while parsing the UEFI {BlockName} configuration string section. + BlockName, + /// Error while parsing the UEFI {BlockConfig} configuration string section. + BlockConfig, +} + +/// Represents an individual element within a UEFI Configuration String. +/// +/// Each element contains an offset, width, and value, defining the data +/// stored at specific memory locations within the configuration. +#[derive(Debug, Default)] +pub struct ConfigurationStringElement { + /// Byte offset in the configuration block + pub offset: u64, + /// Length of the value starting at offset + pub width: u64, + /// Value bytes + pub value: Vec, + // TODO + // nvconfig: HashMap>, +} + +/// A full UEFI Configuration String representation. +/// +/// This structure contains routing information such as GUID and device path, +/// along with the parsed configuration elements. +#[derive(Debug)] +pub struct ConfigurationString { + /// GUID used for identifying the configuration + pub guid: Guid, + /// Name field (optional identifier) + pub name: String, + /// Associated UEFI device path + pub device_path: Box, + /// Parsed UEFI {ConfigElement} sections + pub elements: Vec, +} + +impl ConfigurationString { + fn try_parse_with Option>( + err: ParseError, + parse_fn: F, + ) -> Result { + parse_fn().ok_or(err) + } + + /// Parses a hexadecimal string into an iterator of bytes. + /// + /// # Arguments + /// + /// * `hex` - The hexadecimal string representing binary data. + /// + /// # Returns + /// + /// An iterator over bytes. + pub fn parse_bytes_from_hex(hex: &str) -> impl Iterator { + hex.as_bytes().chunks(2).map(|chunk| { + let chunk = str::from_utf8(chunk).unwrap_or_default(); + u8::from_str_radix(chunk, 16).unwrap_or_default() + }) + } + + /// Converts a hexadecimal string representation into a numeric value. + /// + /// # Arguments + /// + /// * `data` - The hexadecimal string to convert. + /// + /// # Returns + /// + /// An `Option` representing the parsed number. + #[must_use] + pub fn parse_number_from_hex(data: &str) -> Option { + let data: Vec<_> = Self::parse_bytes_from_hex(data).collect(); + match data.len() { + 8 => Some(u64::from_be_bytes(data.try_into().unwrap())), + 4 => Some(u32::from_be_bytes(data.try_into().unwrap()) as u64), + 2 => Some(u16::from_be_bytes(data.try_into().unwrap()) as u64), + 1 => Some(u8::from_be_bytes(data.try_into().unwrap()) as u64), + _ => None, + } + } + + /// Converts a hexadecimal string into a UTF-16 string. + /// + /// # Arguments + /// + /// * `data` - The hexadecimal representation of a string. + /// + /// # Returns + /// + /// An `Option` containing the parsed string. + #[must_use] + pub fn parse_string_from_hex(data: &str) -> Option { + if data.len() % 2 != 0 { + return None; + } + let mut data: Vec<_> = Self::parse_bytes_from_hex(data).collect(); + data.chunks_exact_mut(2).for_each(|c| c.swap(0, 1)); + data.extend_from_slice(&[0, 0]); + let data: &[Char16] = + unsafe { slice::from_raw_parts(data.as_slice().as_ptr().cast(), data.len() / 2) }; + Some(CStr16::from_char16_with_nul(data).ok()?.to_string()) + } + + /// Parses a hexadecimal string into a UEFI GUID. + /// + /// # Arguments + /// + /// * `data` - The hexadecimal GUID representation. + /// + /// # Returns + /// + /// An `Option` containing the parsed GUID. + #[must_use] + pub fn parse_guid_from_hex(data: &str) -> Option { + let v: Vec<_> = Self::parse_bytes_from_hex(data).collect(); + Some(Guid::from_bytes(v.try_into().ok()?)) + } +} + +impl FromStr for ConfigurationString { + type Err = ParseError; + + fn from_str(bfr: &str) -> Result { + let mut splitter = ConfigurationStringSplitter::new(bfr).peekable(); + + let guid = Self::try_parse_with(ParseError::ConfigHdr(ConfigHdrSection::Guid), || { + let v = splitter.next()?; + let v = (v.0 == "GUID").then_some(v.1).flatten()?; + Self::parse_guid_from_hex(v) + })?; + let name = Self::try_parse_with(ParseError::ConfigHdr(ConfigHdrSection::Name), || { + let v = splitter.next()?; + let v = (v.0 == "NAME").then_some(v.1).flatten()?; + Self::parse_string_from_hex(v) + })?; + let device_path = + Self::try_parse_with(ParseError::ConfigHdr(ConfigHdrSection::Path), || { + let v = splitter.next()?.1?; + let v: Vec<_> = Self::parse_bytes_from_hex(v).collect(); + let v = <&DevicePath>::try_from(v.as_slice()).ok()?; + Some(v.to_boxed()) + })?; + + let mut elements = Vec::new(); + loop { + let offset = match splitter.next() { + Some(("OFFSET", Some(data))) => { + Self::parse_number_from_hex(data).ok_or(ParseError::BlockName)? + } + None => break, + _ => return Err(ParseError::BlockName), + }; + let width = match splitter.next() { + Some(("WIDTH", Some(data))) => { + Self::parse_number_from_hex(data).ok_or(ParseError::BlockName)? + } + _ => return Err(ParseError::BlockName), + }; + let value = match splitter.next() { + Some(("VALUE", Some(data))) => Self::parse_bytes_from_hex(data).collect(), + _ => return Err(ParseError::BlockConfig), + }; + + while let Some(next) = splitter.peek() { + if next.0 == "OFFSET" { + break; + } + let _ = splitter.next(); // drop nvconfig entries for now + } + + elements.push(ConfigurationStringElement { + offset, + width, + value, + }); + } + + Ok(Self { + guid, + name, + device_path, + elements, + }) + } +} diff --git a/uefi/src/proto/hii/mod.rs b/uefi/src/proto/hii/mod.rs new file mode 100644 index 000000000..bdece1069 --- /dev/null +++ b/uefi/src/proto/hii/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! HII Protocols + +pub mod config; +#[cfg(feature = "alloc")] +pub mod config_str; diff --git a/uefi/src/proto/mod.rs b/uefi/src/proto/mod.rs index 92c4e47c2..565f8f73e 100644 --- a/uefi/src/proto/mod.rs +++ b/uefi/src/proto/mod.rs @@ -37,6 +37,7 @@ pub mod console; pub mod debug; pub mod device_path; pub mod driver; +pub mod hii; pub mod loaded_image; pub mod media; pub mod misc;