use super::super::{SpecValidator, ValidationIssue};
use super::helpers::build_test_vhdx;
use crate::constants::{HEADER1_OFFSET, HEADER2_OFFSET, REGION_TABLE_SIZE, REGION_TABLE1_OFFSET};
use crc32c::crc32c;
#[test]
fn validation_issue_fields() {
let issue = ValidationIssue::new(
"header",
"HEADER_SIGNATURE_INVALID",
"invalid header signature",
"MS-VHDX/2.2.2",
);
assert_eq!(issue.section(), "header");
assert_eq!(issue.code(), "HEADER_SIGNATURE_INVALID");
assert_eq!(issue.message(), "invalid header signature");
assert_eq!(issue.spec_ref(), "MS-VHDX/2.2.2");
}
#[test]
fn validate_header_valid() {
let buf = build_test_vhdx();
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_header().is_ok());
}
#[test]
fn validate_header_bad_file_signature() {
let mut buf = build_test_vhdx();
buf[0..8].copy_from_slice(b"NOTAVHDX");
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_header().is_err());
}
#[test]
fn validate_header_corrupted_header1() {
let mut buf = build_test_vhdx();
buf[HEADER1_OFFSET as usize] = 0xFF;
buf[HEADER2_OFFSET as usize] = 0xFF;
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_header().is_err());
}
#[test]
fn validate_header_bad_version() {
let mut buf = build_test_vhdx();
buf[HEADER1_OFFSET as usize + 66..HEADER1_OFFSET as usize + 68]
.copy_from_slice(&2u16.to_le_bytes());
buf[HEADER2_OFFSET as usize + 66..HEADER2_OFFSET as usize + 68]
.copy_from_slice(&2u16.to_le_bytes());
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_header().is_err());
}
#[test]
fn validate_region_table_valid() {
let buf = build_test_vhdx();
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_region_table().is_ok());
}
#[test]
fn validate_region_table_bad_signature() {
let mut buf = build_test_vhdx();
buf[REGION_TABLE1_OFFSET as usize] = 0xFF;
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_region_table().is_err());
}
#[test]
fn validate_region_table_bad_entry_count() {
let mut buf = build_test_vhdx();
buf[REGION_TABLE1_OFFSET as usize + 8..REGION_TABLE1_OFFSET as usize + 12]
.copy_from_slice(&3000u32.to_le_bytes());
let checksum = crc32c(&buf[REGION_TABLE1_OFFSET as usize..][..REGION_TABLE_SIZE as usize]);
buf[REGION_TABLE1_OFFSET as usize + 4..REGION_TABLE1_OFFSET as usize + 8]
.copy_from_slice(&checksum.to_le_bytes());
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_region_table().is_err());
}
#[test]
fn validate_bat_valid() {
let buf = build_test_vhdx();
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_bat().is_ok());
}
#[test]
fn validate_metadata_valid() {
let buf = build_test_vhdx();
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_metadata().is_ok());
}
#[test]
fn validate_required_metadata_items_valid() {
let buf = build_test_vhdx();
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_required_metadata_items().is_ok());
}
#[test]
fn validate_required_metadata_items_missing() {
let mut buf = build_test_vhdx();
let metadata_offset = u64::from_le_bytes(
buf[REGION_TABLE1_OFFSET as usize + 64..REGION_TABLE1_OFFSET as usize + 72]
.try_into()
.unwrap(),
);
let mo = usize::try_from(metadata_offset).expect("metadata offset fits usize");
buf[mo + 32..mo + 48].copy_from_slice(&[0u8; 16]);
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_required_metadata_items().is_err());
}
#[test]
fn test_metadata_item_corrupted_file_parameters() {
let mut buf = build_test_vhdx();
let metadata_offset = u64::from_le_bytes(
buf[REGION_TABLE1_OFFSET as usize + 64..REGION_TABLE1_OFFSET as usize + 72]
.try_into()
.unwrap(),
);
let mo = usize::try_from(metadata_offset).expect("metadata offset fits usize");
buf[mo + 32 + 20..mo + 32 + 24].copy_from_slice(&4u32.to_le_bytes());
let validator = SpecValidator::new(&buf, true);
let issues = validator.validate_metadata().unwrap();
assert!(
issues.iter().any(|i| i.code() == "METADATA_ITEM_CORRUPTED"),
"expected METADATA_ITEM_CORRUPTED for undersized FileParameters"
);
}
#[test]
fn test_metadata_item_corrupted_not_for_valid_size() {
let buf = build_test_vhdx();
let validator = SpecValidator::new(&buf, true);
let issues = validator.validate_metadata().unwrap();
let corrupted: Vec<_> = issues
.iter()
.filter(|i| i.code() == "METADATA_ITEM_CORRUPTED")
.collect();
assert!(
corrupted.is_empty(),
"expected no METADATA_ITEM_CORRUPTED for valid sizes, got: {corrupted:?}"
);
}
#[test]
fn test_metadata_item_corrupted_preserves_missing() {
let mut buf = build_test_vhdx();
let metadata_offset = u64::from_le_bytes(
buf[REGION_TABLE1_OFFSET as usize + 64..REGION_TABLE1_OFFSET as usize + 72]
.try_into()
.unwrap(),
);
let mo = usize::try_from(metadata_offset).expect("metadata offset fits usize");
buf[mo + 32..mo + 48].copy_from_slice(&[0u8; 16]);
let validator = SpecValidator::new(&buf, true);
let result = validator.validate_required_metadata_items();
assert!(result.is_err(), "expected error for missing FileParameters");
assert!(
format!("{result:?}").contains("MetadataRequiredMissing"),
"expected MetadataRequiredMissing, got: {result:?}"
);
}
#[test]
fn validate_log_empty_ok() {
let buf = build_test_vhdx();
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_log().is_ok());
}
#[test]
fn validate_file_valid() {
let buf = build_test_vhdx();
let validator = SpecValidator::new(&buf, true);
assert!(validator.validate_file().is_ok());
}
#[test]
fn has_parent_detection() {
let buf = build_test_vhdx();
let validator = SpecValidator::new(&buf, true);
assert!(!validator.has_parent());
}
#[test]
fn spec_validator_new() {
let data = vec![0u8; 1024 * 1024];
let v = SpecValidator::new(&data, false);
assert!(!v.strict);
let v2 = SpecValidator::new(&data, true);
assert!(v2.strict);
}