use crate::guid::Guid;
use crate::Error;
pub const MIN_ENTRY_SIZE: usize = 128;
const NAME_OFFSET: usize = 56;
const NAME_LEN: usize = 72;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct GptEntry {
pub type_guid: Guid,
pub unique_guid: Guid,
pub first_lba: u64,
pub last_lba: u64,
pub attributes: u64,
pub name: String,
}
fn u64_le(b: &[u8], off: usize) -> u64 {
let mut a = [0u8; 8];
a.copy_from_slice(&b[off..off + 8]);
u64::from_le_bytes(a)
}
impl GptEntry {
pub fn parse(bytes: &[u8]) -> Result<GptEntry, Error> {
if bytes.len() < MIN_ENTRY_SIZE {
return Err(Error::TooShort {
need: MIN_ENTRY_SIZE,
got: bytes.len(),
});
}
let mut type_guid = [0u8; 16];
type_guid.copy_from_slice(&bytes[0..16]);
let mut unique_guid = [0u8; 16];
unique_guid.copy_from_slice(&bytes[16..32]);
Ok(GptEntry {
type_guid: Guid(type_guid),
unique_guid: Guid(unique_guid),
first_lba: u64_le(bytes, 32),
last_lba: u64_le(bytes, 40),
attributes: u64_le(bytes, 48),
name: decode_name(&bytes[NAME_OFFSET..NAME_OFFSET + NAME_LEN]),
})
}
#[must_use]
pub fn is_used(&self) -> bool {
!self.type_guid.is_zero()
}
#[must_use]
pub fn type_name(&self) -> Option<&'static str> {
forensicnomicon::gpt::type_name(&self.type_guid.to_string())
}
#[must_use]
pub fn attribute_names(&self) -> Vec<&'static str> {
forensicnomicon::gpt::attribute_names(self.attributes)
}
}
fn decode_name(bytes: &[u8]) -> String {
let units: Vec<u16> = bytes
.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.take_while(|&u| u != 0)
.collect();
String::from_utf16_lossy(&units)
}
#[must_use]
pub fn parse_entry_array(array: &[u8], num_entries: u32, entry_size: u32) -> Vec<GptEntry> {
let stride = entry_size as usize;
if stride < MIN_ENTRY_SIZE {
return Vec::new();
}
let mut out = Vec::new();
for i in 0..num_entries as usize {
let start = i * stride;
let Some(slot) = array.get(start..start + MIN_ENTRY_SIZE) else {
break;
};
if let Ok(entry) = GptEntry::parse(slot) {
if entry.is_used() {
out.push(entry);
}
}
}
out
}