Skip to content

Commit

Permalink
Add support for specifying the id's endianness (#476)
Browse files Browse the repository at this point in the history
Doesn't propagate any further down the dependency tree, *only* affects ID parsing.

Co-authored-by: Matt Palmer <mpalmer@hezmatt.org>
  • Loading branch information
wcampbell0x2a and mpalmer authored Sep 5, 2024
1 parent 616c53d commit 8fad609
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 1 deletion.
11 changes: 11 additions & 0 deletions deku-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ struct DekuData {
/// enum only: type of the enum `id`
id_type: Option<TokenStream>,

/// enum only: endianness of the enum `id`
id_endian: Option<syn::LitStr>,

/// enum only: bit size of the enum `id`
#[cfg(feature = "bits")]
bits: Option<Num>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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"))]
Expand Down Expand Up @@ -741,6 +748,10 @@ struct DekuReceiver {
#[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
id_type: Result<Option<TokenStream>, ReplacementError>,

/// enum only: endianness of the enum `id`
#[darling(default)]
id_endian: Option<syn::LitStr>,

/// enum only: bit size of the enum `id`
#[cfg(feature = "bits")]
#[darling(default)]
Expand Down
6 changes: 5 additions & 1 deletion deku-derive/src/macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TokenStream> {
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)});

Expand Down
43 changes: 43 additions & 0 deletions src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -1379,6 +1380,48 @@ let variant_bytes: Vec<u8> = 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<u8> = 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<u8> = 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.
Expand Down
24 changes: 24 additions & 0 deletions tests/test_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> = ret_read.try_into().unwrap();
assert_eq!(input.to_vec(), ret_write);
}

0 comments on commit 8fad609

Please sign in to comment.