Skip to content

Commit 96bf8bd

Browse files
committed
uefi: Add initial working version of ConfigurationString (Spec 35.2.1) parser
1 parent 60e2710 commit 96bf8bd

File tree

3 files changed

+259
-0
lines changed

3 files changed

+259
-0
lines changed

uefi/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Added `proto::pci::PciRootBridgeIo`.
77
- Added `proto::hii::config::HiiKeywordHandler`.
88
- Added `proto::hii::config::HiiConfigAccess`.
9+
- Added `proto::hii::config_str::ConfigurationString`.
910

1011
## Changed
1112
- **Breaking:** `boot::stall` now take `core::time::Duration` instead of `usize`.

uefi/src/proto/hii/config_str.rs

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
//! UEFI Configuration String parsing according to Spec 35.2.1
4+
5+
use alloc::boxed::Box;
6+
use alloc::string::{String, ToString};
7+
use alloc::vec::Vec;
8+
use core::slice;
9+
use core::str::{self, FromStr};
10+
use uguid::Guid;
11+
12+
use crate::{CStr16, Char16, proto::device_path::DevicePath};
13+
14+
/// A helper struct to split and parse a UEFI Configuration String.
15+
///
16+
/// Configuration strings consist of key-value pairs separated by `&`. Keys
17+
/// and values are separated by `=`. This struct provides an iterator for
18+
/// easy traversal of the key-value pairs.
19+
///
20+
/// For reasons of developer sanity, this is operating on &str instead of &CStr16.
21+
#[derive(Debug)]
22+
pub struct ConfigurationStringSplitter<'a> {
23+
bfr: &'a str,
24+
}
25+
26+
impl<'a> ConfigurationStringSplitter<'a> {
27+
/// Creates a new splitter instance for a given configuration string buffer.
28+
pub fn new(bfr: &'a str) -> Self {
29+
Self { bfr }
30+
}
31+
}
32+
33+
impl<'a> Iterator for ConfigurationStringSplitter<'a> {
34+
type Item = (&'a str, Option<&'a str>);
35+
36+
fn next(&mut self) -> Option<Self::Item> {
37+
if self.bfr.is_empty() {
38+
return None;
39+
}
40+
let (keyval, remainder) = self
41+
.bfr
42+
.split_once('&')
43+
.unwrap_or((&self.bfr[..], &self.bfr[0..0]));
44+
self.bfr = remainder;
45+
let (key, value) = keyval
46+
.split_once('=')
47+
.map(|(key, value)| (key, Some(value)))
48+
.unwrap_or((&keyval[..], None));
49+
Some((key, value))
50+
}
51+
}
52+
53+
/// Enum representing different sections of a UEFI Configuration Header.
54+
///
55+
/// These sections include GUID, Name, and Path elements, which provide
56+
/// routing and identification information for UEFI components.
57+
#[derive(Debug, PartialEq, Eq)]
58+
pub enum ConfigHdrSection {
59+
/// UEFI ConfigurationString {GuidHdr} element
60+
Guid,
61+
/// UEFI ConfigurationString {NameHdr} element
62+
Name,
63+
/// UEFI ConfigurationString {PathHdr} element
64+
Path,
65+
}
66+
67+
/// Enum representing possible parsing errors encountered when processing
68+
/// UEFI Configuration Strings.
69+
#[derive(Debug)]
70+
pub enum ParseError {
71+
/// Error while parsing the UEFI {ConfigHdr} configuration string section.
72+
ConfigHdr(ConfigHdrSection),
73+
/// Error while parsing the UEFI {BlockName} configuration string section.
74+
BlockName,
75+
/// Error while parsing the UEFI {BlockConfig} configuration string section.
76+
BlockConfig,
77+
}
78+
79+
/// Represents an individual element within a UEFI Configuration String.
80+
///
81+
/// Each element contains an offset, width, and value, defining the data
82+
/// stored at specific memory locations within the configuration.
83+
#[derive(Debug, Default)]
84+
pub struct ConfigurationStringElement {
85+
/// Byte offset in the configuration block
86+
pub offset: u64,
87+
/// Length of the value starting at offset
88+
pub width: u64,
89+
/// Value bytes
90+
pub value: Vec<u8>,
91+
// TODO
92+
// nvconfig: HashMap<String, Vec<u8>>,
93+
}
94+
95+
/// A full UEFI Configuration String representation.
96+
///
97+
/// This structure contains routing information such as GUID and device path,
98+
/// along with the parsed configuration elements.
99+
#[derive(Debug)]
100+
pub struct ConfigurationString {
101+
/// GUID used for identifying the configuration
102+
pub guid: Guid,
103+
/// Name field (optional identifier)
104+
pub name: String,
105+
/// Associated UEFI device path
106+
pub device_path: Box<DevicePath>,
107+
/// Parsed UEFI {ConfigElement} sections
108+
pub elements: Vec<ConfigurationStringElement>,
109+
}
110+
111+
impl ConfigurationString {
112+
fn try_parse_with<T, F: FnOnce() -> Option<T>>(
113+
err: ParseError,
114+
parse_fn: F,
115+
) -> Result<T, ParseError> {
116+
parse_fn().ok_or(err)
117+
}
118+
119+
/// Parses a hexadecimal string into an iterator of bytes.
120+
///
121+
/// # Arguments
122+
///
123+
/// * `hex` - The hexadecimal string representing binary data.
124+
///
125+
/// # Returns
126+
///
127+
/// An iterator over bytes.
128+
pub fn parse_bytes_from_hex(hex: &str) -> impl Iterator<Item = u8> {
129+
hex.as_bytes().chunks(2).map(|chunk| {
130+
let chunk = str::from_utf8(chunk).unwrap_or_default();
131+
u8::from_str_radix(chunk, 16).unwrap_or_default()
132+
})
133+
}
134+
135+
/// Converts a hexadecimal string representation into a numeric value.
136+
///
137+
/// # Arguments
138+
///
139+
/// * `data` - The hexadecimal string to convert.
140+
///
141+
/// # Returns
142+
///
143+
/// An `Option<u64>` representing the parsed number.
144+
pub fn parse_number_from_hex(data: &str) -> Option<u64> {
145+
let data: Vec<_> = Self::parse_bytes_from_hex(data).collect();
146+
match data.len() {
147+
8 => Some(u64::from_be_bytes(data.try_into().unwrap())),
148+
4 => Some(u32::from_be_bytes(data.try_into().unwrap()) as u64),
149+
2 => Some(u16::from_be_bytes(data.try_into().unwrap()) as u64),
150+
1 => Some(u8::from_be_bytes(data.try_into().unwrap()) as u64),
151+
_ => None,
152+
}
153+
}
154+
155+
/// Converts a hexadecimal string into a UTF-16 string.
156+
///
157+
/// # Arguments
158+
///
159+
/// * `data` - The hexadecimal representation of a string.
160+
///
161+
/// # Returns
162+
///
163+
/// An `Option<String>` containing the parsed string.
164+
pub fn parse_string_from_hex(data: &str) -> Option<String> {
165+
if data.len() % 2 != 0 {
166+
return None;
167+
}
168+
let mut data: Vec<_> = Self::parse_bytes_from_hex(data).collect();
169+
data.chunks_exact_mut(2).for_each(|c| c.swap(0, 1));
170+
data.extend_from_slice(&[0, 0]);
171+
let data: &[Char16] =
172+
unsafe { slice::from_raw_parts(data.as_slice().as_ptr().cast(), data.len() / 2) };
173+
Some(CStr16::from_char16_with_nul(data).ok()?.to_string())
174+
}
175+
176+
/// Parses a hexadecimal string into a UEFI GUID.
177+
///
178+
/// # Arguments
179+
///
180+
/// * `data` - The hexadecimal GUID representation.
181+
///
182+
/// # Returns
183+
///
184+
/// An `Option<Guid>` containing the parsed GUID.
185+
pub fn parse_guid_from_hex(data: &str) -> Option<Guid> {
186+
let v: Vec<_> = Self::parse_bytes_from_hex(data).collect();
187+
Some(Guid::from_bytes(v.try_into().ok()?))
188+
}
189+
}
190+
191+
impl FromStr for ConfigurationString {
192+
type Err = ParseError;
193+
194+
fn from_str(bfr: &str) -> Result<Self, Self::Err> {
195+
let mut splitter = ConfigurationStringSplitter::new(bfr).peekable();
196+
197+
let guid = Self::try_parse_with(ParseError::ConfigHdr(ConfigHdrSection::Guid), || {
198+
let v = splitter.next()?;
199+
let v = (v.0 == "GUID").then_some(v.1).flatten()?;
200+
Self::parse_guid_from_hex(v)
201+
})?;
202+
let name = Self::try_parse_with(ParseError::ConfigHdr(ConfigHdrSection::Name), || {
203+
let v = splitter.next()?;
204+
let v = (v.0 == "NAME").then_some(v.1).flatten()?;
205+
Self::parse_string_from_hex(v)
206+
})?;
207+
let device_path =
208+
Self::try_parse_with(ParseError::ConfigHdr(ConfigHdrSection::Path), || {
209+
let v = splitter.next()?.1?;
210+
let v: Vec<_> = Self::parse_bytes_from_hex(v).collect();
211+
let v = <&DevicePath>::try_from(v.as_slice()).ok()?;
212+
Some(v.to_boxed())
213+
})?;
214+
215+
let mut elements = Vec::new();
216+
loop {
217+
let offset = match splitter.next() {
218+
Some(("OFFSET", Some(data))) => {
219+
Self::parse_number_from_hex(&data).ok_or(ParseError::BlockName)?
220+
}
221+
None => break,
222+
_ => return Err(ParseError::BlockName),
223+
};
224+
let width = match splitter.next() {
225+
Some(("WIDTH", Some(data))) => {
226+
Self::parse_number_from_hex(&data).ok_or(ParseError::BlockName)?
227+
}
228+
_ => return Err(ParseError::BlockName),
229+
};
230+
let value = match splitter.next() {
231+
Some(("VALUE", Some(data))) => Self::parse_bytes_from_hex(data).collect(),
232+
_ => return Err(ParseError::BlockConfig),
233+
};
234+
235+
while let Some(next) = splitter.peek() {
236+
if next.0 == "OFFSET" {
237+
break;
238+
}
239+
let _ = splitter.next(); // drop nvconfig entries for now
240+
}
241+
242+
elements.push(ConfigurationStringElement {
243+
offset,
244+
width,
245+
value,
246+
});
247+
}
248+
249+
Ok(Self {
250+
guid,
251+
name,
252+
device_path,
253+
elements,
254+
})
255+
}
256+
}

uefi/src/proto/hii/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
//! HII Protocols
44
55
pub mod config;
6+
#[cfg(feature = "alloc")]
7+
pub mod config_str;

0 commit comments

Comments
 (0)