Skip to content

Commit

Permalink
Hide base58::Error internals
Browse files Browse the repository at this point in the history
As is convention here in `rust-bitcoin`, hide the `base58::Error`
internals by adding struct error types.
  • Loading branch information
tcharding committed Mar 20, 2024
1 parent 4f68e79 commit af49841
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 15 deletions.
79 changes: 68 additions & 11 deletions base58/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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),
}
}
}
Expand All @@ -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),
}
}
}
Expand All @@ -51,6 +50,64 @@ impl From<InvalidCharacterError> for Error {
fn from(e: InvalidCharacterError) -> Self { Self::Decode(e) }
}

impl From<IncorrectChecksumError> for Error {
#[inline]
fn from(e: IncorrectChecksumError) -> Self { Self::IncorrectChecksum(e) }
}

impl From<TooShortError> 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 {
Expand Down
10 changes: 6 additions & 4 deletions base58/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -93,7 +95,7 @@ pub fn decode(data: &str) -> Result<Vec<u8>, InvalidCharacterError> {
pub fn decode_check(data: &str) -> Result<Vec<u8>, Error> {
let mut ret: Vec<u8> = 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;

Expand All @@ -104,8 +106,8 @@ pub fn decode_check(data: &str) -> Result<Vec<u8>, 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);
Expand Down Expand Up @@ -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()));
}
}

0 comments on commit af49841

Please sign in to comment.