1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use std::fmt::LowerHex;
use std::path::PathBuf;

/// Create a relative path from a hash with a given depth, such that for the given depth,
/// those beginning values of the hash are sub-directories.
pub trait HashPath {
    /// Get the path object from the hash with the requested sub-directory depth
    fn hashed_path(self, depth: usize) -> PathBuf;
}

// TODO: Fix so this can be used with `String`
// https://github.com/malwaredb/malwaredb-rs/issues/60
impl<T> HashPath for T
where
    T: AsRef<[u8]> + IntoIterator,
    T::Item: LowerHex,
{
    fn hashed_path(self, depth: usize) -> PathBuf {
        let mut path = PathBuf::new();
        let the_hash = hex::encode(&self);
        for (index, value) in self.into_iter().enumerate() {
            path.push(format!("{value:02x}"));
            if index >= depth - 1 {
                break;
            }
        }
        path.push(the_hash);
        path
    }
}

#[cfg(test)]
mod test {
    use super::HashPath;
    use md5::Md5;
    use sha1::Sha1;
    use sha2::{Digest, Sha256, Sha384, Sha512};

    const THE_STRING: &[u8] = b"hello world pretend this is a file!";

    #[test]
    fn test_sha512_path() {
        let mut hasher = Sha512::new();
        hasher.update(THE_STRING);

        let result = hasher.finalize();
        assert_eq!(
            "0f/84/0f84de02c2c3942e2c1b22819a8381270b5476574d8df50fa4b307cc61ffe45193ab7e4f9cdd4fa5a6c4abf8948e3220a8925c58a089473a4790533f50c79d7d",
            result.hashed_path(2).to_str().unwrap()
        );
        assert_eq!(
            "0f/84/de/0f84de02c2c3942e2c1b22819a8381270b5476574d8df50fa4b307cc61ffe45193ab7e4f9cdd4fa5a6c4abf8948e3220a8925c58a089473a4790533f50c79d7d",
            result.hashed_path(3).to_str().unwrap()
        );
    }

    #[test]
    fn test_sha384_path() {
        let mut hasher = Sha384::new();
        hasher.update(THE_STRING);

        let result = hasher.finalize();
        assert_eq!(
            "e4/05/e405fb66b64771f44efe0f16f7d98a2787cdd34f2ec481b3fefa5a458b2526b258a862ff55bb0c62bbfcee425629bc1e",
            result.hashed_path(2).to_str().unwrap()
        );
        assert_eq!(
            "e4/05/fb/e405fb66b64771f44efe0f16f7d98a2787cdd34f2ec481b3fefa5a458b2526b258a862ff55bb0c62bbfcee425629bc1e",
            result.hashed_path(3).to_str().unwrap()
        );
    }

    #[test]
    fn test_sha256_path() {
        let mut hasher = Sha256::new();
        hasher.update(THE_STRING);

        let result = hasher.finalize();
        assert_eq!(
            "12/77/1277be37873848472bcd3b58f76e70d4b01bf792b3e1bb8022f410d40804ab7e",
            result.hashed_path(2).to_str().unwrap()
        );
        assert_eq!(
            "12/77/be/1277be37873848472bcd3b58f76e70d4b01bf792b3e1bb8022f410d40804ab7e",
            result.hashed_path(3).to_str().unwrap()
        );
    }

    #[test]
    fn test_sha1_path() {
        let mut hasher = Sha1::new();
        hasher.update(THE_STRING);

        let result = hasher.finalize();
        assert_eq!(
            "d1/ed/d1ed345c982dd7bd8a260457fa2c0e59ed22f804",
            result.hashed_path(2).to_str().unwrap()
        );
        assert_eq!(
            "d1/ed/34/d1ed345c982dd7bd8a260457fa2c0e59ed22f804",
            result.hashed_path(3).to_str().unwrap()
        );
    }

    #[test]
    fn test_md5_path() {
        let mut hasher = Md5::new();
        hasher.update(THE_STRING);

        let result = hasher.finalize();
        assert_eq!(
            "a7/a9/a7a938a65a579cb64f632a90eff9862b",
            result.hashed_path(2).to_str().unwrap()
        );
        assert_eq!(
            "a7/a9/38/a7a938a65a579cb64f632a90eff9862b",
            result.hashed_path(3).to_str().unwrap()
        );
    }
}