Expand description

Fast and easy bitfield proc macro

Provides a proc macro for compressing a data structure with data which can be expressed with bit lengths that are not a power of Two.

Derive Bitfields

  • Implements the Bitfields trait which offers from\into bytes functions that are non-failable and convert the struct from/into sized u8 arrays ([u8; {total_bit_length * 8}]).
  • read and write functions that allow the field to be accessed or overwritten within a sized u8 array.
  • More information about how each field is handled (bit length, endianness, ..), as well as structure wide effects (bit position, default field endianness, ..), can be found on the Bitfields Derive page.

For example we can define a data structure with 7 total bytes as:

  • A boolean field named one will be the first bit.
  • A floating point field named two will be the next 32 bits. floats must be full sized currently.
  • A signed integer field named three will be the next 14 bits.
  • An unsigned integer field named four will be the next 6 bits.
// Users code
use bondrewd::*;
#[derive(Bitfields)]
#[bondrewd(default_endianness = "be")]
struct SimpleExample {
    // fields that are as expected do not require attributes.
    one: bool,
    two: f32,
    #[bondrewd(bit_length = 14)]
    three: i16,
    #[bondrewd(bit_length = 6)]
    four: u8,
}

Generated Code with function logic omitted. Full Generated Code

impl Bitfields<7usize> for SimpleExample {
    const BIT_SIZE: usize = 53usize;
    fn into_bytes(self) -> [u8; 7usize] { .. }
    fn from_bytes(mut input_byte_buffer: [u8; 7usize]) -> Self { .. }
}
impl SimpleExample {
    pub fn read_one(input_byte_buffer: &[u8; 7usize]) -> bool { .. }
    pub fn read_two(input_byte_buffer: &[u8; 7usize]) -> f32 { .. }
    pub fn read_three(input_byte_buffer: &[u8; 7usize]) -> i16 { .. }
    pub fn read_four(input_byte_buffer: &[u8; 7usize]) -> u8 { .. }
    pub fn write_one(output_byte_buffer: &mut [u8; 7usize], mut one: bool) { .. }
    pub fn write_two(output_byte_buffer: &mut [u8; 7usize], mut two: f32) { .. }
    pub fn write_three(output_byte_buffer: &mut [u8; 7usize], mut three: i16) { .. }
    pub fn write_four(output_byte_buffer: &mut [u8; 7usize], mut four: u8) { .. }
}

Derive BitfieldEnum

  • Implements the BitfieldEnum trait which offers from\into primitive functions that are non-failable and convert the enum from/into a primitive type (u8 is the only currently testing primitive).
  • More information about controlling the end result (define variant values, define a catch/invalid variant) can be found on the BitfieldEnum Derive page.
// Users code
use bondrewd::BitfieldEnum;
#[derive(BitfieldEnum)]
enum SimpleEnum {
    Zero,
    One,
    Six = 6,
    Two,
}

Full Generated Struct Code

// use statement and SimpleEnum definition are hidden.
impl bondrewd::BitfieldEnum for SimpleEnum {
    type Primitive = u8;
    fn into_primitive(self) -> u8 {
        match self {
            Self::Zero => 0,
            Self::One => 1,
            Self::Six => 6,
            Self::Two => 2,
        }
    }
    fn from_primitive(input: u8) -> Self {
        match input {
            0 => Self::Zero,
            1 => Self::One,
            6 => Self::Six,
            _ => Self::Two,
        }
    }
}

Crate Features

Slice functions are convenience functions for reading/wring single or multiple fields without reading the entire structure. Bondrewd will provided 2 ways to access the field:

  • Single field access. These are functions that are added along side the standard read/write field functions in the impl for the input structure. read/write slice functions will check the length of the slice to insure the amount to bytes needed for the field (NOT the entire structure) are present and return BitfieldSliceError if not enough bytes are present.
    • fn read_slice_{field}(&[u8]) -> Result<{field_type}, bondrewd::BondrewdSliceError> { .. }
    • fn write_slice_{field}(&mut [u8], {field_type}) -> Result<(), bondrewd::BondrewdSliceError> { .. }
  • Multiple field access.
    • fn check_slice(&[u8]) -> Result<{struct_name}Checked, bondrewd::BondrewdSliceError> { .. } This function will check the size of the slice, if the slice is big enough it will return a checked structure. the structure will be the same name as the input structure with “Checked” tacked onto the end. the Checked Structure will have getters for each of the input structures fields, the naming is the same as the standard read_{field} functions.
      • fn read_{field}(&self) -> {field_type} { .. }
    • fn check_slice_mut(&mut [u8]) -> Result<{struct_name}CheckedMut, bondrewd::BondrewdSliceError> { .. } This function will check the size of the slice, if the slice is big enough it will return a checked structure. the structure will be the same name as the input structure with “CheckedMut” tacked onto the end. the Checked Structure will have getters and setters for each of the input structures fields, the naming is the same as the standard read_{field} and write_{field} functions.
      • fn read_{field}(&self) -> {field_type} { .. }
      • fn write_{field}(&mut self) -> {field_type} { .. }

Example Cargo.toml Bondrewd dependency
bondrewd = { version = "^0.1", features = ["derive", "slice_fns"] }
Example Generated Slice Api:

impl Simple {
    pub fn check_slice(buffer: &[u8]) -> Result<SimpleChecked, BitfieldSliceError> { .. }
    pub fn check_slice_mut(buffer: &mut [u8]) -> Result<SimpleCheckedMut, BitfieldSliceError> { .. }
    #[inline]
    pub fn read_slice_one(input_byte_buffer: &[u8]) -> Result<u8, BitfieldSliceError> { .. }
    #[inline]
    pub fn read_slice_two(input_byte_buffer: &[u8]) -> Result<bool, BitfieldSliceError> { .. }
    #[inline]
    pub fn read_slice_three(input_byte_buffer: &[u8]) -> Result<u8, BitfieldSliceError> { .. }
    #[inline]
    pub fn write_slice_one(output_byte_buffer: &mut [u8],one: u8) -> Result<(), BitfieldSliceError> { .. }
    #[inline]
    pub fn write_slice_two(output_byte_buffer: &mut [u8],two: bool) -> Result<(), BitfieldSliceError> { .. }
    #[inline]
    pub fn write_slice_three(output_byte_buffer: &mut [u8],three: u8) -> Result<(), BitfieldSliceError> { .. }
}
struct SimpleChecked<'a> {
    buffer: &'a [u8],
}
impl<'a> SimpleChecked<'a> {
    #[inline]
    pub fn read_one(&self) -> u8 { .. }
    #[inline]
    pub fn read_two(&self) -> bool { .. }
    #[inline]
    pub fn read_three(&self) -> u8 { .. }
}
struct SimpleCheckedMut<'a> {
    buffer: &'a mut [u8],
}
impl<'a> SimpleCheckedMut<'a> {
    #[inline]
    pub fn read_one(&self) -> u8 { .. }
    #[inline]
    pub fn read_two(&self) -> bool { .. }
    #[inline]
    pub fn read_three(&self) -> u8 { .. }
    #[inline]
    pub fn write_one(&mut self, one: u8) { .. }
    #[inline]
    pub fn write_two(&mut self, two: bool) { .. }
    #[inline]
    pub fn write_three(&mut self, three: u8) { .. }
}

hex_fns provided from/into hex functions like from/into bytes. The hex inputs/outputs are [u8;N] where N is double the calculated bondrewd STRUCT_SIZE. Hex encoding and decoding is based off the hex crate’s from/into slice functions but with statically sized arrays so we could eliminate sizing errors.

Full Example Generated Code

use bondrewd::*;
struct SimpleExample {
    one: bool,
    two: f32,
    three: i16,
    four: u8,
}
impl Bitfields<7usize> for SimpleExample {
    const BIT_SIZE: usize = 53usize;
    fn into_bytes(self) -> [u8; 7usize] {
        let mut output_byte_buffer: [u8; 7usize] = [0u8; 7usize];
        let one = self.one;
        output_byte_buffer[0usize] |= ((one as u8) << 7usize) & 128u8;
        let two = self.two;
        let two_bytes = (two.to_bits().rotate_right(1u32)).to_be_bytes();
        output_byte_buffer[0usize] |= two_bytes[0usize] & 127u8;
        output_byte_buffer[1usize] |= two_bytes[1usize];
        output_byte_buffer[2usize] |= two_bytes[2usize];
        output_byte_buffer[3usize] |= two_bytes[3usize];
        output_byte_buffer[4usize] |= two_bytes[0] & 128u8;
        let three = self.three;
        let three_bytes = (three.rotate_right(7u32)).to_be_bytes();
        output_byte_buffer[4usize] |= three_bytes[1usize] & 127u8;
        output_byte_buffer[5usize] |= three_bytes[0] & 254u8;
        let four = self.four;
        let four_bytes = (four.rotate_right(5u32)).to_be_bytes();
        output_byte_buffer[5usize] |= four_bytes[0usize] & 1u8;
        output_byte_buffer[6usize] |= four_bytes[0] & 248u8;
        output_byte_buffer
    }
    fn from_bytes(mut input_byte_buffer: [u8; 7usize]) -> Self {
        let one = Self::read_one(&input_byte_buffer);
        let two = Self::read_two(&input_byte_buffer);
        let three = Self::read_three(&input_byte_buffer);
        let four = Self::read_four(&input_byte_buffer);
        Self {
            one,
            two,
            three,
            four,
        }
    }
}
impl SimpleExample {
    #[inline]
    pub fn read_one(input_byte_buffer: &[u8; 7usize]) -> bool {
        ((input_byte_buffer[0usize] & 128u8) != 0)
    }
    #[inline]
    pub fn read_two(input_byte_buffer: &[u8; 7usize]) -> f32 {
        f32::from_bits(
            u32::from_be_bytes({
                let mut two_bytes: [u8; 4usize] = [0u8; 4usize];
                two_bytes[0usize] |= input_byte_buffer[0usize] & 127u8;
                two_bytes[1usize] |= input_byte_buffer[1usize];
                two_bytes[2usize] |= input_byte_buffer[2usize];
                two_bytes[3usize] |= input_byte_buffer[3usize];
                two_bytes[0] |= input_byte_buffer[4usize] & 128u8;
                two_bytes
            })
            .rotate_left(1u32),
        )
    }
    #[inline]
    pub fn read_three(input_byte_buffer: &[u8; 7usize]) -> i16 {
        i16::from_be_bytes({
            let mut three_bytes: [u8; 2usize] = if (input_byte_buffer[4usize] & 64u8) == 64u8 {
                [1u8, 128u8]
            } else {
                [0u8; 2usize]
            };
            three_bytes[1usize] |= input_byte_buffer[4usize] & 127u8;
            three_bytes[0] |= input_byte_buffer[5usize] & 254u8;
            three_bytes
        })
        .rotate_left(7u32)
    }
    #[inline]
    pub fn read_four(input_byte_buffer: &[u8; 7usize]) -> u8 {
        u8::from_be_bytes({
            let mut four_bytes: [u8; 1usize] = [0u8; 1usize];
            four_bytes[0usize] |= input_byte_buffer[5usize] & 1u8;
            four_bytes[0] |= input_byte_buffer[6usize] & 248u8;
            four_bytes
        })
        .rotate_left(5u32)
    }
    #[inline]
    pub fn write_one(output_byte_buffer: &mut [u8; 7usize], mut one: bool) {
        output_byte_buffer[0usize] &= 127u8;
        output_byte_buffer[0usize] |= ((one as u8) << 7usize) & 128u8;
    }
    #[inline]
    pub fn write_two(output_byte_buffer: &mut [u8; 7usize], mut two: f32) {
        output_byte_buffer[0usize] &= 128u8;
        output_byte_buffer[1usize] = 0u8;
        output_byte_buffer[2usize] = 0u8;
        output_byte_buffer[3usize] = 0u8;
        output_byte_buffer[4usize] &= 127u8;
        let two_bytes = (two.to_bits().rotate_right(1u32)).to_be_bytes();
        output_byte_buffer[0usize] |= two_bytes[0usize] & 127u8;
        output_byte_buffer[1usize] |= two_bytes[1usize];
        output_byte_buffer[2usize] |= two_bytes[2usize];
        output_byte_buffer[3usize] |= two_bytes[3usize];
        output_byte_buffer[4usize] |= two_bytes[0] & 128u8;
    }
    #[inline]
    pub fn write_three(output_byte_buffer: &mut [u8; 7usize], mut three: i16) {
        output_byte_buffer[4usize] &= 128u8;
        output_byte_buffer[5usize] &= 1u8;
        let three_bytes = (three.rotate_right(7u32)).to_be_bytes();
        output_byte_buffer[4usize] |= three_bytes[1usize] & 127u8;
        output_byte_buffer[5usize] |= three_bytes[0] & 254u8;
    }
    #[inline]
    pub fn write_four(output_byte_buffer: &mut [u8; 7usize], mut four: u8) {
        output_byte_buffer[5usize] &= 254u8;
        output_byte_buffer[6usize] &= 7u8;
        let four_bytes = (four.rotate_right(5u32)).to_be_bytes();
        output_byte_buffer[5usize] |= four_bytes[0usize] & 1u8;
        output_byte_buffer[6usize] |= four_bytes[0] & 248u8;
    }
}

Derive Macros

Generates an implementation of bondrewd::BitfieldEnum trait.

Generates an implementation of the bondrewd::Bitfield trait, as well as peek and set functions for direct sized u8 arrays access. This crate is designed so that attributes are only required for fields that are not what you would expect without the attribute. For example if you provide a u8 fields with no attributes, the field would be assumed to be the next 8 bits after the field before it. If a field of bool type without attributes is defined, the field would be assumed to be the next bit after the field before it.