From dda8d6e2e7e32eafaa9d9f5b4e5758d4766e1ec0 Mon Sep 17 00:00:00 2001 From: Matt Palmer Date: Wed, 24 May 2023 22:55:24 +1000 Subject: [PATCH] Add support for specifying the id's endianness Doesn't propagate any further down the dependency tree, *only* affects ID parsing. --- deku-derive/src/lib.rs | 11 +++++++++ deku-derive/src/macros/mod.rs | 6 ++++- src/attributes.rs | 43 +++++++++++++++++++++++++++++++++++ tests/test_enum.rs | 24 +++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs index c79626fa..7859ab82 100644 --- a/deku-derive/src/lib.rs +++ b/deku-derive/src/lib.rs @@ -138,6 +138,9 @@ struct DekuData { /// enum only: type of the enum `id` id_type: Option, + /// enum only: endianness of the enum `id` + id_endian: Option, + /// enum only: bit size of the enum `id` #[cfg(feature = "bits")] bits: Option, @@ -202,6 +205,7 @@ impl DekuData { magic: receiver.magic, id: receiver.id, id_type: receiver.id_type?, + id_endian: receiver.id_endian, #[cfg(feature = "bits")] bits: receiver.bits, bytes: receiver.bytes, @@ -236,6 +240,8 @@ impl DekuData { )) } else if data.id.is_some() { Err(cerror(data.id.span(), "`id` only supported on enum")) + } else if data.id_endian.is_some() { + Err(cerror(data.id.span(), "`id_endian` only supported on enum")) } else if data.bytes.is_some() { Err(cerror(data.bytes.span(), "`bytes` only supported on enum")) } else { @@ -348,6 +354,7 @@ impl<'a> TryFrom<&'a DekuData> for DekuDataEnum<'a> { let id_args = crate::macros::gen_id_args( deku_data.endian.as_ref(), + deku_data.id_endian.as_ref(), #[cfg(feature = "bits")] deku_data.bits.as_ref(), #[cfg(not(feature = "bits"))] @@ -741,6 +748,10 @@ struct DekuReceiver { #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] id_type: Result, ReplacementError>, + /// enum only: endianness of the enum `id` + #[darling(default)] + id_endian: Option, + /// enum only: bit size of the enum `id` #[cfg(feature = "bits")] #[darling(default)] diff --git a/deku-derive/src/macros/mod.rs b/deku-derive/src/macros/mod.rs index 19555dd0..06db7b1a 100644 --- a/deku-derive/src/macros/mod.rs +++ b/deku-derive/src/macros/mod.rs @@ -248,11 +248,15 @@ fn gen_type_from_ctx_id( /// `#deku(endian = "big", bytes = 1)` -> `Endian::Big, ByteSize(1)` pub(crate) fn gen_id_args( endian: Option<&syn::LitStr>, + id_endian: Option<&syn::LitStr>, bits: Option<&Num>, bytes: Option<&Num>, ) -> syn::Result { let crate_ = get_crate_name(); - let endian = endian.map(gen_endian_from_str).transpose()?; + let endian = id_endian + .map(gen_endian_from_str) + .or_else(|| endian.map(gen_endian_from_str)) + .transpose()?; let bits = bits.map(|n| quote! {::#crate_::ctx::BitSize(#n)}); let bytes = bytes.map(|n| quote! {::#crate_::ctx::ByteSize(#n)}); diff --git a/src/attributes.rs b/src/attributes.rs index 3be50e85..f73ebdea 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -63,6 +63,7 @@ enum DekuEnum { | [ctx](#ctx) | top-level, field| Context list for context sensitive parsing | [ctx_default](#ctx_default) | top-level, field| Default context values | enum: [id](#id) | top-level, variant | enum or variant id value +| enum: [id_endian](#id_endian) | top-level | Endianness of *just* the enum `id` | enum: [id_pat](#id_pat) | variant | variant id match pattern | enum: [type](#type) | top-level | Set the type of the variant `id` | enum: [bits](#bits-1) | top-level | Set the bit-size of the variant `id` @@ -1379,6 +1380,48 @@ let variant_bytes: Vec = value.try_into().unwrap(); assert_eq!(vec![0x02], variant_bytes); ``` +# id_endian + +Specify the endianness of the variant `id`, without mandating the same endianness for the fields. + +Example: +```rust +# use deku::prelude::*; +# use std::convert::{TryInto, TryFrom}; +# #[derive(PartialEq, Debug, DekuRead, DekuWrite)] +#[deku(id_type = "u16", id_endian = "big", endian = "little")] +enum DekuTest { + // Takes its endianness from the enum spec + #[deku(id = "0x01")] + VariantLittle(u16), + // Force the endianness on the field + #[deku(id = "0x02")] + VariantBig { + #[deku(endian = "big")] + x: u16, + }, +} + +let data: Vec = vec![0x00, 0x01, 0x01, 0x00]; + +let (_, value) = DekuTest::from_bytes((data.as_ref(), 0)).unwrap(); + +assert_eq!( + DekuTest::VariantLittle(1), + value +); + +// ID changes, data bytes the same +let data: Vec = vec![0x00, 0x02, 0x01, 0x00]; + +let (_, value) = DekuTest::from_bytes((data.as_ref(), 0)).unwrap(); + +assert_eq!( + DekuTest::VariantBig { x: 256 }, + value +); +``` + # id_pat Specify the identifier in the form of a match pattern for the enum variant. diff --git a/tests/test_enum.rs b/tests/test_enum.rs index 2d5931d7..8c186997 100644 --- a/tests/test_enum.rs +++ b/tests/test_enum.rs @@ -278,3 +278,27 @@ fn test_litbool_as_id() { ); assert_eq!(input, &*v.to_bytes().unwrap()); } + +#[derive(PartialEq, Debug, DekuRead, DekuWrite)] +#[deku(id_type = "u16", id_endian = "big", endian = "little")] +enum VariableEndian { + #[deku(id = "0x01")] + Little(u16), + #[deku(id = "0x02")] + Big { + #[deku(endian = "big")] + x: u16, + }, +} + +#[rstest(input, expected, +case(&hex!("00010100"), VariableEndian::Little(1)), +case(&hex!("00020100"), VariableEndian::Big{x: 256}) +)] +fn test_variable_endian_enum(input: &[u8], expected: VariableEndian) { + let ret_read = VariableEndian::try_from(input).unwrap(); + assert_eq!(expected, ret_read); + + let ret_write: Vec = ret_read.try_into().unwrap(); + assert_eq!(input.to_vec(), ret_write); +}