ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ptp.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file ptp.h
6 * @brief PTP / IEEE 1588-2008 (PTPv2) message codec + slave clock math (PC_ENABLE_PTP).
7 *
8 * The Precision Time Protocol synchronizes clocks across a LAN to sub-microsecond accuracy by
9 * exchanging timestamped messages. This codec builds and parses the PTPv2 wire format - the 34-octet
10 * common header, the 10-octet (48-bit seconds + 32-bit nanoseconds) timestamp, the E2E Sync /
11 * Delay_Req / Follow_Up / Delay_Resp / Announce messages, and the P2P peer-delay messages (Pdelay_Req /
12 * Pdelay_Resp / Pdelay_Resp_Follow_Up, IEEE 1588-2008 §11.4) - and computes an ordinary-clock **slave**'s
13 * offset-from-master and mean-path-delay from the four transfer timestamps (t1..t4), plus the P2P
14 * meanLinkDelay. All multi-octet
15 * fields are big-endian (network order), per IEEE 1588-2008 clause 13. Pure and host-tested; the UDP
16 * transport (event port 319, general port 320, multicast 224.0.1.129) and the local timestamping are
17 * the application's - see the Ptp example for the ordinary-clock slave that drives this codec.
18 *
19 * @author Douglas Quigg (dstroy0)
20 * @date 2026
21 */
22
23#ifndef PROTOCORE_PTP_H
24#define PROTOCORE_PTP_H
25
26#include "protocore_config.h"
27
28#if PC_ENABLE_PTP
29
30#include <stddef.h>
31#include <stdint.h>
32
33/** @brief PTPv2 messageType values (low nibble of octet 0). */
34enum pc_ptp_msg_type
35{
36 PC_PTP_SYNC = 0x0,
37 PC_PTP_PDELAY_REQ = 0x2, ///< peer-delay request (P2P mechanism)
38 PC_PTP_PDELAY_RESP = 0x3, ///< peer-delay response (P2P mechanism)
39 PC_PTP_DELAY_REQ = 0x1,
40 PC_PTP_FOLLOW_UP = 0x8,
41 PC_PTP_DELAY_RESP = 0x9,
42 PC_PTP_PDELAY_RESP_FOLLOW_UP = 0xA, ///< peer-delay response follow-up (two-step P2P)
43 PC_PTP_ANNOUNCE = 0xB
44};
45
46#define PC_PTP_HEADER_LEN 34 ///< common header length
47#define PC_PTP_TS_LEN 10 ///< on-wire timestamp length (6-octet seconds + 4-octet nanoseconds)
48#define PC_PTP_EVENT_PORT 319 ///< UDP port for event messages (Sync, Delay_Req)
49#define PC_PTP_GENERAL_PORT 320 ///< UDP port for general messages (Follow_Up, Delay_Resp, Announce)
50
51/** @brief A PTP timestamp: 48-bit seconds + 32-bit nanoseconds. */
52struct pc_ptp_timestamp
53{
54 uint64_t seconds; ///< seconds (only the low 48 bits are on the wire)
55 uint32_t nanoseconds; ///< nanoseconds within the second (0 .. 999999999)
56};
57
58/** @brief The 34-octet PTPv2 common header. */
59struct pc_ptp_header
60{
61 uint8_t message_type; ///< pc_ptp_msg_type (octet 0 low nibble)
62 uint8_t transport_specific; ///< octet 0 high nibble (majorSdoId)
63 uint8_t version; ///< PTP version (octet 1 low nibble); 2 for PTPv2
64 uint16_t message_length; ///< total message length in octets
65 uint8_t domain; ///< domainNumber
66 uint16_t flags; ///< flagField
67 int64_t correction; ///< correctionField (nanoseconds scaled by 2^16)
68 uint8_t clock_identity[8]; ///< sourcePortIdentity clockIdentity
69 uint16_t port_number; ///< sourcePortIdentity portNumber
70 uint16_t sequence_id; ///< sequenceId
71 uint8_t control; ///< controlField
72 int8_t log_interval; ///< logMessageInterval
73};
74
75/** @brief Parsed Delay_Resp body. */
76struct pc_ptp_delay_resp
77{
78 pc_ptp_timestamp receive; ///< receiveTimestamp (t4, when the master got our Delay_Req)
79 uint8_t req_clock_id[8]; ///< requestingPortIdentity clockIdentity (echoes our clock id)
80 uint16_t req_port; ///< requestingPortIdentity portNumber
81};
82
83/** @brief Parsed Pdelay_Resp / Pdelay_Resp_Follow_Up body (P2P peer-delay mechanism). */
84struct pc_ptp_pdelay_resp
85{
86 pc_ptp_timestamp timestamp; ///< Pdelay_Resp: requestReceiptTimestamp (t2); Follow_Up: responseOriginTimestamp (t3)
87 uint8_t req_clock_id[8]; ///< requestingPortIdentity clockIdentity (echoes the Pdelay_Req sender)
88 uint16_t req_port; ///< requestingPortIdentity portNumber
89};
90
91/** @brief Parsed Announce body (the master's quality, for best-master selection / display). */
92struct pc_ptp_announce
93{
94 pc_ptp_timestamp origin; ///< originTimestamp
95 int16_t utc_offset; ///< currentUtcOffset (TAI - UTC, seconds)
96 uint8_t gm_priority1; ///< grandmasterPriority1
97 uint8_t gm_clock_class; ///< grandmasterClockQuality.clockClass
98 uint8_t gm_clock_accuracy; ///< grandmasterClockQuality.clockAccuracy
99 uint16_t gm_variance; ///< grandmasterClockQuality.offsetScaledLogVariance
100 uint8_t gm_priority2; ///< grandmasterPriority2
101 uint8_t gm_identity[8]; ///< grandmasterIdentity
102 uint16_t steps_removed; ///< stepsRemoved
103 uint8_t time_source; ///< timeSource
104};
105
106/** @brief Slave sync result: offset from master and mean path delay, in nanoseconds. */
107struct pc_ptp_sync
108{
109 int64_t offset_ns; ///< offsetFromMaster (local - master); subtract to correct the local clock
110 int64_t delay_ns; ///< meanPathDelay
111};
112
113// -- timestamp helpers --
114
115/** @brief Write @p ts to @p p as the 10-octet on-wire form (big-endian). */
116void pc_ptp_ts_write(uint8_t *p, const pc_ptp_timestamp *ts);
117/** @brief Read a 10-octet on-wire timestamp from @p p into @p ts. */
118void pc_ptp_ts_read(const uint8_t *p, pc_ptp_timestamp *ts);
119/** @brief Convert @p ts to signed nanoseconds since its epoch (fits current epochs in int64). */
120int64_t pc_ptp_ts_to_ns(const pc_ptp_timestamp *ts);
121/** @brief Convert signed-nanoseconds @p ns to a timestamp. */
122void pc_ptp_ts_from_ns(int64_t ns, pc_ptp_timestamp *ts);
123
124// -- header --
125
126/**
127 * @brief Build the 34-octet common header into @p buf, stamping messageLength = 34 + @p body_len.
128 * @return PC_PTP_HEADER_LEN or 0 on overflow / bad args.
129 */
130size_t pc_ptp_build_header(uint8_t *buf, size_t cap, const pc_ptp_header *h, uint16_t body_len);
131/** @brief Parse the common header from @p s (@p len octets). Returns false if too short. */
132bool pc_ptp_parse_header(const uint8_t *s, size_t len, pc_ptp_header *h);
133
134// -- messages (build stamps the type-specific messageType / control / length for you) --
135
136/** @brief Build a Sync (@p origin is the originTimestamp; 0 for a two-step Sync). */
137size_t pc_ptp_build_sync(uint8_t *buf, size_t cap, const pc_ptp_header *h, const pc_ptp_timestamp *origin);
138/** @brief Build a Delay_Req (@p origin is the originTimestamp; usually 0). */
139size_t pc_ptp_build_delay_req(uint8_t *buf, size_t cap, const pc_ptp_header *h, const pc_ptp_timestamp *origin);
140/** @brief Build a Follow_Up carrying the precise Sync egress time @p precise (t1). */
141size_t pc_ptp_build_follow_up(uint8_t *buf, size_t cap, const pc_ptp_header *h, const pc_ptp_timestamp *precise);
142/** @brief Build a Delay_Resp carrying t4 @p recv and the requester's port identity. */
143size_t pc_ptp_build_delay_resp(uint8_t *buf, size_t cap, const pc_ptp_header *h, const pc_ptp_timestamp *recv,
144 const uint8_t *req_clock_id, uint16_t req_port);
145/** @brief Build an Announce from @p a - master mode: advertise this clock's quality + origin time. */
146size_t pc_ptp_build_announce(uint8_t *buf, size_t cap, const pc_ptp_header *h, const pc_ptp_announce *a);
147
148// -- P2P peer-delay mechanism (IEEE 1588-2008 §11.4 / clause 13.9-13.11) --
149
150/** @brief Build a Pdelay_Req (@p origin is the originTimestamp, usually 0; the 10-octet reserved tail that
151 * pads it to the Pdelay_Resp length is zeroed). */
152size_t pc_ptp_build_pdelay_req(uint8_t *buf, size_t cap, const pc_ptp_header *h, const pc_ptp_timestamp *origin);
153/** @brief Build a Pdelay_Resp carrying t2 @p recv (requestReceiptTimestamp) + the requester's port identity. */
154size_t pc_ptp_build_pdelay_resp(uint8_t *buf, size_t cap, const pc_ptp_header *h, const pc_ptp_timestamp *recv,
155 const uint8_t *req_clock_id, uint16_t req_port);
156/** @brief Build a Pdelay_Resp_Follow_Up carrying t3 @p origin (responseOriginTimestamp) + the requester's
157 * port identity (two-step P2P). */
158size_t pc_ptp_build_pdelay_resp_follow_up(uint8_t *buf, size_t cap, const pc_ptp_header *h,
159 const pc_ptp_timestamp *origin, const uint8_t *req_clock_id,
160 uint16_t req_port);
161
162/**
163 * @brief Parse a Sync / Delay_Req / Follow_Up message: fills @p h and its single timestamp @p ts.
164 * Returns false on a short frame or a non-timestamp message type.
165 */
166bool pc_ptp_parse_timestamp_msg(const uint8_t *s, size_t len, pc_ptp_header *h, pc_ptp_timestamp *ts);
167/** @brief Parse a Delay_Resp into @p h + @p out. Returns false on a short / wrong-type frame. */
168bool pc_ptp_parse_delay_resp(const uint8_t *s, size_t len, pc_ptp_header *h, pc_ptp_delay_resp *out);
169/** @brief Parse an Announce into @p h + @p out. Returns false on a short / wrong-type frame. */
170bool pc_ptp_parse_announce(const uint8_t *s, size_t len, pc_ptp_header *h, pc_ptp_announce *out);
171/** @brief Parse a Pdelay_Req into @p h + its originTimestamp @p ts. False on a short / wrong-type frame. */
172bool pc_ptp_parse_pdelay_req(const uint8_t *s, size_t len, pc_ptp_header *h, pc_ptp_timestamp *ts);
173/** @brief Parse a Pdelay_Resp into @p h + @p out (@c timestamp is t2). False on a short / wrong-type frame. */
174bool pc_ptp_parse_pdelay_resp(const uint8_t *s, size_t len, pc_ptp_header *h, pc_ptp_pdelay_resp *out);
175/** @brief Parse a Pdelay_Resp_Follow_Up into @p h + @p out (@c timestamp is t3). False on short / wrong type. */
176bool pc_ptp_parse_pdelay_resp_follow_up(const uint8_t *s, size_t len, pc_ptp_header *h, pc_ptp_pdelay_resp *out);
177
178// -- slave clock math --
179
180/**
181 * @brief Compute offsetFromMaster and meanPathDelay from the four PTP transfer timestamps, in
182 * nanoseconds: t1 = Sync egress (master), t2 = Sync ingress (slave), t3 = Delay_Req egress (slave),
183 * t4 = Delay_Req ingress (master). offset = ((t2-t1) - (t4-t3)) / 2, delay = ((t2-t1) + (t4-t3)) / 2.
184 * Fold any correctionField into t1..t4 before calling.
185 */
186void pc_ptp_compute(int64_t t1, int64_t t2, int64_t t3, int64_t t4, pc_ptp_sync *out);
187
188/**
189 * @brief Compute the meanLinkDelay for the P2P mechanism (IEEE 1588-2008 §11.4.3), in nanoseconds:
190 * D = ((t4 - t1) - (t3 - t2)) / 2, where t1 = Pdelay_Req egress, t2 = Pdelay_Req ingress at the peer,
191 * t3 = Pdelay_Resp egress at the peer, t4 = Pdelay_Resp ingress. Fold the correctionFields into t1..t4
192 * before calling. Unlike the E2E delay this is a per-link measurement, independent of master/slave offset.
193 */
194int64_t pc_ptp_compute_link_delay(int64_t t1, int64_t t2, int64_t t3, int64_t t4);
195
196#endif // PC_ENABLE_PTP
197#endif // PROTOCORE_PTP_H
User-facing configuration for ProtoCore.