hex.rs (2250B)
1 use anyhow::{Result, anyhow, bail}; 2 use sha2::{Digest, Sha256}; 3 4 pub fn hex_hash(input: impl AsRef<[u8]>) -> String { 5 hex_encode(Sha256::digest(input.as_ref())) 6 } 7 8 pub(super) fn decode_hex_array<const N: usize>(input: &str) -> Result<[u8; N]> { 9 let bytes = decode_hex(input)?; 10 let len = bytes.len(); 11 bytes 12 .try_into() 13 .map_err(|_| anyhow!("expected {} hex bytes, got {len}", N)) 14 } 15 16 pub(super) fn decode_hex(input: &str) -> Result<Vec<u8>> { 17 if input.len() % 2 != 0 { 18 bail!("hex string has odd length"); 19 } 20 21 let mut bytes = Vec::with_capacity(input.len() / 2); 22 for pair in input.as_bytes().chunks_exact(2) { 23 let high = hex_value(pair[0])?; 24 let low = hex_value(pair[1])?; 25 bytes.push((high << 4) | low); 26 } 27 Ok(bytes) 28 } 29 30 fn hex_value(byte: u8) -> Result<u8> { 31 match byte { 32 b'0'..=b'9' => Ok(byte - b'0'), 33 b'a'..=b'f' => Ok(byte - b'a' + 10), 34 b'A'..=b'F' => Ok(byte - b'A' + 10), 35 _ => bail!("invalid hex character"), 36 } 37 } 38 39 pub(super) fn hex_encode(bytes: impl AsRef<[u8]>) -> String { 40 const HEX: &[u8; 16] = b"0123456789abcdef"; 41 let bytes = bytes.as_ref(); 42 let mut encoded = String::with_capacity(bytes.len() * 2); 43 for byte in bytes { 44 encoded.push(HEX[(byte >> 4) as usize] as char); 45 encoded.push(HEX[(byte & 0x0f) as usize] as char); 46 } 47 encoded 48 } 49 50 #[cfg(test)] 51 mod tests { 52 use super::{decode_hex, decode_hex_array, hex_encode, hex_hash}; 53 54 #[test] 55 fn hex_roundtrips_bytes_and_accepts_uppercase() { 56 let bytes = [0x00, 0x0f, 0x10, 0xab, 0xff]; 57 58 assert_eq!(hex_encode(bytes), "000f10abff"); 59 assert_eq!(decode_hex("000F10ABff").unwrap(), bytes); 60 assert_eq!(decode_hex_array::<5>("000f10abff").unwrap(), bytes); 61 } 62 63 #[test] 64 fn hex_decoder_rejects_odd_length_invalid_digits_and_wrong_array_size() { 65 assert!(decode_hex("0").is_err()); 66 assert!(decode_hex("zz").is_err()); 67 assert!(decode_hex_array::<2>("00").is_err()); 68 } 69 70 #[test] 71 fn hex_hash_is_sha256_hex() { 72 assert_eq!( 73 hex_hash("iuna"), 74 "a66946533b68cc0eb75a82632d7a28256633f5a06ef04e3906c1960d437239aa" 75 ); 76 } 77 }