aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJack O'Connor <[email protected]>2021-02-03 13:27:46 -0500
committerJack O'Connor <[email protected]>2021-02-04 15:36:29 -0500
commitcc21dd013254624c30fdf461b83a8822d877b9f6 (patch)
tree99a9682e0299b2e48244e19b1cbd5cf999142052 /src
parent9e08f5c38de44b8bb9aeb8a388653458b29e9665 (diff)
implement Error for ParseError, make it opaque, and support from_hex(&[u8])
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs75
-rw-r--r--src/test.rs14
2 files changed, 58 insertions, 31 deletions
diff --git a/src/lib.rs b/src/lib.rs
index c8148fa..f165814 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -161,16 +161,6 @@ 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
@@ -243,31 +233,32 @@ impl Hash {
s
}
- /// Parse a hexidecimal string and return the resulting Hash.
+ /// Parse 64 hexidecimal characters into a 32-byte `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));
-
+ /// Both uppercase and lowercase ASCII characters are supported. Any character outside the
+ /// ranges `(0, 9)`, `(a, f)` and `(A, F)` results in an error. An input length other than 64
+ /// also results in an error.
+ ///
+ /// Note that `Hash` also implements `FromStr`, `Hash::from_hex("...")` is equivalent to
+ /// `"...".parse()`.
+ pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, ParseError> {
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),
+ _ => Err(ParseError(ParseErrorInner::InvalidByte(byte))),
}
}
+ let hex_bytes: &[u8] = hex.as_ref();
+ if hex_bytes.len() != OUT_LEN * 2 {
+ return Err(ParseError(ParseErrorInner::InvalidLen(hex_bytes.len())));
+ }
+ let mut hash_bytes: [u8; OUT_LEN] = [0; OUT_LEN];
+ for i in 0..OUT_LEN {
+ hash_bytes[i] = 16 * hex_val(hex_bytes[2 * i])? + hex_val(hex_bytes[2 * i + 1])?;
+ }
+ Ok(Hash::from(hash_bytes))
}
}
@@ -323,6 +314,36 @@ impl fmt::Debug for Hash {
}
}
+/// Errors from parsing hex values
+#[derive(Clone, Debug)]
+pub struct ParseError(ParseErrorInner);
+
+#[derive(Clone, Debug)]
+pub enum ParseErrorInner {
+ InvalidByte(u8),
+ InvalidLen(usize),
+}
+
+impl fmt::Display for ParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.0 {
+ ParseErrorInner::InvalidByte(byte) => {
+ if byte < 128 {
+ write!(f, "invalid hex character: {:?}", byte as char)
+ } else {
+ write!(f, "invalid hex character: 0x{:x}", byte)
+ }
+ }
+ ParseErrorInner::InvalidLen(len) => {
+ write!(f, "expected 64 hex bytes, received {}", len)
+ }
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ParseError {}
+
// Each chunk or parent node can produce either a 32-byte chaining value or, by
// setting the ROOT flag, any number of final output bytes. The Output struct
// captures the state just prior to choosing between those two possibilities.
diff --git a/src/test.rs b/src/test.rs
index 6156185..ff9a92d 100644
--- a/src/test.rs
+++ b/src/test.rs
@@ -586,10 +586,16 @@ fn test_hex_encoding_decoding() {
// Test errors
let bad_len = "04e0bb39f30b1";
- let result = crate::Hash::from_hex(bad_len).unwrap_err();
- assert_eq!(result, crate::ParseError::InvalidLen);
+ let _result = crate::Hash::from_hex(bad_len).unwrap_err();
+ #[cfg(feature = "std")]
+ assert_eq!(_result.to_string(), "expected 64 hex bytes, received 13");
let bad_char = "Z4e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9";
- let result = crate::Hash::from_hex(bad_char).unwrap_err();
- assert_eq!(result, crate::ParseError::InvalidChar);
+ let _result = crate::Hash::from_hex(bad_char).unwrap_err();
+ #[cfg(feature = "std")]
+ assert_eq!(_result.to_string(), "invalid hex character: 'Z'");
+
+ let _result = crate::Hash::from_hex([128; 64]).unwrap_err();
+ #[cfg(feature = "std")]
+ assert_eq!(_result.to_string(), "invalid hex character: 0x80");
}