1use super::MessageType;
2use crate::{
3 Error,
4 common::{PortIdentity, TimeInterval},
5};
6
7#[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 pub sdo_id: SdoId,
18 pub version: PtpVersion,
20 pub domain_number: u8,
25 pub alternate_master_flag: bool,
30 pub two_step_flag: bool,
32 pub unicast_flag: bool,
34 pub ptp_profile_specific_1: bool,
36 pub ptp_profile_specific_2: bool,
38 pub leap61: bool,
41 pub leap59: bool,
44 pub current_utc_offset_valid: bool,
47 pub ptp_timescale: bool,
50 pub time_tracable: bool,
53 pub frequency_tracable: bool,
56 pub synchronization_uncertain: bool,
59 pub correction_field: TimeInterval,
64 pub source_port_identity: PortIdentity,
66 pub sequence_id: u16,
68 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 #[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#[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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub struct PtpVersion {
290 major: u8,
291 minor: u8,
292}
293
294impl PtpVersion {
295 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 #[must_use]
309 pub fn major(self) -> u8 {
310 self.major
311 }
312
313 #[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 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 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 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 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}