use bitvec::prelude::*;
use crate::constants::{
FP_BITFIELDS, FP_BITS_END, FP_BLOCK_SIZE, FP_HAS_PARENT, FP_LEAVE_BLOCK_ALLOCATED,
FP_RESERVED_START, KV_ENTRY_SIZE, LOCATOR_HEADER_SIZE, METADATA_SIGNATURE, METADATA_TABLE_SIZE,
TABLE_ENTRY_SIZE, TABLE_HEADER_SIZE,
};
use crate::error::{Error, Result, SignaturePosition};
use crate::types::Guid;
pub use crate::types::StandardItems;
#[derive(Clone, Copy)]
pub struct Metadata<'a> {
data: &'a [u8],
}
impl<'a> Metadata<'a> {
pub(crate) fn new(data: &'a [u8]) -> Result<Self> {
if data.len() < METADATA_TABLE_SIZE as usize {
return Err(Error::InvalidMetadata(format!(
"metadata region too small: {} bytes, need at least {METADATA_TABLE_SIZE}",
data.len()
)));
}
Ok(Self { data })
}
#[must_use]
pub fn table(&self) -> MetadataTable<'a> {
MetadataTable {
data: &self.data[..METADATA_TABLE_SIZE as usize],
}
}
#[must_use]
pub fn items(&self) -> MetadataItems<'a> {
MetadataItems {
table: self.table(),
items_data: self.data,
}
}
}
pub struct MetadataTable<'a> {
data: &'a [u8],
}
impl<'a> MetadataTable<'a> {
#[must_use]
pub fn header(&self) -> TableHeader<'a> {
TableHeader {
data: &self.data[..TABLE_HEADER_SIZE as usize],
}
}
pub fn entry(&self, item_id: &Guid) -> Result<TableEntry<'a>> {
for e in self.entries() {
if e.item_id() == *item_id {
return Ok(e);
}
}
Err(Error::MetadataNotFound { guid: *item_id })
}
pub fn entries(&self) -> impl Iterator<Item = TableEntry<'a>> + 'a {
let count = self.header().entry_count() as usize;
let data = self.data;
(0..count).map(move |i| {
let start = TABLE_HEADER_SIZE as usize + i * TABLE_ENTRY_SIZE as usize;
TableEntry {
data: &data[start..start + TABLE_ENTRY_SIZE as usize],
}
})
}
}
pub struct TableHeader<'a> {
data: &'a [u8],
}
impl<'a> TableHeader<'a> {
#[must_use]
pub fn signature(&self) -> &'a [u8; 8] {
self.data[..8]
.try_into()
.expect("header has 8 signature bytes")
}
#[must_use]
pub fn reserved(&self) -> &'a [u8; 2] {
self.data[8..10]
.try_into()
.expect("header has 2 reserved bytes")
}
#[must_use]
pub fn entry_count(&self) -> u16 {
u16::from_le_bytes(self.data[10..12].try_into().unwrap())
}
#[must_use]
pub fn reserved2(&self) -> &'a [u8; 20] {
self.data[12..32]
.try_into()
.expect("header has 20 reserved2 bytes")
}
pub(crate) fn validate_signature(&self) -> Result<()> {
let signature = *self.signature();
if signature.view_bits::<Lsb0>() != *METADATA_SIGNATURE {
return Err(Error::InvalidSignature {
position: SignaturePosition::MetadataTable,
expected: METADATA_SIGNATURE.into_inner().to_le_bytes(),
found: signature,
});
}
Ok(())
}
}
pub struct TableEntry<'a> {
data: &'a [u8],
}
impl TableEntry<'_> {
#[must_use]
pub fn item_id(&self) -> Guid {
let bytes: [u8; 16] = self.data[..16].try_into().expect("entry has 16 guid bytes");
Guid::from_bytes(bytes)
}
#[must_use]
pub fn offset(&self) -> u32 {
u32::from_le_bytes(self.data[16..20].try_into().unwrap())
}
#[must_use]
pub fn length(&self) -> u32 {
u32::from_le_bytes(self.data[20..24].try_into().unwrap())
}
#[must_use]
pub fn flags_bits(&self) -> u32 {
u32::from_le_bytes(self.data[24..28].try_into().unwrap())
}
#[must_use]
pub fn reserved(&self) -> u32 {
u32::from_le_bytes(self.data[28..32].try_into().unwrap())
}
#[must_use]
pub fn flags(&self) -> EntryFlags<'_> {
EntryFlags {
data: &self.data[24..28],
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct EntryFlags<'a> {
pub(super) data: &'a [u8],
}
impl EntryFlags<'_> {
#[cfg(test)]
pub(crate) fn new(data: &[u8]) -> EntryFlags<'_> {
EntryFlags { data }
}
#[must_use]
pub fn is_user(&self) -> bool {
self.data.view_bits::<Lsb0>()[0]
}
#[must_use]
pub fn is_virtual_disk(&self) -> bool {
self.data.view_bits::<Lsb0>()[1]
}
#[must_use]
pub fn is_required(&self) -> bool {
self.data.view_bits::<Lsb0>()[2]
}
pub(crate) fn has_reserved_bits(&self) -> bool {
self.data.view_bits::<Lsb0>()[3..=31].any()
}
}
pub struct MetadataItems<'a> {
table: MetadataTable<'a>,
items_data: &'a [u8],
}
impl<'a> MetadataItems<'a> {
fn item_data(&self, guid: &Guid) -> Result<&'a [u8]> {
let Ok(entry) = self.table.entry(guid) else {
return Err(Error::MetadataRequiredMissing { guid: *guid });
};
let offset = entry.offset() as usize;
let length = entry.length() as usize;
if length == 0 {
return Ok(&[]);
}
let end = offset
.checked_add(length)
.ok_or(Error::MetadataRequiredMissing { guid: *guid })?;
if end > self.items_data.len() {
return Err(Error::MetadataRequiredMissing { guid: *guid });
}
Ok(&self.items_data[offset..end])
}
pub fn file_parameters(&self) -> Result<FileParameters<'a>> {
let data = self.item_data(&StandardItems::FILE_PARAMETERS)?;
Ok(FileParameters { data })
}
pub fn virtual_disk_size(&self) -> Result<u64> {
let data = self.item_data(&StandardItems::VIRTUAL_DISK_SIZE)?;
if data.len() < 8 {
return Err(Error::MetadataRequiredMissing {
guid: StandardItems::VIRTUAL_DISK_SIZE,
});
}
Ok(u64::from_le_bytes(data[..8].try_into().unwrap()))
}
pub fn virtual_disk_id(&self) -> Result<Guid> {
let data = self.item_data(&StandardItems::VIRTUAL_DISK_ID)?;
if data.len() < 16 {
return Err(Error::MetadataRequiredMissing {
guid: StandardItems::VIRTUAL_DISK_ID,
});
}
let bytes: [u8; 16] =
data[..16]
.try_into()
.map_err(|_| Error::MetadataRequiredMissing {
guid: StandardItems::VIRTUAL_DISK_ID,
})?;
Ok(Guid::from_bytes(bytes))
}
pub fn logical_sector_size(&self) -> Result<u32> {
let data = self.item_data(&StandardItems::LOGICAL_SECTOR_SIZE)?;
if data.len() < 4 {
return Err(Error::MetadataRequiredMissing {
guid: StandardItems::LOGICAL_SECTOR_SIZE,
});
}
Ok(u32::from_le_bytes(data[..4].try_into().unwrap()))
}
pub fn physical_sector_size(&self) -> Result<u32> {
let data = self.item_data(&StandardItems::PHYSICAL_SECTOR_SIZE)?;
if data.len() < 4 {
return Err(Error::MetadataRequiredMissing {
guid: StandardItems::PHYSICAL_SECTOR_SIZE,
});
}
Ok(u32::from_le_bytes(data[..4].try_into().unwrap()))
}
pub fn parent_locator(&self) -> Result<ParentLocator<'a>> {
let data = self.item_data(&StandardItems::PARENT_LOCATOR)?;
Ok(ParentLocator { data })
}
}
pub struct FileParameters<'a> {
data: &'a [u8],
}
impl FileParameters<'_> {
#[must_use]
pub fn block_size(&self) -> u32 {
if self.data.len() < 8 {
return 0;
}
self.data.view_bits::<Lsb0>()[FP_BLOCK_SIZE].load_le::<u32>()
}
pub(crate) fn flags(&self) -> u32 {
if self.data.len() < 8 {
return 0;
}
self.data.view_bits::<Lsb0>()[FP_BITFIELDS].load_le::<u32>()
}
#[must_use]
pub fn leave_block_allocated(&self) -> bool {
if self.data.len() < 8 {
return false;
}
self.data.view_bits::<Lsb0>()[FP_LEAVE_BLOCK_ALLOCATED]
}
#[must_use]
pub fn has_parent(&self) -> bool {
if self.data.len() < 8 {
return false;
}
self.data.view_bits::<Lsb0>()[FP_HAS_PARENT]
}
pub(crate) fn has_reserved_bits_set(&self) -> bool {
if self.data.len() < 8 {
return false;
}
self.data.view_bits::<Lsb0>()[FP_RESERVED_START..FP_BITS_END].any()
}
}
pub struct ParentLocator<'a> {
data: &'a [u8],
}
impl<'a> ParentLocator<'a> {
#[must_use]
pub fn header(&self) -> LocatorHeader<'a> {
LocatorHeader {
data: &self.data[..(LOCATOR_HEADER_SIZE as usize).min(self.data.len())],
}
}
pub fn entry(&self, index: usize) -> Result<KeyValueEntry<'a>> {
let count = self.header().key_value_count() as usize;
if index >= count {
return Err(Error::InvalidParameter(format!(
"parent locator entry index {index} out of range (count={count})"
)));
}
let start = LOCATOR_HEADER_SIZE as usize + index * KV_ENTRY_SIZE as usize;
let end = start + KV_ENTRY_SIZE as usize;
if end > self.data.len() {
return Err(Error::InvalidParentLocator(
"parent locator data too short for entries".into(),
));
}
Ok(KeyValueEntry {
data: &self.data[start..end],
})
}
pub fn entries(&self) -> impl Iterator<Item = KeyValueEntry<'a>> + 'a {
let count = self.header().key_value_count() as usize;
let data = self.data;
(0..count).filter_map(move |i| {
let start = LOCATOR_HEADER_SIZE as usize + i * KV_ENTRY_SIZE as usize;
let end = start + KV_ENTRY_SIZE as usize;
if end <= data.len() {
Some(KeyValueEntry {
data: &data[start..end],
})
} else {
None
}
})
}
#[must_use]
pub fn key_value_data(&self) -> &'a [u8] {
self.data
}
pub fn resolve_parent_path(&self) -> Result<std::path::PathBuf> {
const PATH_KEYS: [&str; 3] = ["relative_path", "volume_path", "absolute_win32_path"];
let data = self.key_value_data();
let mut paths: [Option<std::path::PathBuf>; 3] = [None, None, None];
for entry in self.entries() {
let key = entry.key(data)?;
if let Some(index) = PATH_KEYS.iter().position(|candidate| *candidate == key) {
paths[index] = Some(std::path::PathBuf::from(entry.value(data)?));
}
}
paths
.into_iter()
.flatten()
.next()
.ok_or(Error::ParentNotFound)
}
}
pub struct LocatorHeader<'a> {
data: &'a [u8],
}
impl LocatorHeader<'_> {
#[must_use]
pub fn locator_type(&self) -> Guid {
let bytes: [u8; 16] = if self.data.len() >= 16 {
self.data[..16].try_into().expect("16 bytes")
} else {
[0u8; 16]
};
Guid::from_bytes(bytes)
}
#[must_use]
pub fn reserved(&self) -> u16 {
if self.data.len() >= 18 {
u16::from_le_bytes(self.data[16..18].try_into().unwrap())
} else {
0
}
}
#[must_use]
pub fn key_value_count(&self) -> u16 {
if self.data.len() >= 20 {
u16::from_le_bytes(self.data[18..20].try_into().unwrap())
} else {
0
}
}
}
pub struct KeyValueEntry<'a> {
data: &'a [u8],
}
impl KeyValueEntry<'_> {
#[must_use]
pub fn key_offset(&self) -> u32 {
u32::from_le_bytes(self.data[..4].try_into().unwrap())
}
#[must_use]
pub fn value_offset(&self) -> u32 {
u32::from_le_bytes(self.data[4..8].try_into().unwrap())
}
#[must_use]
pub fn key_length(&self) -> u16 {
u16::from_le_bytes(self.data[8..10].try_into().unwrap())
}
#[must_use]
pub fn value_length(&self) -> u16 {
u16::from_le_bytes(self.data[10..12].try_into().unwrap())
}
pub fn key(&self, data: &[u8]) -> Result<String> {
decode_utf16le(data, self.key_offset() as usize, self.key_length() as usize)
}
pub fn value(&self, data: &[u8]) -> Result<String> {
decode_utf16le(
data,
self.value_offset() as usize,
self.value_length() as usize,
)
}
}
pub(crate) fn decode_utf16le(data: &[u8], offset: usize, byte_len: usize) -> Result<String> {
let end = offset
.checked_add(byte_len)
.ok_or_else(|| Error::InvalidParentLocator("key/value offset+length overflow".into()))?;
if end > data.len() {
return Err(Error::InvalidParentLocator(format!(
"key/value data out of bounds: offset={offset}, len={byte_len}, data_len={}",
data.len()
)));
}
if !byte_len.is_multiple_of(2) {
return Err(Error::InvalidParentLocator(
"UTF-16LE string has odd byte length".into(),
));
}
let units: Vec<u16> = data[offset..end]
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect();
String::from_utf16(&units)
.map_err(|e| Error::InvalidParentLocator(format!("invalid UTF-16LE string: {e}")))
}