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("BLN parse error: 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("BLN parse error: 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!(
59                "BLN parse error: second header index too small: bcc_end = {}",
60                bcc_end
61            )
62            .into());
63        }
64
65        // Ensure enough bytes are available for BCC computation
66        if buf.len() < bcc_end {
67            return Err(format!(
68                "BLN parse error: not enough data for BCC computation, need {} bytes, got {}",
69                bcc_end,
70                buf.len()
71            )
72            .into());
73        }
74
75        // Compute BCC (checksum) from the frame body (excluding header and CRC)
76        let bcc = self.bcc(&buf[2..bcc_end.saturating_sub(1)]);
77
78        // Double check buffer length again (for header and length field)
79        if buf.len() < 10 {
80            return Err("BLN parse error: insufficient buffer for header fields".into());
81        }
82
83        // Extract length field from buffer (little endian), mask with 0x1FFF
84        let mut len = ((buf[9] as u16) << 8) | (buf[8] as u16);
85        len &= 0x1fff;
86
87        // Ensure the full frame is present
88        if buf.len() < (len + 10) as usize {
89            return Err("BLN parse error: insufficient buffer for full frame".into());
90        }
91
92        Ok(bcc)
93    }
94
95    /// Computes XOR-based checksum (BCC) from a slice of bytes.
96    ///
97    /// # Arguments
98    /// * `data` - Byte slice to compute checksum for.
99    ///
100    /// # Returns
101    /// * A single-byte XOR checksum result.
102    fn bcc(&self, data: &[u8]) -> u8 {
103        data.iter().fold(0u8, |acc, &b| acc ^ b)
104    }
105
106    /// Finds the positions of the first and second occurrences of a given byte pattern.
107    ///
108    /// This is useful for detecting valid frame start boundaries and possibly the start of the next frame.
109    ///
110    /// # Arguments
111    /// * `buf` - The full buffer to search.
112    /// * `pattern` - The byte pattern to match, e.g., `[0x55, 0xAA]`.
113    ///
114    /// # Returns
115    /// * A tuple of `(first_match_index, second_match_index)`; each is `Option<usize>`.
116    fn find_first_two_matches(&self, buf: &[u8], pattern: &[u8]) -> (Option<usize>, Option<usize>) {
117        let mut first = None;
118        let mut second = None;
119
120        for (i, window) in buf.windows(pattern.len()).enumerate() {
121            if window == pattern {
122                if first.is_none() {
123                    first = Some(i);
124                } else {
125                    second = Some(i);
126                    break;
127                }
128            }
129        }
130
131        (first, second)
132    }
133}