Skip to main content

statime_wire/messages/
header.rs

1use super::MessageType;
2use crate::{
3    Error,
4    common::{PortIdentity, TimeInterval},
5};
6
7/// The header of a PTP version 2 message.
8///
9/// For more details, see *IEEE1588-2019 section 13.3*.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[expect(
12    clippy::struct_excessive_bools,
13    reason = "Struct is a direct representation of the PTP header, which has a lot of boolean elements which don't all map well to enums."
14)]
15pub struct Header {
16    /// Sdo ID of the profile in use for the message.
17    pub sdo_id: SdoId,
18    /// Specific version of PTP for this message.
19    pub version: PtpVersion,
20    /// The domain number of the message.
21    ///
22    /// This is used to allow multiple PTP setups to run within the same network
23    /// without interfering with each other.
24    pub domain_number: u8,
25    /// Indicates whether the sender is an alternate master.
26    ///
27    /// This is used with the Grandmaster Cluster and Alternate Master optional
28    /// PTP features. For more details, see *IEEE1588-2019 sections 17.2 and17.3*
29    pub alternate_master_flag: bool,
30    /// Indicates whether the message is part of a two-step time transmission.
31    pub two_step_flag: bool,
32    /// Indicates whether the message is sent via a unicast connection.
33    pub unicast_flag: bool,
34    /// Used for PTP profile-specific information.
35    pub ptp_profile_specific_1: bool,
36    /// Used for PTP profile-specific information.
37    pub ptp_profile_specific_2: bool,
38    /// In announce messages, indicates whether the last minute of the current
39    /// UTC day shall have 61 seconds.
40    pub leap61: bool,
41    /// In announce messages, indicates whether the last minute of the current
42    /// UTC day shall have 59 seconds.
43    pub leap59: bool,
44    /// In announce messages, indicates whether the offset between UTC and TAI
45    /// is valid.
46    pub current_utc_offset_valid: bool,
47    /// In announce messages, indicates whether the timescale is TAI (when
48    /// true), or something else.
49    pub ptp_timescale: bool,
50    /// In announce messages, indicates whether the time provided is traceable
51    /// in the metrology sense.
52    pub time_tracable: bool,
53    /// In announce messages, indicates whether the frequency provided is
54    /// traceable in the metrology sense.
55    pub frequency_tracable: bool,
56    /// In announce messages, a true value indicates that the time provider may
57    /// not itself be properly synchronized.
58    pub synchronization_uncertain: bool,
59    /// Correction field to the timestamps in event messages.
60    ///
61    /// This is used to improve the precision of those timestamps, as well as
62    /// to allow for compensating for the dwell time of messages in switches.
63    pub correction_field: TimeInterval,
64    /// Identity of the PTP port that sent this message.
65    pub source_port_identity: PortIdentity,
66    /// Rolling sequence id for the message.
67    pub sequence_id: u16,
68    /// Used for communication of message sending intervals for some messages
69    ///
70    /// See *IEEE1588 Table 42 in section 13.3.2.14* for more details.
71    pub log_message_interval: i8,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub(crate) struct DeserializedHeader {
76    pub(crate) header: Header,
77    pub(crate) message_type: MessageType,
78    pub(crate) message_length: u16,
79}
80
81impl Header {
82    /// Get a new header initialized with default values for all of the fields.
83    #[must_use]
84    pub fn new(minor_ptp_version: u8) -> Self {
85        Self {
86            sdo_id: SdoId(0),
87            version: PtpVersion {
88                major: 2,
89                minor: minor_ptp_version,
90            },
91            domain_number: 0,
92            alternate_master_flag: false,
93            two_step_flag: false,
94            unicast_flag: false,
95            ptp_profile_specific_1: false,
96            ptp_profile_specific_2: false,
97            leap59: false,
98            leap61: false,
99            current_utc_offset_valid: false,
100            ptp_timescale: false,
101            time_tracable: false,
102            frequency_tracable: false,
103            synchronization_uncertain: false,
104            correction_field: TimeInterval::default(),
105            source_port_identity: PortIdentity::default(),
106            sequence_id: 0,
107            log_message_interval: 0,
108        }
109    }
110
111    pub(crate) fn wire_size(&self) -> usize {
112        34
113    }
114
115    pub(crate) fn serialize_header(
116        &self,
117        content_type: MessageType,
118        content_length: usize,
119        buffer: &mut [u8],
120    ) -> Result<(), Error> {
121        let message_length =
122            u16::try_from(content_length + self.wire_size()).map_err(|_| Error::Invalid)?;
123        buffer[0] = ((self.sdo_id.high_byte()) << 4) | ((content_type as u8) & 0x0f);
124        buffer[1] = self.version.as_byte();
125        buffer[2..4].copy_from_slice(&message_length.to_be_bytes());
126        buffer[4] = self.domain_number;
127        buffer[5] = self.sdo_id.low_byte();
128        buffer[6] = 0;
129        buffer[7] = 0;
130        buffer[6] |= u8::from(self.alternate_master_flag);
131        buffer[6] |= u8::from(self.two_step_flag) << 1;
132        buffer[6] |= u8::from(self.unicast_flag) << 2;
133        buffer[6] |= u8::from(self.ptp_profile_specific_1) << 5;
134        buffer[6] |= u8::from(self.ptp_profile_specific_2) << 6;
135        buffer[7] |= u8::from(self.leap61);
136        buffer[7] |= u8::from(self.leap59) << 1;
137        buffer[7] |= u8::from(self.current_utc_offset_valid) << 2;
138        buffer[7] |= u8::from(self.ptp_timescale) << 3;
139        buffer[7] |= u8::from(self.time_tracable) << 4;
140        buffer[7] |= u8::from(self.frequency_tracable) << 5;
141        buffer[7] |= u8::from(self.synchronization_uncertain) << 6;
142        self.correction_field.serialize(&mut buffer[8..16])?;
143        buffer[16..20].copy_from_slice(&[0, 0, 0, 0]);
144        self.source_port_identity.serialize(&mut buffer[20..30])?;
145        buffer[30..32].copy_from_slice(&self.sequence_id.to_be_bytes());
146        buffer[32] = 0;
147        buffer[33] = self.log_message_interval.cast_unsigned();
148
149        Ok(())
150    }
151
152    pub(crate) fn deserialize_header(buffer: &[u8]) -> Result<DeserializedHeader, Error> {
153        if buffer.len() < 34 {
154            return Err(Error::BufferTooShort);
155        }
156
157        let version = PtpVersion::from_byte(buffer[1]);
158        let sdo_id = SdoId((u16::from(buffer[0] & 0xf0) << 4) | u16::from(buffer[5]));
159
160        Ok(DeserializedHeader {
161            header: Self {
162                sdo_id,
163                version,
164                domain_number: buffer[4],
165                alternate_master_flag: (buffer[6] & (1 << 0)) > 0,
166                two_step_flag: (buffer[6] & (1 << 1)) > 0,
167                unicast_flag: (buffer[6] & (1 << 2)) > 0,
168                ptp_profile_specific_1: (buffer[6] & (1 << 5)) > 0,
169                ptp_profile_specific_2: (buffer[6] & (1 << 6)) > 0,
170                leap61: (buffer[7] & (1 << 0)) > 0,
171                leap59: (buffer[7] & (1 << 1)) > 0,
172                current_utc_offset_valid: (buffer[7] & (1 << 2)) > 0,
173                ptp_timescale: (buffer[7] & (1 << 3)) > 0,
174                time_tracable: (buffer[7] & (1 << 4)) > 0,
175                frequency_tracable: (buffer[7] & (1 << 5)) > 0,
176                synchronization_uncertain: (buffer[7] & (1 << 6)) > 0,
177                correction_field: TimeInterval::deserialize(&buffer[8..16])?,
178                source_port_identity: PortIdentity::deserialize(&buffer[20..30])?,
179                sequence_id: u16::from_be_bytes(buffer[30..32].try_into().unwrap()),
180                log_message_interval: buffer[33].cast_signed(),
181            },
182            message_type: (buffer[0] & 0x0f).try_into()?,
183            message_length: u16::from_be_bytes(buffer[2..4].try_into().unwrap()),
184        })
185    }
186}
187
188/// A wrapper type for PTP Sdo Identifiers.
189///
190/// Because `SdoId`s are 12 bit values they always lie within `0..=0xFFF`.
191///
192/// For more details, see *IEEE1588-2019 table 2 in section 7.1.4*.
193///
194/// # Example
195/// ```
196/// # use statime_wire::SdoId;
197/// assert_eq!(SdoId::default(), SdoId::try_from(0x000).unwrap());
198///
199/// let sdo_id = SdoId::try_from(0x100).unwrap();
200/// assert_eq!(u16::from(sdo_id), 0x100);
201///
202/// assert!(SdoId::try_from(0x1000).is_err());
203/// ```
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
205#[cfg_attr(feature = "serde", derive(serde::Serialize))]
206pub struct SdoId(u16);
207
208impl core::fmt::Display for SdoId {
209    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210        self.0.fmt(f)
211    }
212}
213
214impl SdoId {
215    const fn high_byte(self) -> u8 {
216        (self.0 >> 8) as u8
217    }
218
219    const fn low_byte(self) -> u8 {
220        (self.0 & 0xFF) as u8
221    }
222}
223
224#[cfg(feature = "serde")]
225struct SdoIdVisitor;
226
227#[cfg(feature = "serde")]
228impl<'de> serde::Deserialize<'de> for SdoId {
229    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
230    where
231        D: serde::Deserializer<'de>,
232    {
233        deserializer.deserialize_newtype_struct("SdoId", SdoIdVisitor)
234    }
235}
236
237#[cfg(feature = "serde")]
238impl<'de> serde::de::Visitor<'de> for SdoIdVisitor {
239    type Value = SdoId;
240
241    fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
242        formatter.write_str("a 12 bit value within the 0..=0xFFF range")
243    }
244
245    fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
246    where
247        D: serde::Deserializer<'de>,
248    {
249        use serde::{Deserialize, de::Error};
250        let v = u16::deserialize(deserializer)?;
251        SdoId::try_from(v).or(Err(D::Error::custom(std::format!(
252            "SdoId not in range of 0..=0xFFF: {v}",
253        ))))
254    }
255
256    fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
257    where
258        E: serde::de::Error,
259    {
260        SdoId::try_from(v).or(Err(E::custom(std::format!(
261            "SdoId not in range of 0..=0xFFF: {v}",
262        ))))
263    }
264}
265
266impl TryFrom<u16> for SdoId {
267    type Error = Error;
268
269    /// Turn a `u16` into a `SdoId` ensuring it is in the range `0..=0xFFF`.
270    fn try_from(sdo_id: u16) -> Result<Self, Self::Error> {
271        (0..=0xfff)
272            .contains(&sdo_id)
273            .then_some(Self(sdo_id))
274            .ok_or(Error::Invalid)
275    }
276}
277
278impl From<SdoId> for u16 {
279    fn from(value: SdoId) -> Self {
280        value.0
281    }
282}
283
284/// The PTP version of a message.
285///
286/// For most uses of this library, the major version of this version number
287/// will be 2.
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub struct PtpVersion {
290    major: u8,
291    minor: u8,
292}
293
294impl PtpVersion {
295    /// Get a new [`PtpVersion`] instance with the given major and minor version.
296    ///
297    /// # Errors
298    /// This fails when either the major or minor version is larger than 15.
299    pub fn new(major: u8, minor: u8) -> Result<Self, Error> {
300        if major >= 0x10 || minor >= 0x10 {
301            Err(Error::Invalid)
302        } else {
303            Ok(Self { major, minor })
304        }
305    }
306
307    /// The major part of the PTP version number.
308    #[must_use]
309    pub fn major(self) -> u8 {
310        self.major
311    }
312
313    /// The minor part of the PTP version number.
314    #[must_use]
315    pub fn minor(self) -> u8 {
316        self.minor
317    }
318
319    fn as_byte(self) -> u8 {
320        (self.minor << 4) | self.major
321    }
322
323    fn from_byte(byte: u8) -> Self {
324        Self {
325            major: byte & 0x0f,
326            minor: byte >> 4,
327        }
328    }
329}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334    use crate::common::ClockIdentity;
335
336    #[test]
337    fn flagfield_wireformat() {
338        #[rustfmt::skip]
339        let representations = [
340            ([0x00, 0x00u8], Header::new(1)),
341            ([0x01, 0x00u8], Header { alternate_master_flag: true, ..Header::new(1) }),
342            ([0x02, 0x00u8], Header { two_step_flag: true, ..Header::new(1) }),
343            ([0x04, 0x00u8], Header { unicast_flag: true, ..Header::new(1) }),
344            ([0x20, 0x00u8], Header { ptp_profile_specific_1: true, ..Header::new(1) }),
345            ([0x40, 0x00u8], Header { ptp_profile_specific_2: true, ..Header::new(1) }),
346            ([0x00, 0x01u8], Header { leap61: true, ..Header::new(1) }),
347            ([0x00, 0x02u8], Header { leap59: true, ..Header::new(1) }),
348            ([0x00, 0x04u8], Header { current_utc_offset_valid: true, ..Header::new(1) }),
349            ([0x00, 0x08u8], Header { ptp_timescale: true, ..Header::new(1) }),
350            ([0x00, 0x10u8], Header { time_tracable: true, ..Header::new(1) }),
351            ([0x00, 0x20u8], Header { frequency_tracable: true, ..Header::new(1) }),
352            ([0x00, 0x40u8], Header { synchronization_uncertain: true, ..Header::new(1) }),
353        ];
354
355        for (i, (byte_representation, flag_representation)) in
356            representations.into_iter().enumerate()
357        {
358            // Test the serialization output
359            let mut serialization_buffer = [0; 34];
360            flag_representation
361                .serialize_header(MessageType::Sync, 0, &mut serialization_buffer)
362                .unwrap();
363            assert_eq!(
364                serialization_buffer[6..8],
365                byte_representation,
366                "The serialized flag field is not what it's supposed to for variant {i}",
367            );
368
369            // Test the deserialization output
370            serialization_buffer[6] = byte_representation[0];
371            serialization_buffer[7] = byte_representation[1];
372            let deserialized_flag_field =
373                Header::deserialize_header(&serialization_buffer).unwrap();
374            assert_eq!(
375                deserialized_flag_field.header, flag_representation,
376                "The deserialized flag field is not what it's supposed to for variant {i}",
377            );
378        }
379    }
380
381    #[test]
382    fn header_wireformat() {
383        let representations = [(
384            [
385                0x59,
386                0xa1,
387                0x12,
388                0x34,
389                0xaa,
390                0xbb,
391                0b0100_0101,
392                0b0010_1010,
393                0x00,
394                0x00,
395                0x00,
396                0x00,
397                0x00,
398                0x01,
399                0x80,
400                0x00,
401                0,
402                0,
403                0,
404                0,
405                0,
406                1,
407                2,
408                3,
409                4,
410                5,
411                6,
412                7,
413                0x55,
414                0x55,
415                0xde,
416                0xad,
417                0x00,
418                0x16,
419            ],
420            DeserializedHeader {
421                header: Header {
422                    sdo_id: SdoId(0x5bb),
423                    version: PtpVersion {
424                        major: 0x1,
425                        minor: 0xa,
426                    },
427                    domain_number: 0xaa,
428                    alternate_master_flag: true,
429                    two_step_flag: false,
430                    unicast_flag: true,
431                    ptp_profile_specific_1: false,
432                    ptp_profile_specific_2: true,
433                    leap61: false,
434                    leap59: true,
435                    current_utc_offset_valid: false,
436                    ptp_timescale: true,
437                    time_tracable: false,
438                    frequency_tracable: true,
439                    synchronization_uncertain: false,
440                    correction_field: TimeInterval::from_nanos(1.5f64).unwrap(),
441                    source_port_identity: PortIdentity {
442                        clock_identity: ClockIdentity([0, 1, 2, 3, 4, 5, 6, 7]),
443                        port_number: 0x5555,
444                    },
445                    sequence_id: 0xdead,
446                    log_message_interval: 0x16,
447                },
448                message_type: MessageType::DelayResp,
449                message_length: 0x1234,
450            },
451        )];
452
453        for (byte_representation, object_representation) in representations {
454            // Test the serialization output
455            let mut serialization_buffer = [0; 34];
456            object_representation
457                .header
458                .serialize_header(
459                    object_representation.message_type,
460                    object_representation.message_length as usize
461                        - object_representation.header.wire_size(),
462                    &mut serialization_buffer,
463                )
464                .unwrap();
465            assert_eq!(serialization_buffer, byte_representation);
466
467            // Test the deserialization output
468            let deserialized_data = Header::deserialize_header(&byte_representation).unwrap();
469            assert_eq!(deserialized_data, object_representation);
470        }
471    }
472
473    #[cfg(feature = "serde")]
474    #[test]
475    fn sdo_id_checks() {
476        use serde_test::{Token, assert_de_tokens_error, assert_tokens};
477        let correct_sdo_id = SdoId::try_from(0xfff).unwrap();
478        let faulty_sdo_id = SdoId::try_from(0x1000);
479
480        assert_eq!(0xfff, u16::from(correct_sdo_id));
481        assert!(faulty_sdo_id.is_err());
482
483        assert_tokens(
484            &correct_sdo_id,
485            &[Token::NewtypeStruct { name: "SdoId" }, Token::U16(4095)],
486        );
487
488        assert_de_tokens_error::<SdoId>(
489            &[Token::NewtypeStruct { name: "SdoId" }, Token::U16(4096)],
490            "SdoId not in range of 0..=0xFFF: 4096",
491        );
492    }
493}