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}