statime/
ptp_instance.rs

1use core::{
2    cell::RefCell,
3    marker::PhantomData,
4    sync::atomic::{AtomicI8, Ordering},
5};
6
7use rand::Rng;
8
9#[allow(unused_imports)]
10use crate::float_polyfill::FloatPolyfill;
11use crate::{
12    bmc::{acceptable_master::AcceptableMasterList, bmca::Bmca},
13    clock::Clock,
14    config::{InstanceConfig, PortConfig},
15    datastructures::{
16        common::PortIdentity,
17        datasets::{
18            InternalCurrentDS, InternalDefaultDS, InternalParentDS, PathTraceDS, TimePropertiesDS,
19        },
20    },
21    filters::{Filter, FilterEstimate},
22    observability::{current::CurrentDS, default::DefaultDS, parent::ParentDS},
23    port::{InBmca, Port},
24    time::Duration,
25};
26
27/// A PTP node.
28///
29/// This object handles the complete running of the PTP protocol once created.
30/// It provides all the logic for both ordinary and boundary clock mode.
31///
32/// # Example
33///
34/// ```no_run
35/// # struct MockClock;
36/// # impl statime::Clock for MockClock {
37/// #     type Error = ();
38/// #     fn now(&self) -> statime::time::Time {
39/// #         unimplemented!()
40/// #     }
41/// #     fn step_clock(&mut self, _: statime::time::Duration) -> Result<statime::time::Time, Self::Error> {
42/// #         unimplemented!()
43/// #     }
44/// #     fn set_frequency(&mut self, _: f64) -> Result<statime::time::Time, Self::Error> {
45/// #         unimplemented!()
46/// #     }
47/// #     fn set_properties(&mut self, _: &TimePropertiesDS) -> Result<(), Self::Error> {
48/// #         unimplemented!()
49/// #     }
50/// # }
51/// # mod system {
52/// #     pub fn get_mac() -> [u8; 6] { unimplemented!() }
53/// #     pub fn sleep(time: core::time::Duration) { unimplemented!() }
54/// # }
55/// # let port_config: statime::config::PortConfig<AcceptAnyMaster> = unimplemented!();
56/// # let filter_config = unimplemented!();
57/// # let clock: MockClock = unimplemented!();
58/// # let rng: rand::rngs::mock::StepRng = unimplemented!();
59/// #
60/// use statime::PtpInstance;
61/// use statime::config::{AcceptAnyMaster, ClockIdentity, InstanceConfig, TimePropertiesDS, TimeSource};
62/// use statime::filters::BasicFilter;
63///
64/// let instance_config = InstanceConfig {
65///     clock_identity: ClockIdentity::from_mac_address(system::get_mac()),
66///     priority_1: 128,
67///     priority_2: 128,
68///     domain_number: 0,
69///     slave_only: false,
70///     sdo_id: Default::default(),
71///     path_trace: false,
72/// };
73/// let time_properties_ds = TimePropertiesDS::new_arbitrary_time(false, false, TimeSource::InternalOscillator);
74///
75/// let mut instance = PtpInstance::<BasicFilter>::new(
76///     instance_config,
77///     time_properties_ds,
78/// );
79///
80/// let mut port = instance.add_port(port_config, filter_config, clock, rng);
81///
82/// // Send of port to its own thread/task to do its work
83///
84/// loop {
85///     instance.bmca(&mut [&mut port]);
86///     system::sleep(instance.bmca_interval());
87/// }
88/// ```
89pub struct PtpInstance<F, S = RefCell<PtpInstanceState>> {
90    state: S,
91    log_bmca_interval: AtomicI8,
92    _filter: PhantomData<F>,
93}
94
95/// The inner state of a [`PtpInstance`]
96#[derive(Debug)]
97pub struct PtpInstanceState {
98    pub(crate) default_ds: InternalDefaultDS,
99    pub(crate) current_ds: InternalCurrentDS,
100    pub(crate) parent_ds: InternalParentDS,
101    pub(crate) path_trace_ds: PathTraceDS,
102    pub(crate) time_properties_ds: TimePropertiesDS,
103}
104
105impl PtpInstanceState {
106    fn bmca<A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, S: PtpInstanceStateMutex>(
107        &mut self,
108        ports: &mut [&mut Port<'_, InBmca, A, R, C, F, S>],
109        bmca_interval: Duration,
110    ) {
111        debug_assert_eq!(self.default_ds.number_ports as usize, ports.len());
112
113        for port in ports.iter_mut() {
114            port.calculate_best_local_announce_message()
115        }
116
117        let ebest = Bmca::<()>::find_best_announce_message(
118            ports
119                .iter()
120                .filter_map(|port| port.best_local_announce_message_for_bmca()),
121        );
122
123        for port in ports.iter_mut() {
124            let recommended_state = Bmca::<()>::calculate_recommended_state(
125                &self.default_ds,
126                ebest,
127                port.best_local_announce_message_for_state(), // erbest
128                port.state(),
129            );
130
131            log::debug!(
132                "Recommended state port {}: {recommended_state:?}",
133                port.number(),
134            );
135
136            if let Some(recommended_state) = recommended_state {
137                port.set_recommended_state(
138                    recommended_state,
139                    &mut self.path_trace_ds,
140                    &mut self.time_properties_ds,
141                    &mut self.current_ds,
142                    &mut self.parent_ds,
143                    &self.default_ds,
144                );
145            }
146        }
147
148        // And update announce message ages
149        for port in ports.iter_mut() {
150            port.step_announce_age(bmca_interval);
151        }
152    }
153}
154
155impl<F, S: PtpInstanceStateMutex> PtpInstance<F, S> {
156    /// Construct a new [`PtpInstance`] with the given config and time
157    /// properties
158    pub fn new(config: InstanceConfig, time_properties_ds: TimePropertiesDS) -> Self {
159        let default_ds = InternalDefaultDS::new(config);
160
161        Self {
162            state: S::new(PtpInstanceState {
163                default_ds,
164                current_ds: Default::default(),
165                parent_ds: InternalParentDS::new(default_ds),
166                path_trace_ds: PathTraceDS::new(config.path_trace),
167                time_properties_ds,
168            }),
169            log_bmca_interval: AtomicI8::new(i8::MAX),
170            _filter: PhantomData,
171        }
172    }
173
174    /// Return IEEE-1588 defaultDS for introspection
175    pub fn default_ds(&self) -> DefaultDS {
176        self.state.with_ref(|s| (&s.default_ds).into())
177    }
178
179    /// Return IEEE-1588 currentDS for introspection
180    pub fn current_ds(&self, port_contribution: Option<FilterEstimate>) -> CurrentDS {
181        self.state
182            .with_ref(|s| CurrentDS::from_state(&s.current_ds, port_contribution))
183    }
184
185    /// Return IEEE-1588 parentDS for introspection
186    pub fn parent_ds(&self) -> ParentDS {
187        self.state.with_ref(|s| (&s.parent_ds).into())
188    }
189
190    /// Return IEEE-1588 timePropertiesDS for introspection
191    pub fn time_properties_ds(&self) -> TimePropertiesDS {
192        self.state.with_ref(|s| s.time_properties_ds)
193    }
194
195    /// Return IEEE-1588 pathTraceDS for introspection
196    pub fn path_trace_ds(&self) -> PathTraceDS {
197        self.state.with_ref(|s| s.path_trace_ds.clone())
198    }
199}
200
201impl<F: Filter, S: PtpInstanceStateMutex> PtpInstance<F, S> {
202    /// Add and initialize this port
203    ///
204    /// We start in the BMCA state because that is convenient
205    ///
206    /// When providing the port with a different clock than the instance clock,
207    /// the caller is responsible for propagating any property changes to this
208    /// clock, and for synchronizing this clock with the instance clock as
209    /// appropriate based on the ports state.
210    pub fn add_port<A, C, R: Rng>(
211        &self,
212        config: PortConfig<A>,
213        filter_config: F::Config,
214        clock: C,
215        rng: R,
216    ) -> Port<'_, InBmca, A, R, C, F, S> {
217        self.log_bmca_interval
218            .fetch_min(config.announce_interval.as_log_2(), Ordering::Relaxed);
219        let port_identity = self.state.with_mut(|state| {
220            state.default_ds.number_ports += 1;
221            PortIdentity {
222                clock_identity: state.default_ds.clock_identity,
223                port_number: state.default_ds.number_ports,
224            }
225        });
226
227        Port::new(
228            &self.state,
229            config,
230            filter_config,
231            clock,
232            port_identity,
233            rng,
234        )
235    }
236
237    /// Run the best master clock algorithm (BMCA)
238    ///
239    /// The caller must pass all the ports that were created on this instance in
240    /// the slice!
241    pub fn bmca<A: AcceptableMasterList, C: Clock, R: Rng>(
242        &self,
243        ports: &mut [&mut Port<'_, InBmca, A, R, C, F, S>],
244    ) {
245        self.state.with_mut(|state| {
246            state.bmca(
247                ports,
248                Duration::from_seconds(
249                    2f64.powi(self.log_bmca_interval.load(Ordering::Relaxed) as i32),
250                ),
251            );
252        });
253    }
254
255    /// Time to wait between calls to [`PtpInstance::bmca`]
256    pub fn bmca_interval(&self) -> core::time::Duration {
257        core::time::Duration::from_secs_f64(
258            2f64.powi(self.log_bmca_interval.load(Ordering::Relaxed) as i32),
259        )
260    }
261}
262
263/// A mutex over a [`PtpInstanceState`]
264///
265/// This provides an abstraction for locking state in various environments.
266/// Implementations are provided for [`core::cell::RefCell`] and
267/// [`std::sync::RwLock`].
268pub trait PtpInstanceStateMutex {
269    /// Creates a new instance of the mutex
270    fn new(state: PtpInstanceState) -> Self;
271
272    /// Takes a shared reference to the contained state and calls `f` with it
273    fn with_ref<R, F: FnOnce(&PtpInstanceState) -> R>(&self, f: F) -> R;
274
275    /// Takes a mutable reference to the contained state and calls `f` with it
276    fn with_mut<R, F: FnOnce(&mut PtpInstanceState) -> R>(&self, f: F) -> R;
277}
278
279impl PtpInstanceStateMutex for RefCell<PtpInstanceState> {
280    fn new(state: PtpInstanceState) -> Self {
281        RefCell::new(state)
282    }
283
284    fn with_ref<R, F: FnOnce(&PtpInstanceState) -> R>(&self, f: F) -> R {
285        f(&RefCell::borrow(self))
286    }
287
288    fn with_mut<R, F: FnOnce(&mut PtpInstanceState) -> R>(&self, f: F) -> R {
289        f(&mut RefCell::borrow_mut(self))
290    }
291}
292
293#[cfg(feature = "std")]
294impl PtpInstanceStateMutex for std::sync::RwLock<PtpInstanceState> {
295    fn new(state: PtpInstanceState) -> Self {
296        std::sync::RwLock::new(state)
297    }
298
299    fn with_ref<R, F: FnOnce(&PtpInstanceState) -> R>(&self, f: F) -> R {
300        f(&self.read().unwrap())
301    }
302
303    fn with_mut<R, F: FnOnce(&mut PtpInstanceState) -> R>(&self, f: F) -> R {
304        f(&mut self.write().unwrap())
305    }
306}