1use tracing::warn;
10
11pub fn encode_frame(data: &[u8]) -> Vec<u8> {
13 let max_encoded = ucobs::max_encoded_len(data.len());
14 let mut buf = vec![0u8; max_encoded];
15 let n = match ucobs::encode(data, &mut buf) {
16 Some(n) => n,
17 None => return vec![0x00], };
19 buf.truncate(n);
20 buf.push(0x00);
21 buf
22}
23
24pub fn decode_frame(encoded: &[u8]) -> Option<Vec<u8>> {
26 if encoded.is_empty() {
27 return None;
28 }
29 let mut buf = vec![0u8; encoded.len()];
30 let n = ucobs::decode(encoded, &mut buf)?;
31 buf.truncate(n);
32 Some(buf)
33}
34
35pub struct FrameReader {
44 buf: Vec<u8>,
45}
46
47impl FrameReader {
48 pub fn new() -> Self {
50 Self { buf: Vec::with_capacity(512) }
51 }
52
53 pub fn feed(&mut self, data: &[u8]) -> Vec<Vec<u8>> {
58 self.buf.extend_from_slice(data);
59
60 let mut frames = Vec::new();
61 while let Some(sentinel_pos) = self.buf.iter().position(|&b| b == 0x00) {
62 let encoded = &self.buf[..sentinel_pos];
63 if !encoded.is_empty() {
64 match decode_frame(encoded) {
65 Some(decoded) => frames.push(decoded),
66 None => warn!("bad COBS frame ({} bytes) — skipped", encoded.len()),
67 }
68 }
69 self.buf.drain(..=sentinel_pos);
71 }
72 frames
73 }
74
75 pub fn buffered(&self) -> usize {
77 self.buf.len()
78 }
79}
80
81impl Default for FrameReader {
82 fn default() -> Self {
83 Self::new()
84 }
85}
86
87pub fn read_frame(reader: &mut dyn std::io::Read) -> anyhow::Result<Option<Vec<u8>>> {
93 let mut buf = Vec::with_capacity(280);
94 let mut byte = [0u8; 1];
95
96 loop {
97 match reader.read(&mut byte) {
98 Ok(0) => {
99 return Ok(None);
101 }
102 Ok(_) => {
103 if byte[0] == 0x00 {
104 break;
105 }
106 buf.push(byte[0]);
107 }
108 Err(e) if matches!(e.kind(), std::io::ErrorKind::TimedOut | std::io::ErrorKind::WouldBlock) => {
109 return Ok(None);
110 }
111 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
112 Err(e) => return Err(e.into()),
113 }
114 }
115
116 if buf.is_empty() {
117 return Ok(None);
118 }
119
120 decode_frame(&buf).map(Some).ok_or_else(|| anyhow::anyhow!("COBS decode error"))
121}
122
123#[cfg(test)]
126#[allow(clippy::unwrap_used, clippy::expect_used)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn encode_decode_roundtrip() {
132 let data = b"hello world";
133 let encoded = encode_frame(data);
134 assert_eq!(encoded.last(), Some(&0x00));
136 assert!(!encoded[..encoded.len() - 1].contains(&0x00));
138 let decoded = decode_frame(&encoded[..encoded.len() - 1]);
140 assert_eq!(decoded.as_deref(), Some(data.as_slice()));
141 }
142
143 #[test]
144 fn encode_decode_empty() {
145 let encoded = encode_frame(b"");
149 assert_eq!(encoded.last(), Some(&0x00));
150 }
151
152 #[test]
153 fn encode_decode_with_zeros() {
154 let data = &[0x00, 0x01, 0x00, 0x02, 0x00];
155 let encoded = encode_frame(data);
156 let decoded = decode_frame(&encoded[..encoded.len() - 1]);
157 assert_eq!(decoded.as_deref(), Some(data.as_slice()));
158 }
159
160 #[test]
161 fn decode_frame_invalid() {
162 assert!(decode_frame(&[]).is_none());
163 }
164
165 #[test]
166 fn frame_reader_single_frame() {
167 let mut reader = FrameReader::new();
168 let data = b"test";
169 let frame = encode_frame(data);
170 let frames = reader.feed(&frame);
171 assert_eq!(frames.len(), 1);
172 assert_eq!(frames[0], data);
173 }
174
175 #[test]
176 fn frame_reader_multiple_frames_at_once() {
177 let mut reader = FrameReader::new();
178 let f1 = encode_frame(b"one");
179 let f2 = encode_frame(b"two");
180 let mut combined = f1;
181 combined.extend_from_slice(&f2);
182 let frames = reader.feed(&combined);
183 assert_eq!(frames.len(), 2);
184 assert_eq!(frames[0], b"one");
185 assert_eq!(frames[1], b"two");
186 }
187
188 #[test]
189 fn frame_reader_partial_then_complete() {
190 let mut reader = FrameReader::new();
191 let frame = encode_frame(b"split");
192 let mid = frame.len() / 2;
193
194 let frames = reader.feed(&frame[..mid]);
196 assert!(frames.is_empty());
197 assert!(reader.buffered() > 0);
198
199 let frames = reader.feed(&frame[mid..]);
201 assert_eq!(frames.len(), 1);
202 assert_eq!(frames[0], b"split");
203 assert_eq!(reader.buffered(), 0);
204 }
205
206 #[test]
207 fn frame_reader_empty_gaps() {
208 let mut reader = FrameReader::new();
209 let f = encode_frame(b"data");
211 let mut input = vec![0x00, 0x00]; input.extend_from_slice(&f);
213 input.push(0x00); let frames = reader.feed(&input);
215 assert_eq!(frames.len(), 1);
216 assert_eq!(frames[0], b"data");
217 }
218
219 #[test]
220 fn frame_reader_bad_cobs_skipped() {
221 let mut reader = FrameReader::new();
222 let bad = &[0xFF, 0x01, 0x00]; let good = encode_frame(b"ok");
226 let mut input = bad.to_vec();
227 input.extend_from_slice(&good);
228 let frames = reader.feed(&input);
229 assert_eq!(frames.len(), 1);
231 assert_eq!(frames[0], b"ok");
232 }
233
234 #[test]
235 fn read_frame_from_bytes() {
236 let data = b"frame";
237 let encoded = encode_frame(data);
238 let mut cursor = std::io::Cursor::new(encoded);
239 let result = read_frame(&mut cursor);
240 assert!(result.is_ok());
241 assert_eq!(result.ok().flatten().as_deref(), Some(data.as_slice()));
242 }
243
244 #[test]
245 fn read_frame_timeout() {
246 let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
248 let result = read_frame(&mut cursor);
249 assert!(result.is_ok());
250 assert!(result.ok().flatten().is_none());
251 }
252
253 #[test]
254 fn read_frame_empty_gap_then_data() {
255 let data = b"after_gap";
256 let mut input = vec![0x00]; input.extend_from_slice(&encode_frame(data));
258 let mut cursor = std::io::Cursor::new(input);
259 let r1 = read_frame(&mut cursor);
261 assert_eq!(r1.ok().flatten(), None);
262 let r2 = read_frame(&mut cursor);
264 assert_eq!(r2.ok().flatten().as_deref(), Some(data.as_slice()));
265 }
266
267 #[test]
268 fn encode_all_byte_values_roundtrip() {
269 let data: Vec<u8> = (0..=255).collect();
271 let encoded = encode_frame(&data);
272 let decoded = decode_frame(&encoded[..encoded.len() - 1]);
273 assert_eq!(decoded, Some(data));
274 }
275
276 #[test]
277 fn config_response_cobs_roundtrip() {
278 let data = vec![0x01, 0x48, 0x82, 0x45, 0x36, 0x06, 0x07, 0x05, 0x44, 0x34, 0x16, 0x10, 0x00, 0x01];
282 assert_eq!(data.len(), 14);
283 let encoded = encode_frame(&data);
284 let decoded = decode_frame(&encoded[..encoded.len() - 1]);
285 assert_eq!(decoded, Some(data));
286 }
287
288 #[test]
289 fn config_response_cobs_roundtrip_cad_zero() {
290 let data = vec![0x01, 0x48, 0x82, 0x45, 0x36, 0x06, 0x07, 0x05, 0x44, 0x34, 0x16, 0x10, 0x00, 0x00];
292 assert_eq!(data.len(), 14);
293 let encoded = encode_frame(&data);
294 let decoded = decode_frame(&encoded[..encoded.len() - 1]);
295 assert_eq!(decoded, Some(data));
296 }
297
298 #[cfg(unix)]
299 #[test]
300 fn read_frame_socket_timeout_returns_none() {
301 use std::os::unix::net::UnixStream;
302 use std::time::Duration;
303
304 let (mut a, _b) = UnixStream::pair().unwrap();
305 a.set_read_timeout(Some(Duration::from_millis(10))).unwrap();
306
307 let result = read_frame(&mut a);
309 assert!(result.is_ok(), "socket timeout should be Ok(None), got: {result:?}");
310 assert!(result.unwrap().is_none());
311 }
312}