1use std::fs;
2use std::io::Read;
3use std::path::Path;
4
5use anyhow::{Result, anyhow};
6use sha2::{Digest, Sha256, Sha512};
7use walkdir::WalkDir;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum HashAlgorithm {
12 Sha512,
14 Sha256
16}
17
18impl HashAlgorithm {
19 pub fn from_len(len: usize) -> Option<Self> {
21 match len {
22 128 => Some(HashAlgorithm::Sha512),
23 64 => Some(HashAlgorithm::Sha256),
24 _ => None
25 }
26 }
27
28 pub fn from_name(name: &str) -> Option<Self> {
30 match name.to_lowercase().as_str() {
31 "sha512" => Some(HashAlgorithm::Sha512),
32 "sha256" => Some(HashAlgorithm::Sha256),
33 _ => None
34 }
35 }
36}
37
38pub fn calculate_file_hash(path: &Path, algo: HashAlgorithm) -> Result<String> {
44 let mut file = fs::File::open(path)
45 .map_err(|e| anyhow!("Failed to open file {}: {e}", path.display()))?;
46
47 match algo {
48 HashAlgorithm::Sha512 => {
49 let mut hasher = Sha512::new();
50 let mut buffer = [0; 8192];
51 loop {
52 let bytes_read = file.read(&mut buffer)?;
53 if bytes_read == 0 {
54 break;
55 }
56 hasher.update(
57 buffer
58 .get(..bytes_read)
59 .ok_or_else(|| anyhow!("Buffer overflow"))?
60 );
61 }
62 Ok(hex::encode(hasher.finalize()))
63 }
64 HashAlgorithm::Sha256 => {
65 let mut hasher = Sha256::new();
66 let mut buffer = [0; 8192];
67 loop {
68 let bytes_read = file.read(&mut buffer)?;
69 if bytes_read == 0 {
70 break;
71 }
72 hasher.update(
73 buffer
74 .get(..bytes_read)
75 .ok_or_else(|| anyhow!("Buffer overflow"))?
76 );
77 }
78 Ok(hex::encode(hasher.finalize()))
79 }
80 }
81}
82
83pub fn calculate_string_hash(input: &str, algo: HashAlgorithm) -> String {
85 match algo {
86 HashAlgorithm::Sha512 => {
87 let mut hasher = Sha512::new();
88 hasher.update(input.as_bytes());
89 hex::encode(hasher.finalize())
90 }
91 HashAlgorithm::Sha256 => {
92 let mut hasher = Sha256::new();
93 hasher.update(input.as_bytes());
94 hex::encode(hasher.finalize())
95 }
96 }
97}
98
99pub fn calculate_dir_hash(path: &Path) -> Result<String> {
117 if !path.is_dir() {
118 return Err(anyhow!("Path is not a directory"));
119 }
120
121 let mut hasher = Sha512::new();
122 let mut paths = Vec::new();
123
124 for entry in WalkDir::new(path)
125 .into_iter()
126 .filter_map(std::result::Result::ok)
127 {
128 if entry.file_type().is_file() {
129 paths.push(entry.path().to_path_buf());
130 }
131 }
132
133 paths.sort();
134
135 for file_path in paths {
136 if let Ok(rel_path) = file_path.strip_prefix(path) {
137 let rel_path_str =
138 rel_path.to_string_lossy().to_string().replace('\\', "/");
139 let path_bytes = rel_path_str.as_bytes();
140 hasher.update((path_bytes.len() as u64).to_le_bytes());
141 hasher.update(path_bytes);
142 }
143
144 let mut file = fs::File::open(&file_path)?;
145 let metadata = file.metadata()?;
146 hasher.update(metadata.len().to_le_bytes());
147
148 let mut buffer = [0; 8192];
149 loop {
150 let bytes_read = file.read(&mut buffer)?;
151 if bytes_read == 0 {
152 break;
153 }
154 hasher.update(
155 buffer
156 .get(..bytes_read)
157 .ok_or_else(|| anyhow!("Buffer overflow"))?
158 );
159 }
160 }
161
162 Ok(hex::encode(hasher.finalize()))
163}