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) {
269 self.state.with_mut(|state| {
270 state.default_ds.clock_quality = clock_quality;
271 });
272 }
273}
274
275pub trait PtpInstanceStateMutex {
281 fn new(state: PtpInstanceState) -> Self;
283
284 fn with_ref<R, F: FnOnce(&PtpInstanceState) -> R>(&self, f: F) -> R;
286
287 fn with_mut<R, F: FnOnce(&mut PtpInstanceState) -> R>(&self, f: F) -> R;
289}
290
291impl PtpInstanceStateMutex for RefCell<PtpInstanceState> {
292 fn new(state: PtpInstanceState) -> Self {
293 RefCell::new(state)
294 }
295
296 fn with_ref<R, F: FnOnce(&PtpInstanceState) -> R>(&self, f: F) -> R {
297 f(&RefCell::borrow(self))
298 }
299
300 fn with_mut<R, F: FnOnce(&mut PtpInstanceState) -> R>(&self, f: F) -> R {
301 f(&mut RefCell::borrow_mut(self))
302 }
303}
304
305#[cfg(feature = "std")]
306impl PtpInstanceStateMutex for std::sync::RwLock<PtpInstanceState> {
307 fn new(state: PtpInstanceState) -> Self {
308 std::sync::RwLock::new(state)
309 }
310
311 fn with_ref<R, F: FnOnce(&PtpInstanceState) -> R>(&self, f: F) -> R {
312 f(&self.read().unwrap())
313 }
314
315 fn with_mut<R, F: FnOnce(&mut PtpInstanceState) -> R>(&self, f: F) -> R {
316 f(&mut self.write().unwrap())
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use crate::{
324 config::{
325 ClockAccuracy, ClockIdentity, ClockQuality, InstanceConfig, TimePropertiesDS,
326 TimeSource,
327 },
328 filters::BasicFilter,
329 };
330
331 fn create_test_instance() -> PtpInstance<BasicFilter> {
332 let config = InstanceConfig {
333 clock_identity: ClockIdentity::from_mac_address([1, 2, 3, 4, 5, 6]),
334 priority_1: 128,
335 priority_2: 128,
336 domain_number: 0,
337 slave_only: false,
338 sdo_id: Default::default(),
339 path_trace: false,
340 clock_quality: ClockQuality::default(),
341 };
342 let time_properties_ds =
343 TimePropertiesDS::new_arbitrary_time(false, false, TimeSource::InternalOscillator);
344 PtpInstance::new(config, time_properties_ds)
345 }
346
347 #[test]
348 fn test_set_clock_quality_basic() {
349 let instance = create_test_instance();
350
351 let default_ds = instance.default_ds();
352 assert_eq!(default_ds.clock_quality.clock_class, 248);
353 assert_eq!(
354 default_ds.clock_quality.clock_accuracy,
355 ClockAccuracy::Unknown
356 );
357
358 let custom_quality = ClockQuality {
360 clock_class: 6,
361 clock_accuracy: ClockAccuracy::NS1,
362 offset_scaled_log_variance: 0x8000 - (20 * 256),
363 };
364
365 instance.set_clock_quality(custom_quality);
366
367 let updated_ds = instance.default_ds();
368 assert_eq!(updated_ds.clock_quality.clock_class, 6);
369 assert_eq!(updated_ds.clock_quality.clock_accuracy, ClockAccuracy::NS1);
370 assert_eq!(
371 updated_ds.clock_quality.offset_scaled_log_variance,
372 0x8000 - (20 * 256)
373 );
374 }
375}