use binrw::{ args, binread, io::{Cursor, Seek, SeekFrom}, BinRead, BinResult, FilePtr, NullString, }; #[test] fn all_the_things() { #[derive(Debug)] struct PlainObject; #[allow(dead_code)] #[derive(BinRead, Debug)] #[br(is_big = true, magic = b"TEST")] #[br(assert(entries.len() as u32 == extra_entry_count + 1))] struct Test { extra_entry_count: u32, #[br(count = extra_entry_count + 1, args { inner: args! { inner: args! { extra_val: 0x69 } } })] entries: Vec>, #[br(default)] start_as_none: Option, #[br(calc = 1 + 2)] calc_test: u32, } #[binrw::parser(reader, endian)] fn read_offsets() -> BinResult<(u16, u16)> { Ok(( u16::read_options(reader, endian, ())?, u16::read_options(reader, endian, ())?, )) } #[allow(dead_code)] #[derive(BinRead, Debug)] #[br(little, magic = b"TST2")] #[br(import { extra_val: u8 })] struct TestEntry { #[br(map = |val: u32| val.to_string())] entry_num: String, #[br(assert(offsets.1 - offsets.0 == 0x10))] #[br(seek_before(SeekFrom::Current(4)))] #[br(parse_with = read_offsets)] #[br(is_big = entry_num == "1")] offsets: (u16, u16), #[br(if(offsets.0 == 0x20))] name: Option>, #[br(calc(extra_val))] extra_val: u8, } Test::read(&mut Cursor::new(include_bytes!("data/test_file.bin"))).unwrap(); } #[test] fn assert() { #[allow(dead_code)] #[derive(BinRead, Debug)] struct Test { #[br(assert(a == 1))] a: u8, } Test::read_le(&mut Cursor::new("\x01")).unwrap(); let error = Test::read_le(&mut Cursor::new("\0")).expect_err("accepted bad data"); match error { binrw::Error::AssertFail { pos, message } => { assert_eq!(pos, 0); assert_eq!(message, "assertion failed: `a == 1`"); } _ => panic!("bad error type"), } } #[test] fn assert_custom_err() { #[derive(Debug)] struct Oops(u8); impl core::fmt::Display for Oops { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "oops!") } } #[allow(dead_code)] #[derive(BinRead, Debug)] struct Test { #[br(assert(a == 1, Oops(a)))] a: u8, } Test::read_le(&mut Cursor::new("\x01")).unwrap(); let error = Test::read_le(&mut Cursor::new("\x02")).expect_err("accepted bad data"); assert_eq!(format!("{error}"), "oops! at 0x0"); let error = error.custom_err::().expect("bad error type"); assert_eq!(error.0, 2); } #[test] fn assert_formatted() { #[allow(dead_code)] #[derive(BinRead, Debug)] struct Test { #[br(assert(a == 1, "a was {}", a))] a: u8, } Test::read_le(&mut Cursor::new("\x01")).unwrap(); let error = Test::read_le(&mut Cursor::new("\0")).expect_err("accepted bad data"); match error { binrw::Error::AssertFail { pos, message } => { assert_eq!(pos, 0); assert_eq!(message, "a was 0"); } _ => panic!("bad error type"), } } #[test] fn calc_temp_field() { #[binread] #[derive(Debug, PartialEq)] #[br(big)] struct Test { #[br(temp)] len: u32, #[br(count = len)] vec: Vec, } let result = Test::read(&mut Cursor::new(b"\0\0\0\x05ABCDE")).unwrap(); // This also indirectly checks that `temp` is actually working since // compilation would fail if it weren’t due to missing the `len` field assert_eq!( result, Test { vec: b"ABCDE".to_vec() } ); } #[test] fn count_too_big() { #[derive(BinRead, Debug)] #[br(little)] struct Test { _a: u128, #[br(count = _a)] _b: Vec, } let error = Test::read(&mut Cursor::new(u128::MAX.to_le_bytes())).expect_err("accepted bad count"); match error { binrw::Error::AssertFail { pos, message } => { assert_eq!(pos, core::mem::size_of::() as u64); assert_eq!( message, format!("count {} out of range of usize", u128::MAX) ); } _ => panic!("bad error type"), } } #[test] fn count_no_useless_conversion_lint() { const LEN: usize = 1; #[derive(BinRead, Debug, PartialEq)] #[br(little)] struct Test { #[br(count = LEN)] data: Vec, } assert_eq!( Test::read(&mut Cursor::new("\x01")).unwrap(), Test { data: vec![1; 1] } ); } #[test] fn deref_now() { #[derive(BinRead, Debug, PartialEq)] #[br(big, magic = b"TEST")] struct Test { a: FilePtr, b: i32, } let result = Test::read(&mut Cursor::new(include_bytes!("data/deref_now.bin"))).unwrap(); assert_eq!( result, Test { a: FilePtr { ptr: 0x10, value: NullString(b"Test string".to_vec()) }, b: -1, } ); } #[test] fn move_args() { #[derive(Debug, PartialEq)] struct NonCopyArg; #[derive(BinRead, Debug, PartialEq)] #[br(import(v: NonCopyArg))] struct Inner(#[br(calc = v)] NonCopyArg); #[derive(BinRead, Debug, PartialEq)] #[br(import(v: NonCopyArg))] struct Test { #[br(args(v))] inner: Inner, } assert_eq!( Test::read_le_args(&mut Cursor::new(b""), (NonCopyArg,)).unwrap(), Test { inner: Inner(NonCopyArg) } ); } #[test] fn move_parser() { struct A; impl A { fn accept(&self, x: u32) -> bool { x == 0 } } #[derive(BinRead, Debug, PartialEq)] #[br(import(a: A))] struct Test { #[br(parse_with = binrw::helpers::until(|x| a.accept(*x)))] using_until: Vec, } assert_eq!( Test::read_le_args(&mut Cursor::new(b"\x01\0\0\0\x02\0\0\0\0\0\0\0"), (A,)).unwrap(), Test { using_until: vec![1, 2, 0], } ); } #[test] fn move_stream() { #[binread] #[derive(Debug, PartialEq)] struct Test { #[br(map_stream = |r| r)] flags: u32, } assert_eq!( Test::read_le(&mut Cursor::new(b"\x01\0\0\0")).unwrap(), Test { flags: 1 } ); } #[test] fn move_stream_with_count() { #[binread] #[derive(Debug, PartialEq)] struct Test { #[br(count = 1, map_stream = |r| r)] flags: Vec, } assert_eq!( Test::read_le(&mut Cursor::new(b"\x01\0\0\0")).unwrap(), Test { flags: vec![1] } ); } #[test] fn move_named_stream_with_count() { #[binread] #[derive(Debug, PartialEq)] #[br(stream = s, map_stream = |r| r)] struct Test { #[br(count = 1)] flags: Vec, } assert_eq!( Test::read_le(&mut Cursor::new(b"\x01\0\0\0")).unwrap(), Test { flags: vec![1] } ); } // See https://github.com/jam1garner/binrw/issues/118 #[test] fn move_temp_field() { #[binread] #[derive(Debug, Eq, PartialEq)] struct Foo { #[br(temp)] foo: binrw::NullString, #[br(calc = foo)] bar: binrw::NullString, } assert_eq!( Foo::read_le(&mut Cursor::new(b"hello\0goodbyte\0")).unwrap(), Foo { bar: binrw::NullString::from("hello"), } ); } #[test] fn mut_map() { #[derive(BinRead, Debug, PartialEq)] #[br(import(v: &mut Vec))] struct Test { #[br(map = |a: u8| a + v.pop().unwrap())] a: u8, #[br(map = |b: u8| b + v.pop().unwrap())] b: u8, } assert_eq!( Test::read_le_args(&mut Cursor::new(b"\x01\x02"), (&mut vec![1, 2],)).unwrap(), Test { a: 3, b: 3 } ); } #[test] fn empty_imports() { #[derive(BinRead, Debug, PartialEq)] #[br(import())] struct Test { a: u8, } let result = Test::read_le(&mut Cursor::new(b"\x01")).unwrap(); assert_eq!(result, Test { a: 1 }); } #[test] fn empty_named_imports() { #[derive(BinRead, Debug, PartialEq)] #[br(import{})] struct Test { a: u8, } let result = Test::read_le(&mut Cursor::new(b"\x01")).unwrap(); assert_eq!(result, Test { a: 1 }); } #[test] fn all_default_imports() { #[derive(BinRead, Debug, PartialEq)] #[br(import { _default: u8 = 42 })] struct Test { a: u8, } let result = Test::read_le(&mut Cursor::new(b"\x01")).unwrap(); assert_eq!(result, Test { a: 1 }); } #[test] fn recursive_lifetime_imports() { #[derive(Default)] struct InnerArgs<'a> { inner: &'a str, } #[derive(Default)] struct OuterArgs<'a, Extra> { outer: &'a str, extra: Extra, } #[derive(BinRead, Debug, PartialEq)] #[br(import_raw(args: OuterArgs<'_, &'_ InnerArgs<'_>>))] struct Test { #[br(calc(String::from(args.outer) + args.extra.inner))] a: String, } assert_eq!( Test::read_le_args( &mut Cursor::new(""), OuterArgs { outer: "hello", extra: &InnerArgs { inner: " world" } } ) .unwrap(), Test { a: String::from("hello world") } ); } #[test] fn gat_list() { #[derive(BinRead, Debug, PartialEq)] #[br(little, import(borrowed: &u8))] struct Test { #[br(calc(*borrowed))] a: u8, } assert_eq!( Test::read_args(&mut Cursor::new(b""), (&1_u8,)).unwrap(), Test { a: 1 } ); } #[test] fn gat_named() { #[derive(BinRead, Debug, PartialEq)] #[br(little, import { borrowed: &u8 })] struct Test { #[br(calc(*borrowed))] a: u8, } assert_eq!( Test::read_args(&mut Cursor::new(b""), binrw::args! { borrowed: &1_u8 }).unwrap(), Test { a: 1 } ); } #[test] fn gat_raw() { #[derive(BinRead, Debug, PartialEq)] #[br(little, import_raw(borrowed: &u8))] struct Test { #[br(calc(*borrowed))] a: u8, } assert_eq!( Test::read_args(&mut Cursor::new(b""), &1_u8).unwrap(), Test { a: 1 } ); } #[test] fn if_alternate() { #[derive(BinRead, Debug)] #[br(import{ try_read: bool })] struct Test { #[br(if(try_read, 10))] a: u8, } let result = Test::read_le_args( &mut Cursor::new(b"\x01"), ::Args::builder().try_read(true).finalize(), ) .unwrap(); assert_eq!(result.a, 1); let result = Test::read_le_args(&mut Cursor::new(b"\x01"), binrw::args! { try_read: false }).unwrap(); assert_eq!(result.a, 10); } #[test] fn ignore_and_default() { #[derive(Debug, Eq, PartialEq)] struct One(u8); impl Default for One { fn default() -> Self { Self(1) } } #[derive(BinRead, Debug, PartialEq)] #[br(big)] struct Test { a: u8, #[br(default)] b: One, #[br(ignore)] c: One, } let result = Test::read(&mut Cursor::new(b"\x02")).unwrap(); assert_eq!( result, Test { a: 2, b: <_>::default(), c: <_>::default() } ); } #[test] fn magic_byte() { #[derive(BinRead, Debug)] #[br(magic = b'a')] struct Test; Test::read(&mut Cursor::new(b"a")).unwrap(); Test::read(&mut Cursor::new(b"")).expect_err("accepted bad data"); Test::read(&mut Cursor::new(b"x")).expect_err("accepted bad data"); } #[test] fn magic_field() { #[derive(BinRead, Debug, PartialEq)] #[br(magic(b"A"))] struct Test { b: u8, #[br(magic(b"C"))] d: u8, } Test::read_le(&mut Cursor::new(b"ABBB")).expect_err("accepted bad data"); let result = Test::read_le(&mut Cursor::new(b"ABCD")).unwrap(); assert_eq!(result, Test { b: b'B', d: b'D' }); } #[test] fn magic_const() { use binrw::meta::ReadMagic; #[derive(BinRead, Debug)] #[br(magic = b'a')] struct Test; assert_eq!(Test::MAGIC, b'a'); } #[test] fn map_stream() { use binrw::io::TakeSeekExt; #[derive(BinRead, Debug, PartialEq)] #[br(magic = b"magic", map_stream = |reader| reader.take_seek(4))] struct Test { #[br(parse_with = binrw::helpers::until_eof)] a: Vec, } assert_eq!( Test::read_le(&mut Cursor::new(b"magichello world")).unwrap(), Test { a: b"hell".to_vec() } ); } #[test] fn map_stream_field() { use binrw::io::TakeSeekExt; #[derive(BinRead, Debug, PartialEq)] struct Test { #[br(magic = b"magic", map_stream = |reader| reader.take_seek(5), parse_with = binrw::helpers::until_eof)] a: Vec, b: u8, #[br(magic = b"magic", map_stream = |reader| reader.take_seek(5), parse_with = binrw::helpers::until_eof)] c: Vec, } assert_eq!( Test::read_le(&mut Cursor::new(b"magichello magicworldx")).unwrap(), Test { a: b"hello".to_vec(), b: b' ', c: b"world".to_vec(), } ); } #[test] fn map_stream_parse_with_args() { use binrw::{helpers::until_eof, io::TakeSeekExt}; #[derive(BinRead, Debug, PartialEq)] #[br(import(extra: u8))] struct Inner(#[br(map = |v: u8| v + extra)] u8); #[derive(BinRead, Debug, PartialEq)] struct Test { a: u8, #[br(map_stream = |s| s.take_seek(4), parse_with = until_eof, args(a))] b: Vec, c: u8, } assert_eq!( Test::read_le(&mut Cursor::new(b"\x0a\x00\x01\x02\x03\x04")).unwrap(), Test { a: 10, b: vec![Inner(10), Inner(11), Inner(12), Inner(13)], c: 4 } ); } #[test] fn named_args_trailing_commas() { #[rustfmt::skip] #[derive(BinRead, Debug, PartialEq)] struct Test { count: u8, #[br(args { count: count.into(), inner: ItemBinReadArgs { count: 2, }, })] items: Vec, } #[rustfmt::skip] #[derive(BinRead, Debug, PartialEq)] #[br(import { count: usize, })] struct Item(#[br(args { count, })] Vec); assert_eq!( Test::read_le(&mut Cursor::new(b"\x03\x04\0\x05\0\x06\0")).unwrap(), Test { count: 3, items: vec![Item(vec![4, 0]), Item(vec![5, 0]), Item(vec![6, 0])] } ); } #[test] fn pad_after_before() { #[derive(BinRead, Debug, PartialEq)] struct Test { #[br(pad_after = 1, pad_before = 1)] a: u8, b: u8, } let result = Test::read_le(&mut Cursor::new(b"\0\x01\0\x02")).unwrap(); assert_eq!(result, Test { a: 1, b: 2 }); } #[test] fn pad_size_to() { #[derive(BinRead, Debug, PartialEq)] struct Test { #[br(pad_before = 1, pad_size_to = 2)] a: u8, b: u8, } let result = Test::read_le(&mut Cursor::new(b"\0\x01\0\x02")).unwrap(); assert_eq!(result, Test { a: 1, b: 2 }); } #[test] fn parse_with_default_args() { #[derive(Clone)] struct Args(u8); impl Default for Args { fn default() -> Self { Self(42) } } #[derive(BinRead, Debug, PartialEq)] #[br(import { in_a: u8 })] struct InnerImport { #[br(calc(in_a))] a: u8, b: u8, } #[derive(BinRead, Debug, PartialEq)] #[br(import_raw(args: Args))] struct InnerImportTuple { #[br(calc(args.0))] a: u8, b: u8, } #[derive(BinRead, Debug, PartialEq)] struct Test { #[br(args{ in_a: 0 })] #[br(parse_with = InnerImport::read_options)] inner: InnerImport, #[br(parse_with = InnerImportTuple::read_options)] inner_tuple: InnerImportTuple, } let result = Test::read_le(&mut Cursor::new(b"\x02\x04")).unwrap(); assert_eq!( result, Test { inner: InnerImport { a: 0, b: 2 }, inner_tuple: InnerImportTuple { a: 42, b: 4 } } ); } #[test] fn args_type_hint_borrowck() { #[derive(BinRead, Debug, PartialEq)] #[br(import(a: u8))] struct NeedsArgs(#[br(map = |x: u8| x + a)] u8); #[derive(BinRead, Debug, PartialEq)] struct Test { #[br(args(4), parse_with = binrw::helpers::until(|x| x == &NeedsArgs(4)))] a: Vec, } let result = Test::read_le(&mut Cursor::new(b"\x01\x00\x02")).unwrap(); assert_eq!( result, Test { a: vec![NeedsArgs(5), NeedsArgs(4)] } ); } #[test] fn args_same_name() { #[allow(dead_code)] #[derive(BinRead, Debug)] #[br(import { y: u16, x: u8 })] struct Test { #[br(calc(x))] z: u8, #[br(calc(y))] z2: u16, } #[allow(dead_code)] #[derive(BinRead, Debug)] struct Test2 { #[br(calc(3))] x: u8, #[br(args { x, y: 3 })] y: Test, } let result = Test2::read_le(&mut Cursor::new(b"")).unwrap(); assert_eq!(result.y.z, 3); } #[test] fn import_tuple() { #[derive(BinRead, Debug)] struct Test { #[br(args_raw = (1, 2))] a: Child, } #[derive(BinRead, Debug)] #[br(import_raw(args: (u8, u8)))] struct Child { #[br(calc(args.0 + args.1))] a: u8, } let result = Test::read_le(&mut Cursor::new(b"")).unwrap(); assert_eq!(result.a.a, 3); } #[test] fn mixed_attrs() { #[binread] #[binrw::binwrite] #[brw(big)] struct Foo { a: Bar, } #[binrw::binwrite] #[binread] struct Bar { a: u8, } let test = Foo::read(&mut Cursor::new(b"\x2a")).unwrap(); assert_eq!(test.a.a, 42); let mut output = Cursor::new(vec![]); binrw::BinWrite::write(&test, &mut output).unwrap(); assert_eq!(output.into_inner(), b"\x2a"); } #[test] fn raw_ident() { #[allow(dead_code)] #[derive(BinRead)] struct Test { r#type: u32, } Test::read_le(&mut Cursor::new(vec![0x00, 0x00, 0x00, 0x00])).unwrap(); } #[test] fn reader_var() { struct Checksum { inner: T, check: core::num::Wrapping, } impl Checksum { fn new(inner: T) -> Self { Self { inner, check: core::num::Wrapping(0), } } fn check(&self) -> u8 { self.check.0 } } impl binrw::io::Read for Checksum { fn read(&mut self, buf: &mut [u8]) -> binrw::io::Result { let size = self.inner.read(buf)?; for b in &buf[0..size] { self.check += b; } Ok(size) } } impl Seek for Checksum { fn seek(&mut self, pos: SeekFrom) -> binrw::io::Result { self.inner.seek(pos) } } #[derive(BinRead, Debug, PartialEq)] #[br(little, stream = r, map_stream = Checksum::new)] struct Test { a: u16, b: u16, #[br(calc(r.check()))] c: u8, } assert_eq!( Test::read(&mut Cursor::new(b"\x01\x02\x03\x04")).unwrap(), Test { a: 0x201, b: 0x403, c: 10, } ); } #[test] fn top_level_assert_has_self() { #[allow(dead_code)] #[derive(BinRead, Debug)] #[br(assert(self.verify(), "verify failed"))] struct Test { a: u8, b: u8, } impl Test { fn verify(&self) -> bool { self.a == self.b } } let mut data = Cursor::new(b"\x01\x01"); Test::read_le(&mut data).expect("a == b passed"); let mut data = Cursor::new(b"\x01\x02"); let err = Test::read_le(&mut data).expect_err("a == b failed"); assert!(matches!(err, binrw::Error::AssertFail { message, .. } if message == "verify failed")); } #[test] fn top_level_assert_self_err_output_not_transformed() { #[allow(dead_code)] #[derive(BinRead, Debug)] #[br(assert(self.verify()))] struct Test { a: u8, b: u8, } impl Test { fn verify(&self) -> bool { self.a == self.b } } let mut data = Cursor::new(b"\x01\x01"); Test::read_le(&mut data).expect("a == b passed"); let mut data = Cursor::new(b"\x01\x02"); let err = Test::read_le(&mut data).expect_err("a == b failed"); assert!(matches!(err, binrw::Error::AssertFail { message, .. } if message == "assertion failed: `self.verify()`")); } #[test] fn top_level_assert_self_weird() { #[allow(dead_code)] #[derive(BinRead, Debug)] #[br(assert(Test::verify(&self), "verify failed"))] struct Test { a: u8, b: u8, } impl Test { fn verify(&self) -> bool { self.a == self.b } } let mut data = Cursor::new(b"\x01\x01"); Test::read_le(&mut data).expect("a == b passed"); let mut data = Cursor::new(b"\x01\x02"); let err = Test::read_le(&mut data).expect_err("a == b failed"); assert!(matches!(err, binrw::Error::AssertFail { message, .. } if message == "verify failed")); } #[test] fn rewind_on_assert() { #[allow(dead_code)] #[derive(BinRead, Debug)] #[br(assert(b == 1))] struct Test { a: u8, b: u8, } let mut data = Cursor::new(b"\0\0\0"); let expected = data.seek(SeekFrom::Start(1)).unwrap(); Test::read_le(&mut data).expect_err("accepted bad data"); assert_eq!(expected, data.stream_position().unwrap()); } #[test] fn rewind_on_eof() { #[derive(BinRead, Debug)] struct Test { _a: u8, // Fail on the second field to actually test that a rewind happens to // the beginning of the struct, not just the beginning of the field _b: u16, } let mut data = Cursor::new(b"\0\0\0"); let expected = data.seek(SeekFrom::Start(1)).unwrap(); Test::read_le(&mut data).expect_err("accepted bad data"); assert_eq!(expected, data.stream_position().unwrap()); } #[test] fn rewind_on_field_assert() { #[allow(dead_code)] #[derive(BinRead, Debug)] struct Test { a: u8, // Assert on the second field to actually test that a rewind happens to // the beginning of the struct, not just the beginning of the field #[br(assert(b == 1))] b: u8, } let mut data = Cursor::new(b"\0\0\0"); let expected = data.seek(SeekFrom::Start(1)).unwrap(); Test::read_le(&mut data).expect_err("accepted bad data"); assert_eq!(expected, data.stream_position().unwrap()); } #[test] fn try_directive() { #[derive(BinRead)] #[br(big)] struct Test { #[br(try)] a: Option<[i32; 2]>, } let result = Test::read(&mut Cursor::new(b"\0\0\0\0")).unwrap(); assert!(result.a.is_none()); let result = Test::read(&mut Cursor::new(b"\xff\xff\xff\xff\0\0\0\0")).unwrap(); assert_eq!(result.a, Some([-1, 0])); } #[test] fn try_calc() { #[derive(BinRead, Debug, PartialEq)] #[br(big, import(v: u32))] struct Test { #[br(try_calc = <_>::try_from(v))] a: u16, } assert_eq!( Test::read_args(&mut Cursor::new(b""), (1,)).unwrap(), Test { a: 1 } ); Test::read_args(&mut Cursor::new(b""), (0x1_0000,)).unwrap_err(); } #[test] fn tuple() { #[derive(BinRead, Debug, Eq, PartialEq)] struct Test(#[br(big)] u16, #[br(little)] u16); let result = Test::read_le(&mut Cursor::new("\0\x01\x02\0")).unwrap(); assert_eq!(result, Test(1, 2)); } #[test] fn tuple_calc_temp_field() { #[binread] #[derive(Debug, Eq, PartialEq)] #[br(big)] struct Test(#[br(temp)] u16, #[br(calc((self_0 + 1).into()))] u32); let result = Test::read(&mut Cursor::new(b"\0\x04")).unwrap(); // This also indirectly checks that `temp` is actually working since // compilation would fail if it weren’t due to missing a second item assert_eq!(result, Test(5u32)); } #[test] fn no_clone_needed_for_parse_with() { #[binread] #[derive(Debug, Eq, PartialEq)] pub struct Test { foo: u8, #[br(parse_with = files_parser, args(&mut foo))] files: u8, } #[binrw::parser] fn files_parser(foo: &mut u8) -> BinResult { let old_value = *foo; *foo = 0x0A; Ok(old_value) } let result = Test::read_le(&mut Cursor::new(b"\x0B")).unwrap(); assert_eq!( result, Test { foo: 0x0A, files: 0x0B } ); }