bitfield-struct-rs icon indicating copy to clipboard operation
bitfield-struct-rs copied to clipboard

Question: validation before structure creation?

Open JPHutchins opened this issue 1 year ago • 8 comments

I have a simple protocol that defines 2 bits for its Version field. Represented as an enum, only the values of 0 and 1 are valid - values 2 and 3 would represent a malformed message. The following implementation works just fine for compile time safety, but I'm not sure how to handle the runtime checking of data on the wire that could be malformed. Presently it hits unreachable.

What is ideal is for unreachable to be hit at compile time and for an Error to be generated at runtime.

Being new to Rust I am just stumped on how to do this declaratively!

The following is runnable with

[dependencies]
binrw = "0.14.0"
bitfield-struct = "0.8.0"
use binrw::{io::Cursor, BinRead, BinReaderExt, BinWrite};
use bitfield_struct::bitfield;

#[derive(Debug, PartialEq, Eq)]
#[repr(u8)]
enum Version {
    V1 = 0,
    V2 = 1,
}
impl Version {
    const fn into_bits(self) -> u8 {
        self as u8
    }
    const fn from_bits(bits: u8) -> Self {
        match bits {
            0 => Version::V1,
            1 => Version::V2,
            _ => unreachable!(),
        }
    }
}

#[bitfield(u8)]
#[derive(BinRead, BinWrite)]
struct Header {
    #[bits(3)]
    op: u8,
    #[bits(2)]
    version: Version,
    #[bits(3)]
    _reserved: u8,
}

fn main() {
    const GOOD_DATA: [u8; 1] = [0x01 | (1 << 3)];
    let mut reader = Cursor::new(&GOOD_DATA);
    let header: Header = match reader.read_be() {
        Ok(header) => header,
        Err(e) => panic!("Error reading header: {:?}", e),
    };
    println!("{:?}", header);

    const BAD_DATA: [u8; 1] = [0x01 | (2 << 3)];
    reader = Cursor::new(&BAD_DATA);
    let header: Header = match reader.read_be() {
        Ok(header) => header,
        Err(e) => panic!("Error reading header: {:?}", e),
    };
    println!("{:?}", header);
}

Output:

Header { op: 1, version: V2 }
thread 'main' panicked at src/main.rs:18:18:
internal error: entered unreachable code

So far I've added a "try from impl" to the Version that could return an Option<Version>. But this is not right either, because the field of the struct is not Optional, it is an invariant of the struct.

For example, this is not an "option":

#[bitfield(u8)]
#[derive(BinRead, BinWrite)]
struct Header {
    #[bits(3)]
    op: u8,
    #[bits(2, from = Version::try_from_bits)]
    version: Option<Version>,
    #[bits(3)]
    _reserved: u8,
}

Thanks for this great work! JP

JPHutchins avatar Aug 11 '24 18:08 JPHutchins