From 8e2d29d087604f1027c89278dfaf2338120479ba Mon Sep 17 00:00:00 2001 From: Emmanuel Thompson Date: Mon, 1 Jun 2020 16:47:26 -0400 Subject: [PATCH] feat: Add no_std support w/ alloc --- .github/workflows/main.yml | 11 +++++ .gitignore | 2 +- Cargo.toml | 10 ++-- README.md | 8 ++++ deku-derive/src/macros/deku_read.rs | 8 ++-- deku-derive/src/macros/deku_write.rs | 12 ++--- ensure_no_std/Cargo.toml | 23 ++++++++++ ensure_no_std/src/bin/main.rs | 69 ++++++++++++++++++++++++++++ src/error.rs | 21 +++++---- src/lib.rs | 14 +++++- 10 files changed, 153 insertions(+), 25 deletions(-) create mode 100644 ensure_no_std/Cargo.toml create mode 100644 ensure_no_std/src/bin/main.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e4e71137..80d09e64 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -77,6 +77,17 @@ jobs: command: clippy args: -- -D warnings + ensure_no_std: + name: Ensure no_std + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - run: cd ensure_no_std && cargo run --release + coverage: name: Coverage runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 69369904..fdc1c355 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -/target +**/target **/*.rs.bk Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 06e01edf..73a5253b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ version = "0.2.0" license = "MIT OR Apache-2.0" repository = "https://github.com/sharksforarms/deku" keywords = ["deku", "bits", "serialization", "deserialization", "struct"] -categories = ["parsing"] +categories = ["encoding", "parsing", "no-std"] description = "bit level serialization/deserialization proc-macro for structs" readme = "README.md" @@ -18,10 +18,14 @@ members = [ "deku-derive" ] +[features] +default = ["std", "bitvec/std"] +std = ["alloc"] +alloc = ["bitvec/alloc"] + [dependencies] deku_derive = { version = "^0.2.0", path = "deku-derive" } -bitvec = "0.17" - +bitvec = { version = "0.17", default-features = false } [dev-dependencies] hex-literal = "0.2" diff --git a/README.md b/README.md index ebce509b..4623fabc 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ This crate provides bit-level, symmetric, serialization/deserialization implemen **Productivity**: Focus and declare your types that represent the data, Deku will generate symmetric reader/writer functions for your type! Avoid the requirement of writing redundant, error-prone parsing and writing code for binary structs or network headers +**no_std**: Compatible with `#![no_std]` + ## Usage ```toml @@ -20,6 +22,12 @@ This crate provides bit-level, symmetric, serialization/deserialization implemen deku = "0.2" ``` +no_std: +```toml +[dependencies] +deku = { version = "0.2", default-features = false, features = ["alloc"] } +``` + ## Example See [documentation](https://docs.rs/deku) or [examples](https://github.com/sharksforarms/deku/tree/master/examples) folder for more! diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs index 90bc8034..c1b063ec 100644 --- a/deku-derive/src/macros/deku_read.rs +++ b/deku-derive/src/macros/deku_read.rs @@ -41,7 +41,7 @@ fn emit_struct(input: &DekuReceiver) -> Result { let initialize_struct = super::gen_struct_init(is_named_struct, field_idents); tokens.extend(quote! { - impl #imp std::convert::TryFrom<&[u8]> for #ident #wher { + impl #imp core::convert::TryFrom<&[u8]> for #ident #wher { type Error = DekuError; fn try_from(input: &[u8]) -> Result { @@ -52,7 +52,7 @@ fn emit_struct(input: &DekuReceiver) -> Result { impl #imp #ident #wher { fn from_bytes(input: (&[u8], usize)) -> Result<((&[u8], usize), Self), DekuError> { - use std::convert::TryFrom; + use core::convert::TryFrom; let input_bits = input.0.bits::(); let mut rest = input.0.bits::(); @@ -70,7 +70,7 @@ fn emit_struct(input: &DekuReceiver) -> Result { impl #imp BitsReader for #ident #wher { fn read(input: &BitSlice, _input_is_le: bool, _bit_size: Option, _count: Option) -> Result<(&BitSlice, Self), DekuError> { - use std::convert::TryFrom; + use core::convert::TryFrom; let mut rest = input; #(#field_reads)* @@ -153,7 +153,7 @@ fn emit_enum(input: &DekuReceiver) -> Result { } tokens.extend(quote! { - impl #imp std::convert::TryFrom<&[u8]> for #ident #wher { + impl #imp core::convert::TryFrom<&[u8]> for #ident #wher { type Error = DekuError; fn try_from(input: &[u8]) -> Result { diff --git a/deku-derive/src/macros/deku_write.rs b/deku-derive/src/macros/deku_write.rs index 1dc4b5e3..f70c75d5 100644 --- a/deku-derive/src/macros/deku_write.rs +++ b/deku-derive/src/macros/deku_write.rs @@ -33,7 +33,7 @@ fn emit_struct(input: &DekuReceiver) -> Result { let field_updates = emit_field_updates(&fields, Some(quote! { self. }))?; tokens.extend(quote! { - impl #imp std::convert::TryFrom<#ident> for BitVec #wher { + impl #imp core::convert::TryFrom<#ident> for BitVec #wher { type Error = DekuError; fn try_from(input: #ident) -> Result { @@ -41,7 +41,7 @@ fn emit_struct(input: &DekuReceiver) -> Result { } } - impl #imp std::convert::TryFrom<#ident> for Vec #wher { + impl #imp core::convert::TryFrom<#ident> for Vec #wher { type Error = DekuError; fn try_from(input: #ident) -> Result { @@ -52,7 +52,7 @@ fn emit_struct(input: &DekuReceiver) -> Result { impl #imp #ident #wher { pub fn update(&mut self) -> Result<(), DekuError> { - use std::convert::TryInto; + use core::convert::TryInto; #(#field_updates)* Ok(()) @@ -158,7 +158,7 @@ fn emit_enum(input: &DekuReceiver) -> Result { } tokens.extend(quote! { - impl #imp std::convert::TryFrom<#ident> for BitVec #wher { + impl #imp core::convert::TryFrom<#ident> for BitVec #wher { type Error = DekuError; fn try_from(input: #ident) -> Result { @@ -166,7 +166,7 @@ fn emit_enum(input: &DekuReceiver) -> Result { } } - impl #imp std::convert::TryFrom<#ident> for Vec #wher { + impl #imp core::convert::TryFrom<#ident> for Vec #wher { type Error = DekuError; fn try_from(input: #ident) -> Result { @@ -176,7 +176,7 @@ fn emit_enum(input: &DekuReceiver) -> Result { impl #imp #ident #wher { pub fn update(&mut self) -> Result<(), DekuError> { - use std::convert::TryInto; + use core::convert::TryInto; match self { #(#variant_updates),* diff --git a/ensure_no_std/Cargo.toml b/ensure_no_std/Cargo.toml new file mode 100644 index 00000000..a8da0522 --- /dev/null +++ b/ensure_no_std/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ensure_no_std" +version = "0.1.0" +authors = ["sharks "] +edition = "2018" + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true + +[workspace] + + +[features] +default = ["alloc"] +alloc = [] + +[dependencies] +wee_alloc = "0.4" +deku = { path = "../", default-features = false, features = ["alloc"] } diff --git a/ensure_no_std/src/bin/main.rs b/ensure_no_std/src/bin/main.rs new file mode 100644 index 00000000..faad7d42 --- /dev/null +++ b/ensure_no_std/src/bin/main.rs @@ -0,0 +1,69 @@ +//! Based on https://github.com/rustwasm/wee_alloc/tree/master/example +//! Run with `cargo +nightly run --release` + +#![no_std] +#![no_main] +#![feature(core_intrinsics, lang_items, alloc_error_handler)] + +extern crate alloc; +extern crate wee_alloc; + +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +// Need to provide a tiny `panic` implementation for `#![no_std]`. +// This translates into an `unreachable` instruction that will +// raise a `trap` the WebAssembly execution if we panic at runtime. +#[panic_handler] +#[no_mangle] +pub fn panic(_info: &::core::panic::PanicInfo) -> ! { + ::core::intrinsics::abort(); +} + +// Need to provide an allocation error handler which just aborts +// the execution with trap. +#[alloc_error_handler] +#[no_mangle] +pub extern "C" fn oom(_: ::core::alloc::Layout) -> ! { + ::core::intrinsics::abort(); +} + +// Needed for non-wasm targets. +#[lang = "eh_personality"] +#[no_mangle] +pub extern "C" fn eh_personality() {} + +use alloc::{vec, vec::Vec}; +use deku::prelude::*; + +#[derive(Debug, PartialEq, DekuRead, DekuWrite)] +struct DekuTest { + #[deku(bits = "5")] + field_a: u8, + #[deku(bits = "3")] + field_b: u8, + count: u8, + #[deku(len = "count")] + data: Vec, +} + +#[no_mangle] +pub extern "C" fn main() -> () { + let test_data: Vec = vec![0b10101_101, 0x02, 0xBE, 0xEF]; + + // Test reading + let (_rest, val) = DekuTest::from_bytes((&test_data, 0)).unwrap(); + assert_eq!( + DekuTest { + field_a: 0b10101, + field_b: 0b101, + count: 0x02, + data: vec![0xBE, 0xEF] + }, + val + ); + + // Test writing + let val = val.to_bytes().unwrap(); + assert_eq!(test_data, val); +} diff --git a/src/error.rs b/src/error.rs index 21590dee..1161e6a3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,5 @@ -use std::error; -use std::fmt; +#![cfg(feature = "alloc")] +use alloc::{format, string::String, string::ToString}; /// Deku errors #[derive(Debug, PartialEq)] @@ -10,20 +10,20 @@ pub enum DekuError { InvalidParam(String), } -impl From for DekuError { - fn from(e: std::num::TryFromIntError) -> DekuError { +impl From for DekuError { + fn from(e: core::num::TryFromIntError) -> DekuError { DekuError::Parse(format!("error parsing int: {}", e.to_string())) } } -impl From for DekuError { - fn from(_e: std::convert::Infallible) -> DekuError { +impl From for DekuError { + fn from(_e: core::convert::Infallible) -> DekuError { unreachable!(); } } -impl fmt::Display for DekuError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl core::fmt::Display for DekuError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { match *self { DekuError::Parse(ref err) => write!(f, "Parse error: {}", err), DekuError::InvalidParam(ref err) => write!(f, "Invalid param error: {}", err), @@ -31,8 +31,9 @@ impl fmt::Display for DekuError { } } -impl error::Error for DekuError { - fn cause(&self) -> Option<&dyn error::Error> { +#[cfg(feature = "std")] +impl std::error::Error for DekuError { + fn cause(&self) -> Option<&dyn std::error::Error> { Some(self) } } diff --git a/src/lib.rs b/src/lib.rs index b6779b89..a39b554e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,10 @@ For documentation and examples on available `#deku[()]` attributes and features, For more examples, see the [examples folder](https://github.com/sharksforarms/deku/tree/master/examples)! +## no_std + +For use in `no_std` environments, `alloc` is the single feature which is required on deku. + # Simple Example Let's read big-endian data into a struct, with fields containing different sizes, modify a value, and write it back @@ -155,6 +159,14 @@ assert_eq!(DekuTest::VariantB(0xBEEF) , val); ``` */ +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "alloc")] +use alloc::{format, vec::Vec}; + use bitvec::prelude::*; pub use deku_derive::*; pub mod attributes; @@ -207,7 +219,7 @@ macro_rules! ImplDekuTraits { ) -> Result<(&BitSlice, Self), DekuError> { assert!(count.is_none(), "Dev error: `count` should always be None"); - let max_type_bits: usize = std::mem::size_of::<$typ>() * 8; + let max_type_bits: usize = core::mem::size_of::<$typ>() * 8; let bit_size = match bit_size { None => max_type_bits,