use core::fmt;
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
use crate::Check;
#[cfg(any(feature = "check", feature = "cb58"))]
use crate::CHECKSUM_LEN;
use crate::Alphabet;
#[allow(missing_debug_implementations)]
pub struct EncodeBuilder<'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,
}
pub trait EncodeTarget {
fn encode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize>;
}
impl<T: EncodeTarget + ?Sized> EncodeTarget for &mut T {
fn encode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize> {
T::encode_with(self, max_len, f)
}
}
#[cfg(feature = "alloc")]
impl EncodeTarget for Vec<u8> {
fn encode_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>> EncodeTarget for smallvec::SmallVec<A> {
fn encode_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>> EncodeTarget for tinyvec::ArrayVec<A> {
fn encode_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 EncodeTarget for tinyvec::SliceVec<'_, u8> {
fn encode_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>> EncodeTarget for tinyvec::TinyVec<A> {
fn encode_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 = "alloc")]
impl EncodeTarget for String {
fn encode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize> {
let mut output = core::mem::take(self).into_bytes();
let len = output.encode_with(max_len, f)?;
*self = String::from_utf8(output).unwrap();
Ok(len)
}
}
impl EncodeTarget for [u8] {
fn encode_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 EncodeTarget for str {
fn encode_with(
&mut self,
max_len: usize,
f: impl for<'a> FnOnce(&'a mut [u8]) -> Result<usize>,
) -> Result<usize> {
struct Guard<'a>(&'a mut [u8]);
impl Drop for Guard<'_> {
fn drop(&mut self) {
let mut index = 0;
loop {
match core::str::from_utf8(&self.0[index..]) {
Ok(_) => return,
Err(e) => {
index += e.valid_up_to();
if let Some(len) = e.error_len() {
for i in &mut self.0[index..index + len] {
*i = 0;
}
index += len;
} else {
for i in &mut self.0[index..] {
*i = 0;
}
index += self.0[index..].len();
}
}
}
}
}
}
let _ = max_len;
#[allow(unsafe_code)]
let guard = Guard(unsafe { self.as_bytes_mut() });
f(&mut *guard.0)
}
}
impl<'a, I: AsRef<[u8]>> EncodeBuilder<'a, I> {
pub fn new(input: I, alpha: &'a Alphabet) -> EncodeBuilder<'a, I> {
EncodeBuilder {
input,
alpha,
check: Check::Disabled,
}
}
pub(crate) fn from_input(input: I) -> EncodeBuilder<'static, I> {
EncodeBuilder {
input,
alpha: Alphabet::DEFAULT,
check: Check::Disabled,
}
}
pub fn with_alphabet(self, alpha: &'a Alphabet) -> EncodeBuilder<'a, I> {
EncodeBuilder { alpha, ..self }
}
#[cfg(feature = "check")]
pub fn with_check(self) -> EncodeBuilder<'a, I> {
let check = Check::Enabled(None);
EncodeBuilder { check, ..self }
}
#[cfg(feature = "check")]
pub fn with_check_version(self, expected_ver: u8) -> EncodeBuilder<'a, I> {
let check = Check::Enabled(Some(expected_ver));
EncodeBuilder { check, ..self }
}
#[cfg(feature = "cb58")]
pub fn as_cb58(self, expected_ver: Option<u8>) -> EncodeBuilder<'a, I> {
let check = Check::CB58(expected_ver);
EncodeBuilder { check, ..self }
}
#[cfg(feature = "alloc")]
pub fn into_string(self) -> String {
let mut output = String::new();
self.onto(&mut output).unwrap();
output
}
#[cfg(feature = "alloc")]
pub fn into_vec(self) -> Vec<u8> {
let mut output = Vec::new();
self.onto(&mut output).unwrap();
output
}
pub fn onto(self, mut output: impl EncodeTarget) -> Result<usize> {
let input = self.input.as_ref();
match self.check {
Check::Disabled => output.encode_with(max_encoded_len(input.len()), |output| {
encode_into(input, output, self.alpha)
}),
#[cfg(feature = "check")]
Check::Enabled(version) => {
let input_len = input.len() + CHECKSUM_LEN + version.map_or(0, |_| 1);
output.encode_with(max_encoded_len(input_len), |output| {
encode_check_into(self.input.as_ref(), output, self.alpha, version)
})
}
#[cfg(feature = "cb58")]
Check::CB58(version) => {
let input_len = input.len() + CHECKSUM_LEN + version.map_or(0, |_| 1);
output.encode_with(max_encoded_len(input_len), |output| {
encode_cb58_into(self.input.as_ref(), output, self.alpha, version)
})
}
}
}
}
fn max_encoded_len(len: usize) -> usize {
len + (len + 1) / 2
}
fn encode_into<'a, I>(input: I, output: &mut [u8], alpha: &Alphabet) -> Result<usize>
where
I: Clone + IntoIterator<Item = &'a u8>,
{
let mut index = 0;
for &val in input.clone() {
let mut carry = val as usize;
for byte in &mut output[..index] {
carry += (*byte as usize) << 8;
*byte = (carry % 58) as u8;
carry /= 58;
}
while carry > 0 {
if index == output.len() {
return Err(Error::BufferTooSmall);
}
output[index] = (carry % 58) as u8;
index += 1;
carry /= 58;
}
}
for _ in input.into_iter().take_while(|v| **v == 0) {
if index == output.len() {
return Err(Error::BufferTooSmall);
}
output[index] = 0;
index += 1;
}
for val in &mut output[..index] {
*val = alpha.encode[*val as usize];
}
output[..index].reverse();
Ok(index)
}
#[cfg(feature = "check")]
fn encode_check_into(
input: &[u8],
output: &mut [u8],
alpha: &Alphabet,
version: Option<u8>,
) -> Result<usize> {
use sha2::{Digest, Sha256};
let mut first_hash = Sha256::new();
if let Some(version) = version {
first_hash.update([version; 1]);
}
let first_hash = first_hash.chain_update(input).finalize();
let second_hash = Sha256::digest(first_hash);
let checksum = &second_hash[0..CHECKSUM_LEN];
encode_into(
version.iter().chain(input.iter()).chain(checksum.iter()),
output,
alpha,
)
}
#[cfg(feature = "cb58")]
fn encode_cb58_into(
input: &[u8],
output: &mut [u8],
alpha: &Alphabet,
version: Option<u8>,
) -> Result<usize> {
use sha2::{Digest, Sha256};
let mut hash = Sha256::new();
if let Some(version) = version {
hash.update([version; 1]);
}
let hash = hash.chain_update(input).finalize();
let checksum = &hash[hash.len() - CHECKSUM_LEN..];
encode_into(
version.iter().chain(input.iter()).chain(checksum.iter()),
output,
alpha,
)
}
#[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 encode base58 string into was too small"
),
}
}
}