- Upgrade donglora-client from 0.1 (monorepo path dep) to 0.2 (crates.io) - Import wire-level constants from donglora_client::protocol::* - Fix repository URL to github.com/donglora/mux-rs - Add homepage, rust-version, exclude to Cargo.toml - Fix README (remove monorepo paths, add install instructions) - Allow clippy unwrap in test module
114 lines
3.5 KiB
Rust
114 lines
3.5 KiB
Rust
//! DongLoRa USB Multiplexer — share one dongle with multiple applications.
|
|
//!
|
|
//! Owns the USB serial connection and exposes a Unix domain socket (and
|
|
//! optional TCP) that speaks the same COBS-framed protocol. Clients connect
|
|
//! and get solicited responses routed, RxPackets broadcast, and StartRx/StopRx
|
|
//! reference-counted.
|
|
|
|
mod daemon;
|
|
mod intercept;
|
|
mod session;
|
|
|
|
use clap::Parser;
|
|
use tokio_util::sync::CancellationToken;
|
|
use tracing::info;
|
|
|
|
#[derive(Parser)]
|
|
#[command(name = "donglora-mux", about = "DongLoRa USB Multiplexer")]
|
|
struct Args {
|
|
/// Serial port (auto-detect by USB VID:PID if omitted)
|
|
#[arg(short, long)]
|
|
port: Option<String>,
|
|
|
|
/// Unix socket path (default: XDG or /tmp/donglora-mux.sock)
|
|
#[arg(short, long)]
|
|
socket: Option<String>,
|
|
|
|
/// TCP listen address [HOST:]PORT (default: 0.0.0.0:5741, "none" to disable)
|
|
#[arg(short, long, default_value = "0.0.0.0:5741")]
|
|
tcp: String,
|
|
|
|
/// Enable debug logging
|
|
#[arg(short, long)]
|
|
verbose: bool,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
let args = Args::parse();
|
|
|
|
// Init tracing — daemon-style: wall-clock time, level, message
|
|
let filter = if args.verbose { "debug" } else { "info" };
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(filter)),
|
|
)
|
|
.with_target(false)
|
|
.init();
|
|
|
|
// Resolve serial port
|
|
let port = match args.port {
|
|
Some(p) => p,
|
|
None => tokio::task::spawn_blocking(|| {
|
|
donglora_client::find_port().unwrap_or_else(|| {
|
|
info!("waiting for DongLoRa device...");
|
|
donglora_client::wait_for_device()
|
|
})
|
|
})
|
|
.await
|
|
.map_err(|e| anyhow::anyhow!("device discovery task failed: {e}"))?,
|
|
};
|
|
|
|
// Resolve socket path
|
|
let socket_path = args.socket.unwrap_or_else(donglora_client::default_socket_path);
|
|
|
|
// Parse TCP address
|
|
let tcp_addr = parse_tcp_addr(&args.tcp);
|
|
|
|
// Shutdown token
|
|
let shutdown = CancellationToken::new();
|
|
|
|
// Install signal handlers
|
|
let sig_shutdown = shutdown.clone();
|
|
tokio::spawn(async move {
|
|
if let Err(e) = signal_handler(sig_shutdown).await {
|
|
tracing::error!("signal handler error: {e}");
|
|
}
|
|
});
|
|
|
|
// Run the daemon
|
|
let daemon = daemon::MuxDaemon::new(port, socket_path, tcp_addr, shutdown);
|
|
daemon.run().await
|
|
}
|
|
|
|
fn parse_tcp_addr(tcp: &str) -> Option<(String, u16)> {
|
|
if tcp.eq_ignore_ascii_case("none") {
|
|
return None;
|
|
}
|
|
if let Some((host, port_str)) = tcp.rsplit_once(':')
|
|
&& let Ok(port) = port_str.parse()
|
|
{
|
|
return Some((host.to_string(), port));
|
|
}
|
|
if let Ok(port) = tcp.parse() {
|
|
return Some(("0.0.0.0".to_string(), port));
|
|
}
|
|
None
|
|
}
|
|
|
|
async fn signal_handler(shutdown: CancellationToken) -> anyhow::Result<()> {
|
|
let mut sigint = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())
|
|
.map_err(|e| anyhow::anyhow!("failed to register SIGINT handler: {e}"))?;
|
|
let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
|
|
.map_err(|e| anyhow::anyhow!("failed to register SIGTERM handler: {e}"))?;
|
|
|
|
tokio::select! {
|
|
_ = sigint.recv() => info!("received SIGINT"),
|
|
_ = sigterm.recv() => info!("received SIGTERM"),
|
|
}
|
|
|
|
shutdown.cancel();
|
|
Ok(())
|
|
}
|