1pub const HEADER_LEN: usize = 34;
16pub const TIMESTAMP_LEN: usize = 10;
18pub const BODY_OFFSET: usize = HEADER_LEN;
20pub const PTP_VERSION: u8 = 2;
22pub const DELAY_REQ_LEN: usize = HEADER_LEN + TIMESTAMP_LEN;
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum PtpMessageType {
29 Sync,
30 DelayReq,
31 FollowUp,
32 DelayResp,
33 Announce,
34 Management,
35 Other(u8),
36}
37
38impl PtpMessageType {
39 fn from_nibble(n: u8) -> Self {
40 match n & 0x0f {
41 0x0 => Self::Sync,
42 0x1 => Self::DelayReq,
43 0x8 => Self::FollowUp,
44 0x9 => Self::DelayResp,
45 0xb => Self::Announce,
46 0xd => Self::Management,
47 other => Self::Other(other),
48 }
49 }
50
51 pub(crate) fn nibble(self) -> u8 {
52 match self {
53 Self::Sync => 0x0,
54 Self::DelayReq => 0x1,
55 Self::FollowUp => 0x8,
56 Self::DelayResp => 0x9,
57 Self::Announce => 0xb,
58 Self::Management => 0xd,
59 Self::Other(o) => o & 0x0f,
60 }
61 }
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub struct PtpHeader {
67 pub message_type: PtpMessageType,
68 pub version: u8,
69 pub message_length: u16,
70 pub domain: u8,
71 pub flags: u16,
72 pub correction_ns: i64,
74 pub source_clock_id: [u8; 8],
75 pub source_port: u16,
76 pub sequence_id: u16,
77}
78
79impl PtpHeader {
80 pub fn parse(buf: &[u8]) -> Option<Self> {
82 if buf.len() < HEADER_LEN {
83 return None;
84 }
85 let correction_raw = i64::from_be_bytes(buf[8..16].try_into().ok()?);
86 Some(Self {
87 message_type: PtpMessageType::from_nibble(buf[0]),
88 version: buf[1] & 0x0f,
89 message_length: u16::from_be_bytes([buf[2], buf[3]]),
90 domain: buf[4],
91 flags: u16::from_be_bytes([buf[6], buf[7]]),
92 correction_ns: correction_raw >> 16,
94 source_clock_id: buf[20..28].try_into().ok()?,
95 source_port: u16::from_be_bytes([buf[28], buf[29]]),
96 sequence_id: u16::from_be_bytes([buf[30], buf[31]]),
97 })
98 }
99
100 pub fn two_step(&self) -> bool {
103 self.flags & 0x0200 != 0
104 }
105}
106
107pub fn parse_timestamp(buf: &[u8], off: usize) -> Option<u64> {
110 let b = buf.get(off..off + TIMESTAMP_LEN)?;
111 let secs = (u64::from(b[0]) << 40)
112 | (u64::from(b[1]) << 32)
113 | (u64::from(b[2]) << 24)
114 | (u64::from(b[3]) << 16)
115 | (u64::from(b[4]) << 8)
116 | u64::from(b[5]);
117 let nanos = u64::from(u32::from_be_bytes([b[6], b[7], b[8], b[9]]));
118 secs.checked_mul(1_000_000_000)?.checked_add(nanos)
119}
120
121pub fn parse_sync_origin(buf: &[u8]) -> Option<u64> {
124 parse_timestamp(buf, BODY_OFFSET)
125}
126
127pub fn parse_follow_up_origin(buf: &[u8]) -> Option<u64> {
129 parse_timestamp(buf, BODY_OFFSET)
130}
131
132#[derive(Clone, Copy, Debug, PartialEq, Eq)]
135pub struct DelayResp {
136 pub receive_ts_ns: u64,
137 pub requesting_clock_id: [u8; 8],
138 pub requesting_port: u16,
139}
140
141impl DelayResp {
142 pub fn parse(buf: &[u8]) -> Option<Self> {
145 let receive_ts_ns = parse_timestamp(buf, BODY_OFFSET)?;
146 let id_off = BODY_OFFSET + TIMESTAMP_LEN;
148 let clock = buf.get(id_off..id_off + 8)?;
149 let port = buf.get(id_off + 8..id_off + 10)?;
150 Some(Self {
151 receive_ts_ns,
152 requesting_clock_id: clock.try_into().ok()?,
153 requesting_port: u16::from_be_bytes([port[0], port[1]]),
154 })
155 }
156}
157
158pub fn build_delay_req(
162 domain: u8,
163 clock_id: [u8; 8],
164 port: u16,
165 sequence_id: u16,
166) -> [u8; DELAY_REQ_LEN] {
167 let mut m = [0u8; DELAY_REQ_LEN];
168 m[0] = PtpMessageType::DelayReq.nibble(); m[1] = PTP_VERSION; let len = DELAY_REQ_LEN as u16;
171 m[2..4].copy_from_slice(&len.to_be_bytes());
172 m[4] = domain;
173 m[20..28].copy_from_slice(&clock_id);
175 m[28..30].copy_from_slice(&port.to_be_bytes());
176 m[30..32].copy_from_slice(&sequence_id.to_be_bytes());
177 m[32] = 0x01; m[33] = 0x7f; m
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 fn put_header(buf: &mut [u8], mtype: u8, two_step: bool, domain: u8, seq: u16, corr_ns: i64) {
188 buf[0] = mtype & 0x0f;
189 buf[1] = PTP_VERSION;
190 let len = buf.len() as u16;
191 buf[2..4].copy_from_slice(&len.to_be_bytes());
192 buf[4] = domain;
193 let flags: u16 = if two_step { 0x0200 } else { 0 };
194 buf[6..8].copy_from_slice(&flags.to_be_bytes());
195 buf[8..16].copy_from_slice(&(corr_ns << 16).to_be_bytes());
196 buf[20..28].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
197 buf[28..30].copy_from_slice(&1u16.to_be_bytes());
198 buf[30..32].copy_from_slice(&seq.to_be_bytes());
199 }
200
201 fn put_timestamp(buf: &mut [u8], off: usize, secs: u64, nanos: u32) {
202 buf[off] = (secs >> 40) as u8;
203 buf[off + 1] = (secs >> 32) as u8;
204 buf[off + 2] = (secs >> 24) as u8;
205 buf[off + 3] = (secs >> 16) as u8;
206 buf[off + 4] = (secs >> 8) as u8;
207 buf[off + 5] = secs as u8;
208 buf[off + 6..off + 10].copy_from_slice(&nanos.to_be_bytes());
209 }
210
211 #[test]
212 fn rejects_short_buffers() {
213 assert!(PtpHeader::parse(&[0u8; 10]).is_none());
214 assert!(parse_timestamp(&[0u8; 5], 0).is_none());
215 assert!(
216 DelayResp::parse(&[0u8; HEADER_LEN]).is_none(),
217 "no room for the body"
218 );
219 }
220
221 #[test]
222 fn parses_a_two_step_sync_header() {
223 let mut buf = [0u8; HEADER_LEN + TIMESTAMP_LEN];
224 put_header(&mut buf, 0x0, true, 0, 42, 0);
225 let h = PtpHeader::parse(&buf).unwrap();
226 assert_eq!(h.message_type, PtpMessageType::Sync);
227 assert_eq!(h.version, PTP_VERSION);
228 assert_eq!(h.domain, 0);
229 assert_eq!(h.sequence_id, 42);
230 assert_eq!(h.source_clock_id, [1, 2, 3, 4, 5, 6, 7, 8]);
231 assert!(h.two_step(), "twoStepFlag set");
232 }
233
234 #[test]
235 fn parses_timestamps_and_correction() {
236 let mut buf = [0u8; HEADER_LEN + TIMESTAMP_LEN];
237 put_header(&mut buf, 0x8, false, 0, 7, 1234); put_timestamp(&mut buf, BODY_OFFSET, 1_700_000_000, 500_000_000);
239 let h = PtpHeader::parse(&buf).unwrap();
240 assert_eq!(h.message_type, PtpMessageType::FollowUp);
241 assert_eq!(h.correction_ns, 1234);
242 assert_eq!(
243 parse_follow_up_origin(&buf),
244 Some(1_700_000_000_500_000_000)
245 );
246 }
247
248 #[test]
249 fn parses_a_delay_resp_body() {
250 let mut buf = [0u8; HEADER_LEN + TIMESTAMP_LEN + 10];
251 put_header(&mut buf, 0x9, false, 0, 7, 0);
252 put_timestamp(&mut buf, BODY_OFFSET, 1_700_000_001, 250);
253 buf[BODY_OFFSET + TIMESTAMP_LEN..BODY_OFFSET + TIMESTAMP_LEN + 8]
255 .copy_from_slice(&[9, 9, 9, 9, 9, 9, 9, 9]);
256 buf[BODY_OFFSET + TIMESTAMP_LEN + 8..BODY_OFFSET + TIMESTAMP_LEN + 10]
257 .copy_from_slice(&3u16.to_be_bytes());
258 let r = DelayResp::parse(&buf).unwrap();
259 assert_eq!(r.receive_ts_ns, 1_700_000_001_000_000_250);
260 assert_eq!(r.requesting_clock_id, [9; 8]);
261 assert_eq!(r.requesting_port, 3);
262 }
263
264 #[test]
265 fn builds_a_parseable_delay_req() {
266 let m = build_delay_req(0, [1, 2, 3, 4, 5, 6, 7, 8], 1, 99);
267 assert_eq!(m.len(), DELAY_REQ_LEN);
268 let h = PtpHeader::parse(&m).unwrap();
269 assert_eq!(h.message_type, PtpMessageType::DelayReq);
270 assert_eq!(h.version, PTP_VERSION);
271 assert_eq!(h.sequence_id, 99);
272 assert_eq!(h.source_clock_id, [1, 2, 3, 4, 5, 6, 7, 8]);
273 assert_eq!(h.source_port, 1);
274 assert!(!h.two_step());
275 }
276}