Skip to main content

minecraft_java_rs_core/utils/
hash.rs

1use 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
15/// Compute a hex-encoded hash of the file at `path`.
16pub 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// ── Tests ─────────────────────────────────────────────────────────────────────
43
44#[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}