use super::{
BAT_REGION_GUID, Error, Guid, Header, METADATA_REGION_GUID, Result, SpecValidator,
ValidationIssue,
};
impl SpecValidator {
pub fn validate_parent_locator(&self) -> Result<Vec<ValidationIssue>> {
let mut issues = Vec::new();
let Some(meta_data) = self.metadata_region() else {
return Ok(issues);
};
let meta = crate::metadata::Metadata::new(meta_data)?;
let Ok(locator) = meta.items().parent_locator() else {
return Ok(issues);
};
Self::validate_parent_locator_keys(&locator, &mut issues)?;
Ok(issues)
}
fn validate_parent_locator_keys(
locator: &crate::metadata::ParentLocator<'_>, issues: &mut Vec<ValidationIssue>,
) -> Result<Option<Guid>> {
let kv_data = locator.key_value_data();
let mut has_parent_linkage = false;
let mut has_path = false;
let mut parent_linkage_guid: Option<Guid> = None;
for kv in locator.entries() {
let key = kv.key(kv_data)?;
match key.as_str() {
"parent_linkage" => {
has_parent_linkage = true;
if let Ok(value) = kv.value(kv_data) {
parent_linkage_guid = parse_guid_from_braced_string(&value);
}
}
"parent_linkage2" => {
Self::push_issue(
issues,
ValidationIssue::new(
"parent_locator",
"PARENT_LOCATOR_LINKAGE2_CONFLICT",
"parent_linkage2 present",
"MS-VHDX/2.6.2.6.3",
),
);
return Err(Error::ParentLocatorLinkage2Conflict);
}
"relative_path" | "volume_path" | "absolute_win32_path" => {
has_path = true;
}
_ => {}
}
}
if !has_parent_linkage {
Self::push_issue(
issues,
ValidationIssue::new(
"parent_locator",
"PARENT_LOCATOR_MISSING_LINKAGE",
"parent_linkage key not found",
"MS-VHDX/2.6.2.6.3",
),
);
return Err(Error::ParentLocatorMissingLinkage);
}
if parent_linkage_guid.is_none() {
Self::push_issue(
issues,
ValidationIssue::new(
"parent_locator",
"PARENT_LOCATOR_FORMAT_ERROR",
"parent_linkage value is not a valid GUID format",
"VALEXT",
),
);
return Err(Error::InvalidParentLocator(
"parent_linkage value is not a valid GUID format".into(),
));
}
if !has_path {
Self::push_issue(
issues,
ValidationIssue::new(
"parent_locator",
"PARENT_LOCATOR_NO_VALID_PATH",
"no valid parent path (relative_path/volume_path/absolute_win32_path)",
"MS-VHDX/2.6.2.6.3",
),
);
return Err(Error::ParentNotFound);
}
Ok(parent_linkage_guid)
}
pub(super) fn parse_header(&self) -> Result<Header<'_>> {
Header::new(&self.data)
}
pub(super) fn log_region(&self) -> Option<&[u8]> {
let header = self.parse_header().ok()?;
let current = header.header(0).ok()?;
let log_offset = usize::try_from(current.log_offset()).ok()?;
let log_length = usize::try_from(current.log_length()).ok()?;
if log_offset == 0 && log_length == 0 {
return None;
}
let end = log_offset.checked_add(log_length)?;
if end > self.data.len() {
return None;
}
Some(&self.data[log_offset..end])
}
pub(super) fn region_for_guid(&self, guid: &Guid) -> Option<&[u8]> {
let header = self.parse_header().ok()?;
let rt = header.region_table(0).ok()?;
for entry in rt.entries() {
if entry.guid() == *guid {
let offset = usize::try_from(entry.file_offset()).ok()?;
let length = usize::try_from(entry.length()).ok()?;
let end = offset.checked_add(length)?;
if end <= self.data.len() {
return Some(&self.data[offset..end]);
}
}
}
None
}
pub(super) fn bat_region(&self) -> Option<&[u8]> {
self.region_for_guid(&BAT_REGION_GUID)
}
pub(super) fn metadata_region(&self) -> Option<&[u8]> {
self.region_for_guid(&METADATA_REGION_GUID)
}
pub(super) fn has_parent(&self) -> bool {
if let Some(meta_data) = self.metadata_region()
&& let Ok(meta) = crate::metadata::Metadata::new(meta_data)
&& let Ok(fp) = meta.items().file_parameters()
{
return fp.has_parent();
}
false
}
pub(super) fn current_log_guid(header: &Header<'_>) -> Result<Guid> {
let current = header.header(0)?;
Ok(current.log_guid())
}
pub(super) fn chunk_ratio(&self) -> u64 {
let block_size = u64::from(self.block_size());
let logical_sector_size = u64::from(self.logical_sector_size());
if block_size == 0 || logical_sector_size == 0 {
return 0;
}
crate::common::compute_chunk_ratio(block_size, logical_sector_size)
}
pub(super) fn block_size(&self) -> u32 {
if let Some(meta_data) = self.metadata_region()
&& let Ok(meta) = crate::metadata::Metadata::new(meta_data)
&& let Ok(fp) = meta.items().file_parameters()
{
return fp.block_size();
}
0
}
pub(super) fn logical_sector_size(&self) -> u32 {
if let Some(meta_data) = self.metadata_region()
&& let Ok(meta) = crate::metadata::Metadata::new(meta_data)
&& let Ok(lss) = meta.items().logical_sector_size()
{
return lss;
}
0
}
pub(super) fn virtual_disk_size(&self) -> u64 {
if let Some(meta_data) = self.metadata_region()
&& let Ok(meta) = crate::metadata::Metadata::new(meta_data)
&& let Ok(vds) = meta.items().virtual_disk_size()
{
return vds;
}
0
}
}
fn parse_guid_from_braced_string(s: &str) -> Option<Guid> {
let s = s.trim();
let inner = s.strip_prefix('{').and_then(|s| s.strip_suffix('}'))?;
let hex: String = inner.chars().filter(|c| *c != '-').collect();
if hex.len() != 32 {
return None;
}
let mut bytes = [0u8; 16];
for i in 0..16 {
let byte_str = &hex[i * 2..i * 2 + 2];
bytes[i] = u8::from_str_radix(byte_str, 16).ok()?;
}
Some(Guid::from_bytes(bytes))
}