Compare commits
7 Commits
020739397c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 200eadf9a1 | |||
| f9671f9408 | |||
| 250dcd8d6f | |||
| 50a0e9abcb | |||
| e801da4beb | |||
| a16fbd3a8e | |||
| dc5196404f |
@@ -1,251 +0,0 @@
|
|||||||
use clap::Parser;
|
|
||||||
use std::{
|
|
||||||
fs::File,
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use pcap_file::{DataLink, pcap::{PcapPacket, PcapWriter, PcapHeader}};
|
|
||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
use ax25::frame::{CommandResponse, Ax25Frame, FrameContent};
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(author, version, about, long_about = None)]
|
|
||||||
struct Arg {
|
|
||||||
#[arg(short, long, default_value_t = false)]
|
|
||||||
verbose: bool,
|
|
||||||
infile: PathBuf,
|
|
||||||
outfile: PathBuf,
|
|
||||||
}
|
|
||||||
trait PrintData {
|
|
||||||
fn type_str_short(&self) -> &'static str;
|
|
||||||
fn print_short(&self);
|
|
||||||
fn print_long(&self);
|
|
||||||
}
|
|
||||||
impl PrintData for Ax25Frame {
|
|
||||||
fn type_str_short(&self) -> &'static str {
|
|
||||||
match self.content {
|
|
||||||
FrameContent::Information(_) => "Info",
|
|
||||||
FrameContent::ReceiveReady(_) => "RecvRdy",
|
|
||||||
FrameContent::ReceiveNotReady(_) => "RecvNotRdy",
|
|
||||||
FrameContent::Reject(_) => "Reject",
|
|
||||||
FrameContent::SetAsynchronousBalancedMode(_) => "AsyncBalMode", //12 long
|
|
||||||
FrameContent::Disconnect(_) => "Disconnect",
|
|
||||||
FrameContent::DisconnectedMode(_) => "DisconMode",
|
|
||||||
FrameContent::UnnumberedAcknowledge(_) => "UnNumAck",
|
|
||||||
FrameContent::FrameReject(_) => "FrameReject",
|
|
||||||
FrameContent::UnnumberedInformation(_) => "UnNumInfo",
|
|
||||||
FrameContent::UnknownContent(_) => "Unknown",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn print_short(&self) {
|
|
||||||
//Format: "[source] -> [dest] via [..] [CMD/RSP/NIL] [frameType]: [DATA str rep]"
|
|
||||||
let src = self.source.to_string();
|
|
||||||
let dst = self.destination.to_string();
|
|
||||||
//let via: String = self.route.iter().map(|re| format!(" {} ", re.repeater.to_string())).collect(); //todo
|
|
||||||
let rsp = match self.command_or_response {
|
|
||||||
Some(CommandResponse::Command) => "CMD",
|
|
||||||
Some(CommandResponse::Response) => "RSP",
|
|
||||||
None => "NIL",
|
|
||||||
};
|
|
||||||
let ftype = self.type_str_short();
|
|
||||||
|
|
||||||
//let mut rawdata;
|
|
||||||
let (rawdata, finfo) = match &self.content {
|
|
||||||
FrameContent::Information(_i) =>
|
|
||||||
(_i.info.clone(),
|
|
||||||
format!("pid: {:?}, seq: {}/{}, info: ", _i.pid, _i.send_sequence, _i.receive_sequence)),
|
|
||||||
FrameContent::ReceiveReady(rr) => (vec![], format!("seq: {}, poll/final: {}", rr.receive_sequence, rr.poll_or_final)),
|
|
||||||
FrameContent::ReceiveNotReady(rnr) => (vec![], format!("seq: {}, poll/final: {}", rnr.receive_sequence, rnr.poll_or_final)),
|
|
||||||
FrameContent::Reject(rnr) => (vec![], format!("seq: {}, poll/final: {}", rnr.receive_sequence, rnr.poll_or_final)),
|
|
||||||
FrameContent::SetAsynchronousBalancedMode(abm) => (vec![], format!("poll: {}", abm.poll)),
|
|
||||||
FrameContent::Disconnect(abm) => (vec![], format!("poll: {}", abm.poll)),
|
|
||||||
FrameContent::DisconnectedMode(abm) => (vec![], format!("final bit: {}", abm.final_bit)),
|
|
||||||
FrameContent::UnnumberedAcknowledge(abm) => (vec![], format!("final bit: {}", abm.final_bit)),
|
|
||||||
FrameContent::FrameReject(fr) => (vec![], format!("[{}{}{}{}], seq: {}/{}, raw: {}, cmdRsp: {}, final: {}", if fr.z {"Z"}else{""}, if fr.y {"Y"}else{""}, if fr.x {"X"}else{""}, if fr.w{"W"}else{""}, fr.send_sequence, fr.receive_sequence, fr.rejected_control_field_raw, match fr.command_response {CommandResponse::Command => "CMD", CommandResponse::Response => "RSP"}, fr.final_bit)),
|
|
||||||
FrameContent::UnnumberedInformation(ui) => (
|
|
||||||
ui.info.clone(),
|
|
||||||
format!("pid: {:?} info:", ui.pid)),
|
|
||||||
FrameContent::UnknownContent(ukn) => (ukn.raw.clone(), format!("")),
|
|
||||||
};
|
|
||||||
let mut data = String::new();
|
|
||||||
for byte in rawdata.iter() {
|
|
||||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
|
||||||
if *byte >= 32 && *byte <= 126 {
|
|
||||||
data.push((*byte).into());
|
|
||||||
} else {
|
|
||||||
data.push_str(&format!("\\{:x?} ", *byte));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("AX.25 {:12} {} {:9} -> {:9} {}",ftype, rsp, src, dst, if !data.is_empty() {data} else {finfo});
|
|
||||||
}
|
|
||||||
fn print_long(&self) {
|
|
||||||
//Format: "[source] -> [dest] via [..] [CMD/RSP/NIL] [frameType]: [DATA str rep]"
|
|
||||||
let src = self.source.to_string();
|
|
||||||
let dst = self.destination.to_string();
|
|
||||||
let via: String = self.route.iter().map(|re| format!(" {} ", re.repeater.to_string())).collect(); //todo
|
|
||||||
let rsp = match self.command_or_response {
|
|
||||||
Some(CommandResponse::Command) => "CMD",
|
|
||||||
Some(CommandResponse::Response) => "RSP",
|
|
||||||
None => "NIL",
|
|
||||||
};
|
|
||||||
let ftype = self.type_str_short();
|
|
||||||
|
|
||||||
//let mut rawdata;
|
|
||||||
let (rawdata, finfo) = match &self.content {
|
|
||||||
FrameContent::Information(_i) =>
|
|
||||||
(_i.info.clone(),
|
|
||||||
format!("pid: {:?}, seq: {}/{}, info: ", _i.pid, _i.send_sequence, _i.receive_sequence)),
|
|
||||||
FrameContent::ReceiveReady(rr) => (vec![], format!("seq: {}, poll/final: {}", rr.receive_sequence, rr.poll_or_final)),
|
|
||||||
FrameContent::ReceiveNotReady(rnr) => (vec![], format!("seq: {}, poll/final: {}", rnr.receive_sequence, rnr.poll_or_final)),
|
|
||||||
FrameContent::Reject(rnr) => (vec![], format!("seq: {}, poll/final: {}", rnr.receive_sequence, rnr.poll_or_final)),
|
|
||||||
FrameContent::SetAsynchronousBalancedMode(abm) => (vec![], format!("poll: {}", abm.poll)),
|
|
||||||
FrameContent::Disconnect(abm) => (vec![], format!("poll: {}", abm.poll)),
|
|
||||||
FrameContent::DisconnectedMode(abm) => (vec![], format!("final bit: {}", abm.final_bit)),
|
|
||||||
FrameContent::UnnumberedAcknowledge(abm) => (vec![], format!("final bit: {}", abm.final_bit)),
|
|
||||||
FrameContent::FrameReject(fr) => (vec![], format!("[{}{}{}{}], seq: {}/{}, raw: {}, cmdRsp: {}, final: {}", if fr.z {"Z"}else{""}, if fr.y {"Y"}else{""}, if fr.x {"X"}else{""}, if fr.w{"W"}else{""}, fr.send_sequence, fr.receive_sequence, fr.rejected_control_field_raw, match fr.command_response {CommandResponse::Command => "CMD", CommandResponse::Response => "RSP"}, fr.final_bit)),
|
|
||||||
FrameContent::UnnumberedInformation(ui) => (
|
|
||||||
ui.info.clone(),
|
|
||||||
format!("pid: {:?} info:", ui.pid)),
|
|
||||||
FrameContent::UnknownContent(ukn) => (ukn.raw.clone(), format!("")),
|
|
||||||
};
|
|
||||||
let mut data = String::new();
|
|
||||||
for byte in rawdata.iter() {
|
|
||||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
|
||||||
if *byte >= 32 && *byte <= 126 {
|
|
||||||
data.push((*byte).into());
|
|
||||||
} else {
|
|
||||||
data.push_str(&format!("\\{:x?} ", *byte));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("AX.25 {:12} {} {:9} -> {:9} via [{}] {} {}",ftype, rsp, src, dst, via, finfo, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let mut information = 0;
|
|
||||||
let mut receive_ready = 0;
|
|
||||||
let mut receive_not_ready = 0;
|
|
||||||
let mut reject = 0;
|
|
||||||
let mut set_asynchronous_balanced_mode = 0;
|
|
||||||
let mut disconnect = 0;
|
|
||||||
let mut disconnected_mode = 0;
|
|
||||||
let mut unnumbered_acknowledge = 0;
|
|
||||||
let mut frame_reject = 0;
|
|
||||||
let mut unnumbered_information = 0;
|
|
||||||
let mut unknown_content = 0;
|
|
||||||
|
|
||||||
let arg = Arg::parse();
|
|
||||||
|
|
||||||
let verbose = arg.verbose;
|
|
||||||
|
|
||||||
let indisplay = arg.infile.display();
|
|
||||||
let mut file_in = match File::open(&arg.infile) {
|
|
||||||
Err(why) => panic!("couldn't open {}: {}", indisplay, why),
|
|
||||||
Ok(file) => file,
|
|
||||||
};
|
|
||||||
|
|
||||||
let file_out = File::create(&arg.outfile).expect("Error opening file for writing");
|
|
||||||
let header = PcapHeader{datalink: DataLink::AX25_KISS, ..Default::default()};
|
|
||||||
let mut out_file = PcapWriter::with_header(file_out, header).expect("Error writing file");
|
|
||||||
|
|
||||||
|
|
||||||
let mut buffer = vec![0_u8; 4096];
|
|
||||||
let mut rawframe = vec![];
|
|
||||||
let mut escaped = false;
|
|
||||||
let mut timestamp = SystemTime::now()
|
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
|
||||||
.unwrap();
|
|
||||||
let mut orig_len = 0;
|
|
||||||
// while let Ok(n) = file_in.by_ref().take(4096).read_to_end(&mut buffer) {
|
|
||||||
while let Ok(n) = file_in.read(&mut buffer) {
|
|
||||||
if n <= 0 {break;}
|
|
||||||
for byte in buffer[..n].iter() {
|
|
||||||
orig_len += 1;
|
|
||||||
if escaped {
|
|
||||||
match *byte {
|
|
||||||
0xDC => rawframe.push(0xC0),
|
|
||||||
0xDD => rawframe.push(0xDB),
|
|
||||||
_ => { /* ERROR: invalid escape sequence */ }
|
|
||||||
}
|
|
||||||
escaped = false;
|
|
||||||
} else {
|
|
||||||
match *byte {
|
|
||||||
0xDB => escaped = true,
|
|
||||||
0xC0 => {
|
|
||||||
//TODO send and clear frame if not empty
|
|
||||||
if rawframe.len() == 0 {
|
|
||||||
orig_len = 1;
|
|
||||||
timestamp = SystemTime::now()
|
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
|
||||||
.unwrap();
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
if let Ok(frame) = Ax25Frame::from_bytes(&rawframe[1..]) {
|
|
||||||
match frame.content {
|
|
||||||
FrameContent::Information(_) => information += 1,
|
|
||||||
FrameContent::ReceiveReady(_) => receive_ready += 1,
|
|
||||||
FrameContent::ReceiveNotReady(_) => receive_not_ready += 1,
|
|
||||||
FrameContent::Reject(_) => reject += 1,
|
|
||||||
FrameContent::SetAsynchronousBalancedMode(_) => set_asynchronous_balanced_mode += 1,
|
|
||||||
FrameContent::Disconnect(_) => disconnect += 1,
|
|
||||||
FrameContent::DisconnectedMode(_) => disconnected_mode += 1,
|
|
||||||
FrameContent::UnnumberedAcknowledge(_) => unnumbered_acknowledge += 1,
|
|
||||||
FrameContent::FrameReject(_) => frame_reject += 1,
|
|
||||||
FrameContent::UnnumberedInformation(_) => unnumbered_information += 1,
|
|
||||||
FrameContent::UnknownContent(_) => unknown_content +=1,
|
|
||||||
}
|
|
||||||
frame.print_short();
|
|
||||||
//println!("{{source: {}, dest: {}, route: {:?}, cmd/rsp: {:?}, content: {:?}}}", frame.source, frame.destination, frame.route, frame.command_or_response, frame.content);
|
|
||||||
//println!("{}\n", frame.info_string_lossy().unwrap_or("Could not parse AX.25 frame data".to_string()));
|
|
||||||
}else {
|
|
||||||
let mut s = String::new();
|
|
||||||
for byte in rawframe.iter() {
|
|
||||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
|
||||||
if *byte >= 32 && *byte <= 126 {
|
|
||||||
s.push((*byte).into());
|
|
||||||
} else {
|
|
||||||
s.push_str(&format!("\\{:x?} ", *byte));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//TODO print time stamp
|
|
||||||
println!("{}", s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let pkt = PcapPacket::new_owned(timestamp, orig_len, rawframe.clone());
|
|
||||||
out_file.write_packet(&pkt).unwrap();
|
|
||||||
|
|
||||||
//tx.send(KissPkt{ timestamp, orig_len, data: frame.clone()})?;
|
|
||||||
rawframe.clear();
|
|
||||||
orig_len = 1;
|
|
||||||
timestamp = SystemTime::now()
|
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
b => rawframe.push(b),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("\n---Frame type counts---");
|
|
||||||
println!(" Info: {}", information);
|
|
||||||
println!(" RecvRdy: {}", receive_ready);
|
|
||||||
println!(" RecvNotRdy: {}", receive_not_ready);
|
|
||||||
println!(" Reject: {}", reject);
|
|
||||||
println!(" AsyncBalMode: {}", set_asynchronous_balanced_mode);
|
|
||||||
println!(" Disconnect: {}", disconnect);
|
|
||||||
println!(" DisconMode: {}", disconnected_mode);
|
|
||||||
println!(" UnNumAck: {}", unnumbered_acknowledge);
|
|
||||||
println!(" FrameReject: {}", frame_reject);
|
|
||||||
println!(" UnNumInfo: {}", unnumbered_information);
|
|
||||||
println!(" Unknown: {}", unknown_content);
|
|
||||||
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
331
src/main.rs
331
src/main.rs
@@ -1,15 +1,16 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
path::PathBuf,
|
io::{self, IsTerminal},
|
||||||
|
path::{Path, PathBuf},
|
||||||
sync::mpsc::{channel, Sender},
|
sync::mpsc::{channel, Sender},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ax25::frame::{Ax25Frame, CommandResponse, FrameContent};
|
use ax25::frame::Ax25Frame;
|
||||||
use pcap_file::{
|
use pcap_file::{
|
||||||
pcap::{PcapHeader, PcapPacket, PcapWriter},
|
pcap::{PcapHeader, PcapPacket, PcapReader, PcapWriter},
|
||||||
DataLink,
|
DataLink,
|
||||||
};
|
};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
@@ -17,13 +18,29 @@ use std::net::TcpStream;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
mod print_frame;
|
||||||
|
use print_frame::PrintFrame;
|
||||||
|
mod stats;
|
||||||
|
use stats::Stats;
|
||||||
struct KissPkt {
|
struct KissPkt {
|
||||||
timestamp: Duration,
|
timestamp: Duration,
|
||||||
orig_len: u32,
|
orig_len: u32,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tnc_loop(tx: Sender<KissPkt>, mut stream: TcpStream) -> Result<()> {
|
fn pcap_loop<T: Read>(tx: Sender<KissPkt>, stream: T) -> Result<()> {
|
||||||
|
let mut reader = PcapReader::new(stream)?;
|
||||||
|
|
||||||
|
while let Some(Ok(pkt)) = reader.next_packet() {
|
||||||
|
tx.send(KissPkt {
|
||||||
|
timestamp: pkt.timestamp,
|
||||||
|
orig_len: pkt.orig_len,
|
||||||
|
data: pkt.data.into(),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn tnc_loop<T: Read>(tx: Sender<KissPkt>, mut stream: T) -> Result<()> {
|
||||||
let mut buffer = [0_u8; 4096];
|
let mut buffer = [0_u8; 4096];
|
||||||
let mut frame = vec![];
|
let mut frame = vec![];
|
||||||
let mut escaped = false;
|
let mut escaped = false;
|
||||||
@@ -33,6 +50,9 @@ fn tnc_loop(tx: Sender<KissPkt>, mut stream: TcpStream) -> Result<()> {
|
|||||||
let mut orig_len = 0;
|
let mut orig_len = 0;
|
||||||
loop {
|
loop {
|
||||||
let n = stream.read(&mut buffer)?;
|
let n = stream.read(&mut buffer)?;
|
||||||
|
if n == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
for byte in buffer[..n].iter() {
|
for byte in buffer[..n].iter() {
|
||||||
orig_len += 1;
|
orig_len += 1;
|
||||||
if escaped {
|
if escaped {
|
||||||
@@ -66,23 +86,26 @@ fn tnc_loop(tx: Sender<KissPkt>, mut stream: TcpStream) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Ok(())
|
|
||||||
}
|
}
|
||||||
const DEFAULT_ADDR: &str = "127.0.0.1:8001";
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Arg {
|
struct Arg {
|
||||||
#[arg(short, long, default_value_t = false)]
|
#[arg(short, long, default_value_t = false)]
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
#[arg(short, long)]
|
#[arg(short, long, default_value_t = false)]
|
||||||
file: Option<PathBuf>,
|
stats: bool,
|
||||||
address: Option<String>,
|
#[arg(short, long, value_name = "FILE")]
|
||||||
|
output: Option<PathBuf>,
|
||||||
|
input: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let arg = Arg::parse();
|
let arg = Arg::parse();
|
||||||
let address = arg.address.unwrap_or(DEFAULT_ADDR.to_string());
|
let mut stats = match arg.stats {
|
||||||
let mut file = arg.file.map(|path| {
|
true => Some(Stats::new()),
|
||||||
|
false => None,
|
||||||
|
};
|
||||||
|
let mut output = arg.output.map(|path| {
|
||||||
//TODO automatically add file extension
|
//TODO automatically add file extension
|
||||||
let file_out = File::create(path).expect("Error opening file for writing");
|
let file_out = File::create(path).expect("Error opening file for writing");
|
||||||
let header = PcapHeader {
|
let header = PcapHeader {
|
||||||
@@ -91,268 +114,76 @@ fn main() -> Result<()> {
|
|||||||
};
|
};
|
||||||
PcapWriter::with_header(file_out, header).expect("Error writing file")
|
PcapWriter::with_header(file_out, header).expect("Error writing file")
|
||||||
});
|
});
|
||||||
//println!("file: {:?}", arg.file);
|
|
||||||
//println!("address: {}", address);
|
|
||||||
|
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
let stream = TcpStream::connect(address)?;
|
match arg.input.as_str() {
|
||||||
|
"-" => {
|
||||||
let q = tx.clone();
|
let file = io::BufReader::new(io::stdin());
|
||||||
|
thread::spawn(move || tnc_loop(tx, file).unwrap());
|
||||||
|
}
|
||||||
|
path if Path::new(path).exists() => {
|
||||||
|
let file = File::open(path)?;
|
||||||
|
let is_tty = file.is_terminal();
|
||||||
|
let mut filebuf = io::BufReader::new(file);
|
||||||
|
if !is_tty && has_pcap_magic(&mut filebuf) {
|
||||||
|
thread::spawn(move || pcap_loop(tx, filebuf).unwrap());
|
||||||
|
} else {
|
||||||
|
thread::spawn(move || tnc_loop(tx, filebuf).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addr => {
|
||||||
|
let stream = TcpStream::connect(addr)?;
|
||||||
let strm = stream.try_clone()?;
|
let strm = stream.try_clone()?;
|
||||||
thread::spawn(move || tnc_loop(q, strm).unwrap());
|
thread::spawn(move || tnc_loop(tx, strm).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while let Ok(pkt) = rx.recv() {
|
while let Ok(pkt) = rx.recv() {
|
||||||
//let time = SystemTime::now();
|
if let Ok(frame) = Ax25Frame::from_bytes(&pkt.data[1..]) {
|
||||||
//let timestamp = time.duration_since(SystemTime::UNIX_EPOCH);
|
if arg.verbose {
|
||||||
|
frame.print_long();
|
||||||
|
} else {
|
||||||
|
frame.print_short();
|
||||||
|
}
|
||||||
|
if let Some(stats) = stats.as_mut() {
|
||||||
|
stats.record(&frame);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
for byte in pkt.data.iter() {
|
for byte in pkt.data.iter() {
|
||||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
|
||||||
if *byte >= 32 && *byte <= 126 {
|
if *byte >= 32 && *byte <= 126 {
|
||||||
s.push((*byte).into());
|
s.push((*byte).into());
|
||||||
} else {
|
} else {
|
||||||
s.push_str(&format!("\\{:x?} ", *byte));
|
s.push_str(&format!("\\{:x?} ", *byte));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if arg.verbose {
|
|
||||||
if let Ok(frame) = Ax25Frame::from_bytes(&pkt.data[1..]) {
|
|
||||||
frame.print_long();
|
|
||||||
} else {
|
|
||||||
//TODO print time stamp
|
//TODO print time stamp
|
||||||
println!("{}", s);
|
println!("{}", s);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if let Ok(frame) = Ax25Frame::from_bytes(&pkt.data[1..]) {
|
if let Some(ref mut writer) = output {
|
||||||
frame.print_short();
|
let pcpkt = PcapPacket::new_owned(pkt.timestamp, pkt.orig_len, pkt.data);
|
||||||
} else {
|
writer.write_packet(&pcpkt).unwrap();
|
||||||
//TODO print time stamp
|
|
||||||
println!("{}", s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref mut writer) = file {
|
if let Some(stats) = stats.take() {
|
||||||
let pkt = PcapPacket::new_owned(pkt.timestamp, pkt.orig_len, pkt.data);
|
stats.dump();
|
||||||
writer.write_packet(&pkt).unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn has_pcap_magic(filebuf: &mut io::BufReader<File>) -> bool {
|
||||||
trait PrintData {
|
const PCAP_MAGIC_MS: u32 = 0xA1B2C3D4;
|
||||||
fn type_str_short(&self) -> &'static str;
|
const PCAP_MAGIC_NS: u32 = 0xA1B23C4D;
|
||||||
fn print_short(&self);
|
match io::BufRead::fill_buf(filebuf) {
|
||||||
fn print_long(&self);
|
Ok(buf) if buf.len() >= 4 => {
|
||||||
}
|
let mut tmp = [0_u8; 4];
|
||||||
impl PrintData for Ax25Frame {
|
tmp.copy_from_slice(&buf[..4]);
|
||||||
fn type_str_short(&self) -> &'static str {
|
let be = u32::from_be_bytes(tmp);
|
||||||
match self.content {
|
let le = u32::from_le_bytes(tmp);
|
||||||
FrameContent::Information(_) => "Info",
|
be == PCAP_MAGIC_MS || be == PCAP_MAGIC_NS || le == PCAP_MAGIC_MS || le == PCAP_MAGIC_NS
|
||||||
FrameContent::ReceiveReady(_) => "RecvRdy",
|
|
||||||
FrameContent::ReceiveNotReady(_) => "RecvNotRdy",
|
|
||||||
FrameContent::Reject(_) => "Reject",
|
|
||||||
FrameContent::SetAsynchronousBalancedMode(_) => "AsyncBalMode", //12 long
|
|
||||||
FrameContent::Disconnect(_) => "Disconnect",
|
|
||||||
FrameContent::DisconnectedMode(_) => "DisconMode",
|
|
||||||
FrameContent::UnnumberedAcknowledge(_) => "UnNumAck",
|
|
||||||
FrameContent::FrameReject(_) => "FrameReject",
|
|
||||||
FrameContent::UnnumberedInformation(_) => "UnNumInfo",
|
|
||||||
FrameContent::UnknownContent(_) => "Unknown",
|
|
||||||
}
|
}
|
||||||
}
|
_ => false,
|
||||||
fn print_short(&self) {
|
|
||||||
//Format: "[source] -> [dest] via [..] [CMD/RSP/NIL] [frameType]: [DATA str rep]"
|
|
||||||
let src = self.source.to_string();
|
|
||||||
let dst = self.destination.to_string();
|
|
||||||
//let via: String = self.route.iter().map(|re| format!(" {} ", re.repeater.to_string())).collect(); //todo
|
|
||||||
let rsp = match self.command_or_response {
|
|
||||||
Some(CommandResponse::Command) => "CMD",
|
|
||||||
Some(CommandResponse::Response) => "RSP",
|
|
||||||
None => "NIL",
|
|
||||||
};
|
|
||||||
let ftype = self.type_str_short();
|
|
||||||
let mut routed = false;
|
|
||||||
for route in self.route.iter() {
|
|
||||||
if route.has_repeated {
|
|
||||||
routed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let arrow_shaft = if routed { "=" } else { "-" };
|
|
||||||
|
|
||||||
//let mut rawdata;
|
|
||||||
let (rawdata, finfo) = match &self.content {
|
|
||||||
FrameContent::Information(_i) => (
|
|
||||||
_i.info.clone(),
|
|
||||||
format!(
|
|
||||||
"pid: {:?}, seq: {}/{}, info: ",
|
|
||||||
_i.pid, _i.send_sequence, _i.receive_sequence
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FrameContent::ReceiveReady(rr) => (
|
|
||||||
vec![],
|
|
||||||
format!(
|
|
||||||
"seq: {}, poll/final: {}",
|
|
||||||
rr.receive_sequence, rr.poll_or_final
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FrameContent::ReceiveNotReady(rnr) => (
|
|
||||||
vec![],
|
|
||||||
format!(
|
|
||||||
"seq: {}, poll/final: {}",
|
|
||||||
rnr.receive_sequence, rnr.poll_or_final
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FrameContent::Reject(rnr) => (
|
|
||||||
vec![],
|
|
||||||
format!(
|
|
||||||
"seq: {}, poll/final: {}",
|
|
||||||
rnr.receive_sequence, rnr.poll_or_final
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FrameContent::SetAsynchronousBalancedMode(abm) => {
|
|
||||||
(vec![], format!("poll: {}", abm.poll))
|
|
||||||
}
|
|
||||||
FrameContent::Disconnect(abm) => (vec![], format!("poll: {}", abm.poll)),
|
|
||||||
FrameContent::DisconnectedMode(abm) => {
|
|
||||||
(vec![], format!("final bit: {}", abm.final_bit))
|
|
||||||
}
|
|
||||||
FrameContent::UnnumberedAcknowledge(abm) => {
|
|
||||||
(vec![], format!("final bit: {}", abm.final_bit))
|
|
||||||
}
|
|
||||||
FrameContent::FrameReject(fr) => (
|
|
||||||
vec![],
|
|
||||||
format!(
|
|
||||||
"[{}{}{}{}], seq: {}/{}, raw: {}, cmdRsp: {}, final: {}",
|
|
||||||
if fr.z { "Z" } else { "" },
|
|
||||||
if fr.y { "Y" } else { "" },
|
|
||||||
if fr.x { "X" } else { "" },
|
|
||||||
if fr.w { "W" } else { "" },
|
|
||||||
fr.send_sequence,
|
|
||||||
fr.receive_sequence,
|
|
||||||
fr.rejected_control_field_raw,
|
|
||||||
match fr.command_response {
|
|
||||||
CommandResponse::Command => "CMD",
|
|
||||||
CommandResponse::Response => "RSP",
|
|
||||||
},
|
|
||||||
fr.final_bit
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FrameContent::UnnumberedInformation(ui) => {
|
|
||||||
(ui.info.clone(), format!("pid: {:?} info:", ui.pid))
|
|
||||||
}
|
|
||||||
FrameContent::UnknownContent(ukn) => (ukn.raw.clone(), format!("")),
|
|
||||||
};
|
|
||||||
let mut data = String::new();
|
|
||||||
for byte in rawdata.iter() {
|
|
||||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
|
||||||
if *byte >= 32 && *byte <= 126 {
|
|
||||||
data.push((*byte).into());
|
|
||||||
} else {
|
|
||||||
data.push_str(&format!("\\{:x?} ", *byte));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!(
|
|
||||||
"AX.25 {:12} {} {:9} {}> {:9} {}",
|
|
||||||
ftype,
|
|
||||||
rsp,
|
|
||||||
src,
|
|
||||||
arrow_shaft,
|
|
||||||
dst,
|
|
||||||
if !data.is_empty() { data } else { finfo }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
fn print_long(&self) {
|
|
||||||
//Format: "[source] -> [dest] via [..] [CMD/RSP/NIL] [frameType]: [DATA str rep]"
|
|
||||||
let src = self.source.to_string();
|
|
||||||
let dst = self.destination.to_string();
|
|
||||||
let via: String = self
|
|
||||||
.route
|
|
||||||
.iter()
|
|
||||||
.map(|re| format!(" {} ", re.repeater.to_string()))
|
|
||||||
.collect(); //todo
|
|
||||||
let rsp = match self.command_or_response {
|
|
||||||
Some(CommandResponse::Command) => "CMD",
|
|
||||||
Some(CommandResponse::Response) => "RSP",
|
|
||||||
None => "NIL",
|
|
||||||
};
|
|
||||||
let ftype = self.type_str_short();
|
|
||||||
|
|
||||||
//let mut rawdata;
|
|
||||||
let (rawdata, finfo) = match &self.content {
|
|
||||||
FrameContent::Information(_i) => (
|
|
||||||
_i.info.clone(),
|
|
||||||
format!(
|
|
||||||
"pid: {:?}, seq: {}/{}, info: ",
|
|
||||||
_i.pid, _i.send_sequence, _i.receive_sequence
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FrameContent::ReceiveReady(rr) => (
|
|
||||||
vec![],
|
|
||||||
format!(
|
|
||||||
"seq: {}, poll/final: {}",
|
|
||||||
rr.receive_sequence, rr.poll_or_final
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FrameContent::ReceiveNotReady(rnr) => (
|
|
||||||
vec![],
|
|
||||||
format!(
|
|
||||||
"seq: {}, poll/final: {}",
|
|
||||||
rnr.receive_sequence, rnr.poll_or_final
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FrameContent::Reject(rnr) => (
|
|
||||||
vec![],
|
|
||||||
format!(
|
|
||||||
"seq: {}, poll/final: {}",
|
|
||||||
rnr.receive_sequence, rnr.poll_or_final
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FrameContent::SetAsynchronousBalancedMode(abm) => {
|
|
||||||
(vec![], format!("poll: {}", abm.poll))
|
|
||||||
}
|
|
||||||
FrameContent::Disconnect(abm) => (vec![], format!("poll: {}", abm.poll)),
|
|
||||||
FrameContent::DisconnectedMode(abm) => {
|
|
||||||
(vec![], format!("final bit: {}", abm.final_bit))
|
|
||||||
}
|
|
||||||
FrameContent::UnnumberedAcknowledge(abm) => {
|
|
||||||
(vec![], format!("final bit: {}", abm.final_bit))
|
|
||||||
}
|
|
||||||
FrameContent::FrameReject(fr) => (
|
|
||||||
vec![],
|
|
||||||
format!(
|
|
||||||
"[{}{}{}{}], seq: {}/{}, raw: {}, cmdRsp: {}, final: {}",
|
|
||||||
if fr.z { "Z" } else { "" },
|
|
||||||
if fr.y { "Y" } else { "" },
|
|
||||||
if fr.x { "X" } else { "" },
|
|
||||||
if fr.w { "W" } else { "" },
|
|
||||||
fr.send_sequence,
|
|
||||||
fr.receive_sequence,
|
|
||||||
fr.rejected_control_field_raw,
|
|
||||||
match fr.command_response {
|
|
||||||
CommandResponse::Command => "CMD",
|
|
||||||
CommandResponse::Response => "RSP",
|
|
||||||
},
|
|
||||||
fr.final_bit
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FrameContent::UnnumberedInformation(ui) => {
|
|
||||||
(ui.info.clone(), format!("pid: {:?} info:", ui.pid))
|
|
||||||
}
|
|
||||||
FrameContent::UnknownContent(ukn) => (ukn.raw.clone(), format!("")),
|
|
||||||
};
|
|
||||||
let mut data = String::new();
|
|
||||||
for byte in rawdata.iter() {
|
|
||||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
|
||||||
if *byte >= 32 && *byte <= 126 {
|
|
||||||
data.push((*byte).into());
|
|
||||||
} else {
|
|
||||||
data.push_str(&format!("\\{:x?} ", *byte));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!(
|
|
||||||
"AX.25 {:12} {} {:9} -> {:9} via [{}] {} {}",
|
|
||||||
ftype, rsp, src, dst, via, finfo, data
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
218
src/print_frame.rs
Normal file
218
src/print_frame.rs
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
use ax25::frame::{Ax25Frame, CommandResponse, FrameContent};
|
||||||
|
pub trait PrintFrame {
|
||||||
|
fn type_str_short(&self) -> &'static str;
|
||||||
|
fn print_short(&self);
|
||||||
|
fn print_long(&self);
|
||||||
|
}
|
||||||
|
impl PrintFrame for Ax25Frame {
|
||||||
|
fn type_str_short(&self) -> &'static str {
|
||||||
|
match self.content {
|
||||||
|
FrameContent::Information(_) => "Info",
|
||||||
|
FrameContent::ReceiveReady(_) => "RecvRdy",
|
||||||
|
FrameContent::ReceiveNotReady(_) => "RecvNotRdy",
|
||||||
|
FrameContent::Reject(_) => "Reject",
|
||||||
|
FrameContent::SetAsynchronousBalancedMode(_) => "AsyncBalMode", //12 long
|
||||||
|
FrameContent::Disconnect(_) => "Disconnect",
|
||||||
|
FrameContent::DisconnectedMode(_) => "DisconMode",
|
||||||
|
FrameContent::UnnumberedAcknowledge(_) => "UnNumAck",
|
||||||
|
FrameContent::FrameReject(_) => "FrameReject",
|
||||||
|
FrameContent::UnnumberedInformation(_) => "UnNumInfo",
|
||||||
|
FrameContent::UnknownContent(_) => "Unknown",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn print_short(&self) {
|
||||||
|
//Format: "[source] -> [dest] via [..] [CMD/RSP/NIL] [frameType]: [DATA str rep]"
|
||||||
|
let src = self.source.to_string();
|
||||||
|
let dst = self.destination.to_string();
|
||||||
|
//let via: String = self.route.iter().map(|re| format!(" {} ", re.repeater.to_string())).collect(); //todo
|
||||||
|
let rsp = match self.command_or_response {
|
||||||
|
Some(CommandResponse::Command) => "CMD",
|
||||||
|
Some(CommandResponse::Response) => "RSP",
|
||||||
|
None => "NIL",
|
||||||
|
};
|
||||||
|
let ftype = self.type_str_short();
|
||||||
|
let mut routed = false;
|
||||||
|
for route in self.route.iter() {
|
||||||
|
if route.has_repeated {
|
||||||
|
routed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let arrow_shaft = if routed { "=" } else { "-" };
|
||||||
|
|
||||||
|
//let mut rawdata;
|
||||||
|
let (rawdata, finfo) = match &self.content {
|
||||||
|
FrameContent::Information(_i) => (
|
||||||
|
_i.info.clone(),
|
||||||
|
format!(
|
||||||
|
"pid: {:?}, seq: {}/{}, info: ",
|
||||||
|
_i.pid, _i.send_sequence, _i.receive_sequence
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FrameContent::ReceiveReady(rr) => (
|
||||||
|
vec![],
|
||||||
|
format!(
|
||||||
|
"seq: {}, poll/final: {}",
|
||||||
|
rr.receive_sequence, rr.poll_or_final
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FrameContent::ReceiveNotReady(rnr) => (
|
||||||
|
vec![],
|
||||||
|
format!(
|
||||||
|
"seq: {}, poll/final: {}",
|
||||||
|
rnr.receive_sequence, rnr.poll_or_final
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FrameContent::Reject(rnr) => (
|
||||||
|
vec![],
|
||||||
|
format!(
|
||||||
|
"seq: {}, poll/final: {}",
|
||||||
|
rnr.receive_sequence, rnr.poll_or_final
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FrameContent::SetAsynchronousBalancedMode(abm) => {
|
||||||
|
(vec![], format!("poll: {}", abm.poll))
|
||||||
|
}
|
||||||
|
FrameContent::Disconnect(abm) => (vec![], format!("poll: {}", abm.poll)),
|
||||||
|
FrameContent::DisconnectedMode(abm) => {
|
||||||
|
(vec![], format!("final bit: {}", abm.final_bit))
|
||||||
|
}
|
||||||
|
FrameContent::UnnumberedAcknowledge(abm) => {
|
||||||
|
(vec![], format!("final bit: {}", abm.final_bit))
|
||||||
|
}
|
||||||
|
FrameContent::FrameReject(fr) => (
|
||||||
|
vec![],
|
||||||
|
format!(
|
||||||
|
"[{}{}{}{}], seq: {}/{}, raw: {}, cmdRsp: {}, final: {}",
|
||||||
|
if fr.z { "Z" } else { "" },
|
||||||
|
if fr.y { "Y" } else { "" },
|
||||||
|
if fr.x { "X" } else { "" },
|
||||||
|
if fr.w { "W" } else { "" },
|
||||||
|
fr.send_sequence,
|
||||||
|
fr.receive_sequence,
|
||||||
|
fr.rejected_control_field_raw,
|
||||||
|
match fr.command_response {
|
||||||
|
CommandResponse::Command => "CMD",
|
||||||
|
CommandResponse::Response => "RSP",
|
||||||
|
},
|
||||||
|
fr.final_bit
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FrameContent::UnnumberedInformation(ui) => {
|
||||||
|
(ui.info.clone(), format!("pid: {:?} info:", ui.pid))
|
||||||
|
}
|
||||||
|
FrameContent::UnknownContent(ukn) => (ukn.raw.clone(), format!("")),
|
||||||
|
};
|
||||||
|
let mut data = String::new();
|
||||||
|
for byte in rawdata.iter() {
|
||||||
|
//if byte >= ' '.into() && byte <= '~'.into() {
|
||||||
|
if *byte >= 32 && *byte <= 126 {
|
||||||
|
data.push((*byte).into());
|
||||||
|
} else {
|
||||||
|
data.push_str(&format!("\\{:x?} ", *byte));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"AX.25 {:12} {} {:9} {}> {:9} {}",
|
||||||
|
ftype,
|
||||||
|
rsp,
|
||||||
|
src,
|
||||||
|
arrow_shaft,
|
||||||
|
dst,
|
||||||
|
if !data.is_empty() { data } else { finfo }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn print_long(&self) {
|
||||||
|
//Format: "[source] -> [dest] via [..] [CMD/RSP/NIL] [frameType]: [DATA str rep]"
|
||||||
|
let src = self.source.to_string();
|
||||||
|
let dst = self.destination.to_string();
|
||||||
|
let via: String = self
|
||||||
|
.route
|
||||||
|
.iter()
|
||||||
|
.map(|re| format!(" {} ", re.repeater.to_string()))
|
||||||
|
.collect(); //todo
|
||||||
|
let rsp = match self.command_or_response {
|
||||||
|
Some(CommandResponse::Command) => "CMD",
|
||||||
|
Some(CommandResponse::Response) => "RSP",
|
||||||
|
None => "NIL",
|
||||||
|
};
|
||||||
|
let ftype = self.type_str_short();
|
||||||
|
|
||||||
|
//let mut rawdata;
|
||||||
|
let (rawdata, finfo) = match &self.content {
|
||||||
|
FrameContent::Information(_i) => (
|
||||||
|
_i.info.clone(),
|
||||||
|
format!(
|
||||||
|
"pid: {:?}, seq: {}/{}, info: ",
|
||||||
|
_i.pid, _i.send_sequence, _i.receive_sequence
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FrameContent::ReceiveReady(rr) => (
|
||||||
|
vec![],
|
||||||
|
format!(
|
||||||
|
"seq: {}, poll/final: {}",
|
||||||
|
rr.receive_sequence, rr.poll_or_final
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FrameContent::ReceiveNotReady(rnr) => (
|
||||||
|
vec![],
|
||||||
|
format!(
|
||||||
|
"seq: {}, poll/final: {}",
|
||||||
|
rnr.receive_sequence, rnr.poll_or_final
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FrameContent::Reject(rnr) => (
|
||||||
|
vec![],
|
||||||
|
format!(
|
||||||
|
"seq: {}, poll/final: {}",
|
||||||
|
rnr.receive_sequence, rnr.poll_or_final
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FrameContent::SetAsynchronousBalancedMode(abm) => {
|
||||||
|
(vec![], format!("poll: {}", abm.poll))
|
||||||
|
}
|
||||||
|
FrameContent::Disconnect(abm) => (vec![], format!("poll: {}", abm.poll)),
|
||||||
|
FrameContent::DisconnectedMode(abm) => {
|
||||||
|
(vec![], format!("final bit: {}", abm.final_bit))
|
||||||
|
}
|
||||||
|
FrameContent::UnnumberedAcknowledge(abm) => {
|
||||||
|
(vec![], format!("final bit: {}", abm.final_bit))
|
||||||
|
}
|
||||||
|
FrameContent::FrameReject(fr) => (
|
||||||
|
vec![],
|
||||||
|
format!(
|
||||||
|
"[{}{}{}{}], seq: {}/{}, raw: {}, cmdRsp: {}, final: {}",
|
||||||
|
if fr.z { "Z" } else { "" },
|
||||||
|
if fr.y { "Y" } else { "" },
|
||||||
|
if fr.x { "X" } else { "" },
|
||||||
|
if fr.w { "W" } else { "" },
|
||||||
|
fr.send_sequence,
|
||||||
|
fr.receive_sequence,
|
||||||
|
fr.rejected_control_field_raw,
|
||||||
|
match fr.command_response {
|
||||||
|
CommandResponse::Command => "CMD",
|
||||||
|
CommandResponse::Response => "RSP",
|
||||||
|
},
|
||||||
|
fr.final_bit
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FrameContent::UnnumberedInformation(ui) => {
|
||||||
|
(ui.info.clone(), format!("pid: {:?} info:", ui.pid))
|
||||||
|
}
|
||||||
|
FrameContent::UnknownContent(ukn) => (ukn.raw.clone(), format!("")),
|
||||||
|
};
|
||||||
|
let mut data = String::new();
|
||||||
|
for byte in rawdata.iter() {
|
||||||
|
//if byte >= ' '.into() && byte <= '~'.into() {
|
||||||
|
if *byte >= 32 && *byte <= 126 {
|
||||||
|
data.push((*byte).into());
|
||||||
|
} else {
|
||||||
|
data.push_str(&format!("\\{:x?} ", *byte));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"AX.25 {:12} {} {:9} -> {:9} via [{}] {} {}",
|
||||||
|
ftype, rsp, src, dst, via, finfo, data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/stats.rs
Normal file
85
src/stats.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use ax25::frame::{Ax25Frame, FrameContent};
|
||||||
|
|
||||||
|
pub struct Stats {
|
||||||
|
frame_types: [usize; FrameContent::MAX],
|
||||||
|
dests: HashMap<String, usize>,
|
||||||
|
srcs: HashMap<String, usize>,
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user