use std::io::Write;
use anyhow::Result;
use clap::Parser;
use log::info;
use tokio::io::AsyncReadExt;
use rax25::{
Addr,
r#async::{ConnectionBuilder, connect_kiss_endpoint},
parse_duration,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
#[derive(Parser, Debug)]
struct Opt {
#[allow(clippy::doc_markdown)]
#[clap(short = 'p', default_value = "serial:///dev/null")]
port: String,
#[clap(short = 's')]
src: String,
#[clap(short = 'r')]
cr: bool,
#[clap(short = 'e')]
ext: bool,
#[clap(short = 'v', default_value = "info")]
v: LogLevel,
#[clap(long)]
capture: Option<std::path::PathBuf>,
#[clap(long, value_parser = parse_duration)]
srt: Option<std::time::Duration>,
#[clap(long, value_parser = parse_duration)]
t3v: Option<std::time::Duration>,
#[clap(long)]
mtu: Option<usize>,
#[clap(long, value_enum, value_delimiter = ',')]
experiments: Vec<rax25::Experiment>,
#[clap()]
dst: String,
}
#[tokio::main]
async fn main() -> Result<()> {
let opt = Opt::parse();
stderrlog::new()
.module(module_path!())
.module("rax25")
.verbosity(opt.v as usize)
.show_module_names(true)
.init()
.unwrap();
let port = connect_kiss_endpoint(&opt.port).await?;
let mut stdin = tokio::io::stdin();
let builder = {
let mut builder = ConnectionBuilder::new(Addr::new(&opt.src)?, port)?;
if opt.ext {
builder = builder.extended(Some(opt.ext));
}
if let Some(capture) = opt.capture {
builder = builder.capture(capture);
}
if let Some(v) = opt.srt {
builder = builder.srt_default(v);
}
if let Some(v) = opt.t3v {
builder = builder.t3v(v);
}
if let Some(v) = opt.mtu {
builder = builder.mtu(v);
}
for ex in &opt.experiments {
builder = builder.enable_experiment(*ex);
}
builder
};
let st = std::time::Instant::now();
let mut client = builder.connect(Addr::new(&opt.dst)?).await?;
info!("Connected after {:?}", st.elapsed());
let mut sigint = {
use tokio::signal::unix::{SignalKind, signal};
signal(SignalKind::interrupt())?
};
loop {
let mut buf = [0; 1024];
tokio::select! {
_ = sigint.recv() => {
eprintln!("Sigint received");
break;
},
res = stdin.read(&mut buf) => {
let res = res?;
if res == 0 {
info!("Got EOF from stdin");
break;
}
let buf = &buf[..res];
if buf == b"exit\n" {
info!("Got 'exit' from user");
break;
}
let buf: Vec<_> = if opt.cr {
buf.iter().map(|&b| if b == b'\n' { b'\r' } else {b}).collect()
} else {
buf.to_vec()
};
client.write(&buf).await?;
},
data = client.read() => {
let data = data?;
if data.is_empty() {
info!("Got EOF from client");
break;
}
let s = match String::from_utf8(data.clone()) {
Ok(s) => s,
Err(_) => String::from_utf8(data.iter().map(|&b| b & 0x7F).collect())?,
};
let s = if opt.cr { s.replace('\r', "\n") } else {s};
print!("{s}");
std::io::stdout().flush()?;
},
}
}
info!("End of main loop");
client.disconnect().await?;
info!("Disconnected");
Ok(())
}