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
//! Cryptographic Hash Functions
//! 

use std::marker::PhantomData;

/// The hash function trait with multiple updates.
pub trait Hasher {
    fn new() -> Self;
    fn update(&mut self, input: &[u8]);
    fn finalize(self) -> Vec<u8>;
}

/// The hash function trait with one-shot hash call.
pub trait OnceHasher {
    type Hasher: Hasher;

    fn hash(&self, input: &[u8]) -> Vec<u8>;
    fn new_hasher(&self) -> Self::Hasher {
        Self::Hasher::new()
    }
}

/// The type wraps a Hahser to a OnceHasher
pub struct OnceWrapper<H: Hasher>(PhantomData<H>);

impl<H: Hasher> OnceHasher for OnceWrapper<H> {
    type Hasher = H;

    fn hash(&self, input: &[u8]) -> Vec<u8> {
        let mut hasher = H::new();
        hasher.update(input);
        hasher.finalize()
    }
}

/// A macro to call hash with variadic arguments.
#[macro_export]
macro_rules! hash {
    ($oh:ident, $($arg:expr),+) => {
        {
            use $crate::hash::{Hasher, OnceHasher};
            let mut hasher = $crate::hash::$oh.new_hasher();
            $(hasher.update($arg);)+
            hasher.finalize()
        }
    };
}

mod sha1;
pub use sha1::Sha1;

/// SHA1 is a constant Sha1 instance with OnceHasher trait.
pub const SHA1: OnceWrapper<Sha1> = OnceWrapper(PhantomData);

/// A macro to call sha1 hash with variadic arguments.
#[macro_export]
macro_rules! sha1 {
    ($($arg:expr),+) => {
        $crate::hash!(SHA1, $($arg),+)
    };
}

#[cfg(test)]
mod tests {

    use super::*;
    use hex_literal::hex;
    use rstest::*;

    #[rstest]
    #[case(Sha1::new())]
    fn update_finalize(#[case] mut hasher: impl Hasher) {
        // let mut hasher = Sha1::new();
        let data1 = r#"{"nickName":"Band","gender":1,"language":"zh_CN","city":"Guangzhou","province":"Guangdong","country":"CN","avatarUrl":"http://wx.qlogo.cn/mmopen/vi_32/1vZvI39NWFQ9XM4LtQpFrQJ1xlgZxx3w7bQxKARol6503Iuswjjn6nIGBiaycAjAtpujxyzYsrztuuICqIM5ibXQ/0"}"#;
        let data2 = "HyVFkGl5F5OQWJZZaNzBBg==";
        hasher.update(data1.as_bytes());
        hasher.update(data2.as_bytes());
        let result = hasher.finalize();
        assert_eq!(result[..], hex!("75e81ceda165f4ffa64f4068af58c64b8f54b88c"));
    }

    #[rstest]
    #[case(SHA1)]
    fn hash(#[case] hasher: impl OnceHasher) {
        let data = r#"{"nickName":"Band","gender":1,"language":"zh_CN","city":"Guangzhou","province":"Guangdong","country":"CN","avatarUrl":"http://wx.qlogo.cn/mmopen/vi_32/1vZvI39NWFQ9XM4LtQpFrQJ1xlgZxx3w7bQxKARol6503Iuswjjn6nIGBiaycAjAtpujxyzYsrztuuICqIM5ibXQ/0"}HyVFkGl5F5OQWJZZaNzBBg=="#;
        let result = hasher.hash(data.as_bytes());
        assert_eq!(result[..], hex!("75e81ceda165f4ffa64f4068af58c64b8f54b88c"));
    }

    #[test]
    fn macro_test() {
        let data1 = r#"{"nickName":"Band","gender":1,"language":"zh_CN","city":"Guangzhou","province":"Guangdong","country":"CN","avatarUrl":"http://wx.qlogo.cn/mmopen/vi_32/1vZvI39NWFQ9XM4LtQpFrQJ1xlgZxx3w7bQxKARol6503Iuswjjn6nIGBiaycAjAtpujxyzYsrztuuICqIM5ibXQ/0"}"#;
        let data2 = "HyVFkGl5F5OQWJZZaNzBBg==";
        let result = crate::sha1!(data1.as_bytes(), data2.as_bytes());
        assert_eq!(result[..], hex!("75e81ceda165f4ffa64f4068af58c64b8f54b88c"));
    }
}