diff --git a/Cargo.lock b/Cargo.lock index d9363989b..ef5d28fc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,6 +253,17 @@ dependencies = [ "typenum", ] +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getset" version = "0.1.2" @@ -453,6 +464,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -495,6 +512,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -563,6 +610,7 @@ dependencies = [ "getset", "libm", "log", + "rand", ] [[package]] @@ -575,6 +623,7 @@ dependencies = [ "libc", "log", "pin-project", + "rand", "statime", "thiserror", "tokio", diff --git a/statime-linux/Cargo.toml b/statime-linux/Cargo.toml index dfb05ee61..3c33977be 100644 --- a/statime-linux/Cargo.toml +++ b/statime-linux/Cargo.toml @@ -15,4 +15,4 @@ statime = { path = "../statime" } thiserror = "1.0.43" pin-project = "1.1" tokio = { version = "1.29", features = ["full"] } - +rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"] } diff --git a/statime-linux/src/main.rs b/statime-linux/src/main.rs index 71cab31b2..5e91f94e9 100644 --- a/statime-linux/src/main.rs +++ b/statime-linux/src/main.rs @@ -5,6 +5,7 @@ use std::{ use clap::Parser; use fern::colors::Color; +use rand::{rngs::StdRng, SeedableRng}; use statime::{ BasicFilter, Clock, ClockIdentity, DefaultDS, DelayMechanism, Duration, Interval, PortAction, PortActionIterator, PortConfig, PortIdentity, PtpInstance, SdoId, Time, TimePropertiesDS, @@ -237,7 +238,8 @@ async fn actual_main() { local_clock.clone(), BasicFilter::new(0.25), ); - let mut bmca_port = instance.add_port(port_config); + let rng = StdRng::from_entropy(); + let mut bmca_port = instance.add_port(port_config, rng); let mut bmca_timer = pin!(Timer::new()); let mut port_sync_timer = pin!(Timer::new()); diff --git a/statime/Cargo.toml b/statime/Cargo.toml index 0b43784c7..c571c10c8 100644 --- a/statime/Cargo.toml +++ b/statime/Cargo.toml @@ -27,3 +27,4 @@ fixed = "1.23" getset = "0.1.2" libm = "0.2.7" log = { version = "0.4.19", default-features = false } +rand = { version = "0.8.5", default-features = false } diff --git a/statime/src/config/port.rs b/statime/src/config/port.rs index 32b1edc7d..60fc56463 100644 --- a/statime/src/config/port.rs +++ b/statime/src/config/port.rs @@ -1,3 +1,5 @@ +use rand::Rng; + use crate::{time::Interval, Duration, PortIdentity}; /// Which delay mechanism a port is using. @@ -37,8 +39,12 @@ impl PortConfig { } } - pub fn announce_duration(&self) -> core::time::Duration { - // timeout is the number of announce intervals before the announce expires - self.announce_interval.as_core_duration() * self.announce_receipt_timeout as u32 + // section 9.2.6.12 + pub fn announce_duration(&self, rng: &mut impl Rng) -> core::time::Duration { + // add some randomness so that not all timers expire at the same time + let factor = 1.0 + rng.sample::(rand::distributions::Open01); + let duration = self.announce_interval.as_core_duration(); + + duration.mul_f64(factor * self.announce_receipt_timeout as u32 as f64) } } diff --git a/statime/src/port/mod.rs b/statime/src/port/mod.rs index a62507c24..ada6c954d 100644 --- a/statime/src/port/mod.rs +++ b/statime/src/port/mod.rs @@ -6,6 +6,7 @@ use core::{ use arrayvec::ArrayVec; pub use error::{PortError, Result}; pub use measurement::Measurement; +use rand::Rng; use state::{MasterState, PortState}; use self::state::SlaveState; @@ -58,13 +59,14 @@ pub mod state; /// A single port of the PTP instance /// /// One of these needs to be created per port of the PTP instance. -pub struct Port { +pub struct Port { config: PortConfig, // Corresponds with PortDS port_state and enabled port_state: PortState, bmca: Bmca, packet_buffer: [u8; MAX_DATA_LEN], lifecycle: L, + rng: R, } pub struct Running<'a, C, F> { @@ -136,7 +138,7 @@ impl<'a> Iterator for PortActionIterator<'a> { } } -impl<'a, C: Clock, F: Filter> Port> { +impl<'a, C: Clock, F: Filter, R: Rng> Port, R> { // Send timestamp for last timecritical message became available pub fn handle_send_timestamp( &mut self, @@ -264,7 +266,7 @@ impl<'a, C: Clock, F: Filter> Port> { self.lifecycle.state.local_clock.borrow().now().into(), ); actions![PortAction::ResetAnnounceReceiptTimer { - duration: self.config.announce_duration(), + duration: self.config.announce_duration(&mut self.rng), }] } _ => { @@ -286,11 +288,12 @@ impl<'a, C: Clock, F: Filter> Port> { // Start a BMCA cycle and ensure this happens instantly from the perspective of // the port - pub fn start_bmca(self) -> Port> { + pub fn start_bmca(self) -> Port, R> { Port { port_state: self.port_state, config: self.config, bmca: self.bmca, + rng: self.rng, packet_buffer: [0; MAX_DATA_LEN], lifecycle: InBmca { pending_action: actions![], @@ -301,14 +304,15 @@ impl<'a, C: Clock, F: Filter> Port> { } } -impl<'a, C, F> Port> { +impl<'a, C, F, R> Port, R> { // End a BMCA cycle and make the port available again - pub fn end_bmca(self) -> (Port>, PortActionIterator<'static>) { + pub fn end_bmca(self) -> (Port, R>, PortActionIterator<'static>) { ( Port { port_state: self.port_state, config: self.config, bmca: self.bmca, + rng: self.rng, packet_buffer: [0; MAX_DATA_LEN], lifecycle: Running { state_refcell: self.lifecycle.state_refcell, @@ -320,7 +324,7 @@ impl<'a, C, F> Port> { } } -impl Port { +impl Port { fn set_forced_port_state(&mut self, state: PortState) { log::info!( "new state for port {}: {} -> {}", @@ -340,7 +344,7 @@ impl Port { } } -impl<'a, C, F> Port> { +impl<'a, C, F, R: Rng> Port, R> { pub(crate) fn calculate_best_local_announce_message(&mut self, current_time: WireTimestamp) { self.lifecycle.local_best = self.bmca.take_best_port_announce_message(current_time) } @@ -419,7 +423,7 @@ impl<'a, C, F> Port> { if update_state { self.set_forced_port_state(state); - let duration = self.config.announce_duration(); + let duration = self.config.announce_duration(&mut self.rng); let action = PortAction::ResetAnnounceReceiptTimer { duration }; self.lifecycle.pending_action = actions![action]; } @@ -448,23 +452,25 @@ impl<'a, C, F> Port> { } } -impl<'a, C, F> Port> { +impl<'a, C, F, R: Rng> Port, R> { /// Create a new port from a port dataset on a given interface. pub(crate) fn new( state_refcell: &'a RefCell>, config: PortConfig, + mut rng: R, ) -> Self { let bmca = Bmca::new( config.announce_interval.as_duration().into(), config.port_identity, ); - let duration = config.announce_duration(); + let duration = config.announce_duration(&mut rng); Port { config, port_state: PortState::Listening, bmca, + rng, packet_buffer: [0; MAX_DATA_LEN], lifecycle: InBmca { pending_action: actions![PortAction::ResetAnnounceReceiptTimer { duration }], diff --git a/statime/src/ptp_instance.rs b/statime/src/ptp_instance.rs index 6db8c5760..4cd944518 100644 --- a/statime/src/ptp_instance.rs +++ b/statime/src/ptp_instance.rs @@ -3,6 +3,8 @@ use core::{ sync::atomic::{AtomicI8, Ordering}, }; +use rand::Rng; + use crate::{ bmc::bmca::Bmca, clock::Clock, @@ -70,7 +72,7 @@ pub(crate) struct PtpInstanceState { } impl PtpInstanceState { - fn bmca(&mut self, ports: &mut [&mut Port>]) { + fn bmca(&mut self, ports: &mut [&mut Port, R>]) { let current_time = self.local_clock.get_mut().now().into(); for port in ports.iter_mut() { @@ -137,13 +139,13 @@ impl PtpInstance { } } - pub fn add_port(&self, config: PortConfig) -> Port> { + pub fn add_port(&self, config: PortConfig, rng: R) -> Port, R> { self.log_bmca_interval .fetch_min(config.announce_interval.as_log_2(), Ordering::Relaxed); - Port::new(&self.state, config) + Port::new(&self.state, config, rng) } - pub fn bmca(&self, ports: &mut [&mut Port>]) { + pub fn bmca(&self, ports: &mut [&mut Port, R>]) { self.state.borrow_mut().bmca(ports) }