1use binrw::BinRead;
4
5#[derive(Debug, Clone, BinRead)]
9#[br(little, magic = b"regf")]
10pub struct BaseBlock {
11 pub primary_sequence: u32,
13 pub secondary_sequence: u32,
15 pub last_written: u64,
17 pub major_version: u32,
19 pub minor_version: u32,
21 pub file_type: u32,
23 pub format: u32,
25 pub root_cell_offset: u32,
27 pub hive_bins_data_size: u32,
29 pub clustering_factor: u32,
31 pub file_name: [u8; 64],
33 pub rm_id: [u8; 16],
35 pub log_id: [u8; 16],
37 pub flags: u32,
39 pub tm_id: [u8; 16],
41 pub guid_signature: u32,
43 pub last_reorganize_time: u64,
45 #[br(count = 332)]
47 pub reserved1: Vec<u8>,
48 pub checksum: u32,
50}
51
52impl BaseBlock {
53 pub const SIZE: usize = 4096;
55
56 pub fn validate_checksum(header_bytes: &[u8]) -> bool {
61 if header_bytes.len() < 512 {
62 return false;
63 }
64 let computed = Self::compute_checksum(header_bytes);
65 let stored = u32::from_le_bytes([
66 header_bytes[0x1FC],
67 header_bytes[0x1FD],
68 header_bytes[0x1FE],
69 header_bytes[0x1FF],
70 ]);
71 computed == stored
72 }
73
74 pub fn compute_checksum(header_bytes: &[u8]) -> u32 {
76 let mut checksum: u32 = 0;
77 for i in 0..127 {
78 let offset = i * 4;
79 let word = u32::from_le_bytes([
80 header_bytes[offset],
81 header_bytes[offset + 1],
82 header_bytes[offset + 2],
83 header_bytes[offset + 3],
84 ]);
85 checksum ^= word;
86 }
87 if checksum == 0 {
88 checksum = 1;
89 }
90 if checksum == 0xFFFF_FFFF {
91 checksum = 0xFFFF_FFFE;
92 }
93 checksum
94 }
95
96 pub fn is_clean(&self) -> bool {
98 self.primary_sequence == self.secondary_sequence
99 }
100
101 pub fn file_name_string(&self) -> String {
103 let u16s: Vec<u16> = self
104 .file_name
105 .chunks_exact(2)
106 .map(|c| u16::from_le_bytes([c[0], c[1]]))
107 .take_while(|&c| c != 0)
108 .collect();
109 String::from_utf16_lossy(&u16s)
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use std::io::Cursor;
117
118 fn build_test_header() -> Vec<u8> {
120 let mut buf = vec![0u8; 4096];
121 buf[0..4].copy_from_slice(b"regf");
123 buf[0x04..0x08].copy_from_slice(&1u32.to_le_bytes());
125 buf[0x08..0x0C].copy_from_slice(&1u32.to_le_bytes());
127 buf[0x14..0x18].copy_from_slice(&1u32.to_le_bytes());
129 buf[0x18..0x1C].copy_from_slice(&5u32.to_le_bytes());
131 buf[0x20..0x24].copy_from_slice(&1u32.to_le_bytes());
133 buf[0x24..0x28].copy_from_slice(&32u32.to_le_bytes());
135 buf[0x28..0x2C].copy_from_slice(&4096u32.to_le_bytes());
137 buf[0x2C..0x30].copy_from_slice(&1u32.to_le_bytes());
139 let checksum = BaseBlock::compute_checksum(&buf);
141 buf[0x1FC..0x200].copy_from_slice(&checksum.to_le_bytes());
142 buf
143 }
144
145 #[test]
146 fn parse_base_block_from_bytes() {
147 let buf = build_test_header();
148 let mut cursor = Cursor::new(&buf[..]);
149 let header = BaseBlock::read(&mut cursor).expect("should parse valid header");
150 assert_eq!(header.major_version, 1);
151 assert_eq!(header.minor_version, 5);
152 assert_eq!(header.root_cell_offset, 32);
153 assert_eq!(header.hive_bins_data_size, 4096);
154 assert!(header.is_clean());
155 }
156
157 #[test]
158 fn checksum_validates_on_clean_header() {
159 let buf = build_test_header();
160 assert!(BaseBlock::validate_checksum(&buf));
161 }
162
163 #[test]
164 fn checksum_fails_on_corrupt_header() {
165 let mut buf = build_test_header();
166 buf[0x14] = 0xFF; assert!(!BaseBlock::validate_checksum(&buf));
168 }
169
170 #[test]
171 fn checksum_special_case_zero_becomes_one() {
172 let mut buf = vec![0u8; 512];
177 buf[0..4].copy_from_slice(b"regf");
178 buf[4..8].copy_from_slice(b"regf");
179 let checksum = BaseBlock::compute_checksum(&buf);
180 assert_eq!(checksum, 1, "zero checksum should become 1");
181 }
182
183 #[test]
184 fn dirty_hive_detection() {
185 let mut buf = build_test_header();
186 buf[0x04..0x08].copy_from_slice(&2u32.to_le_bytes());
188 let checksum = BaseBlock::compute_checksum(&buf);
190 buf[0x1FC..0x200].copy_from_slice(&checksum.to_le_bytes());
191
192 let mut cursor = Cursor::new(&buf[..]);
193 let header = BaseBlock::read(&mut cursor).unwrap();
194 assert!(!header.is_clean());
195 }
196
197 #[test]
198 fn rejects_invalid_signature() {
199 let mut buf = build_test_header();
200 buf[0..4].copy_from_slice(b"nope");
201 let mut cursor = Cursor::new(&buf[..]);
202 assert!(BaseBlock::read(&mut cursor).is_err());
203 }
204}