use std::time::SystemTime;
use arrayvec::ArrayString;
use crate::msk::BitSink;
const SYN: u8 = 0x16;
const SYN_INV: u8 = !SYN; const SOH: u8 = 0x01;
const ETX: u8 = 0x83; const ETB: u8 = 0x97; const DLE: u8 = 0x7F;
const MAX_FRAME_LEN: usize = 240;
const DLE_ESCAPE_MIN_LEN: usize = 20;
#[derive(Clone, Debug)]
pub struct AcarsMessage {
pub timestamp: SystemTime,
pub channel_idx: u8,
pub freq_hz: f64,
pub level_db: f32,
pub error_count: u8,
pub mode: u8,
pub label: [u8; 2],
pub block_id: u8,
pub ack: u8,
pub aircraft: ArrayString<8>,
pub flight_id: Option<ArrayString<7>>,
pub message_no: Option<ArrayString<5>>,
pub text: String,
pub end_of_message: bool,
pub reassembled_block_count: u8,
pub parsed: Option<crate::label_parsers::Oooi>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum State {
WaitingSyn,
Syn2,
SeekingSoh,
Text,
Crc1,
Crc2,
}
pub struct FrameParser {
state: State,
out_bits: u8,
n_bits: u8,
buf: Vec<u8>,
parity_errors: Vec<usize>,
parity_err_count: u8,
crc_bytes: [u8; 2],
polarity_flip_pending: bool,
pending_messages: std::collections::VecDeque<AcarsMessage>,
channel_idx: u8,
channel_freq_hz: f64,
}
impl FrameParser {
#[must_use]
pub fn new(channel_idx: u8, channel_freq_hz: f64) -> Self {
Self {
state: State::WaitingSyn,
out_bits: 0,
n_bits: 8,
buf: Vec::with_capacity(256),
parity_errors: Vec::new(),
parity_err_count: 0,
crc_bytes: [0, 0],
polarity_flip_pending: false,
pending_messages: std::collections::VecDeque::new(),
channel_idx,
channel_freq_hz,
}
}
fn reset_to_idle(&mut self) {
self.state = State::WaitingSyn;
self.n_bits = 1;
self.buf.clear();
self.parity_errors.clear();
self.parity_err_count = 0;
self.crc_bytes = [0, 0];
}
pub fn take_polarity_flip(&mut self) -> bool {
std::mem::replace(&mut self.polarity_flip_pending, false)
}
pub fn drain<F: FnMut(AcarsMessage)>(&mut self, mut on_message: F) {
while let Some(msg) = self.pending_messages.pop_front() {
on_message(msg);
}
}
fn consume_byte(&mut self, byte: u8) {
match self.state {
State::WaitingSyn => {
if byte == SYN {
self.state = State::Syn2;
self.n_bits = 8;
} else if byte == SYN_INV {
self.polarity_flip_pending = true;
self.state = State::Syn2;
self.n_bits = 8;
} else {
self.n_bits = 1;
}
}
State::Syn2 => {
if byte == SYN {
self.state = State::SeekingSoh;
self.n_bits = 8;
} else if byte == SYN_INV {
self.polarity_flip_pending = true;
self.n_bits = 8;
} else {
self.reset_to_idle();
}
}
State::SeekingSoh => {
if byte == SOH {
self.buf.clear();
self.parity_errors.clear();
self.parity_err_count = 0;
self.crc_bytes = [0, 0];
self.state = State::Text;
self.n_bits = 8;
} else {
self.reset_to_idle();
}
}
State::Text => {
self.buf.push(byte);
let pos = self.buf.len() - 1;
if !has_odd_parity(byte) {
self.parity_err_count = self.parity_err_count.saturating_add(1);
self.parity_errors.push(pos);
if usize::from(self.parity_err_count) > crate::syndrom::MAX_PARITY_ERRORS + 1 {
self.reset_to_idle();
return;
}
}
if byte == ETX || byte == ETB {
self.state = State::Crc1;
self.n_bits = 8;
return;
}
if self.buf.len() > DLE_ESCAPE_MIN_LEN && byte == DLE {
let new_len = self.buf.len() - 3;
self.crc_bytes[0] = self.buf[new_len];
self.crc_bytes[1] = self.buf[new_len + 1];
self.buf.truncate(new_len);
self.parity_errors.retain(|&pos| pos < new_len);
self.parity_err_count =
u8::try_from(self.parity_errors.len()).unwrap_or(u8::MAX);
self.finalize_frame();
return;
}
if self.buf.len() > MAX_FRAME_LEN {
self.reset_to_idle();
return;
}
self.n_bits = 8;
}
State::Crc1 => {
self.crc_bytes[0] = byte;
self.state = State::Crc2;
self.n_bits = 8;
}
State::Crc2 => {
self.crc_bytes[1] = byte;
self.finalize_frame();
}
}
}
fn finalize_frame(&mut self) {
let mut crc = crate::crc::compute(&self.buf);
crc = crate::crc::update(crc, self.crc_bytes[0]);
crc = crate::crc::update(crc, self.crc_bytes[1]);
if crc != 0 {
let recovered = if self.parity_errors.is_empty() {
crate::syndrom::fix_double_error(&mut self.buf, crc)
} else {
crate::syndrom::fix_parity_errors(&mut self.buf, crc, &self.parity_errors)
};
if !recovered {
self.reset_to_idle();
return;
}
}
if self.buf.len() < 13 {
self.reset_to_idle();
return;
}
let mode = self.buf[0] & 0x7F;
let mut aircraft = ArrayString::<8>::new();
for &b in &self.buf[1..8] {
let _ = aircraft.try_push((b & 0x7F) as char);
}
let mut ack = self.buf[8] & 0x7F;
if ack == 0x15 {
ack = b'!';
}
let mut label = [self.buf[9] & 0x7F, self.buf[10] & 0x7F];
if label[1] == 0x7F {
label[1] = b'd';
}
let block_id = self.buf[11] & 0x7F;
let is_downlink = block_id.is_ascii_digit();
let text_end = self.buf.len() - 1;
let mut message_no: Option<ArrayString<5>> = None;
let mut flight_id: Option<ArrayString<7>> = None;
let text_start: usize = if is_downlink && text_end > 13 {
let msgno_finish = 17.min(text_end);
if msgno_finish > 13 {
let mut no = ArrayString::<5>::new();
for &b in &self.buf[13..msgno_finish] {
let _ = no.try_push((b & 0x7F) as char);
}
if !no.is_empty() {
message_no = Some(no);
}
}
let flight_start = msgno_finish;
let flight_finish = 23.min(text_end);
if flight_start < flight_finish {
let mut fid = ArrayString::<7>::new();
for &b in &self.buf[flight_start..flight_finish] {
let _ = fid.try_push((b & 0x7F) as char);
}
if !fid.is_empty() {
flight_id = Some(fid);
}
}
flight_finish
} else {
13
};
let mut text = String::with_capacity(text_end.saturating_sub(text_start));
if text_end > text_start {
for &b in &self.buf[text_start..text_end] {
text.push((b & 0x7F) as char);
}
}
let end_of_message = (self.buf[text_end] & 0x7F) == 0x03;
let msg = AcarsMessage {
timestamp: SystemTime::now(),
channel_idx: self.channel_idx,
freq_hz: self.channel_freq_hz,
level_db: 0.0, error_count: self.parity_err_count,
mode,
label,
block_id,
ack,
aircraft,
flight_id,
message_no,
text,
end_of_message,
reassembled_block_count: 1,
parsed: None,
};
self.pending_messages.push_back(msg);
self.reset_to_idle();
}
pub fn feed_bytes<F: FnMut(AcarsMessage)>(&mut self, bytes: &[u8], mut on_message: F) {
for &b in bytes {
self.consume_byte(b);
}
self.drain(&mut on_message);
}
}
impl BitSink for FrameParser {
fn take_polarity_flip(&mut self) -> bool {
FrameParser::take_polarity_flip(self)
}
fn put_bit(&mut self, value: f32) {
self.out_bits >>= 1;
if value > 0.0 {
self.out_bits |= 0x80;
}
self.n_bits = self.n_bits.saturating_sub(1);
if self.n_bits == 0 {
let byte = self.out_bits;
self.consume_byte(byte);
}
}
}
fn has_odd_parity(b: u8) -> bool {
b.count_ones() & 1 == 1
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
fn add_odd_parity(bytes: &mut [u8]) {
for b in bytes.iter_mut() {
if (b.count_ones() & 1) == 0 {
*b |= 0x80;
}
}
}
fn synthesize_frame(block_id: u8, text: &[u8]) -> Vec<u8> {
let mut buf = vec![0x16, 0x16, 0x01];
buf.push(b'2'); buf.extend_from_slice(b".N12345"); buf.push(b'!'); buf.extend_from_slice(b"H1"); buf.push(block_id);
buf.push(0x02); buf.extend_from_slice(text);
buf.push(0x03); let payload_start = 3;
let payload_end = buf.len();
add_odd_parity(&mut buf[payload_start..payload_end]);
let crc = crate::crc::compute(&buf[payload_start..payload_end]);
buf.push((crc & 0xFF) as u8); buf.push((crc >> 8) as u8); buf
}
fn synthesize_minimal_frame() -> Vec<u8> {
synthesize_frame(b'A', b"TEST")
}
#[test]
fn parses_a_known_good_uplink_frame() {
let bytes = synthesize_minimal_frame();
let mut parser = FrameParser::new(0, 0.0);
let mut decoded = Vec::new();
parser.feed_bytes(&bytes, |msg| decoded.push(msg));
assert_eq!(decoded.len(), 1, "expected exactly one frame");
let msg = &decoded[0];
assert_eq!(msg.mode, b'2');
assert_eq!(&msg.aircraft[..], ".N12345");
assert_eq!(msg.label, *b"H1");
assert_eq!(msg.block_id, b'A');
assert_eq!(msg.ack, b'!');
assert_eq!(msg.text, "TEST");
assert!(msg.end_of_message);
assert_eq!(msg.channel_idx, 0);
assert!(msg.flight_id.is_none(), "uplink has no flight_id");
assert!(msg.message_no.is_none(), "uplink has no message_no");
}
#[test]
fn parses_a_known_good_downlink_frame() {
let bytes = synthesize_frame(b'0', b"S64ABA031TBODY");
let mut parser = FrameParser::new(0, 0.0);
let mut decoded = Vec::new();
parser.feed_bytes(&bytes, |msg| decoded.push(msg));
assert_eq!(decoded.len(), 1, "expected exactly one frame");
let msg = &decoded[0];
assert_eq!(msg.block_id, b'0');
assert_eq!(msg.message_no.as_deref(), Some("S64A"));
assert_eq!(msg.flight_id.as_deref(), Some("BA031T"));
assert_eq!(msg.text, "BODY");
}
#[test]
fn rejects_a_corrupted_frame_when_fec_cant_recover() {
let mut bytes = synthesize_minimal_frame();
let n = bytes.len();
bytes[n - 2] = 0x00;
bytes[n - 1] = 0x00;
let mut parser = FrameParser::new(0, 0.0);
let mut decoded = Vec::new();
parser.feed_bytes(&bytes, |msg| decoded.push(msg));
assert!(decoded.is_empty(), "corrupted frame must not decode");
}
#[test]
fn ignores_bytes_outside_a_frame() {
let mut parser = FrameParser::new(0, 0.0);
let mut decoded = Vec::new();
parser.feed_bytes(b"\x00\xFF\x00\xFF\x00", |msg| decoded.push(msg));
assert!(decoded.is_empty());
}
#[test]
fn dle_recovery_drops_stale_parity_offsets() {
let mut bytes = vec![SYN, SYN, SOH];
bytes.extend(std::iter::repeat_n(0x80, 22));
bytes.extend_from_slice(&[0x00, 0x00, 0x00]);
bytes.push(DLE);
let mut parser = FrameParser::new(0, 0.0);
let mut decoded = Vec::new();
parser.feed_bytes(&bytes, |msg| decoded.push(msg));
assert!(
decoded.is_empty(),
"synthetic DLE-recovery frame must not decode"
);
}
}