Skip to content

Commit

Permalink
Merge pull request #111 from CryZe/no-std
Browse files Browse the repository at this point in the history
Implement Support for no_std
  • Loading branch information
marshallpierce committed Sep 3, 2019
2 parents d4cd64f + b4da176 commit a40d420
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 26 deletions.
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

0 comments on commit a40d420

Please sign in to comment.