#![allow(clippy::result_large_err)]
use crate::ast::*;
use crate::error::DirectiveError;
use crate::parser::lexer::{count_args, split_word};
use crate::span::Span;
use crate::values::*;
fn err<T>(_directive: &str, e: DirectiveError) -> Result<T, DirectiveError> {
Err(e)
}
fn require_exact_args(args: &str, expected: usize, span: &Span, directive: &str) -> Result<(), DirectiveError> {
let actual = count_args(args);
if actual == expected { return Ok(()); }
if actual < expected {
Err(DirectiveError::MissingArgument { directive: directive.into(), expected, got: actual, span: span.clone() })
} else {
Err(DirectiveError::TooManyArguments { directive: directive.into(), expected, got: actual, span: span.clone() })
}
}
fn parse_i32(word: &str, span: &Span, directive: &str) -> Result<i32, DirectiveError> {
word.parse::<i32>().map_err(|_| DirectiveError::InvalidValue {
directive: directive.into(), expected: "integer", got: word.to_string(), span: span.clone(),
})
}
fn parse_u32(word: &str, span: &Span, directive: &str) -> Result<u32, DirectiveError> {
word.parse::<u32>().map_err(|_| DirectiveError::InvalidValue {
directive: directive.into(), expected: "unsigned integer", got: word.to_string(), span: span.clone(),
})
}
fn parse_f64(word: &str, span: &Span, directive: &str) -> Result<f64, DirectiveError> {
word.parse::<f64>().map_err(|_| DirectiveError::InvalidValue {
directive: directive.into(), expected: "float", got: word.to_string(), span: span.clone(),
})
}
fn parse_poll_interval(word: &str, span: &Span, directive: &str) -> Result<PollInterval, DirectiveError> {
let v = parse_i32(word, span, directive)?;
PollInterval::new(v as i8).map_err(|_| DirectiveError::ValueOutOfRange {
directive: directive.into(), value: v.to_string(),
min: PollInterval::MIN.to_string(), max: PollInterval::MAX.to_string(), span: span.clone(),
})
}
fn parse_udp_port(word: &str, span: &Span, directive: &str) -> Result<UdpPort, DirectiveError> {
let v = parse_u32(word, span, directive)?;
UdpPort::new(v as u16).map_err(|_| DirectiveError::ValueOutOfRange {
directive: directive.into(), value: v.to_string(),
min: UdpPort::MIN.to_string(), max: UdpPort::MAX.to_string(), span: span.clone(),
})
}
fn parse_dscp_value(word: &str, span: &Span, directive: &str) -> Result<Dscp, DirectiveError> {
let v = parse_i32(word, span, directive)?;
Dscp::new(v as u8).map_err(|_| DirectiveError::ValueOutOfRange {
directive: directive.into(), value: v.to_string(),
min: Dscp::MIN.to_string(), max: Dscp::MAX.to_string(), span: span.clone(),
})
}
fn parse_stratum(word: &str, span: &Span, directive: &str) -> Result<Stratum, DirectiveError> {
let v = parse_i32(word, span, directive)?;
Stratum::new(v as u8).map_err(|_| DirectiveError::ValueOutOfRange {
directive: directive.into(), value: v.to_string(),
min: Stratum::MIN.to_string(), max: Stratum::MAX.to_string(), span: span.clone(),
})
}
fn parse_poll_target(word: &str, span: &Span, directive: &str) -> Result<PollTarget, DirectiveError> {
let v = parse_u32(word, span, directive)?;
PollTarget::new(v).map_err(|_| DirectiveError::ValueOutOfRange {
directive: directive.into(), value: v.to_string(),
min: PollTarget::MIN.to_string(), max: PollTarget::MAX.to_string(), span: span.clone(),
})
}
fn parse_ntp_version(word: &str, span: &Span, directive: &str) -> Result<NtpVersion, DirectiveError> {
let v = parse_i32(word, span, directive)?;
NtpVersion::new(v as u8).map_err(|_| DirectiveError::ValueOutOfRange {
directive: directive.into(), value: v.to_string(),
min: NtpVersion::MIN.to_string(), max: NtpVersion::MAX.to_string(), span: span.clone(),
})
}
fn parse_sched_priority_value(word: &str, span: &Span, directive: &str) -> Result<SchedPriority, DirectiveError> {
let v = parse_i32(word, span, directive)?;
SchedPriority::new(v as u8).map_err(|_| DirectiveError::ValueOutOfRange {
directive: directive.into(), value: v.to_string(),
min: SchedPriority::MIN.to_string(), max: SchedPriority::MAX.to_string(), span: span.clone(),
})
}
fn parse_ptp_domain_inner(word: &str, span: &Span, directive: &str) -> Result<PtpDomain, DirectiveError> {
let v = parse_i32(word, span, directive)?;
PtpDomain::new(v as u8).map_err(|_| DirectiveError::ValueOutOfRange {
directive: directive.into(), value: v.to_string(),
min: PtpDomain::MIN.to_string(), max: PtpDomain::MAX.to_string(), span: span.clone(),
})
}
fn parse_extfield(word: &str, span: &Span, directive: &str) -> Result<u32, DirectiveError> {
u32::from_str_radix(word, 16).map_err(|_| DirectiveError::InvalidValue {
directive: directive.into(), expected: "hex integer", got: word.to_string(), span: span.clone(),
})
}
fn parse_string_directive(args: &str, span: Span, name: &str, f: impl FnOnce(String) -> DirectiveKind) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, name)?;
Ok(f(args.to_string()))
}
pub fn parse_driftfile(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
let (path, rest) = split_word(args);
if path.is_empty() { return err("driftfile", DirectiveError::MissingArgument { directive: "driftfile".into(), expected: 1, got: 0, span }); }
let mut interval = None;
if !rest.is_empty() {
let (key, val) = split_word(rest);
if key.eq_ignore_ascii_case("interval") {
if val.is_empty() { return err("driftfile", DirectiveError::MissingArgument { directive: "driftfile".into(), expected: 2, got: 1, span }); }
interval = Some(parse_u32(val, &span, "driftfile")?);
let (_, extra) = split_word(split_word(rest).1);
if !extra.is_empty() { return err("driftfile", DirectiveError::TooManyArguments { directive: "driftfile".into(), expected: 2, got: count_args(args), span }); }
} else {
return err("driftfile", DirectiveError::InvalidOption { directive: "driftfile".into(), option: key.to_string(), span });
}
}
Ok(DirectiveKind::DriftFile(DriftFileConfig { path: path.to_string(), interval }))
}
pub fn parse_rtconutc(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 0, &span, "rtconutc")?;
Ok(DirectiveKind::RtcOnUtc)
}
pub fn parse_rtcsync(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 0, &span, "rtcsync")?;
Ok(DirectiveKind::RtcSync)
}
pub fn parse_lock_all(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 0, &span, "lock_all")?;
Ok(DirectiveKind::LockAll)
}
pub fn parse_manual(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 0, &span, "manual")?;
Ok(DirectiveKind::Manual)
}
pub fn parse_no_system_cert(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 0, &span, "nosystemcert")?;
Ok(DirectiveKind::NoSystemCert)
}
pub fn parse_no_client_log(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 0, &span, "noclientlog")?;
Ok(DirectiveKind::NoClientLog)
}
pub fn parse_dump_on_exit(_args: &str, _span: Span) -> Result<DirectiveKind, DirectiveError> {
Ok(DirectiveKind::DumpOnExit)
}
fn parse_int_directive<T>(args: &str, span: Span, name: &str, parse: fn(&str, &Span, &str) -> Result<T, DirectiveError>, f: impl FnOnce(T) -> DirectiveKind) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, name)?;
let val = parse(args, &span, name)?;
Ok(f(val))
}
pub fn parse_dscp(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_int_directive(args, span, "dscp", parse_dscp_value, |v| DirectiveKind::Dscp(DscpConfig { dscp: v }))
}
pub fn parse_sched_priority(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_int_directive(args, span, "sched_priority", parse_sched_priority_value, |v| DirectiveKind::SchedPriority(SchedPriorityConfig { priority: v }))
}
pub fn parse_ptp_domain(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_int_directive(args, span, "ptpdomain", parse_ptp_domain_inner, |v| DirectiveKind::PtpDomain(PtpDomainConfig { domain: v }))
}
pub fn parse_client_log_limit(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "clientloglimit")?;
let v: u64 = args.parse().map_err(|_| DirectiveError::InvalidValue { directive: "clientloglimit".into(), expected: "unsigned integer", got: args.to_string(), span: span.clone() })?;
Ok(DirectiveKind::ClientLogLimit(ClientLogLimitConfig { limit: ClientLogSize::new(v).map_err(|_| DirectiveError::ValueOutOfRange { directive: "clientloglimit".into(), value: v.to_string(), min: ClientLogSize::MIN.to_string(), max: ClientLogSize::MAX.to_string(), span: span.clone() })? }))
}
fn parse_double_directive(args: &str, span: Span, name: &str, f: impl FnOnce(f64) -> DirectiveKind) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, name)?;
let val = parse_f64(args, &span, name)?;
Ok(f(val))
}
pub fn parse_clock_precision(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "clockprecision", |v| DirectiveKind::ClockPrecision(ClockPrecisionConfig { precision: v }))
}
pub fn parse_corr_time_ratio(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "corrtimeratio", |v| DirectiveKind::CorrTimeRatio(CorrTimeRatioConfig { ratio: v }))
}
pub fn parse_max_distance(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "maxdistance", |v| DirectiveKind::MaxDistance(MaxDistanceConfig { distance: v }))
}
pub fn parse_max_jitter(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "maxjitter", |v| DirectiveKind::MaxJitter(MaxJitterConfig { jitter: v }))
}
pub fn parse_combine_limit(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "combinelimit", |v| DirectiveKind::CombineLimit(CombineLimitConfig { limit: v }))
}
pub fn parse_reselect_dist(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "reselectdist", |v| DirectiveKind::ReselectDist(ReselectDistConfig { distance: v }))
}
pub fn parse_stratum_weight(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "stratumweight", |v| DirectiveKind::StratumWeight(StratumWeightConfig { distance: v }))
}
pub fn parse_max_clock_error(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "maxclockerror", |v| DirectiveKind::MaxClockError(MaxClockErrorConfig { error_ppm: v }))
}
pub fn parse_max_drift(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "maxdrift", |v| DirectiveKind::MaxDrift(MaxDriftConfig { drift_ppm: v }))
}
pub fn parse_max_update_skew(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "maxupdateskew", |v| DirectiveKind::MaxUpdateSkew(MaxUpdateSkewConfig { skew_ppm: v }))
}
pub fn parse_max_slew_rate(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "maxslewrate", |v| DirectiveKind::MaxSlewRate(MaxSlewRateConfig { rate_ppm: v }))
}
pub fn parse_log_change(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "logchange", |v| DirectiveKind::LogChange(LogChangeConfig { threshold: v }))
}
pub fn parse_rtc_auto_trim(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "rtcautotrim", |v| DirectiveKind::RtcAutoTrim(RtcAutoTrimConfig { threshold: v }))
}
pub fn parse_hw_ts_timeout(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_double_directive(args, span, "hwtstimeout", |v| DirectiveKind::HwTsTimeout(HwTsTimeoutConfig { timeout: v }))
}
pub fn parse_authselectmode(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "authselectmode")?;
let mode = match args.to_lowercase().as_str() {
"require" => AuthSelectMode::Require,
"prefer" => AuthSelectMode::Prefer,
"mix" => AuthSelectMode::Mix,
"ignore" => AuthSelectMode::Ignore,
_ => return err("authselectmode", DirectiveError::InvalidValue { directive: "authselectmode".into(), expected: "require|prefer|mix|ignore", got: args.to_string(), span }),
};
Ok(DirectiveKind::AuthSelectMode(mode))
}
pub fn parse_leapsecmode(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "leapsecmode")?;
let mode = match args.to_lowercase().as_str() {
"system" => LeapSecMode::System,
"step" => LeapSecMode::Step,
"slew" => LeapSecMode::Slew,
"ignore" => LeapSecMode::Ignore,
_ => return err("leapsecmode", DirectiveError::InvalidValue { directive: "leapsecmode".into(), expected: "system|step|slew|ignore", got: args.to_string(), span }),
};
Ok(DirectiveKind::LeapSecMode(mode))
}
pub fn parse_fallbackdrift(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 2, &span, "fallbackdrift")?;
let (w1, w2) = split_word(args);
let min = parse_i32(w1, &span, "fallbackdrift")?;
let max = parse_i32(w2, &span, "fallbackdrift")?;
Ok(DirectiveKind::FallbackDrift(FallbackDriftConfig { min, max }))
}
pub fn parse_makestep(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 2, &span, "makestep")?;
let (w1, w2) = split_word(args);
let threshold = parse_f64(w1, &span, "makestep")?;
let limit = parse_i32(w2, &span, "makestep")?;
Ok(DirectiveKind::MakeStep(MakeStepConfig { threshold, limit }))
}
pub fn parse_maxchange(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 3, &span, "maxchange")?;
let (w1, rest) = split_word(args);
let (w2, w3) = split_word(rest);
let offset = parse_f64(w1, &span, "maxchange")?;
let start = parse_i32(w2, &span, "maxchange")?;
let ignore = parse_i32(w3, &span, "maxchange")?;
Ok(DirectiveKind::MaxChange(MaxChangeConfig { offset, start, ignore }))
}
pub fn parse_mailonchange(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 2, &span, "mailonchange")?;
let (email, threshold_str) = split_word(args);
let threshold = parse_f64(threshold_str, &span, "mailonchange")?;
Ok(DirectiveKind::MailOnChange(MailOnChangeConfig { email: email.to_string(), threshold }))
}
pub fn parse_dumpdir(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "dumpdir", |s| DirectiveKind::DumpDir(DumpDirConfig { directory: s }))
}
pub fn parse_logdir(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "logdir", |s| DirectiveKind::LogDir(LogDirConfig { directory: s }))
}
pub fn parse_rtc_device(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "rtcdevice", |s| DirectiveKind::RtcDevice(RtcDeviceConfig { device: s }))
}
pub fn parse_rtc_file(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "rtcfile", |s| DirectiveKind::RtcFile(RtcFileConfig { file: s }))
}
pub fn parse_hw_clock_file(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "hwclockfile", |s| DirectiveKind::HwClockFile(HwClockFileConfig { file: s }))
}
pub fn parse_pidfile(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "pidfile", |s| DirectiveKind::PidFile(PidFileConfig { file: s }))
}
pub fn parse_user(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "user", |s| DirectiveKind::User(UserConfig { user: s }))
}
pub fn parse_keyfile(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "keyfile", |s| DirectiveKind::KeyFile(KeyFileConfig { file: s }))
}
pub fn parse_ntp_signd_socket(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "ntpsigndsocket", |s| DirectiveKind::NtpSignDSocket(NtpSignDSocketConfig { directory: s }))
}
pub fn parse_nts_ntp_server(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "ntsntpserver", |s| DirectiveKind::NtsNtpServer(NtsNtpServerConfig { hostname: s }))
}
pub fn parse_leap_sec_tz(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "leapsectz", |s| DirectiveKind::LeapSecTz(LeapSecTzConfig { timezone: s }))
}
pub fn parse_leap_sec_list(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "leapseclist", |s| DirectiveKind::LeapSecList(LeapSecListConfig { file: s }))
}
pub fn parse_bind_device(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "binddevice", |s| DirectiveKind::BindDevice(BindDeviceConfig { interface: s }))
}
pub fn parse_bind_acq_device(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "bindacqdevice", |s| DirectiveKind::BindAcqDevice(BindDeviceConfig { interface: s }))
}
pub fn parse_bind_cmd_device(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "bindcmddevice", |s| DirectiveKind::BindCmdDevice(BindCmdDeviceConfig { interface: s }))
}
pub fn parse_confdir(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "confdir", |s| DirectiveKind::ConfDir(ConfDirConfig { directory: s }))
}
pub fn parse_include(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "include", |s| DirectiveKind::Include(IncludeConfig { pattern: s }))
}
pub fn parse_source_dir(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "sourcedir", |s| DirectiveKind::SourceDir(SourceDirConfig { directory: s }))
}
pub fn parse_bind_address(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "bindaddress", |s| DirectiveKind::BindAddress(BindAddressConfig { address: s }))
}
pub fn parse_bind_acq_address(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "bindacqaddress", |s| DirectiveKind::BindAcqAddress(BindAddressConfig { address: s }))
}
pub fn parse_bind_cmd_address(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "bindcmdaddress", |s| DirectiveKind::BindCmdAddress(BindCmdAddressConfig { address: s }))
}
pub fn parse_nts_server_cert(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "ntsservercert", |s| DirectiveKind::NtsServerCert(NtsServerCertConfig { file: s }))
}
pub fn parse_nts_server_key(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "ntsserverkey", |s| DirectiveKind::NtsServerKey(NtsServerKeyConfig { file: s }))
}
pub fn parse_nts_dump_dir(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_string_directive(args, span, "ntsdumpdir", |s| DirectiveKind::NtsDumpDir(NtsDumpDirConfig { directory: s }))
}
pub fn parse_port(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "port")?;
let port = parse_udp_port(args, &span, "port")?;
Ok(DirectiveKind::Port(PortConfig { port }))
}
pub fn parse_acquisition_port(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "acquisitionport")?;
let port = parse_udp_port(args, &span, "acquisitionport")?;
Ok(DirectiveKind::AcquisitionPort(AcquisitionPortConfig { port }))
}
pub fn parse_cmd_port(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "cmdport")?;
let v: u16 = args.parse().map_err(|_| DirectiveError::InvalidValue { directive: "cmdport".into(), expected: "port number", got: args.to_string(), span: span.clone() })?;
Ok(DirectiveKind::CmdPort(CmdPortConfig { port: v }))
}
pub fn parse_nts_port(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "ntsport")?;
let port = parse_udp_port(args, &span, "ntsport")?;
Ok(DirectiveKind::NtsPort(NtsPortConfig { port }))
}
pub fn parse_ptp_port(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "ptpport")?;
let port = parse_udp_port(args, &span, "ptpport")?;
Ok(DirectiveKind::PtpPort(PtpPortConfig { port }))
}
pub fn parse_minsources(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "minsources")?;
let v = parse_u32(args, &span, "minsources")?;
Ok(DirectiveKind::MinSources(MinSourcesConfig { sources: v }))
}
pub fn parse_max_samples(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "maxsamples")?;
let v = parse_u32(args, &span, "maxsamples")?;
Ok(DirectiveKind::MaxSamples(MaxSamplesConfig { samples: v }))
}
pub fn parse_min_samples(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "minsamples")?;
let v = parse_u32(args, &span, "minsamples")?;
Ok(DirectiveKind::MinSamples(MinSamplesConfig { samples: v }))
}
pub fn parse_log_banner(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "logbanner")?;
let v = parse_u32(args, &span, "logbanner")?;
Ok(DirectiveKind::LogBanner(LogBannerConfig { limit: v }))
}
pub fn parse_max_nts_connections(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "maxntsconnections")?;
let v = parse_u32(args, &span, "maxntsconnections")?;
Ok(DirectiveKind::MaxNtsConnections(MaxNtsConnectionsConfig { connections: v }))
}
pub fn parse_nts_rotate(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "ntsrotate")?;
let v = parse_u32(args, &span, "ntsrotate")?;
Ok(DirectiveKind::NtsRotate(NtsRotateConfig { interval: v }))
}
pub fn parse_nts_refresh(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "ntsrefresh")?;
let v = parse_u32(args, &span, "ntsrefresh")?;
Ok(DirectiveKind::NtsRefresh(NtsRefreshConfig { interval: v }))
}
pub fn parse_refresh(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "refresh")?;
let v = parse_u32(args, &span, "refresh")?;
Ok(DirectiveKind::Refresh(RefreshConfig { interval: v }))
}
pub fn parse_no_cert_time_check(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "nocerttimecheck")?;
let v = parse_u32(args, &span, "nocerttimecheck")?;
Ok(DirectiveKind::NoCertTimeCheck(NoCertTimeCheckConfig { limit: v }))
}
pub fn parse_nts_processes(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "ntsprocesses")?;
let v = parse_u32(args, &span, "ntsprocesses")?;
let p = NtsProcesses::new(v as u16).map_err(|_| DirectiveError::ValueOutOfRange {
directive: "ntsprocesses".into(), value: v.to_string(),
min: NtsProcesses::MIN.to_string(), max: NtsProcesses::MAX.to_string(), span: span.clone(),
})?;
Ok(DirectiveKind::NtsProcesses(NtsProcessesConfig { processes: p }))
}
pub fn parse_max_tx_buffers(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
require_exact_args(args, 1, &span, "maxtxbuffers")?;
let v = parse_u32(args, &span, "maxtxbuffers")?;
let b = TxBuffers::new(v).map_err(|_| DirectiveError::ValueOutOfRange {
directive: "maxtxbuffers".into(), value: v.to_string(),
min: TxBuffers::MIN.to_string(), max: TxBuffers::MAX.to_string(), span: span.clone(),
})?;
Ok(DirectiveKind::MaxTxBuffers(MaxTxBuffersConfig { buffers: b }))
}
pub fn parse_source_options(args: &str, span: Span, source_type: &str) -> Result<DirectiveKind, DirectiveError> {
let (hostname, rest) = split_word(args);
if hostname.is_empty() {
return err(source_type, DirectiveError::MissingArgument { directive: source_type.into(), expected: 1, got: 0, span });
}
let mut config = ServerConfig { hostname: hostname.to_string(), ..Default::default() };
let mut max_sources_override: Option<u32> = None;
let mut cursor = rest;
while !cursor.is_empty() {
let (opt, next) = split_word(cursor);
if opt.is_empty() { break; }
cursor = next;
match opt.to_lowercase().as_str() {
"auto_offline" => config.auto_offline = true,
"burst" => config.burst = true,
"copy" => config.copy = true,
"iburst" => config.iburst = true,
"nts" => config.nts = true,
"xleave" => config.interleaved = true,
"offline" => config.connectivity = SourceConnectivity::Offline,
"ipv4" => config.family = AddressFamily::Inet4,
"ipv6" => config.family = AddressFamily::Inet6,
"prefer" => config.select_opts.set(SelectOptions::PREFER),
"noselect" => config.select_opts.set(SelectOptions::NOSELECT),
"trust" => config.select_opts.set(SelectOptions::TRUST),
"require" => config.select_opts.set(SelectOptions::REQUIRE),
_ => {
let (val, rest2) = split_word(cursor);
cursor = rest2;
if val.is_empty() {
return err(source_type, DirectiveError::InvalidOption { directive: source_type.into(), option: opt.to_string(), span: span.clone() });
}
match opt.to_lowercase().as_str() {
"minpoll" => config.minpoll = parse_poll_interval(val, &span, source_type)?,
"maxpoll" => config.maxpoll = parse_poll_interval(val, &span, source_type)?,
"maxdelay" => config.max_delay = parse_f64(val, &span, source_type)?,
"maxdelayratio" => config.max_delay_ratio = parse_f64(val, &span, source_type)?,
"maxdelaydevratio" => config.max_delay_dev_ratio = parse_f64(val, &span, source_type)?,
"maxdelayquant" => config.max_delay_quant = parse_f64(val, &span, source_type)?,
"mindelay" => config.min_delay = parse_f64(val, &span, source_type)?,
"asymmetry" => config.asymmetry = parse_f64(val, &span, source_type)?,
"offset" => config.offset = parse_f64(val, &span, source_type)?,
"key" => config.authkey = parse_u32(val, &span, source_type)?,
"certset" => config.cert_set = parse_u32(val, &span, source_type)?,
"port" => config.port = parse_udp_port(val, &span, source_type)?,
"ntsport" => config.nts_port = parse_udp_port(val, &span, source_type)?,
"polltarget" => config.poll_target = parse_poll_target(val, &span, source_type)?,
"minstratum" => config.min_stratum = parse_stratum(val, &span, source_type)?,
"version" => config.version = Some(parse_ntp_version(val, &span, source_type)?),
"filter" => config.filter = Some(parse_u32(val, &span, source_type)?),
"minsamples" => config.min_samples = Some(parse_u32(val, &span, source_type)?),
"maxsamples" => config.max_samples = Some(parse_u32(val, &span, source_type)?),
"maxsources" if source_type == "pool" => max_sources_override = Some(parse_u32(val, &span, source_type)?),
"maxsources" => return err(source_type, DirectiveError::InvalidOption { directive: source_type.into(), option: "maxsources".into(), span: span.clone() }),
"presend" => config.presend = parse_poll_interval(val, &span, source_type)?,
"extfield" => config.ext_fields = parse_extfield(val, &span, source_type)?,
_ => return err(source_type, DirectiveError::InvalidOption { directive: source_type.into(), option: opt.to_string(), span: span.clone() }),
}
}
}
}
match source_type {
"server" => Ok(DirectiveKind::Server(config)),
"pool" => Ok(DirectiveKind::Pool(PoolConfig { source: config, max_sources: max_sources_override.unwrap_or(4) })),
"peer" => Ok(DirectiveKind::Peer(config)),
_ => Err(DirectiveError::InvalidDirective { name: source_type.into(), span }),
}
}
pub fn parse_initstepslew(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
let (threshold_str, rest) = split_word(args);
if threshold_str.is_empty() {
return err("initstepslew", DirectiveError::MissingArgument { directive: "initstepslew".into(), expected: 1, got: 0, span });
}
let threshold = parse_f64(threshold_str, &span, "initstepslew")?;
let hostnames: Vec<String> = rest.split_whitespace().map(String::from).collect();
Ok(DirectiveKind::InitStepSlew(InitStepSlewConfig { threshold, hostnames }))
}
pub fn parse_refclock(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
let (driver_str, rest) = split_word(args);
if driver_str.is_empty() {
return err("refclock", DirectiveError::MissingArgument { directive: "refclock".into(), expected: 1, got: 0, span });
}
let (param, rest2) = split_word(rest);
if param.is_empty() {
return err("refclock", DirectiveError::MissingArgument { directive: "refclock".into(), expected: 2, got: 1, span });
}
let driver = match driver_str.to_lowercase().as_str() {
"pps" => RefClockDriverKind::Pps,
"sock" => RefClockDriverKind::Sock,
"shm" => RefClockDriverKind::Shm,
"phc" => RefClockDriverKind::Phc,
_ => return err("refclock", DirectiveError::InvalidValue { directive: "refclock".into(), expected: "PPS|SOCK|SHM|PHC", got: driver_str.to_string(), span: span.clone() }),
};
let mut config = RefClockConfig { driver, parameter: param.to_string(), ..Default::default() };
let mut cursor = rest2;
while !cursor.is_empty() {
let (opt, next) = split_word(cursor);
if opt.is_empty() { break; }
cursor = next;
match opt.to_lowercase().as_str() {
"local" => { config.local = true; continue; }
"pps" => { config.pps_forced = true; continue; }
"tai" => { config.tai = true; continue; }
"prefer" => { config.select_opts.set(SelectOptions::PREFER); continue; }
"noselect" => { config.select_opts.set(SelectOptions::NOSELECT); continue; }
"trust" => { config.select_opts.set(SelectOptions::TRUST); continue; }
"require" => { config.select_opts.set(SelectOptions::REQUIRE); continue; }
_ => {}
}
let (val, rest2) = split_word(cursor);
cursor = rest2;
if val.is_empty() {
return err("refclock", DirectiveError::InvalidOption { directive: "refclock".into(), option: opt.to_string(), span: span.clone() });
}
match opt.to_lowercase().as_str() {
"poll" => config.poll = parse_poll_interval(val, &span, "refclock")?,
"dpoll" => config.dpoll = parse_poll_interval(val, &span, "refclock")?,
"filter" => config.filter_length = parse_u32(val, &span, "refclock")?,
"rate" => config.pps_rate = PpsRate::new(parse_u32(val, &span, "refclock")?).map_err(|_| DirectiveError::ValueOutOfRange { directive: "refclock".into(), value: val.to_string(), min: PpsRate::MIN.to_string(), max: PpsRate::MAX.to_string(), span: span.clone() })?,
"minsamples" => config.min_samples = Some(parse_u32(val, &span, "refclock")?),
"maxsamples" => config.max_samples = Some(parse_u32(val, &span, "refclock")?),
"maxlockage" => config.max_lock_age = parse_u32(val, &span, "refclock")?,
"maxunreach" => config.max_unreach = parse_u32(val, &span, "refclock")?,
"offset" => config.offset = parse_f64(val, &span, "refclock")?,
"delay" => config.delay = parse_f64(val, &span, "refclock")?,
"precision" => config.precision = parse_f64(val, &span, "refclock")?,
"maxdispersion" => config.max_dispersion = parse_f64(val, &span, "refclock")?,
"stratum" => config.stratum = parse_stratum(val, &span, "refclock")?,
"width" => config.pulse_width = parse_f64(val, &span, "refclock")?,
"refid" => config.ref_id = parse_refid(val),
"lock" => config.lock_ref_id = parse_refid(val),
_ => return err("refclock", DirectiveError::InvalidOption { directive: "refclock".into(), option: opt.to_string(), span: span.clone() }),
}
}
Ok(DirectiveKind::RefClock(config))
}
fn parse_refid(word: &str) -> u32 {
let mut id: u32 = 0;
for (i, &b) in word.as_bytes().iter().take(4).enumerate() {
id |= (b as u32) << (24 - i * 8);
}
id
}
pub fn parse_allow(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_allow_deny(args, span, "allow", false)
}
pub fn parse_deny(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_allow_deny(args, span, "deny", false)
}
pub fn parse_cmd_allow(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_allow_deny(args, span, "cmdallow", true)
}
pub fn parse_cmd_deny(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_allow_deny(args, span, "cmddeny", true)
}
fn parse_allow_deny(args: &str, span: Span, name: &str, is_cmd: bool) -> Result<DirectiveKind, DirectiveError> {
let mut all = false;
let mut subnet = None;
let mut cursor = args;
if !cursor.is_empty() {
let (first, rest) = split_word(cursor);
if first.eq_ignore_ascii_case("all") {
all = true;
cursor = rest;
}
let (addr, rest2) = split_word(cursor);
if !addr.is_empty() {
subnet = Some(addr.to_string());
cursor = rest2;
}
}
if !cursor.trim().is_empty() {
return err(name, DirectiveError::TooManyArguments { directive: name.into(), expected: 0, got: count_args(args), span });
}
let config = AllowDenyConfig { all, subnet };
let kind = match (is_cmd, name.contains("allow")) {
(false, true) => DirectiveKind::Allow(config),
(false, false) => DirectiveKind::Deny(config),
(true, true) => DirectiveKind::CmdAllow(config),
(true, false) => DirectiveKind::CmdDeny(config),
};
Ok(kind)
}
pub fn parse_ratelimit(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_ratelimit_impl(args, span, "ratelimit", true, 3)
}
pub fn parse_nts_ratelimit(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
parse_ratelimit_impl(args, span, "ntsratelimit", false, 6)
}
pub fn parse_cmd_ratelimit(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
let config = parse_ratelimit_args(args, span, "cmdratelimit", false, -4)?;
Ok(DirectiveKind::CmdRateLimit(CmdRateLimitConfig { interval: config.interval, burst: config.burst, leak: config.leak }))
}
fn parse_ratelimit_impl(args: &str, span: Span, name: &str, has_kod: bool, default_interval: i32) -> Result<DirectiveKind, DirectiveError> {
let config = parse_ratelimit_args(args, span, name, has_kod, default_interval)?;
if has_kod {
Ok(DirectiveKind::RateLimit(config))
} else {
Ok(DirectiveKind::NtsRateLimit(NtsRateLimitConfig { interval: config.interval, burst: config.burst, leak: config.leak }))
}
}
fn parse_ratelimit_args(args: &str, span: Span, name: &str, has_kod: bool, default_interval: i32) -> Result<RateLimitConfig, DirectiveError> {
let mut config = RateLimitConfig { interval: default_interval, ..Default::default() };
let mut cursor = args;
while !cursor.is_empty() {
let (opt, rest) = split_word(cursor);
if opt.is_empty() { break; }
let (val_str, rest2) = split_word(rest);
if val_str.is_empty() {
return err(name, DirectiveError::InvalidOption { directive: name.into(), option: opt.to_string(), span: span.clone() });
}
cursor = rest2;
let val = parse_i32(val_str, &span, name)?;
match opt.to_lowercase().as_str() {
"interval" => config.interval = val,
"burst" => config.burst = val,
"leak" => config.leak = val,
"kod" if has_kod => config.kod = Some(val),
_ => return err(name, DirectiveError::InvalidOption { directive: name.into(), option: opt.to_string(), span: span.clone() }),
}
}
Ok(config)
}
pub fn parse_tempcomp(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
let n = count_args(args);
let (file, rest) = split_word(args);
if file.is_empty() {
return err("tempcomp", DirectiveError::MissingArgument { directive: "tempcomp".into(), expected: 3, got: 0, span });
}
if n == 3 {
let (interval_str, points_file) = split_word(rest);
let interval = parse_f64(interval_str, &span, "tempcomp")?;
Ok(DirectiveKind::TempComp(TempCompConfig::PointFile {
file: file.to_string(), interval, points_file: points_file.to_string(),
}))
} else {
require_exact_args(args, 6, &span, "tempcomp")?;
let mut iter = rest.split_whitespace().map(|s| parse_f64(s, &span, "tempcomp"));
let interval = iter.next().unwrap_or(Err(DirectiveError::MissingArgument { directive: "tempcomp".into(), expected: 6, got: 1, span: span.clone() }))?;
let t0 = iter.next().unwrap_or(Err(DirectiveError::MissingArgument { directive: "tempcomp".into(), expected: 6, got: 2, span: span.clone() }))?;
let k0 = iter.next().unwrap_or(Err(DirectiveError::MissingArgument { directive: "tempcomp".into(), expected: 6, got: 3, span: span.clone() }))?;
let k1 = iter.next().unwrap_or(Err(DirectiveError::MissingArgument { directive: "tempcomp".into(), expected: 6, got: 4, span: span.clone() }))?;
let k2 = iter.next().unwrap_or(Err(DirectiveError::MissingArgument { directive: "tempcomp".into(), expected: 6, got: 5, span: span.clone() }))?;
Ok(DirectiveKind::TempComp(TempCompConfig::Coefficients { file: file.to_string(), interval, t0, k0, k1, k2 }))
}
}
pub fn parse_local(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
let mut config = LocalConfig::default();
let mut cursor = args;
while !cursor.is_empty() {
let (opt, rest) = split_word(cursor);
if opt.is_empty() { break; }
cursor = rest;
if opt.eq_ignore_ascii_case("orphan") {
config.orphan = true;
continue;
}
let (val, rest2) = split_word(cursor);
cursor = rest2;
if val.is_empty() {
return err("local", DirectiveError::InvalidOption { directive: "local".into(), option: opt.to_string(), span: span.clone() });
}
match opt.to_lowercase().as_str() {
"stratum" => config.stratum = parse_stratum(val, &span, "local")?,
"distance" => config.distance = parse_f64(val, &span, "local")?,
"activate" => config.activate = parse_f64(val, &span, "local")?,
"waitsynced" => config.wait_synced = parse_f64(val, &span, "local")?,
"waitunsynced" => config.wait_unsynced = parse_f64(val, &span, "local")?,
_ => return err("local", DirectiveError::InvalidOption { directive: "local".into(), option: opt.to_string(), span: span.clone() }),
}
}
if config.wait_unsynced < 0.0 {
config.wait_unsynced = if config.orphan { 300.0 } else { 0.0 };
}
Ok(DirectiveKind::Local(config))
}
pub fn parse_smoothtime(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
let (w1, rest) = split_word(args);
let (w2, rest2) = split_word(rest);
if w1.is_empty() || w2.is_empty() {
return err("smoothtime", DirectiveError::MissingArgument { directive: "smoothtime".into(), expected: 2, got: count_args(args), span });
}
let max_freq = parse_f64(w1, &span, "smoothtime")?;
let max_wander = parse_f64(w2, &span, "smoothtime")?;
let leap_only = !rest2.is_empty() && rest2.eq_ignore_ascii_case("leaponly");
Ok(DirectiveKind::SmoothTime(SmoothTimeConfig { max_freq, max_wander, leap_only }))
}
pub fn parse_broadcast(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
let (interval_str, rest) = split_word(args);
let (addr_str, rest2) = split_word(rest);
if interval_str.is_empty() || addr_str.is_empty() {
return err("broadcast", DirectiveError::MissingArgument { directive: "broadcast".into(), expected: 2, got: count_args(args), span });
}
let interval = parse_u32(interval_str, &span, "broadcast")?;
let port = if rest2.is_empty() {
UdpPort::new_unchecked(123)
} else {
parse_udp_port(rest2, &span, "broadcast")?
};
Ok(DirectiveKind::Broadcast(BroadcastConfig { interval, address: addr_str.to_string(), port }))
}
pub fn parse_log(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
let mut config = LogConfig::default();
for word in args.split_whitespace() {
match word.to_lowercase().as_str() {
"rawmeasurements" => { config.raw_measurements = true; config.measurements = true; }
"measurements" => config.measurements = true,
"selection" => config.selection = true,
"statistics" => config.statistics = true,
"tracking" => config.tracking = true,
"rtc" => config.rtc = true,
"refclocks" => config.refclocks = true,
"tempcomp" => config.tempcomp = true,
_ => return err("log", DirectiveError::InvalidValue { directive: "log".into(), expected: "log option keyword", got: word.to_string(), span: span.clone() }),
}
}
Ok(DirectiveKind::Log(config))
}
pub fn parse_hwtimestamp(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
let (iface, rest) = split_word(args);
if iface.is_empty() {
return err("hwtimestamp", DirectiveError::MissingArgument { directive: "hwtimestamp".into(), expected: 1, got: 0, span });
}
let mut config = HwTimestampConfig { interface: iface.to_string(), ..Default::default() };
let mut maxpoll_set = false;
let mut cursor = rest;
while !cursor.is_empty() {
let (opt, next) = split_word(cursor);
if opt.is_empty() { break; }
cursor = next;
if opt.eq_ignore_ascii_case("nocrossts") {
config.nocrossts = true;
continue;
}
let (val, rest2) = split_word(cursor);
if val.is_empty() {
return err("hwtimestamp", DirectiveError::InvalidOption { directive: "hwtimestamp".into(), option: opt.to_string(), span: span.clone() });
}
cursor = rest2;
match opt.to_lowercase().as_str() {
"minpoll" => config.minpoll = parse_poll_interval(val, &span, "hwtimestamp")?,
"maxpoll" => { config.maxpoll = parse_poll_interval(val, &span, "hwtimestamp")?; maxpoll_set = true; }
"minsamples" => config.minsamples = parse_u32(val, &span, "hwtimestamp")?,
"maxsamples" => config.maxsamples = parse_u32(val, &span, "hwtimestamp")?,
"precision" => config.precision = parse_f64(val, &span, "hwtimestamp")?,
"rxcomp" => config.rx_comp = parse_f64(val, &span, "hwtimestamp")?,
"txcomp" => config.tx_comp = parse_f64(val, &span, "hwtimestamp")?,
"rxfilter" => {
config.rxfilter = match val.to_lowercase().as_str() {
"none" => HwTsRxFilter::None, "ntp" => HwTsRxFilter::Ntp,
"ptp" => HwTsRxFilter::Ptp, "all" => HwTsRxFilter::All,
_ => return err("hwtimestamp", DirectiveError::InvalidValue { directive: "hwtimestamp".into(), expected: "none|ntp|ptp|all", got: val.to_string(), span: span.clone() }),
};
}
_ => return err("hwtimestamp", DirectiveError::InvalidOption { directive: "hwtimestamp".into(), option: opt.to_string(), span: span.clone() }),
}
}
if !maxpoll_set {
config.maxpoll = PollInterval::new(config.minpoll.get().saturating_add(1)).unwrap_or(config.minpoll);
}
Ok(DirectiveKind::HwTimestamp(config))
}
pub fn parse_nts_aeads(args: &str, _span: Span) -> Result<DirectiveKind, DirectiveError> {
let ids: Vec<u32> = args.split_whitespace().filter_map(|w| w.parse::<u32>().ok()).collect();
Ok(DirectiveKind::NtsAeads(NtsAeadsConfig { ids }))
}
pub fn parse_ntstrustedcerts(args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
let n = count_args(args);
let (first, rest) = split_word(args);
let (second, _) = split_word(rest);
if n == 2 {
let set_id = parse_u32(first, &span, "ntstrustedcerts")?;
Ok(DirectiveKind::NtsTrustedCerts(NtsTrustedCertsConfig { set_id, path: second.to_string() }))
} else {
require_exact_args(args, 1, &span, "ntstrustedcerts")?;
Ok(DirectiveKind::NtsTrustedCerts(NtsTrustedCertsConfig { set_id: 0, path: first.to_string() }))
}
}
pub fn parse_open_commands(args: &str, _span: Span) -> Result<DirectiveKind, DirectiveError> {
let valid = &["activity", "authdata", "clients", "manual", "ntpdata", "rtcdata",
"selectdata", "serverstats", "smoothing", "sourcename", "sources", "sourcestats", "tracking"];
let commands: Vec<String> = args.split_whitespace().map(|w| {
for &v in valid {
if w.eq_ignore_ascii_case(v) {
return v.to_string();
}
}
w.to_string() }).collect();
Ok(DirectiveKind::OpenCommands(OpenCommandsConfig { commands }))
}