use crate::bytes::{le_u16, le_u64, read_guid};
use crate::error::{BdeError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BdeVariant {
WindowsVista,
Windows7OrLater,
BitLockerToGo,
}
#[derive(Debug, Clone)]
pub struct VolumeHeader {
pub variant: BdeVariant,
pub bytes_per_sector: u16,
pub bitlocker_guid: [u8; 16],
pub fve_metadata_offsets: [u64; 3],
}
const SIG_FVE: &[u8; 8] = b"-FVE-FS-";
const SIG_TO_GO: &[u8; 8] = b"MSWIN4.1";
const BOOT_WIN7: [u8; 3] = [0xeb, 0x58, 0x90];
impl VolumeHeader {
pub fn parse(sector: &[u8]) -> Result<VolumeHeader> {
let mut signature = [0u8; 8];
if let Some(s) = sector.get(3..11) {
signature.copy_from_slice(s);
}
let mut bytes_per_sector = le_u16(sector, 11);
if bytes_per_sector == 0 {
bytes_per_sector = 512;
}
if &signature == SIG_TO_GO {
return Ok(VolumeHeader {
variant: BdeVariant::BitLockerToGo,
bytes_per_sector,
bitlocker_guid: read_guid(sector, 424),
fve_metadata_offsets: [
le_u64(sector, 440),
le_u64(sector, 448),
le_u64(sector, 456),
],
});
}
if &signature == SIG_FVE {
let boot = sector.get(0..3).unwrap_or(&[]);
if boot == BOOT_WIN7 {
return Ok(VolumeHeader {
variant: BdeVariant::Windows7OrLater,
bytes_per_sector,
bitlocker_guid: read_guid(sector, 160),
fve_metadata_offsets: [
le_u64(sector, 176),
le_u64(sector, 184),
le_u64(sector, 192),
],
});
}
let sectors_per_cluster = u64::from(sector.get(13).copied().unwrap_or(1).max(1));
let cluster_size = u64::from(bytes_per_sector) * sectors_per_cluster;
let block1 = le_u64(sector, 56).saturating_mul(cluster_size);
return Ok(VolumeHeader {
variant: BdeVariant::WindowsVista,
bytes_per_sector,
bitlocker_guid: [0u8; 16],
fve_metadata_offsets: [block1, 0, 0],
});
}
Err(BdeError::NotBitLocker { signature })
}
}
#[cfg(test)]
mod tests {
use super::*;
fn base_sector() -> Vec<u8> {
let mut s = vec![0u8; 512];
s[11] = 0x00;
s[12] = 0x02; s
}
#[test]
fn parses_bitlocker_to_go() {
let mut s = base_sector();
s[0..3].copy_from_slice(&[0xeb, 0x58, 0x90]);
s[3..11].copy_from_slice(b"MSWIN4.1");
s[424..440].copy_from_slice(&[
0x3b, 0xd6, 0x67, 0x49, 0x29, 0x2e, 0xd8, 0x4a, 0x83, 0x99, 0xf6, 0xa3, 0x39, 0xe3,
0xd0, 0x01,
]);
s[440..448].copy_from_slice(&0x0210_0000u64.to_le_bytes());
s[448..456].copy_from_slice(&0x02b5_5800u64.to_le_bytes());
s[456..464].copy_from_slice(&0x035a_b000u64.to_le_bytes());
let h = VolumeHeader::parse(&s).unwrap();
assert_eq!(h.variant, BdeVariant::BitLockerToGo);
assert_eq!(h.bytes_per_sector, 512);
assert_eq!(
h.fve_metadata_offsets,
[0x0210_0000, 0x02b5_5800, 0x035a_b000]
);
assert_eq!(
crate::format_guid(&h.bitlocker_guid),
"4967d63b-2e29-4ad8-8399-f6a339e3d001"
);
}
#[test]
fn parses_windows7() {
let mut s = base_sector();
s[0..3].copy_from_slice(&[0xeb, 0x58, 0x90]);
s[3..11].copy_from_slice(b"-FVE-FS-");
s[176..184].copy_from_slice(&0x1000u64.to_le_bytes());
s[184..192].copy_from_slice(&0x2000u64.to_le_bytes());
s[192..200].copy_from_slice(&0x3000u64.to_le_bytes());
let h = VolumeHeader::parse(&s).unwrap();
assert_eq!(h.variant, BdeVariant::Windows7OrLater);
assert_eq!(h.fve_metadata_offsets, [0x1000, 0x2000, 0x3000]);
}
#[test]
fn parses_vista_cluster_offset() {
let mut s = base_sector();
s[0..3].copy_from_slice(&[0xeb, 0x52, 0x90]);
s[3..11].copy_from_slice(b"-FVE-FS-");
s[13] = 8; s[56..64].copy_from_slice(&100u64.to_le_bytes()); let h = VolumeHeader::parse(&s).unwrap();
assert_eq!(h.variant, BdeVariant::WindowsVista);
assert_eq!(h.fve_metadata_offsets[0], 100 * 512 * 8);
}
#[test]
fn rejects_non_bitlocker_with_signature() {
let mut s = base_sector();
s[0..3].copy_from_slice(&[0xeb, 0x52, 0x90]);
s[3..11].copy_from_slice(b"NTFS ");
let err = VolumeHeader::parse(&s).unwrap_err();
assert!(matches!(err, BdeError::NotBitLocker { signature } if &signature == b"NTFS "));
}
#[test]
fn short_sector_does_not_panic() {
assert!(matches!(
VolumeHeader::parse(&[0u8; 4]),
Err(BdeError::NotBitLocker { .. })
));
}
}