1use crate::bytes::{le_u16, le_u64, read_guid};
11use crate::error::{BdeError, Result};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum BdeVariant {
16 WindowsVista,
19 Windows7OrLater,
21 BitLockerToGo,
23}
24
25#[derive(Debug, Clone)]
27pub struct VolumeHeader {
28 pub variant: BdeVariant,
30 pub bytes_per_sector: u16,
32 pub bitlocker_guid: [u8; 16],
35 pub fve_metadata_offsets: [u64; 3],
39}
40
41const SIG_FVE: &[u8; 8] = b"-FVE-FS-";
42const SIG_TO_GO: &[u8; 8] = b"MSWIN4.1";
43const BOOT_WIN7: [u8; 3] = [0xeb, 0x58, 0x90];
44
45impl VolumeHeader {
46 pub fn parse(sector: &[u8]) -> Result<VolumeHeader> {
52 let mut signature = [0u8; 8];
53 if let Some(s) = sector.get(3..11) {
54 signature.copy_from_slice(s);
55 }
56 let mut bytes_per_sector = le_u16(sector, 11);
57 if bytes_per_sector == 0 {
58 bytes_per_sector = 512;
59 }
60
61 if &signature == SIG_TO_GO {
62 return Ok(VolumeHeader {
63 variant: BdeVariant::BitLockerToGo,
64 bytes_per_sector,
65 bitlocker_guid: read_guid(sector, 424),
66 fve_metadata_offsets: [
67 le_u64(sector, 440),
68 le_u64(sector, 448),
69 le_u64(sector, 456),
70 ],
71 });
72 }
73
74 if &signature == SIG_FVE {
75 let boot = sector.get(0..3).unwrap_or(&[]);
76 if boot == BOOT_WIN7 {
77 return Ok(VolumeHeader {
78 variant: BdeVariant::Windows7OrLater,
79 bytes_per_sector,
80 bitlocker_guid: read_guid(sector, 160),
81 fve_metadata_offsets: [
82 le_u64(sector, 176),
83 le_u64(sector, 184),
84 le_u64(sector, 192),
85 ],
86 });
87 }
88 let sectors_per_cluster = u64::from(sector.get(13).copied().unwrap_or(1).max(1));
91 let cluster_size = u64::from(bytes_per_sector) * sectors_per_cluster;
92 let block1 = le_u64(sector, 56).saturating_mul(cluster_size);
93 return Ok(VolumeHeader {
94 variant: BdeVariant::WindowsVista,
95 bytes_per_sector,
96 bitlocker_guid: [0u8; 16],
97 fve_metadata_offsets: [block1, 0, 0],
98 });
99 }
100
101 Err(BdeError::NotBitLocker { signature })
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 fn base_sector() -> Vec<u8> {
110 let mut s = vec![0u8; 512];
111 s[11] = 0x00;
112 s[12] = 0x02; s
114 }
115
116 #[test]
117 fn parses_bitlocker_to_go() {
118 let mut s = base_sector();
119 s[0..3].copy_from_slice(&[0xeb, 0x58, 0x90]);
120 s[3..11].copy_from_slice(b"MSWIN4.1");
121 s[424..440].copy_from_slice(&[
122 0x3b, 0xd6, 0x67, 0x49, 0x29, 0x2e, 0xd8, 0x4a, 0x83, 0x99, 0xf6, 0xa3, 0x39, 0xe3,
123 0xd0, 0x01,
124 ]);
125 s[440..448].copy_from_slice(&0x0210_0000u64.to_le_bytes());
126 s[448..456].copy_from_slice(&0x02b5_5800u64.to_le_bytes());
127 s[456..464].copy_from_slice(&0x035a_b000u64.to_le_bytes());
128
129 let h = VolumeHeader::parse(&s).unwrap();
130 assert_eq!(h.variant, BdeVariant::BitLockerToGo);
131 assert_eq!(h.bytes_per_sector, 512);
132 assert_eq!(
133 h.fve_metadata_offsets,
134 [0x0210_0000, 0x02b5_5800, 0x035a_b000]
135 );
136 assert_eq!(
137 crate::format_guid(&h.bitlocker_guid),
138 "4967d63b-2e29-4ad8-8399-f6a339e3d001"
139 );
140 }
141
142 #[test]
143 fn parses_windows7() {
144 let mut s = base_sector();
145 s[0..3].copy_from_slice(&[0xeb, 0x58, 0x90]);
146 s[3..11].copy_from_slice(b"-FVE-FS-");
147 s[176..184].copy_from_slice(&0x1000u64.to_le_bytes());
148 s[184..192].copy_from_slice(&0x2000u64.to_le_bytes());
149 s[192..200].copy_from_slice(&0x3000u64.to_le_bytes());
150
151 let h = VolumeHeader::parse(&s).unwrap();
152 assert_eq!(h.variant, BdeVariant::Windows7OrLater);
153 assert_eq!(h.fve_metadata_offsets, [0x1000, 0x2000, 0x3000]);
154 }
155
156 #[test]
157 fn parses_vista_cluster_offset() {
158 let mut s = base_sector();
159 s[0..3].copy_from_slice(&[0xeb, 0x52, 0x90]);
160 s[3..11].copy_from_slice(b"-FVE-FS-");
161 s[13] = 8; s[56..64].copy_from_slice(&100u64.to_le_bytes()); let h = VolumeHeader::parse(&s).unwrap();
164 assert_eq!(h.variant, BdeVariant::WindowsVista);
165 assert_eq!(h.fve_metadata_offsets[0], 100 * 512 * 8);
167 }
168
169 #[test]
170 fn rejects_non_bitlocker_with_signature() {
171 let mut s = base_sector();
172 s[0..3].copy_from_slice(&[0xeb, 0x52, 0x90]);
173 s[3..11].copy_from_slice(b"NTFS ");
174 let err = VolumeHeader::parse(&s).unwrap_err();
175 assert!(matches!(err, BdeError::NotBitLocker { signature } if &signature == b"NTFS "));
176 }
177
178 #[test]
179 fn short_sector_does_not_panic() {
180 assert!(matches!(
181 VolumeHeader::parse(&[0u8; 4]),
182 Err(BdeError::NotBitLocker { .. })
183 ));
184 }
185}