no-std bin+lib bitrange

Simple plugin to map bits in integer values to fields

5 unstable releases

0.3.0 Apr 3, 2022
0.2.0 Apr 8, 2018
0.1.2 Apr 7, 2018
0.1.1 Apr 6, 2018
0.1.0 Apr 6, 2018

MIT license

17KB
276 lines

bitrange

getting started

To get started, add this to your Cargo.toml:

[dependencies]
bitrange = { git = "https://github.com/trangar/bitrange" }
bitrange_plugin = { git = "https://github.com/trangar/bitrange" }

Then add the following code to your main.rs or lib.rs

#[macro_use]
extern crate bitrange;
#[macro_use]
extern crate bitrange_plugin;

bitrange needs a nightly version of the compiler because it uses the feature proc_macro which is not stabilized yet

quoted strings

Because of an openstanding RFC 2320, stringify! can not be used in a proc-macro attribute. The code generated by this crate contains:

#[derive(Bitrange)]
#[BitrangeMask = $format]
#[BitrangeSize = $struct_size_string]

where $format and $struct_size_string are from the bitrange macro.

In this instance we'd like to use stringify!($format) and stringify($struct_size_string), however this does not work until RFC 2320 lands.

For this reason, the format needs to be quoted, and we need to annotate the type twice. e.g.

bitrange! {
    Test: u8, "u8",
    "aaaa_bbbb",
    a: first,
    b: second
}

instead of the desired

bitrange! {
    Test: u8,
    aaaa_bbbb,
    a: first,
    b: second
}

examples

Bitrange helps you map bit fields to proper getters and setters.

Say you're trying to make an IP parser. The rfc will give you this:

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version|  IHL  |Type of Service|          Total Length         |

If you wanted to parse this in Rust, you'd have to make the following mapping:

  • the first 4 bits are mapped to version
  • The next 4 bits are mapped to ihl
  • The next 8 bits are mapped to type_of_service
  • The last 16 bits are mapped to total_length

With bitrange, you can easily map bytes to fields. To parse this part of the protocol, simply write

#![feature(proc_macro)]
#[macro_use]
extern crate bitrange;

bitrange! {
    IpHeader: u32, "u32",                    // struct name
    "aaaa_bbbb_cccccccc_dddddddddddddddd",   // pattern that we're matching against
    a: version,                              // map character 'a' to field 'version'
    b: ihl,                                  // map character 'b' to field 'ihl'
    c: type_of_service,                      // map character 'c' to field 'type_of_service'
    d: total_length                          // map character 'd' to field 'total_length'
}

fn main() {
    let header = IpHeader::from(0b0001_0010_00000011_0000000000000100);
    assert_eq!(header.version(), 0b0001);
    assert_eq!(header.ihl(), 0b0010);
    assert_eq!(header.type_of_service(), 0b0011);
    assert_eq!(header.total_length(), 0b0100);
}

If you wanted to make a field mutable, simply add a second ident to the field mapping, e.g.:


bitrange! {
    IpHeader: u32, "u32",                    // struct name
    "aaaa_bbbb_cccccccc_dddddddddddddddd",   // pattern that we're matching against
    a: version set_version,                  // map character 'a' to field 'version', and create setter 'set_version'
    b: ihl,                                  // map character 'b' to field 'ihl'
    c: type_of_service,                      // map character 'c' to field 'type_of_service'
    d: total_length                          // map character 'd' to field 'total_length'
}

fn main() {
    let mut header = IpHeader::from(0b0001_0010_00000011_0000000000000100);
    assert_eq!(header.version(), 0b0001);
    assert_eq!(header.ihl(), 0b0010);
    assert_eq!(header.type_of_service(), 0b0011);
    assert_eq!(header.total_length(), 0b0100);

    header.set_version(0b0100);
    assert_eq!(header.version(), 0b0100);
}

In addition, you can define constraints to bits that have to always be 0 or 1


bitrange! {
    Test: u8, "u8",
    // from left (highest) to right (lowest)
    // first 3 bits are mapped to a
    // the next bit is always 1
    // the next bit is always 0
    // the last 3 bits are mapped to b
    "aaa1_0bbb",
    a: first,
    b: second
}

fn main() {
    // This panics at runtime
    // Because the 4th highest bit should always be 1
    // Test::from(0);

    // The enum also implements Default, so you can simply do:
    let _test = Test::default();
    // And this will have value 0b0001_0000
}

Compile-time checks

bitrange will also check fields at compile time to see if they exist

bitrange! { 
    Test: u8, "u8",
    "aaa1_0bbb",
    a: first,
    b: second,
    c: third // this will panic with
             // Token 'c' is not found in pattern "aaa10bbb"
}

However, this does not work for unmapped fields

bitrange! {
    Test: u8, "u8",
    "aaa1_0bbb",
    a: first,
    // b is not mapped
    // Does not give a warning
}

Dependencies

~1.5MB
~36K SLoC