gix_pack/
verify.rs

1use std::{path::Path, sync::atomic::AtomicBool};
2
3use gix_features::progress::Progress;
4
5///
6pub mod checksum {
7    /// Returned by various methods to verify the checksum of a memory mapped file that might also exist on disk.
8    #[derive(thiserror::Error, Debug)]
9    #[allow(missing_docs)]
10    pub enum Error {
11        #[error("Interrupted by user")]
12        Interrupted,
13        #[error("index checksum mismatch: expected {expected}, got {actual}")]
14        Mismatch {
15            expected: gix_hash::ObjectId,
16            actual: gix_hash::ObjectId,
17        },
18    }
19}
20
21/// Returns the `index` at which the following `index + 1` value is not an increment over the value at `index`.
22pub fn fan(data: &[u32]) -> Option<usize> {
23    data.windows(2)
24        .enumerate()
25        .find_map(|(win_index, v)| (v[0] > v[1]).then_some(win_index))
26}
27
28/// Calculate the hash of the given kind by trying to read the file from disk at `data_path` or falling back on the mapped content in `data`.
29/// `Ok(desired_hash)` or `Err(Some(actual_hash))` is returned if the hash matches or mismatches.
30/// If the `Err(None)` is returned, the operation was interrupted.
31pub fn checksum_on_disk_or_mmap(
32    data_path: &Path,
33    data: &[u8],
34    expected: gix_hash::ObjectId,
35    object_hash: gix_hash::Kind,
36    progress: &mut dyn Progress,
37    should_interrupt: &AtomicBool,
38) -> Result<gix_hash::ObjectId, checksum::Error> {
39    let data_len_without_trailer = data.len() - object_hash.len_in_bytes();
40    let actual = match gix_features::hash::bytes_of_file(
41        data_path,
42        data_len_without_trailer as u64,
43        object_hash,
44        progress,
45        should_interrupt,
46    ) {
47        Ok(id) => id,
48        Err(err) if err.kind() == std::io::ErrorKind::Interrupted => return Err(checksum::Error::Interrupted),
49        Err(_io_err) => {
50            let start = std::time::Instant::now();
51            let mut hasher = gix_features::hash::hasher(object_hash);
52            hasher.update(&data[..data_len_without_trailer]);
53            progress.inc_by(data_len_without_trailer);
54            progress.show_throughput(start);
55            gix_hash::ObjectId::from(hasher.digest())
56        }
57    };
58
59    if actual == expected {
60        Ok(actual)
61    } else {
62        Err(checksum::Error::Mismatch { actual, expected })
63    }
64}