Repository: sharksforarms/deku
Pull request #476 (2024-09-04 23:59:35)

Add support for specifying the id's endianness
Rebased and recent https://github.com/sharksforarms/deku/pull/337

Closes #337 

github-actions[bot]: ## Benchmark for 82c5749
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 5.0±0.02µs | 5.0±0.01µs | 0.00% |
| deku_read_bits | **1146.1±18.65ns** | 1201.7±19.64ns | **+4.85%** |
| deku_read_byte | 3.3±0.15ns | 3.3±0.04ns | 0.00% |
| deku_read_enum | 3.2±0.09ns | **3.1±0.01ns** | **-3.13%** |
| deku_read_vec | 42.5±0.31ns | **42.2±0.22ns** | **-0.71%** |
| deku_write_bits | **190.2±4.40ns** | 201.3±4.18ns | **+5.84%** |
| deku_write_byte | 24.9±0.39ns | 24.9±0.32ns | 0.00% |
| deku_write_enum | 23.5±0.51ns | 23.5±0.29ns | 0.00% |
| deku_write_vec | 417.3±6.78ns | 412.6±6.48ns | -1.13% |
| read_all | 5.5±0.02µs | 5.5±0.02µs | 0.00% |
| read_all_bytes | 3.7±0.05µs | 3.7±0.07µs | 0.00% |

  </details>
  
github-actions[bot]: ## Benchmark for a08f542
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 5.0±0.02µs | 5.0±0.01µs | 0.00% |
| deku_read_bits | **1147.1±17.33ns** | 1169.2±17.99ns | **+1.93%** |
| deku_read_byte | 3.1±0.05ns | 3.1±0.04ns | 0.00% |
| deku_read_enum | 3.1±0.03ns | 3.1±0.02ns | 0.00% |
| deku_read_vec | 42.2±0.34ns | 42.2±0.33ns | 0.00% |
| deku_write_bits | 194.0±3.89ns | 195.1±7.96ns | +0.57% |
| deku_write_byte | 24.5±0.21ns | 24.5±0.63ns | 0.00% |
| deku_write_enum | 23.0±0.33ns | 22.9±0.28ns | -0.43% |
| deku_write_vec | 412.9±7.17ns | 412.7±5.90ns | -0.05% |
| read_all | **3.7±0.06µs** | 5.5±0.03µs | **+48.65%** |
| read_all_bytes | 5.4±0.02µs | **3.8±0.12µs** | **-29.63%** |

  </details>
  
github-actions[bot]: ## Benchmark for fb2c47b
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 5.0±0.01µs | 5.0±0.01µs | 0.00% |
| deku_read_bits | 1144.7±18.40ns | 1144.7±16.65ns | 0.00% |
| deku_read_byte | 3.1±0.04ns | 3.1±0.14ns | 0.00% |
| deku_read_enum | 3.2±0.05ns | 3.2±0.02ns | 0.00% |
| deku_read_vec | 42.2±0.38ns | 42.2±0.42ns | 0.00% |
| deku_write_bits | 197.3±6.20ns | 202.1±6.53ns | +2.43% |
| deku_write_byte | 24.4±0.46ns | 24.5±0.42ns | +0.41% |
| deku_write_enum | 23.0±0.41ns | 23.0±0.39ns | 0.00% |
| deku_write_vec | **432.0±7.19ns** | 447.5±18.26ns | **+3.59%** |
| read_all | 5.5±0.03µs | 5.5±0.02µs | 0.00% |
| read_all_bytes | 3.8±0.06µs | 3.8±0.06µs | 0.00% |

  </details>
  

Patch:
diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs
index c79626f..7859ab8 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<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>,
@@ -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<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)]
diff --git a/deku-derive/src/macros/mod.rs b/deku-derive/src/macros/mod.rs
index 19555dd..06db7b1 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<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)});
 
diff --git a/src/attributes.rs b/src/attributes.rs
index 3be50e8..f73ebde 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<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.


Test patch:
diff --git a/tests/test_enum.rs b/tests/test_enum.rs
index 2d5931d..8c18699 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<u8> = ret_read.try_into().unwrap();
+    assert_eq!(input.to_vec(), ret_write);
+}