Skip to content

Commit 0135386

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

File tree

3 files changed

+265
-0
lines changed

3 files changed

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

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)