From e18e057bf86e67e028ed6da0ee4f1850978d2301 Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Sun, 10 Mar 2024 12:31:48 +0100 Subject: [PATCH] Move const-compatible API onto `decode::DecodeBuilder` directly --- src/decode.rs | 155 ++++++++++++++++++++++++++++++++++++++++++-- src/decode_const.rs | 117 --------------------------------- src/lib.rs | 44 +------------ tests/decode.rs | 6 +- 4 files changed, 154 insertions(+), 168 deletions(-) delete mode 100644 src/decode_const.rs diff --git a/src/decode.rs b/src/decode.rs index ab43a27..9463bfd 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -196,7 +196,7 @@ impl DecodeTarget for [u8; N] { impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> { /// Setup decoder for the given string using the given alphabet. /// Preferably use [`bs58::decode`](crate::decode()) instead of this directly. - pub fn new(input: I, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> { + pub const fn new(input: I, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> { DecodeBuilder { input, alpha, @@ -205,7 +205,7 @@ impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> { } /// Setup decoder for the given string using default prepared alphabet. - pub(crate) fn from_input(input: I) -> DecodeBuilder<'static, I> { + pub(crate) const fn from_input(input: I) -> DecodeBuilder<'static, I> { DecodeBuilder { input, alpha: Alphabet::DEFAULT, @@ -225,8 +225,9 @@ impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> { /// .into_vec()?); /// # Ok::<(), bs58::decode::Error>(()) /// ``` - pub fn with_alphabet(self, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> { - DecodeBuilder { alpha, ..self } + pub const fn with_alphabet(mut self, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> { + self.alpha = alpha; + self } /// Expect and check checksum using the [Base58Check][] algorithm when @@ -276,7 +277,6 @@ impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> { let check = Check::CB58(expected_ver); DecodeBuilder { check, ..self } } - /// Decode into a new vector of bytes. /// /// See the documentation for [`bs58::decode`](crate::decode()) for an @@ -348,6 +348,66 @@ impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> { } } +/// For `const` compatibility we are restricted to using a concrete input and output type, as +/// `const` trait implementations and `&mut` are unstable. These methods will eventually be +/// deprecated once the primary interfaces can be converted into `const fn` directly. +impl<'a, 'b> DecodeBuilder<'a, &'b [u8]> { + /// Decode into a new array. + /// + /// Returns the decoded array as bytes. + /// + /// See the documentation for [`bs58::decode`](crate::decode()) + /// for an explanation of the errors that may occur. + /// + /// # Examples + /// + /// ```rust + /// const _: () = { + /// let Ok(output) = bs58::decode(b"EUYUqQf".as_slice()).into_array_const::<5>() else { + /// panic!() + /// }; + /// assert!(matches!(&output, b"world")); + /// }; + /// ``` + pub const fn into_array_const(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) + } + + /// [`Self::into_array_const`] but the result will be unwrapped, turning any error into a panic + /// message via [`Error::unwrap_const`], as a simple `into_array_const().unwrap()` isn't + /// possible yet. + /// + /// # Examples + /// + /// ```rust + /// const _: () = { + /// let output: [u8; 5] = bs58::decode(b"EUYUqQf".as_slice()).into_array_const_unwrap(); + /// assert!(matches!(&output, b"world")); + /// }; + /// ``` + /// + /// ```rust + /// const _: () = { + /// assert!(matches!( + /// bs58::decode(b"he11owor1d".as_slice()) + /// .with_alphabet(bs58::Alphabet::RIPPLE) + /// .into_array_const_unwrap(), + /// [0x60, 0x65, 0xe7, 0x9b, 0xba, 0x2f, 0x78], + /// )); + /// }; + /// ``` + pub const fn into_array_const_unwrap(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 { let mut index = 0; let zero = alpha.encode[0]; @@ -480,6 +540,69 @@ fn decode_cb58_into( } } +const fn decode_into_const(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; + } + + // 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; + } + + Ok(output) +} + #[cfg(feature = "std")] impl std::error::Error for Error {} @@ -520,3 +643,25 @@ impl fmt::Display for Error { } } } + +impl Error { + /// Panic with an error message based on this error. This cannot include any of the dynamic + /// content because formatting in `const` is not yet possible. + 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"), + } + } +} diff --git a/src/decode_const.rs b/src/decode_const.rs deleted file mode 100644 index 57363b2..0000000 --- a/src/decode_const.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! 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 5e975ef..cbeee1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,6 @@ pub mod alphabet; pub use alphabet::Alphabet; pub mod decode; -pub mod decode_const; pub mod encode; #[cfg(any(feature = "check", feature = "cb58"))] @@ -168,51 +167,10 @@ enum Check { /// bs58::decode::Error::BufferTooSmall, /// bs58::decode("he11owor1d").onto(&mut output).unwrap_err()); /// ``` -pub fn decode>(input: I) -> decode::DecodeBuilder<'static, I> { +pub const 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 5680f7c..e466867 100644 --- a/tests/decode.rs +++ b/tests/decode.rs @@ -17,7 +17,7 @@ fn test_decode() { } { - let vec = bs58::decode_const(s.as_bytes()).into_array::<128>(); + let vec = bs58::decode(s.as_bytes()).into_array_const_unwrap::<128>(); let mut check = [0; 128]; check[..val.len()].copy_from_slice(val); assert_eq!(vec, check); @@ -77,14 +77,14 @@ fn test_decode_small_buffer_err() { #[test] #[should_panic] fn test_decode_const_small_buffer_panic() { - bs58::decode_const(b"a3gV").into_array::<2>(); + bs58::decode(&b"a3gV"[..]).into_array_const_unwrap::<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>(); + let _ = bs58::decode(sample.as_bytes()).into_array_const_unwrap::<32>(); } #[test]