SatCat5
ptp_client.cc
1 // Copyright 2023-2024 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/log.h>
7 #include <satcat5/ptp_client.h>
8 #include <satcat5/ptp_tlv.h>
9 #include <satcat5/timeref.h>
10 #include <satcat5/utils.h>
11 
12 namespace log = satcat5::log;
16 using satcat5::ptp::ClientMode;
17 using satcat5::ptp::ClientState;
19 using satcat5::ptp::DispatchTo;
25 using satcat5::ptp::Time;
29 
30 // Local shortcut for div_round:
31 #define div_round satcat5::util::div_round<unsigned>
32 
33 // For now the various identity fields are build-time constants.
34 #ifndef SATCAT5_PTP_DOMAIN
35 #define SATCAT5_PTP_DOMAIN 0
36 #endif
37 
38 #ifndef SATCAT5_PTP_SDO_ID
39 #define SATCAT5_PTP_SDO_ID 0
40 #endif
41 
42 #ifndef SATCAT5_PTP_PORT
43 #define SATCAT5_PTP_PORT 1
44 #endif
45 
46 // Default SYNC or PDELAY rate is 2^3 = 8x per second.
47 #ifndef SATCAT5_PTP_RATE
48 #define SATCAT5_PTP_RATE 3
49 #endif
50 
51 // Maximum ANNOUNCE, SYNC, or PDELAY rate is 2^8 = 256x per second.
52 #ifndef SATCAT5_PTP_RMAX
53 #define SATCAT5_PTP_RMAX 8
54 #endif
55 
56 // Enable support for SPTP?
57 // https://engineering.fb.com/2024/02/07/production-engineering/simple-precision-time-protocol-sptp-meta/
58 // https://ieeexplore.ieee.org/document/10296989
59 #ifndef SATCAT5_SPTP_ENABLE
60 #define SATCAT5_SPTP_ENABLE 1
61 #endif
62 
63 // Default offset from TAI to UTC is a build-time constant.
64 // This is equal to the number of leap seconds since the PTP epoch.
65 // The value provided below is valid from 2017 to 2035.
66 #ifndef SATCAT5_UTC_OFFSET
67 #define SATCAT5_UTC_OFFSET 37
68 #endif
69 
70 // Set logging verbosity level (0/1/2).
71 static constexpr unsigned DEBUG_VERBOSE = 0;
72 
73 // Most PTP messages are fixed-length (Section 13.*)
74 // (Stated lengths do not include TLVs.)
75 static constexpr u16 MSGLEN_ANNOUNCE = 64;
76 static constexpr u16 MSGLEN_SYNC = 44;
77 static constexpr u16 MSGLEN_DELAY_REQ = 44;
78 static constexpr u16 MSGLEN_FOLLOW_UP = 44;
79 static constexpr u16 MSGLEN_DELAY_RESP = 54;
80 static constexpr u16 MSGLEN_PDELAY_REQ = 54;
81 static constexpr u16 MSGLEN_PDELAY_RESP = 54;
82 static constexpr u16 MSGLEN_PDELAY_RFU = 54;
83 static constexpr u16 MSGLEN_SIGNALING = 44;
84 
85 // Convert mode to preferred broadcast type.
86 constexpr inline DispatchTo broadcast_to(const ClientMode& mode) {
87  return (mode == ClientMode::MASTER_L2)
88  ? DispatchTo::BROADCAST_L2
89  : DispatchTo::BROADCAST_L3;
90 }
91 
92 const char* satcat5::ptp::to_string(satcat5::ptp::ClientMode mode) {
93  switch (mode) {
94  case ClientMode::DISABLED: return "Disabled";
95  case ClientMode::MASTER_L2: return "MasterL2";
96  case ClientMode::MASTER_L3: return "MasterL3";
97  case ClientMode::SLAVE_ONLY: return "SlaveOnly";
98  case ClientMode::SLAVE_SPTP: return "SlaveSimple";
99  default: return "Passive";
100  }
101 }
102 
103 const char* satcat5::ptp::to_string(satcat5::ptp::ClientState state) {
104  switch (state) {
105  case ClientState::DISABLED: return "Disabled";
106  case ClientState::LISTENING: return "Listening";
107  case ClientState::MASTER: return "Master";
108  case ClientState::PASSIVE: return "Passive";
109  default: return "Slave";
110  }
111 }
112 
113 Client::Client(
114  satcat5::ptp::Interface* ptp_iface,
115  satcat5::ip::Dispatch* ip_dispatch,
116  ClientMode mode)
117  : m_iface(ptp_iface, ip_dispatch)
118  , m_mode(ClientMode::DISABLED)
119  , m_state(ClientState::DISABLED)
120  , m_cache()
121  , m_clock_local(satcat5::ptp::DEFAULT_CLOCK)
122  , m_clock_remote(satcat5::ptp::DEFAULT_CLOCK)
123  , m_current_source(satcat5::ptp::PORT_NONE)
124  , m_announce_count(0)
125  , m_announce_every(0)
126  , m_sync_count(0)
127  , m_sync_every(0)
128  , m_cache_wdog(0)
129  , m_request_wdog(0)
130  , m_announce_rate(0)
131  , m_sync_rate(SATCAT5_PTP_RATE)
132  , m_pdelay_rate(SATCAT5_PTP_RATE)
133  , m_announce_id(0)
134  , m_sync_id(0)
135  , m_pdelay_id(0)
136  , m_utc_offset(SATCAT5_UTC_OFFSET)
137 {
138  // Set grandmaster clock identity from interface MAC address.
139  m_clock_local.grandmasterIdentity = m_iface.macaddr().to_ptp_clockid();
140 
141  // Link to the upstream interface.
142  m_iface.ptp_callback(this);
143 
144  // Set mode and initial state.
145  set_mode(mode);
146 }
147 
148 #if SATCAT5_ALLOW_DELETION
149 Client::~Client() {
150  m_iface.ptp_callback(0);
151 }
152 #endif
153 
154 void Client::set_mode(satcat5::ptp::ClientMode mode) {
155  // Set initial state for the new mode.
156  m_mode = mode;
157  m_current_source = satcat5::ptp::PORT_NONE;
158  switch (mode) {
159  case ClientMode::MASTER_L2: m_state = ClientState::MASTER; break;
160  case ClientMode::MASTER_L3: m_state = ClientState::MASTER; break;
161  case ClientMode::SLAVE_ONLY: m_state = ClientState::LISTENING; break;
162  #if SATCAT5_SPTP_ENABLE
163  case ClientMode::SLAVE_SPTP: m_state = ClientState::LISTENING; break;
164  #endif
165  case ClientMode::PASSIVE: m_state = ClientState::PASSIVE; break;
166  default: m_state = ClientState::DISABLED; break;
167  }
168 
169  // L3 server only: Join PTP-related UDP multicast groups.
170  // See also: IEEE-1588-2019, Section C.3
171  if (mode == ClientMode::MASTER_L3) {
172  m_mcast_primary.join(m_iface.igmp(), satcat5::ip::ADDR_PTP_PRIMARY);
173  m_mcast_pdelay.join(m_iface.igmp(), satcat5::ip::ADDR_PTP_PDELAY);
174  } else {
175  m_mcast_primary.leave(m_iface.igmp());
176  m_mcast_pdelay.leave(m_iface.igmp());
177  }
178 
179  // Configure or stop the timer based on the new state.
180  timer_reset();
181 }
182 
183 inline int rate_clamp(int rate) {
184  return (rate < SATCAT5_PTP_RMAX) ? rate : SATCAT5_PTP_RMAX;
185 }
186 
188  // Store the new rate setting and reconfigure timers.
189  m_announce_rate = rate_clamp(rate);
190  timer_reset();
191 }
192 
193 void Client::set_sync_rate(int rate) {
194  // Store the new rate setting and reconfigure timers.
195  m_sync_rate = rate_clamp(rate);
196  timer_reset();
197 }
198 
199 void Client::set_pdelay_rate(int rate) {
200  // Store the new rate setting and reconfigure timers.
201  m_pdelay_rate = rate_clamp(rate);
202  timer_reset();
203 }
204 
205 bool Client::send_sync_bcast() {
206  return send_sync(broadcast_to(m_mode), ++m_sync_id);
207 }
208 
210  const satcat5::eth::MacAddr& mac,
211  const satcat5::ip::Addr& ip,
212  const satcat5::eth::VlanTag& vtag)
213 {
214  // Sanity check: Only master should send Sync messages.
215  if (m_state != ClientState::MASTER) return false;
216 
217  // Set the new address and immediately issue a SYNC message.
218  // (Safe to overwrite stored address; it's not used by the master.)
219  m_iface.store_addr(mac, ip, vtag);
220  return send_sync(DispatchTo::STORED, ++m_sync_id);
221 }
222 
224  // Sanity check: Immediately discard all messages if disabled.
225  if (m_state == ClientState::DISABLED) return;
226 
227  // Read the basic PTP message header.
228  Header hdr; bool ok = hdr.read_from(&rd);
229  if (DEBUG_VERBOSE > 1)
230  log::Log(log::DEBUG, "PtpClient: ptp_rcvd").write(hdr.type);
231 
232  // Sanity-check on received message length:
233  // * hdr.length includes the entire header + message + TLVs.
234  // * hdr.msglen() is message only, based on header's type field.
235  unsigned rcvd_len = hdr.HEADER_LEN + rd.get_read_ready();
236  if (!ok || rcvd_len < hdr.length
237  || hdr.length < hdr.HEADER_LEN + hdr.msglen()
238  || hdr.msglen() > Header::MAX_MSGLEN) {
239  log::Log(log::WARNING, "PtpClient: Malformed header");
240  return; // Abort further processing...
241  } else if (hdr.msglen() == 0) {
242  rcvd_unexpected(hdr); // Unsupported message type.
243  return; // Abort further processing...
244  }
245 
246  // Copy the message contents to a working buffer.
247  u8 msg_buff[Header::MAX_MSGLEN];
248  rd.read_bytes(hdr.msglen(), msg_buff);
249  ArrayRead msg(msg_buff, hdr.msglen());
250 
251  // Parse the chain of type/length/value (TLV) triplets...
252  TlvHeader tlv;
253  while (tlv.read_from(&rd)) {
254  // Try matching against each registered TlvHandler.
255  LimitedRead tmp(&rd, tlv.length);
256  TlvHandler* next = m_tlv_list.head();
257  while (next && !next->tlv_rcvd(hdr, tlv, tmp)) {
258  next = m_tlv_list.next(next);
259  }
260  // Consume any leftover bytes to get ready for next TLV.
261  tmp.read_finalize();
262  }
263 
264  // Take further action depending on message type...
265  switch (hdr.type & 0x0F) {
266  case Header::TYPE_SYNC: rcvd_sync(hdr, msg); break;
267  case Header::TYPE_DELAY_REQ: rcvd_delay_req(hdr, msg); break;
268  case Header::TYPE_PDELAY_REQ: rcvd_pdelay_req(hdr, msg); break;
269  case Header::TYPE_FOLLOW_UP: rcvd_follow_up(hdr, msg); break;
270  case Header::TYPE_PDELAY_RFU: rcvd_pdelay_follow_up(hdr, msg); break;
271  case Header::TYPE_DELAY_RESP: rcvd_delay_resp(hdr, msg); break;
272  case Header::TYPE_PDELAY_RESP: rcvd_pdelay_resp(hdr, msg); break;
273  case Header::TYPE_ANNOUNCE: rcvd_announce(hdr, msg); break;
274  }
275 }
276 
278  if (m_state == ClientState::MASTER) {
279  // Announcement message every N timer events.
280  // Note: MailMap may block if multiple packets are sent too quickly.
281  // Simplest workaround is a short fixed delay, otherwise harmless.
282  if (m_announce_count) {
283  --m_announce_count;
284  } else if (m_announce_every && send_announce()) {
285  m_announce_count = m_announce_every - 1;
286  SATCAT5_CLOCK->busywait_usec(10);
287  }
288  // Sync message every N timer events.
289  if (m_sync_count) {
290  --m_sync_count;
291  } else if (m_sync_every && send_sync_bcast()) {
292  m_sync_count = m_sync_every - 1;
293  }
294  } else if (m_state == ClientState::SLAVE) {
295  if (SATCAT5_SPTP_ENABLE && m_mode == ClientMode::SLAVE_SPTP) {
296  // SPTP clients send unsolicited DELAY_REQ at regular intervals.
297  send_delay_req_sptp();
298  } else {
299  // Timeout waiting for SYNC from master.
300  client_timeout();
301  }
302  } else if (m_state == ClientState::PASSIVE) {
303  // Send PDELAY_REQ at regular intervals.
304  send_pdelay_req();
305  }
306 }
307 
308 void Client::timer_reset() {
309  // Reset both one-in-N counters.
310  m_announce_count = 0;
311  m_announce_every = 0;
312  m_sync_count = 0;
313  m_sync_every = 0;
314  // Configure timers based on requested state...
315  int gcd_rate = (m_sync_rate > m_announce_rate) ? m_sync_rate : m_announce_rate;
316  if (m_state == ClientState::MASTER && gcd_rate > INT_MIN) {
317  // Conventional masters send both SYNC and ANNOUNCE.
318  // Timer rate is set by the greatest common denominator.
319  timer_every(div_round(1000u, 1u << gcd_rate));
320  if (m_announce_rate > INT_MIN)
321  m_announce_every = 1u << (gcd_rate - m_announce_rate);
322  if (m_sync_rate > INT_MIN)
323  m_sync_every = 1u << (gcd_rate - m_sync_rate);
324  } else if (m_state == ClientState::PASSIVE && m_pdelay_rate > INT_MIN) {
325  // On entry or rate change, passive mode sets a timer:
326  // * PDELAY_REQ (variable 0.9 x 2^rate / sec) (Section 9.5.13.2)
327  timer_every(div_round(900u, 1u << m_pdelay_rate));
328  } else if (m_state == ClientState::SLAVE) {
329  bool sptp_mode = SATCAT5_SPTP_ENABLE && (m_mode == ClientMode::SLAVE_SPTP);
330  if (sptp_mode && m_sync_rate > INT_MIN) {
331  // SPTP slaves send DELAY_REQ at regular intervals (2^rate / sec).
332  timer_every(div_round(1000, 1u << m_sync_rate));
333  } else {
334  // Watchdog timer for loss of communication.
335  timer_once(5000);
336  }
337  } else {
338  // Timer is not used in current state.
339  timer_stop();
340  }
341 }
342 
343 void Client::cache_miss() {
344  // Rare errors (< 10%) are harmless, but high-latency connections may
345  // need to increase SATCAT5_PTP_CACHE_SIZE (see "ptp_measurement.h").
346  // A running tally logs this error only if the rate is excessive:
347  // * -1 for each received SYNC or PDELAY_REQ message.
348  // * +N for each cache miss -> Upward trend if average rate > 1/N.
349  m_cache_wdog += 10;
350  if (DEBUG_VERBOSE > 0 || m_cache_wdog >= 50) {
351  log::Log(log::WARNING, "PtpClient: Unmatched SeqID");
352  m_cache_wdog = 0;
353  }
354 }
355 
356 unsigned Client::tlv_send(const Header& hdr, satcat5::io::Writeable* wr) {
357  // Callback to each registered TlvHandler, return total length.
358  // If wr is null, this is a prediction; otherwise write TLV(s).
359  unsigned total = 0;
360  TlvHandler* next = m_tlv_list.head();
361  while (next) {
362  total += next->tlv_send(hdr, wr);
363  next = m_tlv_list.next(next);
364  }
365  return total;
366 }
367 
368 void Client::notify_if_complete(const Measurement* meas) {
369  if (meas->done()) {
370  // Make a local copy of the measurement object.
371  // (TlvHandlers may modify or invalidate the timestamps.)
372  Measurement temp = *meas;
373  // Callback to each registered TlvHandler.
374  TlvHandler* next = m_tlv_list.head();
375  while (next && temp.done()) {
376  next->tlv_meas(temp);
377  next = m_tlv_list.next(next);
378  }
379  // Once finished, notify registered PTP callbacks.
380  if (temp.done()) notify_callbacks(temp);
381  }
382 }
383 
384 void Client::client_timeout() {
385  log::Log(log::WARNING, "PtpClient: Connection timeout.");
386  if (m_state == ClientState::SLAVE) {
387  // Revert to LISTENING state, so we can identify a new server.
388  m_state = ClientState::LISTENING;
389  timer_reset();
390  }
391 }
392 
393 void Client::rcvd_announce(const Header& hdr, ArrayRead& rd) {
394  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "PtpClient: Announcement");
395 
396  // Message contents defined in Section 13.5.1.
397  Time origin; satcat5::ptp::ClockInfo clk_info;
398  bool ok = origin.read_from(&rd); // originTimestamp (ignored)
399  s16 utc_offset = rd.read_s16(); // currentUtcOffset
400  rd.read_u8(); // reserved
401  ok = ok && clk_info.read_from(&rd); // grandmaster clock info
402  if (!ok) return; // Invalid message?
403 
404  // See Section 9.5.3, including flowchart in Figure 36.
405  if (m_state == ClientState::LISTENING) {
406  // For now, listening state just accepts the first ANNOUNCE message.
407  // TODO: Listen a while and select the best option or self-promote.
408  log::Log(log::INFO, "PtpClient: Selected master.");
409  m_iface.store_reply_addr();
410  m_current_source = hdr.src_port;
411  m_request_wdog = 0;
412  m_state = ClientState::SLAVE;
413  m_clock_remote = clk_info;
414  m_utc_offset = utc_offset;
415  timer_reset();
416  } else if (m_state == ClientState::MASTER) {
417  // TODO: Self-demote if a better master clock comes along.
418  } else if (m_state == ClientState::SLAVE && hdr.src_port == m_current_source) {
419  // Update local parameters to match the server.
420  m_clock_remote = clk_info;
421  m_utc_offset = utc_offset;
422  }
423 }
424 
425 void Client::rcvd_sync(const Header& hdr, ArrayRead& rd) {
426  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "PtpClient: Sync");
427  bool mode_sptp = SATCAT5_SPTP_ENABLE && (m_mode == ClientMode::SLAVE_SPTP);
428 
429  // See Section 9.5.4, including flowchart in Figure 37.
430  if (m_state == ClientState::SLAVE && hdr.src_port == m_current_source) {
431  // Reset the watchdog timer, unless we are in SPTP mode.
432  if (!mode_sptp) timer_reset();
433 
434  // Decrement the cache-miss watchdog (see "cache_miss").
435  if (m_cache_wdog) --m_cache_wdog;
436 
437  // Message contents defined in Section 13.6.1.
438  Time origin; bool ok = origin.read_from(&rd);
439  Time rxtime = m_iface.ptp_rx_timestamp();
440  if (!ok) return;
441 
442  // SPTP: Attempt search for matching DELAY_REQ (may return null).
443  // Normal: SYNC message begins a new handshake (always succeeds).
444  auto meas = mode_sptp ? m_cache.find(hdr) : m_cache.push(hdr);
445  if (!meas) {cache_miss(); return;}
446  meas->t2 = rxtime - Time(s64(hdr.correction));
447 
448  // Are we expecting a FOLLOW_UP message?
449  auto rcvd_2step = hdr.flags & Header::FLAG_TWO_STEP;
450  auto rcvd_sptp = hdr.flags & Header::FLAG_SPTP;
451  if (mode_sptp) {
452  // SPTP mode: Always two-step, with T4 in the "origin" field.
453  if (rcvd_2step && rcvd_sptp) {
454  meas->t4 = origin;
455  m_request_wdog = 0;
456  }
457  } else if (rcvd_2step) {
458  // Two-step mode: No further action until FOLLOW_UP.
459  } else {
460  // One-step mode: Note origin timestamp and send reply.
461  meas->t1 = origin;
462  if (send_delay_req(hdr.seq_id))
463  meas->t3 = m_iface.ptp_tx_timestamp();
464  }
465  }
466 }
467 
468 void Client::rcvd_follow_up(const Header& hdr, ArrayRead& rd) {
469  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "PtpClient: Follow-up");
470  bool mode_sptp = SATCAT5_SPTP_ENABLE && (m_mode == ClientMode::SLAVE_SPTP);
471 
472  // See Section 9.5.5, including flowchart in Figure 38.
473  if (m_state == ClientState::SLAVE && hdr.src_port == m_current_source) {
474  // Message contents defined in Section 13.7.1.
475  Time origin; bool ok = origin.read_from(&rd);
476  if (!ok) return;
477 
478  // Find the corresponding SYNC message.
479  // Normal mode sends a reply, SPTP mode completes handshake.
480  auto meas = m_cache.find(hdr, hdr.src_port);
481  if (meas) {
482  if (mode_sptp) {
483  meas->t1 = origin;
484  meas->t3 += Time(s64(hdr.correction));
485  notify_if_complete(meas);
486  } else if (send_delay_req(hdr.seq_id)) {
487  meas->t1 = origin + Time(s64(hdr.correction));
488  meas->t3 = m_iface.ptp_tx_timestamp();
489  }
490  } else {cache_miss();} // GCOVR_EXCL_LINE
491  }
492 }
493 
494 void Client::rcvd_delay_req(const Header& hdr, ArrayRead& rd) {
495  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "PtpClient: Delay request");
496 
497  // See Section 9.5.6, including flowchart in Figure 39.
498  // (Except SPTP requests, which reply with SYNC instead.)
499  if (m_state == ClientState::MASTER) {
500  auto rcvd_sptp = hdr.flags & Header::FLAG_SPTP;
501  if (SATCAT5_SPTP_ENABLE && rcvd_sptp) {
502  u16 sptp_flags = Header::FLAG_SPTP | Header::FLAG_TWO_STEP;
503  send_sync(DispatchTo::REPLY, hdr.seq_id, sptp_flags, hdr.correction);
504  } else {
505  send_delay_resp(hdr);
506  }
507  }
508 }
509 
510 void Client::rcvd_pdelay_req(const Header& hdr, ArrayRead& rd) {
511  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "PtpClient: PDelay request");
512 
513  if (m_state == ClientState::PASSIVE) {
514  send_pdelay_resp(hdr);
515  }
516 }
517 
518 void Client::rcvd_delay_resp(const Header& hdr, ArrayRead& rd) {
519  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "PtpClient: Delay response");
520 
521  // See Section 9.5.7, including flowchart in Figure 40.
522  // This message is not used in SPTP mode.
523  if (m_state == ClientState::SLAVE
524  && m_mode != ClientMode::SLAVE_SPTP
525  && hdr.src_port == m_current_source) {
526  // Message contents defined in Section 13.8.1.
527  Time rxtime; bool ok = rxtime.read_from(&rd);
528  if (!ok) return;
529 
530  // Decrement the cache-miss watchdog (see "cache_miss").
531  if (m_cache_wdog) --m_cache_wdog;
532 
533  // Find the corresponding SYNC message...
534  auto meas = m_cache.find(hdr, hdr.src_port);
535  if (meas) {
536  meas->t4 = rxtime - Time(s64(hdr.correction));
537  // Optional diagnostics showing all collected timestamps.
538  if (DEBUG_VERBOSE > 0)
539  log::Log(log::DEBUG, "PtpClient: Measurement ready").write_obj(*meas);
540  // If we have every timestamp, notify all callback object(s).
541  notify_if_complete(meas);
542  } else {cache_miss();} // GCOVR_EXCL_LINE
543  }
544 }
545 
546 void Client::rcvd_pdelay_resp(const Header& hdr, ArrayRead& rd) {
547  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "PtpClient: PDelay response");
548 
549  if (m_state == ClientState::PASSIVE) {
550  // Message contents defined in Section 13.9.1.
551  Time t4 = m_iface.ptp_rx_timestamp();
552  Time t2; bool ok = t2.read_from(&rd);
553  auto rcvd_2step = hdr.flags & Header::FLAG_TWO_STEP;
554 
555  // Find the corresponding PDELAY_REQ message.
556  // If this completes the peer-to-peer delay request, notify the callback object.
557  auto meas = m_cache.find(hdr, hdr.src_port);
558  if (ok && meas) {
559  // Use T2 if provided, otherwise best-guess placeholder.
560  // Note: T1 and T4 are the only precise timestamps in this mode.
561  meas->t1 += Time(s64(hdr.correction - meas->ref.correction));
562  meas->t2 = (t2 == TIME_ZERO) ? (meas->t1 + t4)/2 : t2;
563  meas->t3 = meas->t2;
564  meas->t4 = t4;
565  if (!rcvd_2step) notify_if_complete(meas);
566  } else {cache_miss();}
567  }
568 }
569 
570 void Client::rcvd_pdelay_follow_up(const Header& hdr, ArrayRead& rd) {
571  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "PtpClient: PDelay response follow up");
572 
573  // Message contents defined in Section 13.11.1.
574  Time origin; bool ok = origin.read_from(&rd);
575  if (!ok) return;
576 
577  // Find the corresponding PDELAY_REQ message.
578  auto meas = m_cache.find(hdr, hdr.src_port);
579  if (meas) {
580  meas->t1 += Time(s64(hdr.correction));
581  notify_if_complete(meas);
582  } else {cache_miss();} // GCOVR_EXCL_LINE
583 }
584 
585 void Client::rcvd_unexpected(const Header& hdr) {
586  // Log all unexpected message types, but take no further action.
587  log::Log(log::INFO, "PtpClient: Unexpected message").write(hdr.type);
588 }
589 
590 Header Client::make_header(u8 type, u16 seq_id) {
591  // Most fields are simple constants.
592  Header hdr;
593  hdr.type = type;
594  hdr.version = 2; // PTPv2
595  hdr.domain = SATCAT5_PTP_DOMAIN;
596  hdr.sdo_id = SATCAT5_PTP_SDO_ID;
597  hdr.flags = 0; // Section 13.3.2.8
598  hdr.correction = 0; // Always initialized to zero
599  hdr.subtype = 0; // Reserved
600  hdr.src_port = {m_clock_local.grandmasterIdentity, SATCAT5_PTP_PORT};
601  hdr.seq_id = seq_id; // Section 7.3.7
602  hdr.control = 0; // Obsolete (Section 13.3.2.13)
603 
604  // The flags we care about are:
605  // * FLAG_PTP_TIMESCALE (required on all announce messages)
606  // * FLAG_UNICAST (inferred by type)
607  // * FLAG_TWO_STEP (set by caller if required)
608  // * FLAG_SPTP aka FLAG_PROFILE1 (set if SPTP mode is enabled)
609  if (type == Header::TYPE_ANNOUNCE)
610  hdr.flags |= Header::FLAG_PTP_TIMESCALE;
611  if (type == Header::TYPE_DELAY_REQ || type == Header::TYPE_DELAY_RESP)
612  hdr.flags |= Header::FLAG_UNICAST;
613  if (SATCAT5_SPTP_ENABLE && m_mode == ClientMode::SLAVE_SPTP)
614  hdr.flags |= Header::FLAG_SPTP;
615 
616  // Set messageLength based on type (Section 13.*)
617  switch (type & 0x0F) {
618  case Header::TYPE_SYNC: hdr.length = MSGLEN_SYNC; break;
619  case Header::TYPE_DELAY_REQ: hdr.length = MSGLEN_DELAY_REQ; break;
620  case Header::TYPE_PDELAY_REQ: hdr.length = MSGLEN_PDELAY_REQ; break;
621  case Header::TYPE_PDELAY_RESP: hdr.length = MSGLEN_PDELAY_RESP; break;
622  case Header::TYPE_FOLLOW_UP: hdr.length = MSGLEN_FOLLOW_UP; break;
623  case Header::TYPE_DELAY_RESP: hdr.length = MSGLEN_DELAY_RESP; break;
624  case Header::TYPE_PDELAY_RFU: hdr.length = MSGLEN_PDELAY_RFU; break;
625  case Header::TYPE_ANNOUNCE: hdr.length = MSGLEN_ANNOUNCE; break;
626  default: hdr.length = 0; break; // Reserved / GCOVR_EXCL_LINE
627  }
628 
629  // Set logMessageInterval based on type (Section 13.3.2.14)
630  switch (type & 0x0F) {
631  case Header::TYPE_ANNOUNCE: hdr.log_interval = (s8)(-m_announce_rate); break;
632  case Header::TYPE_SYNC: hdr.log_interval = (s8)(-m_sync_rate); break;
633  case Header::TYPE_FOLLOW_UP: hdr.log_interval = (s8)(-m_sync_rate); break;
634  case Header::TYPE_DELAY_RESP: hdr.log_interval = 0; break;
635  default: hdr.log_interval = 0x7F; break;
636  }
637 
638  return hdr;
639 }
640 
641 bool Client::send_announce() {
642  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "PtpClient: send_announce");
643  // ANNOUNCE messages are always broadcast.
644  // Message contents defined in Section 13.5.
645  // Note: Dummy timestamp is acceptable (Section 13.5.2.1)
646  Header hdr = make_header(Header::TYPE_ANNOUNCE, ++m_announce_id);
647  hdr.length += tlv_send(hdr, 0); // Predict tag length
648  auto wr = m_iface.ptp_send(broadcast_to(m_mode), hdr.length, hdr.type);
649  if (!wr) return false;
650  wr->write_obj(hdr); // Common message header
651  wr->write_obj(TIME_ZERO); // originTimestamp
652  wr->write_u16(m_utc_offset); // currentUtcOffset
653  wr->write_u8(0); // Reserved
654  wr->write_obj(m_clock_local); // Grandmaster clock info
655  tlv_send(hdr, wr); // Write TLV(s)
656  return wr->write_finalize();
657 }
658 
659 bool Client::send_sync(DispatchTo addr, u16 seq_id, u16 flags, u64 tref) {
660  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "PtpClient: send_sync");
661 
662  // Before we do anything, note the receive timestamp for SPTP only.
663  bool send_sptp = SATCAT5_SPTP_ENABLE && (flags & Header::FLAG_SPTP);
664  Time t4 = send_sptp ? m_iface.ptp_rx_timestamp() : TIME_ZERO;
665 
666  // Always use two-step mode for SPTP or for upstream requests.
667  // (If so, avoid calling "ptp_tx_start" to prevent double-booking.)
668  // Otherwise, attempt one-step mode if supported by hardware.
669  bool req_2step = send_sptp || (flags & Header::FLAG_TWO_STEP);
670  Time t1 = req_2step ? TIME_ZERO : m_iface.ptp_tx_start();
671 
672  // One-step mode: correctionField per Section 9.5.10.
673  // Two-step mode: correctionField and originTimestamp are zero.
674  // SPTP mode: correctionField zero, originTimestamp is T4.
675  Header hdr = make_header(Header::TYPE_SYNC, seq_id);
676  Time origin_time;
677  if (send_sptp) {
678  hdr.flags |= Header::FLAG_TWO_STEP | Header::FLAG_SPTP;
679  hdr.correction = 0;
680  origin_time = t4;
681  } else if (t1 == TIME_ZERO) {
682  hdr.flags |= Header::FLAG_TWO_STEP;
683  hdr.correction = 0;
684  origin_time = TIME_ZERO;
685  } else {
686  hdr.correction = t1.correction();
687  origin_time = t1;
688  }
689 
690  // SYNC messages are broadcast by default, unicast on-demand,
691  // Message contents defined in Section 13.6.
692  hdr.length += tlv_send(hdr, 0); // Predict tag length
693  auto wr = m_iface.ptp_send(addr, hdr.length, hdr.type);
694  if (!wr) return false;
695  wr->write_obj(hdr); // Common message header
696  wr->write_obj(origin_time); // originTimestamp
697  tlv_send(hdr, wr); // Write TLV(s)
698  if (hdr.flags & Header::FLAG_TWO_STEP) {
699  return wr->write_finalize() && send_follow_up(addr, seq_id, flags, tref);
700  } else {
701  return wr->write_finalize();
702  }
703 }
704 
705 bool Client::send_follow_up(
706  satcat5::ptp::DispatchTo addr,
707  u16 seq_id, u16 flags, u64 tref)
708 {
709  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "PtpClient: send_follow_up");
710 
711  // Get the timestamp from the SYNC message we just sent.
712  Time t1 = m_iface.ptp_tx_timestamp();
713  if (t1 == TIME_ZERO) log::Log(log::ERROR, "PtpClient: Bad hardware timestamp.");
714 
715  // FOLLOW_UP messages are sent to the same recipient(s) as the SYNC.
716  // Message contents defined in Section 13.7.
717  // Two-step correctionField per Section 9.5.10.
718  Header hdr = make_header(Header::TYPE_FOLLOW_UP, seq_id);
719  hdr.correction = t1.correction() + tref;
720  hdr.flags |= flags;
721  hdr.length += tlv_send(hdr, 0); // Predict tag length
722  auto wr = m_iface.ptp_send(addr, hdr.length, hdr.type);
723  if (!wr) return false;
724  wr->write_obj(hdr); // Common message header
725  wr->write_obj(t1); // preciseOriginTimestamp
726  tlv_send(hdr, wr); // Write TLV(s)
727  return wr->write_finalize();
728 }
729 
730 void Client::send_delay_req_sptp() {
731  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "PtpClient: send_delay_req_sptp");
732 
733  // Error if we send N requests in a row with no response.
734  unsigned timeout = 5 << m_sync_rate;
735  if (++m_request_wdog >= timeout) {
736  client_timeout(); return;
737  }
738 
739  // Attempt to send a DELAY_REQ message with the SPTP flag.
740  // If successful, note the outgoing timestamp.
741  Header hdr = make_header(Header::TYPE_DELAY_REQ, ++m_sync_id);
742  hdr.src_port = m_current_source;
743  if (send_delay_req(hdr.seq_id, Header::FLAG_SPTP)) {
744  auto meas = m_cache.push(hdr);
745  meas->t3 = m_iface.ptp_tx_timestamp();
746  }
747 }
748 
749 bool Client::send_delay_req(u16 seq_id, u16 flags) {
750  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "PtpClient: send_delay_req");
751 
752  // The timestamp we send here can be approximate (Section 11.3.2 c).
753  // Do NOT call ptp_tx_start() here, since incrementing correctionField
754  // double-books the elapsed time compared to ptp_tx_timestamp().
755  Time t3_approx = m_iface.ptp_time_now();
756 
757  // DELAY_REQ messages are usually sent in response to a SYNC message.
758  // (Except for unsolicited messages sent by SPTP clients.)
759  // Message contents defined in Section 13.6.
760  // Set correctionField to zero per Section 11.3.2 c.
761  Header hdr = make_header(Header::TYPE_DELAY_REQ, seq_id);
762  hdr.correction = 0;
763  hdr.flags |= flags;
764  hdr.length += tlv_send(hdr, 0); // Predict tag length
765  auto wr = m_iface.ptp_send(DispatchTo::REPLY, hdr.length, hdr.type);
766  if (!wr) return false;
767  wr->write_obj(hdr); // Common message header
768  wr->write_obj(t3_approx); // originTimestamp
769  tlv_send(hdr, wr); // Write TLV(s)
770  return wr->write_finalize();
771 }
772 
773 bool Client::send_delay_resp(const Header& ref) {
774  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "PtpClient: send_delay_resp");
775 
776  // Get the timestamp from the DELAY_REQ message we just received.
777  // (Also echo correctionField from the received packet.)
778  Time t4 = m_iface.ptp_rx_timestamp();
779  if (t4 == TIME_ZERO) log::Log(log::ERROR, "PtpClient: Bad hardware timestamp.");
780 
781  // DELAY_RESP messages are always replies to the client.
782  // Message contents defined in Section 13.8.
783  // Calculate correctionField per Section 11.3.2 d.
784  Header hdr = make_header(Header::TYPE_DELAY_RESP, ref.seq_id);
785  hdr.correction = ref.correction - t4.correction();
786  hdr.length += tlv_send(hdr, 0); // Predict tag length
787  auto wr = m_iface.ptp_send(DispatchTo::REPLY, hdr.length, hdr.type);
788  if (!wr) return false;
789  wr->write_obj(hdr); // Common message header
790  wr->write_obj(t4); // receiveTimestamp
791  wr->write_obj(ref.src_port); // requestingPortIdentity
792  tlv_send(hdr, wr); // Write TLV(s)
793  return wr->write_finalize();
794 }
795 
796 bool Client::send_pdelay_req() {
797  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "PtpClient: send_pdelay_req");
798 
799  // Estimate the current time for the originTimestamp field.
800  // To avoid double-counting, do not call "ptp_tx_start" or set
801  // outgoing correctionField. Further discussion under "send_delay_req".
802  Time t1_approx = m_iface.ptp_time_now();
803 
804  // Message contents defined in Section 13.9 and Section 11.4.2.
805  Header hdr = make_header(Header::TYPE_PDELAY_REQ, ++m_pdelay_id);
806  hdr.length += tlv_send(hdr, 0); // Predict tag length
807  auto wr = m_iface.ptp_send(DispatchTo::STORED, hdr.length, hdr.type);
808  if (!wr) return false;
809  wr->write_obj(hdr); // Common message header
810  wr->write_obj(t1_approx); // originTimestamp
811  wr->write_obj(TIME_ZERO); // reserved = 0
812  tlv_send(hdr, wr); // Write TLV(s)
813  bool ok = wr->write_finalize();
814 
815  // If successful, note the precise transmit timestamp.
816  if (ok) {
817  Time t1_actual = m_iface.ptp_tx_timestamp();
818  auto meas = m_cache.push(hdr);
819  meas->t1 = t1_actual;
820  }
821 
822  return ok;
823 }
824 
825 bool Client::send_pdelay_resp(const satcat5::ptp::Header& ref) {
826  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "PtpClient: send_pdelay_resp");
827 
828  // Get the timestamp from the DELAY_REQ message we just received.
829  // (Also echo correctionField from the received packet.)
830  Time t2 = m_iface.ptp_rx_timestamp();
831 
832  // If one-step mode is supported, predict outgoing timestamp.
833  Time t3 = m_iface.ptp_tx_start();
834 
835  // PDELAY_RESP messages are always replies to the client.
836  // Message contents defined in Section 13.8.
837  Header hdr = make_header(Header::TYPE_PDELAY_RESP, ref.seq_id);
838  hdr.domain = ref.domain;
839  hdr.sdo_id = ref.sdo_id;
840  hdr.src_port = ref.src_port;
841  if (t3 == TIME_ZERO) {
842  // In two-step mode, set correction to 0
843  hdr.correction = 0;
844  } else {
845  // See Section 11.4.2 b
846  hdr.correction = ref.correction + (t3 - t2).delta_subns();
847  }
848 
849  hdr.length += tlv_send(hdr, 0); // Predict tag length
850  auto wr = m_iface.ptp_send(DispatchTo::REPLY, hdr.length, hdr.type);
851  if (!wr) return false;
852  wr->write_obj(hdr); // Common message header
853  wr->write_obj(TIME_ZERO); // requestReceiptTimestamp
854  wr->write_obj(ref.src_port); // requestingPortIdentity
855  tlv_send(hdr, wr); // Write TLV(s)
856 
857  if (hdr.flags & Header::FLAG_TWO_STEP || t3 == TIME_ZERO) {
858  return wr->write_finalize() && send_pdelay_follow_up(ref);
859  } else {
860  return wr->write_finalize();
861  }
862 }
863 
864 bool Client::send_pdelay_follow_up(const satcat5::ptp::Header& ref) {
865  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "PtpClient: send_pdelay_follow_up");
866 
867  // Two-step mode uses most recent transmit and receive timestamps.
868  Time t2 = m_iface.ptp_rx_timestamp();
869  Time t3 = m_iface.ptp_tx_timestamp();
870 
871  // Message contents defined in Section 13.11.
872  Header hdr = make_header(Header::TYPE_PDELAY_RFU, ref.seq_id);
873  hdr.domain = ref.domain;
874  hdr.sdo_id = ref.sdo_id;
875  hdr.src_port = ref.src_port;
876 
877  // See Section 11.4.2 c
878  hdr.correction = ref.correction + (t3 - t2).delta_subns();
879  hdr.length += tlv_send(hdr, 0); // Predict tag length
880  auto wr = m_iface.ptp_send(DispatchTo::REPLY, hdr.length, hdr.type);
881  if (!wr) return false;
882  wr->write_obj(hdr); // Common message header
883  wr->write_obj(TIME_ZERO); // responseOriginTimestamp
884  wr->write_obj(ref.src_port); // requestingPortIdentity
885  tlv_send(hdr, wr); // Write TLV(s)
886  return wr->write_finalize();
887 }
888 
890  : m_client(client)
891  , m_dstmac(satcat5::eth::MACADDR_NONE)
892 {
893  // Nothing else to initialize.
894 }
895 
897  if (m_dstmac != satcat5::eth::MACADDR_NONE) {
898  m_client->send_sync_unicast(m_dstmac);
899  }
900 }
901 
903  : m_client(client)
904  , m_addr(client->get_iface(), satcat5::ip::PROTO_UDP)
905 {
906  // Nothing else to initialize.
907 }
908 
910  if (m_addr.ready()) {
911  m_client->send_sync_unicast(m_addr.dstmac(), m_addr.dstaddr());
912  }
913 }
Ephemeral Readable interface for a simple array.
Definition: io_readable.h:206
Limited read of next N bytes.
Definition: io_readable.h:255
bool read_bytes(unsigned nbytes, void *dst) override
Read 0 or more bytes into a buffer.
Definition: io_readable.cc:296
unsigned get_read_ready() const override
How many bytes can be read without blocking?
Definition: io_readable.cc:293
void read_finalize() override
Consume any remaining bytes in this frame, if applicable.
Definition: io_readable.cc:317
u8 read_u8()
One of many functions for reading integer/floating point values, see details.
Definition: io_readable.cc:44
Abstract API for writing byte-streams and packets.
Definition: io_writeable.h:24
void write_u8(u8 data)
One of many functions for writing integer/floating point values, see details.
Definition: io_writeable.cc:20
virtual bool write_finalize()
Mark end of frame and release temporary working data.
void write_obj(const T &obj)
Templated wrapper for any object with the following method: void write_to(satcat5::io::Writeable* wr)...
Definition: io_writeable.h:104
bool ready() const override
Is this address object ready for use? Child MUST override this method.
Definition: ip_address.h:68
Protocol handler and dispatch unit for Internet Protocol v4 (IPv4).
Definition: ip_dispatch.h:43
The Log class creates and formats one log message.
Definition: log.h:195
Log & write_obj(const T &obj)
Templated wrapper for custom output formatting.
Definition: log.h:251
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
void timer_stop()
Stop all future notifications.
Definition: polling.cc:326
void timer_once(unsigned msec)
Configure a one-time notification after X milliseconds.
Definition: polling.cc:316
void timer_every(unsigned msec)
Configure a repeating notification every X milliseconds.
Definition: polling.cc:321
PTP callback accepts each complete measurement from the Source.
Definition: ptp_source.h:38
Client for the IEEE 1588-2019 Precision Time Protocol (PTP)
Definition: ptp_client.h:67
void timer_event() override
Child class MUST override this method.
Definition: ptp_client.cc:277
void set_announce_rate(int rate)
Set the ANNOUNCE message rate to 2^N / sec.
Definition: ptp_client.cc:187
void set_sync_rate(int rate)
Set the SYNC message rate to 2^N / sec.
Definition: ptp_client.cc:193
void set_pdelay_rate(int rate)
Set the pdelay message rate to 0.9 x 2^N / sec.
Definition: ptp_client.cc:199
bool send_sync_unicast(const satcat5::eth::MacAddr &mac, const satcat5::ip::Addr &ip=satcat5::ip::ADDR_NONE, const satcat5::eth::VlanTag &vtag=satcat5::eth::VTAG_NONE)
Send a unicast SYNC message to the designated address.
Definition: ptp_client.cc:209
void ptp_rcvd(satcat5::io::LimitedRead &rd)
Dispatch calls this method for each incoming packet.
Definition: ptp_client.cc:223
void set_mode(satcat5::ptp::ClientMode mode)
Mode and state accessors.
Definition: ptp_client.cc:154
void ptp_callback(satcat5::ptp::Client *client)
Set the callback object for incoming messages.
Definition: ptp_dispatch.h:63
satcat5::ptp::Time ptp_tx_start()
Accessors for one-step and two-step timestamps.
Definition: ptp_dispatch.h:71
satcat5::ptp::Time ptp_tx_timestamp()
Accessors for one-step and two-step timestamps.
Definition: ptp_dispatch.h:73
void store_addr(const satcat5::eth::MacAddr &mac, const satcat5::ip::Addr &ip=satcat5::ip::ADDR_NONE, const satcat5::eth::VlanTag &vtag=satcat5::eth::VTAG_NONE)
Set the address for use with DispatchTo::STORED.
Definition: ptp_dispatch.cc:83
satcat5::ptp::Time ptp_rx_timestamp()
Accessors for one-step and two-step timestamps.
Definition: ptp_dispatch.h:75
void store_reply_addr()
Set the address for use with DispatchTo::STORED.
Definition: ptp_dispatch.cc:77
satcat5::io::Writeable * ptp_send(satcat5::ptp::DispatchTo where, unsigned num_bytes, u8 ptp_msg_type)
Send a PTP message to the designated address(es).
Definition: ptp_dispatch.cc:41
satcat5::ptp::Time ptp_time_now()
Accessors for one-step and two-step timestamps.
Definition: ptp_dispatch.h:69
Generic API for network ports that support PTP.
Definition: ptp_interface.h:27
satcat5::ptp::Measurement * find(const satcat5::ptp::Header &hdr, const satcat5::ptp::PortId &port)
Find the first matching measurement in the cache.
satcat5::ptp::Measurement * push(const satcat5::ptp::Header &hdr)
Create a new measurement, overwriting the oldest.
void notify_callbacks(const satcat5::ptp::Measurement &meas)
Notify all Callback objects of a new Measurement.
Definition: ptp_source.cc:13
Helper class for sending unicast Sync messages to an L2 client.
Definition: ptp_client.h:242
void timer_event()
Child class MUST override this method.
Definition: ptp_client.cc:896
SyncUnicastL2(satcat5::ptp::Client *client)
Create this object.
Definition: ptp_client.cc:889
Helper class for sending unicast Sync messages to an L3 client.
Definition: ptp_client.h:261
SyncUnicastL3(satcat5::ptp::Client *client)
Create this object.
Definition: ptp_client.cc:902
void timer_event()
Child class MUST override this method.
Definition: ptp_client.cc:909
High-precision timestamp for use with PTP / IEEE1588.
Definition: ptp_time.h:41
bool read_from(satcat5::io::Readable *src)
Read the standard 10-byte timestamp from a PTP message (e.g., originTimestamp: u48 seconds + u32 nano...
Definition: ptp_time.cc:64
u64 correction() const
Get the correctionField value in subnanoseconds.
Definition: ptp_time.h:108
Users should derive custom TLV objects from this base class.
Definition: ptp_tlv.h:111
virtual unsigned tlv_send(const satcat5::ptp::Header &hdr, satcat5::io::Writeable *wr)
Child class SHOULD override this method to append outgoing TLV(s).
Definition: ptp_tlv.cc:87
virtual bool tlv_rcvd(const satcat5::ptp::Header &hdr, const satcat5::ptp::TlvHeader &tlv, satcat5::io::LimitedRead &rd)
Child class SHOULD override this method to read incoming TLV(s).
Definition: ptp_tlv.cc:78
virtual void tlv_meas(satcat5::ptp::Measurement &meas)
Child class MAY override this method to read or modify each complete two-way handshake event.
Definition: ptp_tlv.cc:95
T * next(const T *item) const
Fetch pointer to the next item.
Definition: list.h:261
Diagnostic logging to UART and/or Ethernet ports.
constexpr satcat5::ptp::Time TIME_ZERO(0LL)
Common time-related constants.
TLV metadata for the IEEE 1588-2019 Precision Time Protocol (PTP).
An Ethernet MAC address (with serializable interface).
Definition: eth_header.h:29
constexpr u64 to_ptp_clockid() const
Convert to PTP Clock-ID (IEEE 1588-2008 7.5.2.2.2 Note 2).
Definition: eth_header.h:55
Header contents for an 802.1Q Virtual-LAN tag.
Definition: eth_header.h:124
void join(satcat5::igmp::Client *igmp, const satcat5::ip::Addr &addr)
Join a multicast group.
Definition: igmp_client.h:162
void leave(satcat5::igmp::Client *igmp)
Leave a multicast group.
Definition: igmp_client.h:168
IPv4 address is a 32-bit unsigned integer.
Definition: ip_core.h:15
Clock configuration metadata for the ANNOUNCE message.
Definition: ptp_header.h:117
u64 grandmasterIdentity
Fields defined in Section 13.5.1, Table 43.
Definition: ptp_header.h:126
bool read_from(satcat5::io::Readable *rd)
Read clock information from a given source.
Definition: ptp_header.cc:117
Struct representing the PTP header used for all message types.
Definition: ptp_header.h:46
u8 control
controlField
Definition: ptp_header.h:58
u32 subtype
messageTypeSpecific
Definition: ptp_header.h:55
u64 correction
correctionField
Definition: ptp_header.h:54
u16 flags
flagField
Definition: ptp_header.h:53
u8 version
versionPTP only
Definition: ptp_header.h:49
satcat5::ptp::PortId src_port
sourcePortIdentity
Definition: ptp_header.h:56
s8 log_interval
logMessageInterval
Definition: ptp_header.h:59
static constexpr u16 FLAG_SPTP
SPTP uses the PROFILE1 flag, so define an alias.
Definition: ptp_header.h:93
u16 sdo_id
majorSdoId + minorSdoId
Definition: ptp_header.h:52
bool read_from(satcat5::io::Readable *rd)
Read header contents from a given source.
Definition: ptp_header.cc:78
u16 length
messageLength
Definition: ptp_header.h:50
static constexpr unsigned HEADER_LEN
Header itself is exactly 34 bytes.
Definition: ptp_header.h:62
unsigned msglen() const
Expected length of message fields, not including header.
Definition: ptp_header.cc:43
u8 type
messageType (0-15)
Definition: ptp_header.h:48
u8 domain
domainNumber
Definition: ptp_header.h:51
static constexpr u8 TYPE_SYNC
Message types (Section 13.3.2.3 / Table 36)
Definition: ptp_header.h:66
u16 seq_id
sequenceId
Definition: ptp_header.h:57
Timestamps and metadata for a two-way time-transfer handshake.
satcat5::ptp::Time t1
Timestamp T1 (A to B / Tx)
bool done() const
Is this measurement completed? (i.e., T1/T2/T3/T4 all known)
satcat5::ptp::Header ref
Reference header is copied from the initiating PTP message (i.e., SYNC or PDELAY_REQ) and used to mat...
satcat5::ptp::Time t2
Timestamp T2 (A to B / Rx)
satcat5::ptp::Time t4
Timestamp T4 (B to A / Rx)
satcat5::ptp::Time t3
Timestamp T3 (B to A / Tx)
Struct used for sourcePortIdentity and requestingPortIdentity.
Definition: ptp_header.h:17
Data structure for identifying TLV headers.
Definition: ptp_tlv.h:70
bool read_from(satcat5::io::Readable *rd)
I/O functions read or write the TLV header only.
Definition: ptp_tlv.cc:42
TimeRef and TimeVal define the API for monotonic timers.
Miscellaneous mathematical utility functions.