tidecoin-primitives 0.102.0

Primitive types used by the rust-tidecoin ecosystem
Documentation
// SPDX-License-Identifier: CC0-1.0

//! Hex encoding utilities.
//!
//! Various types in primitives need to be rendered and parsed from
//! hexadecimal encodings. Since `consensus_encoding` doesn't provide
//! a convenient way to do this, this module provides utilities and
//! associated errors for encoding and decoding `Encodable` types
//! within the primitives crate.

use core::convert::Infallible;
use core::fmt;
use core::fmt::Write as _;

use encoding::{Decodable, Decoder, Encodable, EncodableByteIter};
use internals::hex::{BytesToHexIter, Case, HexToBytesIter, HexToBytesIterError};
use internals::write_err;

/// Hex encoding wrapper type for `Encodable` + `Decodable` types.
///
/// Implements `Display`, `Debug`, `LowerHex`, and `UpperHex` as well as an inherent `from_str`
/// method that returns `T`.
pub(crate) struct HexPrimitive<'a, T>(pub(crate) &'a T);

impl<'a, T: Encodable + Decodable> IntoIterator for &HexPrimitive<'a, T> {
    type Item = u8;
    type IntoIter = EncodableByteIter<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        EncodableByteIter::new(self.0)
    }
}

impl<T: Decodable> HexPrimitive<'_, T> {
    /// Parses a given string into an instance of the type `T`.
    ///
    /// Since `FromStr` would return an instance of `Self` and thus a &T, this function
    /// is implemented directly on the struct to return the owned instance of T.
    /// Other `FromStr` implementations can directly return the result of
    /// [`HexPrimitive::from_str`].
    ///
    /// # Errors
    ///
    /// * `OddLength` or `InvalidChar` if decoding the hex string to bytes fails.
    /// * `Decode` if consensus decoding the hex-decoded bytes fails.
    pub(crate) fn from_str(s: &str) -> Result<T, ParsePrimitiveError<T>> {
        let iter = 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(encoding::DecodeError::Parse)
                    .map_err(ParsePrimitiveError::Decode)?;
                index = 0;
            }
            buffer[index] = result?;
            index += 1;
        }

        // Flush remaining buffer to decoder
        decoder
            .push_bytes(&mut (&buffer[..index]))
            .map_err(encoding::DecodeError::Parse)
            .map_err(ParsePrimitiveError::Decode)?;

        decoder.end().map_err(encoding::DecodeError::Parse).map_err(ParsePrimitiveError::Decode)
    }
}

impl<T: Encodable> HexPrimitive<'_, T> {
    /// Writes an Encodable object to the given formatter in the requested case.
    #[inline]
    fn fmt_hex(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
        // Closure to write a given pad character out a given number of times.
        let write_pad = |f: &mut fmt::Formatter, pad_len: usize| -> fmt::Result {
            for _ in 0..pad_len {
                f.write_char(f.fill())?;
            }
            Ok(())
        };

        // Count hex chars
        let len = EncodableByteIter::new(self.0).count() * 2;
        let iter = BytesToHexIter::new(EncodableByteIter::new(self.0), case);

        let extra_len = if f.alternate() { 2 } else { 0 };
        let total_len = len + extra_len;

        // We pad for width, and truncate for precision, but not vice-versa
        let pad_width = f.width().unwrap_or(total_len);
        let trunc_width = f.precision().map_or(len, |v| v.saturating_sub(extra_len));

        let pad_diff = pad_width.saturating_sub(total_len);

        // Left padding
        let left_pad = match f.align() {
            Some(fmt::Alignment::Left) => 0,
            Some(fmt::Alignment::Center) => pad_diff / 2,
            Some(fmt::Alignment::Right) => pad_diff,
            None => 0,
        };
        write_pad(f, left_pad)?;

        // Alt characters
        if f.alternate() {
            f.write_str(match case {
                Case::Lower => "0x",
                Case::Upper => "0X",
            })?;
        }

        // Hex data
        for (i, ch) in iter.enumerate() {
            if i >= trunc_width {
                break;
            }
            f.write_char(ch)?;
        }

        // Right padding
        write_pad(f, pad_diff.saturating_sub(left_pad))?;

        Ok(())
    }
}

impl<T: Encodable> fmt::Display for HexPrimitive<'_, T> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::LowerHex::fmt(self, f)
    }
}

impl<T: Encodable> fmt::Debug for HexPrimitive<'_, T> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::LowerHex::fmt(self, f)
    }
}

impl<T: Encodable> fmt::LowerHex for HexPrimitive<'_, T> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.fmt_hex(f, Case::Lower)
    }
}

impl<T: Encodable> fmt::UpperHex for HexPrimitive<'_, T> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.fmt_hex(f, Case::Upper)
    }
}

/// 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(HexToBytesIterError),
    /// Encountered an invalid hex character.
    InvalidChar(HexToBytesIterError),
    /// A decode error from `consensus_encoding`
    Decode(encoding::DecodeError<<T::Decoder as encoding::Decoder>::Error>),
}

impl<T: Decodable> From<Infallible> for ParsePrimitiveError<T> {
    fn from(never: Infallible) -> Self {
        match never {}
    }
}

// Manual impls for Debug, Clone, PartialEq and Eq so that errors which wrap
// `ParsePrimitiveError` can properly derive the defaults.
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>())
            }
        }
    }
}

impl<T: Decodable> Clone for ParsePrimitiveError<T>
where
    <<T as Decodable>::Decoder as Decoder>::Error: Clone,
{
    fn clone(&self) -> Self {
        match self {
            Self::OddLengthString(ref e) => Self::OddLengthString(e.clone()),
            Self::InvalidChar(ref e) => Self::InvalidChar(e.clone()),
            Self::Decode(ref e) => Self::Decode(e.clone()),
        }
    }
}

impl<T: Decodable> PartialEq for ParsePrimitiveError<T>
where
    <<T as Decodable>::Decoder as Decoder>::Error: PartialEq,
{
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::OddLengthString(ref e1), Self::OddLengthString(ref e2)) => e1 == e2,
            (Self::InvalidChar(ref e1), Self::InvalidChar(ref e2)) => e1 == e2,
            (Self::Decode(ref e1), Self::Decode(ref e2)) => e1 == e2,
            _ => false,
        }
    }
}

impl<T: Decodable> Eq for ParsePrimitiveError<T> where
    <<T as Decodable>::Decoder as Decoder>::Error: PartialEq
{
}

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> From<HexToBytesIterError> for ParsePrimitiveError<T> {
    fn from(err: HexToBytesIterError) -> Self {
        match err {
            HexToBytesIterError::OddLengthString => Self::OddLengthString(err),
            HexToBytesIterError::InvalidChar { .. } => Self::InvalidChar(err),
        }
    }
}

#[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(test)]
mod tests {
    #[cfg(feature = "alloc")]
    use alloc::{format, string::ToString};

    #[cfg(feature = "alloc")]
    use super::*;
    use crate::block;

    #[test]
    #[cfg(feature = "alloc")]
    fn parse_primitive_error_display() {
        let odd: ParsePrimitiveError<block::Header> = HexPrimitive::from_str("0").unwrap_err();
        let invalid: ParsePrimitiveError<block::Header> = HexPrimitive::from_str("zz").unwrap_err();
        let decode: ParsePrimitiveError<block::Header> = HexPrimitive::from_str("00").unwrap_err();

        assert!(!odd.to_string().is_empty());
        assert!(!invalid.to_string().is_empty());
        assert!(!decode.to_string().is_empty());
    }

    #[test]
    #[cfg(feature = "std")]
    fn parse_primitive_error_source() {
        use std::error::Error as _;

        let odd: ParsePrimitiveError<block::Header> = HexPrimitive::from_str("0").unwrap_err();
        let invalid: ParsePrimitiveError<block::Header> = HexPrimitive::from_str("zz").unwrap_err();
        let decode: ParsePrimitiveError<block::Header> = HexPrimitive::from_str("00").unwrap_err();

        assert!(odd.source().is_some());
        assert!(invalid.source().is_some());
        assert!(decode.source().is_none());
    }

    #[test]
    #[cfg(feature = "alloc")]
    fn hex_primitive_iter_and_debug() {
        let header: block::Header =
            encoding::decode_from_slice(&[0u8; block::Header::SIZE]).expect("valid header");
        let hex = HexPrimitive(&header);

        assert_eq!((&hex).into_iter().next(), Some(0u8));
        assert!(!format!("{hex:?}").is_empty());
    }
}