packed_struct.rs
packed_struct.rs copied to clipboard
Question on LSB0 endianness
Let's say I'm trying to interpret a u32
as the following bit-packed struct (bit index: 0=lsb, 31=msb)
bit | 31, 30, ..., 15, 16 | 15, 14, 13, 12 | 11, 10, 9, 8 | 7, 6, 5, 4 | 3, 2, 1, 0 |
---|---|---|---|---|---|
field | e | d | c | b | a |
What I expect: 0x12345678
should unpack into:
- a == 0x8
- b == 0x7
- c == 0x6
- d == 0x5
- e == 0x1234
Note that 0x12345678
in little-endian memory is [0x78, 0x56, 0x34, 0x12]
.
I attempted to implement this as follows:
#!/usr/bin/env rust-script
// cargo-deps: packed_struct="0.10"
use packed_struct::prelude::*;
#[derive(Copy, Clone, Debug, Eq, PartialEq, PackedStruct)]
#[packed_struct(size_bytes="4", bit_numbering="lsb0", endian="lsb")]
pub struct Pack {
#[packed_field(bits="0..4")]
pub a: u8,
#[packed_field(bits="4..8")]
pub b: u8,
#[packed_field(bits="8..12")]
pub c: u8,
#[packed_field(bits="12..16")]
pub d: u8,
#[packed_field(bits="16..32")]
pub e: u16,
}
pub fn main() {
{
let packed = 0x12345678u32;
let packed_bytes = packed.to_le_bytes();
let fields = Pack::unpack(&packed_bytes).unwrap();
println!("{}", fields);
assert_eq!(packed_bytes, [0x78u8, 0x56u8, 0x34u8, 0x12u8]);
assert_eq!(fields, Pack { a: 0x8, b: 0x7, c: 0x6, d: 0x5, e: 0x1234 });
}
{
let fields = Pack { a: 0x8, b: 0x7, c: 0x6, d: 0x5, e: 0x1234 };
let packed_bytes = fields.pack().unwrap();
let packed = u32::from_le_bytes(packed_bytes);
println!("{}", fields);
assert_eq!(packed_bytes, [0x78u8, 0x56u8, 0x34u8, 0x12u8]);
assert_eq!(packed, 0x12345678u32);
}
}
It fails, printing:
Pack (4 bytes)
Decimal
[120, 86, 52, 18]
Hex
[0x78, 0x56, 0x34, 0x12]
Binary
[0b01111000, 0b01010110, 0b00110100, 0b00010010]
a | bits 28:31 | 0b0010 | "2"
b | bits 24:27 | 0b0001 | "1"
c | bits 20:23 | 0b0100 | "4"
d | bits 16:19 | 0b0011 | "3"
x | bits 0:15 | 0b0111100001010110 | "22136"
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `Pack { a: 2, b: 1, c: 4, d: 3, x: 22136 }`,
right: `Pack { a: 8, b: 7, c: 6, d: 5, x: 4660 }`', pack_test.rs:27:9
I specified a
to be the least significant 4 bits, i.e. packed[3:0]
in Verilog notation. However:
- The debug printing shows that
a
is bits28:31
. - The actual value of
a
ispacked[27:24]
.
Also, for sure 0b0111100001010110 != 22136
...
Why?
A useful hint might be that the struct-level endian
parameter only sets the endianness per each field, not for the entire struct. That has to be done manually if needed. This library is really endianness agnostic by only providing arrays as the interface for structs. Also, the formatter only ever prints out the debug fields in MSB0 notation, that's why it looks a bit odd compared to the struct.
So by changing the field-level endiannes to msb
, and only taking your first two paragraphs into account, then I believe that the example is consistent. Try to work from there to fit your needs.
use packed_struct::prelude::*;
#[derive(Copy, Clone, Debug, Eq, PartialEq, PackedStruct)]
#[packed_struct(size_bytes="4", bit_numbering="lsb0", endian="msb")]
pub struct Pack {
#[packed_field(bits="0..4")]
pub a: u8,
#[packed_field(bits="4..8")]
pub b: u8,
#[packed_field(bits="8..12")]
pub c: u8,
#[packed_field(bits="12..16")]
pub d: u8,
#[packed_field(bits="16..32")]
pub e: u16,
}
#[test]
#[cfg(test)]
pub fn issue_92() {
// packing
let fields = Pack { a: 0x8, b: 0x7, c: 0x6, d: 0x5, e: 0x1234 };
println!("{}", fields);
let packed_bytes = fields.pack().unwrap();
assert_eq!(packed_bytes, [0x12u8, 0x34u8, 0x56u8, 0x78u8]);
let packed_int = u32::from_be_bytes(packed_bytes);
assert_eq!(packed_int, 0x12345678u32);
// unpacking
let unpacked = Pack::unpack(&packed_bytes).unwrap();
assert_eq!(unpacked, fields);
}
Thanks. Confirmed that endian="msb"
together with to_be_bytes
gives the correct result.
However, if the library is truly endianness agnostic, why does it not work if endian="lsb"
is used together with to_le_bytes
?
Hit a similar problem myself, and in that case, I cannot simply call from_le_bytes
on the struct being unpacked, because it's normally nested three levels deep in other packed structs that get read and unpacked or packed and written as a unit. Given that the library is already endian-aware to some extent, would it make sense to add another specifier or extend the functionality of endian
to control how the byte array is interpreted?