lune_std_serde/
hash.rs

1use std::fmt::Write;
2
3use bstr::BString;
4use md5::Md5;
5use mlua::prelude::*;
6
7use blake3::Hasher as Blake3;
8use sha1::Sha1;
9use sha2::{Sha224, Sha256, Sha384, Sha512};
10use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512};
11
12pub struct HashOptions {
13    algorithm: HashAlgorithm,
14    message: BString,
15    secret: Option<BString>,
16    // seed: Option<BString>,
17}
18
19#[derive(Debug, Clone, Copy)]
20enum HashAlgorithm {
21    Md5,
22    Sha1,
23    // SHA-2 variants
24    Sha2_224,
25    Sha2_256,
26    Sha2_384,
27    Sha2_512,
28    // SHA-3 variants
29    Sha3_224,
30    Sha3_256,
31    Sha3_384,
32    Sha3_512,
33    // Blake3
34    Blake3,
35}
36
37impl HashAlgorithm {
38    pub const ALL: [Self; 11] = [
39        Self::Md5,
40        Self::Sha1,
41        Self::Sha2_224,
42        Self::Sha2_256,
43        Self::Sha2_384,
44        Self::Sha2_512,
45        Self::Sha3_224,
46        Self::Sha3_256,
47        Self::Sha3_384,
48        Self::Sha3_512,
49        Self::Blake3,
50    ];
51
52    pub const fn name(self) -> &'static str {
53        match self {
54            Self::Md5 => "md5",
55            Self::Sha1 => "sha1",
56            Self::Sha2_224 => "sha224",
57            Self::Sha2_256 => "sha256",
58            Self::Sha2_384 => "sha384",
59            Self::Sha2_512 => "sha512",
60            Self::Sha3_224 => "sha3-224",
61            Self::Sha3_256 => "sha3-256",
62            Self::Sha3_384 => "sha3-384",
63            Self::Sha3_512 => "sha3-512",
64            Self::Blake3 => "blake3",
65        }
66    }
67}
68
69impl HashOptions {
70    /**
71        Computes the hash for the `message` using whatever `algorithm` is
72        contained within this struct and returns it as a string of hex digits.
73    */
74    #[inline]
75    #[must_use = "hashing a message is useless without using the resulting hash"]
76    pub fn hash(self) -> String {
77        use digest::Digest;
78
79        let message = self.message;
80        let bytes = match self.algorithm {
81            HashAlgorithm::Md5 => Md5::digest(message).to_vec(),
82            HashAlgorithm::Sha1 => Sha1::digest(message).to_vec(),
83            HashAlgorithm::Sha2_224 => Sha224::digest(message).to_vec(),
84            HashAlgorithm::Sha2_256 => Sha256::digest(message).to_vec(),
85            HashAlgorithm::Sha2_384 => Sha384::digest(message).to_vec(),
86            HashAlgorithm::Sha2_512 => Sha512::digest(message).to_vec(),
87
88            HashAlgorithm::Sha3_224 => Sha3_224::digest(message).to_vec(),
89            HashAlgorithm::Sha3_256 => Sha3_256::digest(message).to_vec(),
90            HashAlgorithm::Sha3_384 => Sha3_384::digest(message).to_vec(),
91            HashAlgorithm::Sha3_512 => Sha3_512::digest(message).to_vec(),
92
93            HashAlgorithm::Blake3 => Blake3::digest(message).to_vec(),
94        };
95
96        // We don't want to return raw binary data generally, since that's not
97        // what most people want a hash for. So we have to make a hex string.
98        bytes
99            .iter()
100            .fold(String::with_capacity(bytes.len() * 2), |mut output, b| {
101                let _ = write!(output, "{b:02x}");
102                output
103            })
104    }
105
106    /**
107        Computes the HMAC for the `message` using whatever `algorithm` and
108        `secret` are contained within this struct. The computed value is
109        returned as a string of hex digits.
110
111        # Errors
112
113        If the `secret` is not provided or is otherwise invalid.
114    */
115    #[inline]
116    pub fn hmac(self) -> LuaResult<String> {
117        use hmac::{Hmac, Mac, SimpleHmac};
118
119        let secret = self
120            .secret
121            .ok_or_else(|| LuaError::FromLuaConversionError {
122                from: "nil",
123                to: "string or buffer".to_string(),
124                message: Some("Argument #3 missing or nil".to_string()),
125            })?;
126
127        /*
128            These macros exist to remove what would ultimately be dozens of
129            repeating lines. Essentially, there's several step to processing
130            HMacs, which expands into the 3 lines you see below. However,
131            the Hmac struct is specialized towards eager block-based processes.
132            In order to support anything else, like blake3, there's a second
133            type named `SimpleHmac`. This results in duplicate macros like
134            there are below.
135        */
136        macro_rules! hmac {
137            ($Type:ty) => {{
138                let mut mac: Hmac<$Type> = Hmac::new_from_slice(&secret).into_lua_err()?;
139                mac.update(&self.message);
140                mac.finalize().into_bytes().to_vec()
141            }};
142        }
143        macro_rules! hmac_no_blocks {
144            ($Type:ty) => {{
145                let mut mac: SimpleHmac<$Type> =
146                    SimpleHmac::new_from_slice(&secret).into_lua_err()?;
147                mac.update(&self.message);
148                mac.finalize().into_bytes().to_vec()
149            }};
150        }
151
152        let bytes = match self.algorithm {
153            HashAlgorithm::Md5 => hmac!(Md5),
154            HashAlgorithm::Sha1 => hmac!(Sha1),
155
156            HashAlgorithm::Sha2_224 => hmac!(Sha224),
157            HashAlgorithm::Sha2_256 => hmac!(Sha256),
158            HashAlgorithm::Sha2_384 => hmac!(Sha384),
159            HashAlgorithm::Sha2_512 => hmac!(Sha512),
160
161            HashAlgorithm::Sha3_224 => hmac!(Sha3_224),
162            HashAlgorithm::Sha3_256 => hmac!(Sha3_256),
163            HashAlgorithm::Sha3_384 => hmac!(Sha3_384),
164            HashAlgorithm::Sha3_512 => hmac!(Sha3_512),
165
166            HashAlgorithm::Blake3 => hmac_no_blocks!(Blake3),
167        };
168        Ok(bytes
169            .iter()
170            .fold(String::with_capacity(bytes.len() * 2), |mut output, b| {
171                let _ = write!(output, "{b:02x}");
172                output
173            }))
174    }
175}
176
177impl FromLua for HashAlgorithm {
178    fn from_lua(value: LuaValue, _lua: &Lua) -> LuaResult<Self> {
179        if let LuaValue::String(str) = value {
180            /*
181                Casing tends to vary for algorithms, so rather than force
182                people to remember it we'll just accept any casing.
183            */
184            let str = str.to_str()?.to_ascii_lowercase();
185            match str.as_str() {
186                "md5" => Ok(Self::Md5),
187                "sha1" => Ok(Self::Sha1),
188
189                "sha2-224" | "sha2_224" | "sha224" => Ok(Self::Sha2_224),
190                "sha2-256" | "sha2_256" | "sha256" => Ok(Self::Sha2_256),
191                "sha2-384" | "sha2_384" | "sha384" => Ok(Self::Sha2_384),
192                "sha2-512" | "sha2_512" | "sha512" => Ok(Self::Sha2_512),
193
194                "sha3-224" | "sha3_224" => Ok(Self::Sha3_224),
195                "sha3-256" | "sha3_256" => Ok(Self::Sha3_256),
196                "sha3-384" | "sha3_384" => Ok(Self::Sha3_384),
197                "sha3-512" | "sha3_512" => Ok(Self::Sha3_512),
198
199                "blake3" => Ok(Self::Blake3),
200
201                _ => Err(LuaError::FromLuaConversionError {
202                    from: "string",
203                    to: "HashAlgorithm".to_string(),
204                    message: Some(format!(
205                        "Invalid hashing algorithm '{str}', valid kinds are:\n{}",
206                        HashAlgorithm::ALL
207                            .into_iter()
208                            .map(HashAlgorithm::name)
209                            .collect::<Vec<_>>()
210                            .join(", ")
211                    )),
212                }),
213            }
214        } else {
215            Err(LuaError::FromLuaConversionError {
216                from: value.type_name(),
217                to: "HashAlgorithm".to_string(),
218                message: None,
219            })
220        }
221    }
222}
223
224impl FromLuaMulti for HashOptions {
225    fn from_lua_multi(mut values: LuaMultiValue, lua: &Lua) -> LuaResult<Self> {
226        let algorithm = values
227            .pop_front()
228            .map(|value| HashAlgorithm::from_lua(value, lua))
229            .transpose()?
230            .ok_or_else(|| LuaError::FromLuaConversionError {
231                from: "nil",
232                to: "HashOptions".to_string(),
233                message: Some("Argument #1 missing or nil".to_string()),
234            })?;
235        let message = values
236            .pop_front()
237            .map(|value| BString::from_lua(value, lua))
238            .transpose()?
239            .ok_or_else(|| LuaError::FromLuaConversionError {
240                from: "nil",
241                to: "string or buffer".to_string(),
242                message: Some("Argument #2 missing or nil".to_string()),
243            })?;
244        let secret = values
245            .pop_front()
246            .map(|value| BString::from_lua(value, lua))
247            .transpose()?;
248        // let seed = values
249        //     .pop_front()
250        //     .map(|value| BString::from_lua(value, lua))
251        //     .transpose()?;
252
253        Ok(HashOptions {
254            algorithm,
255            message,
256            secret,
257            // seed,
258        })
259    }
260}