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(()) }