1use crate::guid::Guid;
8use crate::Error;
9
10pub const MIN_ENTRY_SIZE: usize = 128;
12const NAME_OFFSET: usize = 56;
14const NAME_LEN: usize = 72;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20pub struct GptEntry {
21 pub type_guid: Guid,
23 pub unique_guid: Guid,
25 pub first_lba: u64,
27 pub last_lba: u64,
29 pub attributes: u64,
31 pub name: String,
33}
34
35fn u64_le(b: &[u8], off: usize) -> u64 {
36 let mut a = [0u8; 8];
37 a.copy_from_slice(&b[off..off + 8]);
38 u64::from_le_bytes(a)
39}
40
41impl GptEntry {
42 pub fn parse(bytes: &[u8]) -> Result<GptEntry, Error> {
47 if bytes.len() < MIN_ENTRY_SIZE {
48 return Err(Error::TooShort {
49 need: MIN_ENTRY_SIZE,
50 got: bytes.len(),
51 });
52 }
53 let mut type_guid = [0u8; 16];
54 type_guid.copy_from_slice(&bytes[0..16]);
55 let mut unique_guid = [0u8; 16];
56 unique_guid.copy_from_slice(&bytes[16..32]);
57 Ok(GptEntry {
58 type_guid: Guid(type_guid),
59 unique_guid: Guid(unique_guid),
60 first_lba: u64_le(bytes, 32),
61 last_lba: u64_le(bytes, 40),
62 attributes: u64_le(bytes, 48),
63 name: decode_name(&bytes[NAME_OFFSET..NAME_OFFSET + NAME_LEN]),
64 })
65 }
66
67 #[must_use]
69 pub fn is_used(&self) -> bool {
70 !self.type_guid.is_zero()
71 }
72
73 #[must_use]
76 pub fn type_name(&self) -> Option<&'static str> {
77 forensicnomicon::gpt::type_name(&self.type_guid.to_string())
78 }
79
80 #[must_use]
83 pub fn attribute_names(&self) -> Vec<&'static str> {
84 forensicnomicon::gpt::attribute_names(self.attributes)
85 }
86}
87
88fn decode_name(bytes: &[u8]) -> String {
90 let units: Vec<u16> = bytes
91 .chunks_exact(2)
92 .map(|c| u16::from_le_bytes([c[0], c[1]]))
93 .take_while(|&u| u != 0)
94 .collect();
95 String::from_utf16_lossy(&units)
96}
97
98#[must_use]
104pub fn parse_entry_array(array: &[u8], num_entries: u32, entry_size: u32) -> Vec<GptEntry> {
105 let stride = entry_size as usize;
106 if stride < MIN_ENTRY_SIZE {
107 return Vec::new();
108 }
109 let mut out = Vec::new();
110 for i in 0..num_entries as usize {
111 let start = i * stride;
112 let Some(slot) = array.get(start..start + MIN_ENTRY_SIZE) else {
113 break;
114 };
115 if let Ok(entry) = GptEntry::parse(slot) {
116 if entry.is_used() {
117 out.push(entry);
118 }
119 }
120 }
121 out
122}