use anyhow::Result;
use clap::Parser;
use tokio::io::{AsyncBufReadExt, BufReader, stdin};
use tracing::{info, instrument, warn};
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
use veilnet::{Connection, DHTAddr, connection::Veilid, datagram::socket::Socket};
#[derive(Parser)]
#[command(name = "vcat")]
#[command(about = "A netcat-like utility for Veilid networks")]
#[command(version)]
struct Cli {
#[arg(short, long)]
listen: Option<u16>,
#[arg(short, long)]
verbose: bool,
address: Option<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
initialize_stdout_logging(cli.verbose);
let conn = Veilid::new().await?;
if let Some(port) = cli.listen {
listener(conn, port).await
} else if let Some(address) = cli.address {
dialer(conn, &address).await
} else {
eprintln!("Error: Must specify either -l to listen or provide an address to connect to");
std::process::exit(1);
}
}
#[instrument(skip_all)]
async fn listener<C: Connection + Clone + Send + Sync + 'static>(conn: C, port: u16) -> Result<()> {
let mut sock = Socket::new(conn, None, port).await?;
info!(addr = %sock.addr(), "listening");
loop {
match sock.recv_from().await {
Ok((addr, dgram)) => {
info!(
%addr,
data = str::from_utf8(dgram.as_slice()).unwrap_or("???")
);
continue;
}
Err(err) => {
warn!(?err, "recv_from");
}
}
}
}
#[instrument(skip_all)]
async fn dialer<C>(conn: C, addr_str: &str) -> Result<()>
where
C: Connection + Clone + Send + Sync + 'static,
{
let mut sock = Socket::new(conn, None, 0).await?;
let addr: DHTAddr = addr_str.parse()?;
let mut reader = BufReader::new(stdin()).lines();
loop {
match reader.next_line().await? {
Some(line) => {
match sock.send_to(&addr, line.into_bytes().as_slice()).await {
Ok(()) => continue,
Err(err) => {
warn!(?err, "send_to");
}
};
}
None => return Ok(()),
}
}
}
fn initialize_stdout_logging(verbose: bool) {
tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::layer()
.with_target(true)
.with_level(true)
.with_ansi(false)
.with_writer(std::io::stdout),
)
.with(env_filter(verbose))
.init();
}
fn env_filter(verbose: bool) -> EnvFilter {
if std::env::var("RUST_LOG").is_ok() {
EnvFilter::builder().from_env_lossy()
} else if verbose {
"debug".parse().unwrap()
} else {
"info".parse().unwrap()
}
}