2 * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp).
3 * Copyright (c) 2021-2022 Mike Brady.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 2.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 * Commercial licensing is also available.
19 #include <arpa/inet.h> // ntohl and ntohs
20 #include <string.h> //strsep
22 #include <stdio.h> // snprintf
23 #include <stdlib.h> // drand48
24 #include <unistd.h> // usleep
27 #include "general-utilities.h"
28 #include "nqptp-message-handlers.h"
29 #include "nqptp-ptp-definitions.h"
30 #include "nqptp-utilities.h"
32 char hexcharbuffer
[16384];
33 int reset_clock_smoothing
= 0;
34 uint64_t clock_validity_expiration_time
= 0;
35 int clock_is_active
= 0;
37 char *hex_string(void *buf
, size_t buf_len
) {
38 char *tbuf
= (char *)buf
;
39 char *obfp
= hexcharbuffer
;
41 for (obfc
= 0; obfc
< buf_len
; obfc
++) {
42 snprintf(obfp
, 3, "%02X", *tbuf
);
50 void handle_control_port_messages(char *buf
, ssize_t recv_len
,
51 clock_source_private_data
*clock_private_info
,
52 uint64_t reception_time
) {
54 if ((buf
!= NULL
) && (recv_len
> 0)) {
55 buf
[recv_len
- 1] = 0; // we know it's not empty, so make sure there's a null in it.
56 debug(2, "New control port message: \"%s\".", buf
);
57 // we need to get the client shared memory interface name from the front
59 char *smi_name
= strsep(&ip_list
, " ");
61 if (smi_name
!= NULL
) {
63 command
= strsep(&ip_list
, " ");
65 // "B" is for play begin/resume. Assumes a "T <ip>" already
66 // "E" is for play end/stop.
67 // "P" is for pause (currently Buffered Audio only).
69 // "T <ip>" is for the IP address of a timer.
70 // "T" means no active timer.
71 // clock_is_active is made true by Play and false by Pause or End.
72 if (command
!= NULL
) {
73 if ((strcmp(command
, "B") == 0) && (ip_list
== NULL
)) {
75 // We want to avoid, as much as possible, resetting the clock smoothing.
76 // If we know the clock is already active or
77 // if it's only been a short time since we know it was last active
78 // then we will not reset the clock.
79 if (clock_is_active
) {
80 debug(2, "clock is already active");
82 // Find out if the clock is active i.e. not sleeping.
83 // We know it is active between "B" and "E" commands.
84 // We also know it is active for brief periods after the "T" and "E" commands are
85 // received. If it is not definitely active, we will reset smoothing.
86 int will_ask_for_a_reset
= 0;
87 if (clock_validity_expiration_time
== 0) {
88 debug(1, "no clock_validity_expiration_time.");
89 will_ask_for_a_reset
= 1;
91 int64_t time_to_clock_expiration
= clock_validity_expiration_time
- reception_time
;
92 // timings obtained with an iPhone Xs Max on battery save
94 // around 30 seconds at a buffered audio pause on an iphone.
95 // around 1 second after a buffered audio stop on an iphone
96 // 10 seconds after a "T" from an iPhone that immediately sleeps
97 // more than a minute from "T" from a HomePod mini.
99 if (time_to_clock_expiration
< 0) {
100 debug(2, "Clock validity may have expired, so ask for a reset.");
101 will_ask_for_a_reset
= 1;
104 if (will_ask_for_a_reset
!= 0) {
105 debug(2, "Reset clock smoothing");
106 reset_clock_smoothing
= 1;
110 clock_validity_expiration_time
= 0;
111 } else if ((strcmp(command
, "E") == 0) && (ip_list
== NULL
)) {
113 if (clock_is_active
) {
114 debug(2, "reset clock_validity_expiration_time to 2.25 seconds in the future.");
115 clock_validity_expiration_time
=
116 reception_time
+ 2250000000; // expiration time can be very soon after an "E"
119 debug(2, "clock is already inactive.");
121 } else if ((strcmp(command
, "P") == 0) && (ip_list
== NULL
)) {
123 // A pause always seems to turn into a Stop in now more than a few seconds, and the
124 // clock keeps going, it seems so there is nothing to do here.
125 } else if ((command
== NULL
) || ((strcmp(command
, "T") == 0) && (ip_list
== NULL
))) {
126 debug(2, "Stop Timing");
128 debug(2, "Clear timing peer group.");
129 // dirty experimental hack -- delete all the clocks
131 for (gc
= 0; gc
< MAX_CLOCKS
; gc
++) {
132 memset(&clock_private_info
[gc
], 0, sizeof(clock_source_private_data
));
134 update_master_clock_info(0, NULL
, 0, 0, 0); // the SMI may have obsolete stuff in it
136 debug(2, "Start Timing");
137 // dirty experimental hack -- delete all the clocks
139 for (gc
= 0; gc
< MAX_CLOCKS
; gc
++) {
140 memset(&clock_private_info
[gc
], 0, sizeof(clock_source_private_data
));
142 debug(2, "get or create new record for \"%s\".", smi_name
);
143 // client_id = get_client_id(smi_name); // create the record if it doesn't exist
144 // if (client_id != -1) {
145 if (strcmp(command
, "T") == 0) {
147 for (i
= 0; i
< MAX_CLOCKS
; i
++) {
148 clock_private_info
[i
].announcements_without_followups
=
149 0; // to allow a possibly silent clock to be revisited when added to a timing
151 clock_private_info
[i
].follow_up_number
= 0;
154 // take the first ip and make it the master, permanently
156 if (ip_list
!= NULL
) {
157 char *new_ip
= strsep(&ip_list
, " ");
158 // look for the IP in the list of clocks, and create an inert entry if not there
159 if ((new_ip
!= NULL
) && (new_ip
[0] != 0)) {
160 int t
= find_clock_source_record(new_ip
, clock_private_info
);
162 t
= create_clock_source_record(new_ip
, clock_private_info
);
163 if (t
!= -1) { // if the clock table is not full, okay
164 debug(2, "Monitor clock at %s.", new_ip
);
166 // otherwise, drop it
169 // a new clock timing record will be started now
170 debug(2, "reset clock_validity_expiration_time to 5.0 seconds in the future.");
171 clock_validity_expiration_time
=
172 reception_time
+ 5000000000L; // clock can stop as soon as 6 seconds after a "T"
174 warn("Unrecognised string on the control port.");
177 // warn("Could not find or create a record for SMI Interface \"%s\".",
183 warn("SMI Interface Name not found on the control port.");
186 warn("Missing or empty packet on the control port.");
189 warn("Bad packet on the control port.");
193 void handle_announce(char *buf
, ssize_t recv_len
, clock_source_private_data
*clock_private_info
,
194 __attribute__((unused
)) uint64_t reception_time
) {
195 // debug_print_buffer(1, buf, (size_t) recv_len);
196 // make way for the new time
197 if ((size_t)recv_len
>= sizeof(struct ptp_announce_message
)) {
198 struct ptp_announce_message
*msg
= (struct ptp_announce_message
*)buf
;
200 uint64_t packet_clock_id
= nctohl(&msg
->header
.clockIdentity
[0]);
201 uint64_t packet_clock_id_low
= nctohl(&msg
->header
.clockIdentity
[4]);
202 packet_clock_id
= packet_clock_id
<< 32;
203 packet_clock_id
= packet_clock_id
+ packet_clock_id_low
;
204 clock_private_info
->clock_id
= packet_clock_id
;
205 clock_private_info
->grandmasterPriority1
=
206 msg
->announce
.grandmasterPriority1
; // need this for possibly pinging it later...
207 clock_private_info
->grandmasterPriority2
=
208 msg
->announce
.grandmasterPriority2
; // need this for possibly pinging it later...
210 debug(2, "announcement seen from %" PRIx64
" at %s.", clock_private_info
->clock_id
,
211 clock_private_info
->ip
);
213 if (clock_private_info
->announcements_without_followups
< 5) // don't keep going forever
214 // a value of 4 means it's parked --
215 // it has seen three, poked the clock and doesn't want to do any more.
216 clock_private_info
->announcements_without_followups
++;
218 uint64_t grandmaster_clock_id
= nctohl(&msg
->announce
.grandmasterIdentity
[0]);
219 uint64_t grandmaster_clock_id_low
= nctohl(&msg
->announce
.grandmasterIdentity
[4]);
220 grandmaster_clock_id
= grandmaster_clock_id
<< 32;
221 grandmaster_clock_id
= grandmaster_clock_id
+ grandmaster_clock_id_low
;
222 uint32_t clockQuality
= ntohl(msg
->announce
.grandmasterClockQuality
);
223 uint8_t clockClass
= (clockQuality
>> 24) & 0xff;
224 uint8_t clockAccuracy
= (clockQuality
>> 16) & 0xff;
225 uint16_t offsetScaledLogVariance
= clockQuality
& 0xffff;
226 uint16_t stepsRemoved
= ntohs(msg
->announce
.stepsRemoved
);
227 uint16_t sourcePortID
= ntohs(msg
->header
.sourcePortID
);
229 clock_private_info
->grandmasterIdentity
= grandmaster_clock_id
;
230 clock_private_info
->grandmasterPriority1
= msg
->announce
.grandmasterPriority1
;
231 clock_private_info
->grandmasterQuality
= clockQuality
;
232 clock_private_info
->grandmasterClass
= clockClass
;
233 clock_private_info
->grandmasterAccuracy
= clockAccuracy
;
234 clock_private_info
->grandmasterVariance
= offsetScaledLogVariance
;
235 clock_private_info
->grandmasterPriority2
= msg
->announce
.grandmasterPriority2
;
236 clock_private_info
->stepsRemoved
= stepsRemoved
;
237 clock_private_info
->clock_port_number
= sourcePortID
;
239 if (clock_private_info
->wakeup_sent
== 0) {
240 send_awakening_announcement_sequence(
241 clock_private_info
->clock_id
, clock_private_info
->ip
, clock_private_info
->family
,
242 clock_private_info
->grandmasterPriority1
, clock_private_info
->grandmasterPriority2
);
243 clock_private_info
->wakeup_sent
= 1;
248 void handle_sync(char *buf
, ssize_t recv_len
, clock_source_private_data
*clock_private_info
,
249 __attribute__((unused
)) uint64_t reception_time
) {
251 // diagnostic -- decide whether to delay the processing of the follow_up to simulate a noisy
252 network if (drand48() < 0.015) {
253 // generate a random delay between 10 and 3500 milliseconds
254 int delay = (int)((3000 - 60) * drand48()) + 60;
255 debug(1,"Delay sync processing by %u milliseconds.", delay);
256 usleep(delay * 1000);
257 reception_time = get_time_now();
261 if (clock_private_info
->clock_id
== 0) {
262 debug(2, "Sync received before announcement -- discarded.");
264 if ((recv_len
>= 0) && ((size_t)recv_len
>= sizeof(struct ptp_sync_message
))) {
265 // debug_print_buffer(1, buf, recv_len);
266 struct ptp_sync_message
*msg
= (struct ptp_sync_message
*)buf
;
270 // actually the precision timestamp needs to be corrected by the Follow_Up Correction_Field contents.
271 // According to IEEE Std 802.1AS-2020, paragraph 11.4.4.2.1:
273 The value of the preciseOriginTimestamp field is the sourceTime of the ClockMaster entity of the Grandmaster PTP Instance,
274 when the associated Sync message was sent by that Grandmaster PTP Instance, with any fractional nanoseconds truncated (see 10.2.9).
275 The sum of the correctionFields in the Follow_Up and associated Sync messages, added to the preciseOriginTimestamp field of the Follow_Up message,
276 is the value of the synchronized time corresponding to the syncEventEgressTimestamp at the PTP Instance that sent the associated Sync message,
277 including any fractional nanoseconds.
282 int64_t correction_field
= ntoh64(msg
->header
.correctionField
);
284 if (correction_field
!= 0)
285 debug(1, "Sync correction field is non-zero: %" PRId64
" ns.", correction_field
);
287 correction_field
= correction_field
/ 65536; // might be signed
289 debug(1, "Sync message is too small to be valid.");
294 void handle_follow_up(char *buf
, ssize_t recv_len
, clock_source_private_data
*clock_private_info
,
295 uint64_t reception_time
) {
296 if (clock_private_info
->clock_id
== 0) {
297 debug(2, "Follow_Up received before announcement -- discarded.");
299 clock_private_info
->announcements_without_followups
= 0;
300 if ((recv_len
>= 0) && ((size_t)recv_len
>= sizeof(struct ptp_follow_up_message
))) {
301 // debug_print_buffer(1, buf, recv_len);
302 struct ptp_follow_up_message
*msg
= (struct ptp_follow_up_message
*)buf
;
303 uint16_t seconds_hi
= nctohs(&msg
->follow_up
.preciseOriginTimestamp
[0]);
304 uint32_t seconds_low
= nctohl(&msg
->follow_up
.preciseOriginTimestamp
[2]);
305 uint32_t nanoseconds
= nctohl(&msg
->follow_up
.preciseOriginTimestamp
[6]);
306 uint64_t preciseOriginTimestamp
= seconds_hi
;
307 preciseOriginTimestamp
= preciseOriginTimestamp
<< 32;
308 preciseOriginTimestamp
= preciseOriginTimestamp
+ seconds_low
;
309 preciseOriginTimestamp
= preciseOriginTimestamp
* 1000000000L;
310 preciseOriginTimestamp
= preciseOriginTimestamp
+ nanoseconds
;
312 // update our sample information
313 int grandmasterClockIsStopped
= 0;
314 if ((clock_private_info
->previous_preciseOriginTimestamp
== preciseOriginTimestamp
) &&
315 (clock_private_info
->clock_id
== clock_private_info
->grandmasterIdentity
)) {
316 clock_private_info
->identical_previous_preciseOriginTimestamp_count
++;
317 grandmasterClockIsStopped
= 1;
318 if (clock_private_info
->identical_previous_preciseOriginTimestamp_count
== 8 * 60) {
319 int64_t duration_of_mastership
=
320 reception_time
- clock_private_info
->mastership_start_time
;
321 if (clock_private_info
->mastership_start_time
== 0)
322 duration_of_mastership
= 0;
324 "Clock %" PRIx64
"'s grandmaster clock has stopped after %f seconds of mastership.",
325 clock_private_info
->clock_id
, 0.000000001 * duration_of_mastership
);
326 int64_t wait_limit
= 62;
327 wait_limit
= wait_limit
* 1000000000;
328 // only try to restart a grandmaster clock on the clock itself.
329 if ((duration_of_mastership
<= wait_limit
) &&
330 (clock_private_info
->clock_id
== clock_private_info
->grandmasterIdentity
)) {
332 "Attempt to start a stopped clock %" PRIx64
", at follow_up_number %u at IP %s.",
333 clock_private_info
->clock_id
, clock_private_info
->follow_up_number
,
334 clock_private_info
->ip
);
335 send_awakening_announcement_sequence(
336 clock_private_info
->clock_id
, clock_private_info
->ip
, clock_private_info
->family
,
337 clock_private_info
->grandmasterPriority1
, clock_private_info
->grandmasterPriority2
);
341 clock_private_info
->identical_previous_preciseOriginTimestamp_count
= 0;
344 clock_private_info
->previous_preciseOriginTimestamp
= preciseOriginTimestamp
;
348 // actually the precision timestamp needs to be corrected by the Follow_Up Correction_Field contents.
349 // According to IEEE Std 802.1AS-2020, paragraph 11.4.4.2.1:
351 The value of the preciseOriginTimestamp field is the sourceTime of the ClockMaster entity of the Grandmaster PTP Instance,
352 when the associated Sync message was sent by that Grandmaster PTP Instance, with any fractional nanoseconds truncated (see 10.2.9).
353 The sum of the correctionFields in the Follow_Up and associated Sync messages, added to the preciseOriginTimestamp field of the Follow_Up message,
354 is the value of the synchronized time corresponding to the syncEventEgressTimestamp at the PTP Instance that sent the associated Sync message,
355 including any fractional nanoseconds.
360 int64_t correction_field
= ntoh64(msg
->header
.correctionField
);
362 // debug(1," Check ntoh64: in: %" PRIx64 ", out: %" PRIx64 ".", msg->header.correctionField,
363 // correction_field);
365 correction_field
= correction_field
/ 65536; // might be signed
366 uint64_t correctedPreciseOriginTimestamp
= preciseOriginTimestamp
+ correction_field
;
368 if (clock_private_info
->follow_up_number
< 100)
369 clock_private_info
->follow_up_number
++;
371 // if (clock_private_info->announcements_without_followups < 4) // if we haven't signalled
373 clock_private_info
->announcements_without_followups
= 0; // we've seen a followup
375 debug(2, "FOLLOWUP from %" PRIx64
", %s.", clock_private_info
->clock_id
,
376 &clock_private_info
->ip
);
377 uint64_t offset
= correctedPreciseOriginTimestamp
- reception_time
;
381 int64_t time_since_previous_offset
= 0;
382 uint64_t smoothed_offset
= offset
;
384 // This is a bit hacky.
385 // Basically, the idea is that if the grandmaster has changed, then acceptance checking and
386 // smoothing should start as it it's a new clock. This is because the
387 // correctedPreciseOriginTimestamp, which is part of the data that is being smoothed, refers
388 // to the grandmaster, so when the grandmaster changes any previous calculations are no
389 // longer valid. The hacky bit is to signal this condition by zeroing the
390 // previous_offset_time.
391 if (clock_private_info
->previous_offset_grandmaster
!=
392 clock_private_info
->grandmasterIdentity
) {
393 clock_private_info
->previous_offset_time
= 0;
394 if (clock_private_info
->previous_offset_grandmaster
== 0)
395 debug(1, "grandmaster is %" PRIx64
".", clock_private_info
->grandmasterIdentity
);
397 debug(1, "grandmaster has changed from %" PRIx64
" to %" PRIx64
".",
398 clock_private_info
->previous_offset_grandmaster
,
399 clock_private_info
->grandmasterIdentity
);
402 // Do acceptance checking and smoothing.
404 // Positive changes in the offset are much more likely to be
405 // legitimate, since they could only occur due to a shorter
406 // propagation time or less of a delay sending or receiving the packet.
407 // When the clock is new, we give preferential weighting to
408 // positive changes in the offset to allow the clock to sync up quickly.
410 // If the new offset is greater, by any amount, than the old offset,
411 // or if it is less by up to the clamping_limit, accept it.
413 // This seems to be quite stable
415 if (reset_clock_smoothing
== 0) {
417 if (clock_private_info
->previous_offset_time
!= 0) {
418 time_since_previous_offset
= reception_time
- clock_private_info
->previous_offset_time
;
419 jitter
= offset
- clock_private_info
->previous_offset
;
422 // We take any positive or a limited negative jitter as a sync event in
423 // a continuous synchronisation sequence.
425 // The full value of a positive offset jitter is accepted for a
426 // number of follow_ups at the start.
427 // After that, the weight of the jitter is reduced.
428 // Follow-ups don't always come in at 125 ms intervals, especially after a discontinuity
429 // Delays makes the offsets smaller than they should be, which is quickly
432 const int64_t clamping_limit
= -2500000; // nanoseconds
434 int64_t mastership_time
= reception_time
- clock_private_info
->mastership_start_time
;
435 if (clock_private_info
->mastership_start_time
== 0)
438 // if ((clock_private_info->previous_offset_time != 0) &&
439 // (clock_private_info->identical_previous_preciseOriginTimestamp_count <= 1)) {
440 if (clock_private_info
->previous_offset_time
!= 0) {
442 int64_t clamped_jitter
= jitter
;
443 if (clamped_jitter
< clamping_limit
) {
444 clamped_jitter
= clamping_limit
; // 0 means ignore a clamped value completely
446 // if (mastership_time < 1000000000) // at the beginning, if jitter is negative
447 // smoothed_offset = clock_private_info->previous_offset + clamped_jitter / 16;
450 // ignore negative jitter at first...
451 smoothed_offset
= clock_private_info
->previous_offset
;
452 if (mastership_time
> 1000000000)
453 smoothed_offset
+= clamped_jitter
/ 256; // later, if jitter is negative
454 } else if (mastership_time
< 1000000000) { // at the beginning
456 clock_private_info
->previous_offset
+
458 1; // at the beginning, if jitter is positive -- accept positive changes quickly
461 clock_private_info
->previous_offset
+ jitter
/ 16; // later, if jitter is positive
464 if (clock_private_info
->previous_offset_time
== 0)
465 debug(2, "Clock %" PRIx64
" record (re)starting at %s.", clock_private_info
->clock_id
,
466 clock_private_info
->ip
);
469 "Timing discontinuity on clock %" PRIx64
470 " at %s: time_since_previous_offset: %.3f seconds%s.",
471 clock_private_info
->clock_id
, clock_private_info
->ip
,
472 0.000000001 * time_since_previous_offset
,
473 grandmasterClockIsStopped
!= 0 ? ", grandmaster clock stopped" : "");
474 smoothed_offset
= offset
;
475 // clock_private_info->follow_up_number = 0;
476 clock_private_info
->mastership_start_time
=
477 reception_time
; // mastership is reset to this time...
480 int64_t delta
= smoothed_offset
- offset
;
482 "Clock %" PRIx64
", grandmaster %" PRIx64
". Offset: %" PRIx64
483 ", smoothed offset: %" PRIx64
484 ". Smoothed Offset - Offset: %10.3f. Raw Precise Origin Timestamp: %" PRIx64
485 "%s correction_field: %" PRIx64
486 ". Time since previous offset: %8.3f milliseconds. ID: %5u, Follow_Up Number: "
488 clock_private_info
->clock_id
, clock_private_info
->grandmasterIdentity
, offset
,
489 smoothed_offset
, 0.000001 * delta
, preciseOriginTimestamp
,
490 clock_is_active
!= 0 ? ". " : "*.", correction_field
,
491 0.000001 * time_since_previous_offset
, ntohs(msg
->header
.sequenceId
),
492 clock_private_info
->follow_up_number
, clock_private_info
->ip
);
493 if (clock_is_active
) {
494 update_master_clock_info(clock_private_info
->grandmasterIdentity
,
495 (const char *)&clock_private_info
->ip
, reception_time
,
496 smoothed_offset
, clock_private_info
->mastership_start_time
);
498 update_master_clock_info(0, NULL
, 0, 0, 0); // the SMI may have obsolete stuff in it
501 clock_private_info
->previous_offset
= smoothed_offset
;
502 clock_private_info
->previous_offset_time
= reception_time
;
505 reset_clock_smoothing
= 0;
506 clock_private_info
->mastership_start_time
= 0;
507 clock_private_info
->previous_offset
= 0;
508 clock_private_info
->previous_offset_time
=
509 0; // so that the first non-stopped sample will be taken as the first one in a sequence
512 clock_private_info
->previous_offset_grandmaster
= clock_private_info
->grandmasterIdentity
;
514 // now do some quick calculations on the possible "Universal Time"
515 // debug_print_buffer(1, "", buf, recv_len);
516 uint8_t *tlv
= (uint8_t *)&msg
->follow_up
.tlvs
[0];
517 uint8_t *lastGmPhaseChange
= tlv
+ 16;
518 uint64_t lpt
= nctoh64(lastGmPhaseChange
+ 4);
519 uint64_t last_tlv_clock
= nctoh64((uint8_t *)buf
+ 86);
520 uint64_t huh
= offset
- lpt
;
521 debug_print_buffer(2, buf
, (size_t)recv_len
);
523 "%" PRIx64
", %" PRIx64
", %s, Origin: %016" PRIx64
", LPT: %016" PRIx64
524 ", Offset: %016" PRIx64
", Universal Offset: %016" PRIx64
", packet length: %u.",
525 clock_private_info
->clock_id
, last_tlv_clock
, hex_string(lastGmPhaseChange
, 12),
526 preciseOriginTimestamp
, lpt
, offset
, huh
, recv_len
);
527 // debug(1,"Clock: %" PRIx64 ", UT: %016" PRIx64 ", correctedPOT: %016" PRIx64 ", part of
528 // lastGMPhaseChange: %016" PRIx64 ".", packet_clock_id, correctedPOT - lpt, correctedPOT,
532 debug(1, "Follow_Up message is too small to be valid.");