diff --git a/Cargo.toml b/Cargo.toml index 6f61304..ec2c767 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,13 @@ keywords = ["ipld", "ipfs", "multihash", "cid", "no_std"] [features] default = ["std"] -std = ["data-encoding/std"] +std = ["data-encoding/std", "alloc"] +alloc = ["data-encoding/alloc", "base-x"] + [dependencies] -base-x = { version = "0.2.7", default-features = false } -data-encoding = { version = "2.3.1", default-features = false, features = ["alloc"] } +base-x = { version = "0.2.7", default-features = false, optional = true } +data-encoding = { version = "2.3.1", default-features = false } data-encoding-macro = "0.1.9" [dev-dependencies] diff --git a/README.md b/README.md index 95cb4af..da4de8a 100644 --- a/README.md +++ b/README.md @@ -32,15 +32,15 @@ multibase = "0.9" ``` For `no_std` -``` +```toml [dependencies] multibase = { version ="0.9", default-features = false } ``` -**note**: This crate relies on the [currently unstable](https://github.com/rust-lang/cargo/issues/7915) `host_dep` feature to [compile proc macros with the proper dependencies](https://docs.rs/data-encoding-macro/0.1.10/data_encoding_macro/), thus **requiring nightly rustc** to use. - Then run `cargo build`. +**note**: This crate relies on the [currently unstable](https://github.com/rust-lang/cargo/issues/7915) `host_dep` feature to [compile proc macros with the proper dependencies](https://docs.rs/data-encoding-macro/0.1.10/data_encoding_macro/), thus **requiring nightly rustc** to use. + ## Usage ```rust @@ -54,6 +54,8 @@ let (base, data) = multibase::decode(base64); be surprised if using a different base turns into a performance bottleneck. You were warned! +See more examples in the [documentation](https://docs.rs/multibase) + ## Maintainers Captain: [@dignifiedquire](https://github.com/dignifiedquire). diff --git a/src/base.rs b/src/base.rs index c6a734b..3928187 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,7 +1,7 @@ use crate::error::{Error, Result}; use crate::impls::*; -#[cfg(not(feature = "std"))] +#[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; macro_rules! build_base_enum { @@ -30,6 +30,7 @@ macro_rules! build_base_enum { } } + #[cfg(feature = "alloc")] /// Encode the given byte slice to base string. pub fn encode>(&self, input: I) -> String { match self { @@ -37,16 +38,46 @@ macro_rules! build_base_enum { } } + #[cfg(feature = "alloc")] /// Decode the base string. pub fn decode>(&self, input: I) -> Result> { match self { $( Self::$base => $base::decode(input), )* } } + + /// Decode the base string into a mutable slice. + pub(crate) fn decode_mut>(&self, input: I, output: &mut [u8]) -> Result { + match self { + $( Self::$base => $base::decode_mut(input, output), )* + } + } + + /// Returns the size of the decoded slice. + pub fn decode_len(&self, len: usize) -> Result { + match self { + $( Self::$base => $base::decode_len(len - $code.len_utf8()), )* + } + } + + /// Encode the given byte slice to mutable slice. + pub(crate) fn encode_mut>(&self, input: I, output: &mut [u8]) { + match self { + $( Self::$base => $base::encode_mut(input, output), )* + } + } + + /// Returns the size of the encoded slice. + pub fn encode_len(&self, len: usize) -> usize { + match self { + $( Self::$base => $base::encode_len(len) + $code.len_utf8(), )* + } + } } } } +#[cfg(feature = "alloc")] build_base_enum! { /// 8-bit binary (encoder and decoder keeps data unmodified). '\x00' => Identity, @@ -60,7 +91,7 @@ build_base_enum! { 'f' => Base16Lower, /// Base16 upper hexadecimal (alphabet: 0123456789ABCDEF). 'F' => Base16Upper, - /// Base32, rfc4648 no padding (alphabet: abcdefghijklmnopqrstuvwxyz234567). + /// Base32, rfc4648 no padding (alphabet: abcdefghijklmnopqrstuvwxyz234567). 'b' => Base32Lower, /// Base32, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567). 'B' => Base32Upper, @@ -95,3 +126,43 @@ build_base_enum! { /// Base64 url, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_). 'U' => Base64UrlPad, } + +#[cfg(not(feature = "alloc"))] +build_base_enum! { + /// 8-bit binary (encoder and decoder keeps data unmodified). + '\x00' => Identity, + /// Base2 (alphabet: 01). + '0' => Base2, + /// Base8 (alphabet: 01234567). + '7' => Base8, + /// Base16 lower hexadecimal (alphabet: 0123456789abcdef). + 'f' => Base16Lower, + /// Base16 upper hexadecimal (alphabet: 0123456789ABCDEF). + 'F' => Base16Upper, + /// Base32, rfc4648 no padding (alphabet: abcdefghijklmnopqrstuvwxyz234567). + 'b' => Base32Lower, + /// Base32, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567). + 'B' => Base32Upper, + /// Base32, rfc4648 with padding (alphabet: abcdefghijklmnopqrstuvwxyz234567). + 'c' => Base32PadLower, + /// Base32, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567). + 'C' => Base32PadUpper, + /// Base32hex, rfc4648 no padding (alphabet: 0123456789abcdefghijklmnopqrstuv). + 'v' => Base32HexLower, + /// Base32hex, rfc4648 no padding (alphabet: 0123456789ABCDEFGHIJKLMNOPQRSTUV). + 'V' => Base32HexUpper, + /// Base32hex, rfc4648 with padding (alphabet: 0123456789abcdefghijklmnopqrstuv). + 't' => Base32HexPadLower, + /// Base32hex, rfc4648 with padding (alphabet: 0123456789ABCDEFGHIJKLMNOPQRSTUV). + 'T' => Base32HexPadUpper, + /// z-base-32 (used by Tahoe-LAFS) (alphabet: ybndrfg8ejkmcpqxot1uwisza345h769). + 'h' => Base32Z, + /// Base64, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/). + 'm' => Base64, + /// Base64, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/). + 'M' => Base64Pad, + /// Base64 url, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_). + 'u' => Base64Url, + /// Base64 url, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_). + 'U' => Base64UrlPad, +} diff --git a/src/encoding.rs b/src/encoding.rs index 9f57a6a..2afe1dc 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -1,6 +1,21 @@ use data_encoding::Encoding; use data_encoding_macro::new_encoding; +// At a later point (probably when floating point arithmatic in const fn is stable) the below functions can be const + +#[cfg(feature = "alloc")] +/// math comes from here https://github.com/bitcoin/bitcoin/blob/f1e2f2a85962c1664e4e55471061af0eaa798d40/src/base58.cpp#L94 +pub(crate) fn calc_encoded_size(base: usize, input_byte_size: usize) -> usize { + (input_byte_size as f64 * (f64::log10(256.0) / f64::log10(base as f64))) as usize + 1 +} + +#[cfg(feature = "alloc")] +/// math comes from here https://github.com/bitcoin/bitcoin/blob/f1e2f2a85962c1664e4e55471061af0eaa798d40/src/base58.cpp#L48 +pub(crate) fn calc_decoded_size(base: usize, input_byte_size: usize) -> usize { + f64::ceil(input_byte_size as f64 * (f64::log10(base as f64) / f64::log10(256.0)) + 1.0) as usize + // this shouldn't need an extra one or ceiling, something is wrong somewhere +} + // Base2 (alphabet: 01) pub const BASE2: Encoding = new_encoding! { symbols: "01", @@ -11,6 +26,7 @@ pub const BASE8: Encoding = new_encoding! { symbols: "01234567", }; +#[cfg(feature = "alloc")] /// Base10 (alphabet: 0123456789) pub const BASE10: &str = "0123456789"; @@ -85,15 +101,19 @@ pub const BASE32Z: Encoding = new_encoding! { symbols: "ybndrfg8ejkmcpqxot1uwisza345h769", }; +#[cfg(feature = "alloc")] /// Base36, [0-9a-z] no padding (alphabet: 0123456789abcdefghijklmnopqrstuvwxyz). pub const BASE36_LOWER: &str = "0123456789abcdefghijklmnopqrstuvwxyz"; +#[cfg(feature = "alloc")] /// Base36, [0-9A-Z] no padding (alphabet: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ). pub const BASE36_UPPER: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +#[cfg(feature = "alloc")] // Base58 Flickr's alphabet for creating short urls from photo ids. pub const BASE58_FLICKR: &str = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; +#[cfg(feature = "alloc")] // Base58 Bitcoin's alphabet as defined in their Base58Check encoding. pub const BASE58_BITCOIN: &str = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; diff --git a/src/error.rs b/src/error.rs index 0c18c1b..d3b0905 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,6 +10,12 @@ pub enum Error { UnknownBase(char), /// Invalid string. InvalidBaseString, + /// Decode Err + DecodeError(data_encoding::DecodeError), + /// Encoding/Decode Failed + WriteFail(data_encoding::DecodePartial), + /// Mismatched sizes + MismatchedSizes(usize, usize), } impl fmt::Display for Error { @@ -17,6 +23,13 @@ impl fmt::Display for Error { match self { Error::UnknownBase(code) => write!(f, "Unknown base code: {}", code), Error::InvalidBaseString => write!(f, "Invalid base string"), + Error::DecodeError(err) => write!(f, "Error decoding something: {:?}", err), + Error::WriteFail(partial) => write!(f, "Partial Decoding: {:?}", partial), + Error::MismatchedSizes(input, output) => write!( + f, + "Input and output slices are different sizes {}:{}", + input, output + ), } } } @@ -24,6 +37,7 @@ impl fmt::Display for Error { #[cfg(feature = "std")] impl std::error::Error for Error {} +#[cfg(feature = "alloc")] impl From for Error { fn from(_: base_x::DecodeError) -> Self { Self::InvalidBaseString @@ -35,3 +49,9 @@ impl From for Error { Self::InvalidBaseString } } + +impl From for Error { + fn from(err: data_encoding::DecodePartial) -> Self { + Self::WriteFail(err) + } +} diff --git a/src/impls.rs b/src/impls.rs index 2d55652..df39c91 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -1,7 +1,7 @@ use crate::encoding; -use crate::error::Result; +use crate::error::{Error, Result}; -#[cfg(not(feature = "std"))] +#[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; macro_rules! derive_base_encoding { @@ -12,22 +12,46 @@ macro_rules! derive_base_encoding { pub(crate) struct $type; impl BaseCodec for $type { + #[cfg(feature = "alloc")] fn encode>(input: I) -> String { $encoding.encode(input.as_ref()) } + #[cfg(feature = "alloc")] fn decode>(input: I) -> Result> { Ok($encoding.decode(input.as_ref().as_bytes())?) } + + fn encode_mut>(input: I, output: &mut [u8]){ + $encoding.encode_mut(input.as_ref(), output) + } + + fn encode_len(len: usize) -> usize { + $encoding.encode_len(len) + } + + fn decode_mut>(input: I, output: &mut [u8]) -> Result { + let input_len = $encoding.decode_len(input.as_ref().len())?; + if input_len != output.len() { + return Err(Error::MismatchedSizes(input_len, output.len())) + } + $encoding.decode_mut(input.as_ref().as_bytes(), output).map_err(Error::WriteFail) + } + + fn decode_len(len: usize) -> Result { + $encoding.decode_len(len).map_err(Error::DecodeError) + } } )* }; } +#[cfg(feature = "alloc")] macro_rules! derive_base_x { ( $(#[$doc:meta] $type:ident, $encoding:expr;)* ) => { $( #[$doc] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub(crate) struct $type; @@ -39,31 +63,53 @@ macro_rules! derive_base_x { fn decode>(input: I) -> Result> { Ok(base_x::decode($encoding, input.as_ref())?) } + + fn encode_mut>(input: I, output: &mut [u8]){ + let out = base_x::encode($encoding, input.as_ref()); + output[..out.len()].copy_from_slice(out.as_bytes()); + } + + fn encode_len(len: usize) -> usize { + encoding::calc_encoded_size($encoding.chars().count(), len) + } + + fn decode_mut>(input: I, output: &mut [u8]) -> Result { + let out = base_x::decode($encoding, input.as_ref())?; + println!("{}\n{:?}\n{:?}", out.len(), out, output); + output[..out.len()].copy_from_slice(out.as_slice()); + Ok(out.len()) + } + + fn decode_len(len: usize) -> Result { + Ok(encoding::calc_decoded_size($encoding.chars().count(), len)) + } } )* }; } pub(crate) trait BaseCodec { + #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] /// Encode with the given byte slice. fn encode>(input: I) -> String; + #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] /// Decode with the given string. fn decode>(input: I) -> Result>; -} -/// Identity, 8-bit binary (encoder and decoder keeps data unmodified). -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub(crate) struct Identity; + /// Encode with the given byte slice to a mutable slice. + fn encode_mut>(input: I, output: &mut [u8]); -impl BaseCodec for Identity { - fn encode>(input: I) -> String { - String::from_utf8(input.as_ref().to_vec()).expect("input must be valid UTF-8 bytes") - } + /// Returns the encoded length of an input of length `len` + fn encode_len(len: usize) -> usize; - fn decode>(input: I) -> Result> { - Ok(input.as_ref().as_bytes().to_vec()) - } + /// Encode with the given byte slice to a mutable slice. + fn decode_mut>(input: I, output: &mut [u8]) -> Result; + + /// Returns the decoded length of an input of length `len` + fn decode_len(len: usize) -> Result; } derive_base_encoding! { @@ -103,6 +149,7 @@ derive_base_encoding! { Base64UrlPad, encoding::BASE64URL_PAD; } +#[cfg(feature = "alloc")] derive_base_x! { /// Base10 (alphabet: 0123456789). Base10, encoding::BASE10; @@ -112,26 +159,52 @@ derive_base_x! { Base58Btc, encoding::BASE58_BITCOIN; } +#[cfg(feature = "alloc")] /// Base36, [0-9a-z] no padding (alphabet: abcdefghijklmnopqrstuvwxyz0123456789). #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub(crate) struct Base36Lower; +#[cfg(feature = "alloc")] impl BaseCodec for Base36Lower { fn encode>(input: I) -> String { base_x::encode(encoding::BASE36_LOWER, input.as_ref()) } fn decode>(input: I) -> Result> { - // The input is case insensitive, hence lowercase it + // The input is case in-sensitive, hence lowercase it let lowercased = input.as_ref().to_ascii_lowercase(); Ok(base_x::decode(encoding::BASE36_LOWER, &lowercased)?) } + + fn encode_mut>(input: I, output: &mut [u8]) { + let out = base_x::encode(encoding::BASE36_LOWER, input.as_ref()); + output[..out.len()].copy_from_slice(out.as_bytes()); + } + + fn encode_len(len: usize) -> usize { + encoding::calc_encoded_size(encoding::BASE36_LOWER.chars().count(), len) + } + + fn decode_mut>(input: I, output: &mut [u8]) -> Result { + let out = base_x::decode(encoding::BASE36_LOWER, input.as_ref())?; + output[..out.len()].copy_from_slice(out.as_slice()); + Ok(out.len()) + } + + fn decode_len(len: usize) -> Result { + Ok(encoding::calc_decoded_size( + encoding::BASE36_LOWER.chars().count(), + len, + )) + } } +#[cfg(feature = "alloc")] /// Base36, [0-9A-Z] no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789). #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub(crate) struct Base36Upper; +#[cfg(feature = "alloc")] impl BaseCodec for Base36Upper { fn encode>(input: I) -> String { base_x::encode(encoding::BASE36_UPPER, input.as_ref()) @@ -142,4 +215,59 @@ impl BaseCodec for Base36Upper { let uppercased = input.as_ref().to_ascii_uppercase(); Ok(base_x::decode(encoding::BASE36_UPPER, &uppercased)?) } + + fn encode_mut>(input: I, output: &mut [u8]) { + let out = base_x::encode(encoding::BASE36_UPPER, input.as_ref()); + output[..out.len()].copy_from_slice(out.as_bytes()); + } + + fn encode_len(len: usize) -> usize { + encoding::calc_encoded_size(encoding::BASE36_UPPER.chars().count(), len) + } + + fn decode_mut>(input: I, output: &mut [u8]) -> Result { + let out = base_x::decode(encoding::BASE36_UPPER, input.as_ref())?; + output[..out.len()].copy_from_slice(out.as_slice()); + Ok(out.len()) + } + + fn decode_len(len: usize) -> Result { + Ok(encoding::calc_decoded_size( + encoding::BASE36_UPPER.chars().count(), + len, + )) + } +} + +/// Identity, 8-bit binary (encoder and decoder keeps data unmodified). +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub(crate) struct Identity; + +impl BaseCodec for Identity { + #[cfg(feature = "alloc")] + fn encode>(input: I) -> String { + String::from_utf8(input.as_ref().to_vec()).expect("input must be valid UTF-8 bytes") + } + + #[cfg(feature = "alloc")] + fn decode>(input: I) -> Result> { + Ok(input.as_ref().as_bytes().to_vec()) + } + + fn encode_mut>(input: I, output: &mut [u8]) { + output.copy_from_slice(input.as_ref()); + } + + fn encode_len(len: usize) -> usize { + len + } + + fn decode_mut>(input: I, output: &mut [u8]) -> Result { + output.copy_from_slice(input.as_ref().as_bytes()); + Ok(input.as_ref().len()) + } + + fn decode_len(len: usize) -> Result { + Ok(len) + } } diff --git a/src/lib.rs b/src/lib.rs index 3b7960d..29aabda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,31 @@ -//! # multibase -//! //! Implementation of [multibase](https://github.com/multiformats/multibase) in Rust. +//! +//! ### Feature flags +//! +//! Default is `std` +//! +//! | Feature | Notes | +//! | --- | --- | +//! | `std` | Includes std::Error conversions and alloc | +//! | `alloc` | Includes non-byte aligned encodings from [base-x](https://github.com/OrKoN/base-x-rs) `Base10`, `Base58(Flickr/Btc)`, and `Base36(Lower/Upper)` | +//! | `[]` | Includes byte aligned encodings from [data-encoding](https://github.com/ia0/data-encoding) writing to mutable slices | +//! +//! +//! ### Notes +//! +//! - This crate relies on the [currently unstable](https://github.com/rust-lang/cargo/issues/7915) **host_dep** feature to compile proc macros with the proper dependencies, thus requiring nightly rustc to use. +//! - `(decode/encode)_mut` and associated `(decode/encode)_len` for base-x are _extremely_ unoptimized and shouldn't be used, they currently only exist for lib simplicity. Base-x is excluded without the alloc flag, where it would be necessary to have such functions anyway. +//! +//! +//! #![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(not(feature = "std"))] +#[cfg(feature = "alloc")] extern crate alloc; -#[cfg(not(feature = "std"))] +#[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; mod base; @@ -31,6 +48,8 @@ pub use self::error::{Error, Result}; /// (Base::Base58Btc, b"hello".to_vec()) /// ); /// ``` +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn decode>(input: T) -> Result<(Base, Vec)> { let input = input.as_ref(); let code = input.chars().next().ok_or(Error::InvalidBaseString)?; @@ -39,6 +58,35 @@ pub fn decode>(input: T) -> Result<(Base, Vec)> { Ok((base, decoded)) } +/// Decode the base string into a mutable slice. +/// +/// NOTE: while you _can_ use this method for non-byte aligned encodings, the `decode` method does exactly the same thing in a more ergonomic way. +/// This is designed to be used when there is no global allocator. +/// +/// # Examples +/// +/// ``` +/// use multibase::{Base, decode_mut}; +/// +/// let input = "MaGVsbG8gd29ybGQ="; +/// let mut buffer = &mut [0u8; 255]; +/// +/// let code = input.chars().next().unwrap(); +/// let base = Base::from_code(code).unwrap(); +/// +/// let output = &mut buffer[0 .. base.decode_len(input.len()).unwrap()]; +/// decode_mut(base, input, output).unwrap(); +/// assert_eq!( +/// (base, core::str::from_utf8(output).unwrap().trim_end_matches('\u{0}')), +/// (Base::Base64Pad, "hello world") +/// ); +/// ``` +pub fn decode_mut>(base: Base, input: T, output: &mut [u8]) -> Result<()> { + let input = input.as_ref(); + base.decode_mut(&input[base.code().len_utf8()..], output)?; + Ok(()) +} + /// Encode with the given byte slice to base string. /// /// # Examples @@ -48,9 +96,42 @@ pub fn decode>(input: T) -> Result<(Base, Vec)> { /// /// assert_eq!(encode(Base::Base58Btc, b"hello"), "zCn8eVZg"); /// ``` +/// +/// Panics where `output.len() != base.encode_len()` +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn encode>(base: Base, input: T) -> String { let input = input.as_ref(); let mut encoded = base.encode(input.as_ref()); encoded.insert(0, base.code()); encoded } + +/// Encode the given byte slice to a mutable slice. +/// +/// NOTE: while you _can_ use this method for non-byte aligned encodings, the `encode` method does exactly the same thing in a more ergonomic way. +/// This is designed to be used when there is no global allocator. +/// +/// # Examples +/// +/// ``` +/// use multibase::{Base, encode_mut}; +/// +/// let input = "hello world"; +/// let buffer = &mut [0u8; 255]; +/// +/// let base = Base::Base64Pad; +/// +/// let output = &mut buffer[0 .. base.encode_len(input.len())]; +/// encode_mut(base, input, output); +/// assert_eq!( +/// core::str::from_utf8(output).unwrap(), +/// "MaGVsbG8gd29ybGQ=" +/// ); +/// ``` +/// +/// Panics where `output.len() != base.encode_len()` +pub fn encode_mut>(base: Base, input: T, output: &mut [u8]) { + base.code().encode_utf8(output); + base.encode_mut(input.as_ref(), &mut output[base.code().len_utf8()..]); +} diff --git a/tests/lib.rs b/tests/lib.rs index 26b8a4e..b594d4c 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,4 +1,6 @@ -use multibase::{decode, encode, Base, Base::*}; +use multibase::{decode, decode_mut, encode, encode_mut, Base, Base::*}; + +// TODO: tests with no-alloc and mut encode/decode fn encode_decode_assert(input: &[u8], test_cases: Vec<(Base, &str)>) { for (base, output) in test_cases { @@ -7,6 +9,57 @@ fn encode_decode_assert(input: &[u8], test_cases: Vec<(Base, &str)>) { } } +fn encode_decode_mut_assert(input: &[u8], test_cases: Vec<(Base, &str)>) { + for (base, output) in test_cases { + { + let buffer = &mut [0u8; 255]; + let out_buf = &mut buffer[0..base.encode_len(input.len())]; + + println!( + "\nencode len {} base {:?}", + base.encode_len(input.len()), + base + ); + encode_mut(base, input, out_buf); + assert_eq!( + core::str::from_utf8(out_buf) + .unwrap() + .trim_end_matches('\u{0}'), + output, + "h {:?}", + base + ); + } + { + let buffer = &mut [0u8; 255]; + let code = output.chars().next().unwrap(); + let out_base = Base::from_code(code).unwrap(); + let out_buf = &mut buffer[0..out_base.decode_len(output.len()).unwrap()]; + + println!( + "decode len {}>{} base {:?}", + out_base.decode_len(output.len()).unwrap(), + out_buf.len(), + base + ); + decode_mut(base, output, out_buf).unwrap(); + assert_eq!( + ( + out_base, + core::str::from_utf8(out_buf) + .unwrap() + .trim_end_matches('\u{0}') + ), + (base, core::str::from_utf8(input).unwrap()), + "h {:?}", + base + ); + } + // assert_eq!(decode(output).unwrap(), (base, input.to_vec())); + println!("done") + } +} + #[test] fn test_bases_code() { assert_eq!(Identity.code(), '\x00'); @@ -65,7 +118,8 @@ fn test_basic() { (Base64Url, "ueWVzIG1hbmkgIQ"), (Base64UrlPad, "UeWVzIG1hbmkgIQ=="), ]; - encode_decode_assert(input, test_cases); + encode_decode_assert(input, test_cases.clone()); + encode_decode_mut_assert(input, test_cases); } #[test] @@ -96,7 +150,8 @@ fn preserves_leading_zero() { (Base64Url, "uAHllcyBtYW5pICE"), (Base64UrlPad, "UAHllcyBtYW5pICE="), ]; - encode_decode_assert(input, test_cases); + encode_decode_assert(input, test_cases.clone()); + encode_decode_mut_assert(input, test_cases); } #[test] @@ -128,12 +183,24 @@ fn preserves_two_leading_zeroes() { (Base64UrlPad, "UAAB5ZXMgbWFuaSAh"), ]; - encode_decode_assert(input, test_cases); + encode_decode_assert(input, test_cases.clone()); + encode_decode_mut_assert(input, test_cases); +} + +#[test] +fn long_content() { + let input = b"kajyvjjynjpofuyctkqmljxmrjchookbohimmyxoacqaxajhoqfsdarmvadwwrylxxnsbjelghuuaueujgyquookunfguflxbylozgpiorklewsplgooqecqebzfsxisxbvwtoafzyxmrgxctmttrvwlyhwsquwp"; + let test_cases = vec![ + (Base58Btc, "z4GKnabjuGcmhEc8DwLU8oUTHK1QRXx6bJ1KNYgWv7cTMe46eNJf833CoKjbY8KGhgoYbTcuaPNvvZpJkfDMx9bKwDVs4TtgV7diXHgU4YS8iTLLVav5t5M8UoUURrbxio24VdBJNLW67T8AsegLS5WSBYrB64oLoHfR7PEBkhXQ3pXWtYVqiT5giyejgubNQGzVjR8ANXkfgGfdpjQrTrcSuqq1"), + ]; + encode_decode_assert(input, test_cases.clone()); + encode_decode_mut_assert(input, test_cases); } #[test] fn case_insensitivity() { let input = b"hello world"; + // excludes base-x let test_cases = vec![ (Base16Lower, "f68656c6c6f20776F726C64"), (Base16Upper, "F68656c6c6f20776F726C64"),