#integer-value #bit-field #parser #map #mapping #proc-macro #protocols

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

#1381 in Network programming

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
~37K SLoC