Repository: sharksforarms/deku
Pull request #503 (2024-11-02 17:52:46)

Add field magic support
- [x] Add docs

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count_from_u8_specialize | 96.1±0.89ns | 96.3±1.54ns | +0.21% |
| count_no_specialize | 2.4±0.02µs | 2.4±0.03µs | 0.00% |
| count_specialize | 97.6±2.59ns | 97.3±2.30ns | -0.31% |
| deku_read_bits | 1200.3±8.91ns | **1176.5±21.89ns** | **-1.98%** |
| deku_read_byte | 4.9±0.11ns | **4.5±0.08ns** | **-8.16%** |
| deku_read_enum | 4.1±0.03ns | **3.8±0.07ns** | **-7.32%** |
| deku_read_vec | 30.2±0.76ns | 30.7±0.84ns | +1.66% |
| deku_write_bits | 190.6±7.57ns | 192.6±8.08ns | +1.05% |
| deku_write_byte | 26.1±0.30ns | 26.1±0.51ns | 0.00% |
| deku_write_enum | 23.9±0.22ns | 23.9±0.53ns | 0.00% |
| deku_write_vec | 424.8±9.12ns | 428.3±8.14ns | +0.82% |
| read_all | **4.4±0.13µs** | 5.0±0.04µs | **+13.64%** |
| read_all_bytes | 4.9±0.03µs | **4.2±0.14µs** | **-14.29%** |

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count_from_u8_specialize | 116.5±1.80ns | **91.7±0.91ns** | **-21.29%** |
| count_no_specialize | **2.4±0.03µs** | 2.6±0.03µs | **+8.33%** |
| count_specialize | 95.8±1.13ns | 96.3±1.24ns | +0.52% |
| deku_read_bits | **1166.8±15.60ns** | 1186.8±18.32ns | **+1.71%** |
| deku_read_byte | **4.2±0.04ns** | 4.7±0.09ns | **+11.90%** |
| deku_read_enum | **3.6±0.04ns** | 4.7±0.07ns | **+30.56%** |
| deku_read_vec | 30.1±0.20ns | 30.3±0.60ns | +0.66% |
| deku_write_bits | 191.7±20.13ns | **183.9±6.50ns** | **-4.07%** |
| deku_write_byte | 26.1±0.50ns | 26.3±1.71ns | +0.77% |
| deku_write_enum | 23.9±0.31ns | 23.9±0.46ns | 0.00% |
| deku_write_vec | 446.5±21.24ns | 448.0±7.43ns | +0.34% |
| read_all | **4.9±0.04µs** | 5.0±0.04µs | **+2.04%** |
| read_all_bytes | **5.0±0.07µs** | 5.2±0.24µs | **+4.00%** |

  </details>
  
wcampbell0x2a: @sharksforarms rebased
github-actions[bot]: ## Benchmark for ceed057
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count_from_u8_specialize | 114.1±1.30ns | **99.8±0.71ns** | **-12.53%** |
| count_no_specialize | 4.1±0.01µs | **2.8±0.10µs** | **-31.71%** |
| count_specialize | 109.9±1.19ns | **96.8±1.35ns** | **-11.92%** |
| deku_read_bits | 1208.8±27.03ns | **1180.8±66.54ns** | **-2.32%** |
| deku_read_byte | 4.9±0.06ns | 4.9±0.05ns | 0.00% |
| deku_read_enum | 4.1±0.06ns | 4.1±0.05ns | 0.00% |
| deku_read_vec | 31.1±1.80ns | 30.9±0.92ns | -0.64% |
| deku_write_bits | 243.7±4.32ns | **234.0±7.31ns** | **-3.98%** |
| deku_write_byte | 24.0±0.37ns | 23.9±0.58ns | -0.42% |
| deku_write_enum | 22.9±0.31ns | 22.6±0.31ns | -1.31% |
| deku_write_vec | 503.9±13.11ns | 494.4±31.37ns | -1.89% |
| read_all | 7.6±0.14µs | **4.9±0.07µs** | **-35.53%** |
| read_all_bytes | 7.5±0.06µs | **5.0±0.13µs** | **-33.33%** |

  </details>
  

Patch:
diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs
index 5c849ea..6857d2a 100644
--- a/deku-derive/src/lib.rs
+++ b/deku-derive/src/lib.rs
@@ -510,6 +510,9 @@ struct FieldData {
 
     /// Bit Order of field
     bit_order: Option<syn::LitStr>,
+
+    /// magic value that needs to appear before field
+    magic: Option<syn::LitByteStr>,
 }
 
 impl FieldData {
@@ -556,6 +559,7 @@ impl FieldData {
             seek_from_end: receiver.seek_from_end?,
             seek_from_start: receiver.seek_from_start?,
             bit_order: receiver.bit_order,
+            magic: receiver.magic,
         };
 
         FieldData::validate(&data)?;
@@ -997,6 +1001,10 @@ struct DekuFieldReceiver {
     /// Bit Order of field
     #[darling(default)]
     bit_order: Option<syn::LitStr>,
+
+    /// magic value that needs to appear before field
+    #[darling(default)]
+    magic: Option<syn::LitByteStr>,
 }
 
 /// Receiver for the variant-level attributes inside a enum
diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs
index 4d2ac31..5526e9d 100644
--- a/deku-derive/src/macros/deku_read.rs
+++ b/deku-derive/src/macros/deku_read.rs
@@ -5,6 +5,7 @@ use darling::ToTokens;
 use proc_macro2::TokenStream;
 use quote::quote;
 use syn::LitStr;
+use syn::{Ident, LitByteStr};
 
 use crate::macros::{
     assertion_failed, gen_bit_order_from_str, gen_ctx_types_and_arg, gen_field_args,
@@ -499,20 +500,24 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
 fn emit_magic_read(input: &DekuData) -> TokenStream {
     let crate_ = super::get_crate_name();
     if let Some(magic) = &input.magic {
-        quote! {
-            let __deku_magic = #magic;
+        emit_magic_read_lit(&crate_, magic)
+    } else {
+        quote! {}
+    }
+}
 
-            for __deku_byte in __deku_magic {
-                let __deku_read_byte = u8::from_reader_with_ctx(__deku_reader, ())?;
-                if *__deku_byte != __deku_read_byte {
-                    extern crate alloc;
-                    use alloc::borrow::Cow;
-                    return Err(::#crate_::DekuError::Parse(Cow::from(format!("Missing magic value {:?}", #magic))));
-                }
+fn emit_magic_read_lit(crate_: &Ident, magic: &LitByteStr) -> TokenStream {
+    quote! {
+        let __deku_magic = #magic;
+
+        for __deku_byte in __deku_magic {
+            let __deku_read_byte = u8::from_reader_with_ctx(__deku_reader, ())?;
+            if *__deku_byte != __deku_read_byte {
+                extern crate alloc;
+                use alloc::borrow::Cow;
+                return Err(::#crate_::DekuError::Parse(Cow::from(format!("Missing magic value {:?}", #magic))));
             }
         }
-    } else {
-        quote! {}
     }
 }
 
@@ -774,6 +779,12 @@ fn emit_field_read(
         quote! {}
     };
 
+    let magic_read = if let Some(magic) = &f.magic {
+        emit_magic_read_lit(&crate_, magic)
+    } else {
+        quote! {}
+    };
+
     let field_read_func = if field_reader.is_some() {
         quote! { #field_reader? }
     } else {
@@ -986,6 +997,7 @@ fn emit_field_read(
 
     let field_read = quote! {
         #seek
+        #magic_read
         #pad_bits_before
 
         #bit_offset
diff --git a/deku-derive/src/macros/deku_write.rs b/deku-derive/src/macros/deku_write.rs
index 166fe42..ff6937c 100644
--- a/deku-derive/src/macros/deku_write.rs
+++ b/deku-derive/src/macros/deku_write.rs
@@ -551,6 +551,13 @@ fn emit_field_write(
     let crate_ = super::get_crate_name();
     let field_endian = f.endian.as_ref().or(input.endian.as_ref());
     let field_bit_order = f.bit_order.as_ref().or(input.bit_order.as_ref());
+    let magic_write = if let Some(magic) = &f.magic {
+        quote! {
+            ::#crate_::DekuWriter::to_writer(#magic, __deku_writer, ())?;
+        }
+    } else {
+        quote! {}
+    };
 
     let seek = if let Some(num) = &f.seek_from_current {
         quote! {
@@ -731,6 +738,7 @@ fn emit_field_write(
 
     let field_write = quote! {
         #seek
+        #magic_write
         #pad_bits_before
 
         #bit_offset
diff --git a/src/attributes.rs b/src/attributes.rs
index 1808ea8..5c56c34 100644
--- a/src/attributes.rs
+++ b/src/attributes.rs
@@ -34,7 +34,7 @@ enum DekuEnum {
 |-----------|------------------|------------
 | [endian](#endian) | top-level, field | Set the endianness
 | [bit_order](#bit_order) | top-level, field | Set the bit-order when reading bits
-| [magic](#magic) | top-level | A magic value that must be present at the start of this struct/enum
+| [magic](#magic) | top-level, field | A magic value that must be present at the start of this struct/enum/field
 | [seek_from_current](#seek_from_current) | top-level, field | Sets the offset of reader and writer to the current position plus the specified number of bytes
 | [seek_from_end](#seek_from_end) | top-level, field | Sets the offset to the size of reader and writer plus the specified number of bytes
 | [seek_from_start](#seek_from_start) | top-level, field | Sets the offset of reader and writer to provided number of bytes
@@ -235,10 +235,10 @@ assert_eq!(bytes, data);
 # magic
 
 Sets a "magic" value that must be present in the data at the start of
-a struct/enum when reading, and that is written out of the start of
+a struct/enum or field when reading, and that is written out of the start of
 that type's data when writing.
 
-Example:
+Example (top-level):
 ```rust
 # use deku::prelude::*;
 # use std::convert::{TryInto, TryFrom};
@@ -261,6 +261,29 @@ let value: Vec<u8> = value.try_into().unwrap();
 assert_eq!(data, value);
 ```
 
+Example (field):
+```rust
+# use deku::prelude::*;
+# use std::convert::{TryInto, TryFrom};
+# #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
+struct DekuTest {
+    #[deku(magic = b"deku")]
+    data: u8
+}
+
+let data: &[u8] = &[b'd', b'e', b'k', b'u', 50];
+
+let value = DekuTest::try_from(data).unwrap();
+
+assert_eq!(
+    DekuTest { data: 50 },
+    value
+);
+
+let value: Vec<u8> = value.try_into().unwrap();
+assert_eq!(data, value);
+```
+
 # seek_from_current
 
 Using the internal reader, seek to current position plus offset before reading field.


Test patch:
diff --git a/tests/test_magic.rs b/tests/test_magic.rs
index b87f017..5b54538 100644
--- a/tests/test_magic.rs
+++ b/tests/test_magic.rs
@@ -72,3 +72,22 @@ fn test_magic_enum(input: &[u8]) {
     let ret_write: Vec<u8> = ret_read.try_into().unwrap();
     assert_eq!(ret_write, input)
 }
+
+#[rstest(input,
+    case(&hex!("64656b7500")),
+)]
+fn test_struct_magic_field(input: &[u8]) {
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    struct TestStruct {
+        #[deku(magic = b"deku")]
+        magic: u8,
+    }
+    let input = input.to_vec();
+
+    let ret_read = TestStruct::try_from(input.as_slice()).unwrap();
+
+    assert_eq!(TestStruct { magic: 0 }, ret_read);
+
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(ret_write, input)
+}