Skip to content

Define zero-copy, endian-aware, bitfield-equipped structs in Rust

Notifications You must be signed in to change notification settings

project-pku/zorua

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

61 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

zorua

Zorua enables the creation of zero-copy, endian-aware, bitfield-equipped structs in Rust.

Installation

To use zorua add the following dependency to your Cargo.toml:

[dependencies]
zorua = { git = "https://github.com/project-pku/zorua.git", tag="v0.5" }

Example

To use the crate simply import the prelude module and use make use of the derive and bitfields macros:

use zorua::prelude::*;

bitfields! {
    #[repr(C)]
    #[derive(ZoruaField)]
    pub struct MyStruct {
        pub field_a: u32,
        pub field_b: u8,
        pub field_c: MyWrapperStruct, // All fields must also implement ZoruaField
        field_d: u16, // Backing field (no `pub` so only bitfields are exposed)
        |pub bitfield_a: u5@0, // 5 bit uint @ index 0
        |pub bitfield_b: bool@5, // 1 bit bool @ index 5
        |bitfield_c: u7@6, // Bitfields can be private too
        |pub padding: u2@13, // The rest of the bits
    };
}

#[derive(ZoruaField)]
pub struct MyWrapperStruct(u8)

Traits

There are two main traits this crate provides: ZoruaField and ZoruaBitField.

ZoruaField

ZoruaField represents a type that is valid for any bitpattern. It can be derived on structs and exhaustive c-like enums (i.e. having a variant for every bitpattern).

use zorua::prelude::*;

// Tuple struct
#[derive(ZoruaField)]
struct MyWrapperStruct(u8)

// Composite struct
#[repr(C)] //mandatory for structs w/ >1 field
#[derive(ZoruaField)]
struct MyStruct {
  field_a: u32,
  field_b: u8,
  field_c: MyWrapperStruct,
  field_d: MyExhaustiveEnum,
}

// u8 Exhaustive enum
#[derive(ZoruaField)]
#[repr(u8)]
enum MyExhaustiveEnum {
  Variant1,
  Variant2,
  //...,
  Variant256, //2^8 = 256
}

The ZoruaField trait enables the use of:

  • to_le_mut and to_be_mut which swaps the byte order, in-place, of the type to the target architecture's endianness.
  • as_bytes_ref and as_bytes_mut which safely transmutes a reference of self to a byte slice, up to mutability.
  • try_from_bytes_ref and try_from_bytes_mut which attempts to safely transmute a byte slice to a reference of self, up to mutability. If this cannot happen (i.e. missized/misaligned), an error will be returned.

ZoruaBitField

ZoruaBitField represents a type that can be represented as an nbit value. It can be derived on exhaustive c-like enums, or implemented manually for structs.

// Struct
// - Can be represented by a u4
struct MyStruct {
  fourbitval: u8
}
impl trait ZoruaBitField for MyStruct {
    type BitRepr = u4;

    fn to_bit_repr(self) -> Self::BitRepr {
      //truncate u8 -> u4
      (self.fourbitval % 16).try_into().unwrap()
    }

    fn from_bit_repr(value: Self::BitRepr) -> Self {
      value.into() //cast u4 -> u8
    }
}

// Exhaustive enum
// - Total number of variants is a power of 2
#[derive(ZoruaField)]
#[repr(u8)]
enum MyExhaustive2BitEnum {
  Variant1,
  Variant2,
  Variant3,
  Variant4, //2^2 = 4
}

Bitfields

So, the ZoruaField trait is required for the safe transmutation of data types to byte slices, as well as provides a structured way to swap a struct's byte order. But what is the point of ZoruaBitField?

The answer is found in the bitfield macro. Consider the struct defined in the example section:

bitfields! {
    #[repr(C)]
    #[derive(ZoruaField)]
    pub struct MyStruct {
        pub field_a: u32,
        pub field_b: u8,
        pub field_c: MyWrapperStruct, // All fields must also implement ZoruaField
        field_d: u16, // Backing field (no `pub` so only bitfields are exposed)
        |pub bitfield_a: u5@0, // 5 bit uint @ index 0
        |pub bitfield_b: bool@5, // 1 bit bool @ index 5
        |bitfield_c: u7@6, // Bitfields can be private too
        |pub padding: u2@13, // The rest of the bits
    };
}

You'll notice that some "fields" in the struct have a | prepended to them. Any block of fields beginning with this character are bitfields and they correspond to the first non-bitfield above them, we call this field their backing field.

In the example above:

field_d: u16, <------------ Backing Field: my_backing_val
|pub bitfield_a: bool@0,
|pub bitfield_b: u5@1,
 ^   ^           ^  ^
 |   |           |  |
 |   |           |  +- Type: u5
 |   |           +---- Bit Index: 1
 |   +---------------- Name: bitfield_b
 +-------------------- (Optional) Vis Modifier: pub

The bitfields must be of a type that implements ZoruaBitField. On top user defined types, ZoruaBitField is implemented on bool (as a 1 bit value), and the provided u1-u127 types.

Implementation

Adding a bitfield to a struct doesn't change anything about the data it stores or its layout. Instead, it adds a getter & setter method onto the struct for that particular bitfield. These functions get/set as many bits from the backing field as needed for bitfield's type, starting at the given index.

Using the example above, we could call the following:

let mystruct: MyStruct = //...init struct

mystruct.field_d = 0b100000; //bit index 5 = 1 (i.e. true)

println!("{}", mystruct.bitfield_a()); //prints "true"
mystruct.set_bitfield_b(false); //sets value of bitfield_a
println!("{}", mystruct.bitfield_a()); //prints "false"

Some things to note about bitfields:

  • Backing fields must be one of: u8, u16, u32, u64, u128.
  • The bitfields macro does not currently enforce that a bitfield block accounts for all bits in the parent, nor that fields do not overlap:
pub parent: u8,
|pub child_1: u4@0 //Takes up bits 0-3
|pub child_2: u5@1 //Takes up bits 1-5

Fallible

While the ZoruaField and ZoruaBitField traits cover exhaustive enums, what about non-exhaustive ones. After all, it's unlikely your enum happens to have a number of variants equal to a power of 2.

Enter the ZoruaFallible trait. Deriving this trait on a c-like enum, with an arbitrary number of variants, allows the use of the Fallible type in fields and bitfields. For example:

#[repr(u8)]
#[derive(ZoruaFallible)]
#[targets(u3, u8)] //can be represented by both u8 and u3
enum MyEnum {
    Variant1,
    Variant2,
    Variant3,
    Variant4,
    Variant5, //not a power of 2 (some bitpatterns are invalid)
}

bitfields! {
  struct MyTestStruct {
    field_a: u16,
    field_b: Fallible<MyEnum, u8>, //1 byte
    field_c: u8,
    |subfield_a: u2@0,
    |subfield_b: Fallible<MyEnum, u3>@2, //3 bits
    |subfield_c: bool@5,
  }
}

About

Define zero-copy, endian-aware, bitfield-equipped structs in Rust

Resources

Stars

Watchers

Forks

Languages