Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions binrw/doc/attribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Glossary of directives in binrw attributes (`#[br]`, `#[bw]`, `#[brw]`).
|-----|-----------|----------|------------
| rw | [`align_after`](#padding-and-alignment) | field | Aligns the <span class="br">reader</span><span class="bw">writer</span> to the Nth byte after a field.
| rw | [`align_before`](#padding-and-alignment) | field | Aligns the <span class="br">reader</span><span class="bw">writer</span> to the Nth byte before a field.
| rw | [`align_size_to`](#padding-and-alignment) | field | Ensures the <span class="br">reader</span><span class="bw">writer</span> is always advanced by a multiple of N bytes.
| rw | [`args`](#arguments) | field | Passes arguments to another binrw object.
| rw | [`args_raw`](#arguments) | field | Like `args`, but specifies a single variable containing the arguments.
| rw | [`assert`](#assert) | struct, field, non-unit enum, data variant | Asserts that a condition is true. Can be used multiple times.
Expand Down Expand Up @@ -2121,6 +2122,25 @@ read the string and `pad_size_to(256)` will ensure the reader skips whatever
padding, if any, remains. If the string is longer than 256 bytes, no padding
will be skipped.

---

The `align_size_to` directive will ensure that the
<span class="br">reader</span><span class="bw">writer</span> has advanced a multiple of the number of bytes given after the field has been
<span class="br">read</span><span class="bw">written</span>:

<div class="br">

```text
#[br(align_size_to = $size:expr)] or #[br(align_size_to($size:expr))]
```
</div>
<div class="bw">

```text
#[bw(align_size_to = $size:expr)] or #[bw(align_size_to($size:expr))]
```
</div>

Any <span class="brw">(earlier only, when reading)</span><span class="br">earlier</span>
field or [import](#arguments) can be
referenced by the expressions in any of these directives.
Expand Down
7 changes: 6 additions & 1 deletion binrw/tests/dbg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ fn dbg() {
last: u8,
#[br(dbg)]
terminator: u8,
#[br(dbg, align_size_to = 2)]
after: u8,
}

// 🥴
if let Some("1") = option_env!("BINRW_IN_CHILD_PROC") {
Test::read(&mut Cursor::new(
b"\0\0\xff\xff\0\0\0\x04\xff\xff\0\x0e\xff\xed\xff\xff\x42\0\0\0\x69",
b"\0\0\xff\xff\0\0\0\x04\xff\xff\0\x0e\xff\xed\xff\xff\x42\0\0\0\x69\x25\0",
))
.unwrap();
} else {
Expand Down Expand Up @@ -55,12 +57,15 @@ fn dbg() {
"[{file}:{offset_2} | offset 0x10] last = 0x42\n",
"[{file}:{offset_2} | pad_size_to 0x4]\n",
"[{file}:{offset_3} | offset 0x14] terminator = 0x69\n",
"[{file}:{offset_4} | offset 0x15] after = 0x25\n",
"[{file}:{offset_4} | align_size_to 0x2]\n",
),
file = core::file!(),
offset_0 = if cfg!(nightly) { 16 } else { 11 },
offset_1 = if cfg!(nightly) { 18 } else { 11 },
offset_2 = if cfg!(nightly) { 20 } else { 11 },
offset_3 = if cfg!(nightly) { 22 } else { 11 },
offset_4 = if cfg!(nightly) { 24 } else { 11 },
)
);
}
Expand Down
13 changes: 13 additions & 0 deletions binrw/tests/derive/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,19 @@ fn pad_size_to() {
assert_eq!(result, Test { a: 1, b: 2 });
}

#[test]
fn align_size_to() {
#[derive(BinRead, Debug, PartialEq)]
struct Test {
#[br(align_size_to = 3)]
a: u32,
b: u8,
}

let result = Test::read_le(&mut Cursor::new(b"\x01\0\0\0\0\0\x02")).unwrap();
assert_eq!(result, Test { a: 1, b: 2 });
}

#[test]
fn parse_with_default_args() {
#[derive(Clone)]
Expand Down
13 changes: 11 additions & 2 deletions binrw/tests/derive/write/padding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ fn padding_round_trip() {

#[brw(pad_size_to = 0x6_u32)]
z: u32,

#[brw(align_size_to = 0x3_u32)]
w: u32,
}

let data = &[
/* pad_before: */ 0, 0, /* x */ 1, /* align: */ 0, 0, 0, 0, 0,
/* align_before: (none)*/ /* y */ 2, /* pad_after: */ 0, 0, 0, /* z */ 0,
0xab, 0xcd, 0xef, /* pad_size_to */ 0, 0,
0xab, 0xcd, 0xef, /* pad_size_to */ 0, 0, /* w */ 0x25,
/* align_size_to */ 0, 0, 0, 0, 0,
];
let test: Test = Cursor::new(data).read_be().unwrap();

Expand All @@ -53,12 +57,16 @@ fn padding_one_way() {

#[brw(pad_size_to = 0x6_u32)]
z: u32,

#[brw(align_size_to = 0x3_u32)]
w: u32,
}

let data = &[
/* pad_before: */ 0, 0, /* x */ 1, /* align: */ 0, 0, 0, 0, 0,
/* align_before: (none)*/ /* y */ 2, /* pad_after: */ 0, 0, 0, /* z */ 0xef,
0xcd, 0xab, 0, /* pad_size_to */ 0, 0,
0xcd, 0xab, 0, /* pad_size_to */ 0, 0, /* w */ 0x25, /* align_size_to */ 0,
0, 0, 0, 0,
];

let mut x = Cursor::new(Vec::new());
Expand All @@ -67,6 +75,7 @@ fn padding_one_way() {
x: 1,
y: 2,
z: 0xabcdef,
w: 0x25,
}
.write_options(&mut x, Endian::Little, ())
.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion binrw/tests/ui/invalid_keyword_struct_field.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `parse_with`, `count`, `offset`, `if`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg`
error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `parse_with`, `count`, `offset`, `if`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `align_size_to`, `dbg`
--> tests/ui/invalid_keyword_struct_field.rs:5:10
|
5 | #[br(invalid_struct_field_keyword)]
Expand Down
4 changes: 2 additions & 2 deletions binrw/tests/ui/non_blocking_errors.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ error: expected one of: `stream`, `big`, `little`, `is_big`, `is_little`, `map`,
6 | #[br(invalid_keyword_struct)]
| ^^^^^^^^^^^^^^^^^^^^^^

error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `parse_with`, `count`, `offset`, `if`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg`
error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `parse_with`, `count`, `offset`, `if`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `align_size_to`, `dbg`
--> tests/ui/non_blocking_errors.rs:8:10
|
8 | #[br(invalid_keyword_struct_field_a)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `parse_with`, `count`, `offset`, `if`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg`
error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `parse_with`, `count`, `offset`, `if`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `align_size_to`, `dbg`
--> tests/ui/non_blocking_errors.rs:10:10
|
10 | #[br(invalid_keyword_struct_field_b)]
Expand Down
3 changes: 2 additions & 1 deletion binrw_derive/src/binrw/backtrace/syntax_highlighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ fn visit_expr_attributes(field: &StructField, visitor: &mut Visitor) {
align_before,
align_after,
seek_before,
pad_size_to
pad_size_to,
align_size_to
);

if let Some(condition) = field.if_cond.clone() {
Expand Down
25 changes: 20 additions & 5 deletions binrw_derive/src/binrw/codegen/read_options/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ impl<'field> FieldGenerator<'field> {
let dbg_align_before = dbg_space("align_before", &at, self.field.align_before.as_ref());
let dbg_pad_size_to = dbg_space("pad_size_to", &at, self.field.pad_size_to.as_ref());
let dbg_pad_after = dbg_space("pad_after", &at, self.field.pad_after.as_ref());
let dbg_align_size_to =
dbg_space("align_size_to", &at, self.field.align_size_to.as_ref());
let dbg_align_after = dbg_space("align_after", &at, self.field.align_after.as_ref());

self.out = quote! {{
Expand All @@ -250,6 +252,7 @@ impl<'field> FieldGenerator<'field> {
);
#dbg_pad_size_to
#dbg_pad_after
#dbg_align_size_to
#dbg_align_after
#TEMP
}};
Expand Down Expand Up @@ -620,6 +623,13 @@ fn generate_seek_after(reader_var: &TokenStream, field: &StructField) -> TokenSt
.pad_after
.as_ref()
.map(|value| map_pad(reader_var, value));
let align_size_to = field.align_size_to.as_ref().map(|alignment| {
quote! {{
let align = (#alignment) as i64;
let size = (#SEEK_TRAIT::stream_position(#reader_var)? - #POS) as i64;
#SEEK_TRAIT::seek(#reader_var, #SEEK_FROM::Current((align - (size % align)) % align))?;
}}
});
let align_after = field
.align_after
.as_ref()
Expand All @@ -628,6 +638,7 @@ fn generate_seek_after(reader_var: &TokenStream, field: &StructField) -> TokenSt
quote! {
#pad_size_to
#pad_after
#align_size_to
#align_after
}
}
Expand All @@ -646,11 +657,15 @@ fn generate_seek_before(reader_var: &TokenStream, field: &StructField) -> TokenS
.align_before
.as_ref()
.map(|value| map_align(reader_var, value));
let pad_size_to_before = field.pad_size_to.as_ref().map(|_| {
quote! {
let #POS = #SEEK_TRAIT::stream_position(#reader_var)?;
}
});
let pad_size_to_before = field
.pad_size_to
.as_ref()
.or(field.align_size_to.as_ref())
.map(|_| {
quote! {
let #POS = #SEEK_TRAIT::stream_position(#reader_var)?;
}
});

quote! {
#seek_before
Expand Down
27 changes: 22 additions & 5 deletions binrw_derive/src/binrw/codegen/write_options/struct_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,18 @@ fn pad_after(writer_var: &TokenStream, field: &StructField) -> TokenStream {
#WRITE_ZEROES(#writer_var, (#padding) as u64)?;
}
});
let align_size_to = field.align_size_to.as_ref().map(|alignment| {
quote! {{
let align = (#alignment) as u64;
let after_pos = #SEEK_TRAIT::stream_position(#writer_var)?;
if let Some(size) = after_pos.checked_sub(#BEFORE_POS) {
let rem = size % align;
if rem != 0 {
#WRITE_ZEROES(#writer_var, align - rem)?;
}
}
}}
});
let align_after = field.align_after.as_ref().map(|alignment| {
quote! {{
let pos = #SEEK_TRAIT::stream_position(#writer_var)?;
Expand All @@ -378,6 +390,7 @@ fn pad_after(writer_var: &TokenStream, field: &StructField) -> TokenStream {
quote! {
#pad_size_to
#pad_after
#align_size_to
#align_after
#restore_position
}
Expand Down Expand Up @@ -407,11 +420,15 @@ fn pad_before(writer_var: &TokenStream, field: &StructField) -> TokenStream {
}
}}
});
let pad_size_to_before = field.pad_size_to.as_ref().map(|_| {
quote! {
let #BEFORE_POS = #SEEK_TRAIT::stream_position(#writer_var)?;
}
});
let pad_size_to_before = field
.pad_size_to
.as_ref()
.or(field.align_size_to.as_ref())
.map(|_| {
quote! {
let #BEFORE_POS = #SEEK_TRAIT::stream_position(#writer_var)?;
}
});
let store_position = field.restore_position.map(|()| {
quote! {
let #SAVED_POSITION = #SEEK_TRAIT::stream_position(#writer_var)?;
Expand Down
1 change: 1 addition & 0 deletions binrw_derive/src/binrw/parser/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use syn::{Expr, FieldValue, Token};

pub(super) type AlignAfter = MetaExpr<kw::align_after>;
pub(super) type AlignBefore = MetaExpr<kw::align_before>;
pub(super) type AlignSizeTo = MetaExpr<kw::align_size_to>;
pub(super) type Args = MetaEnclosedList<kw::args, Expr, FieldValue>;
pub(super) type ArgsRaw = MetaExpr<kw::args_raw>;
pub(super) type AssertLike<Keyword> = MetaList<Keyword, Expr>;
Expand Down
4 changes: 4 additions & 0 deletions binrw_derive/src/binrw/parser/field_level_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ attr_struct! {
pub(crate) seek_before: Option<TokenStream>,
#[from(RW:PadSizeTo)]
pub(crate) pad_size_to: Option<TokenStream>,
#[from(RW:AlignSizeTo)]
pub(crate) align_size_to: Option<TokenStream>,
#[from(RO:Debug)] // TODO is this really RO?
pub(crate) debug: Option<()>,
}
Expand Down Expand Up @@ -128,6 +130,7 @@ impl StructField {
align_after,
seek_before,
pad_size_to,
align_size_to,
magic
)
}
Expand Down Expand Up @@ -239,6 +242,7 @@ impl FromField for StructField {
align_after: <_>::default(),
seek_before: <_>::default(),
pad_size_to: <_>::default(),
align_size_to: <_>::default(),
#[cfg(feature = "verbose-backtrace")]
keyword_spans: <_>::default(),
err_context: <_>::default(),
Expand Down
1 change: 1 addition & 0 deletions binrw_derive/src/binrw/parser/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ macro_rules! define_keywords {
define_keywords! {
align_after,
align_before,
align_size_to,
args,
args_raw,
assert,
Expand Down