From af49841433040967024b32270224ebee6a1266c4 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 21 Feb 2024 16:53:05 +1100 Subject: [PATCH] Hide base58::Error internals As is convention here in `rust-bitcoin`, hide the `base58::Error` internals by adding struct error types. --- base58/src/error.rs | 79 ++++++++++++++++++++++++++++++++++++++------- base58/src/lib.rs | 10 +++--- 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/base58/src/error.rs b/base58/src/error.rs index 9f420f62..584607ed 100644 --- a/base58/src/error.rs +++ b/base58/src/error.rs @@ -6,16 +6,16 @@ use core::fmt; use internals::write_err; -/// An error that might occur during base58 decoding. +/// An error occurred during base58 decoding (with checksum). #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum Error { /// Invalid character while decoding. Decode(InvalidCharacterError), - /// Checksum was not correct (expected, actual). - BadChecksum(u32, u32), - /// Checked data was less than 4 bytes. - TooShort(usize), + /// Checksum was not correct. + IncorrectChecksum(IncorrectChecksumError), + /// Checked data was too short. + TooShort(TooShortError), } internals::impl_from_infallible!(Error); @@ -26,9 +26,8 @@ impl fmt::Display for Error { match *self { Decode(ref e) => write_err!(f, "decode"; e), - BadChecksum(exp, actual) => - write!(f, "base58ck checksum {:#x} does not match expected {:#x}", actual, exp), - TooShort(_) => write!(f, "base58ck data not even long enough for a checksum"), + IncorrectChecksum(ref e) => write_err!(f, "incorrect checksum"; e), + TooShort(ref e) => write_err!(f, "too short"; e), } } } @@ -38,10 +37,10 @@ impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { use Error::*; - match self { + match *self { Decode(ref e) => Some(e), - BadChecksum(_, _) - | TooShort(_) => None, + IncorrectChecksum(ref e) => Some(e), + TooShort(ref e) => Some(e), } } } @@ -51,6 +50,64 @@ impl From for Error { fn from(e: InvalidCharacterError) -> Self { Self::Decode(e) } } +impl From for Error { + #[inline] + fn from(e: IncorrectChecksumError) -> Self { Self::IncorrectChecksum(e) } +} + +impl From for Error { + #[inline] + fn from(e: TooShortError) -> Self { Self::TooShort(e) } +} + +/// Checksum was not correct. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IncorrectChecksumError { + /// The incorrect checksum. + pub(super) incorrect: u32, + /// The expected checksum. + pub(super) expected: u32, +} + +impl IncorrectChecksumError { + /// Returns the incorrect checksum along with the expected checksum. + pub fn incorrect_checksum(&self) -> (u32, u32) { (self.incorrect, self.expected) } +} + +impl fmt::Display for IncorrectChecksumError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "base58 checksum {:#x} does not match expected {:#x}", + self.incorrect, self.expected + ) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for IncorrectChecksumError {} + +/// The decode base58 data was too short (require at least 4 bytes for checksum). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TooShortError { + /// The length of the decoded data. + pub(super) length: usize, +} + +impl TooShortError { + /// Returns the invalid base58 string length (require at least 4 bytes for checksum). + pub fn invalid_base58_length(&self) -> usize { self.length } +} + +impl fmt::Display for TooShortError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "base58 decoded data was not long enough, must be at least 4 byte: {}", self.length) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TooShortError {} + /// Found a invalid ASCII byte while decoding base58 string. #[derive(Debug, Clone, PartialEq, Eq)] pub struct InvalidCharacterError { diff --git a/base58/src/lib.rs b/base58/src/lib.rs index 32ef8276..b0323cd5 100644 --- a/base58/src/lib.rs +++ b/base58/src/lib.rs @@ -34,6 +34,8 @@ pub use std::{string::String, vec::Vec}; use hashes::{sha256d, Hash}; +use crate::error::{IncorrectChecksumError, TooShortError}; + #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] pub use self::error::{Error, InvalidCharacterError}; @@ -93,7 +95,7 @@ pub fn decode(data: &str) -> Result, InvalidCharacterError> { pub fn decode_check(data: &str) -> Result, Error> { let mut ret: Vec = decode(data)?; if ret.len() < 4 { - return Err(Error::TooShort(ret.len())); + return Err(TooShortError { length: ret.len() }.into()); } let check_start = ret.len() - 4; @@ -104,8 +106,8 @@ pub fn decode_check(data: &str) -> Result, Error> { let expected = u32::from_le_bytes(hash_check); let actual = u32::from_le_bytes(data_check); - if expected != actual { - return Err(Error::BadChecksum(expected, actual)); + if actual != expected { + return Err(IncorrectChecksumError { incorrect: actual, expected }.into()); } ret.truncate(check_start); @@ -282,6 +284,6 @@ mod tests { // Check that empty slice passes roundtrip. assert_eq!(decode_check(&encode_check(&[])), Ok(vec![])); // Check that `len > 4` is enforced. - assert_eq!(decode_check(&encode(&[1, 2, 3])), Err(Error::TooShort(3))); + assert_eq!(decode_check(&encode(&[1, 2, 3])), Err(TooShortError { length: 3 }.into())); } }