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 = parse_associd(words.next());
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| {
let mut out = String::new();
out.push_str(&format!(
"associd={} status={:04x} 1 event, {},\n",
associd,
pv.status,
pv.get("srcaddr").unwrap_or("unknown"),
));
let sel = pv
.get("srcaddr")
.map(|s| {
format!(
"associd={} status={:04x} 1 event, {}\n",
associd, pv.status, s
)
})
.unwrap_or_default();
let mut out = sel;
let keys = [
"srcaddr",
"stratum",
"offset",
"delay",
"dispersion",
"jitter",
"hpoll",
"ppoll",
"reach",
"flash",
"leap",
"refid",
"reftime",
"hmode",
"pmode",
"precision",
];
for key in &keys {
if let Some(val) = pv.get(key) {
out.push_str(&format!("{}={}, ", key, val));
}
}
out.push('\n');
out
})
.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>) -> u16 {
let arg = match arg {
Some(a) => a,
None => return 0,
};
if let Some(val) = arg.strip_prefix("associd=") {
val.parse().unwrap_or(0)
} else {
arg.parse().unwrap_or(0)
}
}