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::{ClockQuality, 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
27pub struct PtpInstance<F, S = RefCell<PtpInstanceState>> {
91 state: S,
92 log_bmca_interval: AtomicI8,
93 _filter: PhantomData<F>,
94}
95
96#[derive(Debug)]
98pub struct PtpInstanceState {
99 pub(crate) default_ds: InternalDefaultDS,
100 pub(crate) current_ds: InternalCurrentDS,
101 pub(crate) parent_ds: InternalParentDS,
102 pub(crate) path_trace_ds: PathTraceDS,
103 pub(crate) time_properties_ds: TimePropertiesDS,
104}
105
106impl PtpInstanceState {
107 fn bmca<A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, S: PtpInstanceStateMutex>(
108 &mut self,
109 ports: &mut [&mut Port<'_, InBmca, A, R, C, F, S>],
110 bmca_interval: Duration,
111 ) {
112 debug_assert_eq!(self.default_ds.number_ports as usize, ports.len());
113
114 for port in ports.iter_mut() {
115 port.calculate_best_local_announce_message()
116 }
117
118 let ebest = Bmca::<()>::find_best_announce_message(
119 ports
120 .iter()
121 .filter_map(|port| port.best_local_announce_message_for_bmca()),
122 );
123
124 for port in ports.iter_mut() {
125 let recommended_state = Bmca::<()>::calculate_recommended_state(
126 &self.default_ds,
127 ebest,
128 port.best_local_announce_message_for_state(), port.state(),
130 );
131
132 log::debug!(
133 "Recommended state port {}: {recommended_state:?}",
134 port.number(),
135 );
136
137 if let Some(recommended_state) = recommended_state {
138 port.set_recommended_state(
139 recommended_state,
140 &mut self.path_trace_ds,
141 &mut self.time_properties_ds,
142 &mut self.current_ds,
143 &mut self.parent_ds,
144 &self.default_ds,
145 );
146 }
147 }
148
149 for port in ports.iter_mut() {
151 port.step_announce_age(bmca_interval);
152 }
153 }
154}
155
156impl<F, S: PtpInstanceStateMutex> PtpInstance<F, S> {
157 pub fn new(config: InstanceConfig, time_properties_ds: TimePropertiesDS) -> Self {
160 let default_ds = InternalDefaultDS::new(config);
161
162 Self {
163 state: S::new(PtpInstanceState {
164 default_ds,
165 current_ds: Default::default(),
166 parent_ds: InternalParentDS::new(default_ds),
167 path_trace_ds: PathTraceDS::new(config.path_trace),
168 time_properties_ds,
169 }),
170 log_bmca_interval: AtomicI8::new(i8::MAX),
171 _filter: PhantomData,
172 }
173 }
174
175 pub fn default_ds(&self) -> DefaultDS {
177 self.state.with_ref(|s| (&s.default_ds).into())
178 }
179
180 pub fn current_ds(&self, port_contribution: Option<FilterEstimate>) -> CurrentDS {
182 self.state
183 .with_ref(|s| CurrentDS::from_state(&s.current_ds, port_contribution))
184 }
185
186 pub fn parent_ds(&self) -> ParentDS {
188 self.state.with_ref(|s| (&s.parent_ds).into())
189 }
190
191 pub fn time_properties_ds(&self) -> TimePropertiesDS {
193 self.state.with_ref(|s| s.time_properties_ds)
194 }
195
196 pub fn path_trace_ds(&self) -> PathTraceDS {
198 self.state.with_ref(|s| s.path_trace_ds.clone())
199 }
200}
201
202impl<F: Filter, S: PtpInstanceStateMutex> PtpInstance<F, S> {
203 pub fn add_port<A, C, R: Rng>(
212 &self,
213 config: PortConfig<A>,
214 filter_config: F::Config,
215 clock: C,
216 rng: R,
217 ) -> Port<'_, InBmca, A, R, C, F, S> {
218 self.log_bmca_interval
219 .fetch_min(config.announce_interval.as_log_2(), Ordering::Relaxed);
220 let port_identity = self.state.with_mut(|state| {
221 state.default_ds.number_ports += 1;
222 PortIdentity {
223 clock_identity: state.default_ds.clock_identity,
224 port_number: state.default_ds.number_ports,
225 }
226 });
227
228 Port::new(
229 &self.state,
230 config,
231 filter_config,
232 clock,
233 port_identity,
234 rng,
235 )
236 }
237
238 pub fn bmca<A: AcceptableMasterList, C: Clock, R: Rng>(
243 &self,
244 ports: &mut [&mut Port<'_, InBmca, A, R, C, F, S>],
245 ) {
246 self.state.with_mut(|state| {
247 state.bmca(
248 ports,
249 Duration::from_seconds(
250 2f64.powi(self.log_bmca_interval.load(Ordering::Relaxed) as i32),
251 ),
252 );
253 });
254 }
255
256 pub fn bmca_interval(&self) -> core::time::Duration {
258 core::time::Duration::from_secs_f64(
259 2f64.powi(self.log_bmca_interval.load(Ordering::Relaxed) as i32),
260 )
261 }
262
263 pub fn set_clock_quality(&self, clock_quality: ClockQuality) {
270 self.state.with_mut(|state| {
271 state.default_ds.clock_quality = clock_quality;
272 });
273 }
274
275 pub fn set_slave_only(&self, slave_only: bool) {
282 self.state.with_mut(|state| {
283 state.default_ds.slave_only = slave_only;
284 })
285 }
286}
287
288pub trait PtpInstanceStateMutex {
294 fn new(state: PtpInstanceState) -> Self;
296
297 fn with_ref<R, F: FnOnce(&PtpInstanceState) -> R>(&self, f: F) -> R;
299
300 fn with_mut<R, F: FnOnce(&mut PtpInstanceState) -> R>(&self, f: F) -> R;
302}
303
304impl PtpInstanceStateMutex for RefCell<PtpInstanceState> {
305 fn new(state: PtpInstanceState) -> Self {
306 RefCell::new(state)
307 }
308
309 fn with_ref<R, F: FnOnce(&PtpInstanceState) -> R>(&self, f: F) -> R {
310 f(&RefCell::borrow(self))
311 }
312
313 fn with_mut<R, F: FnOnce(&mut PtpInstanceState) -> R>(&self, f: F) -> R {
314 f(&mut RefCell::borrow_mut(self))
315 }
316}
317
318#[cfg(feature = "std")]
319impl PtpInstanceStateMutex for std::sync::RwLock<PtpInstanceState> {
320 fn new(state: PtpInstanceState) -> Self {
321 std::sync::RwLock::new(state)
322 }
323
324 fn with_ref<R, F: FnOnce(&PtpInstanceState) -> R>(&self, f: F) -> R {
325 f(&self.read().unwrap())
326 }
327
328 fn with_mut<R, F: FnOnce(&mut PtpInstanceState) -> R>(&self, f: F) -> R {
329 f(&mut self.write().unwrap())
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use crate::{
337 config::{
338 ClockAccuracy, ClockIdentity, ClockQuality, InstanceConfig, TimePropertiesDS,
339 TimeSource,
340 },
341 filters::BasicFilter,
342 };
343
344 fn create_test_instance() -> PtpInstance<BasicFilter> {
345 let config = InstanceConfig {
346 clock_identity: ClockIdentity::from_mac_address([1, 2, 3, 4, 5, 6]),
347 priority_1: 128,
348 priority_2: 128,
349 domain_number: 0,
350 slave_only: false,
351 sdo_id: Default::default(),
352 path_trace: false,
353 clock_quality: ClockQuality::default(),
354 };
355 let time_properties_ds =
356 TimePropertiesDS::new_arbitrary_time(false, false, TimeSource::InternalOscillator);
357 PtpInstance::new(config, time_properties_ds)
358 }
359
360 #[test]
361 fn test_set_clock_quality_basic() {
362 let instance = create_test_instance();
363
364 let default_ds = instance.default_ds();
365 assert_eq!(default_ds.clock_quality.clock_class, 248);
366 assert_eq!(
367 default_ds.clock_quality.clock_accuracy,
368 ClockAccuracy::Unknown
369 );
370
371 let custom_quality = ClockQuality {
373 clock_class: 6,
374 clock_accuracy: ClockAccuracy::NS1,
375 offset_scaled_log_variance: 0x8000 - (20 * 256),
376 };
377
378 instance.set_clock_quality(custom_quality);
379
380 let updated_ds = instance.default_ds();
381 assert_eq!(updated_ds.clock_quality.clock_class, 6);
382 assert_eq!(updated_ds.clock_quality.clock_accuracy, ClockAccuracy::NS1);
383 assert_eq!(
384 updated_ds.clock_quality.offset_scaled_log_variance,
385 0x8000 - (20 * 256)
386 );
387 }
388
389 #[test]
390 fn test_set_slave_only() {
391 let instance = create_test_instance();
392
393 instance.set_slave_only(false);
394 assert!(!instance.default_ds().slave_only);
395
396 instance.set_slave_only(true);
397 assert!(instance.default_ds().slave_only);
398 }
399}