lighty_core/
hash.rs

1// Copyright (c) 2025 Hamadi
2// Licensed under the MIT License
3
4//! File hashing utilities
5//!
6//! Provides SHA1 hash verification for files with both sync and async implementations
7
8use std::path::Path;
9use sha1::{Sha1, Digest};
10use std::io::Read;
11use tokio::fs;
12use thiserror::Error;
13
14#[derive(Debug, Error)]
15pub enum HashError {
16    #[error("IO error: {0}")]
17    Io(#[from] std::io::Error),
18
19    #[error("SHA1 mismatch: expected {expected}, got {actual}")]
20    Mismatch { expected: String, actual: String },
21}
22
23pub type HashResult<T> = Result<T, HashError>;
24
25/// Verifies the SHA1 hash of a file (async version)
26///
27/// Reads the entire file into memory and computes the SHA1 hash.
28/// Suitable for small to medium files.
29///
30/// # Arguments
31/// * `path` - Path to the file to verify
32/// * `expected_sha1` - Expected SHA1 hash (case-insensitive)
33///
34/// # Returns
35/// `true` if the hash matches, `false` otherwise
36pub async fn verify_file_sha1(path: &Path, expected_sha1: &str) -> HashResult<bool> {
37    let content = fs::read(path).await?;
38
39    let mut hasher = Sha1::new();
40    hasher.update(&content);
41    let calculated_sha1 = hex::encode(hasher.finalize());
42
43    Ok(calculated_sha1.eq_ignore_ascii_case(expected_sha1))
44}
45
46/// Verifies the SHA1 hash of a file with streaming (async version)
47///
48/// Reads the file in chunks to minimize memory usage.
49/// Suitable for large files.
50///
51/// # Arguments
52/// * `path` - Path to the file to verify
53/// * `expected_sha1` - Expected SHA1 hash (case-insensitive)
54///
55/// # Returns
56/// `true` if the hash matches, `false` otherwise
57pub async fn verify_file_sha1_streaming(path: &Path, expected_sha1: &str) -> HashResult<bool> {
58    use tokio::io::AsyncReadExt;
59
60    let mut file = fs::File::open(path).await?;
61    let mut hasher = Sha1::new();
62    let mut buffer = [0u8; 8192]; // 8KB buffer
63
64    loop {
65        let n = file.read(&mut buffer).await?;
66        if n == 0 {
67            break;
68        }
69        hasher.update(&buffer[..n]);
70    }
71
72    let calculated_sha1 = hex::encode(hasher.finalize());
73    Ok(calculated_sha1.eq_ignore_ascii_case(expected_sha1))
74}
75
76/// Calculates the SHA1 hash of a file (sync version)
77///
78/// Reads the file in chunks using blocking I/O.
79/// Suitable for use in non-async contexts (e.g., zip archive processing).
80///
81/// # Arguments
82/// * `path` - Path to the file
83///
84/// # Returns
85/// The SHA1 hash as a lowercase hex string
86pub fn calculate_file_sha1_sync(path: &Path) -> HashResult<String> {
87    let mut file = std::fs::File::open(path)?;
88    let mut hasher = Sha1::new();
89    let mut buffer = [0u8; 8192];
90
91    loop {
92        let n = file.read(&mut buffer)?;
93        if n == 0 {
94            break;
95        }
96        hasher.update(&buffer[..n]);
97    }
98
99    Ok(hex::encode(hasher.finalize()))
100}
101
102/// Verifies the SHA1 hash of a file (sync version)
103///
104/// # Arguments
105/// * `path` - Path to the file to verify
106/// * `expected_sha1` - Expected SHA1 hash (case-insensitive)
107///
108/// # Returns
109/// `true` if the hash matches, `false` otherwise
110pub fn verify_file_sha1_sync(path: &Path, expected_sha1: &str) -> HashResult<bool> {
111    let calculated_sha1 = calculate_file_sha1_sync(path)?;
112    Ok(calculated_sha1.eq_ignore_ascii_case(expected_sha1))
113}
114
115/// Calculates the SHA1 hash of arbitrary bytes
116///
117/// Useful for hashing strings, usernames, or any data in memory.
118///
119/// # Arguments
120/// * `data` - The bytes to hash
121///
122/// # Returns
123/// The SHA1 hash as a lowercase hex string
124pub fn calculate_sha1_bytes(data: &[u8]) -> String {
125    let mut hasher = Sha1::new();
126    hasher.update(data);
127    hex::encode(hasher.finalize())
128}
129
130/// Calculates the SHA1 hash of arbitrary bytes and returns raw hash bytes
131///
132/// Useful when you need the raw hash bytes instead of hex string.
133///
134/// # Arguments
135/// * `data` - The bytes to hash
136///
137/// # Returns
138/// The SHA1 hash as raw bytes (20 bytes)
139pub fn calculate_sha1_bytes_raw(data: &[u8]) -> [u8; 20] {
140    let mut hasher = Sha1::new();
141    hasher.update(data);
142    hasher.finalize().into()
143}