Skip to main content

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}