binrw/helpers.rs
1//! Helper functions for reading and writing data.
2
3use crate::{
4 io::{self, Read, Seek},
5 BinRead, BinResult, Endian, Error,
6};
7#[cfg(not(feature = "std"))]
8use alloc::vec::Vec;
9use core::iter::from_fn;
10
11/// Creates a parser that reads items into a collection until a condition is
12/// met. The terminal item is added to the collection.
13///
14/// This helper can be used to read into any collection type that implements
15/// [`FromIterator`].
16///
17/// # Examples
18///
19/// ```
20/// # use binrw::{BinRead, helpers::until, io::Cursor, BinReaderExt};
21/// #[derive(BinRead)]
22/// struct NullTerminated {
23/// #[br(parse_with = until(|&byte| byte == 0))]
24/// data: Vec<u8>,
25/// }
26///
27/// # let mut x = Cursor::new(b"\x01\x02\x03\x04\0");
28/// # let x: NullTerminated = x.read_be().unwrap();
29/// # assert_eq!(x.data, &[1, 2, 3, 4, 0]);
30/// ```
31pub fn until<Reader, T, CondFn, Arg, Ret>(
32 cond: CondFn,
33) -> impl Fn(&mut Reader, Endian, Arg) -> BinResult<Ret>
34where
35 T: for<'a> BinRead<Args<'a> = Arg>,
36 Reader: Read + Seek,
37 CondFn: Fn(&T) -> bool,
38 Arg: Clone,
39 Ret: FromIterator<T>,
40{
41 until_with(cond, T::read_options)
42}
43
44/// Creates a parser that uses a given function to read items into a collection
45/// until a condition is met. The terminal item is added to the collection.
46///
47/// The given `read` function should return one item each time it is called.
48///
49/// This helper can be used to read into any collection type that implements
50/// [`FromIterator`].
51///
52/// # Examples
53///
54/// Reading a two-dimensional `VecDeque` by combining [`until_with`] and
55/// [`count`]:
56///
57/// ```
58/// # use binrw::{BinRead, helpers::{until, until_with, count}, io::Cursor, BinReaderExt};
59/// # use std::collections::VecDeque;
60/// #[derive(BinRead)]
61/// struct NullTerminated {
62/// #[br(parse_with = until_with(|bytes| bytes == &[0, 0], count(2)))]
63/// data: VecDeque<VecDeque<u8>>,
64/// }
65///
66/// # let mut x = Cursor::new(b"\x01\x02\x03\x04\0\0");
67/// # let x: NullTerminated = x.read_be().unwrap();
68/// # assert_eq!(x.data, &[[1, 2], [3, 4], [0, 0]]);
69/// ```
70pub fn until_with<Reader, T, CondFn, Arg, ReadFn, Ret>(
71 cond: CondFn,
72 read: ReadFn,
73) -> impl Fn(&mut Reader, Endian, Arg) -> BinResult<Ret>
74where
75 Reader: Read + Seek,
76 CondFn: Fn(&T) -> bool,
77 Arg: Clone,
78 ReadFn: Fn(&mut Reader, Endian, Arg) -> BinResult<T>,
79 Ret: FromIterator<T>,
80{
81 move |reader, endian, args| {
82 let mut last = false;
83 from_fn(|| {
84 if last {
85 None
86 } else {
87 match read(reader, endian, args.clone()) {
88 Ok(value) => {
89 if cond(&value) {
90 last = true;
91 }
92 Some(Ok(value))
93 }
94 err => Some(err),
95 }
96 }
97 })
98 .fuse()
99 .collect()
100 }
101}
102
103/// Creates a parser that reads items into a collection until a condition is
104/// met. The terminal item is discarded.
105///
106/// This helper can be used to read into any collection type that implements
107/// [`FromIterator`].
108///
109/// # Examples
110///
111/// ```
112/// # use binrw::{BinRead, helpers::until_exclusive, io::Cursor, BinReaderExt};
113/// #[derive(BinRead)]
114/// struct NullTerminated {
115/// #[br(parse_with = until_exclusive(|&byte| byte == 0))]
116/// data: Vec<u8>,
117/// }
118///
119/// # let mut x = Cursor::new(b"\x01\x02\x03\x04\0");
120/// # let x: NullTerminated = x.read_be().unwrap();
121/// # assert_eq!(x.data, &[1, 2, 3, 4]);
122/// ```
123pub fn until_exclusive<Reader, T, CondFn, Arg, Ret>(
124 cond: CondFn,
125) -> impl Fn(&mut Reader, Endian, Arg) -> BinResult<Ret>
126where
127 T: for<'a> BinRead<Args<'a> = Arg>,
128 Reader: Read + Seek,
129 CondFn: Fn(&T) -> bool,
130 Arg: Clone,
131 Ret: FromIterator<T>,
132{
133 until_exclusive_with(cond, T::read_options)
134}
135
136/// Creates a parser that uses a given function to read items into a collection
137/// until a condition is met. The terminal item is discarded.
138///
139/// The given `read` function should return one item each time it is called.
140///
141/// This helper can be used to read into any collection type that implements
142/// [`FromIterator`].
143///
144/// # Examples
145///
146/// Reading a two-dimensional `VecDeque` by combining [`until_exclusive_with`]
147/// and [`count`]:
148///
149/// ```
150/// # use binrw::{BinRead, helpers::{until_exclusive, until_exclusive_with, count}, io::Cursor, BinReaderExt};
151/// # use std::collections::VecDeque;
152/// #[derive(BinRead)]
153/// struct NullTerminated {
154/// #[br(parse_with = until_exclusive_with(|bytes| bytes == &[0, 0], count(2)))]
155/// data: VecDeque<VecDeque<u8>>,
156/// }
157///
158/// # let mut x = Cursor::new(b"\x01\x02\x03\x04\0\0");
159/// # let x: NullTerminated = x.read_be().unwrap();
160/// # assert_eq!(x.data, &[[1, 2], [3, 4]]);
161/// ```
162pub fn until_exclusive_with<Reader, T, CondFn, Arg, ReadFn, Ret>(
163 cond: CondFn,
164 read: ReadFn,
165) -> impl Fn(&mut Reader, Endian, Arg) -> BinResult<Ret>
166where
167 Reader: Read + Seek,
168 CondFn: Fn(&T) -> bool,
169 Arg: Clone,
170 ReadFn: Fn(&mut Reader, Endian, Arg) -> BinResult<T>,
171 Ret: FromIterator<T>,
172{
173 move |reader, endian, args| {
174 from_fn(|| match read(reader, endian, args.clone()) {
175 Ok(value) => {
176 if cond(&value) {
177 None
178 } else {
179 Some(Ok(value))
180 }
181 }
182 err => Some(err),
183 })
184 .fuse()
185 .collect()
186 }
187}
188
189/// Creates a parser that reads items into a collection until the end of the
190/// input stream.
191///
192/// This helper can be used to read into any collection type that implements
193/// [`FromIterator`].
194///
195/// # Errors
196///
197/// If reading fails for a reason other than reaching the end of the input, an
198/// [`Error`] variant will be returned.
199///
200/// # Examples
201///
202/// ```
203/// # use binrw::{BinRead, helpers::until_eof, io::Cursor, BinReaderExt};
204/// #[derive(BinRead)]
205/// struct EntireFile {
206/// #[br(parse_with = until_eof)]
207/// data: Vec<u8>,
208/// }
209///
210/// # let mut x = Cursor::new(b"\x01\x02\x03\x04");
211/// # let x: EntireFile = x.read_be().unwrap();
212/// # assert_eq!(x.data, &[1, 2, 3, 4]);
213/// ```
214pub fn until_eof<Reader, T, Arg, Ret>(
215 reader: &mut Reader,
216 endian: Endian,
217 args: Arg,
218) -> BinResult<Ret>
219where
220 T: for<'a> BinRead<Args<'a> = Arg>,
221 Reader: Read + Seek,
222 Arg: Clone,
223 Ret: FromIterator<T>,
224{
225 until_eof_with(T::read_options)(reader, endian, args)
226}
227
228/// Creates a parser that uses a given function to read items into a collection
229/// until the end of the input stream.
230///
231/// The given `read` function should return one item each time it is called.
232///
233/// This helper can be used to read into any collection type that implements
234/// [`FromIterator`].
235///
236/// # Errors
237///
238/// If reading fails for a reason other than reaching the end of the input, an
239/// [`Error`] variant will be returned.
240///
241/// # Examples
242///
243/// Reading a two-dimensional `VecDeque` by combining [`until_eof_with`] and
244/// [`count`]:
245///
246/// ```
247/// # use binrw::{BinRead, helpers::{until_eof, until_eof_with, count}, io::Cursor, BinReaderExt};
248/// # use std::collections::VecDeque;
249/// #[derive(BinRead)]
250/// struct EntireFile {
251/// #[br(parse_with = until_eof_with(count(2)))]
252/// data: VecDeque<VecDeque<u8>>,
253/// }
254///
255/// # let mut x = Cursor::new(b"\x01\x02\x03\x04");
256/// # let x: EntireFile = x.read_be().unwrap();
257/// # assert_eq!(x.data, &[[1, 2], [3, 4]]);
258/// ```
259pub fn until_eof_with<Reader, T, Arg, ReadFn, Ret>(
260 read: ReadFn,
261) -> impl Fn(&mut Reader, Endian, Arg) -> BinResult<Ret>
262where
263 Reader: Read + Seek,
264 Arg: Clone,
265 ReadFn: Fn(&mut Reader, Endian, Arg) -> BinResult<T>,
266 Ret: FromIterator<T>,
267{
268 move |reader, endian, args| {
269 from_fn(|| match read(reader, endian, args.clone()) {
270 ok @ Ok(_) => Some(ok),
271 Err(err) if err.is_eof() => None,
272 err => Some(err),
273 })
274 .fuse()
275 .collect()
276 }
277}
278
279/// Creates a parser that builds a collection using items from the given
280/// iterable object as arguments for the parser.
281///
282/// This helper can be used to read into any collection type that implements
283/// [`FromIterator`].
284///
285/// # Examples
286///
287/// Reading an object containing header data followed by body data:
288///
289/// ```
290/// # use binrw::{args, BinRead, BinReaderExt, helpers::args_iter, io::Cursor};
291/// #[derive(BinRead)]
292/// #[br(big)]
293/// struct Header {
294/// count: u16,
295///
296/// #[br(args { count: count.into() })]
297/// sizes: Vec<u16>,
298/// }
299///
300/// #[derive(BinRead)]
301/// #[br(big)]
302/// struct Object {
303/// header: Header,
304/// #[br(parse_with = args_iter(header.sizes.iter().map(|&size| -> <Vec<u8> as BinRead>::Args<'_> {
305/// args! { count: size.into() }
306/// })))]
307/// segments: Vec<Vec<u8>>,
308/// }
309///
310/// # let mut x = Cursor::new(b"\0\x02\0\x01\0\x02\x03\x04\x05");
311/// # let x = Object::read(&mut x).unwrap();
312/// # assert_eq!(x.segments, &[vec![3], vec![4, 5]]);
313/// ```
314pub fn args_iter<R, T, Arg, Ret, It>(it: It) -> impl FnOnce(&mut R, Endian, ()) -> BinResult<Ret>
315where
316 T: for<'a> BinRead<Args<'a> = Arg>,
317 R: Read + Seek,
318 Arg: Clone,
319 Ret: FromIterator<T>,
320 It: IntoIterator<Item = Arg>,
321{
322 args_iter_with(it, T::read_options)
323}
324
325/// Creates a parser that uses a given function to build a collection, using
326/// items from the given iterable object as arguments for the function.
327///
328/// The given `read` function should return one item each time it is called.
329///
330/// This helper can be used to read into any collection type that implements
331/// [`FromIterator`].
332///
333/// # Examples
334///
335/// Reading an object containing header data followed by body data:
336///
337/// ```
338/// # use binrw::{args, BinRead, BinReaderExt, helpers::args_iter_with, io::Cursor};
339/// #[derive(BinRead)]
340/// #[br(big)]
341/// struct Header {
342/// count: u16,
343///
344/// #[br(args { count: count.into() })]
345/// sizes: Vec<u16>,
346/// }
347///
348/// #[derive(BinRead)]
349/// #[br(big)]
350/// struct Object {
351/// header: Header,
352/// #[br(parse_with = args_iter_with(&header.sizes, |reader, options, &size| {
353/// Vec::<u8>::read_options(reader, options, args! { count: size.into() })
354/// }))]
355/// segments: Vec<Vec<u8>>,
356/// }
357///
358/// # let mut x = Cursor::new(b"\0\x02\0\x01\0\x02\x03\x04\x05");
359/// # let x = Object::read(&mut x).unwrap();
360/// # assert_eq!(x.segments, &[vec![3], vec![4, 5]]);
361/// ```
362pub fn args_iter_with<Reader, T, Arg, Ret, It, ReadFn>(
363 it: It,
364 read: ReadFn,
365) -> impl FnOnce(&mut Reader, Endian, ()) -> BinResult<Ret>
366where
367 Reader: Read + Seek,
368 Arg: Clone,
369 Ret: FromIterator<T>,
370 It: IntoIterator<Item = Arg>,
371 ReadFn: Fn(&mut Reader, Endian, Arg) -> BinResult<T>,
372{
373 move |reader, options, ()| {
374 it.into_iter()
375 .map(|arg| read(reader, options, arg))
376 .collect()
377 }
378}
379
380/// Creates a parser that reads N items into a collection.
381///
382/// This helper is similar to using `#[br(count = N)]` with [`Vec`], but is more
383/// generic so can be used to read into any collection type that implements
384/// [`FromIterator`].
385///
386/// # Examples
387///
388/// ```
389/// # use binrw::{BinRead, helpers::count, io::Cursor, BinReaderExt};
390/// # use std::collections::VecDeque;
391/// #[derive(BinRead)]
392/// struct CountBytes {
393/// len: u8,
394///
395/// #[br(parse_with = count(len as usize))]
396/// data: VecDeque<u8>,
397/// }
398///
399/// # let mut x = Cursor::new(b"\x03\x01\x02\x03");
400/// # let x: CountBytes = x.read_be().unwrap();
401/// # assert_eq!(x.data, &[1, 2, 3]);
402/// ```
403pub fn count<R, T, Arg, Ret>(n: usize) -> impl Fn(&mut R, Endian, Arg) -> BinResult<Ret>
404where
405 T: for<'a> BinRead<Args<'a> = Arg>,
406 R: Read + Seek,
407 Arg: Clone,
408 Ret: FromIterator<T> + 'static,
409{
410 count_with(n, T::read_options)
411}
412
413/// Creates a parser that uses a given function to read N items into a
414/// collection.
415///
416/// The given `read` function should return one item each time it is called.
417///
418/// This helper is similar to using `#[br(count = N)]` with [`Vec`], but is more
419/// generic so can be used to read into any collection type that implements
420/// [`FromIterator`].
421///
422/// # Examples
423///
424/// Reading a two-dimensional `VecDeque` by combining [`count_with`] and
425/// [`count`]:
426///
427/// ```
428/// # use binrw::{BinRead, helpers::count, helpers::count_with, io::Cursor, BinReaderExt};
429/// # use std::collections::VecDeque;
430/// #[derive(BinRead)]
431/// struct CountBytes {
432/// len: u8,
433///
434/// #[br(parse_with = count_with(len as usize, count(2)))]
435/// data: VecDeque<VecDeque<u8>>,
436/// }
437///
438/// # let mut x = Cursor::new(b"\x02\x01\x02\x03\x04");
439/// # let x: CountBytes = x.read_be().unwrap();
440/// # assert_eq!(x.data, &[[1, 2], [3, 4]]);
441/// ```
442pub fn count_with<R, T, Arg, ReadFn, Ret>(
443 n: usize,
444 read: ReadFn,
445) -> impl Fn(&mut R, Endian, Arg) -> BinResult<Ret>
446where
447 R: Read + Seek,
448 Arg: Clone,
449 ReadFn: Fn(&mut R, Endian, Arg) -> BinResult<T>,
450 Ret: FromIterator<T> + 'static,
451{
452 move |reader, endian, args| {
453 let mut container = core::iter::empty::<T>().collect::<Ret>();
454
455 vec_fast_int!(try (i8 i16 u16 i32 u32 i64 u64 i128 u128) using (container, reader, endian, n) else {
456 // This extra branch for `Vec<u8>` makes it faster than
457 // `vec_fast_int`, but *only* because `vec_fast_int` is not allowed
458 // to use unsafe code to eliminate the unnecessary zero-fill.
459 // Otherwise, performance would be identical and it could be
460 // deleted.
461 if let Some(bytes) = <dyn core::any::Any>::downcast_mut::<Vec<u8>>(&mut container) {
462 bytes.reserve_exact(n);
463 let byte_count = reader
464 .take(n.try_into().map_err(not_enough_bytes)?)
465 .read_to_end(bytes)?;
466
467 if byte_count == n {
468 Ok(container)
469 } else {
470 Err(not_enough_bytes(()))
471 }
472 } else {
473 core::iter::repeat_with(|| read(reader, endian, args.clone()))
474 .take(n)
475 .collect()
476 }
477 })
478 }
479}
480
481/// Reads a 24-bit unsigned integer.
482///
483/// # Errors
484///
485/// If reading fails, an [`Error`](crate::Error) variant will be returned.
486///
487/// # Examples
488///
489/// ```
490/// # use binrw::{prelude::*, io::Cursor};
491/// #[derive(BinRead)]
492/// # #[derive(Debug, PartialEq)]
493/// struct Test {
494/// flags: u8,
495/// #[br(parse_with = binrw::helpers::read_u24)]
496/// value: u32,
497/// }
498/// #
499/// # assert_eq!(
500/// # Test::read_be(&mut Cursor::new(b"\x01\x02\x03\x04")).unwrap(),
501/// # Test { flags: 1, value: 0x20304 }
502/// # );
503/// # assert_eq!(
504/// # Test::read_le(&mut Cursor::new(b"\x01\x04\x03\x02")).unwrap(),
505/// # Test { flags: 1, value: 0x20304 }
506/// # );
507/// ```
508#[binrw::parser(reader, endian)]
509pub fn read_u24() -> binrw::BinResult<u32> {
510 type ConvFn = fn([u8; 4]) -> u32;
511 let mut buf = [0u8; 4];
512 let (conv, out): (ConvFn, &mut [u8]) = match endian {
513 Endian::Little => (u32::from_le_bytes, &mut buf[..3]),
514 Endian::Big => (u32::from_be_bytes, &mut buf[1..]),
515 };
516 reader.read_exact(out)?;
517 Ok(conv(buf))
518}
519
520/// Writes a 24-bit unsigned integer.
521///
522/// # Errors
523///
524/// If writing fails, an [`Error`](crate::Error) variant will be returned.
525///
526/// # Examples
527///
528/// ```
529/// # use binrw::{prelude::*, io::Cursor};
530/// #[derive(BinWrite)]
531/// # #[derive(Debug, PartialEq)]
532/// struct Test {
533/// flags: u8,
534/// #[bw(write_with = binrw::helpers::write_u24)]
535/// value: u32,
536/// }
537/// #
538/// # let mut data = Cursor::new(vec![]);
539/// # Test { flags: 1, value: 0x20304 }.write_be(&mut data).unwrap();
540/// # assert_eq!(
541/// # data.get_ref(),
542/// # &[1, 2, 3, 4]
543/// # );
544/// # let mut data = Cursor::new(vec![]);
545/// # Test { flags: 1, value: 0x20304 }.write_le(&mut data).unwrap();
546/// # assert_eq!(
547/// # data.get_ref(),
548/// # &[1, 4, 3, 2]
549/// # );
550/// ```
551#[binrw::writer(writer, endian)]
552pub fn write_u24(value: &u32) -> binrw::BinResult<()> {
553 let (buf, range) = match endian {
554 Endian::Little => (value.to_le_bytes(), 0..3),
555 Endian::Big => (value.to_be_bytes(), 1..4),
556 };
557 writer.write_all(&buf[range]).map_err(Into::into)
558}
559
560fn not_enough_bytes<T>(_: T) -> Error {
561 Error::Io(io::Error::new(
562 io::ErrorKind::UnexpectedEof,
563 "not enough bytes in reader",
564 ))
565}
566
567macro_rules! vec_fast_int {
568 (try ($($Ty:ty)+) using ($list:expr, $reader:expr, $endian:expr, $count:expr) else { $($else:tt)* }) => {
569 $(if let Some(list) = <dyn core::any::Any>::downcast_mut::<Vec<$Ty>>(&mut $list) {
570 let mut start = 0;
571 let mut remaining = $count;
572 // Allocating and reading from the source in chunks is done to keep
573 // a bad `count` from causing huge memory allocations that are
574 // doomed to fail
575 while remaining != 0 {
576 // Using a similar strategy as std `default_read_to_end` to
577 // leverage the memory growth strategy of the underlying Vec
578 // implementation (in std this will be exponential) using a
579 // minimum byte allocation
580 const GROWTH: usize = 32 / core::mem::size_of::<$Ty>();
581 list.reserve(remaining.min(GROWTH.max(1)));
582
583 let items_to_read = remaining.min(list.capacity() - start);
584 let end = start + items_to_read;
585
586 // In benchmarks, this resize decreases performance by 27–40%
587 // relative to using `unsafe` to write directly to uninitialised
588 // memory, but nobody ever got fired for buying IBM
589 list.resize(end, 0);
590 $reader.read_exact(&mut bytemuck::cast_slice_mut::<_, u8>(&mut list[start..end]))?;
591
592 remaining -= items_to_read;
593 start += items_to_read;
594 }
595
596 if
597 core::mem::size_of::<$Ty>() != 1
598 && (
599 (cfg!(target_endian = "big") && $endian == crate::Endian::Little)
600 || (cfg!(target_endian = "little") && $endian == crate::Endian::Big)
601 )
602 {
603 for value in list.iter_mut() {
604 *value = value.swap_bytes();
605 }
606 }
607 Ok($list)
608 } else)* {
609 $($else)*
610 }
611 }
612}
613
614use vec_fast_int;