From e65bfa72a23c57fbc05cad66c9b667c6eae946fa Mon Sep 17 00:00:00 2001 From: Jon C Date: Thu, 29 Feb 2024 01:39:19 +0100 Subject: [PATCH] decode: Add const-compatible decoder --- src/decode_const.rs | 117 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 42 ++++++++++++++++ tests/decode.rs | 20 ++++++++ 3 files changed, 179 insertions(+) create mode 100644 src/decode_const.rs diff --git a/src/decode_const.rs b/src/decode_const.rs new file mode 100644 index 0000000..57363b2 --- /dev/null +++ b/src/decode_const.rs @@ -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(&self) -> [u8; N] { + decode_into::(self.input, self.alpha) + } +} + +const fn decode_into(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 +} diff --git a/src/lib.rs b/src/lib.rs index 8c545b9..5e975ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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"))] @@ -171,6 +172,47 @@ pub fn decode>(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 diff --git a/tests/decode.rs b/tests/decode.rs index 4a253fd..5680f7c 100644 --- a/tests/decode.rs +++ b/tests/decode.rs @@ -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); @@ -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";