diff --git a/proxmox/src/tools/mod.rs b/proxmox/src/tools/mod.rs index 9ce9dd75..ff3a720a 100644 --- a/proxmox/src/tools/mod.rs +++ b/proxmox/src/tools/mod.rs @@ -139,6 +139,37 @@ pub fn bin_to_hex(digest: &[u8]) -> String { AsHex(digest).to_string() } +/// Convert an ascii character into a hex nibble. +fn hex_nibble_to_byte(b: u8) -> Result { + Ok(match b { + b'0'..=b'9' => b - b'0', + b'a'..=b'f' => b - b'a' + 0xA, + b'A'..=b'F' => b - b'A' + 0xA, + _ => bail!("not a hexadecimal digit: {}", char::from(b)), + }) +} + +/// Parse hexadecimal digits into a byte array. +pub fn hex_to_bin_exact(hex: &str, out: &mut [u8]) -> Result<(), Error> { + let bytes = hex.as_bytes(); + + if bytes.len() != out.len() * 2 { + bail!( + "hexadecimal string has invalid length ({}, expected {})", + bytes.len(), + out.len() * 2, + ); + } + + for i in 0..out.len() { + let h = hex_nibble_to_byte(bytes[i * 2])?; + let l = hex_nibble_to_byte(bytes[i * 2 + 1])?; + out[i] = (h << 4) | l; + } + + Ok(()) +} + /// Convert a string of hexadecimal digits to a byte vector. Any non-digits are treated as an /// error, so when there is possible whitespace in the string it must be stripped by the caller /// first. Also, only full bytes are allowed, so the input must consist of an even number of @@ -151,78 +182,36 @@ pub fn bin_to_hex(digest: &[u8]) -> String { /// assert_eq!(&data, &[0xaa, 0xbb, 0x01, 0x23]); /// ``` pub fn hex_to_bin(hex: &str) -> Result, Error> { - let mut result = vec![]; - - let bytes = hex.as_bytes(); - - if (bytes.len() % 2) != 0 { + if (hex.len() % 2) != 0 { bail!("hex_to_bin: got wrong input length."); } - let val = |c| { - if c >= b'0' && c <= b'9' { - return Ok(c - b'0'); - } - if c >= b'a' && c <= b'f' { - return Ok(c - b'a' + 10); - } - if c >= b'A' && c <= b'F' { - return Ok(c - b'A' + 10); - } - bail!("found illegal hex character."); - }; - - for pair in bytes.chunks(2) { - let h = val(pair[0])?; - let l = val(pair[1])?; - result.push((h << 4) | l); - } - - Ok(result) + let mut out = unsafe { vec::uninitialized(hex.len() / 2) }; + hex_to_bin_exact(hex, &mut out)?; + Ok(out) } // FIXME: This should be renamed to contain the digest algorithm, so that the array's size makes // sense. pub fn hex_to_digest(hex: &str) -> Result<[u8; 32], Error> { let mut digest = [0u8; 32]; - - let bytes = hex.as_bytes(); - - if bytes.len() != 64 { - bail!("got wrong digest length."); - } - - let val = |c| { - if c >= b'0' && c <= b'9' { - return Ok(c - b'0'); - } - if c >= b'a' && c <= b'f' { - return Ok(c - b'a' + 10); - } - if c >= b'A' && c <= b'F' { - return Ok(c - b'A' + 10); - } - bail!("found illegal hex character."); - }; - - let mut pos = 0; - for pair in bytes.chunks(2) { - if pos >= digest.len() { - bail!("hex digest too long."); - } - let h = val(pair[0])?; - let l = val(pair[1])?; - digest[pos] = (h << 4) | l; - pos += 1; - } - - if pos != digest.len() { - bail!("hex digest too short."); - } - + hex_to_bin_exact(hex, &mut digest)?; Ok(digest) } +#[test] +fn test_hex() { + let mut out = [0u8; 5]; + hex_to_bin_exact("abCA01239f", &mut out).expect("failed to parse hex digit"); + assert_eq!(out, *b"\xab\xca\x01\x23\x9f"); + let v = hex_to_bin("abCA01239f").expect("failed to parse hex digit"); + assert_eq!(v, out); + + hex_to_bin_exact("abca01239", &mut out).expect_err("parsed invalid hex string"); + hex_to_bin_exact("abca01239fa", &mut out).expect_err("parsed invalid hex string"); + hex_to_bin_exact("abca0x239f", &mut out).expect_err("parsed invalid hex string"); +} + /// Returns the hosts node name (UTS node name) pub fn nodename() -> &'static str { lazy_static! {