use clap::Parser;
use ntpsec_rs_core::control_client::*;
#[derive(Parser, Debug)]
#[command(name = "ntpq-rs", about = "NTP query tool", version)]
struct Cli {
#[arg(default_value = "127.0.0.1")]
host: String,
#[arg(short = 'p', long, default_value = "123")]
port: u16,
#[arg(short = 'c', long)]
command: Vec<String>,
#[arg(short = 'v', long)]
verbose: bool,
#[arg(short = 'n', long)]
numeric: bool,
#[arg(short = 'd', long)]
debug: bool,
#[arg(short = 'a', long)]
auth_key: Option<u32>,
#[arg(short = 'k', long)]
key_file: Option<String>,
#[arg(short = 't', long, default_value = "5")]
timeout: u32,
}
fn main() {
let cli = Cli::parse();
let mut client = ControlClient::new(cli.timeout, 1);
for cmd in &cli.command {
let mut words = cmd.split_whitespace();
let command = words.next().unwrap_or_default();
let result: Result<String, String> = match command {
"rv" => {
let associd = match parse_associd(words.next()) {
Ok(id) => id,
Err(e) => {
eprintln!("ERROR: {e}");
continue;
}
};
if associd == 0 {
client
.read_system_vars(&cli.host, cli.port)
.map(|sys| format_readvar(&sys))
.map_err(|e| format!("{e}"))
} else {
client
.read_peer_vars(&cli.host, cli.port, associd)
.map(|pv| format_peer_readvar(&pv))
.map_err(|e| format!("{e}"))
}
}
"associations" | "as" => client
.read_associations(&cli.host, cli.port)
.map(|assocs| format_associations(&assocs))
.map_err(|e| format!("{e}")),
"peers" | "pe" => {
let assoc_result = client.read_associations(&cli.host, cli.port);
match assoc_result {
Ok(assocs) => {
let mut rows = Vec::new();
for a in &assocs {
if !a.configured && !a.reachable {
continue;
}
match client.read_peer_vars(&cli.host, cli.port, a.associd) {
Ok(pv) => rows.push(PeerRow::from_association(&pv, a)),
Err(e) => {
rows.push(PeerRow {
tally: ' ',
remote: format!("? (error: {e})"),
refid: String::new(),
associd: a.associd,
stratum: 16,
peer_type: 'u',
when: None,
poll: 64,
reach: 0,
delay: 0.0,
offset: 0.0,
jitter: 0.0,
});
}
}
}
Ok(format_peers(&rows))
}
Err(e) => Err(format!("{e}")),
}
}
_ => Err(format!("unknown command: {command}")),
};
match result {
Ok(output) => print!("{}", output),
Err(e) => eprintln!("ERROR: {}", e),
}
}
}
fn parse_associd(arg: Option<&str>) -> Result<u16, String> {
let arg = match arg {
Some(a) => a,
None => return Ok(0),
};
let val = if let Some(stripped) = arg.strip_prefix("associd=") {
stripped
} else {
arg
};
val.parse::<u16>()
.map_err(|_| format!("invalid associd: '{val}'"))
}