broker_ntp/protocol.rs
1//! Types and constants that precisely match the specification.
2//!
3//! Provides `ReadBytes` and `WriteBytes` implementations which extend the byteorder crate
4//! `WriteBytesExt` and `ReadBytesExt` traits with the ability to read and write types from the NTP
5//! protocol respectively.
6//!
7//! Documentation is largely derived (and often copied directly) from IETF RFC 5905.
8
9use byteorder::{ReadBytesExt, WriteBytesExt, BE};
10use conv::TryFrom;
11use std::{fmt, io};
12
13/// NTP port number.
14pub const PORT: u8 = 123;
15
16/// Frequency tolerance PHI (s/s).
17pub const TOLERANCE: f64 = 15e-6;
18
19/// Minimum poll exponent (16 s).
20pub const MINPOLL: u8 = 4;
21
22/// Maximum poll exponent (36 h).
23pub const MAXPOLL: u8 = 17;
24
25/// Maximum dispersion (16 s).
26pub const MAXDISP: f64 = 16.0;
27
28/// Minimum dispersion increment (s).
29pub const MINDISP: f64 = 0.005;
30
31/// Distance threshold (1 s).
32pub const MAXDIST: u8 = 1;
33
34/// Maximum stratum number.
35pub const MAXSTRAT: u8 = 16;
36
37/// A trait for writing any of the Network Time Protocol types to network-endian bytes.
38///
39/// A blanket implementation is provided for all types that implement `byteorder::WriteBytesExt`.
40pub trait WriteBytes {
41 fn write_bytes<P: WriteToBytes>(&mut self, protocol: P) -> io::Result<()>;
42}
43
44/// A trait for reading any of the Network Time Protocol types from network-endian bytes.
45///
46/// A blanket implementation is provided for all types that implement `byteorder::ReadBytesExt`.
47pub trait ReadBytes {
48 fn read_bytes<P: ReadFromBytes>(&mut self) -> io::Result<P>;
49}
50
51/// Network Time Protocol types that may be written to network endian bytes.
52pub trait WriteToBytes {
53 /// Write the command to bytes.
54 fn write_to_bytes<W: WriteBytesExt>(&self, W) -> io::Result<()>;
55}
56
57/// Network Time Protocol types that may be read from network endian bytes.
58pub trait ReadFromBytes: Sized {
59 /// Read the command from bytes.
60 fn read_from_bytes<R: ReadBytesExt>(R) -> io::Result<Self>;
61}
62
63/// Types that have a constant size when written to or read from bytes.
64pub trait ConstPackedSizeBytes {
65 const PACKED_SIZE_BYTES: usize;
66}
67
68/// **NTP Short Format** - Used in delay and dispersion header fields where the full resolution and
69/// range of the other formats are not justified. It includes a 16-bit unsigned seconds field and a
70/// 16-bit fraction field.
71///
72/// ### Layout
73///
74/// ```ignore
75/// 0 1 2 3
76/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
77/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
78/// | Seconds | Fraction |
79/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80/// ```
81#[repr(C)]
82#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
83pub struct ShortFormat {
84 pub seconds: u16,
85 pub fraction: u16,
86}
87
88/// **NTP Timestamp Format** - Used in packet headers and other places with limited word size. It
89/// includes a 32-bit unsigned seconds field spanning 136 years and a 32-bit fraction field
90/// resolving 232 picoseconds.
91///
92/// The prime epoch is 0 h 1 January 1900 UTC, when all bits are zero.
93///
94/// ### Layout
95///
96/// ```ignore
97/// 0 1 2 3
98/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
99/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
100/// | Seconds |
101/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
102/// | Fraction |
103/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
104/// ```
105#[repr(C)]
106#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
107pub struct TimestampFormat {
108 pub seconds: u32,
109 pub fraction: u32,
110}
111
112/// **NTP Date Format** - The prime epoch, or base date of era 0, is 0 h 1 January 1900 UTC, when all
113/// bits are zero. Dates are relative to the prime epoch; values greater than zero represent times
114/// after that date; values less than zero represent times before it.
115///
116/// Note that the `era_offset` field has the same interpretation as the `seconds` field of the
117/// `TimestampFormat` type.
118///
119/// ```ignore
120/// 0 1 2 3
121/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
122/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
123/// | Era Number |
124/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
125/// | Era Offset |
126/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
127/// | |
128/// | Fraction |
129/// | |
130/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
131/// ```
132#[repr(C)]
133#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
134pub struct DateFormat {
135 pub era_number: i32,
136 pub era_offset: u32,
137 pub fraction: u64,
138}
139
140custom_derive! {
141 /// A 2-bit integer warning of an impending leap second to be inserted or deleted in the last
142 /// minute of the current month with values defined below:
143 ///
144 /// Note that this field is packed in the actual header.
145 ///
146 /// As the only constructors are via associated constants, it should be impossible to create an
147 /// invalid `LeapIndicator`.
148 #[repr(u8)]
149 #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, TryFrom(u8))]
150 pub enum LeapIndicator {
151 /// No leap required.
152 NoWarning = 0,
153 /// Last minute of the day has 61 seconds.
154 AddOne = 1,
155 /// Last minute of the day has 59 seconds.
156 SubOne = 2,
157 /// Clock unsynchronized.
158 Unknown = 3,
159 }
160}
161
162/// A 3-bit integer representing the NTP version number, currently 4.
163///
164/// Note that while this struct is 8-bits, this field is packed to 3 in the actual header.
165///
166/// As the only constructors are via associated constants, it should be impossible to create an
167/// invalid `Version`.
168#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
169pub struct Version(u8);
170
171custom_derive! {
172 /// A 3-bit integer representing the mode.
173 ///
174 /// Note that while this struct is 8-bits, this field is packed to 3 in the actual header.
175 ///
176 /// As the only constructors are via associated constants, it should be impossible to create an
177 /// invalid `Mode`.
178 #[repr(u8)]
179 #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, TryFrom(u8))]
180 pub enum Mode {
181 Reserved = 0,
182 SymmetricActive = 1,
183 SymmetricPassive = 2,
184 Client = 3,
185 Server = 4,
186 Broadcast = 5,
187 NtpControlMessage = 6,
188 ReservedForPrivateUse = 7,
189 }
190}
191
192/// An 8-bit integer representing the stratum.
193///
194/// ```ignore
195/// +--------+-----------------------------------------------------+
196/// | Value | Meaning |
197/// +--------+-----------------------------------------------------+
198/// | 0 | unspecified or invalid |
199/// | 1 | primary server (e.g., equipped with a GPS receiver) |
200/// | 2-15 | secondary server (via NTP) |
201/// | 16 | unsynchronized |
202/// | 17-255 | reserved |
203/// +--------+-----------------------------------------------------+
204/// ```
205///
206/// It is customary to map the stratum value 0 in received packets to `MAXSTRAT` in the peer
207/// variable p.stratum and to map p.stratum values of `MAXSTRAT` or greater to 0 in transmitted
208/// packets. This allows reference clocks, which normally appear at stratum 0, to be conveniently
209/// mitigated using the same clock selection algorithms used for external sources.
210#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
211pub struct Stratum(pub u8);
212
213/// A 32-bit code identifying the particular server or reference clock.
214///
215/// The interpretation depends on the value in the stratum field:
216///
217/// - For packet stratum 0 (unspecified or invalid), this is a four-character ASCII [RFC1345]
218/// string, called the "kiss code", used for debugging and monitoring purposes.
219/// - For stratum 1 (reference clock), this is a four-octet, left-justified, zero-padded ASCII
220/// string assigned to the reference clock.
221///
222/// The authoritative list of Reference Identifiers is maintained by IANA; however, any string
223/// beginning with the ASCII character "X" is reserved for unregistered experimentation and
224/// development.
225#[repr(u32)]
226#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
227pub enum ReferenceIdentifier {
228 PrimarySource(PrimarySource),
229 /// The reference identifier of the secondary or client server. Can be used to detect timing
230 /// loops.
231 ///
232 /// If using the IPv4 address family, the identifier is the four-octet IPv4 address.
233 ///
234 /// If using the IPv6 address family, it is the first four octets of the MD5 hash of the IPv6
235 /// address. Note that when using the IPv6 address family on a NTPv4 server with a NTPv3
236 /// client, the Reference Identifier field appears to be a random value and a timing loop might
237 /// not be detected.
238 SecondaryOrClient([u8; 4]),
239 KissOfDeath(KissOfDeath),
240}
241
242// Convert an ascii string to a big-endian u32.
243macro_rules! code_to_u32 {
244 ($w:expr) => {
245 (($w[3] as u32) << 0) |
246 (($w[2] as u32) << 8) |
247 (($w[1] as u32) << 16) |
248 (($w[0] as u32) << 24) |
249 ((*$w as [u8; 4])[0] as u32 * 0)
250 };
251}
252
253custom_derive! {
254 /// A four-octet, left-justified, zero-padded ASCII string assigned to the reference clock.
255 ///
256 /// The authoritative list of Reference Identifiers is maintained by IANA; however, any string
257 /// beginning with the ASCII character "X" is reserved for unregistered experimentation and
258 /// development.
259 #[repr(u32)]
260 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TryFrom(u32))]
261 pub enum PrimarySource {
262 Goes = code_to_u32!(b"GOES"),
263 Gps = code_to_u32!(b"GPS\0"),
264 Cdma = code_to_u32!(b"CDMA"),
265 Gal = code_to_u32!(b"GAL\0"),
266 Pps = code_to_u32!(b"PPS\0"),
267 Irig = code_to_u32!(b"IRIG"),
268 Wwvb = code_to_u32!(b"WWVB"),
269 Dcf = code_to_u32!(b"DCF\0"),
270 Hgb = code_to_u32!(b"HGB\0"),
271 Msf = code_to_u32!(b"MSF\0"),
272 Jjy = code_to_u32!(b"JJY\0"),
273 Lorc = code_to_u32!(b"LORC"),
274 Tdf = code_to_u32!(b"TDF\0"),
275 Chu = code_to_u32!(b"CHU\0"),
276 Wwv = code_to_u32!(b"WWV\0"),
277 Wwvh = code_to_u32!(b"WWVH"),
278 Nist = code_to_u32!(b"NIST"),
279 Acts = code_to_u32!(b"ACTS"),
280 Usno = code_to_u32!(b"USNO"),
281 Ptb = code_to_u32!(b"PTB\0"),
282 Goog = code_to_u32!(b"GOOG"),
283 Locl = code_to_u32!(b"LOCL"),
284 Cesm = code_to_u32!(b"CESM"),
285 Rbdm = code_to_u32!(b"RBDM"),
286 Omeg = code_to_u32!(b"OMEG"),
287 Dcn = code_to_u32!(b"DCN\0"),
288 Tsp = code_to_u32!(b"TSP\0"),
289 Dts = code_to_u32!(b"DTS\0"),
290 Atom = code_to_u32!(b"ATOM"),
291 Vlf = code_to_u32!(b"VLF\0"),
292 Opps = code_to_u32!(b"OPPS"),
293 Free = code_to_u32!(b"FREE"),
294 Init = code_to_u32!(b"INIT"),
295 Null = 0,
296 }
297}
298
299custom_derive! {
300 /// If the Stratum field is 0, which implies unspecified or invalid, the Reference Identifier
301 /// field can be used to convey messages useful for status reporting and access control. These
302 /// are called **Kiss-o'-Death** (KoD) packets and the ASCII messages they convey are called
303 /// kiss codes.
304 ///
305 /// The KoD packets got their name because an early use was to tell clients to stop sending
306 /// packets that violate server access controls. The kiss codes can provide useful information
307 /// for an intelligent client, either NTPv4 or SNTPv4. Kiss codes are encoded in four-character
308 /// ASCII strings that are left justified and zero filled. The strings are designed for
309 /// character displays and log files.
310 ///
311 /// Recipients of kiss codes MUST inspect them and, in the following cases, take the actions
312 /// described.
313 #[repr(u32)]
314 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TryFrom(u32))]
315 pub enum KissOfDeath {
316 /// The client MUST demobilize any associations to that server and stop sending packets to it.
317 Deny = code_to_u32!(b"DENY"),
318 /// The client MUST demobilize any associations to that server and stop sending packets to it.
319 Rstr = code_to_u32!(b"RSTR"),
320 /// The client MUST immediately reduce its polling interval to that server and continue to
321 /// reduce it each time it receives a RATE kiss code.
322 Rate = code_to_u32!(b"RATE"),
323 }
324}
325
326/// **Packet Header** - The most important state variables from an external point of view are the
327/// packet header variables described here.
328///
329/// The NTP packet header consists of an integral number of 32-bit (4 octet) words in network byte
330/// order. The packet format consists of three components: the header itself, one or more optional
331/// extension fields, and an optional message authentication code (MAC).
332///
333/// ```ignore
334/// +-----------+------------+-----------------------+
335/// | Name | Formula | Description |
336/// +-----------+------------+-----------------------+
337/// | leap | leap | leap indicator (LI) |
338/// | version | version | version number (VN) |
339/// | mode | mode | mode |
340/// | stratum | stratum | stratum |
341/// | poll | poll | poll exponent |
342/// | precision | rho | precision exponent |
343/// | rootdelay | delta_r | root delay |
344/// | rootdisp | epsilon_r | root dispersion |
345/// | refid | refid | reference ID |
346/// | reftime | reftime | reference timestamp |
347/// | org | T1 | origin timestamp |
348/// | rec | T2 | receive timestamp |
349/// | xmt | T3 | transmit timestamp |
350/// | dst | T4 | destination timestamp |
351/// | keyid | keyid | key ID |
352/// | dgst | dgst | message digest |
353/// +-----------+------------+-----------------------+
354/// ```
355///
356/// ### Format
357///
358/// The NTP packet is a UDP datagram [RFC0768]. Some fields use multiple words and others are
359/// packed in smaller fields within a word. The NTP packet header shown below has 12 words followed
360/// by optional extension fields and finally an optional message authentication code (MAC)
361/// consisting of the Key Identifier field and Message Digest field.
362///
363/// ```ignore
364/// 0 1 2 3
365/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
366/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
367/// |LI | VN |Mode | Stratum | Poll | Precision |
368/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
369/// | Root Delay |
370/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
371/// | Root Dispersion |
372/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
373/// | Reference ID |
374/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
375/// | |
376/// + Reference Timestamp (64) +
377/// | |
378/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
379/// | |
380/// + Origin Timestamp (64) +
381/// | |
382/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
383/// | |
384/// + Receive Timestamp (64) +
385/// | |
386/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
387/// | |
388/// + Transmit Timestamp (64) +
389/// | |
390/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
391/// | |
392/// . .
393/// . Extension Field 1 (variable) .
394/// . .
395/// | |
396/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
397/// | |
398/// . .
399/// . Extension Field 2 (variable) .
400/// . .
401/// | |
402/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
403/// | Key Identifier |
404/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
405/// | |
406/// | dgst (128) |
407/// | |
408/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
409/// ```
410#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
411pub struct Packet {
412 pub leap_indicator: LeapIndicator,
413 pub version: Version,
414 pub mode: Mode,
415 pub stratum: Stratum,
416 /// 8-bit signed integer representing the maximum interval between successive messages, in log2
417 /// seconds. Suggested default limits for minimum and maximum poll intervals are 6 and 10,
418 /// respectively.
419 pub poll: i8,
420 /// 8-bit signed integer representing the precision of the system clock, in log2 seconds. For
421 /// instance, a value of -18 corresponds to a precision of about one microsecond. The precision
422 /// can be determined when the service first starts up as the minimum time of several
423 /// iterations to read the system clock.
424 pub precision: i8,
425 /// Total round-trip delay to the reference clock, in NTP short format.
426 pub root_delay: ShortFormat,
427 /// Total dispersion to the reference clock, in NTP short format.
428 pub root_dispersion: ShortFormat,
429 pub reference_id: ReferenceIdentifier,
430 /// Time when the system clock was last set or corrected.
431 pub reference_timestamp: TimestampFormat,
432 /// Time at the client when the request departed for the server.
433 pub origin_timestamp: TimestampFormat,
434 /// Time at the server when the request arrived from the client.
435 pub receive_timestamp: TimestampFormat,
436 /// Time at the server when the response left for the client.
437 pub transmit_timestamp: TimestampFormat,
438}
439
440/// The consecutive types within the first packed byte in the NTP packet.
441pub type PacketByte1 = (LeapIndicator, Version, Mode);
442
443// Inherent implementations.
444
445impl PrimarySource {
446 /// The bytestring representation of the primary source.
447 pub fn bytes(&self) -> [u8; 4] {
448 be_u32_to_bytes(*self as u32)
449 }
450}
451
452impl Version {
453 pub const V1: Self = Version(1);
454 pub const V2: Self = Version(2);
455 pub const V3: Self = Version(3);
456 pub const V4: Self = Version(4);
457
458 /// Whether or not the version is a known, valid version.
459 pub fn is_known(&self) -> bool {
460 self.0 >= 1 && self.0 <= 4
461 }
462}
463
464impl Stratum {
465 /// Unspecified or invalid.
466 pub const UNSPECIFIED: Self = Stratum(0);
467 /// The primary server (e.g. equipped with a GPS receiver.
468 pub const PRIMARY: Self = Stratum(1);
469 /// The minimum value specifying a secondary server (via NTP).
470 pub const SECONDARY_MIN: Self = Stratum(2);
471 /// The maximum value specifying a secondary server (via NTP).
472 pub const SECONDARY_MAX: Self = Stratum(15);
473 /// An unsynchronized stratum.
474 pub const UNSYNCHRONIZED: Self = Stratum(16);
475 /// The maximum valid stratum value.
476 pub const MAX: Self = Stratum(16);
477
478 /// Whether or not the stratum represents a secondary server.
479 pub fn is_secondary(&self) -> bool {
480 Self::SECONDARY_MIN <= *self && *self <= Self::SECONDARY_MAX
481 }
482
483 /// Whether or not the stratum is in the reserved range.
484 pub fn is_reserved(&self) -> bool {
485 *self > Self::MAX
486 }
487}
488
489// Size implementations.
490
491impl ConstPackedSizeBytes for ShortFormat {
492 const PACKED_SIZE_BYTES: usize = 4;
493}
494
495impl ConstPackedSizeBytes for TimestampFormat {
496 const PACKED_SIZE_BYTES: usize = 8;
497}
498
499impl ConstPackedSizeBytes for DateFormat {
500 const PACKED_SIZE_BYTES: usize = 16;
501}
502
503impl ConstPackedSizeBytes for Stratum {
504 const PACKED_SIZE_BYTES: usize = 1;
505}
506
507impl ConstPackedSizeBytes for ReferenceIdentifier {
508 const PACKED_SIZE_BYTES: usize = 4;
509}
510
511impl ConstPackedSizeBytes for PacketByte1 {
512 const PACKED_SIZE_BYTES: usize = 1;
513}
514
515impl ConstPackedSizeBytes for Packet {
516 const PACKED_SIZE_BYTES: usize =
517 PacketByte1::PACKED_SIZE_BYTES
518 + Stratum::PACKED_SIZE_BYTES
519 + 2
520 + ShortFormat::PACKED_SIZE_BYTES * 2
521 + ReferenceIdentifier::PACKED_SIZE_BYTES
522 + TimestampFormat::PACKED_SIZE_BYTES * 4;
523}
524
525// Writer implementations.
526
527impl<W> WriteBytes for W
528where
529 W: WriteBytesExt,
530{
531 fn write_bytes<P: WriteToBytes>(&mut self, protocol: P) -> io::Result<()> {
532 protocol.write_to_bytes(self)
533 }
534}
535
536impl<'a, P> WriteToBytes for &'a P
537where
538 P: WriteToBytes,
539{
540 fn write_to_bytes<W: WriteBytesExt>(&self, writer: W) -> io::Result<()> {
541 (*self).write_to_bytes(writer)
542 }
543}
544
545impl WriteToBytes for ShortFormat {
546 fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
547 writer.write_u16::<BE>(self.seconds)?;
548 writer.write_u16::<BE>(self.fraction)?;
549 Ok(())
550 }
551}
552
553impl WriteToBytes for TimestampFormat {
554 fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
555 writer.write_u32::<BE>(self.seconds)?;
556 writer.write_u32::<BE>(self.fraction)?;
557 Ok(())
558 }
559}
560
561impl WriteToBytes for DateFormat {
562 fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
563 writer.write_i32::<BE>(self.era_number)?;
564 writer.write_u32::<BE>(self.era_offset)?;
565 writer.write_u64::<BE>(self.fraction)?;
566 Ok(())
567 }
568}
569
570impl WriteToBytes for Stratum {
571 fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
572 writer.write_u8(self.0)?;
573 Ok(())
574 }
575}
576
577impl WriteToBytes for ReferenceIdentifier {
578 fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
579 match *self {
580 ReferenceIdentifier::KissOfDeath(kod) => {
581 writer.write_u32::<BE>(kod as u32)?;
582 }
583 ReferenceIdentifier::PrimarySource(src) => {
584 writer.write_u32::<BE>(src as u32)?;
585 }
586 ReferenceIdentifier::SecondaryOrClient(arr) => {
587 writer.write_u32::<BE>(code_to_u32!(&arr))?;
588 }
589 }
590 Ok(())
591 }
592}
593
594impl WriteToBytes for (LeapIndicator, Version, Mode) {
595 fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
596 let (li, vn, mode) = *self;
597 let mut li_vn_mode = 0;
598 li_vn_mode |= (li as u8) << 6;
599 li_vn_mode |= vn.0 << 3;
600 li_vn_mode |= mode as u8;
601 writer.write_u8(li_vn_mode)?;
602 Ok(())
603 }
604}
605
606impl WriteToBytes for Packet {
607 fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
608 let li_vn_mode = (self.leap_indicator, self.version, self.mode);
609 writer.write_bytes(li_vn_mode)?;
610 writer.write_bytes(self.stratum)?;
611 writer.write_i8(self.poll)?;
612 writer.write_i8(self.precision)?;
613 writer.write_bytes(self.root_delay)?;
614 writer.write_bytes(self.root_dispersion)?;
615 writer.write_bytes(self.reference_id)?;
616 writer.write_bytes(self.reference_timestamp)?;
617 writer.write_bytes(self.origin_timestamp)?;
618 writer.write_bytes(self.receive_timestamp)?;
619 writer.write_bytes(self.transmit_timestamp)?;
620 Ok(())
621 }
622}
623
624// Reader implementations.
625
626impl<R> ReadBytes for R
627where
628 R: ReadBytesExt,
629{
630 fn read_bytes<P: ReadFromBytes>(&mut self) -> io::Result<P> {
631 P::read_from_bytes(self)
632 }
633}
634
635impl ReadFromBytes for ShortFormat {
636 fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
637 let seconds = reader.read_u16::<BE>()?;
638 let fraction = reader.read_u16::<BE>()?;
639 let short_format = ShortFormat { seconds, fraction };
640 Ok(short_format)
641 }
642}
643
644impl ReadFromBytes for TimestampFormat {
645 fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
646 let seconds = reader.read_u32::<BE>()?;
647 let fraction = reader.read_u32::<BE>()?;
648 let timestamp_format = TimestampFormat { seconds, fraction };
649 Ok(timestamp_format)
650 }
651}
652
653impl ReadFromBytes for DateFormat {
654 fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
655 let era_number = reader.read_i32::<BE>()?;
656 let era_offset = reader.read_u32::<BE>()?;
657 let fraction = reader.read_u64::<BE>()?;
658 let date_format = DateFormat { era_number, era_offset, fraction };
659 Ok(date_format)
660 }
661}
662
663impl ReadFromBytes for Stratum {
664 fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
665 let stratum = Stratum(reader.read_u8()?);
666 Ok(stratum)
667 }
668}
669
670impl ReadFromBytes for (LeapIndicator, Version, Mode) {
671 fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
672 let li_vn_mode = reader.read_u8()?;
673 let li_u8 = li_vn_mode >> 6;
674 let vn_u8 = (li_vn_mode >> 3) & 0b111;
675 let mode_u8 = li_vn_mode & 0b111;
676 let li = match LeapIndicator::try_from(li_u8).ok() {
677 Some(li) => li,
678 None => {
679 let err_msg = "unknown leap indicator";
680 return Err(io::Error::new(io::ErrorKind::InvalidData, err_msg));
681 },
682 };
683 let vn = Version(vn_u8);
684 let mode = match Mode::try_from(mode_u8).ok() {
685 Some(mode) => mode,
686 None => {
687 let err_msg = "unknown association mode";
688 return Err(io::Error::new(io::ErrorKind::InvalidData, err_msg));
689 },
690 };
691 Ok((li, vn, mode))
692 }
693}
694
695impl ReadFromBytes for Packet {
696 fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
697 let (leap_indicator, version, mode) = reader.read_bytes()?;
698 let stratum = reader.read_bytes::<Stratum>()?;
699 let poll = reader.read_i8()?;
700 let precision = reader.read_i8()?;
701 let root_delay = reader.read_bytes()?;
702 let root_dispersion = reader.read_bytes()?;
703 let reference_id = {
704 let u = reader.read_u32::<BE>()?;
705 if stratum == Stratum::PRIMARY {
706 match PrimarySource::try_from(u) {
707 Ok(src) => ReferenceIdentifier::PrimarySource(src),
708 Err(_) => match KissOfDeath::try_from(u) {
709 Ok(kod) => ReferenceIdentifier::KissOfDeath(kod),
710 Err(_) => {
711 let err_msg = "unknown reference id";
712 return Err(io::Error::new(io::ErrorKind::InvalidData, err_msg));
713 }
714 },
715 }
716 } else if stratum.is_secondary() {
717 let arr = be_u32_to_bytes(u);
718 ReferenceIdentifier::SecondaryOrClient(arr)
719 } else {
720 let err_msg = "unsupported stratum";
721 return Err(io::Error::new(io::ErrorKind::InvalidData, err_msg));
722 }
723 };
724 let reference_timestamp = reader.read_bytes()?;
725 let origin_timestamp = reader.read_bytes()?;
726 let receive_timestamp = reader.read_bytes()?;
727 let transmit_timestamp = reader.read_bytes()?;
728 Ok(Packet {
729 leap_indicator,
730 version,
731 mode,
732 stratum,
733 poll,
734 precision,
735 root_delay,
736 root_dispersion,
737 reference_id,
738 reference_timestamp,
739 origin_timestamp,
740 receive_timestamp,
741 transmit_timestamp,
742 })
743 }
744}
745
746// Manual default implementations.
747
748impl Default for LeapIndicator {
749 fn default() -> Self {
750 LeapIndicator::NoWarning
751 }
752}
753
754// Display implementations.
755
756impl fmt::Display for PrimarySource {
757 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
758 let bytes = self.bytes();
759 let s = String::from_utf8_lossy(&bytes);
760 write!(f, "{}", s)
761 }
762}
763
764// Utility functions.
765
766fn be_u32_to_bytes(u: u32) -> [u8; 4] {
767 [
768 (u >> 24 & 0xff) as u8,
769 (u >> 16 & 0xff) as u8,
770 (u >> 8 & 0xff) as u8,
771 (u & 0xff) as u8,
772 ]
773}