From b688927b0e4d416f884add00ef10ad177abccd62 Mon Sep 17 00:00:00 2001 From: wcampbell Date: Mon, 22 Dec 2025 09:10:25 -0500 Subject: [PATCH] Add padding size calculation to DekuSize --- deku-derive/src/macros/deku_size.rs | 53 +++++++++++++ tests/test_size.rs | 115 ++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) diff --git a/deku-derive/src/macros/deku_size.rs b/deku-derive/src/macros/deku_size.rs index cda54d67..c772cfad 100644 --- a/deku-derive/src/macros/deku_size.rs +++ b/deku-derive/src/macros/deku_size.rs @@ -35,6 +35,21 @@ fn calculate_fields_size<'a>( components.push(quote! { #magic_size }); } + #[cfg(feature = "bits")] + { + if let Some(pad_before) = + calculate_padding_size(f.pad_bits_before.as_ref(), f.pad_bytes_before.as_ref()) + { + components.push(pad_before); + } + } + #[cfg(not(feature = "bits"))] + { + if let Some(pad_before) = calculate_padding_size(f.pad_bytes_before.as_ref()) { + components.push(pad_before); + } + } + let field_type = &f.ty; #[cfg(feature = "bits")] if let Some(bits) = &f.bits { @@ -52,6 +67,21 @@ fn calculate_fields_size<'a>( components.push(quote! { <#field_type as ::#crate_::DekuSize>::SIZE_BITS }); } + #[cfg(feature = "bits")] + { + if let Some(pad_after) = + calculate_padding_size(f.pad_bits_after.as_ref(), f.pad_bytes_after.as_ref()) + { + components.push(pad_after); + } + } + #[cfg(not(feature = "bits"))] + { + if let Some(pad_after) = calculate_padding_size(f.pad_bytes_after.as_ref()) { + components.push(pad_after); + } + } + if components.is_empty() { None } else { @@ -68,6 +98,29 @@ fn calculate_magic_size(magic: &syn::LitByteStr) -> TokenStream { quote! { #magic_len * 8 } } +/// Calculate padding size (with bits feature) +#[cfg(feature = "bits")] +fn calculate_padding_size( + pad_bits: Option<&TokenStream>, + pad_bytes: Option<&TokenStream>, +) -> Option { + match (pad_bits, pad_bytes) { + (Some(bits), Some(bytes)) => Some(quote! { (#bits) + ((#bytes) * 8) }), + (Some(bits), None) => Some(quote! { (#bits) }), + (None, Some(bytes)) => Some(quote! { ((#bytes) * 8) }), + (None, None) => None, + } +} + +/// Calculate padding size (without bits feature) +#[cfg(not(feature = "bits"))] +fn calculate_padding_size(pad_bytes: Option<&TokenStream>) -> Option { + match pad_bytes { + Some(bytes) => Some(quote! { ((#bytes) * 8) }), + None => None, + } +} + /// Add DekuSize trait bounds to where clause for fields that need them fn add_field_bounds<'a>( where_clause: &mut Option, diff --git a/tests/test_size.rs b/tests/test_size.rs index 19aae9ac..62853f0a 100644 --- a/tests/test_size.rs +++ b/tests/test_size.rs @@ -562,3 +562,118 @@ fn test_nested_struct_with_magic() { assert_eq!(InnerWithMagic::SIZE_BYTES, Some(4)); assert_eq!(OuterWithMagic::SIZE_BYTES, Some(8)); } + +#[test] +fn test_struct_with_pad_bytes_before() { + #[derive(DekuRead, DekuWrite, DekuSize)] + struct PadBeforeStruct { + field_a: u8, + #[deku(pad_bytes_before = "2")] + field_b: u8, + } + + assert_eq!(PadBeforeStruct::SIZE_BYTES, Some(4)); + assert_eq!(PadBeforeStruct::SIZE_BITS, 32); +} + +#[test] +fn test_struct_with_pad_bytes_after() { + #[derive(DekuRead, DekuWrite, DekuSize)] + struct PadAfterStruct { + #[deku(pad_bytes_after = "3")] + field_a: u8, + field_b: u8, + } + + assert_eq!(PadAfterStruct::SIZE_BYTES, Some(5)); + assert_eq!(PadAfterStruct::SIZE_BITS, 40); +} + +#[test] +fn test_struct_with_pad_bytes_before_and_after() { + #[derive(DekuRead, DekuWrite, DekuSize)] + struct PadBothStruct { + #[deku(pad_bytes_before = "1", pad_bytes_after = "2")] + field_a: u8, + field_b: u16, + } + + assert_eq!(PadBothStruct::SIZE_BYTES, Some(6)); + assert_eq!(PadBothStruct::SIZE_BITS, 48); +} + +#[test] +#[cfg(feature = "bits")] +fn test_struct_with_pad_bits_before() { + #[derive(DekuRead, DekuWrite, DekuSize)] + #[deku(endian = "big")] + struct PadBitsBefore { + #[deku(bits = 4)] + field_a: u8, + #[deku(pad_bits_before = "4")] + #[deku(bits = 4)] + field_b: u8, + } + + assert_eq!(PadBitsBefore::SIZE_BITS, 12); + assert_eq!(PadBitsBefore::SIZE_BYTES, None); +} + +#[test] +#[cfg(feature = "bits")] +fn test_struct_with_pad_bits_after() { + #[derive(DekuRead, DekuWrite, DekuSize)] + #[deku(endian = "big")] + struct PadBitsAfter { + #[deku(bits = 3, pad_bits_after = "5")] + field_a: u8, + field_b: u8, + } + + assert_eq!(PadBitsAfter::SIZE_BITS, 16); + assert_eq!(PadBitsAfter::SIZE_BYTES, Some(2)); +} + +#[test] +#[cfg(feature = "bits")] +fn test_struct_with_mixed_padding() { + #[derive(DekuRead, DekuWrite, DekuSize)] + #[deku(endian = "big")] + struct MixedPadding { + #[deku(pad_bits_before = "2", pad_bytes_before = "1")] + #[deku(bits = 6)] + field_a: u8, + field_b: u8, + } + + assert_eq!(MixedPadding::SIZE_BITS, 24); + assert_eq!(MixedPadding::SIZE_BYTES, Some(3)); +} + +#[test] +fn test_struct_without_padding() { + #[derive(DekuRead, DekuWrite, DekuSize)] + struct NoPaddingStruct { + field_a: u8, + field_b: u16, + } + + assert_eq!(NoPaddingStruct::SIZE_BYTES, Some(3)); + assert_eq!(NoPaddingStruct::SIZE_BITS, 24); +} + +#[test] +fn test_multiple_fields_with_padding() { + #[derive(DekuRead, DekuWrite, DekuSize)] + struct MultiplePadding { + #[deku(pad_bytes_before = "1")] + field_a: u8, + #[deku(pad_bytes_after = "2")] + field_b: u8, + #[deku(pad_bytes_before = "1", pad_bytes_after = "1")] + field_c: u8, + } + + assert_eq!(MultiplePadding::SIZE_BYTES, Some(8)); + assert_eq!(MultiplePadding::SIZE_BITS, 64); +}