rax25 0.1.22

AX.25 connected mode implementation
Documentation
use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use anyhow::Result;
use clap::Parser;

use rax25::sync::Client;
use rax25::{Addr, Kiss};

#[derive(Parser, Debug)]
struct Opt {
    #[clap(short = 'p', default_value = "/dev/null")]
    port: String,

    #[clap(short = 's')]
    src: String,

    #[clap(short = 'r')]
    cr: bool,

    #[clap(short = 'e')]
    ext: bool,

    #[clap(short = 'v', default_value = "0")]
    v: usize,

    #[clap()]
    dst: String,
}

fn main() -> Result<()> {
    let opt = Opt::parse();
    let done = Arc::new(AtomicBool::new(false));
    stderrlog::new()
        .module("rax25")
        .verbosity(opt.v)
        .init()
        .unwrap();
    let k = Kiss::new(&opt.port)?;
    let mut c = Client::new(Addr::new(&opt.src)?, Box::new(k));

    let d = done.clone();
    ctrlc::set_handler(move || {
        println!("Received SIGINT signal, shutting down...");
        d.store(true, Ordering::SeqCst);
    })
    .expect("Error setting Ctrl-C handler");

    let (tx, rx) = std::sync::mpsc::channel();

    let d = done.clone();
    let cr = opt.cr;
    std::thread::spawn(move || {
        use std::io::BufRead;
        while !d.load(Ordering::SeqCst) {
            let stdin = std::io::stdin();
            let mut iterator = stdin.lock().lines();
            if let Some(line) = iterator.next() {
                if cr {
                    tx.send(line.and_then(|s| Ok(s.trim_end().to_owned() + "\r")))
                        .unwrap();
                } else {
                    tx.send(line).unwrap();
                }
            }
        }
    });

    eprintln!("==== CONNECTING");
    c.connect(&Addr::new(&opt.dst)?, opt.ext)?;
    eprintln!("==== Connected to {}", opt.dst);
    //c.write("echo hello world".as_bytes())?;
    while !done.load(Ordering::SeqCst) && !c.eof() {
        if let Ok(Some(data)) = c.read_until(done.clone()) {
            // eprintln!("====> {data:?}");
            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()?;
        }
        match rx.recv_timeout(std::time::Duration::from_secs(0)) {
            Ok(Ok(line)) => {
                if line == "exit" {
                    done.store(true, Ordering::SeqCst);
                    break;
                }
                c.write(&line.as_bytes())?;
            }
            Ok(Err(e)) => eprintln!("Error reading line: {}", e),
            Err(_) => {}
        };
    }
    done.store(true, Ordering::SeqCst);
    Ok(())
}