use ados_protocol::msp::{decode_frame, MspDecodeError, MspFrame};
const MAX_MSP_FRAME: usize = 9 + u16::MAX as usize + 1;
#[derive(Debug, Default)]
pub struct MspReassembler {
buf: Vec<u8>,
}
impl MspReassembler {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, chunk: &[u8]) -> Vec<MspFrame> {
self.buf.extend_from_slice(chunk);
let mut out = Vec::new();
loop {
match decode_frame(&self.buf) {
Ok((frame, consumed)) => {
out.push(frame);
self.buf.drain(..consumed);
}
Err(MspDecodeError::TooShort) => {
if self.buf.len() > MAX_MSP_FRAME {
self.resync();
continue;
}
break;
}
Err(MspDecodeError::BadPreamble) | Err(MspDecodeError::BadCrc { .. }) => {
self.resync();
}
}
}
out
}
pub fn pending(&self) -> usize {
self.buf.len()
}
fn resync(&mut self) {
match self.buf.iter().skip(1).position(|&b| b == b'$') {
Some(rel) => {
self.buf.drain(..rel + 1);
}
None => self.buf.clear(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ados_protocol::msp::set_raw_rc;
fn frame() -> Vec<u8> {
set_raw_rc(&[1500, 1500, 1000, 1500], true).unwrap()
}
#[test]
fn a_frame_split_across_two_chunks_decodes() {
let f = frame();
let mid = f.len() / 2;
let mut r = MspReassembler::new();
assert!(r.push(&f[..mid]).is_empty(), "no frame from the first half");
assert_eq!(r.pending(), mid);
let got = r.push(&f[mid..]);
assert_eq!(
got.len(),
1,
"the reassembled frame appears on the second half"
);
assert_eq!(got[0].cmd, 200); }
#[test]
fn two_frames_glued_in_one_chunk_both_decode() {
let mut glued = frame();
glued.extend_from_slice(&frame());
let got = MspReassembler::new().push(&glued);
assert_eq!(got.len(), 2, "both glued frames decode from one delivery");
}
#[test]
fn leading_garbage_is_resynced_past() {
let f = frame();
let mut chunk = vec![0xde, 0xad, 0xbe];
chunk.extend_from_slice(&f);
let got = MspReassembler::new().push(&chunk);
assert_eq!(got.len(), 1, "the reader resyncs to the real preamble");
}
#[test]
fn a_corrupt_frame_is_skipped_not_yielded() {
let mut corrupt = frame();
let mid = corrupt.len() / 2;
corrupt[mid] ^= 0xff;
let mut stream = corrupt;
stream.extend_from_slice(&frame());
let got = MspReassembler::new().push(&stream);
assert_eq!(
got.len(),
1,
"only the good frame surfaces; the corrupt one is dropped, not returned"
);
}
#[test]
fn a_desynced_stream_does_not_grow_without_bound() {
let mut r = MspReassembler::new();
for _ in 0..1000 {
assert!(r.push(&[0u8; 256]).is_empty());
}
assert!(
r.pending() <= 256,
"a preamble-less stream is drained, not hoarded"
);
}
}