diff options
| author | Jack O'Connor <[email protected]> | 2021-02-03 11:53:56 -0500 |
|---|---|---|
| committer | Jack O'Connor <[email protected]> | 2021-02-03 11:53:56 -0500 |
| commit | 9e08f5c38de44b8bb9aeb8a388653458b29e9665 (patch) | |
| tree | e174d927457cc7d0e4f4af4b0fdf1ff55b0d135c /src | |
| parent | 3a8204f5f38109aae08f4ae58b275663e1cfebab (diff) | |
| parent | c2f90dfdb0ce8b33626f4b3fd789188417c9eb7b (diff) | |
merge "Adding from_hex and implementing FromStr for Hash"
https://github.com/BLAKE3-team/BLAKE3/pull/24
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 45 | ||||
| -rw-r--r-- | src/test.rs | 26 |
2 files changed, 71 insertions, 0 deletions
@@ -161,6 +161,16 @@ const KEYED_HASH: u8 = 1 << 4; const DERIVE_KEY_CONTEXT: u8 = 1 << 5; const DERIVE_KEY_MATERIAL: u8 = 1 << 6; +/// Errors from parsing hex values +#[derive(Debug, PartialEq)] +pub enum ParseError { + /// Hexadecimal str contains invalid character + InvalidChar, + + /// Invalid str length. Only 32 byte digests can be parsed from a 64 char hex encoded str. + InvalidLen, +} + #[inline] fn counter_low(counter: u64) -> u32 { counter as u32 @@ -232,6 +242,33 @@ impl Hash { } s } + + /// Parse a hexidecimal string and return the resulting Hash. + /// + /// The string must be 64 characters long, producting a 32 byte digest. + /// All other string length will return a `ParseError::InvalidLen`. + pub fn from_hex(hex: &str) -> Result<Self, ParseError> { + let str_bytes = hex.as_bytes(); + if str_bytes.len() != OUT_LEN * 2 { + return Err(ParseError::InvalidLen); + } + + let mut bytes: [u8; OUT_LEN] = [0; OUT_LEN]; + for (i, pair) in str_bytes.chunks(2).enumerate() { + bytes[i] = hex_val(pair[0])? << 4 | hex_val(pair[1])?; + } + + return Ok(Hash::from(bytes)); + + fn hex_val(byte: u8) -> Result<u8, ParseError> { + match byte { + b'A'..=b'F' => Ok(byte - b'A' + 10), + b'a'..=b'f' => Ok(byte - b'a' + 10), + b'0'..=b'9' => Ok(byte - b'0'), + _ => Err(ParseError::InvalidChar), + } + } + } } impl From<[u8; OUT_LEN]> for Hash { @@ -248,6 +285,14 @@ impl From<Hash> for [u8; OUT_LEN] { } } +impl core::str::FromStr for Hash { + type Err = ParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Hash::from_hex(s) + } +} + /// This implementation is constant-time. impl PartialEq for Hash { #[inline] diff --git a/src/test.rs b/src/test.rs index eefb1a3..6156185 100644 --- a/src/test.rs +++ b/src/test.rs @@ -567,3 +567,29 @@ fn test_join_lengths() { ); assert_eq!(CUSTOM_JOIN_CALLS.load(Ordering::SeqCst), 1); } + +#[test] +fn test_hex_encoding_decoding() { + let digest_str = "04e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9"; + let mut hasher = crate::Hasher::new(); + hasher.update(b"foo"); + let digest = hasher.finalize(); + assert_eq!(digest.to_hex().as_str(), digest_str); + + // Test round trip + let digest = crate::Hash::from_hex(digest_str).unwrap(); + assert_eq!(digest.to_hex().as_str(), digest_str); + + // Test string parsing via FromStr + let digest: crate::Hash = digest_str.parse().unwrap(); + assert_eq!(digest.to_hex().as_str(), digest_str); + + // Test errors + let bad_len = "04e0bb39f30b1"; + let result = crate::Hash::from_hex(bad_len).unwrap_err(); + assert_eq!(result, crate::ParseError::InvalidLen); + + let bad_char = "Z4e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9"; + let result = crate::Hash::from_hex(bad_char).unwrap_err(); + assert_eq!(result, crate::ParseError::InvalidChar); +} |
