use super::*;
use crate::constants::{
HEADER_SIZE, HEADER1_OFFSET, HEADER2_OFFSET, REGION_ENTRY_SIZE, REGION_TABLE_SIZE,
REGION_TABLE1_OFFSET, REGION_TABLE2_OFFSET, RT_HEADER_SIZE,
};
use crate::error::Error;
use crate::types::Crc32c;
use bitvec::prelude::*;
use crc32c::crc32c;
fn build_test_header_section() -> Vec<u8> {
let mut buf = vec![0u8; (REGION_TABLE2_OFFSET + REGION_TABLE_SIZE) as usize];
buf[0..8].copy_from_slice(b"vhdxfile");
write_header(&mut buf, HEADER1_OFFSET as usize, 5);
write_header(&mut buf, HEADER2_OFFSET as usize, 3);
write_region_table(&mut buf, REGION_TABLE1_OFFSET as usize, 2);
write_region_table(&mut buf, REGION_TABLE2_OFFSET as usize, 2);
buf
}
fn write_header(buf: &mut [u8], offset: usize, seq: u64) {
let slice = &mut buf[offset..][..HEADER_SIZE as usize];
slice[..4].copy_from_slice(b"head");
slice[4..8].copy_from_slice(&0u32.to_le_bytes()); slice[8..16].copy_from_slice(&seq.to_le_bytes());
slice[64..66].copy_from_slice(&0u16.to_le_bytes()); slice[66..68].copy_from_slice(&1u16.to_le_bytes()); slice[68..72].copy_from_slice(&(1024u32 * 1024).to_le_bytes()); slice[72..80].copy_from_slice(&(1024u64 * 1024).to_le_bytes());
let checksum = crc32c(slice);
slice[4..8].copy_from_slice(&checksum.to_le_bytes());
}
fn write_region_table(buf: &mut [u8], offset: usize, entry_count: u32) {
let slice = &mut buf[offset..][..REGION_TABLE_SIZE as usize];
slice[..4].copy_from_slice(b"regi");
slice[4..8].copy_from_slice(&0u32.to_le_bytes()); slice[8..12].copy_from_slice(&entry_count.to_le_bytes());
slice[12..16].copy_from_slice(&0u32.to_le_bytes());
let entries_start = offset + RT_HEADER_SIZE as usize;
for i in 0..entry_count as usize {
let eoff = entries_start + i * REGION_ENTRY_SIZE as usize;
buf[eoff..eoff + 16].copy_from_slice(&[u8::try_from(i).expect("entry index fits u8"); 16]);
buf[eoff + 16..eoff + 24].copy_from_slice(
&(1024 * 1024 * (u64::try_from(i).expect("entry index fits u64") + 2)).to_le_bytes(),
);
buf[eoff + 24..eoff + 28].copy_from_slice(&(1024u32 * 1024).to_le_bytes());
buf[eoff + 28..eoff + 32]
.view_bits_mut::<Lsb0>()
.set(0, true);
}
let slice = &mut buf[offset..][..REGION_TABLE_SIZE as usize];
let checksum = crc32c(slice);
slice[4..8].copy_from_slice(&checksum.to_le_bytes());
}
#[test]
fn file_type_signature() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let ft = header.file_type();
assert_eq!(ft.signature(), b"vhdxfile");
}
#[test]
fn file_type_creator_is_512_bytes() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let creator = header.file_type().creator();
assert_eq!(creator.len(), 512);
}
#[test]
fn header1_valid() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let h = header.header(1).unwrap();
assert_eq!(h.signature(), b"head");
assert_eq!(h.sequence_number(), 5);
}
#[test]
fn header2_valid() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let h = header.header(2).unwrap();
assert_eq!(h.signature(), b"head");
assert_eq!(h.sequence_number(), 3);
}
#[test]
fn current_header_picks_higher_sequence() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let h = header.header(0).unwrap();
assert_eq!(h.sequence_number(), 5);
}
#[test]
fn current_header_picks_only_valid() {
let mut buf = build_test_header_section();
buf[HEADER1_OFFSET as usize] = 0xFF;
let header = Header::new(&buf).unwrap();
let h = header.header(0).unwrap();
assert_eq!(h.sequence_number(), 3); }
#[test]
fn both_headers_corrupt_fails() {
let mut buf = build_test_header_section();
buf[HEADER1_OFFSET as usize] = 0xFF;
buf[HEADER2_OFFSET as usize] = 0xFF;
let header = Header::new(&buf).unwrap();
let result = header.header(0);
assert!(result.is_err());
}
#[test]
fn header_index_out_of_range() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
assert!(header.header(3).is_err());
}
#[test]
fn header_crc_validated() {
let mut buf = build_test_header_section();
buf[HEADER1_OFFSET as usize + 4] ^= 0xFF;
let header = Header::new(&buf).unwrap();
assert!(header.header(1).is_err());
}
#[test]
fn header_fields() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let h = header.header(1).unwrap();
assert_eq!(h.log_version(), 0);
assert_eq!(h.version(), 1);
assert_eq!(h.log_length(), 1024 * 1024);
assert_eq!(h.log_offset(), 1024 * 1024);
}
#[test]
fn region_table1_valid() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let rt = header.region_table(1).unwrap();
assert_eq!(rt.header().signature(), b"regi");
assert_eq!(rt.header().entry_count(), 2);
}
#[test]
fn region_table2_valid() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let rt = header.region_table(2).unwrap();
assert_eq!(rt.header().signature(), b"regi");
assert_eq!(rt.header().entry_count(), 2);
}
#[test]
fn current_region_table_follows_current_header() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let rt = header.region_table(0).unwrap();
assert_eq!(rt.header().entry_count(), 2);
}
#[test]
fn region_table_index_out_of_range() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
assert!(header.region_table(3).is_err());
}
#[test]
fn region_table_crc_validated() {
let mut buf = build_test_header_section();
buf[REGION_TABLE1_OFFSET as usize + 4] ^= 0xFF;
let header = Header::new(&buf).unwrap();
assert!(header.region_table(1).is_err());
}
#[test]
fn region_table_entries_iterator() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let rt = header.region_table(1).unwrap();
let entries: Vec<_> = rt.entries().collect();
assert_eq!(entries.len(), 2);
assert!(entries[0].required());
assert!(entries[1].required());
}
#[test]
fn region_table_entry_fields() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let rt = header.region_table(1).unwrap();
let first = rt.entries().next().unwrap();
assert_eq!(first.file_offset(), 2 * 1024 * 1024);
assert_eq!(first.length(), 1024 * 1024);
}
#[test]
fn buffer_too_small_fails() {
let buf = vec![0u8; 100];
assert!(Header::new(&buf).is_err());
}
#[test]
fn region_table_header_reserved() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let rt = header.region_table(1).unwrap();
assert_eq!(rt.header().reserved(), 0);
}
#[test]
fn region_table_header_checksum() {
let buf = build_test_header_section();
let header = Header::new(&buf).unwrap();
let rt = header.region_table(1).unwrap();
let stored = rt.header().checksum();
let slice = &buf[REGION_TABLE1_OFFSET as usize..][..REGION_TABLE_SIZE as usize];
let mut tmp = slice.to_vec();
tmp[4..8].copy_from_slice(&0u32.to_le_bytes());
assert_eq!(stored, Crc32c::from_raw(crc32c(&tmp)));
}
#[test]
fn current_header_rejects_equal_sequence() {
let mut buf = build_test_header_section();
let h2_offset = HEADER2_OFFSET as usize;
buf[h2_offset + 8..h2_offset + 16].copy_from_slice(&5u64.to_le_bytes());
buf[h2_offset + 4..h2_offset + 8].copy_from_slice(&0u32.to_le_bytes());
let slice = &buf[h2_offset..h2_offset + HEADER_SIZE as usize];
let checksum = crc32c(slice);
buf[h2_offset + 4..h2_offset + 8].copy_from_slice(&checksum.to_le_bytes());
let header = Header::new(&buf).unwrap();
let result = header.header(0);
assert!(
matches!(result, Err(Error::HeaderSequenceNumberInvalid { .. })),
"equal sequence numbers should be rejected"
);
}