lazy_net/
parse.rs

1use bytes::Buf;
2
3/// A trait for parsing framed binary protocols with optional default implementations.
4///
5/// This trait defines a typical framing + checksum pattern for binary protocols.
6/// Types implementing this trait must provide `try_parse_frame`, and optionally override `parse` and helper methods.
7pub trait LazyParse: Send {
8    /// Attempts to parse one complete frame from the given buffer.
9    ///
10    /// # Arguments
11    /// * `buf` - A mutable reference to a `BytesMut` buffer containing the incoming stream data.
12    ///
13    /// # Returns
14    /// * `Ok(())` if a frame was successfully parsed and processed.
15    /// * `Err` if parsing fails or an incomplete frame is encountered.
16    fn try_parse_frame(
17        &mut self,
18        buf: &mut bytes::BytesMut,
19    ) -> Result<(), Box<dyn std::error::Error>>;
20
21    /// The default implementation for extracting and validating a frame.
22    ///
23    /// This includes:
24    /// - Searching for a valid header (`pattern`)
25    /// - Calculating BCC (checksum)
26    /// - Extracting length and validating buffer completeness
27    ///
28    /// # Arguments
29    /// * `buf` - The buffer to parse from.
30    /// * `pattern` - A byte pattern representing the frame header, e.g., `[0x55, 0xAA]`.
31    ///
32    /// # Returns
33    /// * `Ok(bcc)` if the frame is structurally valid and the buffer is large enough.
34    /// * `Err` with a detailed error message otherwise.
35    fn parse(
36        &mut self,
37        buf: &mut bytes::BytesMut,
38        pattern: &[u8],
39    ) -> Result<u8, Box<dyn std::error::Error>> {
40        if buf.len() < 10 {
41            return Err("buffer too short (less than 10 bytes)".into());
42        }
43
44        // Find the positions of the first and second matched headers
45        let (first, second) = self.find_first_two_matches(buf, pattern);
46
47        let Some(start) = first else {
48            return Err("frame header not found".into());
49        };
50
51        // Skip any leading bytes before the first valid header
52        if start != 0 {
53            buf.advance(start);
54        }
55
56        let bcc_end = second.unwrap_or(buf.len());
57        if bcc_end < 3 {
58            return Err(format!("second header index too small: bcc_end = {}", bcc_end).into());
59        }
60
61        // Ensure enough bytes are available for BCC computation
62        if buf.len() < bcc_end {
63            return Err(format!(
64                " not enough data for BCC computation, need {} bytes, got {}",
65                bcc_end,
66                buf.len()
67            )
68            .into());
69        }
70
71        // Compute BCC (checksum) from the frame body (excluding header and CRC)
72        let bcc = self.bcc(&buf[2..bcc_end.saturating_sub(1)]);
73
74        // Double check buffer length again (for header and length field)
75        if buf.len() < 10 {
76            return Err("insufficient buffer for header fields".into());
77        }
78
79        // Extract length field from buffer (little endian), mask with 0x1FFF
80        let mut len = ((buf[9] as u16) << 8) | (buf[8] as u16);
81        len &= 0x1fff;
82
83        // Ensure the full frame is present
84        if buf.len() < (len + 10) as usize {
85            return Err("insufficient buffer for full frame".into());
86        }
87
88        Ok(bcc)
89    }
90
91    /// Computes XOR-based checksum (BCC) from a slice of bytes.
92    ///
93    /// # Arguments
94    /// * `data` - Byte slice to compute checksum for.
95    ///
96    /// # Returns
97    /// * A single-byte XOR checksum result.
98    fn bcc(&self, data: &[u8]) -> u8 {
99        data.iter().fold(0u8, |acc, &b| acc ^ b)
100    }
101
102    /// Finds the positions of the first and second occurrences of a given byte pattern.
103    ///
104    /// This is useful for detecting valid frame start boundaries and possibly the start of the next frame.
105    ///
106    /// # Arguments
107    /// * `buf` - The full buffer to search.
108    /// * `pattern` - The byte pattern to match, e.g., `[0x55, 0xAA]`.
109    ///
110    /// # Returns
111    /// * A tuple of `(first_match_index, second_match_index)`; each is `Option<usize>`.
112    fn find_first_two_matches(&self, buf: &[u8], pattern: &[u8]) -> (Option<usize>, Option<usize>) {
113        let mut first = None;
114        let mut second = None;
115
116        for (i, window) in buf.windows(pattern.len()).enumerate() {
117            if window == pattern {
118                if first.is_none() {
119                    first = Some(i);
120                } else {
121                    second = Some(i);
122                    break;
123                }
124            }
125        }
126
127        (first, second)
128    }
129}