Skip to main content

hdiff_update_core/
hash.rs

1use std::{
2    fs::File,
3    io::{BufReader, Read},
4    path::{Path, PathBuf},
5};
6
7use serde::{Deserialize, Serialize};
8use sha2::{Digest, Sha256};
9
10use crate::{error::io_path, Result};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct FileDigest {
15    pub path: PathBuf,
16    pub sha256: String,
17    pub size: u64,
18}
19
20pub fn sha256_file(path: impl AsRef<Path>) -> Result<FileDigest> {
21    let path = path.as_ref();
22    let file = File::open(path).map_err(|error| io_path(path, error))?;
23    let size = file.metadata().map_err(|error| io_path(path, error))?.len();
24    let mut reader = BufReader::with_capacity(1024 * 1024, file);
25    let mut hasher = Sha256::new();
26    let mut buf = vec![0_u8; 1024 * 1024];
27
28    loop {
29        let read = reader
30            .read(&mut buf)
31            .map_err(|error| io_path(path, error))?;
32        if read == 0 {
33            break;
34        }
35        hasher.update(&buf[..read]);
36    }
37
38    Ok(FileDigest {
39        path: path.to_path_buf(),
40        sha256: hex::encode(hasher.finalize()),
41        size,
42    })
43}
44
45pub fn sha256_bytes(bytes: &[u8]) -> String {
46    let mut hasher = Sha256::new();
47    hasher.update(bytes);
48    hex::encode(hasher.finalize())
49}
50
51pub fn verify_sha256(path: impl AsRef<Path>, expected: &str) -> Result<FileDigest> {
52    let digest = sha256_file(path.as_ref())?;
53    if !digest.sha256.eq_ignore_ascii_case(expected) {
54        return Err(crate::Error::HashMismatch {
55            path: path.as_ref().to_path_buf(),
56            expected: expected.to_string(),
57            actual: digest.sha256,
58        });
59    }
60    Ok(digest)
61}