From 50a0e9abcb27e2aff69e984ba97b2846ae89d9dc Mon Sep 17 00:00:00 2001 From: Lucas Schumacher Date: Sat, 7 Mar 2026 10:25:03 -0500 Subject: [PATCH] Add stats for frame types and source and destination addresses --- src/main.rs | 15 ++++++++++ src/stats.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/stats.rs diff --git a/src/main.rs b/src/main.rs index 1b0cddf..8158b69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,8 @@ use std::time::SystemTime; mod print_frame; use print_frame::PrintFrame; +mod stats; +use stats::Stats; struct KissPkt { timestamp: Duration, orig_len: u32, @@ -78,6 +80,8 @@ fn tnc_loop(tx: Sender, mut stream: T) -> Result<()> { struct Arg { #[arg(short, long, default_value_t = false)] verbose: bool, + #[arg(short, long, default_value_t = false)] + stats: bool, #[arg(short, long, value_name = "FILE")] output: Option, input: String, @@ -85,6 +89,10 @@ struct Arg { fn main() -> Result<()> { let arg = Arg::parse(); + let mut stats = match arg.stats { + true => Some(Stats::new()), + false => None, + }; let mut output = arg.output.map(|path| { //TODO automatically add file extension let file_out = File::create(path).expect("Error opening file for writing"); @@ -119,6 +127,9 @@ fn main() -> Result<()> { } else { frame.print_short(); } + if let Some(stats) = stats.as_mut() { + stats.record(&frame); + } } else { let mut s = String::new(); for byte in pkt.data.iter() { @@ -138,5 +149,9 @@ fn main() -> Result<()> { } } + if let Some(stats) = stats.take() { + stats.dump(); + } + Ok(()) } diff --git a/src/stats.rs b/src/stats.rs new file mode 100644 index 0000000..cffd8ae --- /dev/null +++ b/src/stats.rs @@ -0,0 +1,85 @@ +use std::collections::HashMap; + +use ax25::frame::{Ax25Frame, FrameContent}; + +pub struct Stats { + frame_types: [usize; FrameContent::MAX], + dests: HashMap, + srcs: HashMap, +} +impl Stats { + pub fn new() -> Self { + Stats { + frame_types: [0; FrameContent::MAX], + dests: HashMap::new(), + srcs: HashMap::new(), + } + } + pub fn record(&mut self, frame: &Ax25Frame) { + self.frame_types[frame.content.get_index()] += 1; + if let Some(n) = self.srcs.get_mut(&frame.source.callsign) { + *n += 1; + } else { + self.srcs.insert(frame.source.callsign.clone(), 1); + } + + if let Some(n) = self.dests.get_mut(&frame.destination.callsign) { + *n += 1; + } else { + self.dests.insert(frame.destination.callsign.clone(), 1); + } + } + pub fn dump(mut self) { + println!("\n---Frame type counts---"); + println!(" Info: {}", self.frame_types[0]); + println!(" RecvRdy: {}", self.frame_types[1]); + println!(" RecvNotRdy: {}", self.frame_types[2]); + println!(" Reject: {}", self.frame_types[3]); + println!(" AsyncBalMode: {}", self.frame_types[4]); + println!(" Disconnect: {}", self.frame_types[5]); + println!(" DisconMode: {}", self.frame_types[6]); + println!(" UnNumAck: {}", self.frame_types[7]); + println!(" FrameReject: {}", self.frame_types[8]); + println!(" UnNumInfo: {}", self.frame_types[9]); + println!(" Unknown: {}", self.frame_types[10]); + + println!(""); + let mut top_senders: Vec<(String, usize)> = self.srcs.drain().collect(); + top_senders.sort_unstable_by_key(|x| x.1); + println!("\n--- Top Senders ---"); + for (k, v) in top_senders.iter().rev() { + println!("{:<10}: {v}", k); + } + + println!(""); + let mut top_receivers: Vec<(String, usize)> = self.dests.drain().collect(); + top_receivers.sort_unstable_by_key(|x| x.1); + println!("\n--- Top Receivers ---"); + for (k, v) in top_receivers.iter().rev() { + println!("{:<10}: {v}", k); + } + } +} +trait Indexable { + const MAX: usize; + fn get_index(&self) -> usize; +} +impl Indexable for FrameContent { + const MAX: usize = 11; + + fn get_index(&self) -> usize { + match self { + FrameContent::Information(_) => 0, + FrameContent::ReceiveReady(_) => 1, + FrameContent::ReceiveNotReady(_) => 2, + FrameContent::Reject(_) => 3, + FrameContent::SetAsynchronousBalancedMode(_) => 4, + FrameContent::Disconnect(_) => 5, + FrameContent::DisconnectedMode(_) => 6, + FrameContent::UnnumberedAcknowledge(_) => 7, + FrameContent::FrameReject(_) => 8, + FrameContent::UnnumberedInformation(_) => 9, + FrameContent::UnknownContent(_) => 10, + } + } +}