From a5b93cb1592eb24ff835cb128bf8dd42b72d68d2 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 8 May 2024 14:49:43 +1000 Subject: [PATCH] Flesh out hex unit parsing API Add to `units::parse` the complete suit of hex unit parsing functions: - remove prefix - assert without prefix - parse with or without prefix - parse with prefix - parse without prefix - parse prefix unchecked Refactor `bitcoin` to use the exact function we need, removing code duplication. This is a breaking change to `units`, it does however keep the current re-exports from the public, now empty, `bitcoin::error` module. --- bitcoin/src/blockdata/locktime/absolute.rs | 20 +- bitcoin/src/blockdata/transaction.rs | 22 +- bitcoin/src/error.rs | 129 +-------- bitcoin/src/pow.rs | 45 +--- units/src/locktime/absolute.rs | 6 +- units/src/parse.rs | 299 ++++++++++++++++++--- 6 files changed, 290 insertions(+), 231 deletions(-) diff --git a/bitcoin/src/blockdata/locktime/absolute.rs b/bitcoin/src/blockdata/locktime/absolute.rs index 548bf8e30..c111c4b6f 100644 --- a/bitcoin/src/blockdata/locktime/absolute.rs +++ b/bitcoin/src/blockdata/locktime/absolute.rs @@ -11,12 +11,11 @@ use core::fmt; use io::{BufRead, Write}; #[cfg(all(test, mutate))] use mutagen::mutate; -use units::parse; +use units::parse::{self, PrefixedHexError, UnprefixedHexError}; #[cfg(doc)] use crate::absolute; use crate::consensus::encode::{self, Decodable, Encodable}; -use crate::error::{ContainsPrefixError, MissingPrefixError, PrefixedHexError, UnprefixedHexError}; #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] @@ -92,26 +91,15 @@ impl LockTime { /// The number of bytes that the locktime contributes to the size of a transaction. pub const SIZE: usize = 4; // Serialized length of a u32. - /// Creates a `LockTime` from an prefixed hex string. + /// Creates a `LockTime` from a prefixed hex string. pub fn from_hex(s: &str) -> Result { - let stripped = if let Some(stripped) = s.strip_prefix("0x") { - stripped - } else if let Some(stripped) = s.strip_prefix("0X") { - stripped - } else { - return Err(MissingPrefixError::new(s).into()); - }; - - let lock_time = parse::hex_u32(stripped)?; + let lock_time = parse::hex_u32_prefixed(s)?; Ok(Self::from_consensus(lock_time)) } /// Creates a `LockTime` from an unprefixed hex string. pub fn from_unprefixed_hex(s: &str) -> Result { - if s.starts_with("0x") || s.starts_with("0X") { - return Err(ContainsPrefixError::new(s).into()); - } - let lock_time = parse::hex_u32(s)?; + let lock_time = parse::hex_u32_unprefixed(s)?; Ok(Self::from_consensus(lock_time)) } diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index cdf796f40..767410c46 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -15,7 +15,7 @@ use core::{cmp, fmt, str}; use hashes::{sha256d, Hash}; use internals::write_err; use io::{BufRead, Write}; -use units::parse; +use units::parse::{self, PrefixedHexError, UnprefixedHexError}; use super::Weight; use crate::blockdata::locktime::absolute::{self, Height, Time}; @@ -24,7 +24,6 @@ use crate::blockdata::script::{Script, ScriptBuf}; use crate::blockdata::witness::Witness; use crate::blockdata::FeeRate; use crate::consensus::{encode, Decodable, Encodable}; -use crate::error::{ContainsPrefixError, MissingPrefixError, PrefixedHexError, UnprefixedHexError}; use crate::internal_macros::{impl_consensus_encoding, impl_hashencode}; use crate::prelude::*; #[cfg(doc)] @@ -404,26 +403,15 @@ impl Sequence { self.is_relative_lock_time() & (self.0 & Sequence::LOCK_TYPE_MASK > 0) } - /// Creates a `Sequence` from an prefixed hex string. + /// Creates a `Sequence` from a prefixed hex string. pub fn from_hex(s: &str) -> Result { - let stripped = if let Some(stripped) = s.strip_prefix("0x") { - stripped - } else if let Some(stripped) = s.strip_prefix("0X") { - stripped - } else { - return Err(MissingPrefixError::new(s).into()); - }; - - let sequence = parse::hex_u32(stripped)?; - Ok(Self::from_consensus(sequence)) + let lock_time = parse::hex_u32_prefixed(s)?; + Ok(Self::from_consensus(lock_time)) } /// Creates a `Sequence` from an unprefixed hex string. pub fn from_unprefixed_hex(s: &str) -> Result { - if s.starts_with("0x") || s.starts_with("0X") { - return Err(ContainsPrefixError::new(s).into()); - } - let lock_time = parse::hex_u32(s)?; + let lock_time = parse::hex_u32_unprefixed(s)?; Ok(Self::from_consensus(lock_time)) } diff --git a/bitcoin/src/error.rs b/bitcoin/src/error.rs index da9d60744..bc70574fd 100644 --- a/bitcoin/src/error.rs +++ b/bitcoin/src/error.rs @@ -2,131 +2,8 @@ //! Contains error types and other error handling tools. -use core::fmt; - -use internals::write_err; - -use crate::prelude::*; - #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] -pub use crate::parse::ParseIntError; - -/// Error returned when parsing integer from an supposedly prefixed hex string for -/// a type that can be created infallibly from an integer. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum PrefixedHexError { - /// Hex string is missing prefix. - MissingPrefix(MissingPrefixError), - /// Error parsing integer from hex string. - ParseInt(ParseIntError), -} - -impl fmt::Display for PrefixedHexError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use PrefixedHexError::*; - - match *self { - MissingPrefix(ref e) => write_err!(f, "hex string is missing prefix"; e), - ParseInt(ref e) => write_err!(f, "prefixed hex string invalid int"; e), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for PrefixedHexError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use PrefixedHexError::*; - - match *self { - MissingPrefix(ref e) => Some(e), - ParseInt(ref e) => Some(e), - } - } -} - -impl From for PrefixedHexError { - fn from(e: MissingPrefixError) -> Self { Self::MissingPrefix(e) } -} - -impl From for PrefixedHexError { - fn from(e: ParseIntError) -> Self { Self::ParseInt(e) } -} - -/// Error returned when parsing integer from an supposedly un-prefixed hex string. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum UnprefixedHexError { - /// Hex string contains prefix. - ContainsPrefix(ContainsPrefixError), - /// Error parsing integer from string. - ParseInt(ParseIntError), -} - -impl fmt::Display for UnprefixedHexError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use UnprefixedHexError::*; - - match *self { - ContainsPrefix(ref e) => write_err!(f, "hex string is contains prefix"; e), - ParseInt(ref e) => write_err!(f, "hex string parse int"; e), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for UnprefixedHexError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use UnprefixedHexError::*; - - match *self { - ContainsPrefix(ref e) => Some(e), - ParseInt(ref e) => Some(e), - } - } -} - -impl From for UnprefixedHexError { - fn from(e: ContainsPrefixError) -> Self { Self::ContainsPrefix(e) } -} - -impl From for UnprefixedHexError { - fn from(e: ParseIntError) -> Self { Self::ParseInt(e) } -} - -/// Error when hex string is missing a prefix (e.g. 0x). -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct MissingPrefixError { - hex: String, -} - -impl MissingPrefixError { - pub(crate) fn new(s: &str) -> Self { Self { hex: s.into() } } -} - -impl fmt::Display for MissingPrefixError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "hex string is missing a prefix (e.g. 0x): {}", self.hex) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for MissingPrefixError {} - -/// Error when hex string contains a prefix (e.g. 0x). -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct ContainsPrefixError { - hex: String, -} - -impl ContainsPrefixError { - pub(crate) fn new(s: &str) -> Self { Self { hex: s.into() } } -} - -impl fmt::Display for ContainsPrefixError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "hex string contains a prefix: {}", self.hex) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ContainsPrefixError {} +pub use units::parse::{ + ContainsPrefixError, MissingPrefixError, ParseIntError, PrefixedHexError, UnprefixedHexError, +}; diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs index 9c5501031..09e135763 100644 --- a/bitcoin/src/pow.rs +++ b/bitcoin/src/pow.rs @@ -18,9 +18,7 @@ use crate::block::Header; use crate::blockdata::block::BlockHash; use crate::consensus::encode::{self, Decodable, Encodable}; use crate::consensus::Params; -use crate::error::{ - ContainsPrefixError, MissingPrefixError, ParseIntError, PrefixedHexError, UnprefixedHexError, -}; +use crate::error::{ParseIntError, PrefixedHexError, UnprefixedHexError}; /// Implement traits and methods shared by `Target` and `Work`. macro_rules! do_impl { @@ -350,27 +348,16 @@ do_impl!(Target); pub struct CompactTarget(u32); impl CompactTarget { - /// Creates a `CompactTarget` from an prefixed hex string. + /// Creates a `CompactTarget` from a prefixed hex string. pub fn from_hex(s: &str) -> Result { - let stripped = if let Some(stripped) = s.strip_prefix("0x") { - stripped - } else if let Some(stripped) = s.strip_prefix("0X") { - stripped - } else { - return Err(MissingPrefixError::new(s).into()); - }; - - let target = parse::hex_u32(stripped)?; + let target = parse::hex_u32_prefixed(s)?; Ok(Self::from_consensus(target)) } /// Creates a `CompactTarget` from an unprefixed hex string. pub fn from_unprefixed_hex(s: &str) -> Result { - if s.starts_with("0x") || s.starts_with("0X") { - return Err(ContainsPrefixError::new(s).into()); - } - let lock_time = parse::hex_u32(s)?; - Ok(Self::from_consensus(lock_time)) + let target = parse::hex_u32_unprefixed(s)?; + Ok(Self::from_consensus(target)) } /// Computes the [`CompactTarget`] from a difficulty adjustment. @@ -499,36 +486,28 @@ impl U256 { /// Creates a `U256` from a prefixed hex string. fn from_hex(s: &str) -> Result { - let stripped = if let Some(stripped) = s.strip_prefix("0x") { - stripped - } else if let Some(stripped) = s.strip_prefix("0X") { - stripped - } else { - return Err(MissingPrefixError::new(s).into()); - }; - Ok(U256::from_hex_internal(stripped)?) + let checked = parse::hex_remove_prefix(s)?; + Ok(U256::from_hex_internal(checked)?) } /// Creates a `U256` from an unprefixed hex string. fn from_unprefixed_hex(s: &str) -> Result { - if s.starts_with("0x") || s.starts_with("0X") { - return Err(ContainsPrefixError::new(s).into()); - } - Ok(U256::from_hex_internal(s)?) + let checked = parse::hex_check_unprefixed(s)?; + Ok(U256::from_hex_internal(checked)?) } // Caller to ensure `s` does not contain a prefix. fn from_hex_internal(s: &str) -> Result { let (high, low) = if s.len() < 32 { - let low = parse::hex_u128(s)?; + let low = parse::hex_u128_unchecked(s)?; (0, low) } else { let high_len = s.len() - 32; let high_s = &s[..high_len]; let low_s = &s[high_len..]; - let high = parse::hex_u128(high_s)?; - let low = parse::hex_u128(low_s)?; + let high = parse::hex_u128_unchecked(high_s)?; + let low = parse::hex_u128_unchecked(low_s)?; (high, low) }; diff --git a/units/src/locktime/absolute.rs b/units/src/locktime/absolute.rs index f786c0b6c..9b97a4be7 100644 --- a/units/src/locktime/absolute.rs +++ b/units/src/locktime/absolute.rs @@ -6,7 +6,9 @@ use core::fmt; use internals::write_err; -use crate::parse::{self, ParseIntError}; +use crate::parse::ParseIntError; +#[cfg(feature = "alloc")] +use crate::parse; #[cfg(feature = "alloc")] use crate::prelude::*; @@ -233,7 +235,7 @@ where S: AsRef + Into, F: FnOnce(u32) -> Result, { - let n = i64::from_str_radix(parse::strip_hex_prefix(s.as_ref()), 16) + let n = i64::from_str_radix(parse::hex_remove_optional_prefix(s.as_ref()), 16) .map_err(ParseError::invalid_int(s))?; let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?; f(n).map_err(ParseError::from).map_err(Into::into) diff --git a/units/src/parse.rs b/units/src/parse.rs index 967113f94..46b411f29 100644 --- a/units/src/parse.rs +++ b/units/src/parse.rs @@ -88,43 +88,6 @@ pub fn int + Into>(s: S) -> Result + Into>(s: S) -> Result { - let stripped = strip_hex_prefix(s.as_ref()); - u32::from_str_radix(stripped, 16).map_err(|error| ParseIntError { - input: s.into(), - bits: 32, - is_signed: false, - source: error, - }) -} - -/// Parses a `u128` from a hex string. -/// -/// Input string may or may not contain a `0x` prefix. -pub fn hex_u128 + Into>(s: S) -> Result { - let stripped = strip_hex_prefix(s.as_ref()); - u128::from_str_radix(stripped, 16).map_err(|error| ParseIntError { - input: s.into(), - bits: 128, - is_signed: false, - source: error, - }) -} - -/// Strips the hex prefix off `s` if one is present. -pub(crate) fn strip_hex_prefix(s: &str) -> &str { - if let Some(stripped) = s.strip_prefix("0x") { - stripped - } else if let Some(stripped) = s.strip_prefix("0X") { - stripped - } else { - s - } -} - /// Implements `TryFrom<$from> for $to` using `parse::int`, mapping the output using infallible /// conversion function `fn`. #[macro_export] @@ -197,6 +160,263 @@ macro_rules! impl_parse_str { } } +/// Removes the prefix `0x` (or `0X`) from hex string. +/// +/// # Errors +/// +/// If input string does not contain a prefix. +pub fn hex_remove_prefix(s: &str) -> Result<&str, PrefixedHexError> { + if let Some(checked) = s.strip_prefix("0x") { + Ok(checked) + } else if let Some(checked) = s.strip_prefix("0X") { + Ok(checked) + } else { + Err(MissingPrefixError::new(s.into()).into()) + } +} + +/// Checks hex string does not have a prefix `0x` (or `0X`). +/// +/// # Errors +/// +/// If input string contains a prefix. +pub fn hex_check_unprefixed(s: &str) -> Result<&str, UnprefixedHexError> { + if s.starts_with("0x") || s.starts_with("0X") { + return Err(ContainsPrefixError::new(s.into()).into()); + } + Ok(s) +} + +/// Parses a `u32` from a hex string. +/// +/// Input string may or may not contain a `0x` (or `0X`) prefix. +/// +/// # Errors +/// +/// If the input string is not a valid hex encoding of a `u32`. +pub fn hex_u32(s: &str) -> Result { + let unchecked = hex_remove_optional_prefix(s); + Ok(hex_u32_unchecked(unchecked)?) +} + +/// Parses a `u32` from a prefixed hex string. +/// +/// # Errors +/// +/// - If input string does not contain a `0x` (or `0X`) prefix. +/// - If input string is not a valid hex encoding of a `u32`. +pub fn hex_u32_prefixed(s: &str) -> Result { + let checked = hex_remove_prefix(s)?; + Ok(hex_u32_unchecked(checked)?) +} + +/// Parses a `u32` from an unprefixed hex string. +/// +/// # Errors +/// +/// - If input string contains a `0x` (or `0X`) prefix. +/// - If input string is not a valid hex encoding of a `u32`. +pub fn hex_u32_unprefixed(s: &str) -> Result { + let checked = hex_check_unprefixed(s)?; + Ok(hex_u32_unchecked(checked)?) +} + +/// Parses a `u32` from a hex string. +/// +/// # Errors +/// +/// - If input string is not a valid hex encoding of a `u32`. +/// - With `InvalidDigit` due to the `x` if there is a prefix. +pub fn hex_u32_unchecked(s: &str) -> Result { + u32::from_str_radix(s, 16).map_err(|error| ParseIntError { + input: s.into(), + bits: 32, + is_signed: false, + source: error, + }) +} + +/// Parses a `u128` from a hex string. +/// +/// Input string may or may not contain a `0x` (or `0X`) prefix. +/// +/// # Errors +/// +/// If the input string is not a valid hex encoding of a `u128`. +pub fn hex_u128(s: &str) -> Result { + let unchecked = hex_remove_optional_prefix(s); + Ok(hex_u128_unchecked(unchecked)?) +} + +/// Parses a `u128` from a hex string. +/// +/// # Errors +/// +/// - If input string does not contain a `0x` (or `0X`) prefix. +/// - If input string is not a valid hex encoding of a `u128`. +pub fn hex_u128_prefixed(s: &str) -> Result { + let checked = hex_remove_prefix(s)?; + Ok(hex_u128_unchecked(checked)?) +} + +/// Parses a `u128` from a hex string. +/// +/// # Errors +/// +/// - If input string contains a `0x` (or `0X`) prefix. +/// - If input string is not a valid hex encoding of a `u128`. +pub fn hex_u128_unprefixed(s: &str) -> Result { + let checked = hex_check_unprefixed(s)?; + Ok(hex_u128_unchecked(checked)?) +} + +/// Parses a `u128` from a hex string. +/// +/// # Errors +/// +/// - If input string is not a valid hex encoding of a `u128`. +/// - With `InvalidDigit` due to the `x` if there is a prefix. +pub fn hex_u128_unchecked(s: &str) -> Result { + u128::from_str_radix(s, 16).map_err(|error| ParseIntError { + input: s.into(), + bits: 128, + is_signed: false, + source: error, + }) +} + +/// Strips the hex prefix off `s` if one is present. +pub(crate) fn hex_remove_optional_prefix(s: &str) -> &str { + if let Some(stripped) = s.strip_prefix("0x") { + stripped + } else if let Some(stripped) = s.strip_prefix("0X") { + stripped + } else { + s + } +} + +/// Error returned when parsing integer from an supposedly prefixed hex string for +/// a type that can be created infallibly from an integer. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum PrefixedHexError { + /// Hex string is missing prefix. + MissingPrefix(MissingPrefixError), + /// Error parsing integer from hex string. + ParseInt(ParseIntError), +} + +impl fmt::Display for PrefixedHexError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use PrefixedHexError::*; + + match *self { + MissingPrefix(ref e) => write_err!(f, "hex string is missing prefix"; e), + ParseInt(ref e) => write_err!(f, "prefixed hex string invalid int"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for PrefixedHexError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use PrefixedHexError::*; + + match *self { + MissingPrefix(ref e) => Some(e), + ParseInt(ref e) => Some(e), + } + } +} + +impl From for PrefixedHexError { + fn from(e: MissingPrefixError) -> Self { Self::MissingPrefix(e) } +} + +impl From for PrefixedHexError { + fn from(e: ParseIntError) -> Self { Self::ParseInt(e) } +} + +/// Error returned when parsing integer from an supposedly un-prefixed hex string. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum UnprefixedHexError { + /// Hex string contains prefix. + ContainsPrefix(ContainsPrefixError), + /// Error parsing integer from string. + ParseInt(ParseIntError), +} + +impl fmt::Display for UnprefixedHexError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use UnprefixedHexError::*; + + match *self { + ContainsPrefix(ref e) => write_err!(f, "hex string is contains prefix"; e), + ParseInt(ref e) => write_err!(f, "hex string parse int"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for UnprefixedHexError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use UnprefixedHexError::*; + + match *self { + ContainsPrefix(ref e) => Some(e), + ParseInt(ref e) => Some(e), + } + } +} + +impl From for UnprefixedHexError { + fn from(e: ContainsPrefixError) -> Self { Self::ContainsPrefix(e) } +} + +impl From for UnprefixedHexError { + fn from(e: ParseIntError) -> Self { Self::ParseInt(e) } +} + +/// Error when hex string is missing a prefix (e.g. 0x). +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MissingPrefixError { + hex: String, +} + +impl MissingPrefixError { + /// Creates an error from the string with the missing prefix. + pub(crate) fn new(hex: String) -> Self { Self { hex } } +} + +impl fmt::Display for MissingPrefixError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "hex string is missing a prefix (e.g. 0x): {}", self.hex) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for MissingPrefixError {} + +/// Error when hex string contains a prefix (e.g. 0x). +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ContainsPrefixError { + hex: String, +} + +impl ContainsPrefixError { + /// Creates an error from the string that contains the prefix. + pub(crate) fn new(hex: String) -> Self { Self { hex } } +} + +impl fmt::Display for ContainsPrefixError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "hex string contains a prefix: {}", self.hex) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ContainsPrefixError {} + #[cfg(test)] mod tests { use super::*; @@ -228,4 +448,9 @@ mod tests { let got = hex_u128("deadbeef").expect("failed to parse non-prefixed hex"); assert_eq!(got, want); } + + #[test] + fn parse_u32_from_hex_unchecked_errors_on_prefix() { + assert!(hex_u32_unchecked("0xab").is_err()); + } }