pub const ARLINK_USR_DATA_MAX_LEN: usize = 16384;
pub const HEADER_STREAM: [u8; 4] = [0x23, 0x74, 0x70, 0x00];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RxFrameRef<'a> {
pub msgid: u8,
pub data_len: usize,
pub data: &'a [u8],
pub checksum: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State { Header, MsgId, DataLen, Data, CheckSum }
#[derive(Debug, Clone, Copy)]
struct PendingFrame { data_offset: usize, msgid: u8, data_len: usize, checksum: u8 }
pub struct RxDecoder<const CAP: usize> {
buf: [u8; CAP],
len: usize,
state: State,
header_idx: usize,
data_len: usize,
pending: Option<PendingFrame>,
}
impl<const CAP: usize> Default for RxDecoder<CAP> {
fn default() -> Self { Self::new() }
}
impl<const CAP: usize> RxDecoder<CAP> {
pub fn new() -> Self {
Self { buf: [0u8; CAP], len: 0, state: State::Header, header_idx: 0, data_len: 0, pending: None }
}
pub fn has_frame(&self) -> bool { self.pending.is_some() }
pub fn feed(&mut self, chunk: &[u8]) {
for &b in chunk { self.feed_byte(b); }
}
pub fn feed_byte(&mut self, byte: u8) {
match self.state {
State::Header => {
if self.header_idx < CAP && byte == HEADER_STREAM[self.header_idx] {
self.buf[self.header_idx] = byte;
self.header_idx += 1;
if self.header_idx == HEADER_STREAM.len() {
self.state = State::MsgId;
self.len = HEADER_STREAM.len();
}
} else if self.header_idx > 0 {
self.header_idx = 0;
if byte == HEADER_STREAM[0] { self.buf[0] = byte; self.header_idx = 1; }
} else {
self.header_idx = if byte == HEADER_STREAM[0] { 1 } else { 0 };
}
}
State::MsgId => {
if self.len >= CAP { self.reset(); return; }
self.buf[self.len] = byte;
self.len += 1;
self.state = State::DataLen;
}
State::DataLen => {
let dl = byte as usize;
if self.len + dl + 1 > CAP {
self.reset();
if byte == HEADER_STREAM[0] { self.buf[0] = byte; self.header_idx = 1; }
return;
}
self.buf[self.len] = byte;
self.len += 1;
self.data_len = dl;
self.state = if dl == 0 { State::CheckSum } else { State::Data };
}
State::Data => {
if self.len + 1 >= CAP { self.reset(); return; }
self.buf[self.len] = byte;
self.len += 1;
if self.len == HEADER_STREAM.len() + 2 + self.data_len {
self.state = State::CheckSum;
}
}
State::CheckSum => {
let calc = self.buf[..self.len].iter().fold(0u8, |a, b| a.wrapping_add(*b));
let valid = calc == byte;
let pending = PendingFrame {
data_offset: HEADER_STREAM.len() + 2,
msgid: self.buf[HEADER_STREAM.len()],
data_len: self.data_len,
checksum: byte,
};
self.reset();
if valid { self.pending = Some(pending); }
}
}
}
pub fn take_ref(&mut self) -> Option<RxFrameRef<'_>> {
let p = self.pending.take()?;
Some(RxFrameRef {
msgid: p.msgid,
data_len: p.data_len,
data: &self.buf[p.data_offset..p.data_offset + p.data_len],
checksum: p.checksum,
})
}
pub fn take_packet(&mut self, out: &mut [u8]) -> Option<usize> {
let p = self.pending.take()?;
let total = HEADER_STREAM.len() + 2 + p.data_len + 1;
if out.len() < total { self.pending = Some(p); return None; }
out[..HEADER_STREAM.len()].copy_from_slice(&HEADER_STREAM);
out[HEADER_STREAM.len()] = p.msgid;
out[HEADER_STREAM.len() + 1] = p.data_len as u8;
out[HEADER_STREAM.len() + 2..HEADER_STREAM.len() + 2 + p.data_len]
.copy_from_slice(&self.buf[p.data_offset..p.data_offset + p.data_len]);
out[total - 1] = p.checksum;
Some(total)
}
fn reset(&mut self) {
self.state = State::Header;
self.header_idx = 0;
self.len = 0;
self.data_len = 0;
self.pending = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_frame(msgid: u8, payload: &[u8], out: &mut [u8]) -> usize {
let mut n = 0;
out[n..n+4].copy_from_slice(&HEADER_STREAM); n += 4;
out[n] = msgid; n += 1;
out[n] = payload.len() as u8; n += 1;
out[n..n+payload.len()].copy_from_slice(payload); n += payload.len();
let sum = out[..n].iter().fold(0u8, |a, b| a.wrapping_add(*b));
out[n] = sum; n + 1
}
#[test]
fn parses_single_frame() {
let mut f = [0u8; 64];
let n = make_frame(1, b"hi", &mut f);
let mut d = RxDecoder::<64>::new();
d.feed(&f[..n]);
assert!(d.has_frame());
let fr = d.take_ref().unwrap();
assert_eq!(fr.msgid, 1);
assert_eq!(fr.data, b"hi");
}
#[test]
fn tolerates_partial_chunks() {
let mut f = [0u8; 64];
let n = make_frame(7, b"payload-123456", &mut f);
let mut d = RxDecoder::<64>::new();
for &b in &f[..n] { d.feed_byte(b); }
let fr = d.take_ref().unwrap();
assert_eq!(fr.data, b"payload-123456");
}
#[test]
fn rejects_checksum_mismatch() {
let mut f = [0u8; 64];
let n = make_frame(1, b"abc", &mut f);
f[n-1] = f[n-1].wrapping_add(1);
let mut d = RxDecoder::<64>::new();
d.feed(&f[..n]);
assert!(!d.has_frame());
assert!(d.take_ref().is_none());
}
#[test]
fn rescans_after_garbage() {
let mut f = [0u8; 64];
let mut n = 0;
f[n..n+8].copy_from_slice(b"garbage!"); n += 8;
n += make_frame(3, b"ok", &mut f[n..]);
let mut d = RxDecoder::<64>::new();
d.feed(&f[..n]);
let fr = d.take_ref().unwrap();
assert_eq!(fr.msgid, 3);
assert_eq!(fr.data, b"ok");
}
#[test]
fn take_packet_writes_full_packet() {
let mut f = [0u8; 64];
let n = make_frame(9, b"xyz", &mut f);
let mut d = RxDecoder::<64>::new();
d.feed(&f[..n]);
let mut out = [0u8; 64];
let total = d.take_packet(&mut out).unwrap();
assert_eq!(total, 4+1+1+3+1);
assert_eq!(&out[..4], &HEADER_STREAM);
assert_eq!(out[4], 9);
assert_eq!(&out[6..9], b"xyz");
}
#[test]
fn rejects_frame_larger_than_cap() {
let mut f = [0u8; 64];
let mut n = 0;
f[n..n+4].copy_from_slice(&HEADER_STREAM); n += 4;
f[n] = 1; n += 1;
f[n] = 40; n += 1;
for i in 0..40u8 { f[n] = i; n += 1; }
let sum = f[..n].iter().fold(0u8, |a, b| a.wrapping_add(*b));
f[n] = sum; n += 1;
let mut d = RxDecoder::<16>::new();
d.feed(&f[..n]);
assert!(!d.has_frame());
}
}