ms_pdb/lines/
checksum.rs

1//! Code for the `FILE_CHECKSUMS` subsection.
2
3use super::*;
4
5/// The hash algorithm used for the checksum.
6#[derive(
7    Copy,
8    Clone,
9    Eq,
10    PartialEq,
11    Ord,
12    PartialOrd,
13    IntoBytes,
14    FromBytes,
15    Unaligned,
16    KnownLayout,
17    Immutable,
18)]
19#[repr(transparent)]
20pub struct ChecksumKind(pub u8);
21
22impl ChecksumKind {
23    /// No checksum at all
24    pub const NONE: ChecksumKind = ChecksumKind(0);
25    /// MD-5 checksum. See `/ZH:MD5` for MSVC.
26    pub const MD5: ChecksumKind = ChecksumKind(1);
27    /// SHA-1 checksum. See `/ZH:SHA1` for MSVC
28    pub const SHA_1: ChecksumKind = ChecksumKind(2);
29    /// SHA-256 checksum.  See `/ZH:SHA_256` for MSVC.
30    pub const SHA_256: ChecksumKind = ChecksumKind(3);
31}
32
33impl std::fmt::Debug for ChecksumKind {
34    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
35        static NAMES: [&str; 4] = ["NONE", "MD5", "SHA_1", "SHA_256"];
36
37        if let Some(name) = NAMES.get(self.0 as usize) {
38            f.write_str(name)
39        } else {
40            write!(f, "??({})", self.0)
41        }
42    }
43}
44
45#[test]
46fn checksum_kind_debug() {
47    assert_eq!(format!("{:?}", ChecksumKind::SHA_256), "SHA_256");
48    assert_eq!(format!("{:?}", ChecksumKind(42)), "??(42)");
49}
50
51/// The File Checksums Subection
52///
53/// The file checksums subsection contains records for the source files referenced by Line Data.
54pub struct FileChecksumsSubsection<'a> {
55    #[allow(missing_docs)]
56    pub bytes: &'a [u8],
57}
58
59impl<'a> FileChecksumsSubsection<'a> {
60    #[allow(missing_docs)]
61    pub fn new(bytes: &'a [u8]) -> Self {
62        Self { bytes }
63    }
64
65    /// Iterates the `FileChecksum` records within this subsection.
66    pub fn iter(&self) -> FileChecksumIter<'a> {
67        FileChecksumIter { bytes: self.bytes }
68    }
69
70    /// Given a file index, which is a byte offset into the `FileChecksums` section, gets a
71    /// `FileChecksum` value.
72    pub fn get_file(&self, file_index: u32) -> anyhow::Result<FileChecksum<'a>> {
73        if let Some(b) = self.bytes.get(file_index as usize..) {
74            if let Some(c) = FileChecksumIter::new(b).next() {
75                Ok(c)
76            } else {
77                bail!("failed to decode FileChecksum record");
78            }
79        } else {
80            bail!("file index is out of range of file checksums subsection");
81        }
82    }
83}
84
85/// Like `FileChecksums`, but with mutable access
86pub struct FileChecksumsSubsectionMut<'a> {
87    #[allow(missing_docs)]
88    pub bytes: &'a mut [u8],
89}
90
91impl<'a> HasRestLen for FileChecksumsSubsectionMut<'a> {
92    fn rest_len(&self) -> usize {
93        self.bytes.len()
94    }
95}
96
97impl<'a> FileChecksumsSubsectionMut<'a> {
98    #[allow(missing_docs)]
99    pub fn new(bytes: &'a mut [u8]) -> Self {
100        Self { bytes }
101    }
102
103    /// Iterates the `FileChecksumMut` records within this subsection.
104    pub fn iter_mut(&mut self) -> FileChecksumMutIter<'_> {
105        FileChecksumMutIter { bytes: self.bytes }
106    }
107
108    /// Given a file index, which is a byte offset into the `FileChecksums` section, gets a
109    /// `FileChecksumMut` value.
110    pub fn get_file_mut(&mut self, file_index: u32) -> anyhow::Result<FileChecksumMut<'_>> {
111        if let Some(b) = self.bytes.get_mut(file_index as usize..) {
112            if let Some(c) = FileChecksumMutIter::new(b).next() {
113                Ok(c)
114            } else {
115                bail!("failed to decode FileChecksum record");
116            }
117        } else {
118            bail!("file index is out of range of file checksums subsection");
119        }
120    }
121}
122
123/// Points to a single file checksum record.
124pub struct FileChecksum<'a> {
125    /// The fixed-size header.
126    pub header: &'a FileChecksumHeader,
127    /// The checksum bytes.
128    pub checksum_data: &'a [u8],
129}
130
131/// Points to a single file checksum record, with mutable access.
132pub struct FileChecksumMut<'a> {
133    /// The fixed-size header.
134    pub header: &'a mut FileChecksumHeader,
135    /// The checksum bytes.
136    pub checksum_data: &'a mut [u8],
137}
138
139impl<'a> FileChecksum<'a> {
140    /// Gets the `NameIndex` of the file name for this record. To dereference the `NameIndex value,
141    /// use [`crate::names::NamesStream`].
142    pub fn name(&self) -> NameIndex {
143        NameIndex(self.header.name.get())
144    }
145}
146
147/// The header at the start of a file checksum record.
148///
149/// Each file checksum record specifies the name of the file (using an offset into the Names Stream),
150/// the kind of checksum (none, SHA1, SHA256, MD5, etc.), the size of the checksum, and the
151/// checksum bytes.
152///
153/// The checksum record is variable-length.
154#[derive(IntoBytes, FromBytes, KnownLayout, Immutable, Unaligned, Clone, Debug)]
155#[repr(C)]
156pub struct FileChecksumHeader {
157    /// Offset into the global string table (the `/names` stream) of the PDB.
158    pub name: U32<LE>,
159
160    /// The size in bytes of the checksum. The checksum bytes immediately follow the `FileChecksumHeader`.
161    pub checksum_size: u8,
162
163    /// The hash algorithm used for the checksum.
164    pub checksum_kind: ChecksumKind,
165}
166
167/// Iterates FileChecksum values from a byte stream.
168pub struct FileChecksumIter<'a> {
169    /// The unparsed data
170    pub bytes: &'a [u8],
171}
172
173impl<'a> HasRestLen for FileChecksumIter<'a> {
174    fn rest_len(&self) -> usize {
175        self.bytes.len()
176    }
177}
178
179/// Iterator state. Iterates `FileChecksumMut` values.
180pub struct FileChecksumMutIter<'a> {
181    /// The unparsed data
182    pub bytes: &'a mut [u8],
183}
184
185impl<'a> HasRestLen for FileChecksumMutIter<'a> {
186    fn rest_len(&self) -> usize {
187        self.bytes.len()
188    }
189}
190
191impl<'a> FileChecksumIter<'a> {
192    /// Starts a new iterator
193    pub fn new(bytes: &'a [u8]) -> Self {
194        Self { bytes }
195    }
196}
197
198impl<'a> Iterator for FileChecksumIter<'a> {
199    type Item = FileChecksum<'a>;
200
201    fn next(&mut self) -> Option<Self::Item> {
202        if self.bytes.is_empty() {
203            return None;
204        }
205
206        let mut p = Parser::new(self.bytes);
207        let len_before = p.len();
208        let header: &FileChecksumHeader = p.get().ok()?;
209        let checksum_data = p.bytes(header.checksum_size as usize).ok()?;
210
211        // Align to 4-byte boundaries.
212        let record_len = len_before - p.len();
213        let _ = p.skip((4 - (record_len & 3)) & 3);
214
215        self.bytes = p.into_rest();
216        Some(FileChecksum {
217            header,
218            checksum_data,
219        })
220    }
221}
222
223impl<'a> FileChecksumMutIter<'a> {
224    /// Starts a new iterator
225    pub fn new(bytes: &'a mut [u8]) -> Self {
226        Self { bytes }
227    }
228}
229
230impl<'a> Iterator for FileChecksumMutIter<'a> {
231    type Item = FileChecksumMut<'a>;
232
233    fn next(&mut self) -> Option<Self::Item> {
234        if self.bytes.is_empty() {
235            return None;
236        }
237
238        let mut p = ParserMut::new(take(&mut self.bytes));
239        let len_before = p.len();
240        let header: &mut FileChecksumHeader = p.get_mut().ok()?;
241        let checksum_data = p.bytes_mut(header.checksum_size as usize).ok()?;
242
243        // Align to 4-byte boundaries.
244        let record_len = len_before - p.len();
245        let _ = p.skip((4 - (record_len & 3)) & 3);
246
247        self.bytes = p.into_rest();
248        Some(FileChecksumMut {
249            header,
250            checksum_data,
251        })
252    }
253}
254
255/// Test iteration of records with byte ranges.
256#[test]
257fn iter_ranges() {
258    const PAD: u8 = 0xaa;
259
260    #[rustfmt::skip]
261    let data = &[
262        200, 0, 0, 0,               // name
263        0,                          // checksum_size
264        0,                          // no checksum
265        // <-- offset = 6
266        PAD, PAD,
267        // <-- offset = 8
268
269        42, 0, 0, 0,                // name
270        16,                         // checksum_size
271        1,                          // MD5
272        0xc0, 0xc1, 0xc2, 0xc3,     // checksum
273        0xc4, 0xc5, 0xc6, 0xc7,
274        0xc8, 0xc9, 0xca, 0xcb,
275        0xcc, 0xcd, 0xce, 0xcf,
276        // <-- offset = 30
277        PAD, PAD,
278
279        // <-- offset = 32
280    ];
281
282    let sums = FileChecksumsSubsection::new(data);
283    let mut iter = sums.iter().with_ranges();
284
285    let (sub0_range, _) = iter.next().unwrap();
286    assert_eq!(sub0_range, 0..8);
287
288    let (sub1_range, _) = iter.next().unwrap();
289    assert_eq!(sub1_range, 8..32);
290
291    assert!(iter.next().is_none());
292}
293
294/// Tests that FileChecksumMutIter allows us to modify checksum records.
295#[test]
296fn iter_mut() {
297    const PAD: u8 = 0xaa;
298
299    #[rustfmt::skip]
300    let data = &[
301        42, 0, 0, 0,                // name
302        16,                         // checksum_size
303        1,                          // MD5
304        0xc0, 0xc1, 0xc2, 0xc3,     // checksum
305        0xc4, 0xc5, 0xc6, 0xc7,
306        0xc8, 0xc9, 0xca, 0xcb,
307        0xcc, 0xcd, 0xce, 0xcf,
308        // <-- offset = 22
309        PAD, PAD,
310
311        // <-- offset = 24
312    ];
313
314    let mut data_mut = data.to_vec();
315    let mut sums = FileChecksumsSubsectionMut::new(&mut data_mut);
316    let mut iter = sums.iter_mut();
317    assert_eq!(iter.rest_len(), 24); // initial amount of data in iterator
318
319    let sum0 = iter.next().unwrap();
320    assert_eq!(iter.rest_len(), 0); // initial amount of data in iterator
321    assert_eq!(sum0.header.name.get(), 42);
322    assert_eq!(
323        sum0.checksum_data,
324        &[
325            0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd,
326            0xce, 0xcf
327        ]
328    );
329
330    sum0.header.name = U32::new(0xcafef00d);
331    sum0.checksum_data[4] = 0xff;
332
333    #[rustfmt::skip]
334    let expected_new_data = &[
335        0x0d, 0xf0, 0xfe, 0xca,     // name (modified)
336        16,                         // checksum_size
337        1,                          // MD5
338        0xc0, 0xc1, 0xc2, 0xc3,     // checksum
339        0xff, 0xc5, 0xc6, 0xc7,     // <-- modified
340        0xc8, 0xc9, 0xca, 0xcb,
341        0xcc, 0xcd, 0xce, 0xcf,
342        // <-- offset = 22
343        PAD, PAD,
344
345        // <-- offset = 24
346    ];
347
348    assert_eq!(data_mut.as_slice(), expected_new_data);
349}
350
351/// Tests FileChecksumIter and FileChecksumMutIter.
352#[test]
353fn basic_iter() {
354    const PAD: u8 = 0xaa;
355
356    #[rustfmt::skip]
357    let data = &[
358        42, 0, 0, 0,                // name
359        16,                         // checksum_size
360        1,                          // MD5
361        0xc0, 0xc1, 0xc2, 0xc3,     // checksum
362        0xc4, 0xc5, 0xc6, 0xc7,
363        0xc8, 0xc9, 0xca, 0xcb,
364        0xcc, 0xcd, 0xce, 0xcf,
365        // <-- offset = 22
366        PAD, PAD,
367
368        // <-- offset = 24
369        0, 1, 0, 0,                 // name
370        16,                         // checksum_size
371        1,                          // MD5
372        0xd0, 0xd1, 0xd2, 0xd3,     // checksum
373        0xd4, 0xd5, 0xd6, 0xd7,
374        0xd8, 0xd9, 0xda, 0xdb,
375        0xdc, 0xdd, 0xde, 0xdf,
376        // <-- offset = 46
377        PAD, PAD,
378        // <-- offset = 48
379    ];
380
381    // Test FileChecksumIter (immutable iterator)
382    {
383        let mut iter = FileChecksumIter::new(data);
384        assert_eq!(iter.rest_len(), 48); // initial amount of data in iterator
385        let sum0 = iter.next().unwrap();
386        assert_eq!(sum0.name(), NameIndex(42));
387        assert_eq!(
388            sum0.checksum_data,
389            &[
390                0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd,
391                0xce, 0xcf
392            ]
393        );
394
395        assert_eq!(iter.rest_len(), 24); // record 0 is 24 bytes (including padding)
396
397        let sum1 = iter.next().unwrap();
398        assert_eq!(sum1.name(), NameIndex(0x100));
399        assert_eq!(
400            sum1.checksum_data,
401            &[
402                0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd,
403                0xde, 0xdf,
404            ]
405        );
406
407        assert_eq!(iter.rest_len(), 0); // record 1 is 24 bytes (including padding), leaving nothing in buffer
408        assert!(iter.next().is_none());
409    }
410
411    // Test FileChecksumMutIter (mutable iterator)
412    // We duplicate this because we can't do generics over mutability.
413    {
414        let mut data_mut = data.to_vec();
415        let mut iter = FileChecksumMutIter::new(&mut data_mut);
416        assert_eq!(iter.rest_len(), 48); // initial amount of data in iterator
417        let sum0 = iter.next().unwrap();
418        assert_eq!(sum0.header.name.get(), 42);
419        assert_eq!(
420            sum0.checksum_data,
421            &[
422                0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd,
423                0xce, 0xcf
424            ]
425        );
426
427        assert_eq!(iter.rest_len(), 24); // record 0 is 24 bytes (including padding)
428
429        let sum1 = iter.next().unwrap();
430        assert_eq!(sum1.header.name.get(), 0x100);
431        assert_eq!(
432            sum1.checksum_data,
433            &[
434                0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd,
435                0xde, 0xdf,
436            ]
437        );
438
439        assert_eq!(iter.rest_len(), 0); // record 1 is 24 bytes (including padding), leaving nothing in buffer
440        assert!(iter.next().is_none());
441    }
442}
443
444#[test]
445fn test_get_file() {
446    const PAD: u8 = 0xaa;
447
448    #[rustfmt::skip]
449    let data = &[
450        42,   0,    0, 0, 0, 0, PAD, PAD,      // record 0 at 0
451        0xee, 0,    0, 0, 0, 0, PAD, PAD,      // record 1 at 8
452        0,    0xcc, 0, 0, 0, 0, PAD, PAD,      // record 2 at 0x10
453        // len = 0x18
454    ];
455
456    // Test immutable access
457    {
458        let sums = FileChecksumsSubsection::new(data);
459
460        let sum0 = sums.get_file(0).unwrap();
461        assert_eq!(sum0.name(), NameIndex(42));
462
463        let sum1 = sums.get_file(8).unwrap();
464        assert_eq!(sum1.name(), NameIndex(0xee));
465
466        let sum2 = sums.get_file(0x10).unwrap();
467        assert_eq!(sum2.name(), NameIndex(0xcc00));
468
469        // Test bad index (way outside of data)
470        assert!(sums.get_file(0x1000).is_err());
471
472        // Test bad index (invalid header)
473        assert!(sums.get_file(0x16).is_err());
474    }
475
476    // Test mutable access
477    {
478        let mut data_mut = data.to_vec();
479        let mut sums = FileChecksumsSubsectionMut::new(&mut data_mut);
480
481        let sum0 = sums.get_file_mut(0).unwrap();
482        assert_eq!(sum0.header.name.get(), 42);
483
484        let sum1 = sums.get_file_mut(8).unwrap();
485        assert_eq!(sum1.header.name.get(), 0xee);
486
487        let sum2 = sums.get_file_mut(0x10).unwrap();
488        assert_eq!(sum2.header.name.get(), 0xcc00);
489
490        // Modify one of the records
491        sum2.header.name = U32::new(0xcafe);
492
493        // Test bad index (way outside of data)
494        assert!(sums.get_file_mut(0x1000).is_err());
495
496        // Test bad index (invalid header)
497        assert!(sums.get_file_mut(0x16).is_err());
498
499        #[rustfmt::skip]
500        let expected_data = &[
501            42,   0,    0, 0, 0, 0, PAD, PAD,      // record 0 at 0
502            0xee, 0,    0, 0, 0, 0, PAD, PAD,      // record 1 at 8
503            0xfe, 0xca, 0, 0, 0, 0, PAD, PAD,      // record 2 at 0x10
504        ];
505
506        assert_eq!(data_mut.as_slice(), expected_data);
507    }
508}