diff --git a/.travis.yml b/.travis.yml index 8b4b6278..07a9de71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ sudo: required matrix: include: - - rust: 1.31.0 + - rust: 1.34.0 - rust: stable - rust: beta - rust: nightly @@ -19,6 +19,12 @@ matrix: - cargo tarpaulin --version || RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --force cargo-tarpaulin - cargo fuzz --version || cargo install --force cargo-fuzz + # no_std + - rust: stable + env: TARGET="--target thumbv6m-none-eabi" FEATURES="--no-default-features --features alloc" + install: + - rustup target add thumbv6m-none-eabi + cache: cargo env: @@ -26,9 +32,10 @@ env: - TERM=dumb script: - - cargo build --verbose - - cargo test --verbose - - cargo doc --verbose + - cargo build --verbose $TARGET --no-default-features + - cargo build --verbose $TARGET $FEATURES + - 'if [[ -z "$TARGET" ]]; then cargo test --verbose; fi' + - 'if [[ -z "$TARGET" ]]; then cargo doc --verbose; fi' - 'if [[ "$TRAVIS_RUST_VERSION" = nightly ]]; then cargo bench --no-run; fi' # run for just a second to confirm that it can build and run ok - 'if [[ "$TRAVIS_RUST_VERSION" = nightly ]]; then cargo fuzz list | xargs -L 1 -I FUZZER cargo fuzz run FUZZER -- -max_total_time=1; fi' diff --git a/Cargo.toml b/Cargo.toml index d89d95c8..107537a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ description = "encodes and decodes base64 as bytes or utf8" repository = "https://github.com/marshallpierce/rust-base64" documentation = "https://docs.rs/base64" readme = "README.md" -keywords = ["base64", "utf8", "encode", "decode"] +keywords = ["base64", "utf8", "encode", "decode", "no_std"] categories = ["encoding"] license = "MIT/Apache-2.0" edition = "2018" @@ -15,14 +15,16 @@ edition = "2018" name = "benchmarks" harness = false -[dependencies] -byteorder = "1.2.6" - [dev-dependencies] criterion = "0.2" rand = "0.6.1" doc-comment = "0.3" +[features] +default = ["std"] +alloc = [] +std = [] + [profile.bench] # Useful for better disassembly when using `perf record` and `perf annotate` debug = true diff --git a/README.md b/README.md index 4ad8f1e1..073fa073 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ See the [docs](https://docs.rs/base64) for all the details. Rust version compatibility --- -The minimum required Rust version is 1.31.0. +The minimum required Rust version is 1.34.0. Developing --- @@ -50,6 +50,11 @@ Decoding is aided by some pre-calculated tables, which are generated by: cargo run --example make_tables > src/tables.rs.tmp && mv src/tables.rs.tmp src/tables.rs ``` +no_std +--- + +This crate supports no_std. By default the crate targets std via the `std` feature. You can deactivate the `default-features` to target core instead. In that case you lose out on all the functionality revolving around `std::io`, `std::error::Error` and heap allocations. There is an additional `alloc` feature that you can activate to bring back the support for heap allocations. + Profiling --- diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 3d1ed329..3306e102 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,6 +1,8 @@ # Next - TBD +- Minimum rust version 1.34.0 +- `no_std` is now supported via the two new features `alloc` and `std`. # 0.10.1 diff --git a/src/chunked_encoder.rs b/src/chunked_encoder.rs index 24cc2d4c..7d405ace 100644 --- a/src/chunked_encoder.rs +++ b/src/chunked_encoder.rs @@ -2,7 +2,11 @@ use crate::{ encode::{add_padding, encode_to_slice}, Config, }; -use std::{cmp, str}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use alloc::string::String; +use core::cmp; +#[cfg(any(feature = "alloc", feature = "std", test))] +use core::str; /// The output mechanism for ChunkedEncoder's encoded bytes. pub trait Sink { @@ -80,16 +84,19 @@ fn max_input_length(encoded_buf_len: usize, config: Config) -> usize { } // A really simple sink that just appends to a string +#[cfg(any(feature = "alloc", feature = "std", test))] pub(crate) struct StringSink<'a> { string: &'a mut String, } +#[cfg(any(feature = "alloc", feature = "std", test))] impl<'a> StringSink<'a> { pub(crate) fn new(s: &mut String) -> StringSink { StringSink { string: s } } } +#[cfg(any(feature = "alloc", feature = "std", test))] impl<'a> Sink for StringSink<'a> { type Error = (); diff --git a/src/decode.rs b/src/decode.rs index 44a7782c..f9f19c82 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -1,7 +1,12 @@ -use crate::{tables, Config, STANDARD}; -use byteorder::{BigEndian, ByteOrder}; +use crate::{tables, Config}; -use std::{error, fmt, str}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use crate::STANDARD; +#[cfg(any(feature = "alloc", feature = "std", test))] +use alloc::vec::Vec; +#[cfg(any(feature = "std", test))] +use std::error; +use core::fmt; // decode logic operates on chunks of 8 input bytes without padding const INPUT_CHUNK_LEN: usize = 8; @@ -46,6 +51,7 @@ impl fmt::Display for DecodeError { } } +#[cfg(any(feature = "std", test))] impl error::Error for DecodeError { fn description(&self) -> &str { match *self { @@ -74,6 +80,7 @@ impl error::Error for DecodeError { /// println!("{:?}", bytes); ///} ///``` +#[cfg(any(feature = "alloc", feature = "std", test))] pub fn decode>(input: &T) -> Result, DecodeError> { decode_config(input, STANDARD) } @@ -94,6 +101,7 @@ pub fn decode>(input: &T) -> Result, DecodeError /// println!("{:?}", bytes_url); ///} ///``` +#[cfg(any(feature = "alloc", feature = "std", test))] pub fn decode_config>( input: &T, config: Config, @@ -124,6 +132,7 @@ pub fn decode_config>( /// println!("{:?}", buffer); ///} ///``` +#[cfg(any(feature = "alloc", feature = "std", test))] pub fn decode_config_buf>( input: &T, config: Config, @@ -419,6 +428,11 @@ fn decode_helper( Ok(output_index) } +#[inline] +fn write_u64(output: &mut [u8], value: u64) { + output[..8].copy_from_slice(&value.to_be_bytes()); +} + /// Decode 8 bytes of input into 6 bytes of output. 8 bytes of output will be written, but only the /// first 6 of those contain meaningful data. /// @@ -507,7 +521,7 @@ fn decode_chunk( } accum |= (morsel as u64) << 16; - BigEndian::write_u64(output, accum); + write_u64(output, accum); Ok(()) } diff --git a/src/display.rs b/src/display.rs index 3d768294..cc70aac2 100644 --- a/src/display.rs +++ b/src/display.rs @@ -11,8 +11,8 @@ use super::chunked_encoder::ChunkedEncoder; use super::Config; -use std::fmt::{Display, Formatter}; -use std::{fmt, str}; +use core::fmt::{Display, Formatter}; +use core::{fmt, str}; /// A convenience wrapper for base64'ing bytes into a format string without heap allocation. pub struct Base64Display<'a> { diff --git a/src/encode.rs b/src/encode.rs index 061e9579..ad0aa6ad 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -1,5 +1,9 @@ -use crate::{chunked_encoder, Config, STANDARD}; -use byteorder::{BigEndian, ByteOrder}; +use crate::Config; +#[cfg(any(feature = "alloc", feature = "std", test))] +use crate::{chunked_encoder, STANDARD}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use alloc::{string::String, vec}; +use core::convert::TryInto; ///Encode arbitrary octets as base64. ///Returns a String. @@ -15,6 +19,7 @@ use byteorder::{BigEndian, ByteOrder}; /// println!("{}", b64); ///} ///``` +#[cfg(any(feature = "alloc", feature = "std", test))] pub fn encode>(input: &T) -> String { encode_config(input, STANDARD) } @@ -35,6 +40,7 @@ pub fn encode>(input: &T) -> String { /// println!("{}", b64_url); ///} ///``` +#[cfg(any(feature = "alloc", feature = "std", test))] pub fn encode_config>(input: &T, config: Config) -> String { let mut buf = match encoded_size(input.as_ref().len(), config) { Some(n) => vec![0; n], @@ -65,6 +71,7 @@ pub fn encode_config>(input: &T, config: Config) -> Stri /// println!("{}", buf); ///} ///``` +#[cfg(any(feature = "alloc", feature = "std", test))] pub fn encode_config_buf>(input: &T, config: Config, buf: &mut String) { let input_bytes = input.as_ref(); @@ -153,6 +160,11 @@ fn encode_with_padding(input: &[u8], config: Config, encoded_size: usize, output debug_assert_eq!(encoded_size, encoded_bytes); } +#[inline] +fn read_u64(s: &[u8]) -> u64 { + u64::from_be_bytes(s[..8].try_into().unwrap()) +} + /// Encode input bytes to utf8 base64 bytes. Does not pad. /// `output` must be long enough to hold the encoded `input` without padding. /// Returns the number of bytes written. @@ -183,7 +195,7 @@ pub fn encode_to_slice(input: &[u8], output: &mut [u8], encode_table: &[u8; 64]) // Plus, single-digit percentage performance differences might well be quite different // on different hardware. - let input_u64 = BigEndian::read_u64(&input_chunk[0..]); + let input_u64 = read_u64(&input_chunk[0..]); output_chunk[0] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; output_chunk[1] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; @@ -194,7 +206,7 @@ pub fn encode_to_slice(input: &[u8], output: &mut [u8], encode_table: &[u8; 64]) output_chunk[6] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; output_chunk[7] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; - let input_u64 = BigEndian::read_u64(&input_chunk[6..]); + let input_u64 = read_u64(&input_chunk[6..]); output_chunk[8] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; output_chunk[9] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; @@ -205,7 +217,7 @@ pub fn encode_to_slice(input: &[u8], output: &mut [u8], encode_table: &[u8; 64]) output_chunk[14] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; output_chunk[15] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; - let input_u64 = BigEndian::read_u64(&input_chunk[12..]); + let input_u64 = read_u64(&input_chunk[12..]); output_chunk[16] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; output_chunk[17] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; @@ -216,7 +228,7 @@ pub fn encode_to_slice(input: &[u8], output: &mut [u8], encode_table: &[u8; 64]) output_chunk[22] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; output_chunk[23] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; - let input_u64 = BigEndian::read_u64(&input_chunk[18..]); + let input_u64 = read_u64(&input_chunk[18..]); output_chunk[24] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; output_chunk[25] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; diff --git a/src/lib.rs b/src/lib.rs index 2ba1f6d1..ce71e2c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,12 @@ warnings )] #![forbid(unsafe_code)] +#![cfg_attr(not(any(feature = "std", test)), no_std)] + +#[cfg(all(feature = "alloc", not(any(feature = "std", test))))] +extern crate alloc; +#[cfg(any(feature = "std", test))] +extern crate std as alloc; #[cfg(test)] #[macro_use] @@ -72,15 +78,18 @@ doctest!("../README.md"); mod chunked_encoder; pub mod display; mod tables; +#[cfg(any(feature = "std", test))] pub mod write; mod encode; -pub use crate::encode::{encode, encode_config, encode_config_buf, encode_config_slice}; +pub use crate::encode::encode_config_slice; +#[cfg(any(feature = "alloc", feature = "std", test))] +pub use crate::encode::{encode, encode_config, encode_config_buf}; mod decode; -pub use crate::decode::{ - decode, decode_config, decode_config_buf, decode_config_slice, DecodeError, -}; +#[cfg(any(feature = "alloc", feature = "std", test))] +pub use crate::decode::{decode, decode_config, decode_config_buf}; +pub use crate::decode::{decode_config_slice, DecodeError}; #[cfg(test)] mod tests;