Repository: sharksforarms/deku
Pull request #163 (2021-01-07 19:26:23)

feat: Add padding related attributes
Fixes: #12 


Patch:
diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs
index d2ebccd..f760fac 100644
--- a/deku-derive/src/lib.rs
+++ b/deku-derive/src/lib.rs
@@ -321,6 +321,18 @@ struct FieldData {
     /// skip field reading/writing
     skip: bool,
 
+    /// pad a number of bits before
+    pad_bits_before: Option<TokenStream>,
+
+    /// pad a number of bytes before
+    pad_bytes_before: Option<TokenStream>,
+
+    /// pad a number of bits after
+    pad_bits_after: Option<TokenStream>,
+
+    /// pad a number of bytes after
+    pad_bytes_after: Option<TokenStream>,
+
     /// read field as temporary value, isn't stored
     temp: bool,
 
@@ -355,6 +367,10 @@ impl FieldData {
             reader: receiver.reader?,
             writer: receiver.writer?,
             skip: receiver.skip,
+            pad_bits_before: receiver.pad_bits_before?,
+            pad_bytes_before: receiver.pad_bytes_before?,
+            pad_bits_after: receiver.pad_bits_after?,
+            pad_bytes_after: receiver.pad_bytes_after?,
             temp: receiver.temp,
             default: receiver.default?,
             cond: receiver.cond?,
@@ -673,6 +689,22 @@ struct DekuFieldReceiver {
     #[darling(default)]
     skip: bool,
 
+    /// pad a number of bits before
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    pad_bits_before: Result<Option<TokenStream>, ReplacementError>,
+
+    /// pad a number of bytes before
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    pad_bytes_before: Result<Option<TokenStream>, ReplacementError>,
+
+    /// pad a number of bits after
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    pad_bits_after: Result<Option<TokenStream>, ReplacementError>,
+
+    /// pad a number of bytes after
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    pad_bytes_after: Result<Option<TokenStream>, ReplacementError>,
+
     /// read field as temporary value, isn't stored
     #[darling(default)]
     temp: bool,
diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs
index 1436f19..48a6fb0 100644
--- a/deku-derive/src/macros/deku_read.rs
+++ b/deku-derive/src/macros/deku_read.rs
@@ -431,6 +431,31 @@ fn emit_bit_byte_offsets(
     (bit_offset, byte_offset)
 }
 
+fn emit_padding(bit_size: &TokenStream) -> TokenStream {
+    quote! {
+        {
+            use core::convert::TryFrom;
+            let __deku_pad = usize::try_from(#bit_size).map_err(|e|
+                DekuError::InvalidParam(format!(
+                    "Invalid padding param \"{}\": cannot convert to usize",
+                    stringify!(#bit_size)
+                ))
+            )?;
+
+            if __deku_rest.len() >= __deku_pad {
+                let (__deku_padded_bits, new_rest) = __deku_rest.split_at(__deku_pad);
+                __deku_rest = new_rest;
+            } else {
+                return Err(DekuError::Parse(format!(
+                    "not enough data for padding: expected {} bits got {} bits",
+                    __deku_pad,
+                    __deku_rest.len()
+                )));
+            }
+        }
+    }
+}
+
 fn emit_field_read(
     input: &DekuData,
     i: usize,
@@ -515,6 +540,20 @@ fn emit_field_read(
         }
     };
 
+    let pad_bits_before = match (f.pad_bits_before.as_ref(), f.pad_bytes_before.as_ref()) {
+        (Some(pad_bits), Some(pad_bytes)) => emit_padding(&quote! { #pad_bits + (#pad_bytes * 8) }),
+        (Some(pad_bits), None) => emit_padding(pad_bits),
+        (None, Some(pad_bytes)) => emit_padding(&quote! {(#pad_bytes * 8)}),
+        (None, None) => quote!(),
+    };
+
+    let pad_bits_after = match (f.pad_bits_after.as_ref(), f.pad_bytes_after.as_ref()) {
+        (Some(pad_bits), Some(pad_bytes)) => emit_padding(&quote! { #pad_bits + (#pad_bytes * 8) }),
+        (Some(pad_bits), None) => emit_padding(pad_bits),
+        (None, Some(pad_bytes)) => emit_padding(&quote! {(#pad_bytes * 8)}),
+        (None, None) => quote!(),
+    };
+
     let field_read_normal = quote! {
         let (new_rest, value) = #field_read_func?;
         let value: #field_type = #field_map(value)?;
@@ -523,6 +562,7 @@ fn emit_field_read(
 
         value
     };
+
     let field_default = &f.default;
 
     let field_read_tokens = match (f.skip, &f.cond) {
@@ -560,6 +600,8 @@ fn emit_field_read(
     };
 
     let field_read = quote! {
+        #pad_bits_before
+
         #bit_offset
         #byte_offset
 
@@ -567,6 +609,8 @@ fn emit_field_read(
             #field_read_tokens
         };
         let #field_ident = &#internal_field_ident;
+
+        #pad_bits_after
     };
 
     Ok((field_ident, field_read))
diff --git a/deku-derive/src/macros/deku_write.rs b/deku-derive/src/macros/deku_write.rs
index 8c52abd..3763ebc 100644
--- a/deku-derive/src/macros/deku_write.rs
+++ b/deku-derive/src/macros/deku_write.rs
@@ -459,6 +459,22 @@ fn emit_bit_byte_offsets(
     (bit_offset, byte_offset)
 }
 
+fn emit_padding(bit_size: &TokenStream) -> TokenStream {
+    quote! {
+        {
+            use core::convert::TryFrom;
+            let __deku_pad = usize::try_from(#bit_size).map_err(|e|
+                DekuError::InvalidParam(format!(
+                    "Invalid padding param \"{}\": cannot convert to usize",
+                    stringify!(#bit_size)
+                ))
+            )?;
+            let new_len = __deku_output.len() + __deku_pad;
+            __deku_output.resize(new_len, false);
+        }
+    }
+}
+
 fn emit_field_write(
     input: &DekuData,
     i: usize,
@@ -487,6 +503,20 @@ fn emit_field_write(
         quote! { #object_prefix #field_ident.write(__deku_output, (#write_args)) }
     };
 
+    let pad_bits_before = match (f.pad_bits_before.as_ref(), f.pad_bytes_before.as_ref()) {
+        (Some(pad_bits), Some(pad_bytes)) => emit_padding(&quote! { #pad_bits + (#pad_bytes * 8) }),
+        (Some(pad_bits), None) => emit_padding(pad_bits),
+        (None, Some(pad_bytes)) => emit_padding(&quote! {(#pad_bytes * 8)}),
+        (None, None) => quote!(),
+    };
+
+    let pad_bits_after = match (f.pad_bits_after.as_ref(), f.pad_bytes_after.as_ref()) {
+        (Some(pad_bits), Some(pad_bytes)) => emit_padding(&quote! { #pad_bits + (#pad_bytes * 8) }),
+        (Some(pad_bits), None) => emit_padding(pad_bits),
+        (None, Some(pad_bytes)) => emit_padding(&quote! {(#pad_bytes * 8)}),
+        (None, None) => quote!(),
+    };
+
     let field_write_normal = quote! {
         #field_write_func ?;
     };
@@ -516,9 +546,14 @@ fn emit_field_write(
     };
 
     let field_write = quote! {
+        #pad_bits_before
+
         #bit_offset
         #byte_offset
+
         #field_write_tokens
+
+        #pad_bits_after
     };
 
     Ok(field_write)
diff --git a/src/attributes.rs b/src/attributes.rs
index 48fda55..9f8a9b3 100644
--- a/src/attributes.rs
+++ b/src/attributes.rs
@@ -16,8 +16,12 @@ A documentation-only module for #\[deku\] attributes
 | [update](#update) | field | Apply code over the field when `.update()` is called
 | [temp](#temp) | field | Read the field but exclude it from the struct/enum
 | [skip](#skip) | field | Skip the reading/writing of a field
+| [pad_bytes_before](#pad_bytes_before) | field | Skip bytes before reading, pad before writing
+| [pad_bits_before](#pad_bits_before) | field | Skip bits before reading, pad before writing
+| [pad_bytes_after](#pad_bytes_after) | field | Skip bytes after reading, pad after writing
+| [pad_bits_after](#pad_bits_after) | field | Skip bits after reading, pad after writing
 | [cond](#cond) | field | Conditional expression for the field
-| [default](#default) | field | Custom defaulting code when `skip` is true
+| [default](#default) | field | Provide default value. Used with [skip](#skip) or [cond](#cond)
 | [map](#map) | field | Apply a function over the result of reading
 | [reader](#readerwriter) | variant, field | Custom reader code
 | [writer](#readerwriter) | variant, field | Custom writer code
@@ -433,6 +437,136 @@ assert_eq!(
 );
 ```
 
+# pad_bytes_before
+
+Skip a number of bytes before reading, pad with 0x00s before writing
+
+Example:
+
+```rust
+# use deku::prelude::*;
+# use std::convert::{TryInto, TryFrom};
+#[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+pub struct DekuTest {
+    pub field_a: u8,
+    #[deku(pad_bytes_before = "2")]
+    pub field_b: u8,
+}
+
+let data: Vec<u8> = vec![0xAA, 0xBB, 0xCC, 0xDD];
+
+let value = DekuTest::try_from(data.as_ref()).unwrap();
+
+assert_eq!(
+    DekuTest {
+        field_a: 0xAA,
+        field_b: 0xDD,
+    },
+    value
+);
+
+let value: Vec<u8> = value.try_into().unwrap();
+assert_eq!(vec![0xAA, 0x00, 0x00, 0xDD], value);
+```
+
+# pad_bits_before
+
+Skip a number of bytes before reading, pad with 0s before writing
+
+Example:
+
+```rust
+# use deku::prelude::*;
+# use std::convert::{TryInto, TryFrom};
+#[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+struct DekuTest {
+    #[deku(bits = 2)]
+    field_a: u8,
+    #[deku(pad_bits_before = "2", bits = 4)]
+    field_b: u8,
+}
+
+let data: Vec<u8> = vec![0b10_01_1001];
+
+let value = DekuTest::try_from(data.as_ref()).unwrap();
+
+assert_eq!(
+    DekuTest {
+        field_a: 0b10,
+        field_b: 0b1001,
+    },
+    value
+);
+
+let value: Vec<u8> = value.try_into().unwrap();
+assert_eq!(vec![0b10_00_1001], value);
+```
+
+# pad_bytes_after
+
+Skip a number of bytes after reading, pad with 0x00s after writing
+
+Example:
+
+```rust
+# use deku::prelude::*;
+# use std::convert::{TryInto, TryFrom};
+#[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+pub struct DekuTest {
+    #[deku(pad_bytes_after = "2")]
+    pub field_a: u8,
+    pub field_b: u8,
+}
+
+let data: Vec<u8> = vec![0xAA, 0xBB, 0xCC, 0xDD];
+
+let value = DekuTest::try_from(data.as_ref()).unwrap();
+
+assert_eq!(
+    DekuTest {
+        field_a: 0xAA,
+        field_b: 0xDD,
+    },
+    value
+);
+
+let value: Vec<u8> = value.try_into().unwrap();
+assert_eq!(vec![0xAA, 0x00, 0x00, 0xDD], value);
+```
+
+# pad_bits_after
+
+Skip a number of bytes after reading, pad with 0s after writing
+
+Example:
+
+```rust
+# use deku::prelude::*;
+# use std::convert::{TryInto, TryFrom};
+#[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+struct DekuTest {
+    #[deku(bits = 2, pad_bits_after = "2")]
+    field_a: u8,
+    #[deku(bits = 4)]
+    field_b: u8,
+}
+
+let data: Vec<u8> = vec![0b10_01_1001];
+
+let value = DekuTest::try_from(data.as_ref()).unwrap();
+
+assert_eq!(
+    DekuTest {
+        field_a: 0b10,
+        field_b: 0b1001,
+    },
+    value
+);
+
+let value: Vec<u8> = value.try_into().unwrap();
+assert_eq!(vec![0b10_00_1001], value);
+```
+
 # cond
 
 Specify a condition to parse or skip a field


Test patch:
diff --git a/tests/test_attributes/mod.rs b/tests/test_attributes/mod.rs
index adb11fa..22ba827 100644
--- a/tests/test_attributes/mod.rs
+++ b/tests/test_attributes/mod.rs
@@ -2,6 +2,7 @@ mod test_cond;
 mod test_ctx;
 mod test_limits;
 mod test_map;
+mod test_padding;
 mod test_skip;
 mod test_temp;
 mod test_update;
diff --git a/tests/test_attributes/test_padding/mod.rs b/tests/test_attributes/test_padding/mod.rs
new file mode 100644
index 0000000..cba3b04
--- /dev/null
+++ b/tests/test_attributes/test_padding/mod.rs
@@ -0,0 +1,58 @@
+use deku::prelude::*;
+use std::convert::{TryFrom, TryInto};
+
+mod test_pad_bits_after;
+mod test_pad_bits_before;
+mod test_pad_bytes_after;
+mod test_pad_bytes_before;
+
+#[test]
+fn test_pad_bits_before_and_pad_bytes_before() {
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    struct TestStruct {
+        #[deku(bits = 2)]
+        field_a: u8,
+        #[deku(pad_bits_before = "6", pad_bytes_before = "1")]
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0b10_000000, 0xAA, 0xBB];
+
+    let ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+
+    assert_eq!(
+        TestStruct {
+            field_a: 0b10,
+            field_b: 0xBB,
+        },
+        ret_read
+    );
+
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(vec![0b10_000000, 0x00, 0xBB], ret_write);
+}
+
+#[test]
+fn test_pad_bits_after_and_pad_bytes_after() {
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    struct TestStruct {
+        #[deku(bits = 2, pad_bits_after = "6", pad_bytes_after = "1")]
+        field_a: u8,
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0b10_000000, 0xAA, 0xBB];
+
+    let ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+
+    assert_eq!(
+        TestStruct {
+            field_a: 0b10,
+            field_b: 0xBB,
+        },
+        ret_read
+    );
+
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(vec![0b10_000000, 0x00, 0xBB], ret_write);
+}
diff --git a/tests/test_attributes/test_padding/test_pad_bits_after.rs b/tests/test_attributes/test_padding/test_pad_bits_after.rs
new file mode 100644
index 0000000..65b7dc2
--- /dev/null
+++ b/tests/test_attributes/test_padding/test_pad_bits_after.rs
@@ -0,0 +1,83 @@
+use deku::prelude::*;
+use std::convert::{TryFrom, TryInto};
+
+#[test]
+fn test_pad_bits_after() {
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    struct TestStruct {
+        #[deku(bits = 2)]
+        field_a: u8,
+        #[deku(bits = 4, pad_bits_after = "2")]
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0b10_0110_01];
+
+    let ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+
+    assert_eq!(
+        TestStruct {
+            field_a: 0b10,
+            field_b: 0b0110,
+        },
+        ret_read
+    );
+
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(vec![0b10_0110_00], ret_write);
+}
+
+#[test]
+#[should_panic(expected = "Parse(\"not enough data for padding: expected 4 bits got 2 bits\")")]
+fn test_pad_bits_after_not_enough() {
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    struct TestStruct {
+        #[deku(bits = 2)]
+        field_a: u8,
+        #[deku(bits = 4, pad_bits_after = "4")]
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0b10_0110_01];
+
+    let _ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+}
+
+#[test]
+#[should_panic(
+    expected = r#"InvalidParam("Invalid padding param \"- 1\": cannot convert to usize")"#
+)]
+fn test_pad_bits_after_read_err() {
+    #[derive(PartialEq, Debug, DekuRead)]
+    struct TestStruct {
+        #[deku(bits = 2)]
+        field_a: u8,
+        #[deku(pad_bits_after = "-1", bits = 4)]
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0b10_01_1001];
+
+    let _ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+}
+
+#[test]
+#[should_panic(
+    expected = r#"InvalidParam("Invalid padding param \"- 1\": cannot convert to usize")"#
+)]
+fn test_pad_bits_after_write_err() {
+    #[derive(PartialEq, Debug, DekuWrite)]
+    struct TestStruct {
+        #[deku(bits = 2)]
+        field_a: u8,
+        #[deku(pad_bits_after = "-1", bits = 4)]
+        field_b: u8,
+    }
+
+    let data = TestStruct {
+        field_a: 0b10,
+        field_b: 0b1001,
+    };
+
+    let _ret_write: Vec<u8> = data.try_into().unwrap();
+}
diff --git a/tests/test_attributes/test_padding/test_pad_bits_before.rs b/tests/test_attributes/test_padding/test_pad_bits_before.rs
new file mode 100644
index 0000000..69eefdb
--- /dev/null
+++ b/tests/test_attributes/test_padding/test_pad_bits_before.rs
@@ -0,0 +1,83 @@
+use deku::prelude::*;
+use std::convert::{TryFrom, TryInto};
+
+#[test]
+fn test_pad_bits_before() {
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    struct TestStruct {
+        #[deku(bits = 2)]
+        field_a: u8,
+        #[deku(pad_bits_before = "2", bits = 4)]
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0b10_01_1001];
+
+    let ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+
+    assert_eq!(
+        TestStruct {
+            field_a: 0b10,
+            field_b: 0b1001,
+        },
+        ret_read
+    );
+
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(vec![0b10_00_1001], ret_write);
+}
+
+#[test]
+#[should_panic(expected = "Parse(\"not enough data for padding: expected 6 bits got 4 bits\")")]
+fn test_pad_bits_before_not_enough() {
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    struct TestStruct {
+        #[deku(bits = 4)]
+        field_a: u8,
+        #[deku(pad_bits_before = "6", bits = 2)]
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0b10_01_1001];
+
+    let _ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+}
+
+#[test]
+#[should_panic(
+    expected = r#"InvalidParam("Invalid padding param \"- 1\": cannot convert to usize")"#
+)]
+fn test_pad_bits_before_read_err() {
+    #[derive(PartialEq, Debug, DekuRead)]
+    struct TestStruct {
+        #[deku(bits = 2)]
+        field_a: u8,
+        #[deku(pad_bits_before = "-1", bits = 4)]
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0b10_01_1001];
+
+    let _ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+}
+
+#[test]
+#[should_panic(
+    expected = r#"InvalidParam("Invalid padding param \"- 1\": cannot convert to usize")"#
+)]
+fn test_pad_bits_before_write_err() {
+    #[derive(PartialEq, Debug, DekuWrite)]
+    struct TestStruct {
+        #[deku(bits = 2)]
+        field_a: u8,
+        #[deku(pad_bits_before = "-1", bits = 4)]
+        field_b: u8,
+    }
+
+    let data = TestStruct {
+        field_a: 0b10,
+        field_b: 0b1001,
+    };
+
+    let _ret_write: Vec<u8> = data.try_into().unwrap();
+}
diff --git a/tests/test_attributes/test_padding/test_pad_bytes_after.rs b/tests/test_attributes/test_padding/test_pad_bytes_after.rs
new file mode 100644
index 0000000..93f933c
--- /dev/null
+++ b/tests/test_attributes/test_padding/test_pad_bytes_after.rs
@@ -0,0 +1,79 @@
+use deku::prelude::*;
+use std::convert::{TryFrom, TryInto};
+
+#[test]
+fn test_pad_bytes_after() {
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    struct TestStruct {
+        #[deku(pad_bytes_after = "2")]
+        field_a: u8,
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0xAA, 0xBB, 0xCC, 0xDD];
+
+    let ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+
+    assert_eq!(
+        TestStruct {
+            field_a: 0xAA,
+            field_b: 0xDD,
+        },
+        ret_read
+    );
+
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(vec![0xAA, 0x00, 0x00, 0xDD], ret_write);
+}
+
+#[test]
+#[should_panic(expected = "Parse(\"not enough data for padding: expected 24 bits got 16 bits\")")]
+fn test_pad_bytes_after_not_enough() {
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    struct TestStruct {
+        field_a: u8,
+        #[deku(pad_bytes_after = "3")]
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0xAA, 0xBB, 0xCC, 0xDD];
+
+    let _ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+}
+
+#[test]
+#[should_panic(
+    expected = r#"InvalidParam("Invalid padding param \"(- 2 * 8)\": cannot convert to usize")"#
+)]
+fn test_pad_bytes_after_read_err() {
+    #[derive(PartialEq, Debug, DekuRead)]
+    struct TestStruct {
+        #[deku(pad_bytes_after = "-2")]
+        field_a: u8,
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0xAA, 0xBB, 0xCC, 0xDD];
+
+    let _ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+}
+
+#[test]
+#[should_panic(
+    expected = r#"InvalidParam("Invalid padding param \"(- 2 * 8)\": cannot convert to usize")"#
+)]
+fn test_pad_bytes_after_write_err() {
+    #[derive(PartialEq, Debug, DekuWrite)]
+    struct TestStruct {
+        #[deku(pad_bytes_after = "-2")]
+        field_a: u8,
+        field_b: u8,
+    }
+
+    let data = TestStruct {
+        field_a: 0xAA,
+        field_b: 0xDD,
+    };
+
+    let _ret_write: Vec<u8> = data.try_into().unwrap();
+}
diff --git a/tests/test_attributes/test_padding/test_pad_bytes_before.rs b/tests/test_attributes/test_padding/test_pad_bytes_before.rs
new file mode 100644
index 0000000..a886036
--- /dev/null
+++ b/tests/test_attributes/test_padding/test_pad_bytes_before.rs
@@ -0,0 +1,79 @@
+use deku::prelude::*;
+use std::convert::{TryFrom, TryInto};
+
+#[test]
+fn test_pad_bytes_before() {
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    struct TestStruct {
+        field_a: u8,
+        #[deku(pad_bytes_before = "2")]
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0xAA, 0xBB, 0xCC, 0xDD];
+
+    let ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+
+    assert_eq!(
+        TestStruct {
+            field_a: 0xAA,
+            field_b: 0xDD,
+        },
+        ret_read
+    );
+
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(vec![0xAA, 0x00, 0x00, 0xDD], ret_write);
+}
+
+#[test]
+#[should_panic(expected = "Parse(\"not enough data for padding: expected 16 bits got 0 bits\")")]
+fn test_pad_bytes_before_not_enough() {
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    struct TestStruct {
+        field_a: u8,
+        #[deku(pad_bytes_before = "2")]
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0xAA];
+
+    let _ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+}
+
+#[test]
+#[should_panic(
+    expected = r#"InvalidParam("Invalid padding param \"(- 2 * 8)\": cannot convert to usize")"#
+)]
+fn test_pad_bytes_before_read_err() {
+    #[derive(PartialEq, Debug, DekuRead)]
+    struct TestStruct {
+        field_a: u8,
+        #[deku(pad_bytes_before = "-2")]
+        field_b: u8,
+    }
+
+    let data: Vec<u8> = vec![0xAA, 0xBB, 0xCC, 0xDD];
+
+    let _ret_read = TestStruct::try_from(data.as_ref()).unwrap();
+}
+
+#[test]
+#[should_panic(
+    expected = r#"InvalidParam("Invalid padding param \"(- 2 * 8)\": cannot convert to usize")"#
+)]
+fn test_pad_bytes_before_write_err() {
+    #[derive(PartialEq, Debug, DekuWrite)]
+    struct TestStruct {
+        #[deku(pad_bytes_before = "-2")]
+        field_a: u8,
+        field_b: u8,
+    }
+
+    let data = TestStruct {
+        field_a: 0xAA,
+        field_b: 0xDD,
+    };
+
+    let _ret_write: Vec<u8> = data.try_into().unwrap();
+}