hecatia-elegua / bilge

Use bitsized types as if they were a feature of rust.
Apache License 2.0
171 stars 17 forks source link

Add ability to fill fields from left-to-right instead #88

Open kamakazikamikaze opened 11 months ago

kamakazikamikaze commented 11 months ago

According to the first comment of #7, fields are filled right-to-left.

This causes issues when instantiating a struct that must read data starting from the left.

For example, reading a Command Word from MIL-STD-1553 follows:

Address Transmit Sub-address Words
Bit range 0 - 4 5 6 - 10 11 - 15

Creating a representative struct with the help of bilge:

#[bitsize(16)]
#[derive(FromBits, DebugBits, PartialEq)]
struct Command {
    address: u5,
    transmit: bool,
    subaddress: u5,
    words: u5,
}

We can initialize the fields in proper order manually:

let cmd1: Command = Command::new(u5::new(5), true, u5::new(7), u5::new(3));
assert_eq!(u5::new(5), cmd1.address());
assert_eq!(true, cmd1.transmit());
assert_eq!(u5::new(7), cmd1.subaddress());
assert_eq!(u5::new(3), cmd1.words());

However, when reading the data from a stream in native ordering from the bus, the equivalent to the above would be 0b00101_1_00111_00011. Attempting to use this directly does not yield the desired result:

let cmd2: Command = Command::from(u16::new(0b00101_1_00111_00011));
assert_eq!(cmd1, cmd2);
thread 'test_command' panicked at 'assertion failed: `(left == right)`
  left: `Command { address: 5, transmit: true, subaddress: 7, words: 3 }`,
 right: `Command { address: 3, transmit: true, subaddress: 19, words: 5 }`'

Obviously the right-to-left parsing causes the mix-up. However fixing it client-side isn't quite straightforward. It's not a matter of endianness:

Address Transmit Sub-address Words Hex
Expected 00101 1 00111 00011 2C E3
Received 00011 1 10011 00101 1E 65

Swapping bytes (2CE3 -> E32C) would yield a drastically different result:


let cmd3: Command = Command::from(u16::from_be(0b00101_1_00111_00011));
assert_eq!(cmd1, cmd3);
thread 'test_command' panicked at 'assertion failed: `(left == right)`
  left: `Command { address: 5, transmit: true, subaddress: 7, words: 3 }`,
 right: `Command { address: 12, transmit: true, subaddress: 12, words: 28 }`'

We would instead need to pre-format data so that bilge can handle it:

let original: u16 = u16::new(0b00101_1_00111_00011);
let fixed: u16 = 
      (original & 0b11111) << 11
    | ((original & 0b11111_00000) >> 5) << 6
    | ((original & 0b1_00000_00000) >> 10) << 5
    | ((original & 0b11111_0_00000_00000) >> 11);
let cmd4: Command = Command::from(fixed);
assert_eq!(cmd1, cmd4);

This does not scale well as more structs are defined. Additionally, casting the struct to an integer is done right-to-left as well:

assert_eq!(original, cmd1);
thread 'test_command' panicked at 'assertion failed: `(left == right)`
  left: `11491`,
 right: `6629`',
Address Transmit Sub-address Words Hex
Expected 00101 1 00111 00011 2C E3
Received 00011 0 01111 00101 19 E5

Much of these issues could be resolved if there was an option to instead fill fields left-to-right. Perhaps something like #[derive(FromBitsLeftmost)]

hecatia-elegua commented 10 months ago

Thank you for the detailed writeup. Someone else wanted to switch the order too, since specs usually start with the top bits. I just checked, and reversing the field definition works. Can you try if that works and gives you valid values?

vhdirk commented 10 months ago

A while back, I did something similar yet far more invasive. The idea was that any field could be regarded as either big or little endian. I have a protocol where both the order of the fields can change as well as the endianness of integers...

I started implementing this as a fork of modular-bitfield https://github.com/vhdirk/modular-bitfield, though I can't remember what state I left it in. Perhaps this could serve as inspiration?