1use guid::Guid;
7use std::ffi::CStr;
8use thiserror::Error;
9use ucs2::Ucs2LeSlice;
10use uefi_specs::uefi::boot;
11use zerocopy::FromBytes;
12
13#[derive(Debug, Error)]
14pub enum Error {
15 #[error("Data length incorrect or inconsistent with other fields")]
16 InvalidLength,
17 #[error("Missing null-termination")]
18 NullTerminated(#[source] std::ffi::FromBytesUntilNulError),
19 #[error("Invalid Ucs2 string")]
20 InvalidUcs2(#[source] ucs2::Ucs2ParseError),
21 #[error("Invalid UTF-8 string")]
22 InvalidUtf8(#[source] std::str::Utf8Error),
23 #[error("Device path end structure missing or corrupted")]
24 DevicePathEnd,
25}
26
27#[derive(Debug, PartialEq)]
28pub enum HardwareDevice<'a> {
29 MemoryMapped(boot::EfiMemoryMappedDevice),
30 Vendor {
31 vendor_guid: Guid,
32 data: &'a [u8],
33 },
34 Unknown {
35 device_subtype: boot::EfiHardwareDeviceSubType,
36 path_data: &'a [u8],
37 },
38}
39
40#[derive(Debug, PartialEq)]
41pub enum AcpiDevice<'a> {
42 ExpandedAcpi {
43 numeric: boot::EfiExpandedAcpiDevice,
44 hidstr: &'a CStr,
45 uidstr: &'a CStr,
46 cidstr: &'a CStr,
47 },
48 Unknown {
49 device_subtype: boot::EfiAcpiDeviceSubType,
50 path_data: &'a [u8],
51 },
52}
53
54#[derive(Debug, PartialEq)]
55pub enum MessagingDevice<'a> {
56 Scsi(boot::EfiScsiDevice),
57 Unknown {
58 device_subtype: boot::EfiMessagingDeviceSubType,
59 path_data: &'a [u8],
60 },
61}
62
63#[derive(Debug, PartialEq)]
64pub enum MediaDevice<'a> {
65 HardDrive(boot::EfiHardDriveDevice),
66 File(&'a Ucs2LeSlice),
67 PiwgFirmwareFile(Guid),
68 PiwgFirmwareVolume(Guid),
69 Unknown {
70 device_subtype: boot::EfiMediaDeviceSubType,
71 path_data: &'a [u8],
72 },
73}
74
75#[derive(Debug, PartialEq)]
76pub enum EndDevice<'a> {
77 Instance,
78 Entire,
79 Unknown {
80 device_subtype: boot::EfiEndDeviceSubType,
81 path_data: &'a [u8],
82 },
83}
84
85#[derive(Debug)]
86pub enum EfiDevicePathProtocol<'a> {
87 Hardware(HardwareDevice<'a>),
88 Acpi(AcpiDevice<'a>),
89 Messaging(MessagingDevice<'a>),
90 Media(MediaDevice<'a>),
91 End(EndDevice<'a>),
92 Unknown {
93 device_type: boot::EfiDeviceType,
94 device_subtype: u8,
95 path_data: &'a [u8],
96 },
97}
98
99impl<'a> EfiDevicePathProtocol<'a> {
100 pub fn parse(data: &'a [u8]) -> Result<(Self, &'a [u8]), Error> {
101 let (header, path_data) = boot::EfiDevicePathProtocol::read_from_prefix(data)
102 .map_err(|_| Error::InvalidLength)?; let length = u16::from_le_bytes(header.length) as usize;
105 if data.len() < length {
107 return Err(Error::InvalidLength);
108 }
109
110 let (path_data, remaining) = path_data.split_at(
111 length
112 .checked_sub(size_of::<boot::EfiDevicePathProtocol>())
113 .ok_or(Error::InvalidLength)?,
114 );
115
116 Ok((
117 match header.device_type {
118 boot::EfiDeviceType::HARDWARE => EfiDevicePathProtocol::Hardware(
119 match boot::EfiHardwareDeviceSubType(header.sub_type) {
120 boot::EfiHardwareDeviceSubType::MEMORY_MAPPED => {
121 HardwareDevice::MemoryMapped(
122 boot::EfiMemoryMappedDevice::read_from_bytes(path_data)
123 .map_err(|_| Error::InvalidLength)?, )
125 }
126 boot::EfiHardwareDeviceSubType::VENDOR => {
127 let (vendor_guid, path_data) = Guid::read_from_prefix(path_data)
128 .map_err(|_| Error::InvalidLength)?; HardwareDevice::Vendor {
130 vendor_guid,
131 data: path_data,
132 }
133 }
134 device_subtype => HardwareDevice::Unknown {
135 device_subtype,
136 path_data,
137 },
138 },
139 ),
140 boot::EfiDeviceType::ACPI => {
141 EfiDevicePathProtocol::Acpi(match boot::EfiAcpiDeviceSubType(header.sub_type) {
142 boot::EfiAcpiDeviceSubType::EXPANDED_ACPI => {
143 if path_data.len() < size_of::<boot::EfiExpandedAcpiDevice>() + 3 {
145 return Err(Error::InvalidLength);
146 }
147 let (numeric, path_data) =
148 boot::EfiExpandedAcpiDevice::read_from_prefix(path_data).unwrap(); let hidstr = CStr::from_bytes_until_nul(path_data)
150 .map_err(Error::NullTerminated)?;
151 let path_data = &path_data[hidstr.to_bytes_with_nul().len()..];
152 let uidstr = CStr::from_bytes_until_nul(path_data)
153 .map_err(Error::NullTerminated)?;
154 let path_data = &path_data[uidstr.to_bytes_with_nul().len()..];
155 let cidstr = CStr::from_bytes_until_nul(path_data)
156 .map_err(Error::NullTerminated)?;
157 if cidstr.to_bytes_with_nul().len() != path_data.len() {
158 return Err(Error::InvalidLength);
159 }
160 AcpiDevice::ExpandedAcpi {
161 numeric,
162 hidstr,
163 uidstr,
164 cidstr,
165 }
166 }
167 device_subtype => AcpiDevice::Unknown {
168 device_subtype,
169 path_data,
170 },
171 })
172 }
173 boot::EfiDeviceType::MESSAGING => EfiDevicePathProtocol::Messaging(
174 match boot::EfiMessagingDeviceSubType(header.sub_type) {
175 boot::EfiMessagingDeviceSubType::SCSI => MessagingDevice::Scsi(
176 boot::EfiScsiDevice::read_from_bytes(path_data)
177 .map_err(|_| Error::InvalidLength)?, ),
179 device_subtype => MessagingDevice::Unknown {
180 device_subtype,
181 path_data,
182 },
183 },
184 ),
185 boot::EfiDeviceType::MEDIA => EfiDevicePathProtocol::Media(
186 match boot::EfiMediaDeviceSubType(header.sub_type) {
187 boot::EfiMediaDeviceSubType::HARD_DRIVE => MediaDevice::HardDrive(
188 boot::EfiHardDriveDevice::read_from_bytes(path_data)
189 .map_err(|_| Error::InvalidLength)?, ),
191 boot::EfiMediaDeviceSubType::FILE => {
192 let file_name = Ucs2LeSlice::from_slice_with_nul(path_data)
193 .map_err(Error::InvalidUcs2)?;
194 if file_name.as_bytes().len() != path_data.len() {
195 return Err(Error::InvalidLength);
196 }
197 MediaDevice::File(file_name)
198 }
199 boot::EfiMediaDeviceSubType::PIWG_FIRMWARE_FILE => {
200 MediaDevice::PiwgFirmwareFile(
201 Guid::read_from_bytes(path_data)
202 .map_err(|_| Error::InvalidLength)?, )
204 }
205 boot::EfiMediaDeviceSubType::PIWG_FIRMWARE_VOLUME => {
206 MediaDevice::PiwgFirmwareVolume(
207 Guid::read_from_bytes(path_data)
208 .map_err(|_| Error::InvalidLength)?, )
210 }
211 device_subtype => MediaDevice::Unknown {
212 device_subtype,
213 path_data,
214 },
215 },
216 ),
217 boot::EfiDeviceType::END => {
218 EfiDevicePathProtocol::End(match boot::EfiEndDeviceSubType(header.sub_type) {
219 boot::EfiEndDeviceSubType::INSTANCE => EndDevice::Instance,
220 boot::EfiEndDeviceSubType::ENTIRE => EndDevice::Entire,
221 device_subtype => EndDevice::Unknown {
222 device_subtype,
223 path_data,
224 },
225 })
226 }
227 device_type => EfiDevicePathProtocol::Unknown {
228 device_type,
229 device_subtype: header.sub_type,
230 path_data,
231 },
232 },
233 remaining,
234 ))
235 }
236}
237
238#[derive(Debug)]
239pub struct EfiLoadOption<'a> {
240 pub attributes: u32,
241 pub description: &'a Ucs2LeSlice,
242 pub device_paths: Vec<EfiDevicePathProtocol<'a>>,
243 pub opt: Option<&'a [u8]>,
244}
245
246impl<'a> EfiLoadOption<'a> {
247 pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
248 let (header, data) =
249 boot::EfiLoadOption::read_from_prefix(data).map_err(|_| Error::InvalidLength)?; let description = Ucs2LeSlice::from_slice_with_nul(data).map_err(Error::InvalidUcs2)?;
252 let mut data = &data[description.as_bytes().len()..];
253
254 let mut device_paths = Vec::new();
255 loop {
256 if data.len() < size_of::<boot::EfiDevicePathProtocol>() {
257 return Err(Error::DevicePathEnd);
258 }
259 let path;
260 (path, data) = EfiDevicePathProtocol::parse(data)?;
261 match path {
262 EfiDevicePathProtocol::End(end) => match end {
263 EndDevice::Instance => continue,
264 EndDevice::Entire => break,
265 _ => return Err(Error::DevicePathEnd),
266 },
267 path => device_paths.push(path),
268 }
269 }
270
271 let opt = if !data.is_empty() { Some(data) } else { None };
272
273 Ok(EfiLoadOption {
274 attributes: header.attributes,
275 description,
276 device_paths,
277 opt,
278 })
279 }
280}
281
282pub fn parse_boot_order(data: &[u8]) -> Result<impl Iterator<Item = u16> + '_, Error> {
283 let boot_order_iter = data.chunks_exact(2);
284 if !boot_order_iter.remainder().is_empty() {
285 return Err(Error::InvalidLength);
286 }
287 Ok(boot_order_iter.map(|x| u16::from_le_bytes(x.try_into().unwrap())))
288}