faf_replay_parser/scfa/
bytes.rs

1//! Functions that are optimized for working on byte slices directly.
2//!
3//! If data is available in a byte slice it will be much more efficient to call these functions
4//! than to use the `Read` implementation of byte slices. This is due to the interface of `Read`
5//! requiring an extra copy in order to inspect the data.
6
7use crate::reader::ReplayReadError;
8use crate::scfa::replay_command;
9use crate::ReplayResult;
10use byteorder::{ByteOrder, LittleEndian};
11
12/// A hand optimized function for finding the offset of where the body starts.
13pub fn body_offset(data: &[u8]) -> ReplayResult<usize> {
14    unsafe {
15        let mut ptr = data.as_ptr();
16        let end = ptr.add(data.len());
17        let start: usize = ptr as usize;
18
19        ptr = ptr.add(skip_string(ptr, end)?); // Skip scfa_version
20        ptr = ptr.add(skip_string(ptr, end)?); // Skip the string "\r\n\x00"
21        ptr = ptr.add(skip_string(ptr, end)?); // Skip version_and_mapname
22        ptr = ptr.add(skip_string(ptr, end)?); // Skip the string "\r\n\x1a\x00"
23
24        ptr = ptr.add(read_u32(ptr, end)? as usize + 4); // Skip mods
25        ptr = ptr.add(read_u32(ptr, end)? as usize + 4); // Skip scenario
26
27        // Need 1 byte for num_sources and 6 for the remaining data which must also appear in any
28        // valid replay header.
29        verify_len(ptr, end, 1 + 6)?;
30        let num_sources = *ptr as usize;
31        ptr = ptr.add(1);
32
33        for _ in 0..num_sources {
34            // Skip name
35            ptr = ptr.add(skip_string(ptr, end)?);
36            // Skip player_id
37            verify_len(ptr, end, 4 + 6)?;
38            ptr = ptr.add(4);
39        }
40
41        verify_len(ptr, end, 1 + 5)?;
42        ptr = ptr.add(1); // Skip cheats_enabled
43        verify_len(ptr, end, 1 + 4)?;
44        let army_count = *ptr as usize;
45        ptr = ptr.add(1);
46
47        for _ in 0..army_count {
48            ptr = ptr.add(read_u32(ptr, end)? as usize + 4); // Skip player_data
49
50            verify_len(ptr, end, 1)?;
51            let player_source = *ptr as usize;
52            ptr = ptr.add(1);
53
54            if player_source != 255 {
55                ptr = ptr.add(1);
56            }
57        }
58
59        verify_len(ptr, end, 4)?;
60        ptr = ptr.add(4); // Skip seed
61
62        Ok(ptr as usize - start)
63    }
64}
65
66/// A hand optimized function for extracting the tick count as quickly and with as little
67/// overhead as possible. Does not check for desyncs.
68///
69/// This is currently the most common use case for this library so it might as well be completely
70/// optimized.
71///
72/// A generic version that works with `Read`ers instead of byte slices is available at
73/// [`parser::parse_body_ticks`](../../parser/fn.parse_body_ticks.html)
74pub fn body_ticks(data: &[u8]) -> ReplayResult<u32> {
75    let mut ticks = 0;
76    let mut curr = 0;
77    let end = data.len();
78
79    // Will not throw an error on missing data
80    while curr + 2 < end {
81        // First byte is the command type
82        let command = unsafe { *data.get_unchecked(curr) };
83        if command > replay_command::MAX {
84            return Err(ReplayReadError::Malformed("invalid command"));
85        }
86        // bytes 2 and 3 are the command size
87        let size =
88            unsafe { LittleEndian::read_u16(data.get_unchecked(curr + 1..curr + 3)) as usize };
89
90        // Advance contains exactly 4 more bytes for the number of ticks
91        if command == replay_command::ADVANCE && !(end < curr + size) {
92            if size != 7 {
93                return Err(ReplayReadError::Malformed("invalid command size"));
94            }
95            ticks += unsafe { LittleEndian::read_u32(data.get_unchecked(curr + 3..curr + 7)) };
96        }
97        curr += size;
98    }
99
100    Ok(ticks)
101}
102
103/// Check if a buffer starts with a full frame.
104///
105/// Note, that this only checks the frame header which includes the command ID and the data size.
106/// The data itself is not checked and should be parsed separately to verify validity.
107pub fn has_frame(data: &[u8]) -> ReplayResult<bool> {
108    if data.is_empty() {
109        return Ok(false);
110    }
111
112    let command = unsafe { *data.get_unchecked(0) };
113    if command > replay_command::MAX {
114        return Err(ReplayReadError::Malformed("invalid command"));
115    }
116
117    if data.len() < 3 {
118        return Ok(false);
119    }
120
121    let size = unsafe { LittleEndian::read_u16(data.get_unchecked(1..3)) as usize };
122    if size < 3 {
123        return Err(ReplayReadError::Malformed("invalid command size"));
124    }
125
126    Ok(!(data.len() < size))
127}
128
129unsafe fn skip_string(mut data: *const u8, end: *const u8) -> ReplayResult<usize> {
130    let mut count = 1;
131
132    while data < end {
133        if *data == b'\x00' {
134            return Ok(count);
135        }
136        data = data.add(1);
137        count += 1;
138    }
139
140    Err(ReplayReadError::Malformed("missing header data"))
141}
142
143#[inline]
144unsafe fn read_u32(ptr: *const u8, end: *const u8) -> ReplayResult<u32> {
145    verify_len(ptr, end, 4)?;
146    Ok(LittleEndian::read_u32(std::slice::from_raw_parts(ptr, 4)))
147}
148
149#[inline]
150unsafe fn verify_len(ptr: *const u8, end: *const u8, needed: usize) -> ReplayResult<()> {
151    return match ptr.add(needed) < end {
152        true => Ok(()),
153        false => Err(ReplayReadError::Malformed("missing header data")),
154    };
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160    use pretty_assertions::assert_eq;
161
162    #[test]
163    fn test_body_ticks_empty() {
164        assert_eq!(body_ticks(&[]).unwrap(), 0);
165    }
166
167    #[test]
168    fn test_body_ticks_invalid_command() {
169        body_ticks(&[0xff, 0x03, 0x00]).unwrap_err();
170    }
171
172    #[test]
173    fn test_body_ticks_invalid_command_size() {
174        body_ticks(&[0x0, 0x03, 0x00]).unwrap_err();
175    }
176
177    #[test]
178    fn test_body_ticks_exactly_one() {
179        assert_eq!(
180            body_ticks(&[0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00]).unwrap(),
181            1
182        );
183    }
184
185    #[test]
186    fn test_body_ticks_short_by_one() {
187        assert_eq!(
188            body_ticks(&[0x00, 0x07, 0x00, 0x01, 0x00, 0x00]).unwrap(),
189            0
190        );
191    }
192
193    #[test]
194    fn test_body_ticks_missing_data() {
195        assert_eq!(body_ticks(&[0x00, 0x07, 0x00, 0x01]).unwrap(), 0);
196    }
197
198    #[test]
199    fn test_body_ticks_excessive_data() {
200        assert_eq!(
201            body_ticks(&[0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01,])
202                .unwrap(),
203            1
204        );
205    }
206
207    #[test]
208    fn test_has_frame_empty() {
209        assert_eq!(has_frame(&[]).unwrap(), false);
210    }
211
212    #[test]
213    fn test_has_frame_exact() {
214        let data = [0x00, 0x04, 0x00, 0xff];
215        assert_eq!(has_frame(&data).unwrap(), true);
216    }
217
218    #[test]
219    fn test_has_frame_exact_empty_contents() {
220        let data = [0x00, 0x03, 0x00];
221        assert_eq!(has_frame(&data).unwrap(), true);
222    }
223
224    #[test]
225    fn test_has_frame_missing_data() {
226        let data = [0x00, 0x05, 0x00, 0xff];
227        assert_eq!(has_frame(&data).unwrap(), false);
228    }
229
230    #[test]
231    fn test_has_frame_excessive_data() {
232        let data = [0x00, 0x04, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff];
233        assert_eq!(has_frame(&data).unwrap(), true);
234    }
235}