Skip to main content

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
9#[cfg(feature = "std")]
10use byteorder::{BE, ReadBytesExt, WriteBytesExt};
11use core::fmt;
12#[cfg(feature = "std")]
13use std::io;
14
15use crate::error::ParseError;
16
17/// NTP port number.
18pub const PORT: u8 = 123;
19
20/// Frequency tolerance PHI (s/s).
21pub const TOLERANCE: f64 = 15e-6;
22
23/// Minimum poll exponent (16 s).
24pub const MINPOLL: u8 = 4;
25
26/// Maximum poll exponent (36 h).
27pub const MAXPOLL: u8 = 17;
28
29/// Maximum dispersion (16 s).
30pub const MAXDISP: f64 = 16.0;
31
32/// Minimum dispersion increment (s).
33pub const MINDISP: f64 = 0.005;
34
35/// Distance threshold (1 s).
36pub const MAXDIST: u8 = 1;
37
38/// Maximum stratum number.
39pub const MAXSTRAT: u8 = 16;
40
41/// A trait for writing any of the Network Time Protocol types to network-endian bytes.
42///
43/// A blanket implementation is provided for all types that implement `byteorder::WriteBytesExt`.
44/// Requires the `std` feature.
45#[cfg(feature = "std")]
46pub trait WriteBytes {
47    /// Writes an NTP protocol type to this writer in network byte order.
48    fn write_bytes<P: WriteToBytes>(&mut self, protocol: P) -> io::Result<()>;
49}
50
51/// A trait for reading any of the Network Time Protocol types from network-endian bytes.
52///
53/// A blanket implementation is provided for all types that implement `byteorder::ReadBytesExt`.
54/// Requires the `std` feature.
55#[cfg(feature = "std")]
56pub trait ReadBytes {
57    /// Reads an NTP protocol type from this reader in network byte order.
58    fn read_bytes<P: ReadFromBytes>(&mut self) -> io::Result<P>;
59}
60
61/// Network Time Protocol types that may be written to network endian bytes.
62/// Requires the `std` feature.
63#[cfg(feature = "std")]
64pub trait WriteToBytes {
65    /// Write the command to bytes.
66    fn write_to_bytes<W: WriteBytesExt>(&self, writer: W) -> io::Result<()>;
67}
68
69/// Network Time Protocol types that may be read from network endian bytes.
70/// Requires the `std` feature.
71#[cfg(feature = "std")]
72pub trait ReadFromBytes: Sized {
73    /// Read the command from bytes.
74    fn read_from_bytes<R: ReadBytesExt>(reader: R) -> io::Result<Self>;
75}
76
77/// Types that have a constant size when written to or read from bytes.
78pub trait ConstPackedSizeBytes {
79    /// The constant size in bytes when this type is packed for network transmission.
80    const PACKED_SIZE_BYTES: usize;
81}
82
83/// Parse a type from a byte slice, returning the parsed value and the number
84/// of bytes consumed.
85///
86/// Unlike [`ReadFromBytes`], this trait does not require `std::io` or the `byteorder` crate.
87/// It operates directly on `&[u8]` slices, making it suitable for `no_std` environments
88/// and packet capture analysis.
89pub trait FromBytes: Sized {
90    /// Parse from the given byte slice. Returns the parsed value and the
91    /// number of bytes consumed from the front of `buf`.
92    fn from_bytes(buf: &[u8]) -> Result<(Self, usize), ParseError>;
93}
94
95/// Serialize a type into a byte slice, returning the number of bytes written.
96///
97/// Unlike [`WriteToBytes`], this trait does not require `std::io` or the `byteorder` crate.
98/// It operates directly on `&mut [u8]` slices, making it suitable for `no_std` environments.
99pub trait ToBytes {
100    /// Write this value into the given byte slice. Returns the number of bytes
101    /// written. Fails with [`ParseError::BufferTooShort`] if `buf` is too short.
102    fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ParseError>;
103}
104
105/// **NTP Short Format** - Used in delay and dispersion header fields where the full resolution and
106/// range of the other formats are not justified. It includes a 16-bit unsigned seconds field and a
107/// 16-bit fraction field.
108///
109/// ### Layout
110///
111/// ```ignore
112///  0                   1                   2                   3
113///  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
114/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
115/// |          Seconds              |           Fraction            |
116/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
117/// ```
118#[repr(C)]
119#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
120pub struct ShortFormat {
121    /// Seconds component (16-bit unsigned).
122    pub seconds: u16,
123    /// Fractional seconds component (16-bit unsigned).
124    pub fraction: u16,
125}
126
127/// **NTP Timestamp Format** - Used in packet headers and other places with limited word size. It
128/// includes a 32-bit unsigned seconds field spanning 136 years and a 32-bit fraction field
129/// resolving 232 picoseconds.
130///
131/// The prime epoch is 0 h 1 January 1900 UTC, when all bits are zero.
132///
133/// ### Layout
134///
135/// ```ignore
136///  0                   1                   2                   3
137///  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
138/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
139/// |                            Seconds                            |
140/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
141/// |                            Fraction                           |
142/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
143/// ```
144#[repr(C)]
145#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
146pub struct TimestampFormat {
147    /// Seconds since 1900-01-01 00:00:00 UTC (32-bit unsigned).
148    pub seconds: u32,
149    /// Fractional seconds (32-bit unsigned, resolution of ~232 picoseconds).
150    pub fraction: u32,
151}
152
153/// **NTP Date Format** - The prime epoch, or base date of era 0, is 0 h 1 January 1900 UTC, when all
154/// bits are zero. Dates are relative to the prime epoch; values greater than zero represent times
155/// after that date; values less than zero represent times before it.
156///
157/// Note that the `era_offset` field has the same interpretation as the `seconds` field of the
158/// `TimestampFormat` type.
159///
160/// ```ignore
161///  0                   1                   2                   3
162///  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
163/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
164/// |                           Era Number                          |
165/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
166/// |                           Era Offset                          |
167/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
168/// |                                                               |
169/// |                           Fraction                            |
170/// |                                                               |
171/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
172/// ```
173#[repr(C)]
174#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
175pub struct DateFormat {
176    /// Era number (signed 32-bit integer).
177    pub era_number: i32,
178    /// Offset within the era in seconds (32-bit unsigned).
179    pub era_offset: u32,
180    /// Fractional seconds (64-bit unsigned).
181    pub fraction: u64,
182}
183
184/// A 2-bit integer warning of an impending leap second to be inserted or deleted in the last
185/// minute of the current month with values defined below:
186///
187/// Note that this field is packed in the actual header.
188///
189/// As the only constructors are via associated constants, it should be impossible to create an
190/// invalid `LeapIndicator`.
191#[repr(u8)]
192#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
193pub enum LeapIndicator {
194    /// No leap required.
195    #[default]
196    NoWarning = 0,
197    /// Last minute of the day has 61 seconds.
198    AddOne = 1,
199    /// Last minute of the day has 59 seconds.
200    SubOne = 2,
201    /// Clock unsynchronized.
202    Unknown = 3,
203}
204
205impl TryFrom<u8> for LeapIndicator {
206    type Error = ();
207
208    fn try_from(value: u8) -> Result<Self, Self::Error> {
209        match value {
210            0 => Ok(LeapIndicator::NoWarning),
211            1 => Ok(LeapIndicator::AddOne),
212            2 => Ok(LeapIndicator::SubOne),
213            3 => Ok(LeapIndicator::Unknown),
214            _ => Err(()),
215        }
216    }
217}
218
219/// A 3-bit integer representing the NTP version number, currently 4.
220///
221/// Note that while this struct is 8-bits, this field is packed to 3 in the actual header.
222///
223/// As the only constructors are via associated constants, it should be impossible to create an
224/// invalid `Version`.
225#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
226pub struct Version(u8);
227
228/// A 3-bit integer representing the mode.
229///
230/// Note that while this struct is 8-bits, this field is packed to 3 in the actual header.
231///
232/// As the only constructors are via associated constants, it should be impossible to create an
233/// invalid `Mode`.
234#[repr(u8)]
235#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
236pub enum Mode {
237    /// Reserved mode (value 0).
238    Reserved = 0,
239    /// Symmetric active mode (value 1).
240    SymmetricActive = 1,
241    /// Symmetric passive mode (value 2).
242    SymmetricPassive = 2,
243    /// Client mode (value 3).
244    Client = 3,
245    /// Server mode (value 4).
246    Server = 4,
247    /// Broadcast mode (value 5).
248    Broadcast = 5,
249    /// NTP control message mode (value 6).
250    NtpControlMessage = 6,
251    /// Reserved for private use (value 7).
252    ReservedForPrivateUse = 7,
253}
254
255impl TryFrom<u8> for Mode {
256    type Error = ();
257
258    fn try_from(value: u8) -> Result<Self, Self::Error> {
259        match value {
260            0 => Ok(Mode::Reserved),
261            1 => Ok(Mode::SymmetricActive),
262            2 => Ok(Mode::SymmetricPassive),
263            3 => Ok(Mode::Client),
264            4 => Ok(Mode::Server),
265            5 => Ok(Mode::Broadcast),
266            6 => Ok(Mode::NtpControlMessage),
267            7 => Ok(Mode::ReservedForPrivateUse),
268            _ => Err(()),
269        }
270    }
271}
272
273/// An 8-bit integer representing the stratum.
274///
275/// ```ignore
276/// +--------+-----------------------------------------------------+
277/// | Value  | Meaning                                             |
278/// +--------+-----------------------------------------------------+
279/// | 0      | unspecified or invalid                              |
280/// | 1      | primary server (e.g., equipped with a GPS receiver) |
281/// | 2-15   | secondary server (via NTP)                          |
282/// | 16     | unsynchronized                                      |
283/// | 17-255 | reserved                                            |
284/// +--------+-----------------------------------------------------+
285/// ```
286///
287/// It is customary to map the stratum value 0 in received packets to `MAXSTRAT` in the peer
288/// variable p.stratum and to map p.stratum values of `MAXSTRAT` or greater to 0 in transmitted
289/// packets. This allows reference clocks, which normally appear at stratum 0, to be conveniently
290/// mitigated using the same clock selection algorithms used for external sources.
291#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
292pub struct Stratum(pub u8);
293
294/// A 32-bit code identifying the particular server or reference clock.
295///
296/// The interpretation depends on the value in the stratum field:
297///
298/// - For packet stratum 0 (unspecified or invalid), this is a four-character ASCII \[RFC1345\]
299///   string, called the "kiss code", used for debugging and monitoring purposes.
300/// - For stratum 1 (reference clock), this is a four-octet, left-justified, zero-padded ASCII
301///   string assigned to the reference clock.
302///
303/// The authoritative list of Reference Identifiers is maintained by IANA; however, any string
304/// beginning with the ASCII character "X" is reserved for unregistered experimentation and
305/// development.
306#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
307pub enum ReferenceIdentifier {
308    /// Primary reference source (stratum 1) identifier.
309    PrimarySource(PrimarySource),
310    /// The reference identifier of the secondary or client server. Can be used to detect timing
311    /// loops.
312    ///
313    /// If using the IPv4 address family, the identifier is the four-octet IPv4 address.
314    ///
315    /// If using the IPv6 address family, it is the first four octets of the MD5 hash of the IPv6
316    /// address. Note that when using the IPv6 address family on a NTPv4 server with a NTPv3
317    /// client, the Reference Identifier field appears to be a random value and a timing loop might
318    /// not be detected.
319    SecondaryOrClient([u8; 4]),
320    /// Kiss-o'-Death packet code (stratum 0).
321    KissOfDeath(KissOfDeath),
322    /// An unrecognized 4-byte reference identifier.
323    ///
324    /// Used for stratum 0 packets with non-standard kiss codes, stratum 1 packets with
325    /// unrecognized reference source identifiers, and stratum 16+ (unsynchronized/reserved)
326    /// packets.
327    Unknown([u8; 4]),
328}
329
330// Convert an ascii string to a big-endian u32.
331macro_rules! code_to_u32 {
332    ($w:expr) => {
333        (($w[3] as u32) << 0)
334            | (($w[2] as u32) << 8)
335            | (($w[1] as u32) << 16)
336            | (($w[0] as u32) << 24)
337            | ((*$w as [u8; 4])[0] as u32 * 0)
338    };
339}
340
341/// A four-octet, left-justified, zero-padded ASCII string assigned to the reference clock.
342///
343/// The authoritative list of Reference Identifiers is maintained by IANA; however, any string
344/// beginning with the ASCII character "X" is reserved for unregistered experimentation and
345/// development.
346///
347/// All variants represent standard reference clock identifiers as four-character ASCII codes.
348/// See the [IANA NTP Parameters](https://www.iana.org/assignments/ntp-parameters/) registry
349/// for the complete list of reference identifiers.
350#[repr(u32)]
351#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
352#[allow(missing_docs)]
353pub enum PrimarySource {
354    /// Geosynchronous Orbit Environment Satellite.
355    Goes = code_to_u32!(b"GOES"),
356    /// Global Position System.
357    Gps = code_to_u32!(b"GPS\0"),
358    /// Code Division Multiple Access.
359    Cdma = code_to_u32!(b"CDMA"),
360    Gal = code_to_u32!(b"GAL\0"),
361    Pps = code_to_u32!(b"PPS\0"),
362    Irig = code_to_u32!(b"IRIG"),
363    Wwvb = code_to_u32!(b"WWVB"),
364    Dcf = code_to_u32!(b"DCF\0"),
365    Hgb = code_to_u32!(b"HGB\0"),
366    Msf = code_to_u32!(b"MSF\0"),
367    Jjy = code_to_u32!(b"JJY\0"),
368    Lorc = code_to_u32!(b"LORC"),
369    Tdf = code_to_u32!(b"TDF\0"),
370    Chu = code_to_u32!(b"CHU\0"),
371    Wwv = code_to_u32!(b"WWV\0"),
372    Wwvh = code_to_u32!(b"WWVH"),
373    Nist = code_to_u32!(b"NIST"),
374    Acts = code_to_u32!(b"ACTS"),
375    Usno = code_to_u32!(b"USNO"),
376    Ptb = code_to_u32!(b"PTB\0"),
377    Goog = code_to_u32!(b"GOOG"),
378    Locl = code_to_u32!(b"LOCL"),
379    Cesm = code_to_u32!(b"CESM"),
380    Rbdm = code_to_u32!(b"RBDM"),
381    Omeg = code_to_u32!(b"OMEG"),
382    Dcn = code_to_u32!(b"DCN\0"),
383    Tsp = code_to_u32!(b"TSP\0"),
384    Dts = code_to_u32!(b"DTS\0"),
385    Atom = code_to_u32!(b"ATOM"),
386    Vlf = code_to_u32!(b"VLF\0"),
387    Opps = code_to_u32!(b"OPPS"),
388    Free = code_to_u32!(b"FREE"),
389    Init = code_to_u32!(b"INIT"),
390    Null = 0,
391}
392
393impl TryFrom<u32> for PrimarySource {
394    type Error = ();
395
396    fn try_from(value: u32) -> Result<Self, Self::Error> {
397        match value {
398            v if v == code_to_u32!(b"GOES") => Ok(PrimarySource::Goes),
399            v if v == code_to_u32!(b"GPS\0") => Ok(PrimarySource::Gps),
400            v if v == code_to_u32!(b"CDMA") => Ok(PrimarySource::Cdma),
401            v if v == code_to_u32!(b"GAL\0") => Ok(PrimarySource::Gal),
402            v if v == code_to_u32!(b"PPS\0") => Ok(PrimarySource::Pps),
403            v if v == code_to_u32!(b"IRIG") => Ok(PrimarySource::Irig),
404            v if v == code_to_u32!(b"WWVB") => Ok(PrimarySource::Wwvb),
405            v if v == code_to_u32!(b"DCF\0") => Ok(PrimarySource::Dcf),
406            v if v == code_to_u32!(b"HGB\0") => Ok(PrimarySource::Hgb),
407            v if v == code_to_u32!(b"MSF\0") => Ok(PrimarySource::Msf),
408            v if v == code_to_u32!(b"JJY\0") => Ok(PrimarySource::Jjy),
409            v if v == code_to_u32!(b"LORC") => Ok(PrimarySource::Lorc),
410            v if v == code_to_u32!(b"TDF\0") => Ok(PrimarySource::Tdf),
411            v if v == code_to_u32!(b"CHU\0") => Ok(PrimarySource::Chu),
412            v if v == code_to_u32!(b"WWV\0") => Ok(PrimarySource::Wwv),
413            v if v == code_to_u32!(b"WWVH") => Ok(PrimarySource::Wwvh),
414            v if v == code_to_u32!(b"NIST") => Ok(PrimarySource::Nist),
415            v if v == code_to_u32!(b"ACTS") => Ok(PrimarySource::Acts),
416            v if v == code_to_u32!(b"USNO") => Ok(PrimarySource::Usno),
417            v if v == code_to_u32!(b"PTB\0") => Ok(PrimarySource::Ptb),
418            v if v == code_to_u32!(b"GOOG") => Ok(PrimarySource::Goog),
419            v if v == code_to_u32!(b"LOCL") => Ok(PrimarySource::Locl),
420            v if v == code_to_u32!(b"CESM") => Ok(PrimarySource::Cesm),
421            v if v == code_to_u32!(b"RBDM") => Ok(PrimarySource::Rbdm),
422            v if v == code_to_u32!(b"OMEG") => Ok(PrimarySource::Omeg),
423            v if v == code_to_u32!(b"DCN\0") => Ok(PrimarySource::Dcn),
424            v if v == code_to_u32!(b"TSP\0") => Ok(PrimarySource::Tsp),
425            v if v == code_to_u32!(b"DTS\0") => Ok(PrimarySource::Dts),
426            v if v == code_to_u32!(b"ATOM") => Ok(PrimarySource::Atom),
427            v if v == code_to_u32!(b"VLF\0") => Ok(PrimarySource::Vlf),
428            v if v == code_to_u32!(b"OPPS") => Ok(PrimarySource::Opps),
429            v if v == code_to_u32!(b"FREE") => Ok(PrimarySource::Free),
430            v if v == code_to_u32!(b"INIT") => Ok(PrimarySource::Init),
431            0 => Ok(PrimarySource::Null),
432            _ => Err(()),
433        }
434    }
435}
436
437/// If the Stratum field is 0, which implies unspecified or invalid, the Reference Identifier
438/// field can be used to convey messages useful for status reporting and access control. These
439/// are called **Kiss-o'-Death** (KoD) packets and the ASCII messages they convey are called
440/// kiss codes.
441///
442/// The KoD packets got their name because an early use was to tell clients to stop sending
443/// packets that violate server access controls. The kiss codes can provide useful information
444/// for an intelligent client, either NTPv4 or SNTPv4. Kiss codes are encoded in four-character
445/// ASCII strings that are left justified and zero filled. The strings are designed for
446/// character displays and log files.
447///
448/// Recipients of kiss codes MUST inspect them and, in the following cases, take the actions
449/// described.
450#[repr(u32)]
451#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
452pub enum KissOfDeath {
453    /// The client MUST demobilize any associations to that server and stop sending packets to it.
454    Deny = code_to_u32!(b"DENY"),
455    /// The client MUST demobilize any associations to that server and stop sending packets to it.
456    Rstr = code_to_u32!(b"RSTR"),
457    /// The client MUST immediately reduce its polling interval to that server and continue to
458    /// reduce it each time it receives a RATE kiss code.
459    Rate = code_to_u32!(b"RATE"),
460}
461
462impl TryFrom<u32> for KissOfDeath {
463    type Error = ();
464
465    fn try_from(value: u32) -> Result<Self, Self::Error> {
466        match value {
467            v if v == code_to_u32!(b"DENY") => Ok(KissOfDeath::Deny),
468            v if v == code_to_u32!(b"RSTR") => Ok(KissOfDeath::Rstr),
469            v if v == code_to_u32!(b"RATE") => Ok(KissOfDeath::Rate),
470            _ => Err(()),
471        }
472    }
473}
474
475/// **Packet Header** - The most important state variables from an external point of view are the
476/// packet header variables described here.
477///
478/// The NTP packet header consists of an integral number of 32-bit (4 octet) words in network byte
479/// order. The packet format consists of three components: the header itself, one or more optional
480/// extension fields, and an optional message authentication code (MAC).
481///
482/// ```ignore
483/// +-----------+------------+-----------------------+
484/// | Name      | Formula    | Description           |
485/// +-----------+------------+-----------------------+
486/// | leap      | leap       | leap indicator (LI)   |
487/// | version   | version    | version number (VN)   |
488/// | mode      | mode       | mode                  |
489/// | stratum   | stratum    | stratum               |
490/// | poll      | poll       | poll exponent         |
491/// | precision | rho        | precision exponent    |
492/// | rootdelay | delta_r    | root delay            |
493/// | rootdisp  | epsilon_r  | root dispersion       |
494/// | refid     | refid      | reference ID          |
495/// | reftime   | reftime    | reference timestamp   |
496/// | org       | T1         | origin timestamp      |
497/// | rec       | T2         | receive timestamp     |
498/// | xmt       | T3         | transmit timestamp    |
499/// | dst       | T4         | destination timestamp |
500/// | keyid     | keyid      | key ID                |
501/// | dgst      | dgst       | message digest        |
502/// +-----------+------------+-----------------------+
503/// ```
504///
505/// ### Format
506///
507/// The NTP packet is a UDP datagram \[RFC0768\]. Some fields use multiple words and others are
508/// packed in smaller fields within a word. The NTP packet header shown below has 12 words followed
509/// by optional extension fields and finally an optional message authentication code (MAC)
510/// consisting of the Key Identifier field and Message Digest field.
511///
512/// ```ignore
513///  0                   1                   2                   3
514///  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
515/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
516/// |LI | VN  |Mode |    Stratum     |     Poll      |  Precision   |
517/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
518/// |                         Root Delay                            |
519/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
520/// |                         Root Dispersion                       |
521/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
522/// |                          Reference ID                         |
523/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
524/// |                                                               |
525/// +                     Reference Timestamp (64)                  +
526/// |                                                               |
527/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
528/// |                                                               |
529/// +                      Origin Timestamp (64)                    +
530/// |                                                               |
531/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
532/// |                                                               |
533/// +                      Receive Timestamp (64)                   +
534/// |                                                               |
535/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
536/// |                                                               |
537/// +                      Transmit Timestamp (64)                  +
538/// |                                                               |
539/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
540/// |                                                               |
541/// .                                                               .
542/// .                    Extension Field 1 (variable)               .
543/// .                                                               .
544/// |                                                               |
545/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
546/// |                                                               |
547/// .                                                               .
548/// .                    Extension Field 2 (variable)               .
549/// .                                                               .
550/// |                                                               |
551/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
552/// |                          Key Identifier                       |
553/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
554/// |                                                               |
555/// |                            dgst (128)                         |
556/// |                                                               |
557/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
558/// ```
559#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
560pub struct Packet {
561    /// Leap indicator warning of impending leap second.
562    pub leap_indicator: LeapIndicator,
563    /// NTP protocol version number (1-4).
564    pub version: Version,
565    /// Association mode (client, server, broadcast, etc.).
566    pub mode: Mode,
567    /// Stratum level of the time source (0-15).
568    pub stratum: Stratum,
569    /// 8-bit signed integer representing the maximum interval between successive messages, in log2
570    /// seconds. Suggested default limits for minimum and maximum poll intervals are 6 and 10,
571    /// respectively.
572    pub poll: i8,
573    /// 8-bit signed integer representing the precision of the system clock, in log2 seconds. For
574    /// instance, a value of -18 corresponds to a precision of about one microsecond. The precision
575    /// can be determined when the service first starts up as the minimum time of several
576    /// iterations to read the system clock.
577    pub precision: i8,
578    /// Total round-trip delay to the reference clock, in NTP short format.
579    pub root_delay: ShortFormat,
580    /// Total dispersion to the reference clock, in NTP short format.
581    pub root_dispersion: ShortFormat,
582    /// Reference identifier (clock source or server address).
583    pub reference_id: ReferenceIdentifier,
584    /// Time when the system clock was last set or corrected.
585    pub reference_timestamp: TimestampFormat,
586    /// Time at the client when the request departed for the server.
587    pub origin_timestamp: TimestampFormat,
588    /// Time at the server when the request arrived from the client.
589    pub receive_timestamp: TimestampFormat,
590    /// Time at the server when the response left for the client.
591    pub transmit_timestamp: TimestampFormat,
592}
593
594/// The consecutive types within the first packed byte in the NTP packet.
595pub type PacketByte1 = (LeapIndicator, Version, Mode);
596
597// Inherent implementations.
598
599impl ReferenceIdentifier {
600    /// Returns the raw 4-byte representation of the reference identifier.
601    pub fn as_bytes(&self) -> [u8; 4] {
602        match *self {
603            ReferenceIdentifier::PrimarySource(src) => src.bytes(),
604            ReferenceIdentifier::SecondaryOrClient(arr) => arr,
605            ReferenceIdentifier::KissOfDeath(kod) => be_u32_to_bytes(kod as u32),
606            ReferenceIdentifier::Unknown(arr) => arr,
607        }
608    }
609
610    /// Returns true if this is a Kiss-o'-Death reference identifier.
611    pub fn is_kiss_of_death(&self) -> bool {
612        matches!(self, ReferenceIdentifier::KissOfDeath(_))
613    }
614}
615
616impl PrimarySource {
617    /// The bytestring representation of the primary source.
618    pub fn bytes(&self) -> [u8; 4] {
619        be_u32_to_bytes(*self as u32)
620    }
621}
622
623impl Version {
624    /// NTP version 1.
625    pub const V1: Self = Version(1);
626    /// NTP version 2.
627    pub const V2: Self = Version(2);
628    /// NTP version 3.
629    pub const V3: Self = Version(3);
630    /// NTP version 4 (current standard).
631    pub const V4: Self = Version(4);
632
633    /// Whether or not the version is a known, valid version.
634    pub fn is_known(&self) -> bool {
635        self.0 >= 1 && self.0 <= 4
636    }
637}
638
639impl Stratum {
640    /// Unspecified or invalid.
641    pub const UNSPECIFIED: Self = Stratum(0);
642    /// The primary server (e.g. equipped with a GPS receiver.
643    pub const PRIMARY: Self = Stratum(1);
644    /// The minimum value specifying a secondary server (via NTP).
645    pub const SECONDARY_MIN: Self = Stratum(2);
646    /// The maximum value specifying a secondary server (via NTP).
647    pub const SECONDARY_MAX: Self = Stratum(15);
648    /// An unsynchronized stratum.
649    pub const UNSYNCHRONIZED: Self = Stratum(16);
650    /// The maximum valid stratum value.
651    pub const MAX: Self = Stratum(16);
652
653    /// Whether or not the stratum represents a secondary server.
654    pub fn is_secondary(&self) -> bool {
655        Self::SECONDARY_MIN <= *self && *self <= Self::SECONDARY_MAX
656    }
657
658    /// Whether or not the stratum is in the reserved range.
659    pub fn is_reserved(&self) -> bool {
660        *self > Self::MAX
661    }
662}
663
664// Size implementations.
665
666impl ConstPackedSizeBytes for ShortFormat {
667    const PACKED_SIZE_BYTES: usize = 4;
668}
669
670impl ConstPackedSizeBytes for TimestampFormat {
671    const PACKED_SIZE_BYTES: usize = 8;
672}
673
674impl ConstPackedSizeBytes for DateFormat {
675    const PACKED_SIZE_BYTES: usize = 16;
676}
677
678impl ConstPackedSizeBytes for Stratum {
679    const PACKED_SIZE_BYTES: usize = 1;
680}
681
682impl ConstPackedSizeBytes for ReferenceIdentifier {
683    const PACKED_SIZE_BYTES: usize = 4;
684}
685
686impl ConstPackedSizeBytes for PacketByte1 {
687    const PACKED_SIZE_BYTES: usize = 1;
688}
689
690impl ConstPackedSizeBytes for Packet {
691    const PACKED_SIZE_BYTES: usize = PacketByte1::PACKED_SIZE_BYTES
692        + Stratum::PACKED_SIZE_BYTES
693        + 2
694        + ShortFormat::PACKED_SIZE_BYTES * 2
695        + ReferenceIdentifier::PACKED_SIZE_BYTES
696        + TimestampFormat::PACKED_SIZE_BYTES * 4;
697}
698
699// Writer implementations (requires std).
700
701#[cfg(feature = "std")]
702impl<W> WriteBytes for W
703where
704    W: WriteBytesExt,
705{
706    fn write_bytes<P: WriteToBytes>(&mut self, protocol: P) -> io::Result<()> {
707        protocol.write_to_bytes(self)
708    }
709}
710
711#[cfg(feature = "std")]
712impl<P> WriteToBytes for &P
713where
714    P: WriteToBytes,
715{
716    fn write_to_bytes<W: WriteBytesExt>(&self, writer: W) -> io::Result<()> {
717        (*self).write_to_bytes(writer)
718    }
719}
720
721#[cfg(feature = "std")]
722impl WriteToBytes for ShortFormat {
723    fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
724        writer.write_u16::<BE>(self.seconds)?;
725        writer.write_u16::<BE>(self.fraction)?;
726        Ok(())
727    }
728}
729
730#[cfg(feature = "std")]
731impl WriteToBytes for TimestampFormat {
732    fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
733        writer.write_u32::<BE>(self.seconds)?;
734        writer.write_u32::<BE>(self.fraction)?;
735        Ok(())
736    }
737}
738
739#[cfg(feature = "std")]
740impl WriteToBytes for DateFormat {
741    fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
742        writer.write_i32::<BE>(self.era_number)?;
743        writer.write_u32::<BE>(self.era_offset)?;
744        writer.write_u64::<BE>(self.fraction)?;
745        Ok(())
746    }
747}
748
749#[cfg(feature = "std")]
750impl WriteToBytes for Stratum {
751    fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
752        writer.write_u8(self.0)?;
753        Ok(())
754    }
755}
756
757#[cfg(feature = "std")]
758impl WriteToBytes for ReferenceIdentifier {
759    fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
760        match *self {
761            ReferenceIdentifier::KissOfDeath(kod) => {
762                writer.write_u32::<BE>(kod as u32)?;
763            }
764            ReferenceIdentifier::PrimarySource(src) => {
765                writer.write_u32::<BE>(src as u32)?;
766            }
767            ReferenceIdentifier::SecondaryOrClient(arr) => {
768                writer.write_u32::<BE>(code_to_u32!(&arr))?;
769            }
770            ReferenceIdentifier::Unknown(arr) => {
771                writer.write_u32::<BE>(code_to_u32!(&arr))?;
772            }
773        }
774        Ok(())
775    }
776}
777
778#[cfg(feature = "std")]
779impl WriteToBytes for (LeapIndicator, Version, Mode) {
780    fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
781        let (li, vn, mode) = *self;
782        let mut li_vn_mode = 0;
783        li_vn_mode |= (li as u8) << 6;
784        li_vn_mode |= vn.0 << 3;
785        li_vn_mode |= mode as u8;
786        writer.write_u8(li_vn_mode)?;
787        Ok(())
788    }
789}
790
791#[cfg(feature = "std")]
792impl WriteToBytes for Packet {
793    fn write_to_bytes<W: WriteBytesExt>(&self, mut writer: W) -> io::Result<()> {
794        let li_vn_mode = (self.leap_indicator, self.version, self.mode);
795        writer.write_bytes(li_vn_mode)?;
796        writer.write_bytes(self.stratum)?;
797        writer.write_i8(self.poll)?;
798        writer.write_i8(self.precision)?;
799        writer.write_bytes(self.root_delay)?;
800        writer.write_bytes(self.root_dispersion)?;
801        writer.write_bytes(self.reference_id)?;
802        writer.write_bytes(self.reference_timestamp)?;
803        writer.write_bytes(self.origin_timestamp)?;
804        writer.write_bytes(self.receive_timestamp)?;
805        writer.write_bytes(self.transmit_timestamp)?;
806        Ok(())
807    }
808}
809
810// Reader implementations (requires std).
811
812#[cfg(feature = "std")]
813impl<R> ReadBytes for R
814where
815    R: ReadBytesExt,
816{
817    fn read_bytes<P: ReadFromBytes>(&mut self) -> io::Result<P> {
818        P::read_from_bytes(self)
819    }
820}
821
822#[cfg(feature = "std")]
823impl ReadFromBytes for ShortFormat {
824    fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
825        let seconds = reader.read_u16::<BE>()?;
826        let fraction = reader.read_u16::<BE>()?;
827        let short_format = ShortFormat { seconds, fraction };
828        Ok(short_format)
829    }
830}
831
832#[cfg(feature = "std")]
833impl ReadFromBytes for TimestampFormat {
834    fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
835        let seconds = reader.read_u32::<BE>()?;
836        let fraction = reader.read_u32::<BE>()?;
837        let timestamp_format = TimestampFormat { seconds, fraction };
838        Ok(timestamp_format)
839    }
840}
841
842#[cfg(feature = "std")]
843impl ReadFromBytes for DateFormat {
844    fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
845        let era_number = reader.read_i32::<BE>()?;
846        let era_offset = reader.read_u32::<BE>()?;
847        let fraction = reader.read_u64::<BE>()?;
848        let date_format = DateFormat {
849            era_number,
850            era_offset,
851            fraction,
852        };
853        Ok(date_format)
854    }
855}
856
857#[cfg(feature = "std")]
858impl ReadFromBytes for Stratum {
859    fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
860        let stratum = Stratum(reader.read_u8()?);
861        Ok(stratum)
862    }
863}
864
865#[cfg(feature = "std")]
866impl ReadFromBytes for (LeapIndicator, Version, Mode) {
867    fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
868        let li_vn_mode = reader.read_u8()?;
869        let li_u8 = li_vn_mode >> 6;
870        let vn_u8 = (li_vn_mode >> 3) & 0b111;
871        let mode_u8 = li_vn_mode & 0b111;
872        let li = match LeapIndicator::try_from(li_u8).ok() {
873            Some(li) => li,
874            None => {
875                let err_msg = "unknown leap indicator";
876                return Err(io::Error::new(io::ErrorKind::InvalidData, err_msg));
877            }
878        };
879        let vn = Version(vn_u8);
880        let mode = match Mode::try_from(mode_u8).ok() {
881            Some(mode) => mode,
882            None => {
883                let err_msg = "unknown association mode";
884                return Err(io::Error::new(io::ErrorKind::InvalidData, err_msg));
885            }
886        };
887        Ok((li, vn, mode))
888    }
889}
890
891#[cfg(feature = "std")]
892impl ReadFromBytes for Packet {
893    fn read_from_bytes<R: ReadBytesExt>(mut reader: R) -> io::Result<Self> {
894        let (leap_indicator, version, mode) = reader.read_bytes()?;
895        let stratum = reader.read_bytes::<Stratum>()?;
896        let poll = reader.read_i8()?;
897        let precision = reader.read_i8()?;
898        let root_delay = reader.read_bytes()?;
899        let root_dispersion = reader.read_bytes()?;
900        let reference_id = {
901            let u = reader.read_u32::<BE>()?;
902            let raw_bytes = be_u32_to_bytes(u);
903            if stratum == Stratum::UNSPECIFIED {
904                // Stratum 0: Kiss-o'-Death packet (RFC 5905 Section 7.4).
905                match KissOfDeath::try_from(u) {
906                    Ok(kod) => ReferenceIdentifier::KissOfDeath(kod),
907                    Err(_) => ReferenceIdentifier::Unknown(raw_bytes),
908                }
909            } else if stratum == Stratum::PRIMARY {
910                // Stratum 1: primary reference source (4-char ASCII).
911                match PrimarySource::try_from(u) {
912                    Ok(src) => ReferenceIdentifier::PrimarySource(src),
913                    Err(_) => ReferenceIdentifier::Unknown(raw_bytes),
914                }
915            } else if stratum.is_secondary() {
916                // Stratum 2-15: IPv4 address or first 4 octets of MD5 hash of IPv6 address.
917                ReferenceIdentifier::SecondaryOrClient(raw_bytes)
918            } else {
919                // Stratum 16 (unsynchronized) or 17-255 (reserved).
920                ReferenceIdentifier::Unknown(raw_bytes)
921            }
922        };
923        let reference_timestamp = reader.read_bytes()?;
924        let origin_timestamp = reader.read_bytes()?;
925        let receive_timestamp = reader.read_bytes()?;
926        let transmit_timestamp = reader.read_bytes()?;
927        Ok(Packet {
928            leap_indicator,
929            version,
930            mode,
931            stratum,
932            poll,
933            precision,
934            root_delay,
935            root_dispersion,
936            reference_id,
937            reference_timestamp,
938            origin_timestamp,
939            receive_timestamp,
940            transmit_timestamp,
941        })
942    }
943}
944
945// Display implementations.
946
947impl fmt::Display for PrimarySource {
948    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
949        let bytes = self.bytes();
950        for &b in &bytes {
951            if b == 0 {
952                break;
953            }
954            if b.is_ascii() {
955                write!(f, "{}", b as char)?;
956            } else {
957                write!(f, "?")?;
958            }
959        }
960        Ok(())
961    }
962}
963
964// Buffer-based reader implementations (io-independent).
965
966impl FromBytes for ShortFormat {
967    fn from_bytes(buf: &[u8]) -> Result<(Self, usize), ParseError> {
968        if buf.len() < Self::PACKED_SIZE_BYTES {
969            return Err(ParseError::BufferTooShort {
970                needed: Self::PACKED_SIZE_BYTES,
971                available: buf.len(),
972            });
973        }
974        let seconds = u16::from_be_bytes([buf[0], buf[1]]);
975        let fraction = u16::from_be_bytes([buf[2], buf[3]]);
976        Ok((ShortFormat { seconds, fraction }, Self::PACKED_SIZE_BYTES))
977    }
978}
979
980impl FromBytes for TimestampFormat {
981    fn from_bytes(buf: &[u8]) -> Result<(Self, usize), ParseError> {
982        if buf.len() < Self::PACKED_SIZE_BYTES {
983            return Err(ParseError::BufferTooShort {
984                needed: Self::PACKED_SIZE_BYTES,
985                available: buf.len(),
986            });
987        }
988        let seconds = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
989        let fraction = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
990        Ok((
991            TimestampFormat { seconds, fraction },
992            Self::PACKED_SIZE_BYTES,
993        ))
994    }
995}
996
997impl FromBytes for DateFormat {
998    fn from_bytes(buf: &[u8]) -> Result<(Self, usize), ParseError> {
999        if buf.len() < Self::PACKED_SIZE_BYTES {
1000            return Err(ParseError::BufferTooShort {
1001                needed: Self::PACKED_SIZE_BYTES,
1002                available: buf.len(),
1003            });
1004        }
1005        let era_number = i32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
1006        let era_offset = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
1007        let fraction = u64::from_be_bytes([
1008            buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
1009        ]);
1010        Ok((
1011            DateFormat {
1012                era_number,
1013                era_offset,
1014                fraction,
1015            },
1016            Self::PACKED_SIZE_BYTES,
1017        ))
1018    }
1019}
1020
1021impl FromBytes for Stratum {
1022    fn from_bytes(buf: &[u8]) -> Result<(Self, usize), ParseError> {
1023        if buf.is_empty() {
1024            return Err(ParseError::BufferTooShort {
1025                needed: 1,
1026                available: 0,
1027            });
1028        }
1029        Ok((Stratum(buf[0]), 1))
1030    }
1031}
1032
1033impl FromBytes for (LeapIndicator, Version, Mode) {
1034    fn from_bytes(buf: &[u8]) -> Result<(Self, usize), ParseError> {
1035        if buf.is_empty() {
1036            return Err(ParseError::BufferTooShort {
1037                needed: 1,
1038                available: 0,
1039            });
1040        }
1041        let li_vn_mode = buf[0];
1042        let li_u8 = li_vn_mode >> 6;
1043        let vn_u8 = (li_vn_mode >> 3) & 0b111;
1044        let mode_u8 = li_vn_mode & 0b111;
1045        let li = LeapIndicator::try_from(li_u8).map_err(|_| ParseError::InvalidField {
1046            field: "leap indicator",
1047            value: li_u8 as u32,
1048        })?;
1049        let vn = Version(vn_u8);
1050        let mode = Mode::try_from(mode_u8).map_err(|_| ParseError::InvalidField {
1051            field: "association mode",
1052            value: mode_u8 as u32,
1053        })?;
1054        Ok(((li, vn, mode), 1))
1055    }
1056}
1057
1058impl ReferenceIdentifier {
1059    /// Parse a reference identifier from 4 bytes, using stratum for disambiguation.
1060    ///
1061    /// The interpretation of the reference identifier depends on the stratum:
1062    /// - Stratum 0: Kiss-o'-Death code
1063    /// - Stratum 1: Primary source identifier
1064    /// - Stratum 2-15: Secondary/client reference (IPv4 or IPv6 hash)
1065    /// - Stratum 16+: Unknown
1066    pub fn from_bytes_with_stratum(bytes: [u8; 4], stratum: Stratum) -> Self {
1067        let u = u32::from_be_bytes(bytes);
1068        if stratum == Stratum::UNSPECIFIED {
1069            match KissOfDeath::try_from(u) {
1070                Ok(kod) => ReferenceIdentifier::KissOfDeath(kod),
1071                Err(_) => ReferenceIdentifier::Unknown(bytes),
1072            }
1073        } else if stratum == Stratum::PRIMARY {
1074            match PrimarySource::try_from(u) {
1075                Ok(src) => ReferenceIdentifier::PrimarySource(src),
1076                Err(_) => ReferenceIdentifier::Unknown(bytes),
1077            }
1078        } else if stratum.is_secondary() {
1079            ReferenceIdentifier::SecondaryOrClient(bytes)
1080        } else {
1081            ReferenceIdentifier::Unknown(bytes)
1082        }
1083    }
1084}
1085
1086impl FromBytes for Packet {
1087    fn from_bytes(buf: &[u8]) -> Result<(Self, usize), ParseError> {
1088        if buf.len() < Self::PACKED_SIZE_BYTES {
1089            return Err(ParseError::BufferTooShort {
1090                needed: Self::PACKED_SIZE_BYTES,
1091                available: buf.len(),
1092            });
1093        }
1094
1095        let mut offset = 0;
1096
1097        let ((leap_indicator, version, mode), n) =
1098            <(LeapIndicator, Version, Mode)>::from_bytes(&buf[offset..])?;
1099        offset += n;
1100
1101        let (stratum, n) = Stratum::from_bytes(&buf[offset..])?;
1102        offset += n;
1103
1104        let poll = buf[offset] as i8;
1105        offset += 1;
1106
1107        let precision = buf[offset] as i8;
1108        offset += 1;
1109
1110        let (root_delay, n) = ShortFormat::from_bytes(&buf[offset..])?;
1111        offset += n;
1112
1113        let (root_dispersion, n) = ShortFormat::from_bytes(&buf[offset..])?;
1114        offset += n;
1115
1116        let ref_id_bytes = [
1117            buf[offset],
1118            buf[offset + 1],
1119            buf[offset + 2],
1120            buf[offset + 3],
1121        ];
1122        let reference_id = ReferenceIdentifier::from_bytes_with_stratum(ref_id_bytes, stratum);
1123        offset += 4;
1124
1125        let (reference_timestamp, n) = TimestampFormat::from_bytes(&buf[offset..])?;
1126        offset += n;
1127
1128        let (origin_timestamp, n) = TimestampFormat::from_bytes(&buf[offset..])?;
1129        offset += n;
1130
1131        let (receive_timestamp, n) = TimestampFormat::from_bytes(&buf[offset..])?;
1132        offset += n;
1133
1134        let (transmit_timestamp, n) = TimestampFormat::from_bytes(&buf[offset..])?;
1135        offset += n;
1136
1137        Ok((
1138            Packet {
1139                leap_indicator,
1140                version,
1141                mode,
1142                stratum,
1143                poll,
1144                precision,
1145                root_delay,
1146                root_dispersion,
1147                reference_id,
1148                reference_timestamp,
1149                origin_timestamp,
1150                receive_timestamp,
1151                transmit_timestamp,
1152            },
1153            offset,
1154        ))
1155    }
1156}
1157
1158// Buffer-based writer implementations (io-independent).
1159
1160impl ToBytes for ShortFormat {
1161    fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ParseError> {
1162        if buf.len() < Self::PACKED_SIZE_BYTES {
1163            return Err(ParseError::BufferTooShort {
1164                needed: Self::PACKED_SIZE_BYTES,
1165                available: buf.len(),
1166            });
1167        }
1168        let s = self.seconds.to_be_bytes();
1169        let f = self.fraction.to_be_bytes();
1170        buf[0] = s[0];
1171        buf[1] = s[1];
1172        buf[2] = f[0];
1173        buf[3] = f[1];
1174        Ok(Self::PACKED_SIZE_BYTES)
1175    }
1176}
1177
1178impl ToBytes for TimestampFormat {
1179    fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ParseError> {
1180        if buf.len() < Self::PACKED_SIZE_BYTES {
1181            return Err(ParseError::BufferTooShort {
1182                needed: Self::PACKED_SIZE_BYTES,
1183                available: buf.len(),
1184            });
1185        }
1186        let s = self.seconds.to_be_bytes();
1187        let f = self.fraction.to_be_bytes();
1188        buf[..4].copy_from_slice(&s);
1189        buf[4..8].copy_from_slice(&f);
1190        Ok(Self::PACKED_SIZE_BYTES)
1191    }
1192}
1193
1194impl ToBytes for DateFormat {
1195    fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ParseError> {
1196        if buf.len() < Self::PACKED_SIZE_BYTES {
1197            return Err(ParseError::BufferTooShort {
1198                needed: Self::PACKED_SIZE_BYTES,
1199                available: buf.len(),
1200            });
1201        }
1202        buf[..4].copy_from_slice(&self.era_number.to_be_bytes());
1203        buf[4..8].copy_from_slice(&self.era_offset.to_be_bytes());
1204        buf[8..16].copy_from_slice(&self.fraction.to_be_bytes());
1205        Ok(Self::PACKED_SIZE_BYTES)
1206    }
1207}
1208
1209impl ToBytes for Stratum {
1210    fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ParseError> {
1211        if buf.is_empty() {
1212            return Err(ParseError::BufferTooShort {
1213                needed: 1,
1214                available: 0,
1215            });
1216        }
1217        buf[0] = self.0;
1218        Ok(1)
1219    }
1220}
1221
1222impl ToBytes for (LeapIndicator, Version, Mode) {
1223    fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ParseError> {
1224        if buf.is_empty() {
1225            return Err(ParseError::BufferTooShort {
1226                needed: 1,
1227                available: 0,
1228            });
1229        }
1230        let (li, vn, mode) = *self;
1231        let mut li_vn_mode = 0u8;
1232        li_vn_mode |= (li as u8) << 6;
1233        li_vn_mode |= vn.0 << 3;
1234        li_vn_mode |= mode as u8;
1235        buf[0] = li_vn_mode;
1236        Ok(1)
1237    }
1238}
1239
1240impl ToBytes for ReferenceIdentifier {
1241    fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ParseError> {
1242        if buf.len() < Self::PACKED_SIZE_BYTES {
1243            return Err(ParseError::BufferTooShort {
1244                needed: Self::PACKED_SIZE_BYTES,
1245                available: buf.len(),
1246            });
1247        }
1248        let bytes = self.as_bytes();
1249        buf[..4].copy_from_slice(&bytes);
1250        Ok(Self::PACKED_SIZE_BYTES)
1251    }
1252}
1253
1254impl ToBytes for Packet {
1255    fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ParseError> {
1256        if buf.len() < Self::PACKED_SIZE_BYTES {
1257            return Err(ParseError::BufferTooShort {
1258                needed: Self::PACKED_SIZE_BYTES,
1259                available: buf.len(),
1260            });
1261        }
1262
1263        let mut offset = 0;
1264
1265        let li_vn_mode = (self.leap_indicator, self.version, self.mode);
1266        offset += li_vn_mode.to_bytes(&mut buf[offset..])?;
1267        offset += self.stratum.to_bytes(&mut buf[offset..])?;
1268        buf[offset] = self.poll as u8;
1269        offset += 1;
1270        buf[offset] = self.precision as u8;
1271        offset += 1;
1272        offset += self.root_delay.to_bytes(&mut buf[offset..])?;
1273        offset += self.root_dispersion.to_bytes(&mut buf[offset..])?;
1274        offset += self.reference_id.to_bytes(&mut buf[offset..])?;
1275        offset += self.reference_timestamp.to_bytes(&mut buf[offset..])?;
1276        offset += self.origin_timestamp.to_bytes(&mut buf[offset..])?;
1277        offset += self.receive_timestamp.to_bytes(&mut buf[offset..])?;
1278        offset += self.transmit_timestamp.to_bytes(&mut buf[offset..])?;
1279
1280        Ok(offset)
1281    }
1282}
1283
1284// Utility functions.
1285
1286fn be_u32_to_bytes(u: u32) -> [u8; 4] {
1287    [
1288        (u >> 24 & 0xff) as u8,
1289        (u >> 16 & 0xff) as u8,
1290        (u >> 8 & 0xff) as u8,
1291        (u & 0xff) as u8,
1292    ]
1293}