Skip to content

Commit 7019b04

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

File tree

3 files changed

+264
-0
lines changed

3 files changed

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

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)