Skip to content

Commit

Permalink
Add feature alloc (#12)
Browse files Browse the repository at this point in the history
`std` was the provider for implementations that depends on `Vec`.
However, that was an implementation shortcut, and the proper way to
handle that is to use `alloc` that is `no-std` compatible.

With `alloc`, the features/test structure is simplified. With this
change, all tests are no-std compatible, and can optionally run under
`std` too.
  • Loading branch information
vlopes11 authored Jan 22, 2022
1 parent c1aa6e6 commit 389ad1b
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 169 deletions.
34 changes: 26 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,6 @@ jobs:
command: fmt
args: --all --verbose -- --check

- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --verbose

- name: Build no-std
uses: actions-rs/cargo@v1
with:
Expand All @@ -64,24 +58,48 @@ jobs:
command: build
args: --verbose --target thumbv6m-none-eabi --no-default-features --features serde-types-minimal

- name: Run tests
- name: Build no-std alloc
uses: actions-rs/cargo@v1
with:
command: build
args: --verbose --target thumbv6m-none-eabi --no-default-features --features alloc

- name: Run tests all features
uses: actions-rs/cargo@v1
with:
command: test
args: --verbose --all-features

- name: Run tests serde
uses: actions-rs/cargo@v1
with:
command: test
args: --verbose --features random
args: --verbose --features serde-types

- name: Run tests no-std
uses: actions-rs/cargo@v1
with:
command: test
args: --verbose --no-default-features

- name: Run tests no-std alloc
uses: actions-rs/cargo@v1
with:
command: test
args: --verbose --no-default-features --features alloc

- name: Run tests serde no-std
uses: actions-rs/cargo@v1
with:
command: test
args: --verbose --no-default-features --features serde-types-minimal

- name: Run tests serde no-std alloc
uses: actions-rs/cargo@v1
with:
command: test
args: --verbose --no-default-features --features alloc-serde

- name: Run tests serde
uses: actions-rs/cargo@v1
with:
Expand Down
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ rand = { version = "0.8", default-features = false, optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }

[dev-dependencies]
rand = "0.8"
fuel-types = { path = ".", features = ["random"] }
rand = { version = "0.8", default-features = false, features = ["std_rng"] }

[features]
default = ["std"]
std = []
alloc = []
alloc-serde = ["alloc", "serde/alloc", "serde-types-minimal"]
random = ["rand"]
serde-types = ["serde-types-minimal", "serde/default", "std"]
serde-types-minimal = ["serde"]
std = ["alloc"]
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Rust implementation of the atomic types for the [FuelVM](https:/Fuel
## Compile features

- `std`: Unless set, the crate will link to the core-crate instead of the std-crate. More info [here](https://docs.rust-embedded.org/book/intro/no-std.html).
- `alloc`: Use [Vec](https://doc.rust-lang.org/alloc/vec/struct.Vec.html) from [alloc](https://doc.rust-lang.org/alloc/index.html) for `no-std`.
- `alloc-serde`: Enable `alloc`, `serde/alloc` and `serde-types-minimal`.
- `random`: Implement `no-std` [rand](https://crates.io/crates/rand) features for the provided types.
- `serde-types`: Add support for [serde](https://crates.io/crates/serde) for the provided types.
- `serde-types-minimal`: Add support for `no-std` [serde](https://crates.io/crates/serde) for the provided types.
69 changes: 19 additions & 50 deletions src/bytes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
use crate::Word;

const WORD_SIZE: usize = core::mem::size_of::<Word>();
#[cfg(feature = "std")]
pub use use_std::*;

#[cfg(feature = "alloc")]
pub use use_alloc::*;

/// Memory size of a [`Word`]
pub const WORD_SIZE: usize = core::mem::size_of::<Word>();

/// Define the amount of bytes for a serialization implementation.
pub trait SizedBytes {
Expand Down Expand Up @@ -174,20 +181,24 @@ pub unsafe fn from_slice_unchecked<const N: usize>(buf: &[u8]) -> [u8; N] {
*ptr
}

#[cfg(feature = "std")]
pub use vec_io::*;

#[cfg(feature = "std")]
mod vec_io {
#[cfg(feature = "alloc")]
mod use_alloc {
use super::*;
use std::convert::TryFrom;
use std::io;

use alloc::vec::Vec;

/// Auto-trait to create variable sized vectors out of [`SizedBytes`] implementations.
pub trait SerializableVec: SizedBytes {
/// Create a variable size vector of bytes from the instance.
fn to_bytes(&mut self) -> Vec<u8>;
}
}

#[cfg(feature = "std")]
mod use_std {
use super::*;

use std::io;

/// Describe the ability to deserialize the type from sets of bytes.
pub trait Deserializable: Sized {
Expand Down Expand Up @@ -387,45 +398,3 @@ mod vec_io {
.map(|array| (array, &buf[N..]))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn padded_len_to_fit_word_len() {
assert_eq!(WORD_SIZE * 0, padded_len(&[]));
assert_eq!(WORD_SIZE * 1, padded_len(&[0]));
assert_eq!(WORD_SIZE * 1, padded_len(&[0; WORD_SIZE]));
assert_eq!(WORD_SIZE * 2, padded_len(&[0; WORD_SIZE + 1]));
assert_eq!(WORD_SIZE * 2, padded_len(&[0; WORD_SIZE * 2]));
}

#[test]
fn store_restore_number_unchecked_works() {
fn store_restore<T>(n: T, x: usize, f: unsafe fn(&[u8]) -> (T, &[u8]))
where
T: core::fmt::Debug + Copy + Eq,
Word: From<T>,
{
let mut buffer = [0u8; 255];

assert_eq!(0, store_number_unchecked(&mut buffer[..WORD_SIZE], n).len());
assert_eq!(n, unsafe { f(&buffer).0 });
assert_eq!(0, unsafe { f(&buffer[..WORD_SIZE]).1.len() });

assert_eq!(
x,
store_number_unchecked(&mut buffer[..WORD_SIZE + x], n).len()
);
assert_eq!(n, unsafe { f(&buffer).0 });
assert_eq!(x, unsafe { f(&buffer[..WORD_SIZE + x]).1.len() });
}

store_restore::<Word>(65, 5, restore_number_unchecked);
store_restore::<Word>(65, 5, restore_word_unchecked);
store_restore::<u8>(65, 5, restore_u8_unchecked);
store_restore::<u16>(65, 5, restore_u16_unchecked);
store_restore::<u32>(65, 5, restore_u32_unchecked);
}
}
6 changes: 5 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]

#[cfg(feature = "alloc")]
extern crate alloc;

mod types;

pub use types::*;

/// Word-aligned bytes serialization functions.
pub mod bytes;
pub use types::*;

/// Register ID type
pub type RegisterId = usize;
Expand Down
108 changes: 0 additions & 108 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,111 +239,3 @@ impl ContractId {
/// <https:/FuelLabs/fuel-specs/blob/master/specs/protocol/identifiers.md#contract-id>
pub const SEED: [u8; 4] = 0x4655454C_u32.to_be_bytes();
}

#[cfg(all(test, feature = "std", feature = "random"))]
mod tests_random {
use crate::*;
use rand::rngs::StdRng;
use rand::{Rng, RngCore, SeedableRng};
use std::convert::TryFrom;
use std::{fmt, str};

macro_rules! check_consistency {
($i:ident,$r:expr,$b:expr) => {
unsafe {
let n = $i::LEN;
let s = $r.gen_range(0..$b.len() - n);
let e = $r.gen_range(s + n..$b.len());
let r = $r.gen_range(1..n - 1);
let i = &$b[s..s + n];

let a = $i::from_slice_unchecked(i);
let b = $i::from_slice_unchecked(&$b[s..e]);
let c = $i::try_from(i).expect("Memory conversion");

// `d` will create random smaller slices and expect the value to be parsed correctly
//
// However, this is not the expected usage of the function
let d = $i::from_slice_unchecked(&i[..i.len() - r]);

let e = $i::as_ref_unchecked(i);

// Assert `from_slice_unchecked` will not create two references to the same owned
// memory
assert_ne!(a.as_ptr(), b.as_ptr());

// Assert `as_ref_unchecked` is copy-free
assert_ne!(e.as_ptr(), a.as_ptr());
assert_eq!(e.as_ptr(), i.as_ptr());

assert_eq!(a, b);
assert_eq!(a, c);
assert_eq!(a, d);
assert_eq!(&a, e);
}
};
}

#[test]
fn from_slice_unchecked_safety() {
let rng = &mut StdRng::seed_from_u64(8586);

let mut bytes = [0u8; 257];
rng.fill_bytes(&mut bytes);

for _ in 0..100 {
check_consistency!(Address, rng, bytes);
check_consistency!(Color, rng, bytes);
check_consistency!(ContractId, rng, bytes);
check_consistency!(Bytes4, rng, bytes);
check_consistency!(Bytes8, rng, bytes);
check_consistency!(Bytes32, rng, bytes);
check_consistency!(Bytes64, rng, bytes);
check_consistency!(Salt, rng, bytes);
}
}

#[test]
fn hex_encoding() {
fn encode_decode<T>(t: T)
where
T: fmt::LowerHex + fmt::UpperHex + str::FromStr + Eq + fmt::Debug,
<T as str::FromStr>::Err: fmt::Debug,
{
let lower = format!("{:x}", t);
let lower_alternate = format!("{:#x}", t);
let upper = format!("{:X}", t);
let upper_alternate = format!("{:#X}", t);

assert_ne!(lower, lower_alternate);
assert_ne!(lower, upper);
assert_ne!(lower, upper_alternate);
assert_ne!(lower_alternate, upper);
assert_ne!(lower_alternate, upper_alternate);
assert_ne!(upper, upper_alternate);

let lower = T::from_str(lower.as_str()).expect("Failed to parse lower");
let lower_alternate =
T::from_str(lower_alternate.as_str()).expect("Failed to parse lower alternate");
let upper = T::from_str(upper.as_str()).expect("Failed to parse upper");
let upper_alternate =
T::from_str(upper_alternate.as_str()).expect("Failed to parse upper alternate");

assert_eq!(t, lower);
assert_eq!(t, lower_alternate);
assert_eq!(t, upper);
assert_eq!(t, upper_alternate);
}

let rng = &mut StdRng::seed_from_u64(8586);

encode_decode(rng.gen::<Address>());
encode_decode(rng.gen::<Color>());
encode_decode(rng.gen::<ContractId>());
encode_decode(rng.gen::<Bytes4>());
encode_decode(rng.gen::<Bytes8>());
encode_decode(rng.gen::<Bytes32>());
encode_decode(rng.gen::<Bytes64>());
encode_decode(rng.gen::<Salt>());
}
}
42 changes: 42 additions & 0 deletions tests/bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use fuel_types::bytes::{self, WORD_SIZE};
use fuel_types::Word;

#[test]
fn padded_len_to_fit_word_len() {
assert_eq!(WORD_SIZE * 0, bytes::padded_len(&[]));
assert_eq!(WORD_SIZE * 1, bytes::padded_len(&[0]));
assert_eq!(WORD_SIZE * 1, bytes::padded_len(&[0; WORD_SIZE]));
assert_eq!(WORD_SIZE * 2, bytes::padded_len(&[0; WORD_SIZE + 1]));
assert_eq!(WORD_SIZE * 2, bytes::padded_len(&[0; WORD_SIZE * 2]));
}

#[test]
fn store_restore_number_unchecked_works() {
fn store_restore<T>(n: T, x: usize, f: unsafe fn(&[u8]) -> (T, &[u8]))
where
T: core::fmt::Debug + Copy + Eq,
Word: From<T>,
{
let mut buffer = [0u8; 255];

assert_eq!(
0,
bytes::store_number_unchecked(&mut buffer[..WORD_SIZE], n).len()
);
assert_eq!(n, unsafe { f(&buffer).0 });
assert_eq!(0, unsafe { f(&buffer[..WORD_SIZE]).1.len() });

assert_eq!(
x,
bytes::store_number_unchecked(&mut buffer[..WORD_SIZE + x], n).len()
);
assert_eq!(n, unsafe { f(&buffer).0 });
assert_eq!(x, unsafe { f(&buffer[..WORD_SIZE + x]).1.len() });
}

store_restore::<Word>(65, 5, bytes::restore_number_unchecked);
store_restore::<Word>(65, 5, bytes::restore_word_unchecked);
store_restore::<u8>(65, 5, bytes::restore_u8_unchecked);
store_restore::<u16>(65, 5, bytes::restore_u16_unchecked);
store_restore::<u32>(65, 5, bytes::restore_u32_unchecked);
}
Loading

0 comments on commit 389ad1b

Please sign in to comment.