diff --git a/Cargo.toml b/Cargo.toml index 6f61304..412fc43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,14 @@ keywords = ["ipld", "ipfs", "multihash", "cid", "no_std"] [features] default = ["std"] -std = ["data-encoding/std"] +std = ["data-encoding/std", "alloc"] +alloc = ["base-x"] +unstable = ["smol-base-x/unstable"] [dependencies] -base-x = { version = "0.2.7", default-features = false } +base-x = { version = "0.2.7", default-features = false, optional = true } +smol-base-x = {version = "0.1.0"} +heapless = "0.7.14" data-encoding = { version = "2.3.1", default-features = false, features = ["alloc"] } data-encoding-macro = "0.1.9" diff --git a/src/base.rs b/src/base.rs index c6a734b..48f8496 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,11 +1,17 @@ +use core::convert::TryFrom; + use crate::error::{Error, Result}; use crate::impls::*; -#[cfg(not(feature = "std"))] +#[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::{string::String, vec::Vec}; +#[cfg(feature = "alloc")] +use crate::impls::alloc::*; + +#[cfg(feature = "alloc")] macro_rules! build_base_enum { - ( $(#[$attr:meta] $code:expr => $base:ident,)* ) => { + ( $(#[$attr:meta] $base:ident,)* ) => { /// List of types currently supported in the multibase spec. /// /// Not all base types are supported by this library. @@ -13,12 +19,11 @@ macro_rules! build_base_enum { pub enum Base { $( #[$attr] $base, )* } - impl Base { /// Convert a number to the matching base algorithm, or `Error` if no algorithm is matching. pub fn from_code(code: char) -> Result { match code { - $( $code => Ok(Self::$base), )* + $( $base::CODE => Ok(Self::$base), )* _ => Err(Error::UnknownBase(code)), } } @@ -26,14 +31,14 @@ macro_rules! build_base_enum { /// Get the code corresponding to the base algorithm. pub fn code(&self) -> char { match self { - $( Self::$base => $code, )* + $( Self::$base => $base::CODE, )* } } /// Encode the given byte slice to base string. pub fn encode>(&self, input: I) -> String { match self { - $( Self::$base => $base::encode(input), )* + $( Self::$base => $base::encode(input).unwrap(), )* // encode wont panic for String } } @@ -44,54 +49,164 @@ macro_rules! build_base_enum { } } } + + } } +#[cfg(feature = "alloc")] build_base_enum! { /// 8-bit binary (encoder and decoder keeps data unmodified). - '\x00' => Identity, + Identity, /// Base2 (alphabet: 01). - '0' => Base2, + Base2, /// Base8 (alphabet: 01234567). - '7' => Base8, + Base8, /// Base10 (alphabet: 0123456789). - '9' => Base10, + Base10, /// Base16 lower hexadecimal (alphabet: 0123456789abcdef). - 'f' => Base16Lower, + Base16Lower, /// Base16 upper hexadecimal (alphabet: 0123456789ABCDEF). - 'F' => Base16Upper, + Base16Upper, /// Base32, rfc4648 no padding (alphabet: abcdefghijklmnopqrstuvwxyz234567). - 'b' => Base32Lower, + Base32Lower, /// Base32, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567). - 'B' => Base32Upper, + Base32Upper, /// Base32, rfc4648 with padding (alphabet: abcdefghijklmnopqrstuvwxyz234567). - 'c' => Base32PadLower, + Base32PadLower, /// Base32, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567). - 'C' => Base32PadUpper, + Base32PadUpper, /// Base32hex, rfc4648 no padding (alphabet: 0123456789abcdefghijklmnopqrstuv). - 'v' => Base32HexLower, + Base32HexLower, /// Base32hex, rfc4648 no padding (alphabet: 0123456789ABCDEFGHIJKLMNOPQRSTUV). - 'V' => Base32HexUpper, + Base32HexUpper, /// Base32hex, rfc4648 with padding (alphabet: 0123456789abcdefghijklmnopqrstuv). - 't' => Base32HexPadLower, + Base32HexPadLower, /// Base32hex, rfc4648 with padding (alphabet: 0123456789ABCDEFGHIJKLMNOPQRSTUV). - 'T' => Base32HexPadUpper, + Base32HexPadUpper, /// z-base-32 (used by Tahoe-LAFS) (alphabet: ybndrfg8ejkmcpqxot1uwisza345h769). - 'h' => Base32Z, + Base32Z, /// Base36, [0-9a-z] no padding (alphabet: 0123456789abcdefghijklmnopqrstuvwxyz). - 'k' => Base36Lower, + Base36Lower, /// Base36, [0-9A-Z] no padding (alphabet: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ). - 'K' => Base36Upper, + Base36Upper, + /// Base58 flicker (alphabet: 123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ). + Base58Flickr, + /// Base58 bitcoin (alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz). + Base58Btc, + /// Base64, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/). + Base64, + /// Base64, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/). + Base64Pad, + /// Base64 url, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_). + Base64Url, + /// Base64 url, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_). + Base64UrlPad, +} + +macro_rules! build_base_const_enum { + ( $(#[$attr:meta] $base:ident,)* ) => { + /// List of types currently supported in the multibase spec. + /// + /// Not all base types are supported by this. + /// + /// TODO doc some more things + /// + /// ``` + /// use smol_base_x::util::encoded_arr_size; + /// use multibase::StackBase; + /// // type of + /// type AAA = StackBase<32, {encoded_arr_size(10, 32)}>; + /// ``` + #[derive(PartialEq, Eq, Clone, Copy, Debug)] + pub enum StackBase { + $( #[$attr] $base, )* + } + impl StackBase { + /// Convert a number to the matching base algorithm, or `Error` if no algorithm is matching. + pub fn from_code(code: char) -> Result { + match code { + $( $base::CODE => Ok(Self::$base), )* + _ => Err(Error::UnknownBase(code)), + } + } + + /// Get the code corresponding to the base algorithm. + pub fn code(&self) -> char { + match self { + $( Self::$base => $base::CODE, )* + } + } + + /// Encode the given byte slice to base string. + pub fn encode>(&self, input: I) -> Result> { + match self { + $( Self::$base => <$base as BaseCodec, heapless::Vec>>::encode(input), )* + } + } + + /// Decode the base string. + pub fn decode>(&self, input: I) -> Result> { + match self { + $( Self::$base => <$base as BaseCodec, heapless::Vec>>::decode(input), )* + } + } + } + + + } +} + +use smol::{Base10S, Base58BtcS, Base58FlickrS}; + +build_base_const_enum! { + /// Base10 (alphabet: 0123456789). + Base10S, /// Base58 flicker (alphabet: 123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ). - 'Z' => Base58Flickr, + Base58FlickrS, /// Base58 bitcoin (alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz). - 'z' => Base58Btc, + Base58BtcS, + /// Base2 (alphabet: 01). + Base2, + /// Base8 (alphabet: 01234567). + Base8, + /// Base16 lower hexadecimal (alphabet: 0123456789abcdef). + Base16Lower, + /// Base16 upper hexadecimal (alphabet: 0123456789ABCDEF). + Base16Upper, + /// Base32, rfc4648 no padding (alphabet: abcdefghijklmnopqrstuvwxyz234567). + Base32Lower, + /// Base32, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567). + Base32Upper, + /// Base32, rfc4648 with padding (alphabet: abcdefghijklmnopqrstuvwxyz234567). + Base32PadLower, + /// Base32, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567). + Base32PadUpper, + /// Base32hex, rfc4648 no padding (alphabet: 0123456789abcdefghijklmnopqrstuv). + Base32HexLower, + /// Base32hex, rfc4648 no padding (alphabet: 0123456789ABCDEFGHIJKLMNOPQRSTUV). + Base32HexUpper, + /// Base32hex, rfc4648 with padding (alphabet: 0123456789abcdefghijklmnopqrstuv). + Base32HexPadLower, + /// Base32hex, rfc4648 with padding (alphabet: 0123456789ABCDEFGHIJKLMNOPQRSTUV). + Base32HexPadUpper, + /// z-base-32 (used by Tahoe-LAFS) (alphabet: ybndrfg8ejkmcpqxot1uwisza345h769). + Base32Z, /// Base64, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/). - 'm' => Base64, + Base64, /// Base64, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/). - 'M' => Base64Pad, + Base64Pad, /// Base64 url, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_). - 'u' => Base64Url, + Base64Url, /// Base64 url, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_). - 'U' => Base64UrlPad, + Base64UrlPad, +} + +#[cfg(feature = "alloc")] +impl TryFrom for Base { + type Error = Error; + + fn try_from(value: StackBase) -> Result { + Base::from_code(value.code()) + } } diff --git a/src/error.rs b/src/error.rs index 0c18c1b..bd46223 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,6 +10,8 @@ pub enum Error { UnknownBase(char), /// Invalid string. InvalidBaseString, + /// Container being written into is too small. + ContainerTooSmall, } impl fmt::Display for Error { @@ -17,6 +19,7 @@ impl fmt::Display for Error { match self { Error::UnknownBase(code) => write!(f, "Unknown base code: {}", code), Error::InvalidBaseString => write!(f, "Invalid base string"), + Error::ContainerTooSmall => write!(f, "Output container too small"), } } } @@ -24,6 +27,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 diff --git a/src/impls.rs b/src/impls.rs index 2d55652..d9db2a8 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -1,42 +1,82 @@ -use crate::encoding; +use crate::{encoding, Error}; use crate::error::Result; -#[cfg(not(feature = "std"))] +#[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::{string::String, vec::Vec}; macro_rules! derive_base_encoding { - ( $(#[$doc:meta] $type:ident, $encoding:expr;)* ) => { + ( $(#[$doc:meta] $code:literal => $type:ident, $encoding:expr;)* ) => { $( #[$doc] #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub(crate) struct $type; - impl BaseCodec for $type { - fn encode>(input: I) -> String { - $encoding.encode(input.as_ref()) + impl CodecCode for $type { + const CODE: char = $code; + } + + #[cfg(feature = "alloc")] + impl BaseCodec> for $type { + fn encode(input: impl AsRef<[u8]>) -> Result { + Ok($encoding.encode(input.as_ref())) } - fn decode>(input: I) -> Result> { + fn decode(input: impl AsRef) -> Result> { Ok($encoding.decode(input.as_ref().as_bytes())?) } } + + impl BaseCodec, heapless::Vec> for $type { + fn encode(input: impl AsRef<[u8]>) -> Result> { + let input = input.as_ref(); + let mut s = heapless::String::::default(); + + // SAFETY: trait Base should only contain ascii chars (otherwise would be a borken base implementation) + unsafe { + let vec = s.as_mut_vec(); + // resize is a safe operation + let len = $encoding.encode_len(input.len()); + + vec.resize(len, 0) + .map_err(|_| Error::ContainerTooSmall)?; + // skips first byte to leave room for multibase code + $encoding.encode_mut(input, &mut vec[1..]); + } + Ok(s) + } + + fn decode(input: impl AsRef) -> Result> { + let input = input.as_ref(); + let mut buf = heapless::Vec::::default(); + let len = $encoding.decode_len(input.len())?; + buf.resize(len, 0) + .map_err(|_| Error::ContainerTooSmall)?; + $encoding.decode_mut(input.as_bytes(), &mut buf).unwrap(); + Ok(buf) + } + } + )* }; } macro_rules! derive_base_x { - ( $(#[$doc:meta] $type:ident, $encoding:expr;)* ) => { + ( $(#[$doc:meta] $code:literal => $type:ident, $encoding:expr;)* ) => { $( #[$doc] #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub(crate) struct $type; - impl BaseCodec for $type { - fn encode>(input: I) -> String { - base_x::encode($encoding, input.as_ref()) + impl CodecCode for $type { + const CODE: char = $code; + } + + impl BaseCodec> for $type { + fn encode(input: impl AsRef<[u8]>) -> Result { + Ok(base_x::encode($encoding, input.as_ref())) } - fn decode>(input: I) -> Result> { + fn decode(input: impl AsRef) -> Result> { Ok(base_x::decode($encoding, input.as_ref())?) } } @@ -44,102 +84,200 @@ macro_rules! derive_base_x { }; } -pub(crate) trait BaseCodec { - /// Encode with the given byte slice. - fn encode>(input: I) -> String; - - /// Decode with the given string. - fn decode>(input: I) -> Result>; +pub(crate) trait CodecCode { + const CODE: char; } -/// Identity, 8-bit binary (encoder and decoder keeps data unmodified). -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub(crate) struct Identity; - -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") - } - - fn decode>(input: I) -> Result> { - Ok(input.as_ref().as_bytes().to_vec()) - } +/// Trait codecs use for encoding and decoding, generic over their output continer +/// output container of encode must have the ability to prepend one byte for the codec code +pub(crate) trait BaseCodec: CodecCode { + /// Encode with the given byte slice. + fn encode(input: impl AsRef<[u8]>) -> Result; + /// Decode with the given string (as slice). + fn decode(input: impl AsRef) -> Result; } derive_base_encoding! { /// Base2 (alphabet: 01). - Base2, encoding::BASE2; + '0' => Base2, encoding::BASE2; /// Base8 (alphabet: 01234567). - Base8, encoding::BASE8; + '7' => Base8, encoding::BASE8; /// Base16 lower hexadecimal (alphabet: 0123456789abcdef). - Base16Lower, encoding::BASE16_LOWER; + 'f' => Base16Lower, encoding::BASE16_LOWER; /// Base16 upper hexadecimal (alphabet: 0123456789ABCDEF). - Base16Upper, encoding::BASE16_UPPER; + 'F' => Base16Upper, encoding::BASE16_UPPER; /// Base32, rfc4648 no padding (alphabet: abcdefghijklmnopqrstuvwxyz234567). - Base32Lower, encoding::BASE32_NOPAD_LOWER; + 'b' => Base32Lower, encoding::BASE32_NOPAD_LOWER; /// Base32, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567). - Base32Upper, encoding::BASE32_NOPAD_UPPER; + 'B' => Base32Upper, encoding::BASE32_NOPAD_UPPER; /// Base32, rfc4648 with padding (alphabet: abcdefghijklmnopqrstuvwxyz234567). - Base32PadLower, encoding::BASE32_PAD_LOWER; + 'c' => Base32PadLower, encoding::BASE32_PAD_LOWER; /// Base32, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567). - Base32PadUpper, encoding::BASE32_PAD_UPPER; + 'C' => Base32PadUpper, encoding::BASE32_PAD_UPPER; /// Base32hex, rfc4648 no padding (alphabet: 0123456789abcdefghijklmnopqrstuv). - Base32HexLower, encoding::BASE32HEX_NOPAD_LOWER; + 'v' => Base32HexLower, encoding::BASE32HEX_NOPAD_LOWER; /// Base32hex, rfc4648 no padding (alphabet: 0123456789ABCDEFGHIJKLMNOPQRSTUV). - Base32HexUpper, encoding::BASE32HEX_NOPAD_UPPER; + 'V' => Base32HexUpper, encoding::BASE32HEX_NOPAD_UPPER; /// Base32hex, rfc4648 with padding (alphabet: 0123456789abcdefghijklmnopqrstuv). - Base32HexPadLower, encoding::BASE32HEX_PAD_LOWER; + 't' => Base32HexPadLower, encoding::BASE32HEX_PAD_LOWER; /// Base32hex, rfc4648 with padding (alphabet: 0123456789ABCDEFGHIJKLMNOPQRSTUV). - Base32HexPadUpper, encoding::BASE32HEX_PAD_UPPER; + 'T' => Base32HexPadUpper, encoding::BASE32HEX_PAD_UPPER; /// z-base-32 (used by Tahoe-LAFS) (alphabet: ybndrfg8ejkmcpqxot1uwisza345h769). - Base32Z, encoding::BASE32Z; + 'h' => Base32Z, encoding::BASE32Z; /// Base64, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/). - Base64, encoding::BASE64_NOPAD; + 'm' => Base64, encoding::BASE64_NOPAD; /// Base64, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/). - Base64Pad, encoding::BASE64_PAD; + 'M' => Base64Pad, encoding::BASE64_PAD; /// Base64 url, rfc4648 no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_). - Base64Url, encoding::BASE64URL_NOPAD; + 'u' => Base64Url, encoding::BASE64URL_NOPAD; /// Base64 url, rfc4648 with padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_). - Base64UrlPad, encoding::BASE64URL_PAD; + 'U' => Base64UrlPad, encoding::BASE64URL_PAD; } +#[cfg(feature = "alloc")] derive_base_x! { /// Base10 (alphabet: 0123456789). - Base10, encoding::BASE10; + '9' => Base10, encoding::BASE10; /// Base58 flicker (alphabet: 123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ). - Base58Flickr, encoding::BASE58_FLICKR; + 'Z' => Base58Flickr, encoding::BASE58_FLICKR; /// Base58 bitcoin (alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz). - Base58Btc, encoding::BASE58_BITCOIN; + 'z' => Base58Btc, encoding::BASE58_BITCOIN; } -/// Base36, [0-9a-z] no padding (alphabet: abcdefghijklmnopqrstuvwxyz0123456789). -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub(crate) struct Base36Lower; +#[cfg(feature = "alloc")] +pub(crate) mod alloc { + use crate::encoding; + + /// Identity, 8-bit binary (encoder and decoder keeps data unmodified). + #[derive(PartialEq, Eq, Clone, Copy, Debug)] + pub(crate) struct Identity; -impl BaseCodec for Base36Lower { - fn encode>(input: I) -> String { - base_x::encode(encoding::BASE36_LOWER, input.as_ref()) + impl CodecCode for Identity { + const CODE: char = '\x00'; } - fn decode>(input: I) -> Result> { - // The input is case insensitive, hence lowercase it - let lowercased = input.as_ref().to_ascii_lowercase(); - Ok(base_x::decode(encoding::BASE36_LOWER, &lowercased)?) + impl BaseCodec> for Identity { + fn encode(input: impl AsRef<[u8]>) -> Result { + String::from_utf8(input.as_ref().to_vec()).map_err(|_| crate::Error::InvalidBaseString) + } + + fn decode(input: impl AsRef) -> Result> { + Ok(input.as_ref().as_bytes().to_vec()) + } + } + + use super::{BaseCodec, CodecCode, Result}; + /// Base36, [0-9a-z] no padding (alphabet: abcdefghijklmnopqrstuvwxyz0123456789). + #[derive(PartialEq, Eq, Clone, Copy, Debug)] + pub(crate) struct Base36Lower; + + impl CodecCode for Base36Lower { + const CODE: char = 'k'; + } + + impl BaseCodec> for Base36Lower { + fn encode(input: impl AsRef<[u8]>) -> Result { + Ok(base_x::encode(encoding::BASE36_LOWER, input.as_ref())) + } + + fn decode(input: impl AsRef) -> Result> { + // The input is case insensitive, hence lowercase it + let lowercased = input.as_ref().to_ascii_lowercase(); + Ok(base_x::decode(encoding::BASE36_LOWER, &lowercased)?) + } + } + + /// Base36, [0-9A-Z] no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789). + #[derive(PartialEq, Eq, Clone, Copy, Debug)] + pub(crate) struct Base36Upper; + + impl CodecCode for Base36Upper { + const CODE: char = 'K'; + } + + impl BaseCodec> for Base36Upper { + fn encode(input: impl AsRef<[u8]>) -> Result { + Ok(base_x::encode(encoding::BASE36_UPPER, input.as_ref())) + } + + fn decode(input: impl AsRef) -> Result> { + // The input is case insensitive, hence uppercase it + let uppercased = input.as_ref().to_ascii_uppercase(); + Ok(base_x::decode(encoding::BASE36_UPPER, &uppercased)?) + } } } -/// Base36, [0-9A-Z] no padding (alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789). -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub(crate) struct Base36Upper; +pub mod smol { + + // TODO not unwrap + + use heapless::{String, Vec}; + use smol_base_x::Base; + + use crate::Error; -impl BaseCodec for Base36Upper { - fn encode>(input: I) -> String { - base_x::encode(encoding::BASE36_UPPER, input.as_ref()) + use super::BaseCodec; + use super::CodecCode; + use super::Result; + + // it sucks a lot that I can't + // impl BaseCodec, Vec> for T where + // T: Base + CodecCode + + macro_rules! derive_smol_base_x { + ( $(#[$doc:meta] $code:literal => $base:literal:$type:ident, $encoding:expr;)* ) => { + $( + #[$doc] + #[derive(PartialEq, Eq, Clone, Copy, Debug)] + pub(crate) struct $type; + + impl Base<$base> for $type { + const ALPHABET: [u8; $base] = $encoding; + } + + impl CodecCode for $type { + const CODE: char = $code; + } + + impl BaseCodec, Vec> for $type { + fn encode(input: impl AsRef<[u8]>) -> Result> { + let input = input.as_ref(); + let mut s = String::::default(); + + // SAFETY: trait Base should only contain ascii chars (otherwise would be a borken base implementation) + unsafe { + let vec = s.as_mut_vec(); + // resize is a safe operation + let len = crate::base_x_encoded_size(Self::BASE, input.len()); + vec.resize(len, 0) + .map_err(|_| Error::ContainerTooSmall)?; + // skips first byte to leave room for multibase code + $type::encode_mut(input, &mut vec[1..]).unwrap(); + } + Ok(s) + } + + fn decode(input: impl AsRef) -> Result> { + let input = input.as_ref(); + let mut buf = Vec::::default(); + let len = crate::base_x_decoded_size(Self::BASE, input.len()); + buf.resize(len, 0) + .map_err(|_| Error::ContainerTooSmall)?; + $type::decode_mut(input, &mut buf).unwrap(); + Ok(buf) + } + } + )* + }; } - fn decode>(input: I) -> Result> { - // The input is case insensitive, hence uppercase it - let uppercased = input.as_ref().to_ascii_uppercase(); - Ok(base_x::decode(encoding::BASE36_UPPER, &uppercased)?) + derive_smol_base_x! { + /// Base10 (alphabet: 0123456789). + '9' => 10:Base10S, *b"0123456789"; + /// Base58 bitcoin (alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz). + 'z' => 58:Base58BtcS, *b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + /// Base58 flicker (alphabet: 123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ). + 'Z' => 58:Base58FlickrS, *b"123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; } } diff --git a/src/lib.rs b/src/lib.rs index 3b7960d..8da97ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,10 +5,10 @@ #![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(not(feature = "std"))] +#[cfg(all(feature = "alloc", not(feature = "std")))] extern crate alloc; -#[cfg(not(feature = "std"))] +#[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::{string::String, vec::Vec}; mod base; @@ -16,9 +16,14 @@ mod encoding; mod error; mod impls; +#[cfg(feature = "alloc")] pub use self::base::Base; + +pub use self::base::StackBase; pub use self::error::{Error, Result}; +pub use heapless::{Vec as StackVec, String as StackString}; +#[cfg(feature = "alloc")] /// Decode the base string. /// /// # Examples @@ -39,6 +44,7 @@ pub fn decode>(input: T) -> Result<(Base, Vec)> { Ok((base, decoded)) } +#[cfg(feature = "alloc")] /// Encode with the given byte slice to base string. /// /// # Examples @@ -50,7 +56,59 @@ pub fn decode>(input: T) -> Result<(Base, Vec)> { /// ``` pub fn encode>(base: Base, input: T) -> String { let input = input.as_ref(); - let mut encoded = base.encode(input.as_ref()); + let mut encoded = base.encode(input); encoded.insert(0, base.code()); encoded } + +/// Encode with the given byte slice to *stack allocated* base string. +/// +/// # Examples +/// +/// ``` +/// use multibase::{StackBase, encode_arr}; +/// +/// assert_eq!(encode_arr::<32>(StackBase::Base58BtcS, b"hello").unwrap(), "zCn8eVZg"); +/// ``` +pub fn encode_arr( + base: StackBase<0, S>, + input: &[u8], +) -> Result> { + let mut out = base.encode(input)?; + // encode() leaves an open byte in the begining (for stack implementations) + // SAFETY: this trusts that and all (implemented) multibase codes are ascii + unsafe { out.as_mut_vec()[0] = base.code() as u8 }; + Ok(out) +} + +/// Decode the given byte slice to *stack allocated* output buffer. +/// +/// # Examples +/// +/// ``` +/// use multibase::{StackBase, decode_arr}; +/// +/// assert_eq!(decode_arr::<32>("zCn8eVZg").unwrap().1.as_slice(), "hello".as_bytes()); +/// ``` +pub fn decode_arr(input: &str) -> Result<(StackBase, StackVec)> { + let code = input.chars().next().ok_or(Error::InvalidBaseString)?; + let base = StackBase::from_code(code)?; + let decoded = base.decode(&input[code.len_utf8()..])?; + Ok((base, decoded)) +} + +/// generates encoded size of a given base and input length, useful for determining max encoded size +/// +/// given as `smol_base_x::encoded_size + 1` +pub fn base_x_encoded_size(base: usize, bytes_len: usize) -> usize { + use smol_base_x::util::encoded_size; + encoded_size(base, bytes_len) + 1 +} + +/// generates decoded size of a given base and input byte length, useful for determining decoded size +/// +/// given as `smol_base_x::decoded_size - 1` +pub fn base_x_decoded_size(base: usize, bytes_len: usize) -> usize { + use smol_base_x::util::decoded_size; + decoded_size(base, bytes_len) - 1 +}