pakker/src/utils/hash.rs
NotAShelf 61ced09d25
treewide: fix clippy lints
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I411be69ff31f9cb39cd4cdebc8985b366a6a6964
2026-04-21 19:27:36 +03:00

169 lines
3.9 KiB
Rust

use std::{
fs::File,
io::{BufReader, Read},
path::Path,
};
use md5::{Digest as Md5Digest, Md5};
use sha1::Sha1;
use sha2::{Sha256, Sha512};
use crate::error::{PakkerError, Result};
pub fn hash_to_hex(hash: impl AsRef<[u8]>) -> String {
use std::fmt::Write;
let bytes = hash.as_ref();
let mut hex = String::with_capacity(bytes.len() * 2);
for byte in bytes {
let _ = write!(hex, "{byte:02x}");
}
hex
}
/// Compute SHA1 hash of a file
pub fn compute_sha1<P: AsRef<Path>>(path: P) -> Result<String> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let mut hasher = Sha1::new();
let mut buffer = [0; 8192];
loop {
let n = reader.read(&mut buffer)?;
if n == 0 {
break;
}
hasher.update(&buffer[..n]);
}
Ok(hash_to_hex(hasher.finalize().as_slice()))
}
/// Compute SHA256 hash of a file
pub fn compute_sha256<P: AsRef<Path>>(path: P) -> Result<String> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let mut hasher = Sha256::new();
let mut buffer = [0; 8192];
loop {
let n = reader.read(&mut buffer)?;
if n == 0 {
break;
}
hasher.update(&buffer[..n]);
}
Ok(hash_to_hex(hasher.finalize().as_slice()))
}
/// Compute SHA256 hash of byte data
pub fn compute_sha256_bytes(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
hash_to_hex(hasher.finalize().as_slice())
}
/// Compute SHA512 hash of a file
pub fn compute_sha512<P: AsRef<Path>>(path: P) -> Result<String> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let mut hasher = Sha512::new();
let mut buffer = [0; 8192];
loop {
let n = reader.read(&mut buffer)?;
if n == 0 {
break;
}
hasher.update(&buffer[..n]);
}
Ok(hash_to_hex(hasher.finalize().as_slice()))
}
/// Compute MD5 hash of a file
pub fn compute_md5<P: AsRef<Path>>(path: P) -> Result<String> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let mut hasher = Md5::new();
let mut buffer = [0; 8192];
loop {
let n = reader.read(&mut buffer)?;
if n == 0 {
break;
}
hasher.update(&buffer[..n]);
}
let hash = hasher.finalize();
let mut hex = String::with_capacity(hash.len() * 2);
for byte in hash {
let _ = std::fmt::write(&mut hex, format_args!("{byte:02x}"));
}
Ok(hex)
}
/// Verify a file's hash against expected value
pub fn verify_hash<P: AsRef<Path>>(
path: P,
algorithm: &str,
expected: &str,
) -> Result<bool> {
let path = path.as_ref();
let actual = match algorithm {
"sha1" => compute_sha1(path)?,
"sha256" => compute_sha256(path)?,
"sha512" => compute_sha512(path)?,
"md5" => compute_md5(path)?,
_ => {
return Err(PakkerError::InternalError(format!(
"Unknown hash algorithm: {algorithm}"
)));
},
};
Ok(actual.eq_ignore_ascii_case(expected))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sha256_bytes_deterministic() {
let data = b"test data";
let hash1 = compute_sha256_bytes(data);
let hash2 = compute_sha256_bytes(data);
assert_eq!(hash1, hash2, "SHA256 must be deterministic");
}
#[test]
fn test_sha256_bytes_format() {
let data = b"hello";
let hash = compute_sha256_bytes(data);
assert_eq!(hash.len(), 64, "SHA256 hex should be 64 characters");
assert!(
hash.chars().all(|c| c.is_ascii_hexdigit()),
"SHA256 should only contain hex digits"
);
}
#[test]
fn test_sha256_bytes_empty() {
let hash = compute_sha256_bytes(b"");
assert_eq!(
hash,
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
}
#[test]
fn test_sha256_bytes_known_value() {
// SHA256 of "hello" in hex
let expected =
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
let hash = compute_sha256_bytes(b"hello");
assert_eq!(hash, expected);
}
}