use std::sync::Arc;
use super::{ServerConfig, ServerMetrics, ServerSystemState};
pub(crate) struct ServerBuildConfig {
pub(crate) system_state: ServerSystemState,
pub(crate) server_config: ServerConfig,
pub(crate) max_clients: usize,
pub(crate) metrics: Option<Arc<ServerMetrics>>,
pub(crate) listen_addr: String,
pub(crate) socket_opts: crate::socket_opts::SocketOptions,
}
macro_rules! define_server_builder {
(
$(#[$struct_meta:meta])*
extra_fields { $($extra_field:tt)* }
extra_defaults { $($extra_default:tt)* }
) => {
$(#[$struct_meta])*
pub struct NtpServerBuilder {
listen_addr: String,
system_state: $crate::server_common::ServerSystemState,
allow_list: Option<Vec<$crate::server_common::IpNet>>,
deny_list: Option<Vec<$crate::server_common::IpNet>>,
rate_limit: Option<$crate::server_common::RateLimitConfig>,
enable_interleaved: bool,
max_clients: usize,
socket_opts: $crate::socket_opts::SocketOptions,
metrics: Option<::std::sync::Arc<$crate::server_common::ServerMetrics>>,
$($extra_field)*
}
impl NtpServerBuilder {
fn new() -> Self {
NtpServerBuilder {
listen_addr: $crate::default_listen_addr(123),
system_state: <$crate::server_common::ServerSystemState
as ::std::default::Default>::default(),
allow_list: None,
deny_list: None,
rate_limit: None,
enable_interleaved: false,
max_clients: 100_000,
socket_opts: <$crate::socket_opts::SocketOptions
as ::std::default::Default>::default(),
metrics: None,
$($extra_default)*
}
}
pub fn listen(mut self, addr: impl Into<String>) -> Self {
self.listen_addr = addr.into();
self
}
pub fn stratum(mut self, stratum: $crate::protocol::Stratum) -> Self {
self.system_state.stratum = stratum;
self
}
pub fn reference_id(
mut self,
id: $crate::protocol::ReferenceIdentifier,
) -> Self {
self.system_state.reference_id = id;
self
}
pub fn precision(mut self, precision: i8) -> Self {
self.system_state.precision = precision;
self
}
pub fn leap_indicator(
mut self,
li: $crate::protocol::LeapIndicator,
) -> Self {
self.system_state.leap_indicator = li;
self
}
pub fn root_delay(
mut self,
delay: $crate::protocol::ShortFormat,
) -> Self {
self.system_state.root_delay = delay;
self
}
pub fn root_dispersion(
mut self,
disp: $crate::protocol::ShortFormat,
) -> Self {
self.system_state.root_dispersion = disp;
self
}
pub fn allow(mut self, network: $crate::server_common::IpNet) -> Self {
self.allow_list
.get_or_insert_with(::std::vec::Vec::new)
.push(network);
self
}
pub fn deny(mut self, network: $crate::server_common::IpNet) -> Self {
self.deny_list
.get_or_insert_with(::std::vec::Vec::new)
.push(network);
self
}
pub fn rate_limit(
mut self,
config: $crate::server_common::RateLimitConfig,
) -> Self {
self.rate_limit = Some(config);
self
}
pub fn enable_interleaved(mut self, enable: bool) -> Self {
self.enable_interleaved = enable;
self
}
pub fn max_clients(mut self, max: usize) -> Self {
self.max_clients = max;
self
}
pub fn metrics(
mut self,
metrics: ::std::sync::Arc<$crate::server_common::ServerMetrics>,
) -> Self {
self.metrics = Some(metrics);
self
}
#[cfg(feature = "ntpv5")]
pub fn timescale(
mut self,
ts: $crate::protocol::ntpv5::Timescale,
) -> Self {
self.system_state.timescale = ts;
self
}
#[cfg(feature = "ntpv5")]
pub fn v5_reference_id(mut self, id: [u8; 15]) -> Self {
self.system_state.bloom_filter.insert(&id);
self.system_state.v5_reference_id = id;
self
}
#[cfg(feature = "ntpv5")]
pub fn v5_bloom_filter(
mut self,
filter: $crate::protocol::bloom::BloomFilter,
) -> Self {
self.system_state.bloom_filter = filter;
self
}
#[cfg(feature = "socket-opts")]
pub fn v6only(mut self, enabled: bool) -> Self {
self.socket_opts.v6only = Some(enabled);
self
}
#[cfg(feature = "socket-opts")]
pub fn dscp(mut self, value: u8) -> Self {
self.socket_opts.dscp = Some(value);
self
}
pub(crate) fn into_config(
self,
) -> $crate::server_common::ServerBuildConfig {
$crate::server_common::ServerBuildConfig {
system_state: self.system_state,
server_config: $crate::server_common::ServerConfig {
access_control: $crate::server_common::AccessControl::new(
self.allow_list,
self.deny_list,
),
rate_limit: self.rate_limit,
enable_interleaved: self.enable_interleaved,
},
max_clients: self.max_clients,
metrics: self.metrics,
listen_addr: self.listen_addr,
socket_opts: self.socket_opts,
}
}
}
};
}
pub(crate) use define_server_builder;
#[cfg(test)]
#[allow(unreachable_pub, dead_code)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
use std::time::Duration;
define_server_builder! {
extra_fields {}
extra_defaults {}
}
#[test]
fn test_into_config_defaults() {
let cfg = NtpServerBuilder::new().into_config();
assert_eq!(cfg.max_clients, 100_000);
assert_eq!(cfg.listen_addr, crate::default_listen_addr(123));
assert!(cfg.metrics.is_none());
assert!(cfg.server_config.rate_limit.is_none());
assert!(!cfg.server_config.enable_interleaved);
}
#[test]
fn test_into_config_listen_addr() {
let cfg = NtpServerBuilder::new()
.listen("127.0.0.1:9999")
.into_config();
assert_eq!(cfg.listen_addr, "127.0.0.1:9999");
}
#[test]
fn test_into_config_stratum_precision() {
use crate::protocol::{PrimarySource, ReferenceIdentifier, Stratum};
let cfg = NtpServerBuilder::new()
.stratum(Stratum::SECONDARY_MIN)
.precision(-24)
.reference_id(ReferenceIdentifier::PrimarySource(PrimarySource::Gps))
.into_config();
assert_eq!(cfg.system_state.stratum, Stratum::SECONDARY_MIN);
assert_eq!(cfg.system_state.precision, -24);
assert_eq!(
cfg.system_state.reference_id,
ReferenceIdentifier::PrimarySource(PrimarySource::Gps)
);
}
#[test]
fn test_into_config_allow_deny() {
use crate::server_common::IpNet;
use std::net::IpAddr;
let cfg = NtpServerBuilder::new()
.allow(IpNet::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 0)), 8))
.deny(IpNet::new(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 0)), 16))
.into_config();
let _ac = &cfg.server_config.access_control;
}
#[test]
fn test_into_config_multiple_allow_entries() {
use crate::server_common::IpNet;
use std::net::IpAddr;
let cfg = NtpServerBuilder::new()
.allow(IpNet::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 0)), 8))
.allow(IpNet::new(IpAddr::V4(Ipv4Addr::new(172, 16, 0, 0)), 12))
.allow(IpNet::new(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 0)), 16))
.into_config();
let _ac = &cfg.server_config.access_control;
}
#[test]
fn test_into_config_rate_limit() {
use crate::server_common::RateLimitConfig;
let rl = RateLimitConfig {
max_requests_per_window: 10,
window_duration: Duration::from_secs(60),
min_interval: Duration::from_secs(2),
};
let cfg = NtpServerBuilder::new().rate_limit(rl).into_config();
let rl_cfg = cfg.server_config.rate_limit.unwrap();
assert_eq!(rl_cfg.max_requests_per_window, 10);
assert_eq!(rl_cfg.window_duration, Duration::from_secs(60));
assert_eq!(rl_cfg.min_interval, Duration::from_secs(2));
}
#[test]
fn test_into_config_interleaved_max_clients() {
let cfg = NtpServerBuilder::new()
.enable_interleaved(true)
.max_clients(500)
.into_config();
assert!(cfg.server_config.enable_interleaved);
assert_eq!(cfg.max_clients, 500);
}
#[test]
fn test_into_config_metrics() {
let metrics = Arc::new(ServerMetrics::default());
let cfg = NtpServerBuilder::new()
.metrics(metrics.clone())
.into_config();
assert!(cfg.metrics.is_some());
}
}