chrony-confile 0.1.0

A full-featured Rust library for parsing, editing, validating, and serializing chrony configuration files
Documentation
//! NTP source configuration types.
//!
//! This module defines types for time source directives: [`ServerConfig`] (also used for `peer`),
//! [`PoolConfig`], [`RefClockConfig`], [`InitStepSlewConfig`], [`AllowDenyConfig`],
//! [`RateLimitConfig`], [`SmoothTimeConfig`], [`BroadcastConfig`], and [`LocalConfig`].
//!
//! [`ServerConfig`] is the largest configuration struct with ~30 fields covering NTP source
//! options like polling intervals, authentication, select options, and delay constraints.

use crate::values::*;

// --- Select options bitflags ---

/// Bit flags for source selection options (`prefer`, `noselect`, `trust`, `require`).
///
/// Used by [`ServerConfig`] and [`RefClockConfig`] to track which selection options
/// are active for a given source.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SelectOptions(u8);

impl SelectOptions {
    /// The `noselect` flag: this source should not be selected.
    pub const NOSELECT: u8 = 0x1;
    /// The `prefer` flag: prefer this source over others of equal stratum.
    pub const PREFER: u8 = 0x2;
    /// The `trust` flag: trust this source even when its measurement is unusual.
    pub const TRUST: u8 = 0x4;
    /// The `require` flag: require this source to be reachable for synchronization.
    pub const REQUIRE: u8 = 0x8;

    /// Set a specific flag.
    pub fn set(&mut self, flag: u8) {
        self.0 |= flag;
    }
    /// Returns `true` if the given flag is set.
    pub fn has(&self, flag: u8) -> bool {
        self.0 & flag != 0
    }
    /// Returns `true` if no flags are set.
    pub fn is_empty(&self) -> bool {
        self.0 == 0
    }
}

// --- Source connectivity ---

/// Whether a source should be taken online or offline initially.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SourceConnectivity {
    #[default]
    Online,
    Offline,
}

// --- Address family ---

/// IP address family preference for a source.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AddressFamily {
    #[default]
    Unspec,
    Inet4,
    Inet6,
}

// --- Refclock driver kind ---

/// The type of reference clock driver.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RefClockDriverKind {
    Pps,
    Sock,
    Shm,
    Phc,
}

// --- ServerConfig (main NTP source config, ~30 fields) ---

/// Configuration for an NTP server source (or peer).
///
/// This is the largest configuration struct in the crate, with ~30 fields covering all
/// chrony options for the `server` and `peer` directives: polling intervals, selection
/// options, authentication, NTS, delay constraints, and address family preferences.
///
/// Default values mirror chrony's built-in defaults.
///
/// # Examples
///
/// ```rust
/// use chrony_confile::ast::ServerConfig;
///
/// let config = ServerConfig {
///     hostname: "ntp.example.com".to_string(),
///     iburst: true,
///     ..Default::default()
/// };
///
/// assert!(config.iburst);
/// assert_eq!(config.minpoll.get(), 6);
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct ServerConfig {
    pub hostname: String,
    pub port: UdpPort,
    pub minpoll: PollInterval,
    pub maxpoll: PollInterval,
    pub connectivity: SourceConnectivity,
    pub auto_offline: bool,
    pub presend: PollInterval,
    pub burst: bool,
    pub iburst: bool,
    pub min_stratum: Stratum,
    pub poll_target: PollTarget,
    pub version: Option<NtpVersion>,
    pub min_samples: Option<u32>,
    pub max_samples: Option<u32>,
    pub filter: Option<u32>,
    pub interleaved: bool,
    pub select_opts: SelectOptions,
    pub nts: bool,
    pub nts_port: UdpPort,
    pub copy: bool,
    pub ext_fields: u32,
    pub authkey: u32,
    pub cert_set: u32,
    pub max_delay: f64,
    pub max_delay_ratio: f64,
    pub max_delay_dev_ratio: f64,
    pub max_delay_quant: f64,
    pub min_delay: f64,
    pub asymmetry: f64,
    pub offset: f64,
    pub family: AddressFamily,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            hostname: String::new(),
            port: UdpPort::new_unchecked(123),
            minpoll: PollInterval::new_unchecked(6),
            maxpoll: PollInterval::new_unchecked(10),
            connectivity: SourceConnectivity::Online,
            auto_offline: false,
            presend: PollInterval::new_unchecked(0),
            burst: false,
            iburst: false,
            min_stratum: Stratum::new_unchecked(0),
            poll_target: PollTarget::new_unchecked(8),
            version: None,
            min_samples: None,
            max_samples: None,
            filter: None,
            interleaved: false,
            select_opts: SelectOptions::default(),
            nts: false,
            nts_port: UdpPort::new_unchecked(4460),
            copy: false,
            ext_fields: 0,
            authkey: 0,
            cert_set: 0,
            max_delay: 3.0,
            max_delay_ratio: 0.0,
            max_delay_dev_ratio: 10.0,
            max_delay_quant: 0.0,
            min_delay: 0.0,
            asymmetry: 1.0,
            offset: 0.0,
            family: AddressFamily::Unspec,
        }
    }
}

impl ServerConfig {
    /// Returns `true` if the `prefer` option is set.
    pub fn prefer(&self) -> bool {
        self.select_opts.has(SelectOptions::PREFER)
    }
    /// Returns `true` if the `noselect` option is set.
    pub fn noselect(&self) -> bool {
        self.select_opts.has(SelectOptions::NOSELECT)
    }
    /// Returns `true` if the `trust` option is set.
    pub fn trust(&self) -> bool {
        self.select_opts.has(SelectOptions::TRUST)
    }
    /// Returns `true` if the `require` option is set.
    pub fn require(&self) -> bool {
        self.select_opts.has(SelectOptions::REQUIRE)
    }
    /// Returns `true` if interleaved mode (`xleave`) is enabled.
    pub fn xleave(&self) -> bool {
        self.interleaved
    }
}

// --- PoolConfig ---

/// Configuration for a `pool` directive.
///
/// A pool resolves to multiple NTP servers. The `max_sources` field limits how many
/// servers from the pool are used simultaneously. The `source` field contains the
/// shared server options (hostname, polling, selection, etc.).
#[derive(Debug, Clone, PartialEq)]
pub struct PoolConfig {
    /// The underlying server configuration (hostname, options, etc.).
    pub source: ServerConfig,
    /// Maximum number of sources to use from this pool (default: 4).
    pub max_sources: u32,
}

impl Default for PoolConfig {
    fn default() -> Self {
        Self {
            source: ServerConfig::default(),
            max_sources: 4,
        }
    }
}

// --- PeerConfig = same as ServerConfig ---
pub type PeerConfig = ServerConfig;

// --- InitStepSlew ---

/// Configuration for the `initstepslew` directive.
///
/// Specifies a threshold and a list of hostnames to step the clock at chrony startup
/// if the offset exceeds the threshold.
#[derive(Debug, Clone, PartialEq)]
pub struct InitStepSlewConfig {
    pub threshold: f64,
    pub hostnames: Vec<String>,
}

// --- RefClockConfig ---

/// Configuration for a `refclock` directive (local reference clock).
#[derive(Debug, Clone, PartialEq)]
pub struct RefClockConfig {
    pub driver: RefClockDriverKind,
    pub parameter: String,
    pub poll: PollInterval,
    pub dpoll: PollInterval,
    pub filter_length: u32,
    pub local: bool,
    pub pps_forced: bool,
    pub pps_rate: PpsRate,
    pub min_samples: Option<u32>,
    pub max_samples: Option<u32>,
    pub max_unreach: u32,
    pub max_lock_age: u32,
    pub select_opts: SelectOptions,
    pub stratum: Stratum,
    pub tai: bool,
    pub offset: f64,
    pub delay: f64,
    pub precision: f64,
    pub max_dispersion: f64,
    pub pulse_width: f64,
    pub ref_id: u32,
    pub lock_ref_id: u32,
}

impl Default for RefClockConfig {
    fn default() -> Self {
        Self {
            driver: RefClockDriverKind::Pps,
            parameter: String::new(),
            poll: PollInterval::new_unchecked(4),
            dpoll: PollInterval::new_unchecked(0),
            filter_length: 64,
            local: false,
            pps_forced: false,
            pps_rate: PpsRate::new_unchecked(1),
            min_samples: None,
            max_samples: None,
            max_unreach: 100000,
            max_lock_age: 2,
            select_opts: SelectOptions::default(),
            stratum: Stratum::new_unchecked(0),
            tai: false,
            offset: 0.0,
            delay: 1e-9,
            precision: 0.0,
            max_dispersion: 0.0,
            pulse_width: 0.0,
            ref_id: 0,
            lock_ref_id: 0,
        }
    }
}

// --- AllowDeny ---

/// Configuration for `allow`, `deny`, `cmdallow`, and `cmddeny` directives.
///
/// Controls NTP client access or command access to chronyd.
#[derive(Debug, Clone, PartialEq)]
pub struct AllowDenyConfig {
    pub all: bool,
    pub subnet: Option<String>,
}

// --- RateLimit ---

/// Configuration for the `ratelimit` directive (NTP client rate limiting).
#[derive(Debug, Clone, PartialEq)]
pub struct RateLimitConfig {
    pub interval: i32,
    pub burst: i32,
    pub leak: i32,
    pub kod: Option<i32>,
}

impl Default for RateLimitConfig {
    fn default() -> Self {
        Self {
            interval: 3,
            burst: 8,
            leak: 2,
            kod: None,
        }
    }
}

// --- NtsRateLimit ---

/// Configuration for the `ntsratelimit` directive (NTS rate limiting).
#[derive(Debug, Clone, PartialEq)]
pub struct NtsRateLimitConfig {
    pub interval: i32,
    pub burst: i32,
    pub leak: i32,
}

impl Default for NtsRateLimitConfig {
    fn default() -> Self {
        Self {
            interval: 6,
            burst: 8,
            leak: 2,
        }
    }
}

// --- SmoothTime ---

/// Configuration for the `smoothtime` directive (slew-based time smoothing).
#[derive(Debug, Clone, PartialEq)]
pub struct SmoothTimeConfig {
    pub max_freq: f64,
    pub max_wander: f64,
    pub leap_only: bool,
}

// --- Broadcast ---

/// Configuration for the `broadcast` directive (NTP broadcast server).
#[derive(Debug, Clone, PartialEq)]
pub struct BroadcastConfig {
    pub interval: u32,
    pub address: String,
    pub port: UdpPort,
}

// --- Local ---

/// Configuration for the `local` directive (local stratum reference).
#[derive(Debug, Clone, PartialEq)]
pub struct LocalConfig {
    pub stratum: Stratum,
    pub orphan: bool,
    pub distance: f64,
    pub activate: f64,
    pub wait_synced: f64,
    pub wait_unsynced: f64,
}

impl Default for LocalConfig {
    fn default() -> Self {
        Self {
            stratum: Stratum::new_unchecked(10),
            orphan: false,
            distance: 1.0,
            activate: 0.0,
            wait_synced: 0.0,
            wait_unsynced: -1.0,
        }
    }
}