Skip to content

Commit

Permalink
feat: Add no_std support w/ alloc
Browse files Browse the repository at this point in the history
  • Loading branch information
sharksforarms committed Jul 13, 2020
1 parent 275fe30 commit 8e2d29d
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 25 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/target
**/target
**/*.rs.bk
Cargo.lock
10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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"
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,21 @@ 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
[dependencies]
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!
Expand Down
8 changes: 4 additions & 4 deletions deku-derive/src/macros/deku_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn emit_struct(input: &DekuReceiver) -> Result<TokenStream, darling::Error> {
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<Self, Self::Error> {
Expand All @@ -52,7 +52,7 @@ fn emit_struct(input: &DekuReceiver) -> Result<TokenStream, darling::Error> {

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::<Msb0>();

let mut rest = input.0.bits::<Msb0>();
Expand All @@ -70,7 +70,7 @@ fn emit_struct(input: &DekuReceiver) -> Result<TokenStream, darling::Error> {

impl #imp BitsReader for #ident #wher {
fn read(input: &BitSlice<Msb0, u8>, _input_is_le: bool, _bit_size: Option<usize>, _count: Option<usize>) -> Result<(&BitSlice<Msb0, u8>, Self), DekuError> {
use std::convert::TryFrom;
use core::convert::TryFrom;
let mut rest = input;

#(#field_reads)*
Expand Down Expand Up @@ -153,7 +153,7 @@ fn emit_enum(input: &DekuReceiver) -> Result<TokenStream, darling::Error> {
}

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<Self, Self::Error> {
Expand Down
12 changes: 6 additions & 6 deletions deku-derive/src/macros/deku_write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ fn emit_struct(input: &DekuReceiver) -> Result<TokenStream, darling::Error> {
let field_updates = emit_field_updates(&fields, Some(quote! { self. }))?;

tokens.extend(quote! {
impl #imp std::convert::TryFrom<#ident> for BitVec<Msb0, u8> #wher {
impl #imp core::convert::TryFrom<#ident> for BitVec<Msb0, u8> #wher {
type Error = DekuError;

fn try_from(input: #ident) -> Result<Self, Self::Error> {
input.to_bitvec()
}
}

impl #imp std::convert::TryFrom<#ident> for Vec<u8> #wher {
impl #imp core::convert::TryFrom<#ident> for Vec<u8> #wher {
type Error = DekuError;

fn try_from(input: #ident) -> Result<Self, Self::Error> {
Expand All @@ -52,7 +52,7 @@ fn emit_struct(input: &DekuReceiver) -> Result<TokenStream, darling::Error> {
impl #imp #ident #wher {

pub fn update(&mut self) -> Result<(), DekuError> {
use std::convert::TryInto;
use core::convert::TryInto;
#(#field_updates)*

Ok(())
Expand Down Expand Up @@ -158,15 +158,15 @@ fn emit_enum(input: &DekuReceiver) -> Result<TokenStream, darling::Error> {
}

tokens.extend(quote! {
impl #imp std::convert::TryFrom<#ident> for BitVec<Msb0, u8> #wher {
impl #imp core::convert::TryFrom<#ident> for BitVec<Msb0, u8> #wher {
type Error = DekuError;

fn try_from(input: #ident) -> Result<Self, Self::Error> {
input.to_bitvec()
}
}

impl #imp std::convert::TryFrom<#ident> for Vec<u8> #wher {
impl #imp core::convert::TryFrom<#ident> for Vec<u8> #wher {
type Error = DekuError;

fn try_from(input: #ident) -> Result<Self, Self::Error> {
Expand All @@ -176,7 +176,7 @@ fn emit_enum(input: &DekuReceiver) -> Result<TokenStream, darling::Error> {

impl #imp #ident #wher {
pub fn update(&mut self) -> Result<(), DekuError> {
use std::convert::TryInto;
use core::convert::TryInto;

match self {
#(#variant_updates),*
Expand Down
23 changes: 23 additions & 0 deletions ensure_no_std/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "ensure_no_std"
version = "0.1.0"
authors = ["sharks <sharks@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"] }
69 changes: 69 additions & 0 deletions ensure_no_std/src/bin/main.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
}

#[no_mangle]
pub extern "C" fn main() -> () {
let test_data: Vec<u8> = 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);
}
21 changes: 11 additions & 10 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -10,29 +10,30 @@ pub enum DekuError {
InvalidParam(String),
}

impl From<std::num::TryFromIntError> for DekuError {
fn from(e: std::num::TryFromIntError) -> DekuError {
impl From<core::num::TryFromIntError> for DekuError {
fn from(e: core::num::TryFromIntError) -> DekuError {
DekuError::Parse(format!("error parsing int: {}", e.to_string()))
}
}

impl From<std::convert::Infallible> for DekuError {
fn from(_e: std::convert::Infallible) -> DekuError {
impl From<core::convert::Infallible> 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),
}
}
}

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)
}
}
14 changes: 13 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -207,7 +219,7 @@ macro_rules! ImplDekuTraits {
) -> Result<(&BitSlice<Msb0, u8>, 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,
Expand Down

0 comments on commit 8e2d29d

Please sign in to comment.