// 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::iter::Peekable;
use core::slice;
use core::str::{self, FromStr};
use uguid::Guid;
use crate::mem::AlignedBuffer;
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 ConfigurationStringIter<'a> {
bfr: &'a str,
}
impl<'a> ConfigurationStringIter<'a> {
/// Creates a new splitter instance for a given configuration string.
#[must_use]
pub const fn new(bfr: &'a str) -> Self {
Self { bfr }
}
}
impl<'a> Iterator for ConfigurationStringIter<'a> {
type Item = (&'a str, Option<&'a str>);
fn next(&mut self) -> Option<Self::Item> {
if self.bfr.is_empty() {
return None;
}
let (keyval, remainder) = self
.bfr
.split_once('&')
.unwrap_or_else(|| (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,
/// UEFI ConfigurationString {DescHdr} element
DescHdr,
}
/// 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<u8>,
// TODO
// nvconfig: HashMap<String, Vec<u8>>,
}
/// Internal module with known keys found in uefi configuration strings.
mod keys {
pub const ALTCFG: &str = "ALTCFG";
pub const GUID: &str = "GUID";
pub const NAME: &str = "NAME";
pub const OFFSET: &str = "OFFSET";
pub const VALUE: &str = "VALUE";
pub const WIDTH: &str = "WIDTH";
}
/// 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<DevicePath>,
/// Identifier of a configuration declared in the corresponding IFR.
pub alt_cfg_id: Option<u16>,
/// Parsed UEFI {ConfigElement} sections
pub elements: Vec<ConfigurationStringElement>,
}
impl ConfigurationString {
fn try_parse_with<T, F: FnOnce() -> Option<T>>(
err: ParseError,
parse_fn: F,
) -> Result<T, ParseError> {
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.
#[must_use]
pub fn parse_bytes_from_hex(hex: &str) -> impl DoubleEndedIterator<Item = u8> {
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<u64>` representing the parsed number.
#[must_use]
pub fn parse_number_from_hex(data: &str) -> Option<u64> {
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<String>` containing the parsed string.
#[must_use]
pub fn parse_string_from_hex(data: &str) -> Option<String> {
if !data.len().is_multiple_of(2) {
return None;
}
let size_bytes = data.len() / 2 + 2; // includes \0 terminator
let size_chars = size_bytes / 2;
let mut bfr = AlignedBuffer::from_size_align(size_bytes, 2).ok()?;
bfr.copy_from_iter(Self::parse_bytes_from_hex(data).chain([0, 0]));
bfr.as_slice_mut()
.chunks_exact_mut(2)
.for_each(|c| c.swap(0, 1));
let data: &[Char16] =
unsafe { slice::from_raw_parts(bfr.as_slice().as_ptr().cast(), size_chars) };
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<Guid>` containing the parsed GUID.
#[must_use]
pub fn parse_guid_from_hex(data: &str) -> Option<Guid> {
let v: Vec<_> = Self::parse_bytes_from_hex(data).collect();
Some(Guid::from_bytes(v.try_into().ok()?))
}
/// Parse an instance of `Peekable<ConfigurationStringIter>` from the given kv-pair iterator.
fn parse_from(
splitter: &mut Peekable<ConfigurationStringIter<'_>>,
) -> Result<Self, ParseError> {
let guid = Self::try_parse_with(ParseError::ConfigHdr(ConfigHdrSection::Guid), || {
let v = splitter.next()?;
let v = (v.0 == keys::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 == keys::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 alt_cfg_id = match splitter.peek() {
Some((keys::ALTCFG, _)) => Some(Self::try_parse_with(
ParseError::ConfigHdr(ConfigHdrSection::DescHdr),
|| {
let v = splitter.next()?.1?;
Self::parse_number_from_hex(v).map(|v| v as u16)
},
)?),
_ => None,
};
let mut elements = Vec::new();
loop {
let offset = match splitter.next() {
Some((keys::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((keys::WIDTH, Some(data))) => {
Self::parse_number_from_hex(data).ok_or(ParseError::BlockName)?
}
_ => return Err(ParseError::BlockName),
};
// The uefi specification declares `VALUE` as being a "number" of arbitrary length.
// And numbers have to be endianness-corrected. Thus, the bytes represented by a value's
// hex string of arbitrary length have to be reversed to account for that weird encoding.
let value = match splitter.next() {
Some((keys::VALUE, Some(data))) => Self::parse_bytes_from_hex(data).rev().collect(),
_ => return Err(ParseError::BlockConfig),
};
while let Some(next) = splitter.peek() {
if next.0 == keys::OFFSET || next.0 == keys::GUID {
break;
}
let _ = splitter.next(); // drop nvconfig entries for now
}
elements.push(ConfigurationStringElement {
offset,
width,
value,
});
// Found start of a new [ConfigurationString]
if let Some((keys::GUID, _)) = splitter.peek() {
break;
}
}
Ok(Self {
guid,
name,
device_path,
alt_cfg_id,
elements,
})
}
}
impl FromStr for ConfigurationString {
type Err = ParseError;
fn from_str(bfr: &str) -> Result<Self, Self::Err> {
Self::parse_from(&mut ConfigurationStringIter::new(bfr).peekable())
}
}
/// Iterator over [ConfigurationString]'s in a multi configuration string.
#[derive(Debug)]
pub struct MultiConfigurationStringIter<'a> {
splitter: Peekable<ConfigurationStringIter<'a>>,
}
impl<'a> MultiConfigurationStringIter<'a> {
/// Creates a new iterator instance for a given configuration string.
#[must_use]
pub fn new(bfr: &'a str) -> Self {
let splitter = ConfigurationStringIter::new(bfr).peekable();
Self { splitter }
}
}
impl<'a> Iterator for MultiConfigurationStringIter<'a> {
type Item = Result<ConfigurationString, ParseError>;
fn next(&mut self) -> Option<Self::Item> {
self.splitter.peek()?; // end of iterator?
// try parsing the next full [ConfigurationString] from the splitter
Some(ConfigurationString::parse_from(&mut self.splitter))
}
}
#[cfg(test)]
mod tests {
use crate::proto::hii::config_str::{ConfigurationString, MultiConfigurationStringIter};
use alloc::vec::Vec;
use core::str::FromStr;
#[test]
fn parse_single() {
// exemplary (shortened / manually constructed) UEFI configuration string
let input = "GUID=16d6474bd6a852459d44ccad2e0f4cf9&NAME=00490053004300530049005f0043004f004e004600490047005f004900460052005f004e00560044004100540041&PATH=0104140016d6474bd6a852459d44ccad2e0f4cf97fff0400&OFFSET=01d8&WIDTH=0002&VALUE=0011&OFFSET=01d9&WIDTH=0001&VALUE=00&OFFSET=01da&WIDTH=0001&VALUE=00&OFFSET=01dc&WIDTH=0002&VALUE=03e8&OFFSET=01de&WIDTH=0001&VALUE=00&OFFSET=01df&WIDTH=0001&VALUE=00&OFFSET=05fe&WIDTH=0002&VALUE=0000&OFFSET=062a&WIDTH=0001&VALUE=00&OFFSET=062b&WIDTH=0001&VALUE=01&OFFSET=0fd4&WIDTH=0001&VALUE=00&OFFSET=0fd5&WIDTH=0001&VALUE=00";
let parsed = ConfigurationString::from_str(input).unwrap();
assert_eq!(parsed.guid, guid!("4b47d616-a8d6-4552-9d44-ccad2e0f4cf9"));
assert_eq!(parsed.name, "ISCSI_CONFIG_IFR_NVDATA");
assert_eq!(parsed.alt_cfg_id, None);
assert_eq!(parsed.elements.len(), 11);
assert_eq!(parsed.elements[0].offset, 0x01d8);
assert_eq!(parsed.elements[0].width, 2);
assert_eq!(&parsed.elements[0].value, &[0x11, 0x00]);
assert_eq!(parsed.elements[10].offset, 0x0fd5);
assert_eq!(parsed.elements[10].width, 1);
assert_eq!(&parsed.elements[10].value, &[0x00]);
}
#[test]
fn parse_multiple() {
// exemplary (shortened / manually constructed) UEFI configuration string
let input = "GUID=16d6474bd6a852459d44ccad2e0f4cf9&NAME=00490053004300530049005f0043004f004e004600490047005f004900460052005f004e00560044004100540041&PATH=0104140016d6474bd6a852459d44ccad2e0f4cf97fff0400&OFFSET=01d8&WIDTH=0001&VALUE=00&GUID=16d6474bd6a852459d44ccad2e0f4cf9&NAME=00490053004300530049005f0043004f004e004600490047005f004900460052005f004e00560044004100540041&PATH=0104140016d6474bd6a852459d44ccad2e0f4cf97fff0400&OFFSET=01d8&WIDTH=0001&VALUE=00&OFFSET=1337&WIDTH=0005&VALUE=1122334455";
let parsed: Vec<_> = MultiConfigurationStringIter::new(input)
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(parsed.len(), 2);
assert_eq!(
parsed[0].guid,
guid!("4b47d616-a8d6-4552-9d44-ccad2e0f4cf9")
);
assert_eq!(parsed[0].name, "ISCSI_CONFIG_IFR_NVDATA");
assert_eq!(parsed[0].alt_cfg_id, None);
assert_eq!(parsed[0].elements.len(), 1);
assert_eq!(parsed[0].elements[0].offset, 0x01d8);
assert_eq!(parsed[0].elements[0].width, 1);
assert_eq!(&parsed[0].elements[0].value, &[0x00]);
assert_eq!(
parsed[1].guid,
guid!("4b47d616-a8d6-4552-9d44-ccad2e0f4cf9")
);
assert_eq!(parsed[1].name, "ISCSI_CONFIG_IFR_NVDATA");
assert_eq!(parsed[1].alt_cfg_id, None);
assert_eq!(parsed[1].elements.len(), 2);
assert_eq!(parsed[1].elements[1].offset, 0x1337);
assert_eq!(parsed[1].elements[1].width, 5);
assert_eq!(
&parsed[1].elements[1].value,
&[0x55, 0x44, 0x33, 0x22, 0x11]
);
}
#[test]
fn parse_single_altcfg() {
// exemplary (shortened / manually constructed) UEFI configuration string
let input = "GUID=16d6474bd6a852459d44ccad2e0f4cf9&NAME=00490053004300530049005f0043004f004e004600490047005f004900460052005f004e00560044004100540041&PATH=0104140016d6474bd6a852459d44ccad2e0f4cf97fff0400&ALTCFG=0001&OFFSET=01d8&WIDTH=0001&VALUE=00&OFFSET=01d9&WIDTH=0001&VALUE=00&OFFSET=01da&WIDTH=0001&VALUE=00&OFFSET=01dc&WIDTH=0002&VALUE=03e8&OFFSET=01de&WIDTH=0001&VALUE=00&OFFSET=01df&WIDTH=0001&VALUE=00&OFFSET=05fe&WIDTH=0002&VALUE=0000&OFFSET=062a&WIDTH=0001&VALUE=00&OFFSET=062b&WIDTH=0001&VALUE=01&OFFSET=0fd4&WIDTH=0001&VALUE=00&OFFSET=0fd5&WIDTH=0001&VALUE=00";
let parsed = ConfigurationString::from_str(input).unwrap();
assert_eq!(parsed.guid, guid!("4b47d616-a8d6-4552-9d44-ccad2e0f4cf9"));
assert_eq!(parsed.name, "ISCSI_CONFIG_IFR_NVDATA");
assert_eq!(parsed.alt_cfg_id, Some(1));
assert_eq!(parsed.elements.len(), 11);
assert_eq!(parsed.elements[0].offset, 0x01d8);
assert_eq!(parsed.elements[0].width, 1);
assert_eq!(&parsed.elements[0].value, &[0x00]);
assert_eq!(parsed.elements[10].offset, 0x0fd5);
assert_eq!(parsed.elements[10].width, 1);
assert_eq!(&parsed.elements[10].value, &[0x00]);
}
#[test]
fn parse_multiple_altcfg() {
// exemplary (shortened / manually constructed) UEFI configuration string
let input = "GUID=16d6474bd6a852459d44ccad2e0f4cf9&NAME=00490053004300530049005f0043004f004e004600490047005f004900460052005f004e00560044004100540041&PATH=0104140016d6474bd6a852459d44ccad2e0f4cf97fff0400&ALTCFG=0002&OFFSET=01d8&WIDTH=0001&VALUE=00&GUID=16d6474bd6a852459d44ccad2e0f4cf9&NAME=00490053004300530049005f0043004f004e004600490047005f004900460052005f004e00560044004100540041&PATH=0104140016d6474bd6a852459d44ccad2e0f4cf97fff0400&ALTCFG=0001&OFFSET=01d8&WIDTH=0001&VALUE=00&OFFSET=1337&WIDTH=0005&VALUE=1122334455";
let parsed: Vec<_> = MultiConfigurationStringIter::new(input)
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(parsed.len(), 2);
assert_eq!(
parsed[0].guid,
guid!("4b47d616-a8d6-4552-9d44-ccad2e0f4cf9")
);
assert_eq!(parsed[0].name, "ISCSI_CONFIG_IFR_NVDATA");
assert_eq!(parsed[0].alt_cfg_id, Some(2));
assert_eq!(parsed[0].elements.len(), 1);
assert_eq!(parsed[0].elements[0].offset, 0x01d8);
assert_eq!(parsed[0].elements[0].width, 1);
assert_eq!(&parsed[0].elements[0].value, &[0x00]);
assert_eq!(
parsed[1].guid,
guid!("4b47d616-a8d6-4552-9d44-ccad2e0f4cf9")
);
assert_eq!(parsed[1].name, "ISCSI_CONFIG_IFR_NVDATA");
assert_eq!(parsed[1].alt_cfg_id, Some(1));
assert_eq!(parsed[1].elements.len(), 2);
assert_eq!(parsed[1].elements[1].offset, 0x1337);
assert_eq!(parsed[1].elements[1].width, 5);
assert_eq!(
&parsed[1].elements[1].value,
&[0x55, 0x44, 0x33, 0x22, 0x11]
);
}
}