From 8ccd05c8d377debb8eccf1103150365949c71c03 Mon Sep 17 00:00:00 2001 From: MP Date: Fri, 9 Feb 2024 16:51:50 +0100 Subject: [PATCH 1/5] Added observability of instance datasets --- Cargo.lock | 1 + statime-linux/src/main.rs | 26 +++- statime-linux/src/metrics/exporter.rs | 113 ++++++++++++++---- statime-linux/src/observer.rs | 28 +++-- statime/Cargo.toml | 4 +- statime/src/bmc/bmca.rs | 18 +-- statime/src/bmc/dataset_comparison.rs | 4 +- .../datastructures/common/clock_accuracy.rs | 4 +- .../datastructures/common/clock_identity.rs | 16 +++ .../datastructures/common/clock_quality.rs | 1 + .../datastructures/common/leap_indicator.rs | 1 + .../datastructures/common/port_identity.rs | 7 +- .../src/datastructures/common/time_source.rs | 1 + .../src/datastructures/datasets/current.rs | 2 +- .../src/datastructures/datasets/default.rs | 4 +- statime/src/datastructures/datasets/mod.rs | 6 +- statime/src/datastructures/datasets/parent.rs | 10 +- .../datasets/time_properties.rs | 21 +++- statime/src/datastructures/messages/header.rs | 1 + statime/src/datastructures/messages/mod.rs | 20 ++-- statime/src/filters/kalman.rs | 26 ++-- statime/src/lib.rs | 1 + statime/src/observability/current.rs | 24 ++++ statime/src/observability/default.rs | 43 +++++++ statime/src/observability/mod.rs | 25 ++++ statime/src/observability/parent.rs | 43 +++++++ statime/src/port/bmca.rs | 10 +- statime/src/port/mod.rs | 7 +- statime/src/ptp_instance.rs | 49 +++++++- 29 files changed, 410 insertions(+), 106 deletions(-) create mode 100644 statime/src/observability/current.rs create mode 100644 statime/src/observability/default.rs create mode 100644 statime/src/observability/mod.rs create mode 100644 statime/src/observability/parent.rs diff --git a/Cargo.lock b/Cargo.lock index 662c7c812..c095e4a89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -517,6 +517,7 @@ dependencies = [ "libm", "log", "rand", + "serde", ] [[package]] diff --git a/statime-linux/src/main.rs b/statime-linux/src/main.rs index 0d9371b29..5c2610792 100644 --- a/statime-linux/src/main.rs +++ b/statime-linux/src/main.rs @@ -10,6 +10,7 @@ use rand::{rngs::StdRng, SeedableRng}; use statime::{ config::{ClockIdentity, InstanceConfig, SdoId, TimePropertiesDS, TimeSource}, filters::{Filter, KalmanConfiguration, KalmanFilter}, + observability::ObservableInstanceState, port::{ InBmca, Measurement, Port, PortAction, PortActionIterator, TimestampContext, MAX_DATA_LEN, }, @@ -260,6 +261,16 @@ async fn actual_main() { time_properties_ds, ))); + // The observer for the metrics exporter + let (instance_state_sender, instance_state_receiver) = + tokio::sync::watch::channel(ObservableInstanceState { + default_ds: instance.default_ds(), + current_ds: instance.current_ds(), + parent_ds: instance.parent_ds(), + time_properties_ds: instance.time_properties_ds(), + }); + statime_linux::observer::spawn(&config, instance_state_receiver).await; + let (bmca_notify_sender, bmca_notify_receiver) = tokio::sync::watch::channel(false); let mut main_task_senders = Vec::with_capacity(config.ports.len()); @@ -295,6 +306,7 @@ async fn actual_main() { (LinuxClock::CLOCK_TAI, InterfaceTimestampMode::SoftwareAll) } }; + let rng = StdRng::from_entropy(); let port = instance.add_port( port_config.into(), @@ -376,12 +388,10 @@ async fn actual_main() { .expect("space in channel buffer"); } - // The observer for the metrics exporter - statime_linux::observer::spawn(&config.observability).await; - run( instance, bmca_notify_sender, + instance_state_sender, main_task_receivers, main_task_senders, internal_sync_senders, @@ -393,6 +403,7 @@ async fn actual_main() { async fn run( instance: &'static PtpInstance, bmca_notify_sender: tokio::sync::watch::Sender, + instance_state_sender: tokio::sync::watch::Sender, mut main_task_receivers: Vec>, main_task_senders: Vec>, internal_sync_senders: Vec>, @@ -432,6 +443,15 @@ async fn run( instance.bmca(&mut mut_bmca_ports); + // Update instance state for observability + // We don't care if isn't anybody on the other side + let _ = instance_state_sender.send(ObservableInstanceState { + default_ds: instance.default_ds(), + current_ds: instance.current_ds(), + parent_ds: instance.parent_ds(), + time_properties_ds: instance.time_properties_ds(), + }); + let mut clock_states = vec![ClockSyncMode::FromSystem; internal_sync_senders.len()]; for (idx, port) in mut_bmca_ports.iter().enumerate() { if port.is_steering() { diff --git a/statime-linux/src/metrics/exporter.rs b/statime-linux/src/metrics/exporter.rs index d808368dd..9068c99c8 100644 --- a/statime-linux/src/metrics/exporter.rs +++ b/statime-linux/src/metrics/exporter.rs @@ -11,10 +11,23 @@ use tokio::{ }; use crate::config::Config; +use statime::{ + config::TimePropertiesDS, + observability::{default::DefaultDS, ObservableInstanceState}, +}; #[derive(Debug, Serialize, Deserialize)] pub struct ObservableState { pub program: ProgramData, + pub instance: ObservableInstanceState, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ProgramData { + pub version: String, + pub build_commit: String, + pub build_commit_date: String, + pub uptime_seconds: f64, } impl ProgramData { @@ -26,14 +39,6 @@ impl ProgramData { } } -#[derive(Debug, Serialize, Deserialize)] -pub struct ProgramData { - pub version: String, - pub build_commit: String, - pub build_commit_date: String, - pub uptime_seconds: f64, -} - impl Default for ProgramData { fn default() -> Self { Self { @@ -154,16 +159,6 @@ struct Measurement { value: T, } -#[allow(dead_code)] -impl Measurement { - fn simple(value: T) -> Vec> { - vec![Measurement { - labels: Default::default(), - value, - }] - } -} - #[derive(PartialEq, Eq, Clone, Copy)] enum Unit { Seconds, @@ -190,10 +185,83 @@ impl MetricType { } } +fn format_default_ds(w: &mut impl std::fmt::Write, default_ds: &DefaultDS) -> std::fmt::Result { + let clock_identity = format!("{}", default_ds.clock_identity); + + format_metric( + w, + "number_ports", + "The amount of ports assigned", + MetricType::Gauge, + None, + vec![Measurement { + labels: vec![("clock_identity", clock_identity.clone())], + value: default_ds.number_ports, + }], + )?; + + format_metric( + w, + "quality_class", + "The PTP clock class", + MetricType::Gauge, + None, + vec![Measurement { + labels: vec![("clock_identity", clock_identity.clone())], + value: default_ds.clock_quality.clock_class, + }], + )?; + + format_metric( + w, + "quality_accuracy", + "The quality of the clock", + MetricType::Gauge, + None, + vec![Measurement { + labels: vec![("clock_identity", clock_identity.clone())], + value: default_ds.clock_quality.clock_accuracy.to_primitive(), + }], + )?; + + format_metric( + w, + "quality_offset_scaled_log_variance", + "2-log of the variance (in seconds^2) of the clock when not synchronized", + MetricType::Gauge, + None, + vec![Measurement { + labels: vec![("clock_identity", clock_identity.clone())], + value: default_ds.clock_quality.offset_scaled_log_variance, + }], + )?; + + Ok(()) +} + +pub fn format_time_properties_ds( + w: &mut impl std::fmt::Write, + time_properties_ds: &TimePropertiesDS, +) -> std::fmt::Result { + format_metric( + w, + "current_utc_offset", + "Current offset from UTC", + MetricType::Gauge, + None, + vec![Measurement { + labels: vec![], + value: time_properties_ds.current_utc_offset.unwrap_or(0), + }], + )?; + + Ok(()) +} + pub fn format_state(w: &mut impl std::fmt::Write, state: &ObservableState) -> std::fmt::Result { format_metric( w, - "statime_uptime", + "uptime", "The time that statime has been running", MetricType::Gauge, Some(Unit::Seconds), @@ -207,6 +275,9 @@ pub fn format_state(w: &mut impl std::fmt::Write, state: &ObservableState) -> st }], )?; + format_default_ds(w, &state.instance.default_ds)?; + format_time_properties_ds(w, &state.instance.time_properties_ds)?; + w.write_str("# EOF\n")?; Ok(()) } @@ -220,9 +291,9 @@ fn format_metric( measurements: Vec>, ) -> std::fmt::Result { let name = if let Some(unit) = unit { - format!("{}_{}", name, unit.as_str()) + format!("statime_{}_{}", name, unit.as_str()) } else { - name.to_owned() + format!("statime_{}", name) }; // write help text diff --git a/statime-linux/src/observer.rs b/statime-linux/src/observer.rs index b74883786..8354dc6f0 100644 --- a/statime-linux/src/observer.rs +++ b/statime-linux/src/observer.rs @@ -1,13 +1,19 @@ +use statime::observability::ObservableInstanceState; use std::{fs::Permissions, os::unix::prelude::PermissionsExt, path::Path, time::Instant}; - use tokio::{io::AsyncWriteExt, net::UnixStream, task::JoinHandle}; -use crate::metrics::exporter::{ObservableState, ProgramData}; +use crate::{ + config::Config, + metrics::exporter::{ObservableState, ProgramData}, +}; -pub async fn spawn(config: &super::config::ObservabilityConfig) -> JoinHandle> { +pub async fn spawn( + config: &Config, + instance_state_receiver: tokio::sync::watch::Receiver, +) -> JoinHandle> { let config = config.clone(); tokio::spawn(async move { - let result = observer(config).await; + let result = observer(config, instance_state_receiver).await; if let Err(ref e) = result { log::warn!("Abnormal termination of the state observer: {e}"); log::warn!("The state observer will not be available"); @@ -16,11 +22,14 @@ pub async fn spawn(config: &super::config::ObservabilityConfig) -> JoinHandle std::io::Result<()> { +async fn observer( + config: Config, + instance_state_receiver: tokio::sync::watch::Receiver, +) -> std::io::Result<()> { let start_time = Instant::now(); - let path = match config.observation_path { - Some(path) => path, + let path = match config.observability.observation_path { + Some(ref path) => path, None => return Ok(()), }; @@ -29,15 +38,16 @@ async fn observer(config: super::config::ObservabilityConfig) -> std::io::Result // need elevated permissions to read from the socket. So we explicitly set // the permissions let permissions: std::fs::Permissions = - PermissionsExt::from_mode(config.observation_permissions); + PermissionsExt::from_mode(config.observability.observation_permissions); - let peers_listener = create_unix_socket_with_permissions(&path, permissions)?; + let peers_listener = create_unix_socket_with_permissions(path, permissions)?; loop { let (mut stream, _addr) = peers_listener.accept().await?; let observe = ObservableState { program: ProgramData::with_uptime(start_time.elapsed().as_secs_f64()), + instance: instance_state_receiver.borrow().to_owned(), }; write_json(&mut stream, &observe).await?; diff --git a/statime/Cargo.toml b/statime/Cargo.toml index 69a5d6193..10d175780 100644 --- a/statime/Cargo.toml +++ b/statime/Cargo.toml @@ -12,9 +12,10 @@ publish.workspace = true rust-version.workspace = true [features] -default = ["std"] +default = ["std", "serde"] std = [] fuzz = ["std"] +serde = ["dep:serde"] [dependencies] arrayvec.workspace = true @@ -24,3 +25,4 @@ libm.workspace = true log = { workspace = true, default-features = false} rand = { workspace = true, default-features = false } atomic_refcell.workspace = true +serde = { workspace = true, optional = true } diff --git a/statime/src/bmc/bmca.rs b/statime/src/bmc/bmca.rs index c8a246be3..de6415640 100644 --- a/statime/src/bmc/bmca.rs +++ b/statime/src/bmc/bmca.rs @@ -10,7 +10,7 @@ use super::{ use crate::{ datastructures::{ common::{PortIdentity, TimeInterval}, - datasets::DefaultDS, + datasets::InternalDefaultDS, messages::{AnnounceMessage, Header}, }, port::state::PortState, @@ -106,7 +106,7 @@ impl Bmca { /// If None is returned, then the port should remain in the same state as it /// is now. pub(crate) fn calculate_recommended_state( - own_data: &DefaultDS, + own_data: &InternalDefaultDS, best_global_announce_message: Option, best_port_announce_message: Option, port_state: &PortState, @@ -130,7 +130,7 @@ impl Bmca { } fn calculate_recommended_state_low_class( - own_data: &DefaultDS, + own_data: &InternalDefaultDS, best_port_announce_message: Option, ) -> RecommendedState { let d0 = ComparisonDataset::from_own_data(own_data); @@ -143,7 +143,7 @@ impl Bmca { } fn calculate_recommended_state_high_class( - own_data: &DefaultDS, + own_data: &InternalDefaultDS, best_global_announce_message: Option, best_port_announce_message: Option, ) -> RecommendedState { @@ -287,8 +287,8 @@ impl BestAnnounceMessage { #[derive(Debug, PartialEq, Eq)] pub(crate) enum RecommendedState { - M1(DefaultDS), - M2(DefaultDS), + M1(InternalDefaultDS), + M2(InternalDefaultDS), M3(AnnounceMessage), P1(AnnounceMessage), P2(AnnounceMessage), @@ -443,7 +443,7 @@ mod tests { assert_eq!(message1.compare(&message2), Ordering::Less) } - fn default_own_data() -> DefaultDS { + fn default_own_data() -> InternalDefaultDS { let clock_identity = Default::default(); let priority_1 = 0; let priority_2 = 0; @@ -451,7 +451,7 @@ mod tests { let slave_only = false; let sdo_id = Default::default(); - DefaultDS::new(InstanceConfig { + InternalDefaultDS::new(InstanceConfig { clock_identity, priority_1, priority_2, @@ -492,7 +492,7 @@ mod tests { let slave_only = false; let sdo_id = Default::default(); - let mut own_data = DefaultDS::new(InstanceConfig { + let mut own_data = InternalDefaultDS::new(InstanceConfig { clock_identity, priority_1, priority_2, diff --git a/statime/src/bmc/dataset_comparison.rs b/statime/src/bmc/dataset_comparison.rs index 190f88988..0efcd6f3f 100644 --- a/statime/src/bmc/dataset_comparison.rs +++ b/statime/src/bmc/dataset_comparison.rs @@ -4,7 +4,7 @@ use core::cmp::Ordering; use crate::datastructures::{ common::{ClockIdentity, ClockQuality, PortIdentity}, - datasets::DefaultDS, + datasets::InternalDefaultDS, messages::AnnounceMessage, }; @@ -42,7 +42,7 @@ impl ComparisonDataset { } } - pub(crate) fn from_own_data(data: &DefaultDS) -> Self { + pub(crate) fn from_own_data(data: &InternalDefaultDS) -> Self { Self { gm_priority_1: data.priority_1, gm_identity: data.clock_identity, diff --git a/statime/src/datastructures/common/clock_accuracy.rs b/statime/src/datastructures/common/clock_accuracy.rs index b8aae974d..77e2561de 100644 --- a/statime/src/datastructures/common/clock_accuracy.rs +++ b/statime/src/datastructures/common/clock_accuracy.rs @@ -1,6 +1,7 @@ use core::cmp::Ordering; #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] /// How accurate the underlying clock device is expected to be when not /// synchronized. pub enum ClockAccuracy { @@ -67,7 +68,8 @@ pub enum ClockAccuracy { } impl ClockAccuracy { - pub(crate) fn to_primitive(self) -> u8 { + /// Converts enum to u8 literals + pub fn to_primitive(self) -> u8 { match self { Self::Reserved => 0x00, Self::PS1 => 0x17, diff --git a/statime/src/datastructures/common/clock_identity.rs b/statime/src/datastructures/common/clock_identity.rs index 8f2f2f52d..ad612072c 100644 --- a/statime/src/datastructures/common/clock_identity.rs +++ b/statime/src/datastructures/common/clock_identity.rs @@ -8,6 +8,7 @@ use crate::datastructures::{WireFormat, WireFormatError}; /// /// For more details, see *IEEE1588-2019 section 7.5.2.2.2*. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ClockIdentity(pub [u8; 8]); impl ClockIdentity { @@ -45,6 +46,21 @@ impl WireFormat for ClockIdentity { } } +#[cfg(feature = "std")] +impl core::fmt::Display for ClockIdentity { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + for (i, val) in self.0.iter().enumerate() { + if i != 0 { + write!(f, ":")?; + } + + write!(f, "{:02x}", val)?; + } + + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/statime/src/datastructures/common/clock_quality.rs b/statime/src/datastructures/common/clock_quality.rs index a63127469..68f0f8d2f 100644 --- a/statime/src/datastructures/common/clock_quality.rs +++ b/statime/src/datastructures/common/clock_quality.rs @@ -3,6 +3,7 @@ use crate::datastructures::{WireFormat, WireFormatError}; /// A description of the accuracy and type of a clock. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ClockQuality { /// The PTP clock class. /// diff --git a/statime/src/datastructures/common/leap_indicator.rs b/statime/src/datastructures/common/leap_indicator.rs index f73378571..09a0458ce 100644 --- a/statime/src/datastructures/common/leap_indicator.rs +++ b/statime/src/datastructures/common/leap_indicator.rs @@ -1,5 +1,6 @@ /// Describes upcoming leap seconds. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum LeapIndicator { #[default] /// No leap seconds will be added or removed on this UTC day. diff --git a/statime/src/datastructures/common/port_identity.rs b/statime/src/datastructures/common/port_identity.rs index 123e0cf3a..bb575019b 100644 --- a/statime/src/datastructures/common/port_identity.rs +++ b/statime/src/datastructures/common/port_identity.rs @@ -3,11 +3,12 @@ use crate::datastructures::{WireFormat, WireFormatError}; /// Identity of a single port of a PTP instance #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord)] -pub(crate) struct PortIdentity { +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PortIdentity { /// Identity of the clock this port is part of - pub(crate) clock_identity: ClockIdentity, + pub clock_identity: ClockIdentity, /// Index of the port (1-based). - pub(crate) port_number: u16, + pub port_number: u16, } impl WireFormat for PortIdentity { diff --git a/statime/src/datastructures/common/time_source.rs b/statime/src/datastructures/common/time_source.rs index 1f7c85433..198562a83 100644 --- a/statime/src/datastructures/common/time_source.rs +++ b/statime/src/datastructures/common/time_source.rs @@ -7,6 +7,7 @@ /// For more details see *IEEE1588-2019 section 7.6.2.8* #[allow(missing_docs)] // These varaiants are pretty self explaining #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum TimeSource { AtomicClock, Gnss, diff --git a/statime/src/datastructures/datasets/current.rs b/statime/src/datastructures/datasets/current.rs index cca088293..76e9a95c5 100644 --- a/statime/src/datastructures/datasets/current.rs +++ b/statime/src/datastructures/datasets/current.rs @@ -1,7 +1,7 @@ use crate::time::Duration; #[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub(crate) struct CurrentDS { +pub(crate) struct InternalCurrentDS { pub(crate) steps_removed: u16, pub(crate) offset_from_master: Duration, pub(crate) mean_delay: Duration, diff --git a/statime/src/datastructures/datasets/default.rs b/statime/src/datastructures/datasets/default.rs index 47d9c6b23..12c49fc1b 100644 --- a/statime/src/datastructures/datasets/default.rs +++ b/statime/src/datastructures/datasets/default.rs @@ -14,7 +14,7 @@ use crate::{ /// those related to timebase, which is contained in the /// [TimePropertiesDS](crate::TimePropertiesDS) dataset. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub(crate) struct DefaultDS { +pub(crate) struct InternalDefaultDS { pub(crate) clock_identity: ClockIdentity, pub(crate) number_ports: u16, pub(crate) clock_quality: ClockQuality, @@ -25,7 +25,7 @@ pub(crate) struct DefaultDS { pub(crate) sdo_id: SdoId, } -impl DefaultDS { +impl InternalDefaultDS { pub(crate) fn new(config: InstanceConfig) -> Self { Self { clock_identity: config.clock_identity, diff --git a/statime/src/datastructures/datasets/mod.rs b/statime/src/datastructures/datasets/mod.rs index c404a3b43..a0084bffe 100644 --- a/statime/src/datastructures/datasets/mod.rs +++ b/statime/src/datastructures/datasets/mod.rs @@ -1,6 +1,6 @@ -pub(crate) use current::CurrentDS; -pub(crate) use default::DefaultDS; -pub(crate) use parent::ParentDS; +pub(crate) use current::InternalCurrentDS; +pub(crate) use default::InternalDefaultDS; +pub(crate) use parent::InternalParentDS; pub use time_properties::TimePropertiesDS; mod current; diff --git a/statime/src/datastructures/datasets/parent.rs b/statime/src/datastructures/datasets/parent.rs index 3ce59371b..e53dcb694 100644 --- a/statime/src/datastructures/datasets/parent.rs +++ b/statime/src/datastructures/datasets/parent.rs @@ -1,9 +1,9 @@ -use super::DefaultDS; +use super::InternalDefaultDS; use crate::datastructures::common::{ClockIdentity, ClockQuality, PortIdentity}; // TODO: Discuss moving this (and TimePropertiesDS, ...) to slave? #[derive(Clone, Debug, Eq, PartialEq)] -pub(crate) struct ParentDS { +pub(crate) struct InternalParentDS { pub(crate) parent_port_identity: PortIdentity, pub(crate) parent_stats: bool, pub(crate) observed_parent_offset_scaled_log_variance: u16, @@ -14,9 +14,9 @@ pub(crate) struct ParentDS { pub(crate) grandmaster_priority_2: u8, } -impl ParentDS { - pub(crate) fn new(default_ds: DefaultDS) -> Self { - ParentDS { +impl InternalParentDS { + pub(crate) fn new(default_ds: InternalDefaultDS) -> Self { + InternalParentDS { parent_port_identity: PortIdentity { clock_identity: default_ds.clock_identity, port_number: 0, diff --git a/statime/src/datastructures/datasets/time_properties.rs b/statime/src/datastructures/datasets/time_properties.rs index 6bff781f1..ad37eeba1 100644 --- a/statime/src/datastructures/datasets/time_properties.rs +++ b/statime/src/datastructures/datasets/time_properties.rs @@ -7,13 +7,22 @@ use crate::datastructures::common::{LeapIndicator, TimeSource}; /// /// For more details see *IEEE1588-2019 section 8.2.4*. #[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TimePropertiesDS { - pub(crate) current_utc_offset: Option, - pub(crate) leap_indicator: LeapIndicator, - pub(crate) time_traceable: bool, - pub(crate) frequency_traceable: bool, - pub(crate) ptp_timescale: bool, - pub(crate) time_source: TimeSource, + /// The offset off UTC time compared to TAI time in seconds. + pub current_utc_offset: Option, + /// Describes upcoming leap seconds. + pub leap_indicator: LeapIndicator, + /// Wheter the timescale is tracable to a primary reference + pub time_traceable: bool, + /// Wheter the frequence determining the timescale is tracable to a primary + /// reference. True when the timescale is PTP, false when the timescale is + /// ARB. + pub frequency_traceable: bool, + /// Wheter the timescale of the Grandmaster PTP Instance is PTP. + pub ptp_timescale: bool, + /// The time source used by the Grandmaster PTP instance. + pub time_source: TimeSource, } impl TimePropertiesDS { diff --git a/statime/src/datastructures/messages/header.rs b/statime/src/datastructures/messages/header.rs index a48631aae..33d568950 100644 --- a/statime/src/datastructures/messages/header.rs +++ b/statime/src/datastructures/messages/header.rs @@ -157,6 +157,7 @@ impl Default for Header { /// assert!(SdoId::try_from(0x1000).is_err()); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SdoId(u16); impl core::fmt::Display for SdoId { diff --git a/statime/src/datastructures/messages/mod.rs b/statime/src/datastructures/messages/mod.rs index c913093f4..58fd2ccdb 100644 --- a/statime/src/datastructures/messages/mod.rs +++ b/statime/src/datastructures/messages/mod.rs @@ -13,7 +13,7 @@ pub(crate) use sync::*; use self::{management::ManagementMessage, signalling::SignalingMessage}; use super::{ common::{PortIdentity, TimeInterval, TlvSet, WireTimestamp}, - datasets::DefaultDS, + datasets::InternalDefaultDS, WireFormatError, }; use crate::{ @@ -223,7 +223,11 @@ impl MessageBody { } } -fn base_header(default_ds: &DefaultDS, port_identity: PortIdentity, sequence_id: u16) -> Header { +fn base_header( + default_ds: &InternalDefaultDS, + port_identity: PortIdentity, + sequence_id: u16, +) -> Header { Header { sdo_id: default_ds.sdo_id, domain_number: default_ds.domain_number, @@ -235,7 +239,7 @@ fn base_header(default_ds: &DefaultDS, port_identity: PortIdentity, sequence_id: impl Message<'_> { pub(crate) fn sync( - default_ds: &DefaultDS, + default_ds: &InternalDefaultDS, port_identity: PortIdentity, sequence_id: u16, ) -> Self { @@ -254,7 +258,7 @@ impl Message<'_> { } pub(crate) fn follow_up( - default_ds: &DefaultDS, + default_ds: &InternalDefaultDS, port_identity: PortIdentity, sequence_id: u16, timestamp: Time, @@ -310,7 +314,7 @@ impl Message<'_> { } pub(crate) fn delay_req( - default_ds: &DefaultDS, + default_ds: &InternalDefaultDS, port_identity: PortIdentity, sequence_id: u16, ) -> Self { @@ -361,7 +365,7 @@ impl Message<'_> { } pub(crate) fn pdelay_req( - default_ds: &DefaultDS, + default_ds: &InternalDefaultDS, port_identity: PortIdentity, sequence_id: u16, ) -> Self { @@ -375,7 +379,7 @@ impl Message<'_> { } pub(crate) fn pdelay_resp( - default_ds: &DefaultDS, + default_ds: &InternalDefaultDS, port_identity: PortIdentity, request_header: Header, timestamp: Time, @@ -396,7 +400,7 @@ impl Message<'_> { } pub(crate) fn pdelay_resp_follow_up( - default_ds: &DefaultDS, + default_ds: &InternalDefaultDS, port_identity: PortIdentity, requestor_identity: PortIdentity, sequence_id: u16, diff --git a/statime/src/filters/kalman.rs b/statime/src/filters/kalman.rs index 463ed672a..6c03daf0e 100644 --- a/statime/src/filters/kalman.rs +++ b/statime/src/filters/kalman.rs @@ -98,7 +98,7 @@ fn chi_1(chi: f64) -> f64 { const A4: f64 = -1.453152027; const A5: f64 = 1.061405429; - let x = (chi / 2.).max(0.0).sqrt(); + let x = (chi / 2.).sqrt(); let t = 1. / (1. + P * x); (A1 * t + A2 * t * t + A3 * t * t * t + A4 * t * t * t * t + A5 * t * t * t * t * t) * (-(x * x)).exp() @@ -181,7 +181,7 @@ impl MeasurementErrorEstimator { ); log::info!( "New uncertainty estimate: {}ns", - self.measurement_variance(config).max(0.0).sqrt() * 1e9, + self.measurement_variance(config).sqrt() * 1e9, ); } else { self.last_sync = Some((m.event_time, sync_offset)); @@ -200,7 +200,7 @@ impl MeasurementErrorEstimator { ); log::info!( "New uncertainty estimate: {}ns", - self.measurement_variance(config).max(0.0).sqrt() * 1e9, + self.measurement_variance(config).sqrt() * 1e9, ); } else { self.last_delay = Some((m.event_time, delay_offset)); @@ -443,7 +443,7 @@ impl BaseFilter { fn offset_uncertainty(&self, config: &KalmanConfiguration) -> f64 { self.0 .as_ref() - .map(|inner| inner.uncertainty.entry(0, 0).max(0.0).sqrt()) + .map(|inner| inner.uncertainty.entry(0, 0).sqrt()) .unwrap_or(config.step_threshold.seconds()) } @@ -457,7 +457,7 @@ impl BaseFilter { fn freq_offset_uncertainty(&self, config: &KalmanConfiguration) -> f64 { self.0 .as_ref() - .map(|inner| inner.uncertainty.entry(1, 1).max(0.0).sqrt()) + .map(|inner| inner.uncertainty.entry(1, 1).sqrt()) .unwrap_or(config.initial_frequency_uncertainty) } @@ -471,7 +471,7 @@ impl BaseFilter { fn mean_delay_uncertainty(&self, config: &KalmanConfiguration) -> f64 { self.0 .as_ref() - .map(|inner| inner.uncertainty.entry(2, 2).max(0.0).sqrt()) + .map(|inner| inner.uncertainty.entry(2, 2).sqrt()) .unwrap_or(config.step_threshold.seconds()) } @@ -531,7 +531,6 @@ impl Filter for KalmanFilter { wander: config.initial_wander, wander_measurement_error: measurement_error_estimator .measurement_variance(&config) - .max(0.0) .sqrt(), measurement_error_estimator, cur_frequency: None, @@ -704,25 +703,20 @@ impl KalmanFilter { } fn wander_score_update(&mut self, uncertainty: f64, prediction: f64, actual: f64) { - log::info!( - "Wander uncertainty: {}ns", - uncertainty.max(0.0).sqrt() * 1e9 - ); + log::info!("Wander uncertainty: {}ns", uncertainty.sqrt() * 1e9); if self.wander_measurement_error > 10.0 * self .measurement_error_estimator .measurement_variance(&self.config) - .max(0.0) .sqrt() { self.wander_filter = self.running_filter.clone(); self.wander_measurement_error = self .measurement_error_estimator .measurement_variance(&self.config) - .max(0.0) .sqrt() - } else if uncertainty.max(0.0).sqrt() > 10.0 * self.wander_measurement_error { + } else if uncertainty.sqrt() > 10.0 * self.wander_measurement_error { log::info!( "Wander update predict: {}ns, actual: {}ns", prediction * 1e9, @@ -730,8 +724,7 @@ impl KalmanFilter { ); let p = 1. - chi_1( - sqr(actual - prediction) - / (uncertainty + sqr(self.wander_measurement_error)).max(f64::EPSILON), + sqr(actual - prediction) / (uncertainty + sqr(self.wander_measurement_error)), ); log::info!("p: {}", p); if p < self.config.precision_low_probability { @@ -746,7 +739,6 @@ impl KalmanFilter { self.wander_measurement_error = self .measurement_error_estimator .measurement_variance(&self.config) - .max(0.0) .sqrt(); } } diff --git a/statime/src/lib.rs b/statime/src/lib.rs index 30bd6401d..6e9771528 100644 --- a/statime/src/lib.rs +++ b/statime/src/lib.rs @@ -89,6 +89,7 @@ pub mod config; pub(crate) mod datastructures; pub mod filters; mod float_polyfill; +pub mod observability; pub mod port; mod ptp_instance; pub mod time; diff --git a/statime/src/observability/current.rs b/statime/src/observability/current.rs new file mode 100644 index 000000000..74a4a75a3 --- /dev/null +++ b/statime/src/observability/current.rs @@ -0,0 +1,24 @@ +use crate::datastructures::datasets::InternalCurrentDS; + +/// A concrete implementation of the PTP Current dataset (IEEE1588-2019 section +/// 8.2.2) +#[derive(Debug, Default, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CurrentDS { + /// See *IEEE1588-2019 section 8.2.2.2*. + pub steps_removed: u16, + /// See *IEEE1588-2019 section 8.2.2.3*. + pub offset_from_master: i128, + /// See *IEEE1588-2019 section 8.2.2.4*. + pub mean_delay: i128, +} + +impl From<&InternalCurrentDS> for CurrentDS { + fn from(v: &InternalCurrentDS) -> Self { + Self { + steps_removed: v.steps_removed, + offset_from_master: v.offset_from_master.nanos_rounded(), + mean_delay: v.mean_delay.nanos_rounded(), + } + } +} diff --git a/statime/src/observability/default.rs b/statime/src/observability/default.rs new file mode 100644 index 000000000..5095eda1e --- /dev/null +++ b/statime/src/observability/default.rs @@ -0,0 +1,43 @@ +use crate::datastructures::datasets::InternalDefaultDS; + +/// A concrete implementation of the PTP Current dataset (IEEE1588-2019 section +/// 8.2.1) +/// +/// See [InternalDefaultDS](crate::datastructures::datasets::InternalDefaultDS). +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DefaultDS { + /// The identity of a PTP node. + /// See *IEEE1588-2019 section 8.2.1.2.2*. + pub clock_identity: crate::config::ClockIdentity, + /// The amount of PTP ports on this PTP instance. + /// See *IEEE1588-2019 section 8.2.1.2.3*. + pub number_ports: u16, + /// A description of the accuracy and type of a clock. + pub clock_quality: crate::config::ClockQuality, + /// See *IEEE1588-2019 section 8.2.1.4.1*. + pub priority_1: u8, + /// See *IEEE1588-2019 section 8.2.1.4.2*. + pub priority_2: u8, + /// See *IEEE1588-2019 section 8.2.1.4.3*. + pub domain_number: u8, + /// See *IEEE1588-2019 section 8.2.1.4.4*. + pub slave_only: bool, + /// See *IEEE1588-2019 section 7.1.4 table 2*. + pub sdo_id: crate::config::SdoId, +} + +impl From<&InternalDefaultDS> for DefaultDS { + fn from(v: &InternalDefaultDS) -> Self { + Self { + clock_identity: v.clock_identity, + number_ports: v.number_ports, + clock_quality: v.clock_quality, + priority_1: v.priority_1, + priority_2: v.priority_2, + domain_number: v.domain_number, + slave_only: v.slave_only, + sdo_id: v.sdo_id, + } + } +} diff --git a/statime/src/observability/mod.rs b/statime/src/observability/mod.rs new file mode 100644 index 000000000..e845c283e --- /dev/null +++ b/statime/src/observability/mod.rs @@ -0,0 +1,25 @@ +//! Serializable implementations of datastructures to be used for observability +/// A concrete implementation of the PTP Current dataset (IEEE1588-2019 section 8.2.2) +pub mod current; +/// A concrete implementation of the PTP Default dataset (IEEE1588-2019 section 8.2.1) +pub mod default; +/// A concrete implementation of the PTP Parent dataset (IEEE1588-2019 section 8.2.3) +pub mod parent; + +use crate::datastructures::datasets::TimePropertiesDS; + +use self::{current::CurrentDS, default::DefaultDS, parent::ParentDS}; + +/// Observable version of the InstanceState struct +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ObservableInstanceState { + /// A concrete implementation of the PTP Default dataset (IEEE1588-2019 section 8.2.1) + pub default_ds: DefaultDS, + /// A concrete implementation of the PTP Current dataset (IEEE1588-2019 section 8.2.2) + pub current_ds: CurrentDS, + /// A concrete implementation of the PTP Parent dataset (IEEE1588-2019 section 8.2.3) + pub parent_ds: ParentDS, + /// A concrete implementation of the PTP Time Properties dataset (IEEE1588-2019 section 8.2.4) + pub time_properties_ds: TimePropertiesDS, +} diff --git a/statime/src/observability/parent.rs b/statime/src/observability/parent.rs new file mode 100644 index 000000000..503148a5c --- /dev/null +++ b/statime/src/observability/parent.rs @@ -0,0 +1,43 @@ +use crate::{ + config::{ClockIdentity, ClockQuality}, + datastructures::{common::PortIdentity, datasets::InternalParentDS}, +}; + +/// A concrete implementation of the PTP Current dataset (IEEE1588-2019 section +/// 8.2.3) +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ParentDS { + /// See *IEEE1588-2019 section 8.2.3.2*. + pub parent_port_identity: PortIdentity, + /// See *IEEE1588-2019 section 8.2.3.3*. + pub parent_stats: bool, + /// See *IEEE1588-2019 section 8.2.3.4*. + pub observed_parent_offset_scaled_log_variance: u16, + /// See *IEEE1588-2019 section 8.2.3.5*. + pub observed_parent_clock_phase_change_rate: u32, + /// See *IEEE1588-2019 section 8.2.3.6*. + pub grandmaster_identity: ClockIdentity, + /// See *IEEE1588-2019 section 8.2.3.7*. + pub grandmaster_clock_quality: ClockQuality, + /// See *IEEE1588-2019 section 8.2.3.8*. + pub grandmaster_priority_1: u8, + /// See *IEEE1588-2019 section 8.2.3.9*. + pub grandmaster_priority_2: u8, +} + +impl From<&InternalParentDS> for ParentDS { + fn from(v: &InternalParentDS) -> Self { + Self { + parent_port_identity: v.parent_port_identity, + parent_stats: v.parent_stats, + observed_parent_offset_scaled_log_variance: v + .observed_parent_offset_scaled_log_variance, + observed_parent_clock_phase_change_rate: v.observed_parent_clock_phase_change_rate, + grandmaster_identity: v.grandmaster_identity, + grandmaster_clock_quality: v.grandmaster_clock_quality, + grandmaster_priority_1: v.grandmaster_priority_1, + grandmaster_priority_2: v.grandmaster_priority_2, + } + } +} diff --git a/statime/src/port/bmca.rs b/statime/src/port/bmca.rs index df1a4a1d1..2e6abf1aa 100644 --- a/statime/src/port/bmca.rs +++ b/statime/src/port/bmca.rs @@ -5,7 +5,7 @@ use crate::{ bmc::bmca::{BestAnnounceMessage, RecommendedState}, config::{AcceptableMasterList, LeapIndicator, TimePropertiesDS, TimeSource}, datastructures::{ - datasets::{CurrentDS, DefaultDS, ParentDS}, + datasets::{InternalCurrentDS, InternalDefaultDS, InternalParentDS}, messages::Message, }, filters::Filter, @@ -73,9 +73,9 @@ impl<'a, A, C: Clock, F: Filter, R: Rng> Port, A, R, C, F> { &mut self, recommended_state: RecommendedState, time_properties_ds: &mut TimePropertiesDS, - current_ds: &mut CurrentDS, - parent_ds: &mut ParentDS, - default_ds: &DefaultDS, + current_ds: &mut InternalCurrentDS, + parent_ds: &mut InternalParentDS, + default_ds: &InternalDefaultDS, ) { self.set_recommended_port_state(&recommended_state, default_ds); @@ -134,7 +134,7 @@ impl<'a, A, C: Clock, F: Filter, R: Rng> Port, A, R, C, F> { fn set_recommended_port_state( &mut self, recommended_state: &RecommendedState, - default_ds: &DefaultDS, + default_ds: &InternalDefaultDS, ) { match recommended_state { // TODO set things like steps_removed once they are added diff --git a/statime/src/port/mod.rs b/statime/src/port/mod.rs index f50aa6a19..15b0549e1 100644 --- a/statime/src/port/mod.rs +++ b/statime/src/port/mod.rs @@ -121,7 +121,6 @@ pub(crate) mod state; /// use statime::PtpInstance; /// use statime::time::Interval; /// -/// # let (instance_config, time_properties_ds) = unimplemented!(); /// let mut instance = PtpInstance::::new(instance_config, time_properties_ds); /// /// // TODO make these values sensible @@ -631,7 +630,7 @@ mod tests { use super::*; use crate::{ config::{AcceptAnyMaster, DelayMechanism, InstanceConfig, TimePropertiesDS}, - datastructures::datasets::{DefaultDS, ParentDS}, + datastructures::datasets::{InternalDefaultDS, InternalParentDS}, filters::BasicFilter, time::{Duration, Interval, Time}, Clock, @@ -717,7 +716,7 @@ mod tests { } pub(super) fn setup_test_state() -> AtomicRefCell { - let default_ds = DefaultDS::new(InstanceConfig { + let default_ds = InternalDefaultDS::new(InstanceConfig { clock_identity: Default::default(), priority_1: 255, priority_2: 255, @@ -726,7 +725,7 @@ mod tests { sdo_id: Default::default(), }); - let parent_ds = ParentDS::new(default_ds); + let parent_ds = InternalParentDS::new(default_ds); let state = AtomicRefCell::new(PtpInstanceState { default_ds, diff --git a/statime/src/ptp_instance.rs b/statime/src/ptp_instance.rs index 2f4cd3f24..00317cef1 100644 --- a/statime/src/ptp_instance.rs +++ b/statime/src/ptp_instance.rs @@ -14,9 +14,12 @@ use crate::{ config::{InstanceConfig, PortConfig}, datastructures::{ common::PortIdentity, - datasets::{CurrentDS, DefaultDS, ParentDS, TimePropertiesDS}, + datasets::{InternalCurrentDS, InternalDefaultDS, InternalParentDS, TimePropertiesDS}, }, filters::Filter, + observability::{ + current::CurrentDS, default::DefaultDS, parent::ParentDS, ObservableInstanceState, + }, port::{InBmca, Port}, time::Duration, }; @@ -71,6 +74,7 @@ use crate::{ /// let mut instance = PtpInstance::::new( /// instance_config, /// time_properties_ds, +/// instance_sender, /// ); /// /// let mut port = instance.add_port(port_config, filter_config, clock, rng); @@ -90,9 +94,9 @@ pub struct PtpInstance { #[derive(Debug)] pub(crate) struct PtpInstanceState { - pub(crate) default_ds: DefaultDS, - pub(crate) current_ds: CurrentDS, - pub(crate) parent_ds: ParentDS, + pub(crate) default_ds: InternalDefaultDS, + pub(crate) current_ds: InternalCurrentDS, + pub(crate) parent_ds: InternalParentDS, pub(crate) time_properties_ds: TimePropertiesDS, } @@ -149,18 +153,39 @@ impl PtpInstance { /// Construct a new [`PtpInstance`] with the given config and time /// properties pub fn new(config: InstanceConfig, time_properties_ds: TimePropertiesDS) -> Self { - let default_ds = DefaultDS::new(config); + let default_ds = InternalDefaultDS::new(config); + Self { state: AtomicRefCell::new(PtpInstanceState { default_ds, current_ds: Default::default(), - parent_ds: ParentDS::new(default_ds), + parent_ds: InternalParentDS::new(default_ds), time_properties_ds, }), log_bmca_interval: AtomicI8::new(i8::MAX), _filter: PhantomData, } } + + /// Return IEEE-1588 defaultDS for introspection + pub fn default_ds(&self) -> DefaultDS { + (&self.state.borrow().default_ds).into() + } + + /// Return IEEE-1588 currentDS for introspection + pub fn current_ds(&self) -> CurrentDS { + (&self.state.borrow().current_ds).into() + } + + /// Return IEEE-1588 parentDS for introspection + pub fn parent_ds(&self) -> ParentDS { + (&self.state.borrow().parent_ds).into() + } + + /// Return IEEE-1588 timePropertiesDS for introspection + pub fn time_properties_ds(&self) -> TimePropertiesDS { + self.state.borrow().time_properties_ds + } } impl PtpInstance { @@ -187,6 +212,7 @@ impl PtpInstance { port_number: state.default_ds.number_ports, }; state.default_ds.number_ports += 1; + Port::new( &self.state, config, @@ -219,4 +245,15 @@ impl PtpInstance { 2f64.powi(self.log_bmca_interval.load(Ordering::Relaxed) as i32), ) } + + /// Read the current instance state in a serializable format + pub fn observe_state(&self) -> ObservableInstanceState { + let state = self.state.borrow(); + ObservableInstanceState { + default_ds: (&state.default_ds).into(), + current_ds: (&state.current_ds).into(), + parent_ds: (&state.parent_ds.clone()).into(), + time_properties_ds: state.time_properties_ds, + } + } } From 8abb479ce1fdc63b7c836a8918b518181b5e1add Mon Sep 17 00:00:00 2001 From: marlonp Date: Thu, 15 Feb 2024 15:05:32 +0100 Subject: [PATCH 2/5] Added Deserialize impl for SdoId --- statime/src/datastructures/messages/header.rs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/statime/src/datastructures/messages/header.rs b/statime/src/datastructures/messages/header.rs index 33d568950..ee04b76a8 100644 --- a/statime/src/datastructures/messages/header.rs +++ b/statime/src/datastructures/messages/header.rs @@ -157,7 +157,7 @@ impl Default for Header { /// assert!(SdoId::try_from(0x1000).is_err()); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct SdoId(u16); impl core::fmt::Display for SdoId { @@ -176,6 +176,38 @@ impl SdoId { } } +#[cfg(feature = "serde")] +struct SdoIdVisitor; + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for SdoId { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_u16(SdoIdVisitor) + } +} + +#[cfg(all(feature = "serde", feature = "std"))] +impl<'de> serde::de::Visitor<'de> for SdoIdVisitor { + type Value = SdoId; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("a 12 bit value within the 0..=0xFFF range") + } + + fn visit_u16(self, v: u16) -> Result + where + E: serde::de::Error, + { + SdoId::try_from(v).or(Err(E::custom(std::format!( + "SdoId not in range of 0..=0xFFF: {}", + v + )))) + } +} + impl TryFrom for SdoId { type Error = (); From 7a0b878f17814459c78a5f8d511e18b9c29dbccb Mon Sep 17 00:00:00 2001 From: marlonp Date: Thu, 15 Feb 2024 15:08:04 +0100 Subject: [PATCH 3/5] Fixed example code in docs --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + statime/Cargo.toml | 3 +++ statime/src/datastructures/messages/header.rs | 20 +++++++++++++++---- statime/src/port/mod.rs | 1 + statime/src/ptp_instance.rs | 1 - 6 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c095e4a89..67d481b2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -496,6 +496,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_test" +version = "1.0.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab" +dependencies = [ + "serde", +] + [[package]] name = "socket2" version = "0.5.5" @@ -518,6 +527,7 @@ dependencies = [ "log", "rand", "serde", + "serde_test", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d10c02446..b1b661ab5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ tokio = "1.33" rand = { version = "0.8.5", default-features = false } serde = { version = "1.0.192", features = ["derive"] } serde_json = { version = "1.0.111" } +serde_test = { version = "1.0.176" } az = "1.2.1" fixed = "1.24" libm = "0.2.8" diff --git a/statime/Cargo.toml b/statime/Cargo.toml index 10d175780..aa93f14b7 100644 --- a/statime/Cargo.toml +++ b/statime/Cargo.toml @@ -26,3 +26,6 @@ log = { workspace = true, default-features = false} rand = { workspace = true, default-features = false } atomic_refcell.workspace = true serde = { workspace = true, optional = true } + +[dev-dependencies] +serde_test.workspace = true diff --git a/statime/src/datastructures/messages/header.rs b/statime/src/datastructures/messages/header.rs index ee04b76a8..99e31eb90 100644 --- a/statime/src/datastructures/messages/header.rs +++ b/statime/src/datastructures/messages/header.rs @@ -402,9 +402,21 @@ mod tests { #[test] fn sdo_id_checks() { - let sdo_id = SdoId::try_from(0xfff).unwrap(); - assert_eq!(0xfff, u16::from(sdo_id)); - - assert!(SdoId::try_from(0x1000).is_err()); + use serde_test::{assert_de_tokens_error, assert_tokens, Token}; + let correct_sdo_id = SdoId::try_from(0xfff).unwrap(); + let faulty_sdo_id = SdoId::try_from(0x1000); + + assert_eq!(0xfff, u16::from(correct_sdo_id)); + assert!(faulty_sdo_id.is_err()); + + assert_tokens( + &correct_sdo_id, + &[Token::NewtypeStruct { name: "SdoId" }, Token::U16(4095)], + ); + + assert_de_tokens_error::( + &[Token::NewtypeStruct { name: "SdoId" }, Token::U16(4096)], + "invalid type: newtype struct, expected a 12 bit value within the 0..=0xFFF range", + ); } } diff --git a/statime/src/port/mod.rs b/statime/src/port/mod.rs index 15b0549e1..b900b85c3 100644 --- a/statime/src/port/mod.rs +++ b/statime/src/port/mod.rs @@ -115,6 +115,7 @@ pub(crate) mod state; /// # } /// # } /// # } +/// # let (instance_config, time_properties_ds) = unimplemented!(); /// use rand::thread_rng; /// use statime::config::{AcceptAnyMaster, DelayMechanism, PortConfig}; /// use statime::filters::BasicFilter; diff --git a/statime/src/ptp_instance.rs b/statime/src/ptp_instance.rs index 00317cef1..ba208028e 100644 --- a/statime/src/ptp_instance.rs +++ b/statime/src/ptp_instance.rs @@ -74,7 +74,6 @@ use crate::{ /// let mut instance = PtpInstance::::new( /// instance_config, /// time_properties_ds, -/// instance_sender, /// ); /// /// let mut port = instance.add_port(port_config, filter_config, clock, rng); From cc37eb1f3fcf6bca2dcc266b01aaa9ababa73921 Mon Sep 17 00:00:00 2001 From: marlonp Date: Fri, 16 Feb 2024 11:14:13 +0100 Subject: [PATCH 4/5] Added visitor for newtype struct --- statime/src/datastructures/messages/header.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/statime/src/datastructures/messages/header.rs b/statime/src/datastructures/messages/header.rs index 99e31eb90..6c98a661e 100644 --- a/statime/src/datastructures/messages/header.rs +++ b/statime/src/datastructures/messages/header.rs @@ -185,7 +185,7 @@ impl<'de> serde::Deserialize<'de> for SdoId { where D: serde::Deserializer<'de>, { - deserializer.deserialize_u16(SdoIdVisitor) + deserializer.deserialize_newtype_struct("SdoId", SdoIdVisitor) } } @@ -197,6 +197,19 @@ impl<'de> serde::de::Visitor<'de> for SdoIdVisitor { formatter.write_str("a 12 bit value within the 0..=0xFFF range") } + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + use serde::Deserialize; + let v = u16::deserialize(deserializer)?; + SdoId::try_from(v).or(Err(D::Error::custom(std::format!( + "SdoId not in range of 0..=0xFFF: {}", + v + )))) + } + fn visit_u16(self, v: u16) -> Result where E: serde::de::Error, @@ -416,7 +429,7 @@ mod tests { assert_de_tokens_error::( &[Token::NewtypeStruct { name: "SdoId" }, Token::U16(4096)], - "invalid type: newtype struct, expected a 12 bit value within the 0..=0xFFF range", + "SdoId not in range of 0..=0xFFF: 4096", ); } } From 8123fb36bae037ea90b4047351ff31631b2df226 Mon Sep 17 00:00:00 2001 From: marlonp Date: Mon, 19 Feb 2024 09:54:57 +0100 Subject: [PATCH 5/5] removed unnecessary clone --- statime/src/ptp_instance.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/statime/src/ptp_instance.rs b/statime/src/ptp_instance.rs index ba208028e..714f888a6 100644 --- a/statime/src/ptp_instance.rs +++ b/statime/src/ptp_instance.rs @@ -251,7 +251,7 @@ impl PtpInstance { ObservableInstanceState { default_ds: (&state.default_ds).into(), current_ds: (&state.current_ds).into(), - parent_ds: (&state.parent_ds.clone()).into(), + parent_ds: (&state.parent_ds).into(), time_properties_ds: state.time_properties_ds, } }