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}