1#![cfg_attr(feature = "no_std", no_std)]
8
9use crc::Crc;
10
11use core::char::{decode_utf16, DecodeUtf16Error};
12
13#[cfg(feature = "no_std")]
14extern crate alloc;
15
16#[cfg(feature = "no_std")]
17use alloc::string::String;
18
19#[cfg(feature = "no_std")]
20use core::{mem, ops};
21
22#[cfg(not(feature = "no_std"))]
23use std::{mem, ops};
24
25use ops::{Index, Range};
26
27use mem::size_of;
28
29use explicit_endian::LittleEndian;
30
31#[repr(C, align(1))]
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct Uuid {
34 time_low: LittleEndian<u32>,
35 time_mid: LittleEndian<u16>,
36 time_hi_and_version: LittleEndian<u16>,
37 clock_seq_hi_and_reseved: u8,
38 clock_seq_loq: u8,
39 node: [u8; 6],
40}
41
42impl Uuid {
43 pub fn nil() -> Self {
44 Self {
45 time_low: 0.into(),
46 time_mid: 0.into(),
47 time_hi_and_version: 0.into(),
48 clock_seq_hi_and_reseved: 0,
49 clock_seq_loq: 0,
50 node: [0, 0, 0, 0, 0, 0],
51 }
52 }
53
54 pub fn efi() -> Self {
55 Self {
56 time_low: 0xC12A7328u32.into(),
57 time_mid: 0xF81Fu16.into(),
58 time_hi_and_version: 0x11D2u16.into(),
59 clock_seq_hi_and_reseved: 0xBAu8,
60 clock_seq_loq: 0x4Bu8,
61 node: [0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B]
62 }
63 }
64}
65
66#[repr(C, align(1))]
67#[derive(Debug, Clone, Copy, Eq, PartialEq)]
68pub struct GPTPartition {
69 pub part_type: Uuid,
70 pub id: Uuid,
71 pub first_lba: LittleEndian<u64>,
72 pub last_lba: LittleEndian<u64>,
73 pub attr: [u8; 8],
74 pub name: [LittleEndian<u16>; 36],
75}
76
77#[repr(C, align(1))]
78#[derive(Clone, Copy, Debug, Eq, PartialEq)]
79pub struct GPTHeader {
80 pub signature: [u8; 8], pub revision: LittleEndian<u32>, pub header_size_le: LittleEndian<u32>, pub header_crc32: LittleEndian<u32>, pub reserved: LittleEndian<u32>, pub current_lba: LittleEndian<u64>, pub backup_lba: LittleEndian<u64>, pub first_usable: LittleEndian<u64>, pub last_usable: LittleEndian<u64>, pub disk_guid: Uuid, pub part_start_lba: LittleEndian<u64>, pub num_parts: LittleEndian<u32>, pub part_size: LittleEndian<u32>, pub part_table_crc32: LittleEndian<u32>, }
110
111impl GPTPartition {
112 pub unsafe fn partition_table_checksum(table: &[GPTPartition]) -> u32 {
113 let table: &[u8] = mem::transmute(table);
114 let hasher = Crc::<u32>::new(&crc::CRC_32_JAMCRC);
115 hasher.checksum(table) ^ 0xffffffff
116 }
117
118 pub fn get_name(&self) -> Result<String, DecodeUtf16Error> {
119 let mut ret = String::with_capacity(self.name.len());
120
121 for ch in decode_utf16(self.name.iter().map(|c_le| -> u16 { c_le.clone().into() })) {
122 match ch? {
123 '\0' => break,
124 x => ret.push(x),
125 };
126 }
127
128 Ok(ret)
129 }
130}
131
132#[repr(C)]
133#[derive(Debug, Eq, PartialEq)]
134pub struct LBA<T: Sized + LBAIndex>(T);
135
136#[repr(C)]
137pub struct LBA512([u8; 512]);
138
139#[repr(C)]
140pub struct LBA4K([u8; 4096]);
141
142pub trait LBAIndex {
143 fn index<'a>(&'a self, slice: Range<usize>) -> &'a [u8];
144}
145
146impl LBAIndex for LBA512 {
147 fn index<'a>(&'a self, slice: Range<usize>) -> &'a [u8] {
148 &self.0[slice]
149 }
150}
151
152impl LBAIndex for LBA4K {
153 fn index<'a>(&'a self, slice: Range<usize>) -> &'a [u8] {
154 &self.0[slice]
155 }
156}
157
158impl<T: LBAIndex> Index<Range<usize>> for LBA<T> {
159 type Output = [u8];
160 fn index<'a>(&'a self, slice: Range<usize>) -> &'a [u8] {
161 &self.0.index(slice)
162 }
163}
164
165impl GPTHeader {
166 pub fn verify_signature(&self) -> bool {
167 const SIGNATURE: [u8; 8] = *b"EFI PART";
168 self.signature == SIGNATURE
169 }
170
171 pub unsafe fn parse_gpt_header<'a, T: Sized + LBAIndex>(
172 gpt_header_lba: &'a LBA<T>,
173 ) -> Result<&'a Self, &'static str> {
174 if size_of::<LBA<T>>() > isize::MAX as usize {
175 return Err("Size of LBA block larger than the 32bit address space");
176 }
177 if size_of::<LBA<T>>() < 512 {
178 return Err("Size of LBA smaller than 512 bytes");
179 }
180
181 let header = (gpt_header_lba as *const LBA<T>) as *const GPTHeader;
182 let header = &(*header) as &GPTHeader;
183 if !header.verify_signature() {
184 return Err("Could not verify GPT header signature `EFI PART`");
185 }
186
187 let part_header_size = Into::<u32>::into(header.part_size) as usize;
188
189 if part_header_size % 128 != 0 {
191 return Err("Error in GPT table: Partition size not a multiple of 128");
192 }
193 if (part_header_size / 128).count_ones() != 1 {
194 return Err("Error in GPT table: Partition size not a power of 2");
195 }
196
197 assert_eq!(
199 Self::header_checksum::<T>(&(*header)),
200 header.header_crc32.into(),
201 "GPT header CRC32 verification failed"
202 );
203
204 Ok(header) }
208
209 pub fn parse_partitions<F: Fn(&GPTPartition, &mut T), T>(
210 part_array: &[GPTPartition],
211 size: usize,
212 foreach: F,
213 data: &mut T,
214 ) {
215 let nil_uuid = Uuid::nil();
217
218 let stride = size / size_of::<GPTPartition>();
221
222 for i in 0..part_array.len() {
223 if i % stride != 0 {
224 continue;
225 }
226 if part_array[i].part_type == nil_uuid {
227 continue;
228 }
229 foreach(&part_array[i], data);
230 }
231 }
232
233 fn header_checksum<T: LBAIndex>(header: &GPTHeader) -> u32 {
234 let verify_size = Into::<u32>::into(header.header_size_le) as usize;
235 let mut verify_header: GPTHeader = *header;
236 let v_header_block = (&verify_header as *const GPTHeader) as *const LBA<T>;
237 verify_header.header_crc32 = 0.into();
238 let hasher = Crc::<u32>::new(&crc::CRC_32_JAMCRC);
239 (unsafe { hasher.checksum(&(*v_header_block)[0..verify_size]) }) ^ 0xffffffff
240 }
241}
242
243#[cfg(test)]
244#[cfg(not(feature = "no_std"))]
245mod tests {
246 extern crate base64;
247 extern crate flate2;
248 use base64::Engine;
249 use explicit_endian::LittleEndian;
250
251 use crate::{GPTHeader, GPTPartition, Uuid};
252 use crate::{LBAIndex, LBA, LBA4K, LBA512};
253
254 use std::io::Read;
255 use std::vec::Vec;
256
257 const GPT_EXAMPLE1: &[u8] = b"H4sIAAAAAAAAA+3cPUgjQRQA4E1AOLAQezFi5d9ZWQgSwSKGEAsJCRwStBTFyoBgIQQMlnZqqVUOQtA+XRolVpJKjlME7YSzsEkjuQib9gqPHCd8Hwxv583beQNT7wYBn1k0+NVutyOdp6sPvJ2rJBZTI8sLmWwQRIJ8J/Pl549v7yuRsKK762gYy2GcfDx6fsilosX+6+HXfDoWDfPFcIyvr7x84ED8Yxfxy4H90nbycDe+cZvce8pOn9xUWom30/O72Xpiqtq999Ue9R8rTNRjreZgLR2cNeYbczsHhWzfZnroeGareZ9Zqq6FdeU/7gIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACfy0X8cmC/tJ083I1v3Cb3nrLTJzeVVuLt9Pxutp6Yqo6Gdas96j9WmKjHWs3BWjo4a8w35nYOCtm+zfTQ8cxW8z6zVF0L68o96g8AAAAAAAAAAAAAAAAAAAD/g8RiamR5IZMNgkiQ78wzX2ul9/xVuB4JY/c/AN3v8Ccfj54fcqlosf96+DWfjn0P88VwjK+vvPT+9Pyt3wAlEswAkAEA";
259
260 const GPT_EXAMPLE_LBA4K: &[u8] = b"H4sICFR+fWIAA2xiYTRrLmltZwDt3L9LI0EUAOBdixSJaJErrA57aysJomBiCvEHURCsBRsRGwmiaLS4SrCwCoiV4DVaCEGwE2z8B0RMozYipFYQ1giTVkQu4sH3wTA7b97se+0Uu1HE/6wjaiRJEjefer5wevrvv+4HAACAn2kkX+ydGJoqRVEczTXXS5U/pfd4HPZbt8pUmLNhPuq8WV3pH62dZV6S58b+cEeIb4RxUdvda3/3AAAAwGcc5y67t7aXCzvl3MJ1Ye3hZOBqMb0+/npe3ZypHk7ete796W+qP1jPz9/3jc2edlXq5dTjQSbkZT98CwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAx7nL7q3t5cJOObdwXVh7OBm4Wkyvj7+eVzdnqoeTd6mQl/6m+oP1/Px939jsaVelXk49HmRCXrZN9QEAAAAAAAAAAAAAAAAAAOAnGMkXeyeGpkpRFEdzzfVt8en3e7wn7Mdhbv0HoPUd/lHnzepK/2jtLPOSPDf2h3+F+EYYF7XdvfZ3DwAAAHzGGwzc9x8AkAEA";
262
263 fn test_disk_image<T: LBAIndex>(image: &[u8], uuid_time_low_1: u32, uuid_time_low_2: u32) {
264 let base_decoded = base64::engine::general_purpose::STANDARD
265 .decode(image)
266 .unwrap();
267 let mut disk_data: Vec<u8> = Vec::new();
268 assert!(
269 flate2::read::GzDecoder::new(&base_decoded[..])
270 .read_to_end(&mut disk_data)
271 .is_ok(),
272 "flat2 could not gunzip disk blob"
273 );
274
275 let mbr_lba = disk_data.as_ptr() as *const LBA<T>;
277
278 let gpt_header = unsafe { GPTHeader::parse_gpt_header(&*mbr_lba.add(1)) };
279 assert!(
280 gpt_header.is_ok(),
281 "Could not parse GPT Header: {}",
282 gpt_header.unwrap_err()
283 );
284 let gpt_header = gpt_header.unwrap();
285
286 let partition_lba_offset = Into::<u64>::into(gpt_header.part_start_lba) as usize;
288 let partition_lba_ptr = unsafe { mbr_lba.add(partition_lba_offset) as *const GPTPartition };
289
290 let part_size = Into::<u32>::into(gpt_header.part_size) as usize;
291 let num_parts = Into::<u32>::into(gpt_header.num_parts) as usize;
292
293 assert!(
294 disk_data.len() > (std::mem::size_of::<LBA<T>>() * 2 + (part_size * num_parts)),
295 "Partition table is bigger than buffer size"
296 );
297
298 let stride = part_size / std::mem::size_of::<GPTPartition>();
299 let length = stride * num_parts;
300 let part_array_ptr = core::ptr::slice_from_raw_parts(partition_lba_ptr, length);
301 let part_array = unsafe { &*part_array_ptr };
302
303 let checksum_array =
304 core::ptr::slice_from_raw_parts(partition_lba_ptr, num_parts * part_size);
305 let sum = unsafe { GPTPartition::partition_table_checksum(&*checksum_array) };
306
307 assert_eq!(
308 sum,
309 Into::<u32>::into(gpt_header.part_table_crc32),
310 "Bad CRC for partition array"
311 );
312
313 let mut partitions: Vec<GPTPartition> = Vec::new();
314 GPTHeader::parse_partitions(
315 part_array,
316 Into::<u32>::into(gpt_header.part_size) as usize,
317 |part, parts| {
318 parts.push(part.clone());
319 },
320 &mut partitions,
321 );
322
323 assert_eq!(partitions.len(), 2, "List of partitions should have been 2");
324 let guid_time_low = Into::<u32>::into(partitions[0].part_type.time_low);
325 assert_eq!(
326 guid_time_low, uuid_time_low_1,
327 "First partition type did not match (time_low) expected value {:x}: {:x}",
328 uuid_time_low_1, guid_time_low
329 );
330 let guid_time_low = Into::<u32>::into(partitions[1].part_type.time_low);
331 assert_eq!(
332 guid_time_low, uuid_time_low_2,
333 "Second partition type did not match (time_low) expected value {:x}: {:x}",
334 uuid_time_low_2, guid_time_low
335 );
336 }
337
338 #[test]
339 fn parse_512_header() {
340 test_disk_image::<LBA512>(GPT_EXAMPLE1, 0x0fc63daf, 0xc12a7328);
341 }
342
343 #[test]
344 fn parse_4k_header() {
345 test_disk_image::<LBA4K>(GPT_EXAMPLE_LBA4K, 0x0fc63daf, 0x0fc63daf);
346 }
347
348 #[test]
349 fn test_decode_name() {
350 let v: Vec<u16> = "Hello".encode_utf16().collect();
351 let mut v: Vec<LittleEndian<u16>> = v.iter().map(|v| v.clone().into()).collect();
352
353 let mut part = GPTPartition {
354 part_type: Uuid::nil(),
355 id: Uuid::nil(),
356 first_lba: 0.into(),
357 last_lba: 0.into(),
358 attr: unsafe { std::mem::zeroed() },
359 name: unsafe { std::mem::zeroed() },
360 };
361
362 v.resize(part.name.len(), 0.into());
363 part.name.copy_from_slice(&v);
364
365 let name = part.get_name().unwrap();
366 assert_eq!(name, "Hello");
367
368 }
370}