use core::fmt;
#[cfg(feature = "alloc")]
use alloc::{vec, vec::Vec};
use crate::Check;
#[cfg(feature = "check")]
use crate::CHECKSUM_LEN;
use crate::Alphabet;
#[allow(missing_debug_implementations)]
pub struct DecodeBuilder<'a, I: AsRef<[u8]>> {
input: I,
alpha: &'a Alphabet,
check: Check,
}
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
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,
}
impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> {
pub fn new(input: I, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> {
DecodeBuilder {
input,
alpha,
check: Check::Disabled,
}
}
pub(crate) fn from_input(input: I) -> DecodeBuilder<'static, I> {
DecodeBuilder {
input,
alpha: Alphabet::DEFAULT,
check: Check::Disabled,
}
}
pub fn with_alphabet(self, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> {
DecodeBuilder { alpha, ..self }
}
#[cfg(feature = "check")]
#[cfg_attr(docsrs, doc(cfg(feature = "check")))]
pub fn with_check(self, expected_ver: Option<u8>) -> DecodeBuilder<'a, I> {
let check = Check::Enabled(expected_ver);
DecodeBuilder { check, ..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> {
match self.check {
Check::Disabled => decode_into(self.input.as_ref(), output.as_mut(), &self.alpha),
#[cfg(feature = "check")]
Check::Enabled(expected_ver) => decode_check_into(
self.input.as_ref(),
output.as_mut(),
&self.alpha,
expected_ver,
),
}
}
}
fn decode_into(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Result<usize> {
let mut index = 0;
let zero = alpha.encode[0];
for (i, c) in input.iter().enumerate() {
if *c > 127 {
return Err(Error::NonAsciiCharacter { index: i });
}
let mut val = alpha.decode[*c as usize] as usize;
if val == 0xFF {
return Err(Error::InvalidCharacter {
character: *c as char,
index: i,
});
}
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 _ in input.iter().take_while(|c| **c == zero) {
let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?;
*byte = 0;
index += 1;
}
output[..index].reverse();
Ok(index)
}
#[cfg(feature = "check")]
fn decode_check_into(
input: &[u8],
output: &mut [u8],
alpha: &Alphabet,
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"),
}
}
}