252 lines
12 KiB
Rust
252 lines
12 KiB
Rust
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(())
|
|
}
|