1use std::{convert::TryInto, fmt::Display};
4
5use byteorder::{LittleEndian, ReadBytesExt};
6use uuid::Uuid;
7
8use crate::{push::PushVecU8, utils::read_nt_utf16_string, Error};
9
10use super::consts;
11
12pub enum DevicePath {
13 FilePath(FilePath),
14 HardDrive(EFIHardDrive),
15}
16
17#[derive(Debug, PartialEq, Clone)]
18pub enum EFIHardDriveType {
19 Mbr,
20 Gpt,
21 Unknown, }
23
24impl EFIHardDriveType {
25 pub fn parse(sig_type: u8) -> EFIHardDriveType {
26 match sig_type {
27 0x01 => Self::Mbr,
28 0x02 => Self::Gpt,
29 _ => Self::Unknown,
30 }
31 }
32
33 pub fn as_u8(&self) -> u8 {
34 match self {
35 EFIHardDriveType::Mbr => 0x01,
36 EFIHardDriveType::Gpt => 0x02,
37 EFIHardDriveType::Unknown => panic!(),
38 }
39 }
40}
41
42impl Display for EFIHardDriveType {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 EFIHardDriveType::Mbr => f.write_str("MBR"),
46 EFIHardDriveType::Gpt => f.write_str("GPT"),
47 EFIHardDriveType::Unknown => f.write_str("Unknown"),
48 }
49 }
50}
51
52#[derive(Debug, PartialEq, Clone)]
53pub struct EFIHardDrive {
55 pub partition_number: u32,
56 pub partition_start: u64,
57 pub partition_size: u64,
58 pub partition_sig: Uuid,
59 pub format: u8,
60 pub sig_type: EFIHardDriveType,
61}
62
63impl EFIHardDrive {
64 pub fn parse(buf: &mut &[u8]) -> crate::Result<EFIHardDrive> {
65 Ok(EFIHardDrive {
66 partition_number: buf
67 .read_u32::<LittleEndian>()
68 .map_err(|_| Error::VarParseError)?,
69 partition_start: buf
70 .read_u64::<LittleEndian>()
71 .map_err(|_| Error::VarParseError)?,
72 partition_size: buf
73 .read_u64::<LittleEndian>()
74 .map_err(|_| Error::VarParseError)?,
75 partition_sig: Uuid::from_fields(
76 buf.read_u32::<LittleEndian>()
77 .map_err(|_| Error::VarParseError)?,
78 buf.read_u16::<LittleEndian>()
79 .map_err(|_| Error::VarParseError)?,
80 buf.read_u16::<LittleEndian>()
81 .map_err(|_| Error::VarParseError)?,
82 &buf.read_u64::<LittleEndian>()
83 .map_err(|_| Error::VarParseError)?
84 .to_le_bytes(),
85 ),
86 format: buf.read_u8().map_err(|_| Error::VarParseError)?,
87 sig_type: EFIHardDriveType::parse(buf.read_u8().map_err(|_| Error::VarParseError)?),
88 })
89 }
90}
91
92impl Display for EFIHardDrive {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 write!(
95 f,
96 "HD({},{},{})",
97 self.partition_number, self.sig_type, self.partition_sig
98 )
99 }
100}
101
102impl DevicePath {
103 pub fn parse(buf: &mut &[u8]) -> crate::Result<Option<DevicePath>> {
104 let r#type = buf.read_u8().map_err(|_| Error::VarParseError)?;
105 let subtype = buf.read_u8().map_err(|_| Error::VarParseError)?;
106 let length = buf
107 .read_u16::<LittleEndian>()
108 .map_err(|_| Error::VarParseError)?;
109
110 let data_size = length - 1 - 1 - 2;
111
112 if data_size as usize > buf.len() {
113 return Err(Error::VarParseError);
114 }
115
116 let (mut device_path_data, new_buf) = buf.split_at(data_size.into());
117 *buf = new_buf;
118
119 #[allow(clippy::single_match)]
120 match r#type {
121 consts::DEVICE_PATH_TYPE::MEDIA_DEVICE_PATH => match subtype {
122 consts::MEDIA_DEVICE_PATH_SUBTYPE::FILE_PATH => {
123 return Ok(Some(DevicePath::FilePath(FilePath {
124 path: read_nt_utf16_string(&mut device_path_data)
125 .map_err(crate::Error::StringParseError)?,
126 })));
127 }
128 consts::MEDIA_DEVICE_PATH_SUBTYPE::HARD_DRIVE => {
129 return Ok(Some(DevicePath::HardDrive(EFIHardDrive::parse(
130 &mut device_path_data,
131 )?)));
132 }
133 _ => {}
134 },
135 _ => {}
136 };
137
138 Ok(None)
139 }
140}
141
142fn encap_as_device_path(r#type: u8, r#subtype: u8, mut raw_data: Vec<u8>) -> Vec<u8> {
143 let mut bytes: Vec<u8> = vec![];
144
145 bytes.push_u8(r#type);
146 bytes.push_u8(r#subtype);
147
148 let raw_data_size: u16 = raw_data.len().try_into().expect("length should fit in u16");
149 bytes.push_u16(raw_data_size + 1 + 1 + 2);
150
151 bytes.append(&mut raw_data);
152
153 bytes
154}
155
156impl EFIHardDrive {
157 fn to_bytes_raw(&self) -> Vec<u8> {
159 let mut bytes: Vec<u8> = vec![];
160 bytes.push_u32(self.partition_number);
161 bytes.push_u64(self.partition_start);
162 bytes.push_u64(self.partition_size);
163
164 let (f1, f2, f3, f4) = self.partition_sig.as_fields();
165 bytes.push_u32(f1);
166 bytes.push_u16(f2);
167 bytes.push_u16(f3);
168 bytes.append(&mut f4.to_vec());
169 bytes.push_u8(self.format);
170 bytes.push_u8(self.sig_type.as_u8());
171
172 bytes
173 }
174
175 pub fn to_bytes_encap(&self) -> Vec<u8> {
177 encap_as_device_path(
178 consts::DEVICE_PATH_TYPE::MEDIA_DEVICE_PATH,
179 consts::MEDIA_DEVICE_PATH_SUBTYPE::HARD_DRIVE,
180 self.to_bytes_raw(),
181 )
182 }
183}
184
185#[derive(Debug, PartialEq, Clone)]
186pub struct FilePath {
187 pub path: String,
189}
190
191impl FilePath {
192 fn to_bytes_raw(&self) -> Vec<u8> {
194 let utf16_bytes = self.path.encode_utf16();
195 let mut utf8_bytes: Vec<u8> = utf16_bytes
196 .into_iter()
197 .flat_map(|var| var.to_le_bytes())
198 .collect();
199
200 utf8_bytes.push_u16(0x0000);
202
203 utf8_bytes
204 }
205
206 pub fn to_bytes_encap(&self) -> Vec<u8> {
208 encap_as_device_path(
209 consts::DEVICE_PATH_TYPE::MEDIA_DEVICE_PATH,
210 consts::MEDIA_DEVICE_PATH_SUBTYPE::FILE_PATH,
211 self.to_bytes_raw(),
212 )
213 }
214}
215
216pub fn get_end_device_path_bytes() -> Vec<u8> {
217 encap_as_device_path(
218 consts::DEVICE_PATH_TYPE::END_OF_HARDWARE_DEVICE_PATH,
219 consts::END_OF_HARDWARE_DEVICE_PATH_SUBTYPE::END_ENTIRE_DEVICE_PATH,
220 vec![],
221 )
222}
223
224#[cfg(test)]
225mod tests {
226 use std::str::FromStr;
227
228 use uuid::Uuid;
229
230 use super::{EFIHardDrive, EFIHardDriveType};
231
232 #[test]
233 fn efi_hard_drive_type_parse() {
234 assert_eq!(EFIHardDriveType::parse(0x01), EFIHardDriveType::Mbr);
235 assert_eq!(EFIHardDriveType::parse(0x02), EFIHardDriveType::Gpt);
236 assert_eq!(EFIHardDriveType::parse(0x03), EFIHardDriveType::Unknown);
237 assert_eq!(EFIHardDriveType::parse(0xFF), EFIHardDriveType::Unknown);
238 }
239
240 #[test]
241 fn efi_hard_drive_type_dump() {
242 assert_eq!(EFIHardDriveType::Mbr.as_u8(), 0x01);
243 assert_eq!(EFIHardDriveType::Gpt.as_u8(), 0x02);
244 }
245
246 #[test]
247 fn efi_hard_drive_type_print() {
248 assert_eq!(format!("{}", EFIHardDriveType::Mbr), "MBR");
249 assert_eq!(format!("{}", EFIHardDriveType::Gpt), "GPT");
250 assert_eq!(format!("{}", EFIHardDriveType::Unknown), "Unknown");
251 }
252
253 #[test]
254 #[should_panic]
255 fn efi_hard_drive_type_dump_invalid() {
256 EFIHardDriveType::Unknown.as_u8();
257 }
258
259 #[test]
260 fn print_hard_drive() {
261 assert_eq!(
262 "HD(1,GPT,90364bbd-1000-47fc-8c05-8707e01b4593)",
263 format!(
264 "{}",
265 EFIHardDrive {
266 partition_number: 1,
267 partition_start: 2,
268 partition_size: 3,
269 partition_sig: Uuid::from_str("90364bbd-1000-47fc-8c05-8707e01b4593").unwrap(),
270 format: 5,
271 sig_type: EFIHardDriveType::Gpt,
272 }
273 )
274 );
275 }
276
277 #[test]
278 fn to_from_bytes() {
279 let drive = EFIHardDrive {
280 partition_number: 1,
281 partition_start: 2,
282 partition_size: 3,
283 partition_sig: Uuid::from_str("90364bbd-1000-47fc-8c05-8707e01b4593").unwrap(),
284 format: 5,
285 sig_type: EFIHardDriveType::Gpt,
286 };
287 let bytes = drive.to_bytes_raw();
288 let mut x = bytes.as_slice();
289 let test_parse = EFIHardDrive::parse(&mut x).unwrap();
290 assert_eq!(drive, test_parse)
291 }
292}