minecraft_java_rs_core/utils/
hash.rs1use std::io::Read;
2use std::path::Path;
3
4use sha1::Digest;
5
6use crate::error::LaunchError;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum HashAlgorithm {
10 Sha1,
11 Md5,
12 Sha256,
13}
14
15pub fn get_file_hash(path: &Path, algorithm: HashAlgorithm) -> Result<String, LaunchError> {
17 let mut file = std::fs::File::open(path)?;
18 match algorithm {
19 HashAlgorithm::Sha1 => stream_hash(sha1::Sha1::new(), &mut file),
20 HashAlgorithm::Md5 => stream_hash(md5::Md5::new(), &mut file),
21 HashAlgorithm::Sha256 => stream_hash(sha2::Sha256::new(), &mut file),
22 }
23}
24
25fn stream_hash<D: Digest>(mut hasher: D, reader: &mut impl Read) -> Result<String, LaunchError> {
26 let mut buf = [0u8; 65536];
27 loop {
28 let n = reader.read(&mut buf)?;
29 if n == 0 {
30 break;
31 }
32 hasher.update(&buf[..n]);
33 }
34 Ok(hasher
35 .finalize()
36 .as_ref()
37 .iter()
38 .map(|b| format!("{b:02x}"))
39 .collect())
40}
41
42#[cfg(test)]
45mod tests {
46 use super::*;
47 use std::io::Write;
48 use tempfile::NamedTempFile;
49
50 fn write_temp(content: &[u8]) -> NamedTempFile {
51 let mut f = NamedTempFile::new().unwrap();
52 f.write_all(content).unwrap();
53 f
54 }
55
56 #[test]
57 fn sha1_empty() {
58 let f = write_temp(b"");
59 let h = get_file_hash(f.path(), HashAlgorithm::Sha1).unwrap();
60 assert_eq!(h, "da39a3ee5e6b4b0d3255bfef95601890afd80709");
61 }
62
63 #[test]
64 fn sha1_known() {
65 let f = write_temp(b"hello world");
66 let h = get_file_hash(f.path(), HashAlgorithm::Sha1).unwrap();
67 assert_eq!(h, "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed");
68 }
69
70 #[test]
71 fn md5_known() {
72 let f = write_temp(b"hello world");
73 let h = get_file_hash(f.path(), HashAlgorithm::Md5).unwrap();
74 assert_eq!(h, "5eb63bbbe01eeed093cb22bb8f5acdc3");
75 }
76
77 #[test]
78 fn sha256_known() {
79 let f = write_temp(b"hello world");
80 let h = get_file_hash(f.path(), HashAlgorithm::Sha256).unwrap();
81 assert_eq!(
82 h,
83 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
84 );
85 }
86
87 #[test]
88 fn missing_file_returns_error() {
89 let r = get_file_hash(Path::new("/nonexistent/file.bin"), HashAlgorithm::Sha1);
90 assert!(r.is_err());
91 }
92}