impl FromHexStr for structs with single u32 member
Adds new module `string` to be later converted to its own crate. The module currently contains the FromHexStr trait and an error type to be used for implementing hex parsing on types. This change also adds implementations of FromHexStr for types with a single u32 member such as `Sequence(pub u32)`. All structs that match the following regex have been given this implementation `\(u32\)` and `\(pub u32\)`. All implementations have associated unit tests matching all possible cases. NonStandardSighashType has been ommitted from this change as it is an error and should not be constructed using the methods added in this change. Adds parse::hex_u32 for future use to be made generic to allow different sizes of integers to be parsed from hex strings. The error type FromHexError implements required traits such as Display and std::error::Error
This commit is contained in:
parent
52fbb043b6
commit
e00dfa9806
|
@ -1403,6 +1403,7 @@ pub mod serde {
|
||||||
mod verification {
|
mod verification {
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
// Note regarding the `unwind` parameter: this defines how many iterations
|
// Note regarding the `unwind` parameter: this defines how many iterations
|
||||||
|
|
|
@ -19,6 +19,7 @@ use crate::error::ParseIntError;
|
||||||
use crate::io::{self, Read, Write};
|
use crate::io::{self, Read, Write};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::parse::{self, impl_parse_str_through_int};
|
use crate::parse::{self, impl_parse_str_through_int};
|
||||||
|
use crate::string::FromHexStr;
|
||||||
|
|
||||||
#[cfg(docsrs)]
|
#[cfg(docsrs)]
|
||||||
use crate::absolute;
|
use crate::absolute;
|
||||||
|
@ -86,6 +87,15 @@ impl fmt::Display for PackedLockTime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromHexStr for PackedLockTime {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
|
||||||
|
let packed_lock_time = crate::parse::hex_u32(s)?;
|
||||||
|
Ok(Self(packed_lock_time))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Encodable for PackedLockTime {
|
impl Encodable for PackedLockTime {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
|
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
|
||||||
|
@ -482,6 +492,16 @@ impl fmt::Display for Height {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromHexStr for Height {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
|
||||||
|
let height = crate::parse::hex_u32(s)?;
|
||||||
|
Self::from_consensus(height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
impl FromStr for Height {
|
impl FromStr for Height {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
|
@ -565,6 +585,15 @@ impl fmt::Display for Time {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromHexStr for Time {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
|
||||||
|
let time = crate::parse::hex_u32(s)?;
|
||||||
|
Time::from_consensus(time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for Time {
|
impl FromStr for Time {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
|
@ -750,4 +779,64 @@ mod tests {
|
||||||
let got = format!("{:#}", n);
|
let got = format!("{:#}", n);
|
||||||
assert_eq!(got, "block-height 100");
|
assert_eq!(got, "block-height 100");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn time_from_str_hex_happy_path() {
|
||||||
|
let actual = Time::from_hex_str("0x6289C350").unwrap();
|
||||||
|
let expected = Time::from_consensus(0x6289C350).unwrap();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn time_from_str_hex_no_prefix_happy_path() {
|
||||||
|
let time = Time::from_hex_str_no_prefix("6289C350").unwrap();
|
||||||
|
assert_eq!(time, Time(0x6289C350));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn time_from_str_hex_invalid_hex_should_err() {
|
||||||
|
let hex = "0xzb93";
|
||||||
|
let result = Time::from_hex_str(hex);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn packed_lock_time_from_str_hex_happy_path() {
|
||||||
|
let actual = PackedLockTime::from_hex_str("0xBA70D").unwrap();
|
||||||
|
let expected = PackedLockTime(0xBA70D);
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn packed_lock_time_from_str_hex_no_prefix_happy_path() {
|
||||||
|
let lock_time = PackedLockTime::from_hex_str_no_prefix("BA70D").unwrap();
|
||||||
|
assert_eq!(lock_time, PackedLockTime(0xBA70D));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn packed_lock_time_from_str_hex_invalid_hex_should_ergr() {
|
||||||
|
let hex = "0xzb93";
|
||||||
|
let result = PackedLockTime::from_hex_str(hex);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn height_from_str_hex_happy_path() {
|
||||||
|
let actual = Height::from_hex_str("0xBA70D").unwrap();
|
||||||
|
let expected = Height(0xBA70D);
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn height_from_str_hex_no_prefix_happy_path() {
|
||||||
|
let height = Height::from_hex_str_no_prefix("BA70D").unwrap();
|
||||||
|
assert_eq!(height, Height(0xBA70D));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn height_from_str_hex_invalid_hex_should_err() {
|
||||||
|
let hex = "0xzb93";
|
||||||
|
let result = Height::from_hex_str(hex);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::io;
|
use crate::io;
|
||||||
|
use crate::string::FromHexStr;
|
||||||
use core::{fmt, str, default::Default};
|
use core::{fmt, str, default::Default};
|
||||||
use core::convert::TryFrom;
|
use core::convert::TryFrom;
|
||||||
|
|
||||||
|
@ -404,6 +405,15 @@ impl Sequence {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromHexStr for Sequence {
|
||||||
|
type Error = crate::parse::ParseIntError;
|
||||||
|
|
||||||
|
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
|
||||||
|
let sequence = crate::parse::hex_u32(s)?;
|
||||||
|
Ok(Self::from_consensus(sequence))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Sequence {
|
impl Default for Sequence {
|
||||||
/// The default value of sequence is 0xffffffff.
|
/// The default value of sequence is 0xffffffff.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -1446,6 +1456,25 @@ mod tests {
|
||||||
assert!(unit_time_lock.is_rbf());
|
assert!(unit_time_lock.is_rbf());
|
||||||
assert!(!lock_time_disabled.is_relative_lock_time());
|
assert!(!lock_time_disabled.is_relative_lock_time());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sequence_from_str_hex_happy_path() {
|
||||||
|
let sequence = Sequence::from_hex_str("0xFFFFFFFF").unwrap();
|
||||||
|
assert_eq!(sequence, Sequence::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sequence_from_str_hex_no_prefix_happy_path() {
|
||||||
|
let sequence = Sequence::from_hex_str_no_prefix("FFFFFFFF").unwrap();
|
||||||
|
assert_eq!(sequence, Sequence::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sequence_from_str_hex_invalid_hex_should_err() {
|
||||||
|
let hex = "0xzb93";
|
||||||
|
let result = Sequence::from_hex_str(hex);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(bench)]
|
#[cfg(bench)]
|
||||||
|
|
|
@ -107,6 +107,7 @@ pub mod pow;
|
||||||
pub mod psbt;
|
pub mod psbt;
|
||||||
pub mod sighash;
|
pub mod sighash;
|
||||||
pub mod sign_message;
|
pub mod sign_message;
|
||||||
|
pub mod string;
|
||||||
pub mod taproot;
|
pub mod taproot;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,15 @@ pub(crate) fn int<T: Integer, S: AsRef<str> + Into<String>>(s: S) -> Result<T, P
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn hex_u32<S: AsRef<str> + Into<String>>(s: S) -> Result<u32, ParseIntError> {
|
||||||
|
u32::from_str_radix(s.as_ref(), 16).map_err(|error| ParseIntError {
|
||||||
|
input: s.into(),
|
||||||
|
bits: u8::try_from(core::mem::size_of::<u32>() * 8).expect("max is 32 bits for u32"),
|
||||||
|
is_signed: u32::try_from(-1i8).is_ok(),
|
||||||
|
source: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl_std_error!(ParseIntError, source);
|
impl_std_error!(ParseIntError, source);
|
||||||
|
|
||||||
/// Implements `TryFrom<$from> for $to` using `parse::int`, mapping the output using `fn`
|
/// Implements `TryFrom<$from> for $to` using `parse::int`, mapping the output using `fn`
|
||||||
|
|
|
@ -15,6 +15,8 @@ use crate::consensus::encode::{self, Decodable, Encodable};
|
||||||
use crate::consensus::Params;
|
use crate::consensus::Params;
|
||||||
use crate::hash_types::BlockHash;
|
use crate::hash_types::BlockHash;
|
||||||
use crate::io::{self, Read, Write};
|
use crate::io::{self, Read, Write};
|
||||||
|
use crate::prelude::String;
|
||||||
|
use crate::string::FromHexStr;
|
||||||
|
|
||||||
/// Implements $int * $ty. Requires `u64::from($int)`.
|
/// Implements $int * $ty. Requires `u64::from($int)`.
|
||||||
macro_rules! impl_int_mul {
|
macro_rules! impl_int_mul {
|
||||||
|
@ -283,6 +285,15 @@ impl From<CompactTarget> for Target {
|
||||||
fn from(c: CompactTarget) -> Self { Target::from_compact(c) }
|
fn from(c: CompactTarget) -> Self { Target::from_compact(c) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromHexStr for CompactTarget {
|
||||||
|
type Error = crate::parse::ParseIntError;
|
||||||
|
|
||||||
|
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
|
||||||
|
let compact_target = crate::parse::hex_u32(s)?;
|
||||||
|
Ok(Self::from_consensus(compact_target))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Encodable for CompactTarget {
|
impl Encodable for CompactTarget {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
|
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
|
||||||
|
@ -1403,6 +1414,27 @@ mod tests {
|
||||||
.is_err()); // invalid length
|
.is_err()); // invalid length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compact_target_from_hex_str_happy_path() {
|
||||||
|
let actual = CompactTarget::from_hex_str("0x01003456").unwrap();
|
||||||
|
let expected = CompactTarget(0x01003456);
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compact_target_from_hex_str_no_prefix_happy_path() {
|
||||||
|
let actual = CompactTarget::from_hex_str_no_prefix("01003456").unwrap();
|
||||||
|
let expected = CompactTarget(0x01003456);
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compact_target_from_hex_invalid_hex_should_err() {
|
||||||
|
let hex = "0xzbf9";
|
||||||
|
let result = CompactTarget::from_hex_str(hex);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn target_from_compact() {
|
fn target_from_compact() {
|
||||||
// (nBits, target)
|
// (nBits, target)
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
//! Bitcoin string parsing utilities.
|
||||||
|
//!
|
||||||
|
//! This module provides utility types and traits
|
||||||
|
//! to support handling and parsing strings within `rust-bitcoin`.
|
||||||
|
|
||||||
|
use core::fmt;
|
||||||
|
|
||||||
|
use bitcoin_internals::write_err;
|
||||||
|
|
||||||
|
use crate::prelude::String;
|
||||||
|
|
||||||
|
/// Trait that allows types to be initialized from hex strings
|
||||||
|
pub trait FromHexStr: Sized {
|
||||||
|
/// An error occurred while parsing the hex string.
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// Parses provided string as hex requiring 0x prefix.
|
||||||
|
///
|
||||||
|
/// This is intended for user-supplied inputs or already-existing protocols in which 0x prefix is used.
|
||||||
|
fn from_hex_str<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, FromHexError<Self::Error>> {
|
||||||
|
if !s.as_ref().starts_with("0x") {
|
||||||
|
Err(FromHexError::MissingPrefix(s.into()))
|
||||||
|
} else {
|
||||||
|
Ok(Self::from_hex_str_no_prefix(s.as_ref().trim_start_matches("0x"))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses provided string as hex without requiring 0x prefix.
|
||||||
|
///
|
||||||
|
/// This is **not** recommended for user-supplied inputs because of possible confusion with decimals.
|
||||||
|
/// It should be only used for existing protocols which always encode values as hex without 0x prefix.
|
||||||
|
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hex parsing error
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
|
pub enum FromHexError<E> {
|
||||||
|
/// The input was not a valid hex string, contains the error that occurred while parsing.
|
||||||
|
ParseHex(E),
|
||||||
|
/// The input is missing `0x` prefix, contains the invalid input.
|
||||||
|
MissingPrefix(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> From<E> for FromHexError<E> {
|
||||||
|
fn from(e: E) -> Self { FromHexError::ParseHex(e) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: fmt::Display> fmt::Display for FromHexError<E>
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use self::FromHexError::*;
|
||||||
|
|
||||||
|
match *self {
|
||||||
|
ParseHex(ref e) => write_err!(f, "failed to parse hex string"; e),
|
||||||
|
MissingPrefix(ref value) => write_err!(f, "the input value `{}` is missing the `0x` prefix", value; self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||||
|
impl<E> std::error::Error for FromHexError<E>
|
||||||
|
where
|
||||||
|
E: std::error::Error + 'static,
|
||||||
|
{
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
use self::FromHexError::*;
|
||||||
|
|
||||||
|
match *self {
|
||||||
|
ParseHex(ref e) => Some(e),
|
||||||
|
MissingPrefix(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue