diff --git a/Cargo.toml b/Cargo.toml index 31daa1ce4..b3aedd9b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,7 @@ alloc = [] # Only for CI to make all warnings errors, do not activate otherwise (may break forward compatibility) strict = [] -[dependencies] +[dependencies.arrayvec] +version = "0.7.1" +default-features = false +optional = true diff --git a/contrib/test.sh b/contrib/test.sh index c808b777f..093a8890f 100755 --- a/contrib/test.sh +++ b/contrib/test.sh @@ -19,14 +19,29 @@ cargo test cargo build --no-default-features --features="strict" +# Check "arrayvec" feature alone. + +cargo build --no-default-features --features="strict arrayvec" +cargo test --no-default-features --features="strict arrayvec" + # Check "alloc" feature alone. +cargo build --no-default-features --features="strict alloc" +cargo test --no-default-features --features="strict alloc" + +# Check "alloc" & "arrayvec" features together. + +cargo build --no-default-features --features="strict alloc arrayvec" +cargo test --no-default-features --features="strict alloc arrayvec" + +# Check "std" feature (implies "alloc"). + cargo build --no-default-features --features="strict std" cargo test --no-default-features --features="strict std" -# Check "std" feature (implies "alloc"). +# Check "std" & "arrayvec" features together. -cargo build --no-default-features --features="strict alloc" -cargo test --no-default-features --features="strict alloc" +cargo build --no-default-features --features="strict std arrayvec" +cargo test --no-default-features --features="strict std arrayvec" exit 0 diff --git a/embedded/no-allocator/Cargo.toml b/embedded/no-allocator/Cargo.toml index 72f6fda4a..67da9aafa 100644 --- a/embedded/no-allocator/Cargo.toml +++ b/embedded/no-allocator/Cargo.toml @@ -5,11 +5,24 @@ readme = "README.md" name = "no-allocator" version = "0.1.0" +[dependencies] +cortex-m = "0.6.0" +cortex-m-rt = "0.6.10" +cortex-m-semihosting = "0.3.3" +panic-halt = "0.2.0" +arrayvec = { version = "0.7.1", default-features = false } +bech32 = { path = "../../", default-features = false, features = ["arrayvec"] } + +[[bin]] +name = "no-allocator" +test = false +bench = false + [profile.dev] panic = "abort" [profile.release] panic = "abort" - -[dependencies] -bech32 = { path = "../../", default_features = false } +codegen-units = 1 # better optimizations +debug = true # symbols are nice and they don't increase the size on Flash +lto = true # better optimizations diff --git a/embedded/no-allocator/src/main.rs b/embedded/no-allocator/src/main.rs index d723e82b8..1f7de549b 100644 --- a/embedded/no-allocator/src/main.rs +++ b/embedded/no-allocator/src/main.rs @@ -3,24 +3,52 @@ //! Build with: `cargo rustc -- -C link-arg=-nostartfiles`. //! -#![no_std] +#![feature(alloc_error_handler)] #![no_main] +#![no_std] + +use panic_halt as _; -use core::panic::PanicInfo; +use arrayvec::{ArrayString, ArrayVec}; +use bech32::{self, u5, ComboError, FromBase32, ToBase32, Variant}; +use cortex_m_rt::entry; +use cortex_m_semihosting::{debug, hprintln}; // Note: `#[global_allocator]` is NOT set. -#[allow(unused_imports)] -use bech32; +#[entry] +fn main() -> ! { + let mut encoded = ArrayString::<30>::new(); + + let mut base32 = ArrayVec::::new(); + + [0x00u8, 0x01, 0x02].write_base32(&mut base32).unwrap(); + + bech32::encode_to_fmt_anycase(&mut encoded, "bech32", &base32, Variant::Bech32) + .unwrap() + .unwrap(); + test(&*encoded == "bech321qqqsyrhqy2a"); + + hprintln!("{}", encoded).unwrap(); + + let mut decoded = ArrayVec::::new(); + + let mut scratch = ArrayVec::::new(); + + let (hrp, data, variant) = + bech32::decode_lowercase::(&encoded, &mut decoded, &mut scratch).unwrap(); + test(hrp == "bech32"); + let res = ArrayVec::::from_base32(&data).unwrap(); + test(&res == [0x00, 0x01, 0x02].as_ref()); + test(variant == Variant::Bech32); + + debug::exit(debug::EXIT_SUCCESS); -/// This function is called on panic, defining this ensures build will fail if `std` is enabled -/// because `panic` will be defined twice. -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { loop {} } -#[no_mangle] -pub extern "C" fn _start() -> ! { - loop {} +fn test(result: bool) { + if !result { + debug::exit(debug::EXIT_FAILURE); + } } diff --git a/embedded/with-allocator/Cargo.toml b/embedded/with-allocator/Cargo.toml index d4010dc02..8c55ed649 100644 --- a/embedded/with-allocator/Cargo.toml +++ b/embedded/with-allocator/Cargo.toml @@ -11,7 +11,7 @@ cortex-m-rt = "0.6.10" cortex-m-semihosting = "0.3.3" panic-halt = "0.2.0" alloc-cortex-m = "0.4.1" -bech32 = { path="../../", default-features = false } +bech32 = { path = "../../", default-features = false, features = ["alloc"] } [[bin]] name = "with-allocator" diff --git a/src/lib.rs b/src/lib.rs index 5488ba681..2337f33be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,23 +54,28 @@ assert_eq!(variant, Variant::Bech32); #![deny(non_camel_case_types)] #![deny(non_snake_case)] #![deny(unused_mut)] - #![cfg_attr(feature = "strict", deny(warnings))] #![cfg_attr(all(not(feature = "std"), not(test)), no_std)] #[cfg(feature = "alloc")] extern crate alloc; -#[cfg(all(feature = "alloc", not(feature = "std")))] +#[cfg(any(test, feature = "std"))] +extern crate core; + +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] use alloc::{string::String, vec::Vec}; -#[cfg(all(feature = "alloc", not(feature = "std")))] +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] use alloc::borrow::Cow; -#[cfg(feature = "std")] +#[cfg(any(feature = "std", all(feature = "alloc", test)))] use std::borrow::Cow; use core::{convert::Infallible, fmt, mem}; +#[cfg(feature = "arrayvec")] +use arrayvec::{ArrayVec, CapacityError}; + /// Integer in the range `0..32` #[derive(PartialEq, Eq, Debug, Copy, Clone, Default, PartialOrd, Ord, Hash)] #[allow(non_camel_case_types)] @@ -109,13 +114,16 @@ impl AsRef for u5 { } } -/// Interface to write `u5`s into a sink -pub trait WriteBase32 { +/// Interface to write `u(2*n)`s into a sink +pub trait WriteBaseN +where + T: Copy, +{ /// Write error type Err: fmt::Debug; /// Write a `u5` slice - fn write(&mut self, data: &[u5]) -> Result<(), Self::Err> { + fn write(&mut self, data: &[T]) -> Result<(), Self::Err> { for b in data { self.write_u5(*b)?; } @@ -123,11 +131,23 @@ pub trait WriteBase32 { } /// Write a single `u5` - fn write_u5(&mut self, data: u5) -> Result<(), Self::Err>; + fn write_u5(&mut self, data: T) -> Result<(), Self::Err> { + self.write(&[data]) + } } const CHECKSUM_LENGTH: usize = 6; +/// Interface to write `u5`s into a sink +pub trait WriteBase32: WriteBaseN {} +impl WriteBase32 for T where T: WriteBaseN {} + +/// Interface to write `u8`s into a sink +/// +/// Like `std::io::Writer`, but because the associated type is no_std compatible. +pub trait WriteBase256: WriteBaseN {} +impl WriteBase256 for T where T: WriteBaseN {} + /// Allocationless Bech32 writer that accumulates the checksum data internally and writes them out /// in the end. pub struct Bech32Writer<'a> { @@ -202,7 +222,7 @@ impl<'a> Bech32Writer<'a> { } } -impl<'a> WriteBase32 for Bech32Writer<'a> { +impl<'a> WriteBaseN for Bech32Writer<'a> { type Err = fmt::Error; /// Writes a single 5 bit value of the data part @@ -229,21 +249,71 @@ pub trait FromBase32: Sized { fn from_base32(b32: &[u5]) -> Result; } +#[cfg(feature = "arrayvec")] +impl WriteBaseN for ArrayVec { + type Err = CapacityError; + + fn write(&mut self, data: &[T]) -> Result<(), Self::Err> { + self.try_extend_from_slice(data)?; + Ok(()) + } + + fn write_u5(&mut self, data: T) -> Result<(), Self::Err> { + self.push(data); + Ok(()) + } +} + #[cfg(feature = "alloc")] -impl WriteBase32 for Vec { +impl WriteBaseN for Vec { type Err = Infallible; - fn write(&mut self, data: &[u5]) -> Result<(), Self::Err> { + fn write(&mut self, data: &[T]) -> Result<(), Self::Err> { self.extend_from_slice(data); Ok(()) } - fn write_u5(&mut self, data: u5) -> Result<(), Self::Err> { + fn write_u5(&mut self, data: T) -> Result<(), Self::Err> { self.push(data); Ok(()) } } +#[cfg(feature = "arrayvec")] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +/// Combination of Errors for use with array vec +pub enum ComboError { + /// Error from this crate + Bech32Error(Error), + /// Error from `arrayvec`. + CapacityError(CapacityError), +} +#[cfg(feature = "arrayvec")] +impl From for ComboError { + fn from(e: Error) -> ComboError { + ComboError::Bech32Error(e) + } +} +#[cfg(feature = "arrayvec")] +impl From for ComboError { + fn from(e: CapacityError) -> ComboError { + ComboError::CapacityError(e) + } +} + +#[cfg(feature = "arrayvec")] +impl FromBase32 for ArrayVec { + type Err = ComboError; + + /// Convert base32 to base256, removes null-padding if present, returns + /// `Err(Error::InvalidPadding)` if padding bits are unequal `0` + fn from_base32(b32: &[u5]) -> Result { + let mut ret: ArrayVec = ArrayVec::new(); + convert_bits_in::(b32, 5, 8, false, &mut ret)?; + Ok(ret) + } +} + #[cfg(feature = "alloc")] impl FromBase32 for Vec { type Err = Error; @@ -256,9 +326,9 @@ impl FromBase32 for Vec { } /// A trait for converting a value to a type `T` that represents a `u5` slice. -#[cfg(feature = "alloc")] pub trait ToBase32 { /// Convert `Self` to base32 vector + #[cfg(feature = "alloc")] fn to_base32(&self) -> Vec { let mut vec = Vec::new(); self.write_base32(&mut vec).unwrap(); @@ -267,19 +337,23 @@ pub trait ToBase32 { /// Encode as base32 and write it to the supplied writer /// Implementations shouldn't allocate. - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err>; + fn write_base32( + &self, + writer: &mut W, + ) -> Result<(), >::Err>; } /// Interface to calculate the length of the base32 representation before actually serializing -#[cfg(feature = "alloc")] pub trait Base32Len: ToBase32 { /// Calculate the base32 serialized length fn base32_len(&self) -> usize; } -#[cfg(feature = "alloc")] impl> ToBase32 for T { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { + fn write_base32( + &self, + writer: &mut W, + ) -> Result<(), >::Err> { // Amount of bits left over from last round, stored in buffer. let mut buffer_bits = 0u32; // Holds all unwritten bits left over from last round. The bits are stored beginning from @@ -322,7 +396,6 @@ impl> ToBase32 for T { } } -#[cfg(feature = "alloc")] impl> Base32Len for T { fn base32_len(&self) -> usize { let bits = self.as_ref().len() * 8; @@ -336,7 +409,7 @@ impl> Base32Len for T { /// A trait to convert between u8 arrays and u5 arrays without changing the content of the elements, /// but checking that they are in range. -pub trait CheckBase32> { +pub trait CheckBase32 { /// Error type if conversion fails type Err; @@ -344,20 +417,34 @@ pub trait CheckBase32> { fn check_base32(self) -> Result; } -#[cfg(feature = "alloc")] -impl> CheckBase32> for T { +impl> CheckBase32 for U +where + T: AsRef<[u5]>, + T: core::iter::FromIterator, +{ type Err = Error; - fn check_base32(self) -> Result, Self::Err> { + fn check_base32(self) -> Result { self.as_ref() .iter() .map(|x| u5::try_from_u8(*x)) - .collect::, Error>>() + .collect::>() + } +} + +impl> CheckBase32<()> for U { + type Err = Error; + + fn check_base32(self) -> Result<(), Error> { + self.as_ref() + .iter() + .map(|x| u5::try_from_u8(*x).map(|_| ())) + .find(|r| r.is_err()) + .unwrap_or(Ok(())) } } #[derive(Clone, Copy, PartialEq, Eq)] -#[cfg(feature = "alloc")] enum Case { Upper, Lower, @@ -370,7 +457,6 @@ enum Case { /// * **MixedCase**: If the HRP contains both uppercase and lowercase characters. /// * **InvalidChar**: If the HRP contains any non-ASCII characters (outside 33..=126). /// * **InvalidLength**: If the HRP is outside 1..83 characters long. -#[cfg(feature = "alloc")] fn check_hrp(hrp: &str) -> Result { if hrp.is_empty() || hrp.len() > 83 { return Err(Error::InvalidLength); @@ -422,7 +508,20 @@ pub fn encode_to_fmt>( Case::Lower | Case::None => Cow::Borrowed(hrp), }; - match Bech32Writer::new(&hrp_lower, variant, fmt) { + encode_to_fmt_anycase(fmt, &hrp_lower, data, variant) +} + +/// Encode a bech32 payload to an [fmt::Write], but with any case. +/// This method is intended for implementing traits from [core::fmt] without [std]. +/// +/// See `encode_to_fmt` for meaning of errors. +pub fn encode_to_fmt_anycase>( + fmt: &mut fmt::Write, + hrp: &str, + data: T, + variant: Variant, +) -> Result { + match Bech32Writer::new(&hrp, variant, fmt) { Ok(mut writer) => { Ok(writer.write(data.as_ref()).and_then(|_| { // Finalize manually to avoid panic on drop if write fails @@ -440,12 +539,12 @@ pub fn encode_to_fmt>( /// * If [check_hrp] returns an error for the given HRP. /// # Deviations from standard /// * No length limits are enforced for the data part -#[cfg(feature = "alloc")] pub fn encode_without_checksum_to_fmt>( fmt: &mut fmt::Write, hrp: &str, data: T, ) -> Result { + #[cfg(feature = "alloc")] let hrp = match check_hrp(hrp)? { Case::Upper => Cow::Owned(hrp.to_lowercase()), Case::Lower | Case::None => Cow::Borrowed(hrp), @@ -479,7 +578,6 @@ const BECH32M_CONST: u32 = 0x2bc8_30a3; impl Variant { // Produce the variant based on the remainder of the polymod operation - #[cfg(feature = "alloc")] fn from_remainder(c: u32) -> Option { match c { BECH32_CONST => Some(Variant::Bech32), @@ -610,27 +708,112 @@ fn split_and_decode(s: &str) -> Result<(String, Vec), Error> { Ok((hrp_lower, data)) } -#[cfg(feature = "alloc")] -fn verify_checksum(hrp: &[u8], data: &[u5]) -> Option { - let mut exp = hrp_expand(hrp); - exp.extend_from_slice(data); - Variant::from_remainder(polymod(&exp)) +// TODO deduplicate some +/// Decode a lowercase bech32 string into the raw HRP and the data bytes. +/// +/// Less flexible than [decode], but don't allocate. +pub fn decode_lowercase<'a, 'b, E, R, S>( + s: &'a str, + data: &'b mut R, + scratch: &mut S, +) -> Result<(&'a str, &'b [u5], Variant), E> +where + R: WriteBase32 + AsRef<[u5]>, + S: WriteBase32 + AsRef<[u5]>, + E: From, + E: From, + E: From, +{ + // Ensure overall length is within bounds + if s.len() < 8 { + Err(Error::InvalidLength)?; + } + + // Split at separator and check for two pieces + let (hrp_lower, raw_data) = match s.rfind(SEP) { + None => Err(Error::MissingSeparator)?, + Some(sep) => { + let (hrp, data) = s.split_at(sep); + (hrp, &data[1..]) + } + }; + if raw_data.len() < 6 { + Err(Error::InvalidLength)?; + } + + let case = match check_hrp(&hrp_lower)? { + Case::Upper => Err(Error::MixedCase)?, + // already lowercase + Case::Lower | Case::None => Case::Lower, + }; + + // Check data payload + for c in raw_data.chars() { + // Only check if c is in the ASCII range, all invalid ASCII + // characters have the value -1 in CHARSET_REV (which covers + // the whole ASCII range) and will be filtered out later. + if !c.is_ascii() { + Err(Error::InvalidChar(c))?; + } + + match case { + Case::Upper => Err(Error::MixedCase)?, + Case::None | Case::Lower => {} + } + + // c should be <128 since it is in the ASCII range, CHARSET_REV.len() == 128 + let num_value = CHARSET_REV[c as usize]; + + if num_value > 31 || num_value < 0 { + Err(Error::InvalidChar(c))?; + } + + data.write_u5( + u5::try_from_u8(num_value as u8).expect("range checked above, num_value <= 31"), + )?; + } + + // Ensure checksum + let variant = verify_checksum_in(&hrp_lower.as_bytes(), data.as_ref(), scratch)? + .ok_or(Error::MissingSeparator)?; + + let dbl: usize = data.as_ref().len(); + Ok(( + hrp_lower, + &(*data).as_ref()[..dbl.saturating_sub(6)], + variant, + )) } #[cfg(feature = "alloc")] -fn hrp_expand(hrp: &[u8]) -> Vec { +fn verify_checksum(hrp: &[u8], data: &[u5]) -> Option { let mut v: Vec = Vec::new(); + match verify_checksum_in(hrp, data, &mut v) { + Ok(v) => v, + Err(e) => match e {}, + } +} + +fn verify_checksum_in(hrp: &[u8], data: &[u5], v: &mut T) -> Result, T::Err> +where + T: WriteBase32 + AsRef<[u5]>, +{ + hrp_expand_in(hrp, v)?; + v.write(data)?; + Ok(Variant::from_remainder(polymod(v.as_ref()))) +} + +fn hrp_expand_in(hrp: &[u8], v: &mut T) -> Result<(), T::Err> { for b in hrp { - v.push(u5::try_from_u8(*b >> 5).expect("can't be out of range, max. 7")); + v.write_u5(u5::try_from_u8(*b >> 5).expect("can't be out of range, max. 7"))?; } - v.push(u5::try_from_u8(0).unwrap()); + v.write_u5(u5::try_from_u8(0).unwrap())?; for b in hrp { - v.push(u5::try_from_u8(*b & 0x1f).expect("can't be out of range, max. 31")); + v.write_u5(u5::try_from_u8(*b & 0x1f).expect("can't be out of range, max. 31"))?; } - v + Ok(()) } -#[cfg(feature = "alloc")] fn polymod(values: &[u5]) -> u32 { let mut chk: u32 = 1; let mut b: u8; @@ -659,7 +842,6 @@ const CHARSET: [char; 32] = [ ]; /// Reverse character set. Maps ASCII byte -> CHARSET index on [0,31] -#[cfg(feature = "alloc")] const CHARSET_REV: [i8; 128] = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -697,6 +879,12 @@ pub enum Error { MixedCase, } +impl From for Error { + fn from(v: Infallible) -> Self { + match v {} + } +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -744,51 +932,97 @@ impl std::error::Error for Error { pub fn convert_bits(data: &[T], from: u32, to: u32, pad: bool) -> Result, Error> where T: Into + Copy, +{ + let mut ret: Vec = Vec::new(); + convert_bits_in::(data, from, to, pad, &mut ret)?; + Ok(ret) +} + +/// Convert between bit sizes without allocating +/// +/// Like [convert_bits]. +pub fn convert_bits_in( + data: &[T], + from: u32, + to: u32, + pad: bool, + ret: &mut R, +) -> Result<(), E> +where + T: Into + Copy, + R: WriteBase256, + E: From, + E: From, { if from > 8 || to > 8 || from == 0 || to == 0 { panic!("convert_bits `from` and `to` parameters 0 or greater than 8"); } let mut acc: u32 = 0; let mut bits: u32 = 0; - let mut ret: Vec = Vec::new(); let maxv: u32 = (1 << to) - 1; for value in data { let v: u32 = u32::from(Into::::into(*value)); if (v >> from) != 0 { // Input value exceeds `from` bit size - return Err(Error::InvalidData(v as u8)); + Err(Error::InvalidData(v as u8))?; } acc = (acc << from) | v; bits += from; while bits >= to { bits -= to; - ret.push(((acc >> bits) & maxv) as u8); + ret.write_u5(((acc >> bits) & maxv) as u8)?; } } if pad { if bits > 0 { - ret.push(((acc << (to - bits)) & maxv) as u8); + ret.write_u5(((acc << (to - bits)) & maxv) as u8)?; } } else if bits >= from || ((acc << (to - bits)) & maxv) != 0 { - return Err(Error::InvalidPadding); + Err(Error::InvalidPadding)?; } - Ok(ret) + Ok(()) } #[cfg(test)] -#[cfg(feature = "alloc")] // Note, all the unit tests currently require an allocator. mod tests { + #[cfg(feature = "arrayvec")] + use arrayvec::ArrayString; + use super::*; + trait TextExt { + fn check_base32_vec(self) -> Result, Error>; + } + impl> TextExt for U { + fn check_base32_vec(self) -> Result, Error> { + self.check_base32() + } + } + #[test] + #[cfg(feature = "alloc")] + fn getters_in() { + let mut data_scratch = Vec::new(); + let mut scratch = Vec::new(); + let decoded = + decode_lowercase::("bc1sw50qa3jx3s", &mut data_scratch, &mut scratch) + .unwrap(); + let data = [16, 14, 20, 15, 0].check_base32_vec().unwrap(); + assert_eq!(decoded.0, "bc"); + assert_eq!(decoded.1, data.as_slice()); + } + + #[test] + #[cfg(feature = "alloc")] fn getters() { let decoded = decode("BC1SW50QA3JX3S").unwrap(); - let data = [16, 14, 20, 15, 0].check_base32().unwrap(); + let data = [16, 14, 20, 15, 0].check_base32_vec().unwrap(); assert_eq!(&decoded.0, "bc"); assert_eq!(decoded.1, data.as_slice()); } #[test] + #[cfg(feature = "alloc")] fn valid_checksum() { let strings: Vec<&str> = vec!( // Bech32 @@ -818,6 +1052,7 @@ mod tests { } #[test] + #[cfg(feature = "alloc")] fn invalid_strings() { let pairs: Vec<(&str, Error)> = vec!( (" 1nwldj5", @@ -878,6 +1113,7 @@ mod tests { #[test] #[allow(clippy::type_complexity)] + #[cfg(feature = "alloc")] fn valid_conversion() { // Set of [data, from_bits, to_bits, pad, result] let tests: Vec<(Vec, u32, u32, bool, Vec)> = vec![ @@ -905,6 +1141,7 @@ mod tests { } #[test] + #[cfg(feature = "alloc")] fn invalid_conversion() { // Set of [data, from_bits, to_bits, pad, expected error] let tests: Vec<(Vec, u32, u32, bool, Error)> = vec![ @@ -920,6 +1157,7 @@ mod tests { } #[test] + #[cfg(feature = "alloc")] fn convert_bits_invalid_bit_size() { use std::panic::{catch_unwind, set_hook, take_hook}; @@ -937,23 +1175,24 @@ mod tests { #[test] fn check_base32() { - assert!([0u8, 1, 2, 30, 31].check_base32().is_ok()); - assert!([0u8, 1, 2, 30, 31, 32].check_base32().is_err()); - assert!([0u8, 1, 2, 30, 31, 255].check_base32().is_err()); + assert!([0u8, 1, 2, 30, 31].check_base32_vec().is_ok()); + assert!([0u8, 1, 2, 30, 31, 32].check_base32_vec().is_err()); + assert!([0u8, 1, 2, 30, 31, 255].check_base32_vec().is_err()); - assert!([1u8, 2, 3, 4].check_base32().is_ok()); + assert!([1u8, 2, 3, 4].check_base32_vec().is_ok()); assert_eq!( - [30u8, 31, 35, 20].check_base32(), + [30u8, 31, 35, 20].check_base32_vec(), Err(Error::InvalidData(35)) ); } #[test] + #[cfg(feature = "alloc")] fn test_encode() { assert_eq!( encode( "", - vec![1u8, 2, 3, 4].check_base32().unwrap(), + vec![1u8, 2, 3, 4].check_base32_vec().unwrap(), Variant::Bech32 ), Err(Error::InvalidLength) @@ -961,20 +1200,25 @@ mod tests { } #[test] + #[cfg(feature = "alloc")] fn from_base32() { assert_eq!( - Vec::from_base32(&[0x1f, 0x1c].check_base32().unwrap()), + Vec::from_base32(&[0x1f, 0x1c].check_base32_vec().unwrap()), Ok(vec![0xff]) ); assert_eq!( - Vec::from_base32(&[0x1f, 0x1f].check_base32().unwrap()), + Vec::from_base32(&[0x1f, 0x1f].check_base32_vec().unwrap()), Err(Error::InvalidPadding) ); } #[test] + #[cfg(feature = "alloc")] fn to_base32() { - assert_eq!([0xffu8].to_base32(), [0x1f, 0x1c].check_base32().unwrap()); + assert_eq!( + [0xffu8].to_base32(), + [0x1f, 0x1c].check_base32_vec().unwrap() + ); } #[test] @@ -995,6 +1239,7 @@ mod tests { } #[test] + #[cfg(feature = "alloc")] fn write_with_checksum() { let hrp = "lnbc"; let data = "Hello World!".as_bytes().to_base32(); @@ -1012,6 +1257,7 @@ mod tests { } #[test] + #[cfg(feature = "alloc")] fn write_without_checksum() { let hrp = "lnbc"; let data = "Hello World!".as_bytes().to_base32(); @@ -1031,6 +1277,7 @@ mod tests { } #[test] + #[cfg(feature = "alloc")] fn write_with_checksum_on_drop() { let hrp = "lntb"; let data = "Hello World!".as_bytes().to_base32(); @@ -1047,6 +1294,7 @@ mod tests { } #[test] + #[cfg(feature = "alloc")] fn roundtrip_without_checksum() { let hrp = "lnbc"; let data = "Hello World!".as_bytes().to_base32(); @@ -1060,10 +1308,39 @@ mod tests { } #[test] + #[cfg(feature = "alloc")] fn test_hrp_case() { // Tests for issue with HRP case checking being ignored for encoding let encoded_str = encode("HRP", [0x00, 0x00].to_base32(), Variant::Bech32).unwrap(); assert_eq!(encoded_str, "hrp1qqqq40atq3"); } + + #[test] + #[cfg(feature = "arrayvec")] + fn test_arrayvec() { + let mut encoded = ArrayString::<30>::new(); + + let mut base32 = ArrayVec::::new(); + + [0x00u8, 0x01, 0x02].write_base32(&mut base32).unwrap(); + + encode_to_fmt_anycase(&mut encoded, "bech32", &base32, Variant::Bech32) + .unwrap() + .unwrap(); + assert_eq!(&*encoded, "bech321qqqsyrhqy2a"); + + println!("{}", encoded); + + let mut decoded = ArrayVec::::new(); + + let mut scratch = ArrayVec::::new(); + + let (hrp, data, variant) = + decode_lowercase::(&encoded, &mut decoded, &mut scratch).unwrap(); + assert_eq!(hrp, "bech32"); + let res = ArrayVec::::from_base32(&data).unwrap(); + assert_eq!(&res, [0x00, 0x01, 0x02].as_ref()); + assert_eq!(variant, Variant::Bech32); + } }