Skip to content

Commit

Permalink
decode: Add const-compatible decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
joncinque committed Feb 29, 2024
1 parent 2b0d73b commit e65bfa7
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 0 deletions.
117 changes: 117 additions & 0 deletions src/decode_const.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//! Functions for decoding Base58 encoded strings in a const context.
use crate::Alphabet;

/// A builder for setting up the alphabet and output of a base58 decode.
///
/// See the documentation for [`bs58::decode_const`](crate::decode_const()) for
/// a more high level view of how to use this.
#[allow(missing_debug_implementations)]
pub struct DecodeBuilder<'a, 'b> {
input: &'a [u8],
alpha: &'b Alphabet,
}

impl<'a, 'b> DecodeBuilder<'a, 'b> {
/// Setup decoder for the given string using the given alphabet.
/// Preferably use [`bs58::decode_const`](crate::decode_const()) instead of
/// this directly.
pub const fn new(input: &'a [u8], alpha: &'b Alphabet) -> Self {
Self { input, alpha }
}

/// Setup decoder for the given string using default prepared alphabet.
pub(crate) const fn from_input(input: &'a [u8]) -> Self {
Self {
input,
alpha: Alphabet::DEFAULT,
}
}

/// Change the alphabet that will be used for decoding.
///
/// # Examples
///
/// ```rust
/// assert_eq!(
/// vec![0x60, 0x65, 0xe7, 0x9b, 0xba, 0x2f, 0x78],
/// bs58::decode_const(b"he11owor1d")
/// .with_alphabet(bs58::Alphabet::RIPPLE)
/// .into_array::<7>());
/// ```
pub const fn with_alphabet(self, alpha: &'b Alphabet) -> Self {
Self { alpha, ..self }
}

/// Decode into a new array.
///
/// Returns the decoded array as bytes.
///
/// See the documentation for [`bs58::decode_const`](crate::decode_const())
/// for an explanation of the errors that may occur.
///
/// # Examples
///
/// ```rust
/// let output = bs58::decode_const(b"EUYUqQf").into_array::<5>();
/// assert_eq!(output.len(), 5);
/// assert_eq!("world", std::str::from_utf8(&output)?);
/// # Ok::<(), std::str::Utf8Error>(())
/// ```
pub const fn into_array<const N: usize>(&self) -> [u8; N] {
decode_into::<N>(self.input, self.alpha)
}
}

const fn decode_into<const N: usize>(input: &[u8], alpha: &Alphabet) -> [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];
assert!(c < 128, "provided string contained a non-ascii character");

let mut val = alpha.decode[c as usize] as usize;
assert!(
val != 0xFF,
"provided string contained an invalid character"
);

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 {
output[index] = (val & 0xFF) as u8;
index += 1;
val >>= 8
}
i += 1;
}

let mut i = 0;
while i < input.len() && input[i] == zero {
output[index] = 0;
index += 1;
i += 1;
}

// reverse
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;
}

output
}
42 changes: 42 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pub mod alphabet;
pub use alphabet::Alphabet;

pub mod decode;
pub mod decode_const;
pub mod encode;

#[cfg(any(feature = "check", feature = "cb58"))]
Expand Down Expand Up @@ -171,6 +172,47 @@ pub fn decode<I: AsRef<[u8]>>(input: I) -> decode::DecodeBuilder<'static, I> {
decode::DecodeBuilder::from_input(input)
}

/// Setup decoder for the given string using the [default alphabet][Alphabet::DEFAULT].
///
/// Usable in `const` contexts, so the size of the output array must be specified.
///
/// # Examples
///
/// ## Basic example
///
/// ```rust
/// assert_eq!(
/// vec![0x04, 0x30, 0x5e, 0x2b, 0x24, 0x73, 0xf0, 0x58],
/// bs58::decode_const(b"he11owor1d").into_array::<8>());
/// ```
///
/// ## Changing the alphabet
///
/// ```rust
/// assert_eq!(
/// vec![0x60, 0x65, 0xe7, 0x9b, 0xba, 0x2f, 0x78],
/// bs58::decode_const(b"he11owor1d")
/// .with_alphabet(bs58::Alphabet::RIPPLE)
/// .into_array::<7>());
/// ```
///
/// ## Errors
///
/// ### Invalid Character
///
/// ```should_panic
/// bs58::decode_const(b"hello world").into_array::<10>();
/// ```
///
/// ### Non-ASCII Character
///
/// ```should_panic
/// bs58::decode_const("he11o🇳🇿".as_bytes()).into_array::<10>();
/// ```
pub const fn decode_const(input: &[u8]) -> decode_const::DecodeBuilder<'_, '_> {
decode_const::DecodeBuilder::from_input(input)
}

/// Setup encoder for the given bytes using the [default alphabet][Alphabet::DEFAULT].
///
/// # Examples
Expand Down
20 changes: 20 additions & 0 deletions tests/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ fn test_decode() {
assert_eq!((PREFIX, val), vec.split_at(3));
}

{
let vec = bs58::decode_const(s.as_bytes()).into_array::<128>();
let mut check = [0; 128];
check[..val.len()].copy_from_slice(val);
assert_eq!(vec, check);
}

#[cfg(feature = "smallvec")]
{
let mut vec = smallvec::SmallVec::<[u8; 36]>::from(PREFIX);
Expand Down Expand Up @@ -67,6 +74,19 @@ fn test_decode_small_buffer_err() {
);
}

#[test]
#[should_panic]
fn test_decode_const_small_buffer_panic() {
bs58::decode_const(b"a3gV").into_array::<2>();
}

#[test]
#[should_panic]
fn test_decode_const_invalid_char_panic() {
let sample = "123456789abcd!efghij";
let _ = bs58::decode_const(sample.as_bytes()).into_array::<32>();
}

#[test]
fn test_decode_invalid_char() {
let sample = "123456789abcd!efghij";
Expand Down

0 comments on commit e65bfa7

Please sign in to comment.