From dc5196404fc23fb26a32585ecb228c5d5e85dbf7 Mon Sep 17 00:00:00 2001 From: Lucas Schumacher Date: Tue, 24 Feb 2026 21:47:19 -0500 Subject: [PATCH] Support input from files and stdin --- examples/raw2pcap.rs | 251 ------------------------------------------- src/main.rs | 41 ++++--- 2 files changed, 25 insertions(+), 267 deletions(-) delete mode 100644 examples/raw2pcap.rs diff --git a/examples/raw2pcap.rs b/examples/raw2pcap.rs deleted file mode 100644 index 4c9f0dc..0000000 --- a/examples/raw2pcap.rs +++ /dev/null @@ -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(()) -} diff --git a/src/main.rs b/src/main.rs index 39ebe6b..11b9b14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ use clap::Parser; use std::{ fs::File, - path::PathBuf, + io, + path::{Path, PathBuf}, sync::mpsc::{channel, Sender}, thread, }; @@ -23,7 +24,7 @@ struct KissPkt { data: Vec, } -fn tnc_loop(tx: Sender, mut stream: TcpStream) -> Result<()> { +fn tnc_loop(tx: Sender, mut stream: T) -> Result<()> { let mut buffer = [0_u8; 4096]; let mut frame = vec![]; let mut escaped = false; @@ -33,6 +34,9 @@ fn tnc_loop(tx: Sender, mut stream: TcpStream) -> Result<()> { let mut orig_len = 0; loop { let n = stream.read(&mut buffer)?; + if n == 0 { + return Ok(()); + } for byte in buffer[..n].iter() { orig_len += 1; if escaped { @@ -66,23 +70,20 @@ fn tnc_loop(tx: Sender, mut stream: TcpStream) -> Result<()> { } } } - //Ok(()) } -const DEFAULT_ADDR: &str = "127.0.0.1:8001"; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Arg { #[arg(short, long, default_value_t = false)] verbose: bool, - #[arg(short, long)] - file: Option, - address: Option, + #[arg(short, long, value_name = "FILE")] + output: Option, + input: String, } fn main() -> Result<()> { let arg = Arg::parse(); - let address = arg.address.unwrap_or(DEFAULT_ADDR.to_string()); - let mut file = arg.file.map(|path| { + let mut file = arg.output.map(|path| { //TODO automatically add file extension let file_out = File::create(path).expect("Error opening file for writing"); let header = PcapHeader { @@ -91,15 +92,23 @@ fn main() -> Result<()> { }; PcapWriter::with_header(file_out, header).expect("Error writing file") }); - //println!("file: {:?}", arg.file); - //println!("address: {}", address); let (tx, rx) = channel(); - let stream = TcpStream::connect(address)?; - - let q = tx.clone(); - let strm = stream.try_clone()?; - thread::spawn(move || tnc_loop(q, strm).unwrap()); + match arg.input.as_str() { + "-" => { + let file = io::BufReader::new(io::stdin()); + thread::spawn(move || tnc_loop(tx, file).unwrap()); + } + s if Path::new(s).exists() => { + let file = io::BufReader::new(File::open(s)?); + thread::spawn(move || tnc_loop(tx, file).unwrap()); + } + addr => { + let stream = TcpStream::connect(addr)?; + let strm = stream.try_clone()?; + thread::spawn(move || tnc_loop(tx, strm).unwrap()); + } + } while let Ok(pkt) = rx.recv() { //let time = SystemTime::now();