pub const MAX_PGN_LIST_LEN: usize = 74;
pub(crate) const MAX_PGN: u32 = 0x1_FFFF;
pub const NETWORK_MANAGEMENT_PGNS: [u32; 10] = [
59392, 59904, 60160, 60416, 60928, 126208, 126464, 126993, 126996, 126998,
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RxFilter(Vec<u32>);
impl RxFilter {
pub(crate) fn new(pgns: &[u32]) -> Option<Self> {
let mut pgns = pgns.to_vec();
pgns.sort_unstable();
pgns.dedup();
(!pgns.is_empty()).then_some(Self(pgns))
}
pub(crate) fn passes(&self, pgn: u32) -> bool {
pgn >= crate::engine::codec::SYNTHETIC_PGN_START
|| NETWORK_MANAGEMENT_PGNS.contains(&pgn)
|| self.0.binary_search(&pgn).is_ok()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PgnLists {
pub tx: Vec<u32>,
pub rx: Vec<u32>,
}
impl PgnLists {
pub fn is_empty(&self) -> bool {
self.tx.is_empty() && self.rx.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum PgnListSupport {
Answered,
Pushed,
Unsupported,
Untouched,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PgnListStatus {
pub tx: PgnListSupport,
pub rx: PgnListSupport,
pub dropped: Vec<u32>,
}
pub(crate) struct TxGate {
label: &'static str,
allowed: Vec<u32>,
refused: std::sync::Mutex<Vec<u32>>,
}
impl TxGate {
pub(crate) fn new(label: &'static str, named: &[u32], extra: &[u32]) -> Option<Self> {
if named.is_empty() {
return None;
}
let (listed, _) = merge(&[], &[named, extra].concat());
let mut allowed = NETWORK_MANAGEMENT_PGNS.to_vec();
allowed.extend(listed);
Some(Self {
label,
allowed,
refused: std::sync::Mutex::new(Vec::new()),
})
}
pub(crate) fn allows(&self, pgn: u32) -> bool {
if self.allowed.contains(&pgn) {
return true;
}
if let Ok(mut refused) = self.refused.lock()
&& !refused.contains(&pgn)
{
refused.push(pgn);
log::warn!(
"{}: refusing to send PGN {pgn}: it is not in the transmit list; \
name it (--tx-pgn / pgn_lists.tx) before starting",
self.label
);
}
false
}
}
pub fn merge(builtin: &[u32], extra: &[u32]) -> (Vec<u32>, Vec<u32>) {
let mut list = builtin.to_vec();
let mut dropped = Vec::new();
for &pgn in extra {
if list.contains(&pgn) {
continue;
}
if pgn > MAX_PGN || list.len() >= MAX_PGN_LIST_LEN {
if !dropped.contains(&pgn) {
dropped.push(pgn);
}
continue;
}
list.push(pgn);
}
(list, dropped)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_rx_filter_passes_the_named_and_network_management() {
assert_eq!(RxFilter::new(&[]), None);
let f = RxFilter::new(&[129029, 127250, 129029]).unwrap();
assert!(f.passes(127250));
assert!(f.passes(129029));
assert!(!f.passes(130306));
assert!(f.passes(60928));
assert!(f.passes(126996));
assert!(f.passes(262400));
}
#[test]
fn a_gate_allows_what_is_named_and_refuses_the_rest() {
let gate = TxGate::new("test", &[127508], &[127258]).unwrap();
for pgn in [127508, 127258, 59904, 126996] {
assert!(gate.allows(pgn), "{pgn}");
}
assert!(!gate.allows(127506));
assert!(TxGate::new("test", &[], &[127258]).is_none(), "extra alone");
}
#[test]
fn a_gate_refuses_what_the_list_leaves_out() {
let named: Vec<u32> = (130_000..130_000 + MAX_PGN_LIST_LEN as u32).collect();
let gate = TxGate::new("test", &named, &[127258]).unwrap();
assert!(gate.allows(130_000));
assert!(!gate.allows(127258), "past the limit");
let gate = TxGate::new("test", &[127508, 0x2_0000], &[]).unwrap();
assert!(!gate.allows(0x2_0000), "invalid");
}
#[test]
fn extra_pgns_follow_the_builtin_ones_without_duplicates() {
let (list, dropped) = merge(&[59392, 126464], &[127508, 126464, 127506, 127508]);
assert_eq!(list, [59392, 126464, 127508, 127506]);
assert!(dropped.is_empty());
}
#[test]
fn pgns_that_never_go_on_the_wire_are_dropped() {
let (list, dropped) = merge(&[59392], &[0x40000, 127508, 0x2_0000]);
assert_eq!(list, [59392, 127508]);
assert_eq!(dropped, [0x40000, 0x2_0000]);
}
#[test]
fn the_list_stops_at_one_fast_packet() {
let extra: Vec<u32> = (130_000..130_100).collect();
let (list, dropped) = merge(&[59392], &extra);
assert_eq!(list.len(), MAX_PGN_LIST_LEN);
assert_eq!(dropped.len(), 100 - (MAX_PGN_LIST_LEN - 1));
assert_eq!(dropped[0], 130_000 + (MAX_PGN_LIST_LEN as u32 - 1));
}
}