Adjust from_str in HexPrimitive to be no-alloc
What changed, and why it matters
This commit is a routine refactoring of how hex strings are parsed into Bitcoin data structures in the rust-bitcoin library. It replaces an allocation-based hex decoder with a streaming, no-allocation decoder so the code can run in environments without a heap allocator. There is no direct security bug being fixed; it is a feature-gate and performance/capability cleanup.
No security action required. Treat as normal code-quality/feature-enablement change. Reviewers may want to verify the streaming decoder correctly handles empty input, odd-length strings, and invalid hex characters, but the diff shows those errors are propagated unchanged.
Security signals we found
No security-relevant signal: refactoring only
Feature-gate relaxation (alloc no longer required for Header FromStr/Decodable)
Parser now uses bounded 4096-byte stack buffer instead of heap vector
Error type moved and re-exported; no semantic change to error handling
Evidence from the diff
The patch removes the alloc requirement from HexPrimitive::from_str by switching from hex::decode_to_vec to hex_unstable::HexToBytesIter feeding a fixed 4096-byte stack buffer into a Decoder. It also relaxes #[cfg(feature = "alloc")] gates on HeaderDecoder/HeaderDecoderError and moves ParsePrimitiveError into a hex_codec module that is gated only on feature = "hex". The API surface expands under no-features.txt because Header decoding traits are now available without alloc. No vulnerability, bounds-checking mistake, or cryptographic issue is visible in the diff.
Changed components
primitives/src/lib.rs (hex_codec module, HexPrimitive::from_str)primitives/src/block.rs (Header FromStr, HeaderDecoder feature gates)primitives/src/transaction.rs (ParseTransactionError import path)api/primitives/no-features.txt (expanded public API listings)Inspect captured patch +129 / −87
diff --git a/api/primitives/no-features.txt b/api/primitives/no-features.txt
index 332c7936..34a473a7 100644
--- a/api/primitives/no-features.txt
+++ b/api/primitives/no-features.txt
@@ -1,10 +1,13 @@
+#[non_exhaustive] pub enum bitcoin_primitives::block::HeaderDecoderError
impl bitcoin_consensus_encoding::decode::Decodable for bitcoin_primitives::BlockHash
impl bitcoin_consensus_encoding::decode::Decodable for bitcoin_primitives::TxMerkleNode
impl bitcoin_consensus_encoding::decode::Decodable for bitcoin_primitives::WitnessMerkleNode
+impl bitcoin_consensus_encoding::decode::Decodable for bitcoin_primitives::block::Header
impl bitcoin_consensus_encoding::decode::Decodable for bitcoin_primitives::block::Version
impl bitcoin_consensus_encoding::decode::Decodable for bitcoin_primitives::transaction::OutPoint
impl bitcoin_consensus_encoding::decode::Decodable for bitcoin_primitives::transaction::Version
impl bitcoin_consensus_encoding::decode::Decoder for bitcoin_primitives::block::BlockHashDecoder
+impl bitcoin_consensus_encoding::decode::Decoder for bitcoin_primitives::block::HeaderDecoder
impl bitcoin_consensus_encoding::decode::Decoder for bitcoin_primitives::block::VersionDecoder
impl bitcoin_consensus_encoding::decode::Decoder for bitcoin_primitives::transaction::OutPointDecoder
impl bitcoin_consensus_encoding::decode::Decoder for bitcoin_primitives::transaction::TxMerkleNodeDecoder
@@ -55,6 +58,7 @@ impl core::clone::Clone for bitcoin_primitives::WitnessMerkleNode
impl core::clone::Clone for bitcoin_primitives::Wtxid
impl core::clone::Clone for bitcoin_primitives::block::BlockHashDecoderError
impl core::clone::Clone for bitcoin_primitives::block::Header
+impl core::clone::Clone for bitcoin_primitives::block::HeaderDecoderError
impl core::clone::Clone for bitcoin_primitives::block::Version
impl core::clone::Clone for bitcoin_primitives::block::VersionDecoderError
impl core::clone::Clone for bitcoin_primitives::transaction::OutPoint
@@ -71,6 +75,7 @@ impl core::cmp::Eq for bitcoin_primitives::WitnessMerkleNode
impl core::cmp::Eq for bitcoin_primitives::Wtxid
impl core::cmp::Eq for bitcoin_primitives::block::BlockHashDecoderError
impl core::cmp::Eq for bitcoin_primitives::block::Header
+impl core::cmp::Eq for bitcoin_primitives::block::HeaderDecoderError
impl core::cmp::Eq for bitcoin_primitives::block::Version
impl core::cmp::Eq for bitcoin_primitives::block::VersionDecoderError
impl core::cmp::Eq for bitcoin_primitives::transaction::OutPoint
@@ -98,6 +103,7 @@ impl core::cmp::PartialEq for bitcoin_primitives::WitnessMerkleNode
impl core::cmp::PartialEq for bitcoin_primitives::Wtxid
impl core::cmp::PartialEq for bitcoin_primitives::block::BlockHashDecoderError
impl core::cmp::PartialEq for bitcoin_primitives::block::Header
+impl core::cmp::PartialEq for bitcoin_primitives::block::HeaderDecoderError
impl core::cmp::PartialEq for bitcoin_primitives::block::Version
impl core::cmp::PartialEq for bitcoin_primitives::block::VersionDecoderError
impl core::cmp::PartialEq for bitcoin_primitives::transaction::OutPoint
@@ -134,6 +140,7 @@ impl core::convert::From<&bitcoin_primitives::block::Header> for bitcoin_primiti
impl core::convert::From<bitcoin_primitives::block::Header> for bitcoin_primitives::BlockHash
impl core::convert::From<bitcoin_primitives::transaction::Version> for u32
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::BlockHashDecoderError
+impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::HeaderDecoderError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::VersionDecoderError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::transaction::TxMerkleNodeDecoderError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::transaction::VersionDecoderError
@@ -154,6 +161,7 @@ impl core::fmt::Debug for bitcoin_primitives::WitnessMerkleNode
impl core::fmt::Debug for bitcoin_primitives::Wtxid
impl core::fmt::Debug for bitcoin_primitives::block::BlockHashDecoderError
impl core::fmt::Debug for bitcoin_primitives::block::Header
+impl core::fmt::Debug for bitcoin_primitives::block::HeaderDecoderError
impl core::fmt::Debug for bitcoin_primitives::block::Version
impl core::fmt::Debug for bitcoin_primitives::block::VersionDecoderError
impl core::fmt::Debug for bitcoin_primitives::transaction::OutPoint
@@ -162,6 +170,7 @@ impl core::fmt::Debug for bitcoin_primitives::transaction::TxMerkleNodeDecoderEr
impl core::fmt::Debug for bitcoin_primitives::transaction::Version
impl core::fmt::Debug for bitcoin_primitives::transaction::VersionDecoderError
impl core::fmt::Display for bitcoin_primitives::block::BlockHashDecoderError
+impl core::fmt::Display for bitcoin_primitives::block::HeaderDecoderError
impl core::fmt::Display for bitcoin_primitives::block::Version
impl core::fmt::Display for bitcoin_primitives::block::VersionDecoderError
impl core::fmt::Display for bitcoin_primitives::transaction::OutPointDecoderError
@@ -206,6 +215,8 @@ impl core::marker::Freeze for bitcoin_primitives::Wtxid
impl core::marker::Freeze for bitcoin_primitives::block::BlockHashDecoder
impl core::marker::Freeze for bitcoin_primitives::block::BlockHashDecoderError
impl core::marker::Freeze for bitcoin_primitives::block::Header
+impl core::marker::Freeze for bitcoin_primitives::block::HeaderDecoder
+impl core::marker::Freeze for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Freeze for bitcoin_primitives::block::Version
impl core::marker::Freeze for bitcoin_primitives::block::VersionDecoder
impl core::marker::Freeze for bitcoin_primitives::block::VersionDecoderError
@@ -227,6 +238,8 @@ impl core::marker::Send for bitcoin_primitives::Wtxid
impl core::marker::Send for bitcoin_primitives::block::BlockHashDecoder
impl core::marker::Send for bitcoin_primitives::block::BlockHashDecoderError
impl core::marker::Send for bitcoin_primitives::block::Header
+impl core::marker::Send for bitcoin_primitives::block::HeaderDecoder
+impl core::marker::Send for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Send for bitcoin_primitives::block::Version
impl core::marker::Send for bitcoin_primitives::block::VersionDecoder
impl core::marker::Send for bitcoin_primitives::block::VersionDecoderError
@@ -247,6 +260,7 @@ impl core::marker::StructuralPartialEq for bitcoin_primitives::WitnessMerkleNode
impl core::marker::StructuralPartialEq for bitcoin_primitives::Wtxid
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::BlockHashDecoderError
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::Header
+impl core::marker::StructuralPartialEq for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::Version
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::VersionDecoderError
impl core::marker::StructuralPartialEq for bitcoin_primitives::transaction::OutPoint
@@ -264,6 +278,8 @@ impl core::marker::Sync for bitcoin_primitives::Wtxid
impl core::marker::Sync for bitcoin_primitives::block::BlockHashDecoder
impl core::marker::Sync for bitcoin_primitives::block::BlockHashDecoderError
impl core::marker::Sync for bitcoin_primitives::block::Header
+impl core::marker::Sync for bitcoin_primitives::block::HeaderDecoder
+impl core::marker::Sync for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Sync for bitcoin_primitives::block::Version
impl core::marker::Sync for bitcoin_primitives::block::VersionDecoder
impl core::marker::Sync for bitcoin_primitives::block::VersionDecoderError
@@ -285,6 +301,8 @@ impl core::marker::Unpin for bitcoin_primitives::Wtxid
impl core::marker::Unpin for bitcoin_primitives::block::BlockHashDecoder
impl core::marker::Unpin for bitcoin_primitives::block::BlockHashDecoderError
impl core::marker::Unpin for bitcoin_primitives::block::Header
+impl core::marker::Unpin for bitcoin_primitives::block::HeaderDecoder
+impl core::marker::Unpin for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Unpin for bitcoin_primitives::block::Version
impl core::marker::Unpin for bitcoin_primitives::block::VersionDecoder
impl core::marker::Unpin for bitcoin_primitives::block::VersionDecoderError
@@ -306,6 +324,8 @@ impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::Wtxid
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::BlockHashDecoder
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::BlockHashDecoderError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::Header
+impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::HeaderDecoder
+impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::HeaderDecoderError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::Version
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::VersionDecoder
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::VersionDecoderError
@@ -327,6 +347,8 @@ impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::Wtxid
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::BlockHashDecoder
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::BlockHashDecoderError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::Header
+impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::HeaderDecoder
+impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::HeaderDecoderError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::Version
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::VersionDecoder
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::VersionDecoderError
@@ -406,6 +428,12 @@ pub bitcoin_primitives::block::Header::nonce: u32
pub bitcoin_primitives::block::Header::prev_blockhash: bitcoin_primitives::BlockHash
pub bitcoin_primitives::block::Header::time: bitcoin_units::time::encapsulate::BlockTime
pub bitcoin_primitives::block::Header::version: bitcoin_primitives::block::Version
+pub bitcoin_primitives::block::HeaderDecoderError::Bits(bitcoin_units::pow::CompactTargetDecoderError)
+pub bitcoin_primitives::block::HeaderDecoderError::MerkleRoot(bitcoin_primitives::transaction::TxMerkleNodeDecoderError)
+pub bitcoin_primitives::block::HeaderDecoderError::Nonce(bitcoin_consensus_encoding::decode::decoders::UnexpectedEofError)
+pub bitcoin_primitives::block::HeaderDecoderError::PrevBlockhash(bitcoin_primitives::block::BlockHashDecoderError)
+pub bitcoin_primitives::block::HeaderDecoderError::Time(bitcoin_units::time::BlockTimeDecoderError)
+pub bitcoin_primitives::block::HeaderDecoderError::Version(bitcoin_primitives::block::VersionDecoderError)
pub bitcoin_primitives::transaction::OutPoint::txid: bitcoin_primitives::Txid
pub bitcoin_primitives::transaction::OutPoint::vout: u32
pub const bitcoin_primitives::BlockHash::GENESIS_PREVIOUS_BLOCK_HASH: Self
@@ -551,11 +579,19 @@ pub fn bitcoin_primitives::block::BlockHashEncoder<'e>::new(encoder: bitcoin_con
pub fn bitcoin_primitives::block::Header::block_hash(&self) -> bitcoin_primitives::BlockHash
pub fn bitcoin_primitives::block::Header::clone(&self) -> bitcoin_primitives::block::Header
pub fn bitcoin_primitives::block::Header::cmp(&self, other: &bitcoin_primitives::block::Header) -> core::cmp::Ordering
+pub fn bitcoin_primitives::block::Header::decoder() -> Self::Decoder
pub fn bitcoin_primitives::block::Header::encoder(&self) -> Self::Encoder
pub fn bitcoin_primitives::block::Header::eq(&self, other: &bitcoin_primitives::block::Header) -> bool
pub fn bitcoin_primitives::block::Header::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_primitives::block::Header::hash<__H: core::hash::Hasher>(&self, state: &mut __H)
pub fn bitcoin_primitives::block::Header::partial_cmp(&self, other: &bitcoin_primitives::block::Header) -> core::option::Option<core::cmp::Ordering>
+pub fn bitcoin_primitives::block::HeaderDecoder::end(self) -> core::result::Result<Self::Output, Self::Error>
+pub fn bitcoin_primitives::block::HeaderDecoder::push_bytes(&mut self, bytes: &mut &[u8]) -> core::result::Result<bool, Self::Error>
+pub fn bitcoin_primitives::block::HeaderDecoder::read_limit(&self) -> usize
+pub fn bitcoin_primitives::block::HeaderDecoderError::clone(&self) -> bitcoin_primitives::block::HeaderDecoderError
+pub fn bitcoin_primitives::block::HeaderDecoderError::eq(&self, other: &bitcoin_primitives::block::HeaderDecoderError) -> bool
+pub fn bitcoin_primitives::block::HeaderDecoderError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_primitives::block::HeaderDecoderError::from(never: core::convert::Infallible) -> Self
pub fn bitcoin_primitives::block::HeaderEncoder<'e>::advance(&mut self) -> bool
pub fn bitcoin_primitives::block::HeaderEncoder<'e>::current_chunk(&self) -> &[u8]
pub fn bitcoin_primitives::block::HeaderEncoder<'e>::len(&self) -> usize
@@ -654,6 +690,7 @@ pub struct bitcoin_primitives::block::BlockHashDecoder(_)
pub struct bitcoin_primitives::block::BlockHashDecoderError(_)
pub struct bitcoin_primitives::block::BlockHashEncoder<'e>(_, _)
pub struct bitcoin_primitives::block::Header
+pub struct bitcoin_primitives::block::HeaderDecoder(_)
pub struct bitcoin_primitives::block::HeaderEncoder<'e>(_, _)
pub struct bitcoin_primitives::block::Version(_)
pub struct bitcoin_primitives::block::VersionDecoder(_)
@@ -686,7 +723,10 @@ pub type bitcoin_primitives::WitnessMerkleNode::Decoder = bitcoin_primitives::ha
pub type bitcoin_primitives::WitnessMerkleNode::Encoder<'e> = bitcoin_primitives::hash_types::witness_merkle_node::WitnessMerkleNodeEncoder<'e>
pub type bitcoin_primitives::block::BlockHashDecoder::Error = bitcoin_primitives::block::BlockHashDecoderError
pub type bitcoin_primitives::block::BlockHashDecoder::Output = bitcoin_primitives::BlockHash
+pub type bitcoin_primitives::block::Header::Decoder = bitcoin_primitives::block::HeaderDecoder
pub type bitcoin_primitives::block::Header::Encoder<'e> = bitcoin_primitives::block::HeaderEncoder<'e>
+pub type bitcoin_primitives::block::HeaderDecoder::Error = bitcoin_primitives::block::HeaderDecoderError
+pub type bitcoin_primitives::block::HeaderDecoder::Output = bitcoin_primitives::block::Header
pub type bitcoin_primitives::block::Version::Decoder = bitcoin_primitives::block::VersionDecoder
pub type bitcoin_primitives::block::Version::Encoder<'e> = bitcoin_primitives::block::VersionEncoder<'e>
pub type bitcoin_primitives::block::VersionDecoder::Error = bitcoin_primitives::block::VersionDecoderError
diff --git a/primitives/src/block.rs b/primitives/src/block.rs
index 419607e7..ad863d1a 100644
--- a/primitives/src/block.rs
+++ b/primitives/src/block.rs
@@ -14,23 +14,22 @@ use core::marker::PhantomData;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
-use encoding::Encodable;
+use encoding::{Encodable, Decodable, Decoder, Decoder6};
#[cfg(feature = "hex")]
use encoding::EncodableByteIter;
#[cfg(feature = "alloc")]
use encoding::{
- CompactSizeEncoder, Decodable, Decoder, Decoder2, Decoder6, Encoder2, SliceEncoder, VecDecoder,
+ CompactSizeEncoder, Decoder2, Encoder2, SliceEncoder, VecDecoder,
};
use hashes::{sha256d, HashEngine as _};
use internals::write_err;
-#[cfg(feature = "alloc")]
use crate::pow::{CompactTargetDecoder, CompactTargetDecoderError};
+#[cfg(feature = "hex")]
+use crate::hex_codec::ParsePrimitiveError;
#[cfg(feature = "alloc")]
use crate::prelude::Vec;
-#[cfg(feature = "alloc")]
use crate::time::{BlockTimeDecoder, BlockTimeDecoderError};
-#[cfg(feature = "alloc")]
use crate::transaction::{TxMerkleNodeDecoder, TxMerkleNodeDecoderError};
use crate::{BlockTime, CompactTarget, TxMerkleNode};
#[cfg(feature = "alloc")]
@@ -508,7 +507,7 @@ impl Header {
}
}
-#[cfg(all(feature = "hex", feature = "alloc"))]
+#[cfg(feature = "hex")]
impl core::str::FromStr for Header {
type Err = ParseHeaderError;
@@ -556,20 +555,20 @@ impl fmt::Debug for Header {
}
/// An error that occurs during parsing of a [`Header`] from a hex string.
-#[cfg(all(feature = "hex", feature = "alloc"))]
-pub struct ParseHeaderError(crate::ParsePrimitiveError<Header>);
+#[cfg(feature = "hex")]
+pub struct ParseHeaderError(ParsePrimitiveError<Header>);
-#[cfg(all(feature = "hex", feature = "alloc"))]
+#[cfg(feature = "hex")]
impl fmt::Debug for ParseHeaderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) }
}
-#[cfg(all(feature = "hex", feature = "alloc"))]
+#[cfg(feature = "hex")]
impl fmt::Display for ParseHeaderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self, f) }
}
-#[cfg(all(feature = "hex", feature = "alloc", feature = "std"))]
+#[cfg(all(feature = "hex", feature = "std"))]
impl std::error::Error for ParseHeaderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
std::error::Error::source(&self.0)
@@ -605,7 +604,6 @@ impl Encodable for Header {
}
}
-#[cfg(feature = "alloc")]
type HeaderInnerDecoder = Decoder6<
VersionDecoder,
BlockHashDecoder,
@@ -616,10 +614,8 @@ type HeaderInnerDecoder = Decoder6<
>;
/// The decoder for the [`Header`] type.
-#[cfg(feature = "alloc")]
pub struct HeaderDecoder(HeaderInnerDecoder);
-#[cfg(feature = "alloc")]
impl HeaderDecoder {
fn from_inner(e: <HeaderInnerDecoder as Decoder>::Error) -> HeaderDecoderError {
match e {
@@ -633,7 +629,6 @@ impl HeaderDecoder {
}
}
-#[cfg(feature = "alloc")]
impl Decoder for HeaderDecoder {
type Output = Header;
type Error = HeaderDecoderError;
@@ -655,7 +650,6 @@ impl Decoder for HeaderDecoder {
fn read_limit(&self) -> usize { self.0.read_limit() }
}
-#[cfg(feature = "alloc")]
impl Decodable for Header {
type Decoder = HeaderDecoder;
fn decoder() -> Self::Decoder {
@@ -671,7 +665,6 @@ impl Decodable for Header {
}
/// An error consensus decoding a `Header`.
-#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum HeaderDecoderError {
@@ -689,12 +682,10 @@ pub enum HeaderDecoderError {
Nonce(encoding::UnexpectedEofError),
}
-#[cfg(feature = "alloc")]
impl From<Infallible> for HeaderDecoderError {
fn from(never: Infallible) -> Self { match never {} }
}
-#[cfg(feature = "alloc")]
impl fmt::Display for HeaderDecoderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
@@ -709,7 +700,6 @@ impl fmt::Display for HeaderDecoderError {
}
#[cfg(feature = "std")]
-#[cfg(feature = "alloc")]
impl std::error::Error for HeaderDecoderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs
index 2dca4630..f945afd5 100644
--- a/primitives/src/lib.rs
+++ b/primitives/src/lib.rs
@@ -115,80 +115,65 @@ pub(crate) fn compact_size_encode(value: usize) -> ArrayVec<u8, 9> {
ArrayVec::from_slice(encoder.current_chunk())
}
-#[cfg(all(feature = "hex", feature = "alloc"))]
-use core::{convert, fmt};
-
-#[cfg(all(feature = "hex", feature = "alloc"))]
-use encoding::{Decodable, Decoder};
-#[cfg(all(feature = "hex", feature = "alloc"))]
-use internals::write_err;
-
-/// An error type for errors that can occur during parsing of a `Decodable` type from hex.
-#[cfg(all(feature = "hex", feature = "alloc"))]
-enum ParsePrimitiveError<T: Decodable> {
- /// Tried to decode an odd length string
- OddLengthString(hex::error::OddLengthStringError),
- /// Encountered an invalid hex character
- InvalidChar(hex::error::InvalidCharError),
- /// A decode error from `consensus_encoding`
- Decode(<T::Decoder as Decoder>::Error),
-}
+#[cfg(feature = "hex")]
+pub(crate) mod hex_codec {
+ use core::fmt;
+
+ use encoding::{Decodable, Decoder, Encodable, EncodableByteIter};
+ #[cfg(feature = "alloc")]
+ use hex_unstable::{BytesToHexIter, Case};
+ use internals::write_err;
+
+ /// An error type for errors that can occur during parsing of a `Decodable` type from hex.
+ pub(crate) enum ParsePrimitiveError<T: Decodable> {
+ /// Tried to decode an odd length string
+ OddLengthString(hex_unstable::OddLengthStringError),
+ /// Encountered an invalid hex character
+ InvalidChar(hex_unstable::InvalidCharError),
+ /// A decode error from `consensus_encoding`
+ Decode(<T::Decoder as Decoder>::Error),
+ }
-#[cfg(all(feature = "hex", feature = "alloc"))]
-impl<T: Decodable> fmt::Debug for ParsePrimitiveError<T> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::OddLengthString(ref e) => write_err!(f, "odd length string"; e),
- Self::InvalidChar(ref e) => write_err!(f, "invalid character"; e),
- // Decoder error types don't have Debug, so we only provide this generic error
- Self::Decode(_) =>
- write!(f, "failure decoding hex string into {}", core::any::type_name::<T>()),
+ impl<T: Decodable> fmt::Debug for ParsePrimitiveError<T> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::OddLengthString(ref e) => write_err!(f, "odd length string"; e),
+ Self::InvalidChar(ref e) => write_err!(f, "invalid character"; e),
+ Self::Decode(_) => write!(f, "failure decoding hex string into {}", core::any::type_name::<T>()),
+ }
}
}
-}
-#[cfg(all(feature = "hex", feature = "alloc"))]
-impl<T: Decodable> fmt::Display for ParsePrimitiveError<T> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self, f) }
-}
+ impl<T: Decodable> fmt::Display for ParsePrimitiveError<T> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self, f) }
+ }
-#[cfg(all(feature = "hex", feature = "alloc"))]
-impl<T: Decodable> From<hex::DecodeVariableLengthBytesError> for ParsePrimitiveError<T> {
- fn from(dec_err: hex::DecodeVariableLengthBytesError) -> Self {
- use hex::DecodeVariableLengthBytesError as D;
+ impl<T: Decodable> From<hex_unstable::OddLengthStringError> for ParsePrimitiveError<T> {
+ fn from(err: hex_unstable::OddLengthStringError) -> Self { Self::OddLengthString(err) }
+ }
- match dec_err {
- D::InvalidChar(err) => Self::InvalidChar(err),
- D::OddLengthString(err) => Self::OddLengthString(err),
- }
+ impl<T: Decodable> From<hex_unstable::InvalidCharError> for ParsePrimitiveError<T> {
+ fn from(err: hex_unstable::InvalidCharError) -> Self { Self::InvalidChar(err) }
}
-}
-#[cfg(all(feature = "hex", feature = "alloc"))]
-impl<T: Decodable> From<convert::Infallible> for ParsePrimitiveError<T> {
- fn from(never: convert::Infallible) -> Self { match never {} }
-}
+ impl<T: Decodable> From<core::convert::Infallible> for ParsePrimitiveError<T> {
+ fn from(never: core::convert::Infallible) -> Self { match never {} }
+ }
-#[cfg(all(feature = "hex", feature = "alloc", feature = "std"))]
-impl<T: Decodable> std::error::Error for ParsePrimitiveError<T> {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match self {
- Self::OddLengthString(ref e) => Some(e),
- Self::InvalidChar(ref e) => Some(e),
- Self::Decode(_) => None,
+ #[cfg(feature = "std")]
+ impl<T: Decodable> std::error::Error for ParsePrimitiveError<T> {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::OddLengthString(ref e) => Some(e),
+ Self::InvalidChar(ref e) => Some(e),
+ Self::Decode(_) => None,
+ }
}
}
-}
-
-#[cfg(all(feature = "hex", feature = "alloc"))]
-pub(crate) mod hex_codec {
- use encoding::{Encodable, EncodableByteIter};
- use hex_unstable::{BytesToHexIter, Case};
-
- use super::{fmt, Decodable, ParsePrimitiveError};
/// Writes an Encodable object to the given formatter in the requested case.
#[inline]
+ #[cfg(feature = "alloc")]
fn hex_write_with_case<T: Encodable + Decodable>(
obj: &HexPrimitive<T>,
f: &mut fmt::Formatter,
@@ -226,22 +211,46 @@ pub(crate) mod hex_codec {
/// [`ParsePrimitiveError::OddLengthString`] if the input string is an odd length.
/// [`ParsePrimitiveError::Decode`] if an error occurs during decoding of the object.
pub(crate) fn from_str(s: &str) -> Result<T, ParsePrimitiveError<T>> {
- let bytes = hex::decode_to_vec(s).map_err(ParsePrimitiveError::from)?;
-
- encoding::decode_from_slice(&bytes).map_err(ParsePrimitiveError::Decode)
+ let iter = hex_unstable::HexToBytesIter::new(s)?;
+
+ let mut decoder = T::decoder();
+ let mut buffer = [0u8; 4096]; // 4MB is bigger than most decodables, reducing push_bytes calls.
+ let mut index = 0;
+
+ for result in iter {
+ if index == buffer.len() {
+ // Flush buffer to decoder
+ decoder
+ .push_bytes(&mut (buffer.as_slice()))
+ .map_err(ParsePrimitiveError::Decode)?;
+ index = 0;
+ }
+ buffer[index] = result?;
+ index += 1;
+ }
+
+ // Flush remaining buffer to decoder
+ decoder
+ .push_bytes(&mut (&buffer[..index]))
+ .map_err(ParsePrimitiveError::Decode)?;
+
+ decoder.end().map_err(ParsePrimitiveError::Decode)
}
}
+ #[cfg(feature = "alloc")]
impl<T: Encodable + Decodable> fmt::Display for HexPrimitive<'_, T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
}
+ #[cfg(feature = "alloc")]
impl<T: Encodable + Decodable> fmt::Debug for HexPrimitive<'_, T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
}
+ #[cfg(feature = "alloc")]
impl<T: Encodable + Decodable> fmt::LowerHex for HexPrimitive<'_, T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -249,6 +258,7 @@ pub(crate) mod hex_codec {
}
}
+ #[cfg(feature = "alloc")]
impl<T: Encodable + Decodable> fmt::UpperHex for HexPrimitive<'_, T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -263,7 +273,9 @@ mod tests {
use alloc::{format, string::ToString};
#[cfg(feature = "alloc")]
- use super::*;
+ use super::block;
+ #[cfg(feature = "hex")]
+ use crate::hex_codec::{self, ParsePrimitiveError};
#[test]
#[cfg(all(feature = "alloc", feature = "hex"))]
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index 890cd4e0..c8190919 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -34,6 +34,8 @@ use units::parse_int;
#[cfg(feature = "alloc")]
use crate::amount::{AmountDecoder, AmountEncoder};
+#[cfg(all(feature = "hex", feature = "alloc"))]
+use crate::hex_codec::ParsePrimitiveError;
#[cfg(feature = "alloc")]
use crate::locktime::absolute::{LockTimeDecoder, LockTimeDecoderError, LockTimeEncoder};
#[cfg(feature = "alloc")]
@@ -406,7 +408,7 @@ impl fmt::UpperHex for Transaction {
/// An error that occurs during parsing of a [`Transaction`] from a hex string.
#[cfg(all(feature = "hex", feature = "alloc"))]
-pub struct ParseTransactionError(crate::ParsePrimitiveError<Transaction>);
+pub struct ParseTransactionError(ParsePrimitiveError<Transaction>);
#[cfg(all(feature = "hex", feature = "alloc"))]
impl fmt::Debug for ParseTransactionError {
@@ -1815,8 +1817,6 @@ mod tests {
#[test]
#[cfg(feature = "hex")]
fn transaction_from_hex_str_error() {
- use crate::ParsePrimitiveError;
-
// OddLengthString error
let odd = "abc"; // 3 chars, odd length
let err = Transaction::from_str(odd).unwrap_err();
Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.