initial ver
This commit is contained in:
358
src/main.rs
Normal file
358
src/main.rs
Normal file
@@ -0,0 +1,358 @@
|
||||
use clap::Parser;
|
||||
use std::{
|
||||
fs::File,
|
||||
path::PathBuf,
|
||||
sync::mpsc::{channel, Sender},
|
||||
thread,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use ax25::frame::{Ax25Frame, CommandResponse, FrameContent};
|
||||
use pcap_file::{
|
||||
pcap::{PcapHeader, PcapPacket, PcapWriter},
|
||||
DataLink,
|
||||
};
|
||||
use std::io::Read;
|
||||
use std::net::TcpStream;
|
||||
use std::time::Duration;
|
||||
use std::time::SystemTime;
|
||||
|
||||
struct KissPkt {
|
||||
timestamp: Duration,
|
||||
orig_len: u32,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
fn tnc_loop(tx: Sender<KissPkt>, mut stream: TcpStream) -> Result<()> {
|
||||
let mut buffer = [0_u8; 4096];
|
||||
let mut frame = vec![];
|
||||
let mut escaped = false;
|
||||
let mut timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
let mut orig_len = 0;
|
||||
loop {
|
||||
let n = stream.read(&mut buffer)?;
|
||||
for byte in buffer[..n].iter() {
|
||||
orig_len += 1;
|
||||
if escaped {
|
||||
match *byte {
|
||||
0xDC => frame.push(0xC0),
|
||||
0xDD => frame.push(0xDB),
|
||||
_ => { /* ERROR: invalid escape sequence */ }
|
||||
}
|
||||
escaped = false;
|
||||
} else {
|
||||
match *byte {
|
||||
0xDB => escaped = true,
|
||||
0xC0 => {
|
||||
//TODO send and clear frame if not empty
|
||||
if frame.len() == 0 {
|
||||
orig_len = 1;
|
||||
timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
continue;
|
||||
};
|
||||
tx.send(KissPkt {
|
||||
timestamp,
|
||||
orig_len,
|
||||
data: frame.clone(),
|
||||
})?;
|
||||
frame.clear();
|
||||
}
|
||||
b => frame.push(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//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<PathBuf>,
|
||||
address: Option<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| {
|
||||
//TODO automatically add file extension
|
||||
let file_out = File::create(path).expect("Error opening file for writing");
|
||||
let header = PcapHeader {
|
||||
datalink: DataLink::AX25_KISS,
|
||||
..Default::default()
|
||||
};
|
||||
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());
|
||||
|
||||
while let Ok(pkt) = rx.recv() {
|
||||
//let time = SystemTime::now();
|
||||
//let timestamp = time.duration_since(SystemTime::UNIX_EPOCH);
|
||||
let mut s = String::new();
|
||||
for byte in pkt.data.iter() {
|
||||
//if byte >= ' '.into() && byte <= '~'.into() {
|
||||
if *byte >= 32 && *byte <= 126 {
|
||||
s.push((*byte).into());
|
||||
} else {
|
||||
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
|
||||
println!("{}", s);
|
||||
}
|
||||
} else {
|
||||
if let Ok(frame) = Ax25Frame::from_bytes(&pkt.data[1..]) {
|
||||
frame.print_short();
|
||||
} else {
|
||||
//TODO print time stamp
|
||||
println!("{}", s);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut writer) = file {
|
||||
let pkt = PcapPacket::new_owned(pkt.timestamp, pkt.orig_len, pkt.data);
|
||||
writer.write_packet(&pkt).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 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
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user