iuna

iuna - experimental devnet protocol
git clone https://iuna.jhx.app/git/iuna.git
Log | Files | Refs | README | LICENSE

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 }