Skip to main content

modo/encoding/
hex.rs

1//! Lowercase hexadecimal encoding and SHA-256 digest helper.
2
3use sha2::{Digest as _, Sha256};
4
5const HEX_TABLE: &[u8; 16] = b"0123456789abcdef";
6
7/// Encode a byte slice as a lowercase hexadecimal string.
8///
9/// # Examples
10///
11/// ```rust
12/// use modo::encoding::hex;
13///
14/// assert_eq!(hex::encode(b"\xde\xad\xbe\xef"), "deadbeef");
15/// assert_eq!(hex::encode(b""), "");
16/// ```
17pub fn encode(bytes: &[u8]) -> String {
18    let mut buf = Vec::with_capacity(bytes.len() * 2);
19    for &b in bytes {
20        buf.push(HEX_TABLE[(b >> 4) as usize]);
21        buf.push(HEX_TABLE[(b & 0x0f) as usize]);
22    }
23    // SAFETY: every byte written is from HEX_TABLE which contains only ASCII.
24    unsafe { String::from_utf8_unchecked(buf) }
25}
26
27/// SHA-256 hash of `data`, returned as a 64-character lowercase hex string.
28///
29/// # Examples
30///
31/// ```rust
32/// use modo::encoding::hex;
33///
34/// let digest = hex::sha256(b"hello world");
35/// assert_eq!(digest.len(), 64);
36/// assert_eq!(
37///     digest,
38///     "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
39/// );
40/// ```
41pub fn sha256(data: impl AsRef<[u8]>) -> String {
42    encode(&Sha256::digest(data.as_ref()))
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn encode_empty() {
51        assert_eq!(encode(b""), "");
52    }
53
54    #[test]
55    fn encode_known_bytes() {
56        assert_eq!(encode(b"\xde\xad\xbe\xef"), "deadbeef");
57    }
58
59    #[test]
60    fn encode_all_zeros() {
61        assert_eq!(encode(&[0u8; 4]), "00000000");
62    }
63
64    #[test]
65    fn encode_sequential() {
66        assert_eq!(
67            encode(&[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]),
68            "0123456789abcdef"
69        );
70    }
71}