faf_replay_parser/iter/
mod.rs

1//! Iterators for composing flexible replay parsing pipelines
2//!
3//! Traits from this module are automatically implemented and are made available by star importing
4//! from [`prelude`].
5//!
6//! ```rust
7//! use faf_replay_parser::iter::prelude::*;
8//! ```
9//!
10//! This module defines three traits for creating composable replay command parsing pipelines. Note
11//! that you can't parse replay headers this way, only the command stream. Each pipeline will start
12//! with one of the entrypoint methods defined by [`CommandIter`] depending on the level of parsing
13//! that the pipeline needs as a starting point.
14//!
15//! The pipeline can then use the standard library iterator features such as `.filter` and `.map`,
16//! or even third party iterator adapters, to define whatever processing logic is desired. This
17//! module also contains iterator adapters for parsing the command frames in case the pipeline
18//! needs to start from the raw bytes in order to inject custom logic before the parsing step.
19//! These adapter methods are defined by [`CommandFrameAdapters`].
20//!
21//! There are also some iterator adapters to simplify the handling of errors for common cases,
22//! since the parsing functions return result types. These are implemented by [`ResultAdapters`].
23
24use std::fmt::Debug;
25
26use crate::replay::ReplayCommandFrameSpan;
27use crate::version::Version;
28
29use byteorder::{ByteOrder, LittleEndian};
30
31mod parse;
32pub mod prelude;
33mod verify;
34mod while_;
35
36pub use parse::*;
37pub use verify::*;
38pub use while_::*;
39
40/// Allow objects to return iterators over contained command frames.
41pub trait CommandIter<'a> {
42    type Iter: Iterator<Item = ReplayCommandFrameSpan<'a>>;
43
44    /// Return an iterator over the raw command frame data. The iterator should not validate the
45    /// contents of the command identifier or the command data. It is up to the implementor to
46    /// decide how to handle frames with sizes less than the frame size.
47    ///
48    /// # Examples
49    /// ```rust
50    /// use faf_replay_parser::iter::prelude::*;
51    /// use faf_replay_parser::iter::{parse_command, verify_frame_header};
52    /// use faf_replay_parser::SCFA;
53    /// use faf_replay_parser::scfa::ReplayCommand::*;
54    ///
55    /// let data: &[u8] = &[
56    ///     1, 4, 0, 1,          // SetCommandSource
57    ///     0, 7, 0, 1, 0, 0, 0, // Advance
58    ///     5, 3, 0              // RequestPause
59    /// ];
60    ///
61    /// let mut iter = data.iter_command_frames_raw()
62    ///     .filter(|frame| frame.cmd < 3)
63    ///     .filter_map(|frame| verify_frame_header::<SCFA>(frame).ok())
64    ///     .filter_map(|frame| parse_command::<SCFA>(frame).ok());
65    ///
66    /// match iter.next() {
67    ///     Some(SetCommandSource { id: 1 }) => {},
68    ///     e => panic!("wrong result {:?}", e)
69    /// }
70    /// match iter.next() {
71    ///     Some(Advance { ticks: 1 }) => {},
72    ///     e => panic!("wrong result {:?}", e)
73    /// }
74    /// assert!(iter.next().is_none());
75    ///
76    /// ```
77    fn iter_command_frames_raw(&self) -> Self::Iter;
78
79    /// Return an iterator over the command frames. The command frame header will be checked for
80    /// validity, and any errors returned.
81    ///
82    /// **Note**: If only a subset of commands are needed, it can be more efficient to compose
83    /// `iter_command_frames_raw` with a filter and map adapter to call [`verify_frame_header`]
84    /// only for frames that have the desired command id.
85    fn iter_command_frames<V: Version>(&self) -> VerifyFrame<'a, V, Self::Iter> {
86        VerifyFrame::new(self.iter_command_frames_raw())
87    }
88
89    /// Return an iterator over the parsed `Command`s, verifying the command headers and
90    /// returning any errors.
91    ///
92    /// # Examples
93    /// ```rust
94    /// use faf_replay_parser::iter::prelude::*;
95    /// use faf_replay_parser::SCFA;
96    /// use faf_replay_parser::{ReplayResult, scfa::ReplayCommand};
97    ///
98    /// let data: &[u8] = &[0, 7, 0, 1, 0, 0, 0, 1, 0, 0, 23, 3, 0];
99    /// let commands = data.iter_commands::<SCFA>()
100    ///     .collect::<Vec<ReplayResult<ReplayCommand>>>();
101    ///
102    /// assert_eq!(
103    ///     format!("{:?}", commands),
104    ///     "[Ok(Advance { ticks: 1 }), Err(Malformed(\"invalid command size\")), Ok(EndGame)]"
105    /// )
106    /// ```
107    ///
108    /// **Note**: If only a subset of commands are needed, it can be more efficient to compose
109    /// `iter_command_frames` with a filter and map adapter to call [`parse_command`]
110    /// only for frames that have the desired command id.
111    fn iter_commands<V: Version>(&self) -> ParseCommand<'a, V, Self::Iter> {
112        ParseCommand::new(self.iter_command_frames_raw())
113    }
114}
115
116/// Adapters for iterators over command frames.
117pub trait CommandFrameAdapters<'a>: Iterator<Item = ReplayCommandFrameSpan<'a>> + Sized {
118    /// Verify the validity of the frame headers. See [`verify_frame_header`].
119    fn verify_frame<V: Version>(self) -> VerifyFrame<'a, V, Self> {
120        VerifyFrame::new(self)
121    }
122
123    /// Parse the command data into `Command`s without verifying the frame headers. It is
124    /// recommended to always include
125    /// [`verify_frame`](trait.CommandFrameAdapters.html#method.verify_frame) earlier in the
126    /// pipeline.
127    fn parse_command_data<V: Version>(self) -> ParseCommandData<'a, V, Self> {
128        ParseCommandData::new(self)
129    }
130
131    /// Parse the command data into `Command`s. See [`parse_command`].
132    fn parse_command<V: Version>(self) -> ParseCommand<'a, V, Self> {
133        ParseCommand::new(self)
134    }
135}
136
137impl<'a, I: Iterator<Item = ReplayCommandFrameSpan<'a>> + Sized> CommandFrameAdapters<'a> for I {}
138
139/// Adapters for iterators over results.
140pub trait ResultAdapters<T, U>: Iterator<Item = Result<T, U>> + Sized {
141    /// Take items while they are `Ok` and unwrap them. This is equivalent to:
142    ///
143    /// ```rust
144    /// let list = [Ok(0), Err("foo")];
145    /// list.iter()
146    ///     .take_while(|result| result.is_ok())
147    ///     .map(|result| result.unwrap());
148    /// ```
149    fn while_ok(self) -> WhileOk<Self> {
150        WhileOk::new(self)
151    }
152
153    /// Take items while they are `Err`s and unwrap them. This is equivalent to:
154    ///
155    /// ```rust
156    /// let list = [Ok(0), Err("foo")];
157    /// list.iter()
158    ///     .take_while(|result| result.is_err())
159    ///     .map(|result| result.unwrap_err());
160    /// ```
161    fn while_err(self) -> WhileErr<Self> {
162        WhileErr::new(self)
163    }
164}
165
166impl<T, U: Debug, I: Iterator<Item = Result<T, U>>> ResultAdapters<T, U> for I {}
167
168// ------- Implementation for byte slices ------- //
169
170impl<'a> CommandIter<'a> for &'a [u8] {
171    type Iter = BytesCommandFrameRawIterator<'a>;
172
173    fn iter_command_frames_raw(&self) -> BytesCommandFrameRawIterator<'a> {
174        BytesCommandFrameRawIterator::new(self)
175    }
176}
177
178/// Implements [`CommandIter`] for byte slices
179pub struct BytesCommandFrameRawIterator<'a> {
180    data: &'a [u8],
181}
182impl<'a> BytesCommandFrameRawIterator<'a> {
183    pub fn new(data: &'a [u8]) -> BytesCommandFrameRawIterator<'a> {
184        BytesCommandFrameRawIterator { data }
185    }
186}
187impl<'a> Iterator for BytesCommandFrameRawIterator<'a> {
188    type Item = ReplayCommandFrameSpan<'a>;
189
190    // Inlining here seems to be BAD for performance?
191    fn next(&mut self) -> Option<Self::Item> {
192        let frame = get_frame_raw(self.data)?;
193        self.data = &self.data[frame.data.len()..];
194        Some(frame)
195    }
196
197    fn size_hint(&self) -> (usize, Option<usize>) {
198        // Minimum command frame size is 3 bytes
199        (0, Some(self.data.len() / 3))
200    }
201}
202
203/// Get a command frame from the start of a buffer if enough data is available.
204fn get_frame_raw(data: &[u8]) -> Option<ReplayCommandFrameSpan> {
205    if data.len() < 3 {
206        return None;
207    }
208    let cmd = unsafe { *data.get_unchecked(0) };
209    let size = unsafe { LittleEndian::read_u16(data.get_unchecked(1..3)) as usize };
210    if data.len() < size {
211        return None;
212    }
213    // In case sizes are invalid (less than 3)
214    let amount = std::cmp::max(size, 3);
215
216    unsafe {
217        Some(ReplayCommandFrameSpan {
218            cmd,
219            size: size as u16,
220            data: &data.get_unchecked(..amount),
221        })
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use pretty_assertions::assert_eq;
229
230    use crate::reader::ReplayReadError;
231    use crate::scfa::SCFA;
232    use crate::scfa::{replay_command, ReplayCommand};
233
234    #[test]
235    fn test_basic() {
236        let data: &[u8] = &[
237            0, 3, 0, // Frame with no data
238            100, 0, 0, // Size field is zero
239            3, 5, 0, 0, 0, // Frame with 2 bytes of data
240        ];
241
242        let mut raw_iter = data.iter_command_frames_raw();
243        assert_eq!(
244            raw_iter.next(),
245            Some(ReplayCommandFrameSpan {
246                cmd: 0,
247                size: 3,
248                data: &data[0..3]
249            })
250        );
251        assert_eq!(
252            raw_iter.next(),
253            Some(ReplayCommandFrameSpan {
254                cmd: 100,
255                size: 0,
256                data: &data[3..6]
257            })
258        );
259        assert_eq!(
260            raw_iter.next(),
261            Some(ReplayCommandFrameSpan {
262                cmd: 3,
263                size: 5,
264                data: &data[6..11]
265            })
266        );
267        assert_eq!(raw_iter.next(), None);
268
269        let mut iter = data.iter_command_frames::<SCFA>();
270        assert_eq!(
271            iter.next().unwrap().unwrap(),
272            ReplayCommandFrameSpan {
273                cmd: 0,
274                size: 3,
275                data: &data[0..3]
276            }
277        );
278        assert!(iter.next().unwrap().is_err());
279        assert_eq!(
280            iter.next().unwrap().unwrap(),
281            ReplayCommandFrameSpan {
282                cmd: 3,
283                size: 5,
284                data: &data[6..11]
285            }
286        );
287        assert!(iter.next().is_none());
288    }
289
290    #[test]
291    fn test_basic_commands() {
292        let data: &[u8] = &[
293            0, 3, 0, // Frame with no data
294            23, 0, 0, // Size field is zero
295            0, 7, 0, 1, 0, 0, 0, // Advance command
296        ];
297
298        let mut iter = data.iter_commands::<SCFA>();
299
300        match iter.next().unwrap().unwrap_err() {
301            ReplayReadError::IO(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
302                assert_eq!(format!("{}", e), "failed to fill whole buffer");
303            }
304            _ => panic!("wrong error type"),
305        };
306        match iter.next().unwrap().unwrap_err() {
307            ReplayReadError::Malformed(msg) => assert_eq!(msg, "invalid command size"),
308            _ => panic!("wrong error type"),
309        };
310        match iter.next().unwrap().unwrap() {
311            ReplayCommand::Advance { ticks: 1 } => {}
312            _ => panic!("wrong command type or data"),
313        }
314        assert!(iter.next().is_none());
315    }
316
317    /// An example of composing a custom data parser out of iterator adapters from `std`.
318    #[test]
319    fn test_std_adapters() {
320        use ReplayCommand::Advance;
321
322        let data: &[u8] = &[
323            0, 7, 0, 1, 0, 0, 0, // Ok
324            0, 3, 0, // Malformed, not enough data
325            0, 7, 0, 1, 0, 0, 0, // Ok
326        ];
327
328        // Parse all `Advance` commands stopping on the first malformed command
329        let mut iter = data
330            .iter_command_frames_raw()
331            .filter(|frame| frame.cmd == replay_command::ADVANCE)
332            .map(verify_frame_header::<SCFA>)
333            .take_while(|r| r.is_ok())
334            .map(|r| parse_command::<SCFA>(r.unwrap()))
335            .take_while(|r| r.is_ok())
336            .map(|r| r.unwrap());
337
338        match iter.next() {
339            Some(Advance { ticks: 1 }) => {}
340            e => panic!("wrong result {:?}", e),
341        }
342        assert_eq!(iter.next(), None);
343    }
344
345    /// An example of composing a custom data parser using the provided iterator adapters.
346    #[test]
347    fn test_adapters() {
348        use ReplayCommand::Advance;
349
350        let data: &[u8] = &[
351            0, 7, 0, 1, 0, 0, 0, // Ok
352            0, 3, 0, // Malformed, not enough data
353            0, 7, 0, 1, 0, 0, 0, // Ok
354        ];
355
356        // Parse all `Advance` commands stopping on the first malformed command
357        let mut iter = data
358            .iter_command_frames_raw()
359            .filter(|frame| frame.cmd == replay_command::ADVANCE)
360            .verify_frame::<SCFA>()
361            .while_ok()
362            .parse_command_data::<SCFA>()
363            .while_ok();
364
365        match iter.next() {
366            Some(Advance { ticks: 1 }) => {}
367            e => panic!("wrong result {:?}", e),
368        }
369        assert_eq!(iter.next(), None);
370    }
371}