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_reader_hash(
91 reader: &mut impl Read,
92 algo: HashAlgorithm
93) -> Result<(u64, String)> {
94 let mut buffer = [0u8; 8192];
95 let mut len: u64 = 0;
96
97 macro_rules! stream {
98 ($hasher:expr) => {{
99 loop {
100 let bytes_read = reader.read(&mut buffer)?;
101 if bytes_read == 0 {
102 break;
103 }
104 len += bytes_read as u64;
105 $hasher.update(
106 buffer
107 .get(..bytes_read)
108 .ok_or_else(|| anyhow!("Buffer overflow"))?
109 );
110 }
111 hex::encode($hasher.finalize())
112 }};
113 }
114
115 match algo {
116 HashAlgorithm::Sha512 => {
117 let mut hasher = Sha512::new();
118 let result = stream!(hasher);
119 Ok((len, result))
120 }
121 HashAlgorithm::Sha256 => {
122 let mut hasher = Sha256::new();
123 let result = stream!(hasher);
124 Ok((len, result))
125 }
126 }
127}
128
129pub fn calculate_string_hash(input: &str, algo: HashAlgorithm) -> String {
131 match algo {
132 HashAlgorithm::Sha512 => {
133 let mut hasher = Sha512::new();
134 hasher.update(input.as_bytes());
135 hex::encode(hasher.finalize())
136 }
137 HashAlgorithm::Sha256 => {
138 let mut hasher = Sha256::new();
139 hasher.update(input.as_bytes());
140 hex::encode(hasher.finalize())
141 }
142 }
143}
144
145pub fn calculate_dir_hash(path: &Path) -> Result<String> {
163 if !path.is_dir() {
164 return Err(anyhow!("Path is not a directory"));
165 }
166
167 let mut hasher = Sha512::new();
168 let mut paths = Vec::new();
169
170 for entry in WalkDir::new(path)
171 .into_iter()
172 .filter_map(std::result::Result::ok)
173 {
174 if entry.file_type().is_file() {
175 paths.push(entry.path().to_path_buf());
176 }
177 }
178
179 paths.sort();
180
181 for file_path in paths {
182 if let Ok(rel_path) = file_path.strip_prefix(path) {
183 let rel_path_str =
184 rel_path.to_string_lossy().to_string().replace('\\', "/");
185 let path_bytes = rel_path_str.as_bytes();
186 hasher.update((path_bytes.len() as u64).to_le_bytes());
187 hasher.update(path_bytes);
188 }
189
190 let mut file = fs::File::open(&file_path)?;
191 let metadata = file.metadata()?;
192 hasher.update(metadata.len().to_le_bytes());
193
194 let mut buffer = [0; 8192];
195 loop {
196 let bytes_read = file.read(&mut buffer)?;
197 if bytes_read == 0 {
198 break;
199 }
200 hasher.update(
201 buffer
202 .get(..bytes_read)
203 .ok_or_else(|| anyhow!("Buffer overflow"))?
204 );
205 }
206 }
207
208 Ok(hex::encode(hasher.finalize()))
209}