From cf9c02b83ecacb6f32f04da1519f37e406be35f5 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Mon, 24 Feb 2025 11:00:22 +1100 Subject: [PATCH] WIP: Depend on hex 1.0.0-alpha.0 We are attempting to do some release trickery in order to stabalize hex more quickly. It was observed that we only need the `hex` error types in the public API of downstream bitcoin crates that we want to stabalize. So we created a 1.0 that was just the parsing logci and error types. Depend on the new `hex 1.0.0-alpha.0` crate and re-export all its types and traits in the same spot as they were here. --- CHANGELOG.md | 4 + Cargo.toml | 8 +- src/error.rs | 272 +------------------------------- src/iter.rs | 432 +-------------------------------------------------- src/parse.rs | 146 +---------------- 5 files changed, 23 insertions(+), 839 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e56f1a..13a6074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.4.0 - 2025-02-24 + +- TODO: Write changelog. + # 0.3.0 - 2024-09-18 - Re-implement `HexWriter` [#113](https://github.com/rust-bitcoin/hex-conservative/pull/113) diff --git a/Cargo.toml b/Cargo.toml index e38b7a7..60efbd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hex-conservative" -version = "0.3.0" +version = "0.4.0" authors = ["Martin Habovštiak ", "Andrew Poelstra "] license = "CC0-1.0" repository = "https://github.com/rust-bitcoin/hex-conservative" @@ -22,11 +22,13 @@ members = ["fuzz"] [features] default = ["std"] -std = ["alloc"] -alloc = [] +std = ["alloc", "hex-stable/std"] +alloc = ["hex-stable/alloc"] [dependencies] arrayvec = { version = "0.7.2", default-features = false } +hex-stable = { package = "hex-conservative", version = "1.0.0-alpha.0", git = "https://github.com/tcharding/hex-conservative", branch = "02-24-release-1.0-alpha.0", default-features = false } + serde = { version = "1.0", default-features = false, optional = true } diff --git a/src/error.rs b/src/error.rs index c8f22df..d2a290b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,270 +2,8 @@ //! Error code for the `hex-conservative` crate. -use core::convert::Infallible; -use core::fmt; - -/// Formats error. -/// -/// If `std` feature is OFF appends error source (delimited by `: `). We do this because -/// `e.source()` is only available in std builds, without this macro the error source is lost for -/// no-std builds. -macro_rules! write_err { - ($writer:expr, $string:literal $(, $args:expr)*; $source:expr) => { - { - #[cfg(feature = "std")] - { - let _ = &$source; // Prevents clippy warnings. - write!($writer, $string $(, $args)*) - } - #[cfg(not(feature = "std"))] - { - write!($writer, concat!($string, ": {}") $(, $args)*, $source) - } - } - } -} - -/// Hex decoding error. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct HexToBytesError(pub(crate) ToBytesError); - -impl From for HexToBytesError { - fn from(never: Infallible) -> Self { match never {} } -} - -impl HexToBytesError { - /// Returns a [`ToBytesError`] from this [`HexToBytesError`]. - // Use clone instead of reference to give use maximum forward flexibility. - pub fn parse_error(&self) -> ToBytesError { self.0.clone() } -} - -impl fmt::Display for HexToBytesError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } -} - -#[cfg(feature = "std")] -impl std::error::Error for HexToBytesError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) } -} - -impl From for HexToBytesError { - #[inline] - fn from(e: InvalidCharError) -> Self { Self(e.into()) } -} - -impl From for HexToBytesError { - #[inline] - fn from(e: OddLengthStringError) -> Self { Self(e.into()) } -} - -/// Hex decoding error while parsing to a vector of bytes. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ToBytesError { - /// Non-hexadecimal character. - InvalidChar(InvalidCharError), - /// Purported hex string had odd length. - OddLengthString(OddLengthStringError), -} - -impl From for ToBytesError { - fn from(never: Infallible) -> Self { match never {} } -} - -impl fmt::Display for ToBytesError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use ToBytesError::*; - - match *self { - InvalidChar(ref e) => write_err!(f, "invalid char, failed to create bytes from hex"; e), - OddLengthString(ref e) => - write_err!(f, "odd length, failed to create bytes from hex"; e), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ToBytesError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use ToBytesError::*; - - match *self { - InvalidChar(ref e) => Some(e), - OddLengthString(ref e) => Some(e), - } - } -} - -impl From for ToBytesError { - #[inline] - fn from(e: InvalidCharError) -> Self { Self::InvalidChar(e) } -} - -impl From for ToBytesError { - #[inline] - fn from(e: OddLengthStringError) -> Self { Self::OddLengthString(e) } -} - -/// Invalid hex character. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct InvalidCharError { - pub(crate) invalid: u8, - pub(crate) pos: usize, -} - -impl From for InvalidCharError { - fn from(never: Infallible) -> Self { match never {} } -} - -impl InvalidCharError { - /// Returns the invalid character byte. - pub fn invalid_char(&self) -> u8 { self.invalid } - /// Returns the position of the invalid character byte. - pub fn pos(&self) -> usize { self.pos } -} - -impl fmt::Display for InvalidCharError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "invalid hex char {} at pos {}", self.invalid_char(), self.pos()) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for InvalidCharError {} - -/// Purported hex string had odd length. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct OddLengthStringError { - pub(crate) len: usize, -} - -impl From for OddLengthStringError { - fn from(never: Infallible) -> Self { match never {} } -} - -impl OddLengthStringError { - /// Returns the odd length of the input string. - pub fn length(&self) -> usize { self.len } -} - -impl fmt::Display for OddLengthStringError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "odd hex string length {}", self.length()) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for OddLengthStringError {} - -/// Hex decoding error. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct HexToArrayError(pub(crate) ToArrayError); - -impl From for HexToArrayError { - fn from(never: Infallible) -> Self { match never {} } -} - -impl HexToArrayError { - /// Returns a [`ToArrayError`] from this [`HexToArrayError`]. - // Use clone instead of reference to give use maximum forward flexibility. - pub fn parse_error(&self) -> ToArrayError { self.0.clone() } -} - -impl fmt::Display for HexToArrayError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } -} - -#[cfg(feature = "std")] -impl std::error::Error for HexToArrayError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) } -} - -impl From for HexToArrayError { - #[inline] - fn from(e: InvalidCharError) -> Self { Self(e.into()) } -} - -impl From for HexToArrayError { - #[inline] - fn from(e: InvalidLengthError) -> Self { Self(e.into()) } -} - -/// Hex decoding error while parsing a byte array. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ToArrayError { - /// Non-hexadecimal character. - InvalidChar(InvalidCharError), - /// Tried to parse fixed-length hash from a string with the wrong length. - InvalidLength(InvalidLengthError), -} - -impl From for ToArrayError { - fn from(never: Infallible) -> Self { match never {} } -} - -impl fmt::Display for ToArrayError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use ToArrayError::*; - - match *self { - InvalidChar(ref e) => write_err!(f, "failed to parse hex digit"; e), - InvalidLength(ref e) => write_err!(f, "failed to parse hex"; e), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ToArrayError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use ToArrayError::*; - - match *self { - InvalidChar(ref e) => Some(e), - InvalidLength(ref e) => Some(e), - } - } -} - -impl From for ToArrayError { - #[inline] - fn from(e: InvalidCharError) -> Self { Self::InvalidChar(e) } -} - -impl From for ToArrayError { - #[inline] - fn from(e: InvalidLengthError) -> Self { Self::InvalidLength(e) } -} - -/// Tried to parse fixed-length hash from a string with the wrong length. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct InvalidLengthError { - /// The expected length. - pub(crate) expected: usize, - /// The invalid length. - pub(crate) invalid: usize, -} - -impl From for InvalidLengthError { - fn from(never: Infallible) -> Self { match never {} } -} - -impl InvalidLengthError { - /// Returns the expected length. - pub fn expected_length(&self) -> usize { self.expected } - /// Returns the position of the invalid character byte. - pub fn invalid_length(&self) -> usize { self.invalid } -} - -impl fmt::Display for InvalidLengthError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "invilad hex string length {} (expected {})", - self.invalid_length(), - self.expected_length() - ) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for InvalidLengthError {} +#[doc(inline)] +pub use hex_stable::{ + HexToArrayError, HexToBytesError, InvalidCharError, InvalidLengthError, OddLengthStringError, + ToArrayError, ToBytesError, +}; diff --git a/src/iter.rs b/src/iter.rs index 3897937..8c36418 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -2,255 +2,20 @@ //! Iterator that converts hex to bytes. +// SPDX-License-Identifier: CC0-1.0 + +//! Iterator that converts hex to bytes. + use core::borrow::Borrow; -use core::convert::TryInto; use core::iter::FusedIterator; -use core::str; -#[cfg(feature = "std")] -use std::io; + +#[doc(inline)] +pub use hex_stable::iter::{HexSliceToBytesIter, HexToBytesIter}; #[cfg(all(feature = "alloc", not(feature = "std")))] use crate::alloc::vec::Vec; -use crate::error::{InvalidCharError, OddLengthStringError}; use crate::{Case, Table}; -/// Convenience alias for `HexToBytesIter>`. -pub type HexSliceToBytesIter<'a> = HexToBytesIter>; - -/// Iterator yielding bytes decoded from an iterator of pairs of hex digits. -#[derive(Debug)] -pub struct HexToBytesIter -where - I: Iterator, -{ - iter: I, - original_len: usize, -} - -impl<'a> HexToBytesIter> { - /// Constructs a new `HexToBytesIter` from a string slice. - /// - /// # Errors - /// - /// If the input string is of odd length. - #[inline] - pub fn new(s: &'a str) -> Result { - if s.len() % 2 != 0 { - Err(OddLengthStringError { len: s.len() }) - } else { - Ok(Self::new_unchecked(s)) - } - } - - pub(crate) fn new_unchecked(s: &'a str) -> Self { - Self::from_pairs(HexDigitsIter::new_unchecked(s.as_bytes())) - } - - /// Writes all the bytes yielded by this `HexToBytesIter` to the provided slice. - /// - /// Stops writing if this `HexToBytesIter` yields an `InvalidCharError`. - /// - /// # Panics - /// - /// Panics if the length of this `HexToBytesIter` is not equal to the length of the provided - /// slice. - pub(crate) fn drain_to_slice(self, buf: &mut [u8]) -> Result<(), InvalidCharError> { - assert_eq!(self.len(), buf.len()); - let mut ptr = buf.as_mut_ptr(); - for byte in self { - // SAFETY: for loop iterates `len` times, and `buf` has length `len` - unsafe { - core::ptr::write(ptr, byte?); - ptr = ptr.add(1); - } - } - Ok(()) - } - - /// Writes all the bytes yielded by this `HexToBytesIter` to a `Vec`. - /// - /// This is equivalent to the combinator chain `iter().map().collect()` but was found by - /// benchmarking to be faster. - #[cfg(any(test, feature = "std", feature = "alloc"))] - pub(crate) fn drain_to_vec(self) -> Result, InvalidCharError> { - let len = self.len(); - let mut ret = Vec::with_capacity(len); - let mut ptr = ret.as_mut_ptr(); - for byte in self { - // SAFETY: for loop iterates `len` times, and `ret` has a capacity of at least `len` - unsafe { - // docs: "`core::ptr::write` is appropriate for initializing uninitialized memory" - core::ptr::write(ptr, byte?); - ptr = ptr.add(1); - } - } - // SAFETY: `len` elements have been initialized, and `ret` has a capacity of at least `len` - unsafe { - ret.set_len(len); - } - Ok(ret) - } -} - -impl HexToBytesIter -where - I: Iterator + ExactSizeIterator, -{ - /// Constructs a custom hex decoding iterator from another iterator. - #[inline] - pub fn from_pairs(iter: I) -> Self { Self { original_len: iter.len(), iter } } -} - -impl Iterator for HexToBytesIter -where - I: Iterator + ExactSizeIterator, -{ - type Item = Result; - - #[inline] - fn next(&mut self) -> Option { - let [hi, lo] = self.iter.next()?; - Some(hex_chars_to_byte(hi, lo).map_err(|(c, is_high)| InvalidCharError { - invalid: c, - pos: if is_high { - (self.original_len - self.iter.len() - 1) * 2 - } else { - (self.original_len - self.iter.len() - 1) * 2 + 1 - }, - })) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } - - #[inline] - fn nth(&mut self, n: usize) -> Option { - let [hi, lo] = self.iter.nth(n)?; - Some(hex_chars_to_byte(hi, lo).map_err(|(c, is_high)| InvalidCharError { - invalid: c, - pos: if is_high { - (self.original_len - self.iter.len() - 1) * 2 - } else { - (self.original_len - self.iter.len() - 1) * 2 + 1 - }, - })) - } -} - -impl DoubleEndedIterator for HexToBytesIter -where - I: Iterator + DoubleEndedIterator + ExactSizeIterator, -{ - #[inline] - fn next_back(&mut self) -> Option { - let [hi, lo] = self.iter.next_back()?; - Some(hex_chars_to_byte(hi, lo).map_err(|(c, is_high)| InvalidCharError { - invalid: c, - pos: if is_high { self.iter.len() * 2 } else { self.iter.len() * 2 + 1 }, - })) - } - - #[inline] - fn nth_back(&mut self, n: usize) -> Option { - let [hi, lo] = self.iter.nth_back(n)?; - Some(hex_chars_to_byte(hi, lo).map_err(|(c, is_high)| InvalidCharError { - invalid: c, - pos: if is_high { self.iter.len() * 2 } else { self.iter.len() * 2 + 1 }, - })) - } -} - -impl ExactSizeIterator for HexToBytesIter where I: Iterator + ExactSizeIterator -{} - -impl FusedIterator for HexToBytesIter where - I: Iterator + ExactSizeIterator + FusedIterator -{ -} - -#[cfg(feature = "std")] -impl io::Read for HexToBytesIter -where - I: Iterator + ExactSizeIterator + FusedIterator, -{ - #[inline] - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let mut bytes_read = 0usize; - for dst in buf { - match self.next() { - Some(Ok(src)) => { - *dst = src; - bytes_read += 1; - } - _ => break, - } - } - Ok(bytes_read) - } -} - -/// An internal iterator returning hex digits from a string. -/// -/// Generally you shouldn't need to refer to this or bother with it and just use -/// [`HexToBytesIter::new`] consuming the returned value and use `HexSliceToBytesIter` if you need -/// to refer to the iterator in your types. -#[derive(Debug)] -pub struct HexDigitsIter<'a> { - // Invariant: the length of the chunks is 2. - // Technically, this is `iter::Map` but we can't use it because fn is anonymous. - // We can swap this for actual `ArrayChunks` once it's stable. - iter: core::slice::ChunksExact<'a, u8>, -} - -impl<'a> HexDigitsIter<'a> { - #[inline] - fn new_unchecked(digits: &'a [u8]) -> Self { Self { iter: digits.chunks_exact(2) } } -} - -impl Iterator for HexDigitsIter<'_> { - type Item = [u8; 2]; - - #[inline] - fn next(&mut self) -> Option { - self.iter.next().map(|digits| digits.try_into().expect("HexDigitsIter invariant")) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } - - #[inline] - fn nth(&mut self, n: usize) -> Option { - self.iter.nth(n).map(|digits| digits.try_into().expect("HexDigitsIter invariant")) - } -} - -impl DoubleEndedIterator for HexDigitsIter<'_> { - #[inline] - fn next_back(&mut self) -> Option { - self.iter.next_back().map(|digits| digits.try_into().expect("HexDigitsIter invariant")) - } - - #[inline] - fn nth_back(&mut self, n: usize) -> Option { - self.iter.nth_back(n).map(|digits| digits.try_into().expect("HexDigitsIter invariant")) - } -} - -impl ExactSizeIterator for HexDigitsIter<'_> {} - -impl core::iter::FusedIterator for HexDigitsIter<'_> {} - -/// `hi` and `lo` are bytes representing hex characters. -/// -/// Returns the valid byte or the invalid input byte and a bool indicating error for `hi` or `lo`. -fn hex_chars_to_byte(hi: u8, lo: u8) -> Result { - let hih = (hi as char).to_digit(16).ok_or((hi, true))?; - let loh = (lo as char).to_digit(16).ok_or((lo, false))?; - - let ret = (hih << 4) + loh; - Ok(ret as u8) -} - /// Iterator over bytes which encodes the bytes and yields hex characters. #[derive(Debug)] pub struct BytesToHexIter @@ -351,189 +116,6 @@ where mod tests { use super::*; - #[test] - fn encode_byte() { - assert_eq!(Table::LOWER.byte_to_chars(0x00), ['0', '0']); - assert_eq!(Table::LOWER.byte_to_chars(0x0a), ['0', 'a']); - assert_eq!(Table::LOWER.byte_to_chars(0xad), ['a', 'd']); - assert_eq!(Table::LOWER.byte_to_chars(0xff), ['f', 'f']); - - assert_eq!(Table::UPPER.byte_to_chars(0x00), ['0', '0']); - assert_eq!(Table::UPPER.byte_to_chars(0x0a), ['0', 'A']); - assert_eq!(Table::UPPER.byte_to_chars(0xad), ['A', 'D']); - assert_eq!(Table::UPPER.byte_to_chars(0xff), ['F', 'F']); - - let mut buf = [0u8; 2]; - assert_eq!(Table::LOWER.byte_to_str(&mut buf, 0x00), "00"); - assert_eq!(Table::LOWER.byte_to_str(&mut buf, 0x0a), "0a"); - assert_eq!(Table::LOWER.byte_to_str(&mut buf, 0xad), "ad"); - assert_eq!(Table::LOWER.byte_to_str(&mut buf, 0xff), "ff"); - - assert_eq!(Table::UPPER.byte_to_str(&mut buf, 0x00), "00"); - assert_eq!(Table::UPPER.byte_to_str(&mut buf, 0x0a), "0A"); - assert_eq!(Table::UPPER.byte_to_str(&mut buf, 0xad), "AD"); - assert_eq!(Table::UPPER.byte_to_str(&mut buf, 0xff), "FF"); - } - - #[test] - fn decode_iter_forward() { - let hex = "deadbeef"; - let bytes = [0xde, 0xad, 0xbe, 0xef]; - - for (i, b) in HexToBytesIter::new(hex).unwrap().enumerate() { - assert_eq!(b.unwrap(), bytes[i]); - } - - let mut iter = HexToBytesIter::new(hex).unwrap(); - for i in (0..=bytes.len()).rev() { - assert_eq!(iter.len(), i); - let _ = iter.next(); - } - } - - #[test] - fn decode_iter_backward() { - let hex = "deadbeef"; - let bytes = [0xef, 0xbe, 0xad, 0xde]; - - for (i, b) in HexToBytesIter::new(hex).unwrap().rev().enumerate() { - assert_eq!(b.unwrap(), bytes[i]); - } - - let mut iter = HexToBytesIter::new(hex).unwrap().rev(); - for i in (0..=bytes.len()).rev() { - assert_eq!(iter.len(), i); - let _ = iter.next(); - } - } - - #[test] - fn hex_to_digits_size_hint() { - let hex = "deadbeef"; - let iter = HexDigitsIter::new_unchecked(hex.as_bytes()); - // HexDigitsIter yields two digits at a time `[u8; 2]`. - assert_eq!(iter.size_hint(), (4, Some(4))); - } - - #[test] - fn hex_to_bytes_size_hint() { - let hex = "deadbeef"; - let iter = HexToBytesIter::new_unchecked(hex); - assert_eq!(iter.size_hint(), (4, Some(4))); - } - - #[test] - fn hex_to_bytes_slice_drain() { - let hex = "deadbeef"; - let want = [0xde, 0xad, 0xbe, 0xef]; - let iter = HexToBytesIter::new_unchecked(hex); - let mut got = [0u8; 4]; - iter.drain_to_slice(&mut got).unwrap(); - assert_eq!(got, want); - - let hex = ""; - let want = []; - let iter = HexToBytesIter::new_unchecked(hex); - let mut got = []; - iter.drain_to_slice(&mut got).unwrap(); - assert_eq!(got, want); - } - - #[test] - #[should_panic] - fn hex_to_bytes_slice_drain_panic_empty() { - let hex = "deadbeef"; - let iter = HexToBytesIter::new_unchecked(hex); - let mut got = []; - iter.drain_to_slice(&mut got).unwrap(); - } - - #[test] - #[should_panic] - fn hex_to_bytes_slice_drain_panic_too_small() { - let hex = "deadbeef"; - let iter = HexToBytesIter::new_unchecked(hex); - let mut got = [0u8; 3]; - iter.drain_to_slice(&mut got).unwrap(); - } - - #[test] - #[should_panic] - fn hex_to_bytes_slice_drain_panic_too_big() { - let hex = "deadbeef"; - let iter = HexToBytesIter::new_unchecked(hex); - let mut got = [0u8; 5]; - iter.drain_to_slice(&mut got).unwrap(); - } - - #[test] - fn hex_to_bytes_slice_drain_first_char_error() { - let hex = "geadbeef"; - let iter = HexToBytesIter::new_unchecked(hex); - let mut got = [0u8; 4]; - assert_eq!( - iter.drain_to_slice(&mut got).unwrap_err(), - InvalidCharError { invalid: b'g', pos: 0 } - ); - } - - #[test] - fn hex_to_bytes_slice_drain_middle_char_error() { - let hex = "deadgeef"; - let iter = HexToBytesIter::new_unchecked(hex); - let mut got = [0u8; 4]; - assert_eq!( - iter.drain_to_slice(&mut got).unwrap_err(), - InvalidCharError { invalid: b'g', pos: 4 } - ); - } - - #[test] - fn hex_to_bytes_slice_drain_end_char_error() { - let hex = "deadbeeg"; - let iter = HexToBytesIter::new_unchecked(hex); - let mut got = [0u8; 4]; - assert_eq!( - iter.drain_to_slice(&mut got).unwrap_err(), - InvalidCharError { invalid: b'g', pos: 7 } - ); - } - - #[test] - fn hex_to_bytes_vec_drain() { - let hex = "deadbeef"; - let want = [0xde, 0xad, 0xbe, 0xef]; - let iter = HexToBytesIter::new_unchecked(hex); - let got = iter.drain_to_vec().unwrap(); - assert_eq!(got, want); - - let hex = ""; - let iter = HexToBytesIter::new_unchecked(hex); - let got = iter.drain_to_vec().unwrap(); - assert!(got.is_empty()); - } - - #[test] - fn hex_to_bytes_vec_drain_first_char_error() { - let hex = "geadbeef"; - let iter = HexToBytesIter::new_unchecked(hex); - assert_eq!(iter.drain_to_vec().unwrap_err(), InvalidCharError { invalid: b'g', pos: 0 }); - } - - #[test] - fn hex_to_bytes_vec_drain_middle_char_error() { - let hex = "deadgeef"; - let iter = HexToBytesIter::new_unchecked(hex); - assert_eq!(iter.drain_to_vec().unwrap_err(), InvalidCharError { invalid: b'g', pos: 4 }); - } - - #[test] - fn hex_to_bytes_vec_drain_end_char_error() { - let hex = "deadbeeg"; - let iter = HexToBytesIter::new_unchecked(hex); - assert_eq!(iter.drain_to_vec().unwrap_err(), InvalidCharError { invalid: b'g', pos: 7 }); - } - #[test] fn encode_iter() { let bytes = [0xde, 0xad, 0xbe, 0xef]; diff --git a/src/parse.rs b/src/parse.rs index 2ef6807..0d943b5 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -2,147 +2,5 @@ //! Hex encoding and decoding. -use core::{fmt, str}; - -#[cfg(all(feature = "alloc", not(feature = "std")))] -use crate::alloc::vec::Vec; -use crate::error::InvalidLengthError; -use crate::iter::HexToBytesIter; - -#[rustfmt::skip] // Keep public re-exports separate. -pub use crate::error::{HexToBytesError, HexToArrayError}; - -/// Trait for objects that can be deserialized from hex strings. -pub trait FromHex: Sized + sealed::Sealed { - /// Error type returned while parsing hex string. - type Error: Sized + fmt::Debug + fmt::Display; - - /// Produces an object from a hex string. - fn from_hex(s: &str) -> Result; -} - -#[cfg(feature = "alloc")] -impl FromHex for Vec { - type Error = HexToBytesError; - - fn from_hex(s: &str) -> Result { - Ok(HexToBytesIter::new(s)?.drain_to_vec()?) - } -} - -impl FromHex for [u8; LEN] { - type Error = HexToArrayError; - - fn from_hex(s: &str) -> Result { - if s.len() == LEN * 2 { - let mut ret = [0u8; LEN]; - // checked above - HexToBytesIter::new_unchecked(s).drain_to_slice(&mut ret)?; - Ok(ret) - } else { - Err(InvalidLengthError { invalid: s.len(), expected: 2 * LEN }.into()) - } - } -} - -mod sealed { - /// Used to seal the `FromHex` trait. - pub trait Sealed {} - - #[cfg(feature = "alloc")] - impl Sealed for alloc::vec::Vec {} - - impl Sealed for [u8; LEN] {} -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[cfg(feature = "alloc")] - fn hex_error() { - use crate::error::{InvalidCharError, OddLengthStringError}; - - let oddlen = "0123456789abcdef0"; - let badchar1 = "Z123456789abcdef"; - let badchar2 = "012Y456789abcdeb"; - let badchar3 = "«23456789abcdef"; - - assert_eq!( - Vec::::from_hex(oddlen).unwrap_err(), - OddLengthStringError { len: 17 }.into() - ); - assert_eq!( - <[u8; 4]>::from_hex(oddlen).unwrap_err(), - InvalidLengthError { invalid: 17, expected: 8 }.into() - ); - assert_eq!( - Vec::::from_hex(badchar1).unwrap_err(), - InvalidCharError { pos: 0, invalid: b'Z' }.into() - ); - assert_eq!( - Vec::::from_hex(badchar2).unwrap_err(), - InvalidCharError { pos: 3, invalid: b'Y' }.into() - ); - assert_eq!( - Vec::::from_hex(badchar3).unwrap_err(), - InvalidCharError { pos: 0, invalid: 194 }.into() - ); - } - - #[test] - fn hex_error_position() { - use crate::error::InvalidCharError; - let badpos1 = "Z123456789abcdef"; - let badpos2 = "012Y456789abcdeb"; - let badpos3 = "0123456789abcdeZ"; - let badpos4 = "0123456789abYdef"; - - assert_eq!( - HexToBytesIter::new(badpos1).unwrap().next().unwrap().unwrap_err(), - InvalidCharError { pos: 0, invalid: b'Z' } - ); - assert_eq!( - HexToBytesIter::new(badpos2).unwrap().nth(1).unwrap().unwrap_err(), - InvalidCharError { pos: 3, invalid: b'Y' } - ); - assert_eq!( - HexToBytesIter::new(badpos3).unwrap().next_back().unwrap().unwrap_err(), - InvalidCharError { pos: 15, invalid: b'Z' } - ); - assert_eq!( - HexToBytesIter::new(badpos4).unwrap().nth_back(1).unwrap().unwrap_err(), - InvalidCharError { pos: 12, invalid: b'Y' } - ); - } - - #[test] - fn hex_to_array() { - let len_sixteen = "0123456789abcdef"; - assert!(<[u8; 8]>::from_hex(len_sixteen).is_ok()); - } - - #[test] - fn hex_to_array_error() { - let len_sixteen = "0123456789abcdef"; - assert_eq!( - <[u8; 4]>::from_hex(len_sixteen).unwrap_err(), - InvalidLengthError { invalid: 16, expected: 8 }.into() - ) - } - - #[test] - #[cfg(feature = "alloc")] - fn mixed_case() { - use crate::display::DisplayHex as _; - - let s = "DEADbeef0123"; - let want_lower = "deadbeef0123"; - let want_upper = "DEADBEEF0123"; - - let v = Vec::::from_hex(s).expect("valid hex"); - assert_eq!(format!("{:x}", v.as_hex()), want_lower); - assert_eq!(format!("{:X}", v.as_hex()), want_upper); - } -} +#[doc(inline)] +pub use hex_stable::{FromHex, HexToArrayError, HexToBytesError};