use core::fmt;
#[cfg(feature = "alloc")]
use alloc::{vec, vec::Vec};
#[cfg(feature = "check")]
use crate::CHECKSUM_LEN;
#[allow(missing_debug_implementations)]
pub struct DecodeBuilder<'a, I: AsRef<[u8]>> {
input: I,
alpha: &'a [u8; 58],
check: bool,
expected_ver: Option<u8>,
}
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Error {
BufferTooSmall,
InvalidCharacter {
character: char,
index: usize,
},
NonAsciiCharacter {
index: usize,
},
#[cfg(feature = "check")]
#[cfg_attr(docsrs, doc(cfg(feature = "check")))]
InvalidChecksum {
checksum: [u8; CHECKSUM_LEN],
expected_checksum: [u8; CHECKSUM_LEN],
},
#[cfg(feature = "check")]
#[cfg_attr(docsrs, doc(cfg(feature = "check")))]
InvalidVersion {
ver: u8,
expected_ver: u8,
},
#[cfg(feature = "check")]
#[cfg_attr(docsrs, doc(cfg(feature = "check")))]
NoChecksum,
#[doc(hidden)]
__NonExhaustive,
}
impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> {
pub fn new(input: I, alpha: &'a [u8; 58]) -> DecodeBuilder<'a, I> {
DecodeBuilder {
input,
alpha,
check: false,
expected_ver: None,
}
}
pub fn with_alphabet(self, alpha: &[u8; 58]) -> DecodeBuilder<'_, I> {
DecodeBuilder {
input: self.input,
alpha,
check: self.check,
expected_ver: self.expected_ver,
}
}
#[cfg(feature = "check")]
#[cfg_attr(docsrs, doc(cfg(feature = "check")))]
pub fn with_check(mut self, expected_ver: Option<u8>) -> DecodeBuilder<'a, I> {
self.check = true;
self.expected_ver = expected_ver;
self
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
pub fn into_vec(self) -> Result<Vec<u8>> {
let mut output = vec![0; self.input.as_ref().len()];
self.into(&mut output).map(|len| {
output.truncate(len);
output
})
}
pub fn into<O: AsMut<[u8]>>(self, mut output: O) -> Result<usize> {
if self.check {
#[cfg(feature = "check")]
{
decode_check_into(
self.input.as_ref(),
output.as_mut(),
self.alpha,
self.expected_ver,
)
}
#[cfg(not(feature = "check"))]
{
unreachable!("This function requires 'check' feature")
}
} else {
decode_into(self.input.as_ref(), output.as_mut(), self.alpha)
}
}
}
fn decode_into(input: &[u8], output: &mut [u8], alpha: &[u8; 58]) -> Result<usize> {
let mut index = 0;
let zero = alpha[0];
let alpha = {
let mut rev = [0xFF; 128];
for (i, &c) in alpha.iter().enumerate() {
rev[c as usize] = i as u8;
}
rev
};
for (i, c) in input.iter().enumerate() {
if *c > 127 {
return Err(Error::NonAsciiCharacter { index: i });
}
let mut val = unsafe { *alpha.get_unchecked(*c as usize) as usize };
if val == 0xFF {
return Err(Error::InvalidCharacter {
character: *c as char,
index: i,
});
} else {
for byte in &mut output[..index] {
val += (*byte as usize) * 58;
*byte = (val & 0xFF) as u8;
val >>= 8;
}
while val > 0 {
let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?;
*byte = (val & 0xFF) as u8;
index += 1;
val >>= 8
}
}
}
for c in input {
if *c == zero {
let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?;
*byte = 0;
index += 1;
} else {
break;
}
}
output[..index].reverse();
Ok(index)
}
#[cfg(feature = "check")]
fn decode_check_into(
input: &[u8],
output: &mut [u8],
alpha: &[u8; 58],
expected_ver: Option<u8>,
) -> Result<usize> {
use sha2::{Digest, Sha256};
let decoded_len = decode_into(input, output, alpha)?;
if decoded_len < CHECKSUM_LEN {
return Err(Error::NoChecksum);
}
let checksum_index = decoded_len - CHECKSUM_LEN;
let expected_checksum = &output[checksum_index..decoded_len];
let first_hash = Sha256::digest(&output[0..checksum_index]);
let second_hash = Sha256::digest(&first_hash);
let (checksum, _) = second_hash.split_at(CHECKSUM_LEN);
if checksum == expected_checksum {
if let Some(ver) = expected_ver {
if output[0] == ver {
Ok(checksum_index)
} else {
Err(Error::InvalidVersion {
ver: output[0],
expected_ver: ver,
})
}
} else {
Ok(checksum_index)
}
} else {
let mut a: [u8; CHECKSUM_LEN] = Default::default();
a.copy_from_slice(&checksum[..]);
let mut b: [u8; CHECKSUM_LEN] = Default::default();
b.copy_from_slice(&expected_checksum[..]);
Err(Error::InvalidChecksum {
checksum: a,
expected_checksum: b,
})
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::BufferTooSmall => write!(
f,
"buffer provided to decode base58 encoded string into was too small"
),
Error::InvalidCharacter { character, index } => write!(
f,
"provided string contained invalid character {:?} at byte {}",
character, index
),
Error::NonAsciiCharacter { index } => write!(
f,
"provided string contained non-ascii character starting at byte {}",
index
),
#[cfg(feature = "check")]
Error::InvalidChecksum {
checksum,
expected_checksum,
} => write!(
f,
"invalid checksum, calculated checksum: '{:?}', expected checksum: {:?}",
checksum, expected_checksum
),
#[cfg(feature = "check")]
Error::InvalidVersion { ver, expected_ver } => write!(
f,
"invalid version, payload version: '{:?}', expected version: {:?}",
ver, expected_ver
),
#[cfg(feature = "check")]
Error::NoChecksum => write!(f, "provided string is too small to contain a checksum"),
Error::__NonExhaustive => unreachable!(),
}
}
}