Skip to content

No alloc #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 16 commits into from
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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).
Expand Down
75 changes: 73 additions & 2 deletions src/base.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -30,23 +30,54 @@ macro_rules! build_base_enum {
}
}

#[cfg(feature = "alloc")]
/// Encode the given byte slice to base string.
pub fn encode<I: AsRef<[u8]>>(&self, input: I) -> String {
match self {
$( Self::$base => $base::encode(input), )*
}
}

#[cfg(feature = "alloc")]
/// Decode the base string.
pub fn decode<I: AsRef<str>>(&self, input: I) -> Result<Vec<u8>> {
match self {
$( Self::$base => $base::decode(input), )*
}
}

/// Decode the base string into a mutable slice.
pub(crate) fn decode_mut<I: AsRef<str>>(&self, input: I, output: &mut [u8]) -> Result<usize> {
match self {
$( Self::$base => $base::decode_mut(input, output), )*
}
}

/// Returns the size of the decoded slice.
pub fn decode_len(&self, len: usize) -> Result<usize> {
match self {
$( Self::$base => $base::decode_len(len - $code.len_utf8()), )*
}
}

/// Encode the given byte slice to mutable slice.
pub(crate) fn encode_mut<I: AsRef<[u8]>>(&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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
}
20 changes: 20 additions & 0 deletions src/encoding.rs
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. This should ideally be:

(input_byte_size as f64 * (f64::log2(base as f64) / 8.0))) as usize
// or
(input_byte_size as f64 / (8.0 / f64::log2(base as f64))) as usize

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely agree, but for whatever reason it fails all test cases without the round up and added 1...

Just rounding up gives one short for base10 in the preserves_two_leading_zeroes test

decode len 11>11 base Base10
12
[0, 0, 121, 101, 115, 32, 109, 97, 110, 105, 32, 33]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
thread 'preserves_two_leading_zeroes' panicked at 'range end index 12 out of range for slice of length 11'

I will double check to make sure I am doing the tests correctly before I think about possible issues with base-x

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good that we have that test :) Your code is using .len_utf8(), that should just be .len() as for base encoding it's really about the bits and bytes and not about utf8 characters.

}

// Base2 (alphabet: 01)
pub const BASE2: Encoding = new_encoding! {
symbols: "01",
Expand All @@ -11,6 +26,7 @@ pub const BASE8: Encoding = new_encoding! {
symbols: "01234567",
};

#[cfg(feature = "alloc")]
/// Base10 (alphabet: 0123456789)
pub const BASE10: &str = "0123456789";

Expand Down Expand Up @@ -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";

Expand Down
20 changes: 20 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,34 @@ 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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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
),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for Error {}

#[cfg(feature = "alloc")]
impl From<base_x::DecodeError> for Error {
fn from(_: base_x::DecodeError) -> Self {
Self::InvalidBaseString
Expand All @@ -35,3 +49,9 @@ impl From<data_encoding::DecodeError> for Error {
Self::InvalidBaseString
}
}

impl From<data_encoding::DecodePartial> for Error {
fn from(err: data_encoding::DecodePartial) -> Self {
Self::WriteFail(err)
}
}
Loading