fastpasta/analyze/validators/
lib.rs

1//! Contains utility functions for preprocessing the payload
2
3use crate::util::*;
4
5#[derive(Debug, PartialEq, Clone, Copy)]
6enum DataFormat {
7    V0,
8    V2,
9}
10
11/// Utility function to preprocess the payload and return an iterator over the GBT words
12///
13/// Consists of the following steps:
14/// 1. Extract the end of payload 0xFF padding
15/// 2. Determine if padding is flavor 0 (6 bytes of 0x00 padding following GBT words) or flavor 1 (no padding)
16/// 3. Split the payload into GBT words sized slices, using chunks_exact to allow more compiler optimizations
17///
18/// Arguments:
19///
20/// * `payload` - The payload to be processed
21///
22/// Returns:
23///
24/// * An iterator over the GBT words
25pub fn preprocess_payload(payload: &[u8]) -> Result<ChunksExact<'_, u8>, String> {
26    let ff_padding = extract_payload_ff_padding(payload)?;
27
28    // Determine if padding is flavor 0 (6 bytes of 0x00 padding following GBT words) or flavor 1 (no padding)
29    let detected_data_format = detect_payload_data_format(payload);
30
31    let gbt_word_chunks = chunkify_payload(payload, detected_data_format, &ff_padding);
32    Ok(gbt_word_chunks)
33}
34
35/// Retrieve end of payload 0xFF padding, if it is more than 15 bytes, return an error
36fn extract_payload_ff_padding(payload: &[u8]) -> Result<Vec<&u8>, String> {
37    let ff_padding = payload
38        .iter()
39        .rev()
40        .take_while(|&x| *x == 0xFF)
41        .collect::<Vec<_>>();
42    // Exceeds the maximum padding of 15 bytes that is required to pad to 16 bytes
43    if ff_padding.len() > 15 {
44        return Err(format!("End of payload 0xFF padding is {} bytes, exceeding max of 15 bytes: Skipping current payload",
45        ff_padding.len()));
46    }
47    Ok(ff_padding)
48}
49
50/// Determine if padding is flavor 0 (6 bytes of 0x00 padding following GBT words) or flavor 1 (no padding)
51fn detect_payload_data_format(payload: &[u8]) -> DataFormat {
52    // Using an iterator approach instead of indexing also supports the case where the payload is smaller than 16 bytes or even empty
53    if payload
54        .iter()
55        // Skip the first 10 bytes, meaning the first GBT word
56        .skip(10)
57        // Take the next 6 bytes
58        .take(6)
59        // Take bytes while they are equal to 0x00
60        .take_while(|&x| *x == 0x00)
61        // Count them and check if they are equal to 6
62        .count()
63        == 6
64    {
65        DataFormat::V0
66    } else {
67        DataFormat::V2
68    }
69}
70
71/// Splits a payload into GBT words sized slices, using chunks_exact to allow more compiler optimizations
72fn chunkify_payload<'a>(
73    payload: &'a [u8],
74    data_format: DataFormat,
75    ff_padding: &[&'a u8],
76) -> ChunksExact<'a, u8> {
77    match data_format {
78        DataFormat::V0 => {
79            let chunks = payload.chunks_exact(16);
80            // If dataformat 0, dividing into 16 byte chunks should cut the payload up with no remainder
81            debug_assert!(chunks.remainder().is_empty());
82            chunks
83        }
84        DataFormat::V2 => {
85            // If dataformat 2, and the padding is more than 9 bytes, padding will be processed as a GBT word, therefor exclude it from the slice
86            //    Before calling chunks_exact
87            if ff_padding.len() > 9 {
88                let last_idx_before_padding = payload.len() - ff_padding.len();
89                let chunks = payload[..last_idx_before_padding].chunks_exact(10);
90                debug_assert!(chunks.remainder().is_empty());
91                chunks
92            } else {
93                // Simply divide into 10 byte chunks and assert that the remainder is padding bytes
94                let chunks = payload.chunks_exact(10);
95                debug_assert!(chunks.remainder().iter().all(|&x| x == 0xFF)); // Asserts that the payload padding is 0xFF
96                chunks
97            }
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_preprocess_payload_flavors() {
108        let word_chunk_f0 = preprocess_payload(&START_PAYLOAD_FLAVOR_0).unwrap();
109        let word_chunks_f2 = preprocess_payload(&START_PAYLOAD_FLAVOR_2).unwrap();
110
111        let word_count = word_chunk_f0.count();
112        let word_count_f2 = word_chunks_f2.count();
113
114        assert_eq!(word_count, 2);
115        assert_eq!(word_count_f2, 2);
116    }
117
118    #[test]
119    fn test_extract_payload_padding() {
120        let end_payload_flavor_0_padding =
121            extract_payload_ff_padding(&END_PAYLOAD_FLAVOR_0).unwrap();
122        let end_payload_flavor_2_padding =
123            extract_payload_ff_padding(&END_PAYLOAD_FLAVOR_2).unwrap();
124
125        assert!(end_payload_flavor_0_padding.is_empty());
126        assert_eq!(end_payload_flavor_2_padding.len(), 6);
127    }
128
129    #[test]
130    fn test_detect_payload_data_format() {
131        let detected_data_format_f0 = detect_payload_data_format(&START_PAYLOAD_FLAVOR_0);
132        let detected_data_format_f2 = detect_payload_data_format(&START_PAYLOAD_FLAVOR_2);
133
134        assert_eq!(detected_data_format_f0, DataFormat::V0);
135        assert_eq!(detected_data_format_f2, DataFormat::V2);
136    }
137}