forensicnomicon_core/
volume_serial.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize))]
15pub enum VolumeSerial {
16 Short(u32),
19 Long(u64),
22}
23
24impl VolumeSerial {
25 #[must_use]
28 pub fn display(self) -> String {
29 match self {
30 Self::Short(v) => format!("{:04X}-{:04X}", v >> 16, v & 0xFFFF),
31 Self::Long(v) => format!("{:08X}-{:08X}", (v >> 32) as u32, (v & 0xFFFF_FFFF) as u32),
32 }
33 }
34}
35
36#[must_use]
45pub fn volume_serial(volume: &[u8]) -> Option<VolumeSerial> {
46 if volume.get(3..11) == Some(b"NTFS ") {
47 return u64_le(volume, 0x48).map(VolumeSerial::Long);
48 }
49 if volume.get(3..11) == Some(b"EXFAT ") {
50 return u32_le(volume, 0x64).map(VolumeSerial::Short);
51 }
52 if volume.get(0x52..0x5A) == Some(b"FAT32 ") {
53 return u32_le(volume, 0x43).map(VolumeSerial::Short);
54 }
55 if volume.get(0x36..0x39) == Some(b"FAT") {
56 return u32_le(volume, 0x27).map(VolumeSerial::Short);
57 }
58 None
59}
60
61fn u32_le(b: &[u8], off: usize) -> Option<u32> {
63 let end = off.checked_add(4)?;
64 let bytes: [u8; 4] = b.get(off..end)?.try_into().ok()?;
65 Some(u32::from_le_bytes(bytes))
66}
67
68fn u64_le(b: &[u8], off: usize) -> Option<u64> {
70 let end = off.checked_add(8)?;
71 let bytes: [u8; 8] = b.get(off..end)?.try_into().ok()?;
72 Some(u64::from_le_bytes(bytes))
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn fat32_reads_bs_volid_at_0x43() {
81 let mut vbr = vec![0u8; 512];
82 vbr[0x52..0x5A].copy_from_slice(b"FAT32 ");
83 vbr[0x43..0x47].copy_from_slice(&0xB4D8_5399u32.to_le_bytes());
84 assert_eq!(volume_serial(&vbr), Some(VolumeSerial::Short(0xB4D8_5399)));
85 assert_eq!(VolumeSerial::Short(0xB4D8_5399).display(), "B4D8-5399");
86 }
87
88 #[test]
89 fn fat16_reads_bs_volid_at_0x27() {
90 let mut vbr = vec![0u8; 512];
91 vbr[0x36..0x39].copy_from_slice(b"FAT");
92 vbr[0x27..0x2B].copy_from_slice(&0x1234_5678u32.to_le_bytes());
93 assert_eq!(volume_serial(&vbr), Some(VolumeSerial::Short(0x1234_5678)));
94 }
95
96 #[test]
97 fn ntfs_reads_the_8_byte_serial_at_0x48() {
98 let mut vbr = vec![0u8; 512];
99 vbr[3..11].copy_from_slice(b"NTFS ");
100 vbr[0x48..0x50].copy_from_slice(&0x0011_2233_4455_6677u64.to_le_bytes());
101 assert_eq!(
102 volume_serial(&vbr),
103 Some(VolumeSerial::Long(0x0011_2233_4455_6677))
104 );
105 assert_eq!(
106 VolumeSerial::Long(0x0011_2233_4455_6677).display(),
107 "00112233-44556677"
108 );
109 }
110
111 #[test]
112 fn exfat_reads_the_serial_at_0x64() {
113 let mut vbr = vec![0u8; 512];
114 vbr[3..11].copy_from_slice(b"EXFAT ");
115 vbr[0x64..0x68].copy_from_slice(&0xDEAD_BEEFu32.to_le_bytes());
116 assert_eq!(volume_serial(&vbr), Some(VolumeSerial::Short(0xDEAD_BEEF)));
117 }
118
119 #[test]
120 fn an_unrecognized_or_short_volume_has_no_serial() {
121 assert_eq!(volume_serial(&[0xABu8; 512]), None);
122 assert_eq!(volume_serial(&[]), None);
123 let mut short = vec![0u8; 0x48];
125 short[3..11].copy_from_slice(b"NTFS ");
126 assert_eq!(volume_serial(&short), None);
127 }
128}