Skip to main content

lighty_core/
hash.rs

1// Copyright (c) 2025 Hamadi
2// Licensed under the MIT License
3
4//! SHA1 file hashing utilities (sync and async).
5
6use std::path::Path;
7use sha1::{Sha1, Digest};
8use std::io::Read;
9use tokio::fs;
10use thiserror::Error;
11
12/// Errors raised by the hashing helpers.
13#[derive(Debug, Error)]
14pub enum HashError {
15    #[error("IO error: {0}")]
16    Io(#[from] std::io::Error),
17
18    #[error("SHA1 mismatch: expected {expected}, got {actual}")]
19    Mismatch { expected: String, actual: String },
20}
21
22pub type HashResult<T> = Result<T, HashError>;
23
24/// Verifies the SHA1 hash of a file (async, reads whole file into memory).
25pub async fn verify_file_sha1(path: &Path, expected_sha1: &str) -> HashResult<bool> {
26    let content = fs::read(path).await?;
27
28    let mut hasher = Sha1::new();
29    hasher.update(&content);
30    let calculated_sha1 = hex::encode(hasher.finalize());
31
32    Ok(calculated_sha1.eq_ignore_ascii_case(expected_sha1))
33}
34
35/// Verifies the SHA1 hash of a file (async, streaming for large files).
36pub async fn verify_file_sha1_streaming(path: &Path, expected_sha1: &str) -> HashResult<bool> {
37    use tokio::io::AsyncReadExt;
38
39    let mut file = fs::File::open(path).await?;
40    let mut hasher = Sha1::new();
41    let mut buffer = [0u8; 8192];
42
43    loop {
44        let n = file.read(&mut buffer).await?;
45        if n == 0 {
46            break;
47        }
48        hasher.update(&buffer[..n]);
49    }
50
51    let calculated_sha1 = hex::encode(hasher.finalize());
52    Ok(calculated_sha1.eq_ignore_ascii_case(expected_sha1))
53}
54
55/// Calculates the SHA1 hash of a file (sync, blocking I/O — for non-async contexts).
56pub fn calculate_file_sha1_sync(path: &Path) -> HashResult<String> {
57    let mut file = std::fs::File::open(path)?;
58    let mut hasher = Sha1::new();
59    let mut buffer = [0u8; 8192];
60
61    loop {
62        let n = file.read(&mut buffer)?;
63        if n == 0 {
64            break;
65        }
66        hasher.update(&buffer[..n]);
67    }
68
69    Ok(hex::encode(hasher.finalize()))
70}
71
72/// Verifies the SHA1 hash of a file (sync).
73pub fn verify_file_sha1_sync(path: &Path, expected_sha1: &str) -> HashResult<bool> {
74    let calculated_sha1 = calculate_file_sha1_sync(path)?;
75    Ok(calculated_sha1.eq_ignore_ascii_case(expected_sha1))
76}
77
78/// Calculates the SHA1 hash of arbitrary bytes as a lowercase hex string.
79pub fn calculate_sha1_bytes(data: &[u8]) -> String {
80    let mut hasher = Sha1::new();
81    hasher.update(data);
82    hex::encode(hasher.finalize())
83}
84
85/// Calculates the SHA1 hash of arbitrary bytes as raw 20-byte output.
86pub fn calculate_sha1_bytes_raw(data: &[u8]) -> [u8; 20] {
87    let mut hasher = Sha1::new();
88    hasher.update(data);
89    hasher.finalize().into()
90}