Skip to content

Commit aeee82a

Browse files
committed
uefi: Add initial working version of ConfigurationString (Spec 35.2.1) parser
1 parent 23266f5 commit aeee82a

File tree

3 files changed

+260
-0
lines changed

3 files changed

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

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)