hashmismatch / packed_struct.rs

Bit-level packing and unpacking for Rust
MIT License
164 stars 30 forks source link

Adds header and footer attributes and traits #106

Open JomerDev opened 1 year ago

JomerDev commented 1 year ago

Hello. I do quite like this library but had a need for some more features, so I wrote a PR for them. This PR adds attributes and traits to add static or dynamic headers and footers to a packed struct.

For static values a header and/or footer can be specified with the header and footer attributes. They will be added on pack and will be ignored on unpack. No validation is happening on unpack for either field.

#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)]
#[packed_struct(bit_numbering = "msb0", endian = "msb", header = [0x80, 0x60], footer = 0x16)]
pub struct FunctionCommand {

This fixes https://github.com/hashmismatch/packed_struct.rs/issues/91

If a more dynamic value is needed for either header or footer I've added two traits, PackedStructHeader and PackedStructFooter. They each provide the get_(header/footer) and validate_(header/footer) methods, which receive the already packed structure and can be used to generate values and to validate them. Currently the size of the header or footer must be specified with an attribute ((header/footer)_size = X) because I could not find a way to get the size of the return value in the derive macro. The validation method gets called before the rest of the struct gets unpacked. I added a PackingError::UserError to allow users to easily return possible validation errors

#[derive(PackedStruct, Debug, Clone, PartialEq)]
#[packed_struct(bit_numbering = "msb0", endian = "msb", footer_size = 1)]
pub struct DynamicCommand {
    [...]
}

impl PackedStructFooter for DynamicCommand {
    type FooterByteArray = [u8; 1];

    fn get_footer(&self, data: &[u8]) -> packed_struct::PackingResult<Self::FooterByteArray> {
        let mut xor: u8 = 0;
        data.into_iter().for_each(|value| xor ^= value);
        Ok([xor])
    }

    fn validate_footer(src: &[u8]) -> packed_struct::PackingResult<()> {
        let mut xor: u8 = 0;
        // We don't want to xor the xor value at the end
        src[0..src.len()].into_iter().for_each(|value| xor ^= value);
        if src.ends_with(&[xor]) {
            Ok(())
        } else {
            Err(PackingError::UserError(format!("Invalid xor: {} to {:?}", xor, src.last().unwrap())))
        }
    }
}

I've also added tests/examples for the above. I'm currently also looking into allowing enums with structs inside to work with packed_struct as well by using PackedStructHeader but if I do this it'll be part of a separate PR

JomerDev commented 1 year ago

The crash in the mips build is caused by the tap library, not my changes