Skip to content
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

Implement Support for no_std #111

Merged
merged 3 commits into from Sep 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 11 additions & 4 deletions .travis.yml
Expand Up @@ -5,7 +5,7 @@ sudo: required

matrix:
include:
- rust: 1.31.0
- rust: 1.34.0
- rust: stable
- rust: beta
- rust: nightly
Expand All @@ -19,16 +19,23 @@ 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:
# prevent cargo fuzz list from printing with color
- 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'
Expand Down
10 changes: 6 additions & 4 deletions Cargo.toml
Expand Up @@ -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"
Expand All @@ -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
7 changes: 6 additions & 1 deletion README.md
Expand Up @@ -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
---
Expand All @@ -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
---

Expand Down
2 changes: 2 additions & 0 deletions 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

Expand Down
9 changes: 8 additions & 1 deletion src/chunked_encoder.rs
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 = ();

Expand Down
22 changes: 18 additions & 4 deletions 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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -74,6 +80,7 @@ impl error::Error for DecodeError {
/// println!("{:?}", bytes);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn decode<T: ?Sized + AsRef<[u8]>>(input: &T) -> Result<Vec<u8>, DecodeError> {
decode_config(input, STANDARD)
}
Expand All @@ -94,6 +101,7 @@ pub fn decode<T: ?Sized + AsRef<[u8]>>(input: &T) -> Result<Vec<u8>, DecodeError
/// println!("{:?}", bytes_url);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn decode_config<T: ?Sized + AsRef<[u8]>>(
input: &T,
config: Config,
Expand Down Expand Up @@ -124,6 +132,7 @@ pub fn decode_config<T: ?Sized + AsRef<[u8]>>(
/// println!("{:?}", buffer);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn decode_config_buf<T: ?Sized + AsRef<[u8]>>(
input: &T,
config: Config,
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -507,7 +521,7 @@ fn decode_chunk(
}
accum |= (morsel as u64) << 16;

BigEndian::write_u64(output, accum);
write_u64(output, accum);

Ok(())
}
Expand Down
4 changes: 2 additions & 2 deletions src/display.rs
Expand Up @@ -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> {
Expand Down
24 changes: 18 additions & 6 deletions 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.
Expand All @@ -15,6 +19,7 @@ use byteorder::{BigEndian, ByteOrder};
/// println!("{}", b64);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn encode<T: ?Sized + AsRef<[u8]>>(input: &T) -> String {
encode_config(input, STANDARD)
}
Expand All @@ -35,6 +40,7 @@ pub fn encode<T: ?Sized + AsRef<[u8]>>(input: &T) -> String {
/// println!("{}", b64_url);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn encode_config<T: ?Sized + AsRef<[u8]>>(input: &T, config: Config) -> String {
let mut buf = match encoded_size(input.as_ref().len(), config) {
Some(n) => vec![0; n],
Expand Down Expand Up @@ -65,6 +71,7 @@ pub fn encode_config<T: ?Sized + AsRef<[u8]>>(input: &T, config: Config) -> Stri
/// println!("{}", buf);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn encode_config_buf<T: ?Sized + AsRef<[u8]>>(input: &T, config: Config, buf: &mut String) {
let input_bytes = input.as_ref();

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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];
Expand All @@ -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];
Expand All @@ -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];
Expand All @@ -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];
Expand Down
17 changes: 13 additions & 4 deletions src/lib.rs
Expand Up @@ -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]
Expand All @@ -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;
Expand Down