aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJack O'Connor <[email protected]>2021-02-03 11:53:56 -0500
committerJack O'Connor <[email protected]>2021-02-03 11:53:56 -0500
commit9e08f5c38de44b8bb9aeb8a388653458b29e9665 (patch)
treee174d927457cc7d0e4f4af4b0fdf1ff55b0d135c /src
parent3a8204f5f38109aae08f4ae58b275663e1cfebab (diff)
parentc2f90dfdb0ce8b33626f4b3fd789188417c9eb7b (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.rs45
-rw-r--r--src/test.rs26
2 files changed, 71 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
index bf66b6d..c8148fa 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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);
+}