veilnet 0.4.4

Networking abstractions built on Veilid API primitives
Documentation
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 {
    /// Listen for incoming connections on port
    #[arg(short, long)]
    listen: Option<u16>,

    /// Enable verbose output
    #[arg(short, long)]
    verbose: bool,

    /// Remote address to connect to (if not listening)
    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()
    }
}