use core::fmt;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crate::Check;
#[cfg(any(feature = "check", feature = "cb58"))]
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(any(feature = "check", feature = "cb58"))]
InvalidChecksum {
checksum: [u8; CHECKSUM_LEN],
expected_checksum: [u8; CHECKSUM_LEN],
},
#[cfg(any(feature = "check", feature = "cb58"))]
InvalidVersion {
ver: u8,
expected_ver: u8,
},
#[cfg(any(feature = "check", feature = "cb58"))]
NoChecksum,
}
pub trait DecodeTarget {
fn decode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize>;
}
impl<T: DecodeTarget + ?Sized> DecodeTarget for &mut T {
fn decode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize> {
T::decode_with(self, max_len, f)
}
}
#[cfg(feature = "alloc")]
impl DecodeTarget for Vec<u8> {
fn decode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize> {
let original = self.len();
self.resize(original + max_len, 0);
let len = f(&mut self[original..])?;
self.truncate(original + len);
Ok(len)
}
}
#[cfg(feature = "smallvec")]
impl<A: smallvec::Array<Item = u8>> DecodeTarget for smallvec::SmallVec<A> {
fn decode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize> {
let original = self.len();
self.resize(original + max_len, 0);
let len = f(&mut self[original..])?;
self.truncate(original + len);
Ok(len)
}
}
#[cfg(feature = "tinyvec")]
impl<A: tinyvec::Array<Item = u8>> DecodeTarget for tinyvec::ArrayVec<A> {
fn decode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize> {
let _ = max_len;
let original = self.len();
let len = f(self.grab_spare_slice_mut())?;
self.set_len(original + len);
Ok(len)
}
}
#[cfg(feature = "tinyvec")]
impl DecodeTarget for tinyvec::SliceVec<'_, u8> {
fn decode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize> {
let _ = max_len;
let original = self.len();
let len = f(self.grab_spare_slice_mut())?;
self.set_len(original + len);
Ok(len)
}
}
#[cfg(all(feature = "tinyvec", feature = "alloc"))]
impl<A: tinyvec::Array<Item = u8>> DecodeTarget for tinyvec::TinyVec<A> {
fn decode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize> {
let original = self.len();
self.resize(original + max_len, 0);
let len = f(&mut self[original..])?;
self.truncate(original + len);
Ok(len)
}
}
impl DecodeTarget for [u8] {
fn decode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize> {
let _ = max_len;
f(&mut *self)
}
}
impl<const N: usize> DecodeTarget for [u8; N] {
fn decode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize> {
let _ = max_len;
f(&mut *self)
}
}
impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> {
pub const fn new(input: I, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> {
DecodeBuilder {
input,
alpha,
check: Check::Disabled,
}
}
pub(crate) const fn from_input(input: I) -> DecodeBuilder<'static, I> {
DecodeBuilder {
input,
alpha: Alphabet::DEFAULT,
check: Check::Disabled,
}
}
pub const fn with_alphabet(mut self, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> {
self.alpha = alpha;
self
}
#[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 = "cb58")]
pub fn as_cb58(self, expected_ver: Option<u8>) -> DecodeBuilder<'a, I> {
let check = Check::CB58(expected_ver);
DecodeBuilder { check, ..self }
}
#[cfg(feature = "alloc")]
pub fn into_vec(self) -> Result<Vec<u8>> {
let mut output = Vec::new();
self.onto(&mut output)?;
Ok(output)
}
pub fn onto(self, mut output: impl DecodeTarget) -> Result<usize> {
let max_decoded_len = self.input.as_ref().len();
match self.check {
Check::Disabled => output.decode_with(max_decoded_len, |output| {
decode_into(self.input.as_ref(), output, self.alpha)
}),
#[cfg(feature = "check")]
Check::Enabled(expected_ver) => output.decode_with(max_decoded_len, |output| {
decode_check_into(self.input.as_ref(), output, self.alpha, expected_ver)
}),
#[cfg(feature = "cb58")]
Check::CB58(expected_ver) => output.decode_with(max_decoded_len, |output| {
decode_cb58_into(self.input.as_ref(), output, self.alpha, expected_ver)
}),
}
}
}
impl<'a, 'b> DecodeBuilder<'a, &'b [u8]> {
pub const fn into_array_const<const N: usize>(self) -> Result<[u8; N]> {
assert!(
matches!(self.check, Check::Disabled),
"checksums in const aren't supported (why are you using this API at runtime)",
);
decode_into_const(self.input, self.alpha)
}
pub const fn into_array_const_unwrap<const N: usize>(self) -> [u8; N] {
match self.into_array_const() {
Ok(result) => result,
Err(err) => err.unwrap_const(),
}
}
}
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 = "cb58")]
fn decode_cb58_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 hash = Sha256::digest(&output[0..checksum_index]);
let (_, checksum) = hash.split_at(hash.len() - 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,
})
}
}
const fn decode_into_const<const N: usize>(input: &[u8], alpha: &Alphabet) -> Result<[u8; N]> {
let mut output = [0u8; N];
let mut index = 0;
let zero = alpha.encode[0];
let mut i = 0;
while i < input.len() {
let c = input[i];
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,
});
}
let mut j = 0;
while j < index {
let byte = output[j];
val += (byte as usize) * 58;
output[j] = (val & 0xFF) as u8;
val >>= 8;
j += 1;
}
while val > 0 {
if index >= output.len() {
return Err(Error::BufferTooSmall);
}
output[index] = (val & 0xFF) as u8;
index += 1;
val >>= 8
}
i += 1;
}
let mut i = 0;
while i < input.len() && input[i] == zero {
if index >= output.len() {
return Err(Error::BufferTooSmall);
}
output[index] = 0;
index += 1;
i += 1;
}
let mut i = 0;
let n = index / 2;
while i < n {
let x = output[i];
output[i] = output[index - 1 - i];
output[index - 1 - i] = x;
i += 1;
}
Ok(output)
}
#[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(any(feature = "check", feature = "cb58"))]
Error::InvalidChecksum {
checksum,
expected_checksum,
} => write!(
f,
"invalid checksum, calculated checksum: '{:?}', expected checksum: {:?}",
checksum, expected_checksum
),
#[cfg(any(feature = "check", feature = "cb58"))]
Error::InvalidVersion { ver, expected_ver } => write!(
f,
"invalid version, payload version: '{:?}', expected version: {:?}",
ver, expected_ver
),
#[cfg(any(feature = "check", feature = "cb58"))]
Error::NoChecksum => write!(f, "provided string is too small to contain a checksum"),
}
}
}
impl Error {
pub const fn unwrap_const(self) -> ! {
match self {
Error::BufferTooSmall => {
panic!("buffer provided to decode base58 encoded string into was too small")
}
Error::InvalidCharacter { .. } => panic!("provided string contained invalid character"),
Error::NonAsciiCharacter { .. } => {
panic!("provided string contained non-ascii character")
}
#[cfg(any(feature = "check", feature = "cb58"))]
Error::InvalidChecksum { .. } => panic!("invalid checksum"),
#[cfg(any(feature = "check", feature = "cb58"))]
Error::InvalidVersion { .. } => panic!("invalid version"),
#[cfg(any(feature = "check", feature = "cb58"))]
Error::NoChecksum => panic!("provided string is too small to contain a checksum"),
}
}
}