1use crate::command::Algorithm;
2use anyhow::{Context, Result, anyhow};
3use ring::digest::{Context as DigestContext, SHA1_FOR_LEGACY_USE_ONLY, SHA256, SHA384, SHA512};
4use std::{fs, io::Read, path::Path};
5
6const BUFFER_SIZE: usize = 1024 * 1024;
7const BLAKE3_RAYON_MIN_BYTES: u64 = 128 * 1024;
8const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
9
10#[derive(Copy, Clone)]
11enum Blake3Mode {
12 AlwaysRayon,
13 Adaptive,
14}
15
16fn read_file_chunks(file_path: &Path, mut on_chunk: impl FnMut(&[u8])) -> Result<()> {
17 let mut file = std::fs::File::open(file_path)?;
18 let mut buf = vec![0_u8; BUFFER_SIZE].into_boxed_slice();
19
20 loop {
21 let size = file.read(&mut buf)?;
22 if size == 0 {
23 break;
24 }
25
26 let chunk = buf
27 .get(..size)
28 .ok_or_else(|| anyhow!("read size exceeded buffer length"))?;
29 on_chunk(chunk);
30 }
31
32 Ok(())
33}
34
35fn digest_file(algo: Algorithm, file_path: &Path, blake3_mode: Blake3Mode) -> Result<Vec<u8>> {
36 let hash = match algo {
37 Algorithm::Md5 => md5_digest(file_path)?.to_vec(),
38 Algorithm::Sha1 => sha1_digest(file_path)?,
39 Algorithm::Sha256 => sha256_digest(file_path)?,
40 Algorithm::Sha384 => sha384_digest(file_path)?,
41 Algorithm::Sha512 => sha512_digest(file_path)?,
42 Algorithm::Blake => blake3_digest(file_path, blake3_mode)?.to_vec(),
43 };
44
45 Ok(hash)
46}
47
48pub fn hash_file(algo: Algorithm, file_path: &Path) -> Result<String> {
51 let hash = digest_file(algo, file_path, Blake3Mode::AlwaysRayon)
52 .with_context(|| format!("failed to hash {}", file_path.display()))?;
53 Ok(write_hex_bytes(&hash))
54}
55
56pub(crate) fn hash_file_for_walk(algo: Algorithm, file_path: &Path) -> Result<String> {
59 let hash = digest_file(algo, file_path, Blake3Mode::Adaptive)
60 .with_context(|| format!("failed to hash {}", file_path.display()))?;
61 Ok(write_hex_bytes(&hash))
62}
63
64pub(crate) fn hash_file_bytes_for_walk(algo: Algorithm, file_path: &Path) -> Result<Vec<u8>> {
67 digest_file(algo, file_path, Blake3Mode::Adaptive)
68 .with_context(|| format!("failed to hash {}", file_path.display()))
69}
70
71fn blake3_digest(file_path: &Path, mode: Blake3Mode) -> Result<[u8; blake3::OUT_LEN]> {
72 let mut hasher = blake3::Hasher::new();
73
74 match mode {
75 Blake3Mode::AlwaysRayon => {
76 hasher.update_mmap_rayon(file_path)?;
77 }
78 Blake3Mode::Adaptive => {
79 let metadata = fs::metadata(file_path)?;
80 if metadata.len() >= BLAKE3_RAYON_MIN_BYTES {
81 hasher.update_mmap_rayon(file_path)?;
82 } else {
83 hasher.update_mmap(file_path)?;
84 }
85 }
86 }
87
88 Ok(*hasher.finalize().as_bytes())
89}
90
91fn md5_digest(file_path: &Path) -> Result<[u8; 16]> {
92 let mut context = md5::Context::new();
93 read_file_chunks(file_path, |chunk| context.consume(chunk))?;
94 Ok(context.compute().0)
95}
96
97fn sha1_digest(file_path: &Path) -> Result<Vec<u8>> {
98 let mut context = DigestContext::new(&SHA1_FOR_LEGACY_USE_ONLY);
99 read_file_chunks(file_path, |chunk| context.update(chunk))?;
100 Ok(context.finish().as_ref().to_vec())
101}
102
103fn sha256_digest(file_path: &Path) -> Result<Vec<u8>> {
104 let mut context = DigestContext::new(&SHA256);
105 read_file_chunks(file_path, |chunk| context.update(chunk))?;
106 Ok(context.finish().as_ref().to_vec())
107}
108
109fn sha384_digest(file_path: &Path) -> Result<Vec<u8>> {
110 let mut context = DigestContext::new(&SHA384);
111 read_file_chunks(file_path, |chunk| context.update(chunk))?;
112 Ok(context.finish().as_ref().to_vec())
113}
114
115fn sha512_digest(file_path: &Path) -> Result<Vec<u8>> {
116 let mut context = DigestContext::new(&SHA512);
117 read_file_chunks(file_path, |chunk| context.update(chunk))?;
118 Ok(context.finish().as_ref().to_vec())
119}
120
121pub fn blake3(file_path: &Path) -> Result<String> {
124 let hash = blake3_digest(file_path, Blake3Mode::AlwaysRayon)?;
125 Ok(write_hex_bytes(&hash))
126}
127
128pub fn md5(file_path: &Path) -> Result<String> {
131 let hash = md5_digest(file_path)?;
132 Ok(write_hex_bytes(&hash))
133}
134
135pub fn sha1(file_path: &Path) -> Result<String> {
138 let hash = sha1_digest(file_path)?;
139 Ok(write_hex_bytes(&hash))
140}
141
142pub fn sha256(file_path: &Path) -> Result<String> {
145 let hash = sha256_digest(file_path)?;
146 Ok(write_hex_bytes(&hash))
147}
148
149pub fn sha384(file_path: &Path) -> Result<String> {
152 let hash = sha384_digest(file_path)?;
153 Ok(write_hex_bytes(&hash))
154}
155
156pub fn sha512(file_path: &Path) -> Result<String> {
159 let hash = sha512_digest(file_path)?;
160 Ok(write_hex_bytes(&hash))
161}
162
163#[must_use]
164pub fn write_hex_bytes(bytes: &[u8]) -> String {
165 let mut s = String::with_capacity(bytes.len() * 2);
166 for &byte in bytes {
167 let high = HEX_DIGITS
168 .get(usize::from(byte >> 4))
169 .copied()
170 .unwrap_or(b'0');
171 let low = HEX_DIGITS
172 .get(usize::from(byte & 0x0f))
173 .copied()
174 .unwrap_or(b'0');
175 s.push(char::from(high));
176 s.push(char::from(low));
177 }
178 s
179}