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}