1#![allow(clippy::doc_markdown)] use crate::{Attribute, Claim, DeviceKey, HistorySource, Provenance, SourceKind, Value};
33use disk_forensic::{analyse_disk, DiskReport};
34use forensicnomicon::volume_serial::VolumeSerial;
35use mbr_partition_forensic::DetectedFs;
36use std::io::{Read, Seek, SeekFrom};
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum EncryptionKind {
41 BitLocker,
45 BitLockerToGo,
50 Luks,
53 UnrecognizedFilesystem,
59}
60
61impl EncryptionKind {
62 #[must_use]
64 pub const fn name(self) -> &'static str {
65 match self {
66 Self::BitLocker => "BitLocker",
67 Self::BitLockerToGo => "BitLocker To Go",
68 Self::Luks => "LUKS",
69 Self::UnrecognizedFilesystem => {
70 "unrecognized-filesystem (possible encrypted container)"
71 }
72 }
73 }
74
75 const fn rank(self) -> u8 {
79 match self {
80 Self::BitLocker | Self::BitLockerToGo => 3,
81 Self::Luks => 2,
82 Self::UnrecognizedFilesystem => 1,
83 }
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct DeviceImage {
90 pub disk_signature: u32,
92 pub fat_volume_serial: Option<u32>,
95 pub encryption: Option<EncryptionKind>,
97 pub mbr: [u8; 512],
99}
100
101#[must_use]
105pub fn parse_boot_sectors(image: &[u8]) -> Option<DeviceImage> {
106 analyse_device_image(&mut std::io::Cursor::new(image), image.len() as u64)
107}
108
109pub fn analyse_device_image<R: Read + Seek>(reader: &mut R, disk_size: u64) -> Option<DeviceImage> {
118 let report = analyse_disk(reader, disk_size).ok()?;
122 let mbr = match &report {
123 DiskReport::Mbr(m) | DiskReport::Gpt(m) => m,
124 DiskReport::Apm(_) => return None,
125 };
126 let disk_signature = mbr.disk_serial;
130 let mbr_bytes: [u8; 512] = read_region(reader, 0, 512)?.try_into().ok()?;
131 let mut fat_volume_serial = None;
132 let mut encryption: Option<EncryptionKind> = None;
133 let mut record = |serial: Option<VolumeSerial>, enc: Option<EncryptionKind>| {
134 if let Some(kind) = enc {
135 if encryption.is_none_or(|cur| kind.rank() > cur.rank()) {
136 encryption = Some(kind);
137 }
138 }
139 if fat_volume_serial.is_none() {
140 if let Some(VolumeSerial::Short(v)) = serial {
141 fat_volume_serial = Some(v);
142 }
143 }
144 };
145 match &mbr.gpt {
146 None => {
149 for p in &mbr.partitions {
150 record(p.volume_serial, encryption_of(p.encryption, p.detected_fs));
151 }
152 }
153 Some(g) => {
157 for e in g.partitions.iter().filter(|e| e.is_used()) {
158 record(e.volume_serial, encryption_of(e.encryption, None));
159 }
160 }
161 }
162 Some(DeviceImage {
163 disk_signature,
164 fat_volume_serial,
165 encryption,
166 mbr: mbr_bytes,
167 })
168}
169
170fn read_region<R: Read + Seek>(reader: &mut R, offset: u64, len: usize) -> Option<Vec<u8>> {
173 reader.seek(SeekFrom::Start(offset)).ok()?;
174 let mut buf = vec![0u8; len];
175 let mut filled = 0;
176 while filled < len {
177 match reader.read(&mut buf[filled..]) {
178 Ok(0) => break,
179 Ok(n) => filled += n,
180 Err(_) => return None,
181 }
182 }
183 (filled != 0).then_some(buf)
184}
185
186fn encryption_of(
191 enc: Option<forensicnomicon::volume_encryption::VolumeEncryption>,
192 detected_fs: Option<DetectedFs>,
193) -> Option<EncryptionKind> {
194 use forensicnomicon::volume_encryption::VolumeEncryption;
195 if let Some(e) = enc {
196 return Some(match e {
197 VolumeEncryption::BitLocker => EncryptionKind::BitLocker,
198 VolumeEncryption::BitLockerToGo => EncryptionKind::BitLockerToGo,
199 });
200 }
201 match detected_fs {
202 Some(DetectedFs::Luks) => Some(EncryptionKind::Luks),
203 Some(DetectedFs::Unknown) => Some(EncryptionKind::UnrecognizedFilesystem),
204 _ => None,
205 }
206}
207
208pub struct DeviceImageSource<'a> {
210 image: &'a DeviceImage,
211 locator: String,
212}
213
214impl<'a> DeviceImageSource<'a> {
215 #[must_use]
217 pub fn new(image: &'a DeviceImage, locator: impl Into<String>) -> Self {
218 Self {
219 image,
220 locator: locator.into(),
221 }
222 }
223}
224
225fn fmt_serial(serial: u32) -> String {
227 format!("{:04X}-{:04X}", serial >> 16, serial & 0xFFFF)
228}
229
230#[must_use]
234pub fn export_mbr_hex(image: &DeviceImage, locator: &str) -> String {
235 use std::fmt::Write as _;
236 let mut out = String::new();
237 let _ = writeln!(
238 out,
239 "MBR of {locator} (disk signature {})",
240 fmt_serial(image.disk_signature)
241 );
242 for (i, chunk) in image.mbr.chunks(16).enumerate() {
243 let hex = chunk.iter().fold(String::new(), |mut acc, b| {
244 let _ = write!(acc, "{b:02X} ");
245 acc
246 });
247 let ascii: String = chunk
248 .iter()
249 .map(|&b| {
250 if (0x20..0x7F).contains(&b) {
251 b as char
252 } else {
253 '.'
254 }
255 })
256 .collect();
257 let _ = writeln!(out, "{:08X} {hex:<48} |{ascii}|", i * 16);
258 }
259 out
260}
261
262impl HistorySource for DeviceImageSource<'_> {
263 fn claims(&self) -> Vec<Claim> {
264 let device = DeviceKey(format!("disk-{:08X}", self.image.disk_signature));
266 let make = |attribute, value| Claim {
267 device: device.clone(),
268 attribute,
269 value: Value::Text(value),
270 provenance: Provenance {
271 source: SourceKind::DeviceImage,
272 locator: self.locator.clone(),
273 },
274 };
275 let mut out = Vec::new();
276 if let Some(vsn) = self.image.fat_volume_serial {
279 out.push(make(Attribute::VolumeSerial, fmt_serial(vsn)));
280 }
281 if let Some(enc) = self.image.encryption {
283 out.push(make(Attribute::Encryption, enc.name().to_string()));
284 }
285 out
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 fn fat32_vbr(bs_volid: u32) -> [u8; 512] {
296 let mut vbr = [0u8; 512];
297 vbr[3..11].copy_from_slice(b"MSDOS5.0");
298 vbr[0x52..0x5A].copy_from_slice(b"FAT32 ");
299 vbr[0x43..0x47].copy_from_slice(&bs_volid.to_le_bytes());
300 vbr[510..512].copy_from_slice(&[0x55, 0xAA]);
301 vbr
302 }
303
304 fn fat16_vbr(bs_volid: u32) -> [u8; 512] {
306 let mut vbr = [0u8; 512];
307 vbr[3..11].copy_from_slice(b"MSDOS5.0");
308 vbr[0x36..0x3E].copy_from_slice(b"FAT16 "); vbr[0x27..0x2B].copy_from_slice(&bs_volid.to_le_bytes());
310 vbr[510..512].copy_from_slice(&[0x55, 0xAA]);
311 vbr
312 }
313
314 fn ntfs_vbr() -> [u8; 512] {
316 let mut vbr = [0u8; 512];
317 vbr[3..11].copy_from_slice(b"NTFS ");
318 vbr[510..512].copy_from_slice(&[0x55, 0xAA]);
319 vbr
320 }
321
322 #[test]
323 fn parses_mbr_disk_signature_and_fat_volume_serial() {
324 let img = mbr_disk(0xE221_034C, 0x0B, 2, &fat32_vbr(0xB4D8_5399));
325 let d = parse_boot_sectors(&img).expect("valid MBR");
326 assert_eq!(d.disk_signature, 0xE221_034C);
327 assert_eq!(d.fat_volume_serial, Some(0xB4D8_5399));
328 assert_eq!(d.encryption, None);
329 }
330
331 #[test]
332 fn a_fat16_partition_reads_bs_volid_at_0x27() {
333 let img = mbr_disk(1, 0x06, 2, &fat16_vbr(0x1234_5678));
334 let d = parse_boot_sectors(&img).expect("valid MBR");
335 assert_eq!(d.fat_volume_serial, Some(0x1234_5678));
336 }
337
338 #[test]
339 fn a_non_mbr_image_is_rejected() {
340 assert_eq!(parse_boot_sectors(&[0u8; 512]), None);
341 assert_eq!(parse_boot_sectors(&[0u8; 10]), None);
342 }
343
344 #[test]
345 fn read_region_returns_none_past_eof_and_zero_pads_a_short_read() {
346 use std::io::Cursor;
347 assert_eq!(
350 read_region(&mut Cursor::new(vec![0u8; 16]), 4096, 512),
351 None
352 );
353 let mut backing = vec![0xAAu8; 512 + 100];
355 backing[512..].fill(0xBB);
356 let s = read_region(&mut Cursor::new(backing), 512, 512)
357 .expect("short read still yields a buffer");
358 assert_eq!(s.len(), 512);
359 assert_eq!(&s[..100], &[0xBBu8; 100]);
360 assert_eq!(&s[100..], &[0u8; 412]); }
362
363 #[test]
364 fn read_region_propagates_a_read_error() {
365 struct FailingReader;
367 impl std::io::Read for FailingReader {
368 fn read(&mut self, _: &mut [u8]) -> std::io::Result<usize> {
369 Err(std::io::Error::other("boom"))
370 }
371 }
372 impl std::io::Seek for FailingReader {
373 fn seek(&mut self, _: std::io::SeekFrom) -> std::io::Result<u64> {
374 Ok(0)
375 }
376 }
377 assert_eq!(read_region(&mut FailingReader, 0, 512), None);
378 }
379
380 #[test]
381 fn the_most_specific_encryption_state_wins_across_partitions() {
382 let mut img = mbr_disk(0x0AAA_0BBB, 0x07, 2, &[0xABu8; 512]); let mut luks = [0u8; 512];
387 luks[0..6].copy_from_slice(b"LUKS\xba\xbe");
388 for (i, lba, ptype, vbr) in [(1u8, 4u32, 0x83u8, luks), (2, 6, 0x07, bitlocker_vbr())] {
389 let e = 0x1BE + i as usize * 16;
390 img[e + 4] = ptype;
391 img[e + 8..e + 12].copy_from_slice(&lba.to_le_bytes());
392 img[e + 12..e + 16].copy_from_slice(&8u32.to_le_bytes());
393 let off = lba as usize * 512;
394 img[off..off + 512].copy_from_slice(&vbr);
395 }
396 let d = parse_boot_sectors(&img).expect("valid MBR");
397 assert_eq!(d.encryption, Some(EncryptionKind::BitLocker));
398 }
399
400 #[test]
401 fn the_first_fat_partitions_serial_is_kept() {
402 let mut img = mbr_disk(0xF00D_0001, 0x0B, 2, &fat32_vbr(0x1111_2222));
405 let e = 0x1BE + 16;
406 img[e + 4] = 0x0B;
407 img[e + 8..e + 12].copy_from_slice(&8u32.to_le_bytes());
408 img[e + 12..e + 16].copy_from_slice(&8u32.to_le_bytes());
409 let off = 8 * 512;
410 img[off..off + 512].copy_from_slice(&fat32_vbr(0x9999_8888));
411 let d = parse_boot_sectors(&img).expect("valid MBR");
412 assert_eq!(d.fat_volume_serial, Some(0x1111_2222));
413 }
414
415 #[test]
416 fn a_partition_declared_beyond_the_image_is_skipped() {
417 let mut img = mbr_disk(0xCAFE_0001, 0x0B, 2, &fat32_vbr(0xAABB_CCDD));
420 let e = 0x1BE + 16;
421 img[e + 4] = 0x07;
422 img[e + 8..e + 12].copy_from_slice(&9000u32.to_le_bytes()); img[e + 12..e + 16].copy_from_slice(&8u32.to_le_bytes());
424 let d = parse_boot_sectors(&img).expect("valid MBR");
425 assert_eq!(d.fat_volume_serial, Some(0xAABB_CCDD));
426 }
427
428 #[test]
429 fn an_ntfs_mbr_yields_the_disk_signature_with_no_fat_serial() {
430 let img = mbr_disk(0xDEAD_BEEF, 0x07, 2, &ntfs_vbr());
431 let d = parse_boot_sectors(&img).expect("valid MBR");
432 assert_eq!(d.disk_signature, 0xDEAD_BEEF);
433 assert_eq!(d.fat_volume_serial, None);
434 assert_eq!(d.encryption, None);
435 }
436
437 #[test]
438 fn source_emits_the_fat_volume_serial_keyed_by_disk_signature() {
439 let img = DeviceImage {
440 disk_signature: 0xE221_034C,
441 fat_volume_serial: Some(0xB4D8_5399),
442 encryption: None,
443 mbr: [0u8; 512],
444 };
445 let claims = DeviceImageSource::new(&img, "rm2.raw").claims();
446 assert_eq!(claims.len(), 1);
447 assert_eq!(claims[0].device, DeviceKey("disk-E221034C".to_string()));
450 assert_eq!(claims[0].attribute, Attribute::VolumeSerial);
451 assert_eq!(claims[0].value, Value::Text("B4D8-5399".to_string()));
452 assert_eq!(claims[0].provenance.source, SourceKind::DeviceImage);
453 assert_eq!(claims[0].provenance.locator, "rm2.raw");
454 }
455
456 #[test]
457 fn export_mbr_hex_dumps_the_boot_sector_with_signature_and_ascii() {
458 let img = mbr_disk(0xE221_034C, 0x0B, 2, &fat32_vbr(0xB4D8_5399));
459 let d = parse_boot_sectors(&img).expect("valid MBR");
460 let dump = export_mbr_hex(&d, "rm2.raw");
461 assert!(dump.contains("MBR of rm2.raw"));
462 assert!(dump.contains("E221-034C"), "disk signature in header");
463 assert!(dump.contains("00000000 "), "offset column");
464 assert!(
465 dump.contains("55 AA"),
466 "the boot signature bytes are present"
467 );
468 assert_eq!(dump.lines().count(), 33);
470 }
471
472 #[test]
473 fn source_without_a_fat_serial_or_encryption_emits_nothing() {
474 let img = DeviceImage {
476 disk_signature: 1,
477 fat_volume_serial: None,
478 encryption: None,
479 mbr: [0u8; 512],
480 };
481 assert!(DeviceImageSource::new(&img, "x").claims().is_empty());
482 }
483
484 fn bitlocker_vbr() -> [u8; 512] {
486 let mut vbr = [0u8; 512];
487 vbr[0..3].copy_from_slice(&[0xEB, 0x58, 0x90]);
488 vbr[3..11].copy_from_slice(b"-FVE-FS-");
489 vbr[510..512].copy_from_slice(&[0x55, 0xAA]);
490 vbr
491 }
492
493 #[test]
494 fn bitlocker_signature_is_detected_from_the_vbr() {
495 let img = mbr_disk(0xABCD_1234, 0x07, 2, &bitlocker_vbr());
496 let d = parse_boot_sectors(&img).expect("valid MBR");
497 assert_eq!(d.encryption, Some(EncryptionKind::BitLocker));
498 assert_eq!(EncryptionKind::BitLocker.name(), "BitLocker");
499 assert_eq!(d.fat_volume_serial, None);
501 }
502
503 #[test]
504 fn plain_filesystem_media_is_not_flagged_as_encrypted() {
505 use forensicnomicon::volume_encryption::VolumeEncryption;
506 let img = mbr_disk(1, 0x0B, 2, &fat32_vbr(42));
508 assert_eq!(
509 parse_boot_sectors(&img).expect("valid MBR").encryption,
510 None
511 );
512 assert_eq!(encryption_of(None, Some(DetectedFs::Ntfs)), None);
514 assert_eq!(encryption_of(None, None), None);
515 assert_eq!(
516 encryption_of(Some(VolumeEncryption::BitLocker), None),
517 Some(EncryptionKind::BitLocker)
518 );
519 assert_eq!(
520 encryption_of(Some(VolumeEncryption::BitLockerToGo), None),
521 Some(EncryptionKind::BitLockerToGo)
522 );
523 assert_eq!(
524 encryption_of(None, Some(DetectedFs::Luks)),
525 Some(EncryptionKind::Luks)
526 );
527 assert_eq!(
528 encryption_of(None, Some(DetectedFs::Unknown)),
529 Some(EncryptionKind::UnrecognizedFilesystem)
530 );
531 }
532
533 #[test]
534 fn an_unrecognized_filesystem_partition_is_flagged_as_possibly_encrypted() {
535 let img = mbr_disk(7, 0x07, 2, &[0xABu8; 512]);
538 let d = parse_boot_sectors(&img).expect("valid MBR");
539 assert_eq!(d.encryption, Some(EncryptionKind::UnrecognizedFilesystem));
540 assert_eq!(
541 EncryptionKind::UnrecognizedFilesystem.name(),
542 "unrecognized-filesystem (possible encrypted container)"
543 );
544 }
545
546 #[test]
547 fn source_emits_an_encryption_claim_for_an_encrypted_device() {
548 let img = DeviceImage {
549 disk_signature: 0xABCD_1234,
550 fat_volume_serial: None,
551 encryption: Some(EncryptionKind::BitLocker),
552 mbr: [0u8; 512],
553 };
554 let claims = DeviceImageSource::new(&img, "x").claims();
555 assert_eq!(claims.len(), 1);
556 assert_eq!(claims[0].attribute, Attribute::Encryption);
557 assert_eq!(claims[0].value, Value::Text("BitLocker".to_string()));
558 }
559
560 fn mbr_disk(disk_sig: u32, ptype: u8, start_lba: u32, vbr: &[u8]) -> Vec<u8> {
565 let sectors = start_lba as usize + 16;
566 let mut v = vec![0u8; sectors * 512];
567 v[0x1B8..0x1BC].copy_from_slice(&disk_sig.to_le_bytes());
568 v[0x1FE..0x200].copy_from_slice(&[0x55, 0xAA]);
569 v[0x1BE + 4] = ptype;
570 v[0x1BE + 8..0x1BE + 12].copy_from_slice(&start_lba.to_le_bytes());
571 v[0x1BE + 12..0x1BE + 16].copy_from_slice(&8u32.to_le_bytes()); let off = start_lba as usize * 512;
573 v[off..off + vbr.len()].copy_from_slice(vbr);
574 v
575 }
576
577 fn to_go_vbr() -> [u8; 512] {
581 let mut vbr = [0u8; 512];
582 vbr[0..3].copy_from_slice(&[0xEB, 0x58, 0x90]);
583 vbr[3..11].copy_from_slice(b"MSWIN4.1");
584 vbr[0x52..0x5A].copy_from_slice(b"FAT32 ");
585 vbr[424..440]
587 .copy_from_slice(&forensicnomicon::volume_encryption::BITLOCKER_IDENTIFIER_GUID);
588 vbr[510..512].copy_from_slice(&[0x55, 0xAA]);
589 vbr
590 }
591
592 #[test]
593 fn bitlocker_to_go_detected_via_identifier_guid_on_a_fat_discovery_volume() {
594 let img = mbr_disk(0x1111_2222, 0x0B, 2, &to_go_vbr());
597 let d = parse_boot_sectors(&img).expect("valid MBR");
598 assert_eq!(d.encryption, Some(EncryptionKind::BitLockerToGo));
599 assert_eq!(EncryptionKind::BitLockerToGo.name(), "BitLocker To Go");
600 }
601
602 #[test]
603 fn a_luks_partition_is_flagged_as_luks_encryption() {
604 let mut vbr = [0u8; 512];
605 vbr[0..6].copy_from_slice(b"LUKS\xba\xbe"); let img = mbr_disk(0x3333_4444, 0x83, 2, &vbr);
607 let d = parse_boot_sectors(&img).expect("valid MBR");
608 assert_eq!(d.encryption, Some(EncryptionKind::Luks));
609 assert_eq!(EncryptionKind::Luks.name(), "LUKS");
610 }
611
612 #[test]
613 fn an_apple_partition_map_yields_no_device_image() {
614 let bs = 512usize;
617 let mut d = vec![0u8; bs * 2];
618 d[0..2].copy_from_slice(b"ER"); d[2..4].copy_from_slice(&512u16.to_be_bytes()); d[4..8].copy_from_slice(&4u32.to_be_bytes()); d[bs..bs + 2].copy_from_slice(b"PM"); d[bs + 4..bs + 8].copy_from_slice(&1u32.to_be_bytes()); d[bs + 8..bs + 12].copy_from_slice(&1u32.to_be_bytes()); d[bs + 12..bs + 16].copy_from_slice(&1u32.to_be_bytes()); assert_eq!(parse_boot_sectors(&d), None);
626 }
627
628 fn guid_bytes(s: &str) -> [u8; 16] {
631 let g: Vec<&str> = s.split('-').collect();
632 let mut b = [0u8; 16];
633 b[0..4].copy_from_slice(&u32::from_str_radix(g[0], 16).unwrap().to_le_bytes());
634 b[4..6].copy_from_slice(&u16::from_str_radix(g[1], 16).unwrap().to_le_bytes());
635 b[6..8].copy_from_slice(&u16::from_str_radix(g[2], 16).unwrap().to_le_bytes());
636 b[8..10].copy_from_slice(&u16::from_str_radix(g[3], 16).unwrap().to_be_bytes());
637 b[10..16].copy_from_slice(&u64::from_str_radix(g[4], 16).unwrap().to_be_bytes()[2..8]);
638 b
639 }
640
641 fn gpt_entry(type_guid: &str, first: u64, last: u64) -> [u8; 128] {
642 let mut e = [0u8; 128];
643 e[0..16].copy_from_slice(&guid_bytes(type_guid));
644 e[16..32].copy_from_slice(&guid_bytes("00000000-0000-0000-0000-000000000001"));
645 e[32..40].copy_from_slice(&first.to_le_bytes());
646 e[40..48].copy_from_slice(&last.to_le_bytes());
647 e
648 }
649
650 fn gpt_header(my_lba: u64, alt_lba: u64, entry_lba: u64, array_crc: u32) -> [u8; 512] {
651 let mut s = [0u8; 512];
652 s[0..8].copy_from_slice(b"EFI PART");
653 s[8..12].copy_from_slice(&0x0001_0000u32.to_le_bytes());
654 s[12..16].copy_from_slice(&92u32.to_le_bytes());
655 s[24..32].copy_from_slice(&my_lba.to_le_bytes());
656 s[32..40].copy_from_slice(&alt_lba.to_le_bytes());
657 s[40..48].copy_from_slice(&3u64.to_le_bytes()); s[48..56].copy_from_slice(&61u64.to_le_bytes()); s[56..72].copy_from_slice(&guid_bytes("12345678-1234-5678-1234-567812345678"));
660 s[72..80].copy_from_slice(&entry_lba.to_le_bytes());
661 s[80..84].copy_from_slice(&4u32.to_le_bytes()); s[84..88].copy_from_slice(&128u32.to_le_bytes()); s[88..92].copy_from_slice(&array_crc.to_le_bytes());
664 let crc = gpt_partition_forensic::crc32::checksum(&s[..92]);
665 s[16..20].copy_from_slice(&crc.to_le_bytes());
666 s
667 }
668
669 fn build_gpt(bs_volid: u32) -> Vec<u8> {
672 const SECTOR: usize = 512;
673 const SECTORS: usize = 64;
674 let mut disk = vec![0u8; SECTOR * SECTORS];
675 disk[450] = 0xEE; disk[454..458].copy_from_slice(&1u32.to_le_bytes());
677 disk[458..462].copy_from_slice(&((SECTORS - 1) as u32).to_le_bytes());
678 disk[510..512].copy_from_slice(&[0x55, 0xAA]);
679
680 let mut array = vec![0u8; 4 * 128];
681 array[0..128].copy_from_slice(&gpt_entry(
682 "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7", 3,
684 30,
685 ));
686 let array_crc = gpt_partition_forensic::crc32::checksum(&array);
687 disk[SECTOR..SECTOR + 512].copy_from_slice(&gpt_header(1, 63, 2, array_crc));
688 disk[2 * SECTOR..2 * SECTOR + array.len()].copy_from_slice(&array);
689 disk[62 * SECTOR..62 * SECTOR + array.len()].copy_from_slice(&array);
690 disk[63 * SECTOR..63 * SECTOR + 512].copy_from_slice(&gpt_header(63, 1, 62, array_crc));
691 let vbr = 3 * SECTOR;
693 disk[vbr + 3..vbr + 11].copy_from_slice(b"MSDOS5.0");
694 disk[vbr + 0x52..vbr + 0x5A].copy_from_slice(b"FAT32 ");
695 disk[vbr + 0x43..vbr + 0x47].copy_from_slice(&bs_volid.to_le_bytes());
696 disk[vbr + 510..vbr + 512].copy_from_slice(&[0x55, 0xAA]);
697 disk
698 }
699
700 #[test]
701 fn a_gpt_disk_is_not_false_flagged_and_its_fat_partition_is_read() {
702 let img = build_gpt(0xB4D8_5399);
706 let d = parse_boot_sectors(&img).expect("valid GPT");
707 assert_eq!(
708 d.encryption, None,
709 "GPT header must not be flagged as encrypted"
710 );
711 assert_eq!(d.fat_volume_serial, Some(0xB4D8_5399));
712 }
713
714 #[test]
715 fn a_gpt_partition_beyond_the_image_is_skipped() {
716 const SECTOR: usize = 512;
719 const SECTORS: usize = 64;
720 let mut disk = vec![0u8; SECTOR * SECTORS];
721 disk[450] = 0xEE;
722 disk[454..458].copy_from_slice(&1u32.to_le_bytes());
723 disk[458..462].copy_from_slice(&((SECTORS - 1) as u32).to_le_bytes());
724 disk[510..512].copy_from_slice(&[0x55, 0xAA]);
725 let mut array = vec![0u8; 4 * 128];
726 array[0..128].copy_from_slice(&gpt_entry("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7", 3, 30));
727 array[128..256].copy_from_slice(&gpt_entry(
729 "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7",
730 9000,
731 9030,
732 ));
733 let array_crc = gpt_partition_forensic::crc32::checksum(&array);
734 disk[SECTOR..SECTOR + 512].copy_from_slice(&gpt_header(1, 63, 2, array_crc));
735 disk[2 * SECTOR..2 * SECTOR + array.len()].copy_from_slice(&array);
736 disk[62 * SECTOR..62 * SECTOR + array.len()].copy_from_slice(&array);
737 disk[63 * SECTOR..63 * SECTOR + 512].copy_from_slice(&gpt_header(63, 1, 62, array_crc));
738 let vbr = 3 * SECTOR;
739 disk[vbr + 3..vbr + 11].copy_from_slice(b"MSDOS5.0");
740 disk[vbr + 0x52..vbr + 0x5A].copy_from_slice(b"FAT32 ");
741 disk[vbr + 0x43..vbr + 0x47].copy_from_slice(&0xAABB_CCDDu32.to_le_bytes());
742 disk[vbr + 510..vbr + 512].copy_from_slice(&[0x55, 0xAA]);
743 let d = parse_boot_sectors(&disk).expect("valid GPT");
744 assert_eq!(d.fat_volume_serial, Some(0xAABB_CCDD));
745 }
746}