ntp_proto/packet/
mod.rs

1use std::{borrow::Cow, io::Cursor};
2
3use rand::{thread_rng, Rng};
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    clock::NtpClock,
8    identifiers::ReferenceId,
9    io::NonBlockingWrite,
10    keyset::{DecodedServerCookie, KeySet},
11    system::SystemSnapshot,
12    time_types::{NtpDuration, NtpTimestamp, PollInterval},
13    NtpVersion,
14};
15
16use self::{error::ParsingError, extension_fields::ExtensionFieldData, mac::Mac};
17
18mod crypto;
19mod error;
20mod extension_fields;
21mod mac;
22
23pub mod v5;
24
25pub use crypto::{
26    AesSivCmac256, AesSivCmac512, Cipher, CipherHolder, CipherProvider, DecryptError,
27    EncryptResult, NoCipher,
28};
29pub use error::PacketParsingError;
30pub use extension_fields::{ExtensionField, ExtensionHeaderVersion};
31
32#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub enum NtpLeapIndicator {
34    NoWarning,
35    Leap61,
36    Leap59,
37    // Unknown and unsynchronized have the same wire representation, and weren't distinguished in NTPv4.
38    // however, NTPv5 provides a distinction which we need in some parts of the code. For now, we just use
39    // this to encode both, but long term we might want a different approach here.
40    Unknown,
41    Unsynchronized,
42}
43
44impl NtpLeapIndicator {
45    // This function should only ever be called with 2 bit values
46    // (in the least significant position)
47    fn from_bits(bits: u8) -> NtpLeapIndicator {
48        match bits {
49            0 => NtpLeapIndicator::NoWarning,
50            1 => NtpLeapIndicator::Leap61,
51            2 => NtpLeapIndicator::Leap59,
52            3 => NtpLeapIndicator::Unsynchronized,
53            // This function should only ever be called from the packet parser
54            // with just two bits, so this really should be unreachable
55            _ => unreachable!(),
56        }
57    }
58
59    fn to_bits(self) -> u8 {
60        match self {
61            NtpLeapIndicator::NoWarning => 0,
62            NtpLeapIndicator::Leap61 => 1,
63            NtpLeapIndicator::Leap59 => 2,
64            NtpLeapIndicator::Unknown => 3,
65            NtpLeapIndicator::Unsynchronized => 3,
66        }
67    }
68
69    pub fn is_synchronized(&self) -> bool {
70        !matches!(self, Self::Unsynchronized)
71    }
72}
73
74#[derive(Debug, Copy, Clone, PartialEq, Eq)]
75pub enum NtpAssociationMode {
76    Reserved,
77    SymmetricActive,
78    SymmetricPassive,
79    Client,
80    Server,
81    Broadcast,
82    Control,
83    Private,
84}
85
86impl NtpAssociationMode {
87    // This function should only ever be called with 3 bit values
88    // (in the least significant position)
89    fn from_bits(bits: u8) -> NtpAssociationMode {
90        match bits {
91            0 => NtpAssociationMode::Reserved,
92            1 => NtpAssociationMode::SymmetricActive,
93            2 => NtpAssociationMode::SymmetricPassive,
94            3 => NtpAssociationMode::Client,
95            4 => NtpAssociationMode::Server,
96            5 => NtpAssociationMode::Broadcast,
97            6 => NtpAssociationMode::Control,
98            7 => NtpAssociationMode::Private,
99            // This function should only ever be called from the packet parser
100            // with just three bits, so this really should be unreachable
101            _ => unreachable!(),
102        }
103    }
104
105    fn to_bits(self) -> u8 {
106        match self {
107            NtpAssociationMode::Reserved => 0,
108            NtpAssociationMode::SymmetricActive => 1,
109            NtpAssociationMode::SymmetricPassive => 2,
110            NtpAssociationMode::Client => 3,
111            NtpAssociationMode::Server => 4,
112            NtpAssociationMode::Broadcast => 5,
113            NtpAssociationMode::Control => 6,
114            NtpAssociationMode::Private => 7,
115        }
116    }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct NtpPacket<'a> {
121    header: NtpHeader,
122    efdata: ExtensionFieldData<'a>,
123    mac: Option<Mac<'a>>,
124}
125
126#[derive(Debug, Copy, Clone, PartialEq, Eq)]
127pub enum NtpHeader {
128    V3(NtpHeaderV3V4),
129    V4(NtpHeaderV3V4),
130    V5(v5::NtpHeaderV5),
131}
132
133#[derive(Debug, Copy, Clone, PartialEq, Eq)]
134pub struct NtpHeaderV3V4 {
135    leap: NtpLeapIndicator,
136    mode: NtpAssociationMode,
137    stratum: u8,
138    poll: PollInterval,
139    precision: i8,
140    root_delay: NtpDuration,
141    root_dispersion: NtpDuration,
142    reference_id: ReferenceId,
143    reference_timestamp: NtpTimestamp,
144    /// Time at the client when the request departed for the server
145    origin_timestamp: NtpTimestamp,
146    /// Time at the server when the request arrived from the client
147    receive_timestamp: NtpTimestamp,
148    /// Time at the server when the response left for the client
149    transmit_timestamp: NtpTimestamp,
150}
151
152#[derive(Debug, Copy, Clone, PartialEq, Eq)]
153pub struct RequestIdentifier {
154    expected_origin_timestamp: NtpTimestamp,
155    uid: Option<[u8; 32]>,
156}
157
158impl NtpHeaderV3V4 {
159    const WIRE_LENGTH: usize = 48;
160
161    /// A new, empty NtpHeader
162    fn new() -> Self {
163        Self {
164            leap: NtpLeapIndicator::NoWarning,
165            mode: NtpAssociationMode::Client,
166            stratum: 0,
167            poll: PollInterval::from_byte(0),
168            precision: 0,
169            root_delay: NtpDuration::default(),
170            root_dispersion: NtpDuration::default(),
171            reference_id: ReferenceId::from_int(0),
172            reference_timestamp: NtpTimestamp::default(),
173            origin_timestamp: NtpTimestamp::default(),
174            receive_timestamp: NtpTimestamp::default(),
175            transmit_timestamp: NtpTimestamp::default(),
176        }
177    }
178
179    fn deserialize(data: &[u8]) -> Result<(Self, usize), ParsingError<std::convert::Infallible>> {
180        if data.len() < Self::WIRE_LENGTH {
181            return Err(ParsingError::IncorrectLength);
182        }
183
184        Ok((
185            Self {
186                leap: NtpLeapIndicator::from_bits((data[0] & 0xC0) >> 6),
187                mode: NtpAssociationMode::from_bits(data[0] & 0x07),
188                stratum: data[1],
189                poll: PollInterval::from_byte(data[2]),
190                precision: data[3] as i8,
191                root_delay: NtpDuration::from_bits_short(data[4..8].try_into().unwrap()),
192                root_dispersion: NtpDuration::from_bits_short(data[8..12].try_into().unwrap()),
193                reference_id: ReferenceId::from_bytes(data[12..16].try_into().unwrap()),
194                reference_timestamp: NtpTimestamp::from_bits(data[16..24].try_into().unwrap()),
195                origin_timestamp: NtpTimestamp::from_bits(data[24..32].try_into().unwrap()),
196                receive_timestamp: NtpTimestamp::from_bits(data[32..40].try_into().unwrap()),
197                transmit_timestamp: NtpTimestamp::from_bits(data[40..48].try_into().unwrap()),
198            },
199            Self::WIRE_LENGTH,
200        ))
201    }
202
203    fn serialize(&self, mut w: impl NonBlockingWrite, version: u8) -> std::io::Result<()> {
204        w.write_all(&[(self.leap.to_bits() << 6) | (version << 3) | self.mode.to_bits()])?;
205        w.write_all(&[self.stratum, self.poll.as_byte(), self.precision as u8])?;
206        w.write_all(&self.root_delay.to_bits_short())?;
207        w.write_all(&self.root_dispersion.to_bits_short())?;
208        w.write_all(&self.reference_id.to_bytes())?;
209        w.write_all(&self.reference_timestamp.to_bits())?;
210        w.write_all(&self.origin_timestamp.to_bits())?;
211        w.write_all(&self.receive_timestamp.to_bits())?;
212        w.write_all(&self.transmit_timestamp.to_bits())?;
213        Ok(())
214    }
215
216    fn poll_message(poll_interval: PollInterval) -> (Self, RequestIdentifier) {
217        let mut packet = Self::new();
218        packet.poll = poll_interval;
219        packet.mode = NtpAssociationMode::Client;
220
221        // In order to increase the entropy of the transmit timestamp
222        // it is just a randomly generated timestamp.
223        // We then expect to get it back identically from the remote
224        // in the origin field.
225        let transmit_timestamp = thread_rng().gen();
226        packet.transmit_timestamp = transmit_timestamp;
227
228        (
229            packet,
230            RequestIdentifier {
231                expected_origin_timestamp: transmit_timestamp,
232                uid: None,
233            },
234        )
235    }
236
237    fn timestamp_response<C: NtpClock>(
238        system: &SystemSnapshot,
239        input: Self,
240        recv_timestamp: NtpTimestamp,
241        clock: &C,
242    ) -> Self {
243        Self {
244            mode: NtpAssociationMode::Server,
245            stratum: system.stratum,
246            origin_timestamp: input.transmit_timestamp,
247            receive_timestamp: recv_timestamp,
248            reference_id: system.reference_id,
249            poll: input.poll,
250            precision: system.time_snapshot.precision.log2(),
251            root_delay: system.time_snapshot.root_delay,
252            root_dispersion: system.time_snapshot.root_dispersion(recv_timestamp),
253            // Timestamp must be last to make it as accurate as possible.
254            transmit_timestamp: clock.now().expect("Failed to read time"),
255            leap: system.time_snapshot.leap_indicator,
256            reference_timestamp: recv_timestamp.truncated_second_bits(7),
257        }
258    }
259
260    fn rate_limit_response(packet_from_client: Self) -> Self {
261        Self {
262            mode: NtpAssociationMode::Server,
263            stratum: 0, // indicates a kiss code
264            reference_id: ReferenceId::KISS_RATE,
265            origin_timestamp: packet_from_client.transmit_timestamp,
266            ..Self::new()
267        }
268    }
269
270    fn deny_response(packet_from_client: Self) -> Self {
271        Self {
272            mode: NtpAssociationMode::Server,
273            stratum: 0, // indicates a kiss code
274            reference_id: ReferenceId::KISS_DENY,
275            origin_timestamp: packet_from_client.transmit_timestamp,
276            ..Self::new()
277        }
278    }
279
280    fn nts_nak_response(packet_from_client: Self) -> Self {
281        Self {
282            mode: NtpAssociationMode::Server,
283            stratum: 0,
284            reference_id: ReferenceId::KISS_NTSN,
285            origin_timestamp: packet_from_client.transmit_timestamp,
286            ..Self::new()
287        }
288    }
289}
290
291impl<'a> NtpPacket<'a> {
292    pub fn into_owned(self) -> NtpPacket<'static> {
293        NtpPacket::<'static> {
294            header: self.header,
295            efdata: self.efdata.into_owned(),
296            mac: self.mac.map(|v| v.into_owned()),
297        }
298    }
299
300    #[allow(clippy::result_large_err)]
301    pub fn deserialize(
302        data: &'a [u8],
303        cipher: &(impl CipherProvider + ?Sized),
304    ) -> Result<(Self, Option<DecodedServerCookie>), PacketParsingError<'a>> {
305        if data.is_empty() {
306            return Err(PacketParsingError::IncorrectLength);
307        }
308
309        let version = (data[0] & 0b0011_1000) >> 3;
310
311        match version {
312            3 => {
313                let (header, header_size) =
314                    NtpHeaderV3V4::deserialize(data).map_err(|e| e.generalize())?;
315                let mac = if header_size != data.len() {
316                    Some(Mac::deserialize(&data[header_size..]).map_err(|e| e.generalize())?)
317                } else {
318                    None
319                };
320                Ok((
321                    NtpPacket {
322                        header: NtpHeader::V3(header),
323                        efdata: ExtensionFieldData::default(),
324                        mac,
325                    },
326                    None,
327                ))
328            }
329            4 => {
330                let (header, header_size) =
331                    NtpHeaderV3V4::deserialize(data).map_err(|e| e.generalize())?;
332
333                let construct_packet = |remaining_bytes: &'a [u8], efdata| {
334                    let mac = if !remaining_bytes.is_empty() {
335                        Some(Mac::deserialize(remaining_bytes)?)
336                    } else {
337                        None
338                    };
339
340                    let packet = NtpPacket {
341                        header: NtpHeader::V4(header),
342                        efdata,
343                        mac,
344                    };
345
346                    Ok::<_, ParsingError<std::convert::Infallible>>(packet)
347                };
348
349                match ExtensionFieldData::deserialize(
350                    data,
351                    header_size,
352                    cipher,
353                    ExtensionHeaderVersion::V4,
354                ) {
355                    Ok(decoded) => {
356                        let packet = construct_packet(decoded.remaining_bytes, decoded.efdata)
357                            .map_err(|e| e.generalize())?;
358
359                        Ok((packet, decoded.cookie))
360                    }
361                    Err(e) => {
362                        // return early if it is anything but a decrypt error
363                        let invalid = e.get_decrypt_error()?;
364
365                        let packet = construct_packet(invalid.remaining_bytes, invalid.efdata)
366                            .map_err(|e| e.generalize())?;
367
368                        Err(ParsingError::DecryptError(packet))
369                    }
370                }
371            }
372            5 => {
373                let (header, header_size) =
374                    v5::NtpHeaderV5::deserialize(data).map_err(|e| e.generalize())?;
375
376                let construct_packet = |remaining_bytes: &'a [u8], efdata| {
377                    let mac = if !remaining_bytes.is_empty() {
378                        Some(Mac::deserialize(remaining_bytes)?)
379                    } else {
380                        None
381                    };
382
383                    let packet = NtpPacket {
384                        header: NtpHeader::V5(header),
385                        efdata,
386                        mac,
387                    };
388
389                    Ok::<_, ParsingError<std::convert::Infallible>>(packet)
390                };
391
392                let res_packet = match ExtensionFieldData::deserialize(
393                    data,
394                    header_size,
395                    cipher,
396                    ExtensionHeaderVersion::V5,
397                ) {
398                    Ok(decoded) => {
399                        let packet = construct_packet(decoded.remaining_bytes, decoded.efdata)
400                            .map_err(|e| e.generalize())?;
401
402                        Ok((packet, decoded.cookie))
403                    }
404                    Err(e) => {
405                        // return early if it is anything but a decrypt error
406                        let invalid = e.get_decrypt_error()?;
407
408                        let packet = construct_packet(invalid.remaining_bytes, invalid.efdata)
409                            .map_err(|e| e.generalize())?;
410
411                        Err(ParsingError::DecryptError(packet))
412                    }
413                };
414
415                let (packet, cookie) = res_packet?;
416
417                match packet.draft_id() {
418                    Some(id) if id == v5::DRAFT_VERSION => Ok((packet, cookie)),
419                    received @ (Some(_) | None) => {
420                        tracing::error!(
421                            expected = v5::DRAFT_VERSION,
422                            received,
423                            "Mismatched draft ID ignoring packet!"
424                        );
425                        Err(ParsingError::V5(v5::V5Error::InvalidDraftIdentification))
426                    }
427                }
428            }
429            _ => Err(PacketParsingError::InvalidVersion(version)),
430        }
431    }
432
433    #[cfg(test)]
434    pub fn serialize_without_encryption_vec(
435        &self,
436        desired_size: Option<usize>,
437    ) -> std::io::Result<Vec<u8>> {
438        let mut buffer = vec![0u8; 1024];
439        let mut cursor = Cursor::new(buffer.as_mut_slice());
440
441        self.serialize(&mut cursor, &NoCipher, desired_size)?;
442
443        let length = cursor.position() as usize;
444        let buffer = cursor.into_inner()[..length].to_vec();
445
446        Ok(buffer)
447    }
448
449    pub fn serialize(
450        &self,
451        w: &mut Cursor<&mut [u8]>,
452        cipher: &(impl CipherProvider + ?Sized),
453        desired_size: Option<usize>,
454    ) -> std::io::Result<()> {
455        let start = w.position();
456
457        match self.header {
458            NtpHeader::V3(header) => header.serialize(&mut *w, 3)?,
459            NtpHeader::V4(header) => header.serialize(&mut *w, 4)?,
460            NtpHeader::V5(header) => header.serialize(&mut *w)?,
461        };
462
463        match self.header {
464            NtpHeader::V3(_) => { /* No extension fields in V3 */ }
465            NtpHeader::V4(_) => {
466                self.efdata
467                    .serialize(&mut *w, cipher, ExtensionHeaderVersion::V4)?
468            }
469            NtpHeader::V5(_) => {
470                self.efdata
471                    .serialize(&mut *w, cipher, ExtensionHeaderVersion::V5)?
472            }
473        }
474
475        if let Some(ref mac) = self.mac {
476            mac.serialize(&mut *w)?;
477        }
478
479        if let Some(desired_size) = desired_size {
480            let written = (w.position() - start) as usize;
481            if desired_size > written {
482                ExtensionField::Padding(desired_size - written).serialize(
483                    w,
484                    4,
485                    ExtensionHeaderVersion::V5,
486                )?;
487            }
488        }
489
490        Ok(())
491    }
492
493    pub fn nts_poll_message(
494        cookie: &'a [u8],
495        new_cookies: u8,
496        poll_interval: PollInterval,
497    ) -> (NtpPacket<'static>, RequestIdentifier) {
498        let (header, id) = NtpHeaderV3V4::poll_message(poll_interval);
499
500        let identifier: [u8; 32] = rand::thread_rng().gen();
501
502        let mut authenticated = vec![
503            ExtensionField::UniqueIdentifier(identifier.to_vec().into()),
504            ExtensionField::NtsCookie(cookie.to_vec().into()),
505        ];
506
507        for _ in 1..new_cookies {
508            authenticated.push(ExtensionField::NtsCookiePlaceholder {
509                cookie_length: cookie.len() as u16,
510            });
511        }
512
513        (
514            NtpPacket {
515                header: NtpHeader::V4(header),
516                efdata: ExtensionFieldData {
517                    authenticated,
518                    encrypted: vec![],
519                    untrusted: vec![],
520                },
521                mac: None,
522            },
523            RequestIdentifier {
524                uid: Some(identifier),
525                ..id
526            },
527        )
528    }
529
530    pub fn nts_poll_message_v5(
531        cookie: &'a [u8],
532        new_cookies: u8,
533        poll_interval: PollInterval,
534    ) -> (NtpPacket<'static>, RequestIdentifier) {
535        let (header, id) = v5::NtpHeaderV5::poll_message(poll_interval);
536
537        let identifier: [u8; 32] = rand::thread_rng().gen();
538
539        let mut authenticated = vec![
540            ExtensionField::UniqueIdentifier(identifier.to_vec().into()),
541            ExtensionField::NtsCookie(cookie.to_vec().into()),
542        ];
543
544        for _ in 1..new_cookies {
545            authenticated.push(ExtensionField::NtsCookiePlaceholder {
546                cookie_length: cookie.len() as u16,
547            });
548        }
549
550        let draft_id = ExtensionField::DraftIdentification(Cow::Borrowed(v5::DRAFT_VERSION));
551        authenticated.push(draft_id);
552
553        (
554            NtpPacket {
555                header: NtpHeader::V5(header),
556                efdata: ExtensionFieldData {
557                    authenticated,
558                    encrypted: vec![],
559                    untrusted: vec![],
560                },
561                mac: None,
562            },
563            RequestIdentifier {
564                uid: Some(identifier),
565                ..id
566            },
567        )
568    }
569
570    pub fn poll_message(poll_interval: PollInterval) -> (Self, RequestIdentifier) {
571        let (header, id) = NtpHeaderV3V4::poll_message(poll_interval);
572        (
573            NtpPacket {
574                header: NtpHeader::V4(header),
575                efdata: Default::default(),
576                mac: None,
577            },
578            id,
579        )
580    }
581
582    pub fn poll_message_upgrade_request(poll_interval: PollInterval) -> (Self, RequestIdentifier) {
583        let (mut header, id) = NtpHeaderV3V4::poll_message(poll_interval);
584
585        header.reference_timestamp = v5::UPGRADE_TIMESTAMP;
586
587        (
588            NtpPacket {
589                header: NtpHeader::V4(header),
590                efdata: ExtensionFieldData {
591                    authenticated: vec![],
592                    encrypted: vec![],
593                    untrusted: vec![],
594                },
595                mac: None,
596            },
597            id,
598        )
599    }
600
601    pub fn poll_message_v5(poll_interval: PollInterval) -> (Self, RequestIdentifier) {
602        let (header, id) = v5::NtpHeaderV5::poll_message(poll_interval);
603
604        let draft_id = ExtensionField::DraftIdentification(Cow::Borrowed(v5::DRAFT_VERSION));
605
606        (
607            NtpPacket {
608                header: NtpHeader::V5(header),
609                efdata: ExtensionFieldData {
610                    authenticated: vec![],
611                    encrypted: vec![],
612                    untrusted: vec![draft_id],
613                },
614                mac: None,
615            },
616            id,
617        )
618    }
619
620    pub fn timestamp_response<C: NtpClock>(
621        system: &SystemSnapshot,
622        input: Self,
623        recv_timestamp: NtpTimestamp,
624        clock: &C,
625    ) -> Self {
626        match &input.header {
627            NtpHeader::V3(header) => NtpPacket {
628                header: NtpHeader::V3(NtpHeaderV3V4::timestamp_response(
629                    system,
630                    *header,
631                    recv_timestamp,
632                    clock,
633                )),
634                efdata: Default::default(),
635                mac: None,
636            },
637            NtpHeader::V4(header) => {
638                let mut response_header =
639                    NtpHeaderV3V4::timestamp_response(system, *header, recv_timestamp, clock);
640
641                // Respond with the upgrade timestamp (NTP5NTP5) iff the input had it and the packet
642                // had the correct draft identification
643                if header.reference_timestamp == v5::UPGRADE_TIMESTAMP {
644                    response_header.reference_timestamp = v5::UPGRADE_TIMESTAMP;
645                };
646
647                NtpPacket {
648                    header: NtpHeader::V4(response_header),
649                    efdata: ExtensionFieldData {
650                        authenticated: vec![],
651                        encrypted: vec![],
652                        // Ignore encrypted so as not to accidentally leak anything
653                        untrusted: input
654                            .efdata
655                            .untrusted
656                            .into_iter()
657                            .chain(input.efdata.authenticated)
658                            .filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
659                            .collect(),
660                    },
661                    mac: None,
662                }
663            }
664            NtpHeader::V5(header) => NtpPacket {
665                // TODO deduplicate extension handling with V4
666                header: NtpHeader::V5(v5::NtpHeaderV5::timestamp_response(
667                    system,
668                    *header,
669                    recv_timestamp,
670                    clock,
671                )),
672                efdata: ExtensionFieldData {
673                    authenticated: vec![],
674                    encrypted: vec![],
675                    // Ignore encrypted so as not to accidentally leak anything
676                    untrusted: input
677                        .efdata
678                        .untrusted
679                        .into_iter()
680                        .chain(input.efdata.authenticated)
681                        .filter_map(|ef| match ef {
682                            uid @ ExtensionField::UniqueIdentifier(_) => Some(uid),
683                            ExtensionField::ReferenceIdRequest(req) => {
684                                let response = req.to_response(&system.bloom_filter)?;
685                                Some(ExtensionField::ReferenceIdResponse(response).into_owned())
686                            }
687                            _ => None,
688                        })
689                        .chain(std::iter::once(ExtensionField::DraftIdentification(
690                            Cow::Borrowed(v5::DRAFT_VERSION),
691                        )))
692                        .collect(),
693                },
694                mac: None,
695            },
696        }
697    }
698
699    fn draft_id(&self) -> Option<&'_ str> {
700        self.efdata
701            .untrusted
702            .iter()
703            .chain(self.efdata.authenticated.iter())
704            .find_map(|ef| match ef {
705                ExtensionField::DraftIdentification(id) => Some(&**id),
706                _ => None,
707            })
708    }
709
710    pub fn nts_timestamp_response<C: NtpClock>(
711        system: &SystemSnapshot,
712        input: Self,
713        recv_timestamp: NtpTimestamp,
714        clock: &C,
715        cookie: &DecodedServerCookie,
716        keyset: &KeySet,
717    ) -> Self {
718        match input.header {
719            NtpHeader::V3(_) => unreachable!("NTS shouldn't work with NTPv3"),
720            NtpHeader::V4(header) => NtpPacket {
721                header: NtpHeader::V4(NtpHeaderV3V4::timestamp_response(
722                    system,
723                    header,
724                    recv_timestamp,
725                    clock,
726                )),
727                efdata: ExtensionFieldData {
728                    encrypted: input
729                        .efdata
730                        .authenticated
731                        .iter()
732                        .chain(input.efdata.encrypted.iter())
733                        .filter_map(|f| match f {
734                            ExtensionField::NtsCookiePlaceholder { cookie_length } => {
735                                let new_cookie = keyset.encode_cookie(cookie);
736                                if new_cookie.len() > *cookie_length as usize {
737                                    None
738                                } else {
739                                    Some(ExtensionField::NtsCookie(Cow::Owned(new_cookie)))
740                                }
741                            }
742                            ExtensionField::NtsCookie(old_cookie) => {
743                                let new_cookie = keyset.encode_cookie(cookie);
744                                if new_cookie.len() > old_cookie.len() {
745                                    None
746                                } else {
747                                    Some(ExtensionField::NtsCookie(Cow::Owned(new_cookie)))
748                                }
749                            }
750                            _ => None,
751                        })
752                        .collect(),
753                    authenticated: input
754                        .efdata
755                        .authenticated
756                        .into_iter()
757                        .filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
758                        .collect(),
759                    // Ignore encrypted so as not to accidentally leak anything
760                    untrusted: vec![],
761                },
762                mac: None,
763            },
764            NtpHeader::V5(header) => NtpPacket {
765                header: NtpHeader::V5(v5::NtpHeaderV5::timestamp_response(
766                    system,
767                    header,
768                    recv_timestamp,
769                    clock,
770                )),
771                efdata: ExtensionFieldData {
772                    encrypted: input
773                        .efdata
774                        .authenticated
775                        .iter()
776                        .chain(input.efdata.encrypted.iter())
777                        .filter_map(|f| match f {
778                            ExtensionField::NtsCookiePlaceholder { cookie_length } => {
779                                let new_cookie = keyset.encode_cookie(cookie);
780                                if new_cookie.len() > *cookie_length as usize {
781                                    None
782                                } else {
783                                    Some(ExtensionField::NtsCookie(Cow::Owned(new_cookie)))
784                                }
785                            }
786                            ExtensionField::NtsCookie(old_cookie) => {
787                                let new_cookie = keyset.encode_cookie(cookie);
788                                if new_cookie.len() > old_cookie.len() {
789                                    None
790                                } else {
791                                    Some(ExtensionField::NtsCookie(Cow::Owned(new_cookie)))
792                                }
793                            }
794                            _ => None,
795                        })
796                        .collect(),
797                    authenticated: input
798                        .efdata
799                        .authenticated
800                        .into_iter()
801                        .filter_map(|ef| match ef {
802                            uid @ ExtensionField::UniqueIdentifier(_) => Some(uid),
803                            ExtensionField::ReferenceIdRequest(req) => {
804                                let response = req.to_response(&system.bloom_filter)?;
805                                Some(ExtensionField::ReferenceIdResponse(response).into_owned())
806                            }
807                            _ => None,
808                        })
809                        .chain(std::iter::once(ExtensionField::DraftIdentification(
810                            Cow::Borrowed(v5::DRAFT_VERSION),
811                        )))
812                        .collect(),
813                    untrusted: vec![],
814                },
815                mac: None,
816            },
817        }
818    }
819
820    pub fn rate_limit_response(packet_from_client: Self) -> Self {
821        match packet_from_client.header {
822            NtpHeader::V3(header) => NtpPacket {
823                header: NtpHeader::V3(NtpHeaderV3V4::rate_limit_response(header)),
824                efdata: Default::default(),
825                mac: None,
826            },
827            NtpHeader::V4(header) => NtpPacket {
828                header: NtpHeader::V4(NtpHeaderV3V4::rate_limit_response(header)),
829                efdata: ExtensionFieldData {
830                    authenticated: vec![],
831                    encrypted: vec![],
832                    // Ignore encrypted so as not to accidentally leak anything
833                    untrusted: packet_from_client
834                        .efdata
835                        .untrusted
836                        .into_iter()
837                        .chain(packet_from_client.efdata.authenticated)
838                        .filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
839                        .collect(),
840                },
841                mac: None,
842            },
843            NtpHeader::V5(header) => NtpPacket {
844                header: NtpHeader::V5(v5::NtpHeaderV5::rate_limit_response(header)),
845                efdata: ExtensionFieldData {
846                    authenticated: vec![],
847                    encrypted: vec![],
848                    // Ignore encrypted so as not to accidentally leak anything
849                    untrusted: packet_from_client
850                        .efdata
851                        .untrusted
852                        .into_iter()
853                        .chain(packet_from_client.efdata.authenticated)
854                        .filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
855                        .chain(std::iter::once(ExtensionField::DraftIdentification(
856                            Cow::Borrowed(v5::DRAFT_VERSION),
857                        )))
858                        .collect(),
859                },
860                mac: None,
861            },
862        }
863    }
864
865    pub fn nts_rate_limit_response(packet_from_client: Self) -> Self {
866        match packet_from_client.header {
867            NtpHeader::V3(_) => unreachable!("NTS shouldn't work with NTPv3"),
868            NtpHeader::V4(header) => NtpPacket {
869                header: NtpHeader::V4(NtpHeaderV3V4::rate_limit_response(header)),
870                efdata: ExtensionFieldData {
871                    authenticated: packet_from_client
872                        .efdata
873                        .authenticated
874                        .into_iter()
875                        .filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
876                        .collect(),
877                    encrypted: vec![],
878                    untrusted: vec![],
879                },
880                mac: None,
881            },
882            NtpHeader::V5(header) => NtpPacket {
883                header: NtpHeader::V5(v5::NtpHeaderV5::rate_limit_response(header)),
884                efdata: ExtensionFieldData {
885                    authenticated: packet_from_client
886                        .efdata
887                        .authenticated
888                        .into_iter()
889                        .filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
890                        .chain(std::iter::once(ExtensionField::DraftIdentification(
891                            Cow::Borrowed(v5::DRAFT_VERSION),
892                        )))
893                        .collect(),
894                    encrypted: vec![],
895                    untrusted: vec![],
896                },
897                mac: None,
898            },
899        }
900    }
901
902    pub fn deny_response(packet_from_client: Self) -> Self {
903        match packet_from_client.header {
904            NtpHeader::V3(header) => NtpPacket {
905                header: NtpHeader::V3(NtpHeaderV3V4::deny_response(header)),
906                efdata: Default::default(),
907                mac: None,
908            },
909            NtpHeader::V4(header) => NtpPacket {
910                header: NtpHeader::V4(NtpHeaderV3V4::deny_response(header)),
911                efdata: ExtensionFieldData {
912                    authenticated: vec![],
913                    encrypted: vec![],
914                    // Ignore encrypted so as not to accidentally leak anything
915                    untrusted: packet_from_client
916                        .efdata
917                        .untrusted
918                        .into_iter()
919                        .chain(packet_from_client.efdata.authenticated)
920                        .filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
921                        .collect(),
922                },
923                mac: None,
924            },
925            NtpHeader::V5(header) => NtpPacket {
926                header: NtpHeader::V5(v5::NtpHeaderV5::deny_response(header)),
927                efdata: ExtensionFieldData {
928                    authenticated: vec![],
929                    encrypted: vec![],
930                    // Ignore encrypted so as not to accidentally leak anything
931                    untrusted: packet_from_client
932                        .efdata
933                        .untrusted
934                        .into_iter()
935                        .chain(packet_from_client.efdata.authenticated)
936                        .filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
937                        .chain(std::iter::once(ExtensionField::DraftIdentification(
938                            Cow::Borrowed(v5::DRAFT_VERSION),
939                        )))
940                        .collect(),
941                },
942                mac: None,
943            },
944        }
945    }
946
947    pub fn nts_deny_response(packet_from_client: Self) -> Self {
948        match packet_from_client.header {
949            NtpHeader::V3(_) => unreachable!("NTS shouldn't work with NTPv3"),
950            NtpHeader::V4(header) => NtpPacket {
951                header: NtpHeader::V4(NtpHeaderV3V4::deny_response(header)),
952                efdata: ExtensionFieldData {
953                    authenticated: packet_from_client
954                        .efdata
955                        .authenticated
956                        .into_iter()
957                        .filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
958                        .collect(),
959                    encrypted: vec![],
960                    untrusted: vec![],
961                },
962                mac: None,
963            },
964            NtpHeader::V5(header) => NtpPacket {
965                header: NtpHeader::V5(v5::NtpHeaderV5::deny_response(header)),
966                efdata: ExtensionFieldData {
967                    authenticated: packet_from_client
968                        .efdata
969                        .authenticated
970                        .into_iter()
971                        .filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
972                        .chain(std::iter::once(ExtensionField::DraftIdentification(
973                            Cow::Borrowed(v5::DRAFT_VERSION),
974                        )))
975                        .collect(),
976                    encrypted: vec![],
977                    untrusted: vec![],
978                },
979                mac: None,
980            },
981        }
982    }
983
984    pub fn nts_nak_response(packet_from_client: Self) -> Self {
985        match packet_from_client.header {
986            NtpHeader::V3(_) => unreachable!("NTS shouldn't work with NTPv3"),
987            NtpHeader::V4(header) => NtpPacket {
988                header: NtpHeader::V4(NtpHeaderV3V4::nts_nak_response(header)),
989                efdata: ExtensionFieldData {
990                    authenticated: vec![],
991                    encrypted: vec![],
992                    untrusted: packet_from_client
993                        .efdata
994                        .untrusted
995                        .into_iter()
996                        .chain(packet_from_client.efdata.authenticated)
997                        .filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
998                        .collect(),
999                },
1000                mac: None,
1001            },
1002            NtpHeader::V5(header) => NtpPacket {
1003                header: NtpHeader::V5(v5::NtpHeaderV5::nts_nak_response(header)),
1004                efdata: ExtensionFieldData {
1005                    authenticated: vec![],
1006                    encrypted: vec![],
1007                    untrusted: packet_from_client
1008                        .efdata
1009                        .untrusted
1010                        .into_iter()
1011                        .chain(packet_from_client.efdata.authenticated)
1012                        .filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
1013                        .chain(std::iter::once(ExtensionField::DraftIdentification(
1014                            Cow::Borrowed(v5::DRAFT_VERSION),
1015                        )))
1016                        .collect(),
1017                },
1018                mac: None,
1019            },
1020        }
1021    }
1022}
1023
1024impl<'a> NtpPacket<'a> {
1025    pub fn new_cookies<'b: 'a>(&'b self) -> impl Iterator<Item = Vec<u8>> + 'b {
1026        self.efdata.encrypted.iter().filter_map(|ef| match ef {
1027            ExtensionField::NtsCookie(cookie) => Some(cookie.to_vec()),
1028            _ => None,
1029        })
1030    }
1031
1032    pub fn version(&self) -> NtpVersion {
1033        match self.header {
1034            NtpHeader::V3(_) => NtpVersion::V3,
1035            NtpHeader::V4(_) => NtpVersion::V4,
1036            NtpHeader::V5(_) => NtpVersion::V5,
1037        }
1038    }
1039
1040    pub fn header(&self) -> NtpHeader {
1041        self.header
1042    }
1043
1044    pub fn leap(&self) -> NtpLeapIndicator {
1045        match self.header {
1046            NtpHeader::V3(header) => header.leap,
1047            NtpHeader::V4(header) => header.leap,
1048            NtpHeader::V5(header) => header.leap,
1049        }
1050    }
1051
1052    pub fn mode(&self) -> NtpAssociationMode {
1053        match self.header {
1054            NtpHeader::V3(header) => header.mode,
1055            NtpHeader::V4(header) => header.mode,
1056
1057            // FIXME long term the return type should change to capture both mode types
1058            NtpHeader::V5(header) => match header.mode {
1059                v5::NtpMode::Request => NtpAssociationMode::Client,
1060                v5::NtpMode::Response => NtpAssociationMode::Server,
1061            },
1062        }
1063    }
1064
1065    pub fn poll(&self) -> PollInterval {
1066        match self.header {
1067            NtpHeader::V3(h) | NtpHeader::V4(h) => h.poll,
1068            NtpHeader::V5(h) => h.poll,
1069        }
1070    }
1071
1072    pub fn stratum(&self) -> u8 {
1073        match self.header {
1074            NtpHeader::V3(header) => header.stratum,
1075            NtpHeader::V4(header) => header.stratum,
1076            NtpHeader::V5(header) => header.stratum,
1077        }
1078    }
1079
1080    pub fn precision(&self) -> i8 {
1081        match self.header {
1082            NtpHeader::V3(header) => header.precision,
1083            NtpHeader::V4(header) => header.precision,
1084            NtpHeader::V5(header) => header.precision,
1085        }
1086    }
1087
1088    pub fn root_delay(&self) -> NtpDuration {
1089        match self.header {
1090            NtpHeader::V3(header) => header.root_delay,
1091            NtpHeader::V4(header) => header.root_delay,
1092            NtpHeader::V5(header) => header.root_delay,
1093        }
1094    }
1095
1096    pub fn root_dispersion(&self) -> NtpDuration {
1097        match self.header {
1098            NtpHeader::V3(header) => header.root_dispersion,
1099            NtpHeader::V4(header) => header.root_dispersion,
1100            NtpHeader::V5(header) => header.root_dispersion,
1101        }
1102    }
1103
1104    pub fn receive_timestamp(&self) -> NtpTimestamp {
1105        match self.header {
1106            NtpHeader::V3(header) => header.receive_timestamp,
1107            NtpHeader::V4(header) => header.receive_timestamp,
1108            NtpHeader::V5(header) => header.receive_timestamp,
1109        }
1110    }
1111
1112    pub fn transmit_timestamp(&self) -> NtpTimestamp {
1113        match self.header {
1114            NtpHeader::V3(header) => header.transmit_timestamp,
1115            NtpHeader::V4(header) => header.transmit_timestamp,
1116            NtpHeader::V5(header) => header.transmit_timestamp,
1117        }
1118    }
1119
1120    pub fn reference_id(&self) -> ReferenceId {
1121        match self.header {
1122            NtpHeader::V3(header) => header.reference_id,
1123            NtpHeader::V4(header) => header.reference_id,
1124            // TODO NTPv5 does not have reference IDs so this should always be None for now
1125            NtpHeader::V5(_header) => ReferenceId::NONE,
1126        }
1127    }
1128
1129    fn kiss_code(&self) -> ReferenceId {
1130        match self.header {
1131            NtpHeader::V3(header) => header.reference_id,
1132            NtpHeader::V4(header) => header.reference_id,
1133            // Kiss code in ntpv5 is the first four bytes of the server cookie
1134            NtpHeader::V5(header) => {
1135                ReferenceId::from_bytes(header.server_cookie.0[..4].try_into().unwrap())
1136            }
1137        }
1138    }
1139
1140    pub fn is_kiss(&self) -> bool {
1141        match self.header {
1142            NtpHeader::V3(header) => header.stratum == 0,
1143            NtpHeader::V4(header) => header.stratum == 0,
1144            NtpHeader::V5(header) => header.stratum == 0,
1145        }
1146    }
1147
1148    pub fn is_kiss_deny(&self) -> bool {
1149        self.is_kiss()
1150            && match self.header {
1151                NtpHeader::V3(_) | NtpHeader::V4(_) => self.kiss_code().is_deny(),
1152                NtpHeader::V5(header) => header.poll == PollInterval::NEVER,
1153            }
1154    }
1155
1156    pub fn is_kiss_rate(&self, own_interval: PollInterval) -> bool {
1157        self.is_kiss()
1158            && match self.header {
1159                NtpHeader::V3(_) | NtpHeader::V4(_) => self.kiss_code().is_rate(),
1160                NtpHeader::V5(header) => {
1161                    header.poll > own_interval && header.poll != PollInterval::NEVER
1162                }
1163            }
1164    }
1165
1166    pub fn is_kiss_rstr(&self) -> bool {
1167        self.is_kiss()
1168            && match self.header {
1169                NtpHeader::V3(_) | NtpHeader::V4(_) => self.kiss_code().is_rstr(),
1170                NtpHeader::V5(_) => false,
1171            }
1172    }
1173
1174    pub fn is_kiss_ntsn(&self) -> bool {
1175        self.is_kiss()
1176            && match self.header {
1177                NtpHeader::V3(_) | NtpHeader::V4(_) => self.kiss_code().is_ntsn(),
1178                NtpHeader::V5(header) => header.flags.authnak,
1179            }
1180    }
1181
1182    pub fn is_upgrade(&self) -> bool {
1183        matches!(
1184            self.header,
1185            NtpHeader::V4(NtpHeaderV3V4 {
1186                reference_timestamp: v5::UPGRADE_TIMESTAMP,
1187                ..
1188            }),
1189        )
1190    }
1191
1192    pub fn valid_server_response(&self, identifier: RequestIdentifier, nts_enabled: bool) -> bool {
1193        if let Some(uid) = identifier.uid {
1194            let auth = check_uid_extensionfield(self.efdata.authenticated.iter(), &uid);
1195            let encr = check_uid_extensionfield(self.efdata.encrypted.iter(), &uid);
1196            let untrusted = check_uid_extensionfield(self.efdata.untrusted.iter(), &uid);
1197
1198            // we need at least one uid ef that matches, and none should contradict
1199            // our uid. Untrusted uids should only be considered on nts naks or
1200            // non-nts requests.
1201            let uid_ok = auth != Some(false)
1202                && encr != Some(false)
1203                && (untrusted != Some(false) || (nts_enabled && !self.is_kiss_ntsn()))
1204                && (auth.is_some()
1205                    || encr.is_some()
1206                    || ((!nts_enabled || self.is_kiss_ntsn()) && untrusted.is_some()));
1207            if !uid_ok {
1208                return false;
1209            }
1210        }
1211        match self.header {
1212            NtpHeader::V3(header) => {
1213                header.origin_timestamp == identifier.expected_origin_timestamp
1214            }
1215            NtpHeader::V4(header) => {
1216                header.origin_timestamp == identifier.expected_origin_timestamp
1217            }
1218            NtpHeader::V5(header) => {
1219                header.client_cookie
1220                    == v5::NtpClientCookie::from_ntp_timestamp(identifier.expected_origin_timestamp)
1221            }
1222        }
1223    }
1224
1225    pub fn untrusted_extension_fields(&self) -> impl Iterator<Item = &ExtensionField> {
1226        self.efdata.untrusted.iter()
1227    }
1228
1229    pub fn authenticated_extension_fields(&self) -> impl Iterator<Item = &ExtensionField> {
1230        self.efdata.authenticated.iter()
1231    }
1232
1233    pub fn push_additional(&mut self, ef: ExtensionField<'static>) {
1234        if !self.efdata.authenticated.is_empty() || !self.efdata.encrypted.is_empty() {
1235            self.efdata.authenticated.push(ef);
1236        } else {
1237            self.efdata.untrusted.push(ef);
1238        }
1239    }
1240}
1241
1242// Returns whether all uid extension fields found match the given uid, or
1243// None if there were none.
1244fn check_uid_extensionfield<'a, I: IntoIterator<Item = &'a ExtensionField<'a>>>(
1245    iter: I,
1246    uid: &[u8],
1247) -> Option<bool> {
1248    let mut found_uid = false;
1249    for ef in iter {
1250        if let ExtensionField::UniqueIdentifier(pid) = ef {
1251            if pid.len() < uid.len() || &pid[0..uid.len()] != uid {
1252                return Some(false);
1253            }
1254            found_uid = true;
1255        }
1256    }
1257    if found_uid {
1258        Some(true)
1259    } else {
1260        None
1261    }
1262}
1263
1264#[cfg(any(test, feature = "__internal-fuzz", feature = "__internal-test"))]
1265impl NtpPacket<'_> {
1266    pub fn test() -> Self {
1267        Self::default()
1268    }
1269
1270    pub fn set_mode(&mut self, mode: NtpAssociationMode) {
1271        match &mut self.header {
1272            NtpHeader::V3(ref mut header) => header.mode = mode,
1273            NtpHeader::V4(ref mut header) => header.mode = mode,
1274            NtpHeader::V5(ref mut header) => {
1275                header.mode = match mode {
1276                    NtpAssociationMode::Client => v5::NtpMode::Request,
1277                    NtpAssociationMode::Server => v5::NtpMode::Response,
1278                    _ => todo!("NTPv5 can only handle client-server"),
1279                }
1280            }
1281        }
1282    }
1283
1284    pub fn set_origin_timestamp(&mut self, timestamp: NtpTimestamp) {
1285        match &mut self.header {
1286            NtpHeader::V3(ref mut header) => header.origin_timestamp = timestamp,
1287            NtpHeader::V4(ref mut header) => header.origin_timestamp = timestamp,
1288            NtpHeader::V5(ref mut header) => {
1289                header.client_cookie = v5::NtpClientCookie::from_ntp_timestamp(timestamp)
1290            }
1291        }
1292    }
1293
1294    pub fn set_transmit_timestamp(&mut self, timestamp: NtpTimestamp) {
1295        match &mut self.header {
1296            NtpHeader::V3(ref mut header) => header.transmit_timestamp = timestamp,
1297            NtpHeader::V4(ref mut header) => header.transmit_timestamp = timestamp,
1298            NtpHeader::V5(ref mut header) => header.transmit_timestamp = timestamp,
1299        }
1300    }
1301
1302    pub fn set_receive_timestamp(&mut self, timestamp: NtpTimestamp) {
1303        match &mut self.header {
1304            NtpHeader::V3(ref mut header) => header.receive_timestamp = timestamp,
1305            NtpHeader::V4(ref mut header) => header.receive_timestamp = timestamp,
1306            NtpHeader::V5(ref mut header) => header.receive_timestamp = timestamp,
1307        }
1308    }
1309
1310    pub fn set_precision(&mut self, precision: i8) {
1311        match &mut self.header {
1312            NtpHeader::V3(ref mut header) => header.precision = precision,
1313            NtpHeader::V4(ref mut header) => header.precision = precision,
1314            NtpHeader::V5(ref mut header) => header.precision = precision,
1315        }
1316    }
1317
1318    pub fn set_leap(&mut self, leap: NtpLeapIndicator) {
1319        match &mut self.header {
1320            NtpHeader::V3(ref mut header) => header.leap = leap,
1321            NtpHeader::V4(ref mut header) => header.leap = leap,
1322            NtpHeader::V5(ref mut header) => header.leap = leap,
1323        }
1324    }
1325
1326    pub fn set_stratum(&mut self, stratum: u8) {
1327        match &mut self.header {
1328            NtpHeader::V3(ref mut header) => header.stratum = stratum,
1329            NtpHeader::V4(ref mut header) => header.stratum = stratum,
1330            NtpHeader::V5(ref mut header) => header.stratum = stratum,
1331        }
1332    }
1333
1334    pub fn set_reference_id(&mut self, reference_id: ReferenceId) {
1335        match &mut self.header {
1336            NtpHeader::V3(ref mut header) => header.reference_id = reference_id,
1337            NtpHeader::V4(ref mut header) => header.reference_id = reference_id,
1338            NtpHeader::V5(_header) => todo!("NTPv5 does not have reference IDs"),
1339        }
1340    }
1341
1342    pub fn set_root_delay(&mut self, root_delay: NtpDuration) {
1343        match &mut self.header {
1344            NtpHeader::V3(ref mut header) => header.root_delay = root_delay,
1345            NtpHeader::V4(ref mut header) => header.root_delay = root_delay,
1346            NtpHeader::V5(ref mut header) => header.root_delay = root_delay,
1347        }
1348    }
1349
1350    pub fn set_root_dispersion(&mut self, root_dispersion: NtpDuration) {
1351        match &mut self.header {
1352            NtpHeader::V3(ref mut header) => header.root_dispersion = root_dispersion,
1353            NtpHeader::V4(ref mut header) => header.root_dispersion = root_dispersion,
1354            NtpHeader::V5(ref mut header) => header.root_dispersion = root_dispersion,
1355        }
1356    }
1357}
1358
1359impl Default for NtpPacket<'_> {
1360    fn default() -> Self {
1361        Self {
1362            header: NtpHeader::V4(NtpHeaderV3V4::new()),
1363            efdata: Default::default(),
1364            mac: None,
1365        }
1366    }
1367}
1368
1369#[cfg(test)]
1370mod tests {
1371    use crate::{
1372        keyset::KeySetProvider, nts_record::AeadAlgorithm, system::TimeSnapshot,
1373        time_types::PollIntervalLimits,
1374    };
1375
1376    use super::*;
1377
1378    #[derive(Debug, Clone)]
1379    struct TestClock {
1380        now: NtpTimestamp,
1381    }
1382
1383    impl NtpClock for TestClock {
1384        type Error = std::io::Error;
1385
1386        fn now(&self) -> Result<NtpTimestamp, Self::Error> {
1387            Ok(self.now)
1388        }
1389
1390        fn set_frequency(&self, _freq: f64) -> Result<NtpTimestamp, Self::Error> {
1391            panic!("Unexpected clock steer");
1392        }
1393
1394        fn get_frequency(&self) -> Result<f64, Self::Error> {
1395            Ok(0.0)
1396        }
1397
1398        fn step_clock(&self, _offset: NtpDuration) -> Result<NtpTimestamp, Self::Error> {
1399            panic!("Unexpected clock steer");
1400        }
1401
1402        fn disable_ntp_algorithm(&self) -> Result<(), Self::Error> {
1403            panic!("Unexpected clock steer");
1404        }
1405
1406        fn error_estimate_update(
1407            &self,
1408            _est_error: NtpDuration,
1409            _max_error: NtpDuration,
1410        ) -> Result<(), Self::Error> {
1411            panic!("Unexpected clock steer");
1412        }
1413
1414        fn status_update(&self, _leap_status: NtpLeapIndicator) -> Result<(), Self::Error> {
1415            panic!("Unexpected clock steer");
1416        }
1417    }
1418
1419    #[test]
1420    fn roundtrip_bitrep_leap() {
1421        for i in 0..4u8 {
1422            let a = NtpLeapIndicator::from_bits(i);
1423            let b = a.to_bits();
1424            let c = NtpLeapIndicator::from_bits(b);
1425            assert_eq!(i, b);
1426            assert_eq!(a, c);
1427        }
1428    }
1429
1430    #[test]
1431    fn roundtrip_bitrep_mode() {
1432        for i in 0..8u8 {
1433            let a = NtpAssociationMode::from_bits(i);
1434            let b = a.to_bits();
1435            let c = NtpAssociationMode::from_bits(b);
1436            assert_eq!(i, b);
1437            assert_eq!(a, c);
1438        }
1439    }
1440
1441    #[test]
1442    fn test_captured_client() {
1443        let packet = b"\x23\x02\x06\xe8\x00\x00\x03\xff\x00\x00\x03\x7d\x5e\xc6\x9f\x0f\xe5\xf6\x62\x98\x7b\x61\xb9\xaf\xe5\xf6\x63\x66\x7b\x64\x99\x5d\xe5\xf6\x63\x66\x81\x40\x55\x90\xe5\xf6\x63\xa8\x76\x1d\xde\x48";
1444        let reference = NtpPacket {
1445            header: NtpHeader::V4(NtpHeaderV3V4 {
1446                leap: NtpLeapIndicator::NoWarning,
1447                mode: NtpAssociationMode::Client,
1448                stratum: 2,
1449                poll: PollInterval::from_byte(6),
1450                precision: -24,
1451                root_delay: NtpDuration::from_fixed_int(1023 << 16),
1452                root_dispersion: NtpDuration::from_fixed_int(893 << 16),
1453                reference_id: ReferenceId::from_int(0x5ec69f0f),
1454                reference_timestamp: NtpTimestamp::from_fixed_int(0xe5f662987b61b9af),
1455                origin_timestamp: NtpTimestamp::from_fixed_int(0xe5f663667b64995d),
1456                receive_timestamp: NtpTimestamp::from_fixed_int(0xe5f6636681405590),
1457                transmit_timestamp: NtpTimestamp::from_fixed_int(0xe5f663a8761dde48),
1458            }),
1459            efdata: Default::default(),
1460            mac: None,
1461        };
1462
1463        assert_eq!(
1464            reference,
1465            NtpPacket::deserialize(packet, &NoCipher).unwrap().0
1466        );
1467        match reference.serialize_without_encryption_vec(None) {
1468            Ok(buf) => assert_eq!(packet[..], buf[..]),
1469            Err(e) => panic!("{e:?}"),
1470        }
1471
1472        let packet = b"\x1B\x02\x06\xe8\x00\x00\x03\xff\x00\x00\x03\x7d\x5e\xc6\x9f\x0f\xe5\xf6\x62\x98\x7b\x61\xb9\xaf\xe5\xf6\x63\x66\x7b\x64\x99\x5d\xe5\xf6\x63\x66\x81\x40\x55\x90\xe5\xf6\x63\xa8\x76\x1d\xde\x48";
1473        let reference = NtpPacket {
1474            header: NtpHeader::V3(NtpHeaderV3V4 {
1475                leap: NtpLeapIndicator::NoWarning,
1476                mode: NtpAssociationMode::Client,
1477                stratum: 2,
1478                poll: PollInterval::from_byte(6),
1479                precision: -24,
1480                root_delay: NtpDuration::from_fixed_int(1023 << 16),
1481                root_dispersion: NtpDuration::from_fixed_int(893 << 16),
1482                reference_id: ReferenceId::from_int(0x5ec69f0f),
1483                reference_timestamp: NtpTimestamp::from_fixed_int(0xe5f662987b61b9af),
1484                origin_timestamp: NtpTimestamp::from_fixed_int(0xe5f663667b64995d),
1485                receive_timestamp: NtpTimestamp::from_fixed_int(0xe5f6636681405590),
1486                transmit_timestamp: NtpTimestamp::from_fixed_int(0xe5f663a8761dde48),
1487            }),
1488            efdata: Default::default(),
1489            mac: None,
1490        };
1491
1492        assert_eq!(
1493            reference,
1494            NtpPacket::deserialize(packet, &NoCipher).unwrap().0
1495        );
1496        match reference.serialize_without_encryption_vec(None) {
1497            Ok(buf) => assert_eq!(packet[..], buf[..]),
1498            Err(e) => panic!("{e:?}"),
1499        }
1500    }
1501
1502    #[test]
1503    fn test_captured_server() {
1504        let packet = b"\x24\x02\x06\xe9\x00\x00\x02\x36\x00\x00\x03\xb7\xc0\x35\x67\x6c\xe5\xf6\x61\xfd\x6f\x16\x5f\x03\xe5\xf6\x63\xa8\x76\x19\xef\x40\xe5\xf6\x63\xa8\x79\x8c\x65\x81\xe5\xf6\x63\xa8\x79\x8e\xae\x2b";
1505        let reference = NtpPacket {
1506            header: NtpHeader::V4(NtpHeaderV3V4 {
1507                leap: NtpLeapIndicator::NoWarning,
1508                mode: NtpAssociationMode::Server,
1509                stratum: 2,
1510                poll: PollInterval::from_byte(6),
1511                precision: -23,
1512                root_delay: NtpDuration::from_fixed_int(566 << 16),
1513                root_dispersion: NtpDuration::from_fixed_int(951 << 16),
1514                reference_id: ReferenceId::from_int(0xc035676c),
1515                reference_timestamp: NtpTimestamp::from_fixed_int(0xe5f661fd6f165f03),
1516                origin_timestamp: NtpTimestamp::from_fixed_int(0xe5f663a87619ef40),
1517                receive_timestamp: NtpTimestamp::from_fixed_int(0xe5f663a8798c6581),
1518                transmit_timestamp: NtpTimestamp::from_fixed_int(0xe5f663a8798eae2b),
1519            }),
1520            efdata: Default::default(),
1521            mac: None,
1522        };
1523
1524        assert_eq!(
1525            reference,
1526            NtpPacket::deserialize(packet, &NoCipher).unwrap().0
1527        );
1528        match reference.serialize_without_encryption_vec(None) {
1529            Ok(buf) => assert_eq!(packet[..], buf[..]),
1530            Err(e) => panic!("{e:?}"),
1531        }
1532    }
1533
1534    #[test]
1535    fn test_version() {
1536        let packet = b"\x04\x02\x06\xe9\x00\x00\x02\x36\x00\x00\x03\xb7\xc0\x35\x67\x6c\xe5\xf6\x61\xfd\x6f\x16\x5f\x03\xe5\xf6\x63\xa8\x76\x19\xef\x40\xe5\xf6\x63\xa8\x79\x8c\x65\x81\xe5\xf6\x63\xa8\x79\x8e\xae\x2b";
1537        assert!(NtpPacket::deserialize(packet, &NoCipher).is_err());
1538        let packet = b"\x0B\x02\x06\xe9\x00\x00\x02\x36\x00\x00\x03\xb7\xc0\x35\x67\x6c\xe5\xf6\x61\xfd\x6f\x16\x5f\x03\xe5\xf6\x63\xa8\x76\x19\xef\x40\xe5\xf6\x63\xa8\x79\x8c\x65\x81\xe5\xf6\x63\xa8\x79\x8e\xae\x2b";
1539        assert!(NtpPacket::deserialize(packet, &NoCipher).is_err());
1540        let packet = b"\x14\x02\x06\xe9\x00\x00\x02\x36\x00\x00\x03\xb7\xc0\x35\x67\x6c\xe5\xf6\x61\xfd\x6f\x16\x5f\x03\xe5\xf6\x63\xa8\x76\x19\xef\x40\xe5\xf6\x63\xa8\x79\x8c\x65\x81\xe5\xf6\x63\xa8\x79\x8e\xae\x2b";
1541        assert!(NtpPacket::deserialize(packet, &NoCipher).is_err());
1542        let packet = b"\x34\x02\x06\xe9\x00\x00\x02\x36\x00\x00\x03\xb7\xc0\x35\x67\x6c\xe5\xf6\x61\xfd\x6f\x16\x5f\x03\xe5\xf6\x63\xa8\x76\x19\xef\x40\xe5\xf6\x63\xa8\x79\x8c\x65\x81\xe5\xf6\x63\xa8\x79\x8e\xae\x2b";
1543        assert!(NtpPacket::deserialize(packet, &NoCipher).is_err());
1544        let packet = b"\x3B\x02\x06\xe9\x00\x00\x02\x36\x00\x00\x03\xb7\xc0\x35\x67\x6c\xe5\xf6\x61\xfd\x6f\x16\x5f\x03\xe5\xf6\x63\xa8\x76\x19\xef\x40\xe5\xf6\x63\xa8\x79\x8c\x65\x81\xe5\xf6\x63\xa8\x79\x8e\xae\x2b";
1545        assert!(NtpPacket::deserialize(packet, &NoCipher).is_err());
1546    }
1547
1548    #[test]
1549    fn test_packed_flags() {
1550        let base = b"\x24\x02\x06\xe9\x00\x00\x02\x36\x00\x00\x03\xb7\xc0\x35\x67\x6c\xe5\xf6\x61\xfd\x6f\x16\x5f\x03\xe5\xf6\x63\xa8\x76\x19\xef\x40\xe5\xf6\x63\xa8\x79\x8c\x65\x81\xe5\xf6\x63\xa8\x79\x8e\xae\x2b".to_owned();
1551        let base_structured = NtpPacket::deserialize(&base, &NoCipher).unwrap().0;
1552
1553        for leap_type in 0..3 {
1554            for mode in 0..8 {
1555                let mut header = base_structured.clone();
1556                header.set_leap(NtpLeapIndicator::from_bits(leap_type));
1557                header.set_mode(NtpAssociationMode::from_bits(mode));
1558
1559                let data = header.serialize_without_encryption_vec(None).unwrap();
1560                let copy = NtpPacket::deserialize(&data, &NoCipher).unwrap().0;
1561                assert_eq!(header, copy);
1562            }
1563        }
1564
1565        for i in 0..=0xFF {
1566            let mut packet = base;
1567            packet[0] = i;
1568
1569            if let Ok((a, _)) = NtpPacket::deserialize(&packet, &NoCipher) {
1570                let b = a.serialize_without_encryption_vec(None).unwrap();
1571                assert_eq!(packet[..], b[..]);
1572            }
1573        }
1574    }
1575
1576    #[test]
1577    fn test_nts_roundtrip() {
1578        let cookie = [0; 16];
1579        let (packet1, _) =
1580            NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
1581        let cipher = AesSivCmac512::new(std::array::from_fn::<_, 64, _>(|i| i as u8).into());
1582
1583        let mut buffer = [0u8; 2048];
1584        let mut cursor = Cursor::new(buffer.as_mut());
1585        packet1.serialize(&mut cursor, &cipher, None).unwrap();
1586        let (packet2, _) =
1587            NtpPacket::deserialize(&cursor.get_ref()[..cursor.position() as usize], &cipher)
1588                .unwrap();
1589        assert_eq!(packet1, packet2);
1590    }
1591
1592    #[test]
1593    fn test_nts_captured_server() {
1594        let packet = b"\x24\x01\x04\xe8\x00\x00\x00\x00\x00\x00\x00\x60\x54\x4d\x4e\x4c\xe8\x49\x48\x92\xf9\x29\x57\x9e\x62\x87\xdb\x47\x3f\xf7\x5f\x58\xe8\x49\x48\xb2\xb6\x40\xd7\x01\xe8\x49\x48\xb2\xb6\x44\xbf\xf8\x01\x04\x00\x24\xe4\x83\x3a\x8d\x60\x0e\x13\x42\x43\x5c\xb2\x9d\xe5\x50\xac\xc0\xf8\xd8\xfa\x16\xe5\xc5\x37\x0a\x62\x0b\x15\x5f\x58\x6a\xda\xd6\x04\x04\x00\xd4\x00\x10\x00\xbc\x6a\x1d\xe3\xc2\x6e\x13\xeb\x10\xc7\x39\xd7\x0b\x84\x1f\xad\x1b\x86\xe2\x30\xc6\x3e\x9e\xa5\xf7\x1b\x62\xa8\xa7\x98\x81\xce\x7c\x6b\x17\xcb\x31\x32\x49\x0f\xde\xcf\x21\x10\x56\x4e\x36\x88\x92\xdd\xee\xf1\xf4\x23\xf6\x55\x53\x41\xc2\xc9\x17\x61\x20\xa5\x18\xdc\x1a\x7e\xdc\x5e\xe3\xc8\x3b\x05\x08\x7b\x73\x03\xf7\xab\x86\xd5\x2c\xc7\x49\x0c\xe8\x29\x39\x72\x23\xdc\xef\x2d\x94\xfa\xf8\xd7\x1d\x12\x80\xda\x03\x2d\xd7\x04\x69\xe9\xac\x5f\x82\xef\x57\x81\xd2\x07\xfb\xac\xb4\xa8\xb6\x31\x91\x14\xd5\xf5\x6f\xb2\x2a\x0c\xb6\xd7\xdc\xf7\x7d\xf0\x21\x46\xf6\x7e\x46\x01\xb5\x3b\x21\x7c\xa8\xac\x1a\x4d\x97\xd5\x9b\xce\xeb\x98\x33\x99\x7f\x10\x0e\xd4\x69\x85\x8b\xcd\x73\x52\x01\xad\xec\x38\xcf\x8c\xb2\xc6\xd0\x54\x1a\x97\x67\xdd\xb3\xea\x09\x1d\x63\xd9\x8d\x03\xdd\x6e\x48\x15\x3d\xc9\xb6\x1f\xe5\xd9\x1d\x74\xae\x35\x48";
1595        let cipher = AesSivCmac512::new(
1596            [
1597                244, 6, 63, 13, 47, 226, 180, 25, 104, 212, 47, 14, 186, 70, 187, 93, 134, 140, 2,
1598                82, 238, 254, 113, 79, 90, 31, 135, 138, 123, 210, 121, 47, 228, 208, 243, 76, 126,
1599                213, 196, 233, 65, 15, 33, 163, 196, 30, 6, 197, 222, 105, 40, 14, 73, 138, 200,
1600                45, 235, 127, 48, 248, 171, 8, 141, 180,
1601            ]
1602            .into(),
1603        );
1604
1605        assert!(NtpPacket::deserialize(packet, &cipher).is_ok());
1606    }
1607
1608    #[test]
1609    fn test_nts_captured_client() {
1610        let packet = b"\x23\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x62\x87\xdb\x47\x3f\xf7\x5f\x58\x01\x04\x00\x24\xe4\x83\x3a\x8d\x60\x0e\x13\x42\x43\x5c\xb2\x9d\xe5\x50\xac\xc0\xf8\xd8\xfa\x16\xe5\xc5\x37\x0a\x62\x0b\x15\x5f\x58\x6a\xda\xd6\x02\x04\x00\xac\x1c\xc4\x0a\x94\xda\x3f\x94\xa4\xd1\x2a\xc2\xd6\x09\xf1\x6f\x72\x11\x59\x6a\x0a\xce\xfc\x62\xd1\x1f\x28\x3a\xd1\x08\xd8\x01\xb5\x91\x38\x5d\x9b\xf5\x07\xf9\x0d\x21\x82\xe6\x81\x2a\x58\xa7\x35\xdc\x49\xc4\xd3\xe9\xb7\x9c\x72\xb7\xf6\x44\x64\xf8\xfc\x0d\xed\x25\xea\x1f\x7c\x9b\x31\x5c\xd8\x60\x86\xfd\x67\x74\x90\xf5\x0e\x61\xe6\x68\x0e\x29\x0d\x49\x77\x0c\xed\x44\xd4\x2f\x2d\x9b\xa8\x9f\x4d\x5d\xce\x4f\xdd\x57\x49\x51\x49\x5a\x1f\x38\xdb\xc7\xec\x1b\x86\x5b\xa5\x8f\x23\x1e\xdd\x76\xee\x1d\xaf\xdd\x66\xb2\xb2\x64\x1f\x03\xc6\x47\x9b\x42\x9c\x7f\xf6\x59\x6b\x82\x44\xcf\x67\xb5\xa2\xcd\x20\x9d\x39\xbb\xe6\x40\x2b\xf6\x20\x45\xdf\x95\x50\xf0\x38\x77\x06\x89\x79\x12\x18\x04\x04\x00\x28\x00\x10\x00\x10\xce\x89\xee\x97\x34\x42\xbc\x0f\x43\xaa\xce\x49\x99\xbd\xf5\x8e\x8f\xee\x7b\x1a\x2d\x58\xaf\x6d\xe9\xa2\x0e\x56\x1f\x7f\xf0\x6a";
1611        let cipher = AesSivCmac512::new(
1612            [
1613                170, 111, 161, 118, 7, 200, 232, 128, 145, 250, 170, 186, 87, 143, 171, 252, 110,
1614                241, 170, 179, 13, 150, 134, 147, 211, 248, 62, 207, 122, 155, 198, 109, 167, 15,
1615                18, 118, 146, 63, 186, 146, 212, 188, 175, 27, 89, 3, 237, 212, 52, 113, 28, 21,
1616                203, 200, 230, 17, 8, 186, 126, 1, 52, 230, 86, 40,
1617            ]
1618            .into(),
1619        );
1620
1621        assert!(NtpPacket::deserialize(packet, &cipher).is_ok());
1622    }
1623
1624    #[test]
1625    fn test_nts_poll_message() {
1626        let cookie = [0; 16];
1627        let (packet1, ref1) =
1628            NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
1629        assert_eq!(0, packet1.efdata.encrypted.len());
1630        assert_eq!(0, packet1.efdata.untrusted.len());
1631        let mut have_uid = false;
1632        let mut have_cookie = false;
1633        let mut nplaceholders = 0;
1634        for ef in packet1.efdata.authenticated {
1635            match ef {
1636                ExtensionField::UniqueIdentifier(uid) => {
1637                    assert_eq!(ref1.uid.as_ref().unwrap(), uid.as_ref());
1638                    assert!(!have_uid);
1639                    have_uid = true;
1640                }
1641                ExtensionField::NtsCookie(cookie_p) => {
1642                    assert_eq!(&cookie, cookie_p.as_ref());
1643                    assert!(!have_cookie);
1644                    have_cookie = true;
1645                }
1646                ExtensionField::NtsCookiePlaceholder { cookie_length } => {
1647                    assert_eq!(cookie_length, cookie.len() as u16);
1648                    nplaceholders += 1;
1649                }
1650                _ => unreachable!(),
1651            }
1652        }
1653        assert!(have_cookie);
1654        assert!(have_uid);
1655        assert_eq!(nplaceholders, 0);
1656
1657        let (packet2, ref2) =
1658            NtpPacket::nts_poll_message(&cookie, 3, PollIntervalLimits::default().min);
1659        assert_ne!(
1660            ref1.expected_origin_timestamp,
1661            ref2.expected_origin_timestamp
1662        );
1663        assert_ne!(ref1.uid, ref2.uid);
1664
1665        assert_eq!(0, packet2.efdata.encrypted.len());
1666        assert_eq!(0, packet2.efdata.untrusted.len());
1667        let mut have_uid = false;
1668        let mut have_cookie = false;
1669        let mut nplaceholders = 0;
1670        for ef in packet2.efdata.authenticated {
1671            match ef {
1672                ExtensionField::UniqueIdentifier(uid) => {
1673                    assert_eq!(ref2.uid.as_ref().unwrap(), uid.as_ref());
1674                    assert!(!have_uid);
1675                    have_uid = true;
1676                }
1677                ExtensionField::NtsCookie(cookie_p) => {
1678                    assert_eq!(&cookie, cookie_p.as_ref());
1679                    assert!(!have_cookie);
1680                    have_cookie = true;
1681                }
1682                ExtensionField::NtsCookiePlaceholder { cookie_length } => {
1683                    assert_eq!(cookie_length, cookie.len() as u16);
1684                    nplaceholders += 1;
1685                }
1686                _ => unreachable!(),
1687            }
1688        }
1689        assert!(have_cookie);
1690        assert!(have_uid);
1691        assert_eq!(nplaceholders, 2);
1692    }
1693
1694    #[test]
1695    fn test_nts_response_validation() {
1696        let cookie = [0; 16];
1697        let (packet, id) =
1698            NtpPacket::nts_poll_message(&cookie, 0, PollIntervalLimits::default().min);
1699        let mut response = NtpPacket::timestamp_response(
1700            &SystemSnapshot::default(),
1701            packet,
1702            NtpTimestamp::from_fixed_int(0),
1703            &TestClock {
1704                now: NtpTimestamp::from_fixed_int(2),
1705            },
1706        );
1707
1708        assert!(response.valid_server_response(id, false));
1709        assert!(!response.valid_server_response(id, true));
1710
1711        response
1712            .efdata
1713            .untrusted
1714            .push(ExtensionField::UniqueIdentifier(Cow::Borrowed(
1715                id.uid.as_ref().unwrap(),
1716            )));
1717
1718        assert!(response.valid_server_response(id, false));
1719        assert!(!response.valid_server_response(id, true));
1720
1721        response.efdata.untrusted.clear();
1722        response
1723            .efdata
1724            .authenticated
1725            .push(ExtensionField::UniqueIdentifier(Cow::Borrowed(
1726                id.uid.as_ref().unwrap(),
1727            )));
1728
1729        assert!(response.valid_server_response(id, false));
1730        assert!(response.valid_server_response(id, true));
1731
1732        response
1733            .efdata
1734            .untrusted
1735            .push(ExtensionField::UniqueIdentifier(Cow::Borrowed(&[])));
1736
1737        assert!(!response.valid_server_response(id, false));
1738        assert!(response.valid_server_response(id, true));
1739
1740        response.efdata.untrusted.clear();
1741        response
1742            .efdata
1743            .encrypted
1744            .push(ExtensionField::UniqueIdentifier(Cow::Borrowed(&[])));
1745
1746        assert!(!response.valid_server_response(id, false));
1747        assert!(!response.valid_server_response(id, true));
1748    }
1749
1750    #[test]
1751    fn v5_upgrade_packet() {
1752        let (packet, _) = NtpPacket::poll_message_upgrade_request(PollInterval::default());
1753
1754        let response = NtpPacket::timestamp_response(
1755            &SystemSnapshot::default(),
1756            packet,
1757            NtpTimestamp::from_fixed_int(0),
1758            &TestClock {
1759                now: NtpTimestamp::from_fixed_int(1),
1760            },
1761        );
1762
1763        let NtpHeader::V4(header) = response.header else {
1764            panic!("wrong version");
1765        };
1766
1767        assert_eq!(
1768            header.reference_timestamp,
1769            NtpTimestamp::from_fixed_int(0x4E54503544524654)
1770        );
1771    }
1772
1773    #[test]
1774    fn test_timestamp_response() {
1775        let decoded = DecodedServerCookie {
1776            algorithm: AeadAlgorithm::AeadAesSivCmac256,
1777            s2c: Box::new(AesSivCmac256::new((0..32_u8).collect())),
1778            c2s: Box::new(AesSivCmac256::new((32..64_u8).collect())),
1779        };
1780        let keysetprovider = KeySetProvider::new(1);
1781        let cookie = keysetprovider.get().encode_cookie(&decoded);
1782
1783        let (packet, _) =
1784            NtpPacket::nts_poll_message(&cookie, 0, PollIntervalLimits::default().min);
1785        let packet_id = packet
1786            .efdata
1787            .authenticated
1788            .iter()
1789            .find_map(|f| {
1790                if let ExtensionField::UniqueIdentifier(id) = f {
1791                    Some(id.clone().into_owned())
1792                } else {
1793                    None
1794                }
1795            })
1796            .unwrap();
1797        let response = NtpPacket::timestamp_response(
1798            &SystemSnapshot {
1799                time_snapshot: TimeSnapshot {
1800                    leap_indicator: NtpLeapIndicator::Leap59,
1801                    ..Default::default()
1802                },
1803                ..Default::default()
1804            },
1805            packet,
1806            NtpTimestamp::from_fixed_int(0),
1807            &TestClock {
1808                now: NtpTimestamp::from_fixed_int(1),
1809            },
1810        );
1811        let response_id = response
1812            .efdata
1813            .untrusted
1814            .iter()
1815            .find_map(|f| {
1816                if let ExtensionField::UniqueIdentifier(id) = f {
1817                    Some(id.clone().into_owned())
1818                } else {
1819                    None
1820                }
1821            })
1822            .unwrap();
1823        assert_eq!(packet_id, response_id);
1824        assert_eq!(
1825            response.receive_timestamp(),
1826            NtpTimestamp::from_fixed_int(0)
1827        );
1828        assert_eq!(
1829            response.transmit_timestamp(),
1830            NtpTimestamp::from_fixed_int(1)
1831        );
1832        assert_eq!(response.leap(), NtpLeapIndicator::Leap59);
1833
1834        let (mut packet, _) =
1835            NtpPacket::nts_poll_message(&cookie, 0, PollIntervalLimits::default().min);
1836        std::mem::swap(
1837            &mut packet.efdata.authenticated,
1838            &mut packet.efdata.untrusted,
1839        );
1840        let packet_id = packet
1841            .efdata
1842            .untrusted
1843            .iter()
1844            .find_map(|f| {
1845                if let ExtensionField::UniqueIdentifier(id) = f {
1846                    Some(id.clone().into_owned())
1847                } else {
1848                    None
1849                }
1850            })
1851            .unwrap();
1852        let response = NtpPacket::timestamp_response(
1853            &SystemSnapshot::default(),
1854            packet,
1855            NtpTimestamp::from_fixed_int(0),
1856            &TestClock {
1857                now: NtpTimestamp::from_fixed_int(1),
1858            },
1859        );
1860        let response_id = response
1861            .efdata
1862            .untrusted
1863            .iter()
1864            .find_map(|f| {
1865                if let ExtensionField::UniqueIdentifier(id) = f {
1866                    Some(id.clone().into_owned())
1867                } else {
1868                    None
1869                }
1870            })
1871            .unwrap();
1872        assert_eq!(packet_id, response_id);
1873        assert_eq!(
1874            response.receive_timestamp(),
1875            NtpTimestamp::from_fixed_int(0)
1876        );
1877        assert_eq!(
1878            response.transmit_timestamp(),
1879            NtpTimestamp::from_fixed_int(1)
1880        );
1881
1882        let (packet, _) =
1883            NtpPacket::nts_poll_message(&cookie, 0, PollIntervalLimits::default().min);
1884        let packet_id = packet
1885            .efdata
1886            .authenticated
1887            .iter()
1888            .find_map(|f| {
1889                if let ExtensionField::UniqueIdentifier(id) = f {
1890                    Some(id.clone().into_owned())
1891                } else {
1892                    None
1893                }
1894            })
1895            .unwrap();
1896        let response = NtpPacket::nts_timestamp_response(
1897            &SystemSnapshot::default(),
1898            packet,
1899            NtpTimestamp::from_fixed_int(0),
1900            &TestClock {
1901                now: NtpTimestamp::from_fixed_int(1),
1902            },
1903            &decoded,
1904            &keysetprovider.get(),
1905        );
1906        let response_id = response
1907            .efdata
1908            .authenticated
1909            .iter()
1910            .find_map(|f| {
1911                if let ExtensionField::UniqueIdentifier(id) = f {
1912                    Some(id.clone().into_owned())
1913                } else {
1914                    None
1915                }
1916            })
1917            .unwrap();
1918        assert_eq!(packet_id, response_id);
1919        assert_eq!(
1920            response.receive_timestamp(),
1921            NtpTimestamp::from_fixed_int(0)
1922        );
1923        assert_eq!(
1924            response.transmit_timestamp(),
1925            NtpTimestamp::from_fixed_int(1)
1926        );
1927
1928        let (mut packet, _) =
1929            NtpPacket::nts_poll_message(&cookie, 0, PollIntervalLimits::default().min);
1930        std::mem::swap(
1931            &mut packet.efdata.authenticated,
1932            &mut packet.efdata.untrusted,
1933        );
1934        let response = NtpPacket::nts_timestamp_response(
1935            &SystemSnapshot::default(),
1936            packet,
1937            NtpTimestamp::from_fixed_int(0),
1938            &TestClock {
1939                now: NtpTimestamp::from_fixed_int(1),
1940            },
1941            &decoded,
1942            &keysetprovider.get(),
1943        );
1944        assert!(response
1945            .efdata
1946            .authenticated
1947            .iter()
1948            .find_map(|f| {
1949                if let ExtensionField::UniqueIdentifier(id) = f {
1950                    Some(id.clone().into_owned())
1951                } else {
1952                    None
1953                }
1954            })
1955            .is_none());
1956        assert_eq!(
1957            response.receive_timestamp(),
1958            NtpTimestamp::from_fixed_int(0)
1959        );
1960        assert_eq!(
1961            response.transmit_timestamp(),
1962            NtpTimestamp::from_fixed_int(1)
1963        );
1964    }
1965
1966    #[test]
1967    fn test_timestamp_cookies() {
1968        let decoded = DecodedServerCookie {
1969            algorithm: AeadAlgorithm::AeadAesSivCmac256,
1970            s2c: Box::new(AesSivCmac256::new((0..32_u8).collect())),
1971            c2s: Box::new(AesSivCmac256::new((32..64_u8).collect())),
1972        };
1973        let keysetprovider = KeySetProvider::new(1);
1974        let cookie = keysetprovider.get().encode_cookie(&decoded);
1975
1976        let (packet, _) =
1977            NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
1978        let response = NtpPacket::nts_timestamp_response(
1979            &SystemSnapshot::default(),
1980            packet,
1981            NtpTimestamp::from_fixed_int(0),
1982            &TestClock {
1983                now: NtpTimestamp::from_fixed_int(1),
1984            },
1985            &decoded,
1986            &keysetprovider.get(),
1987        );
1988        assert_eq!(response.new_cookies().count(), 1);
1989
1990        let (packet, _) =
1991            NtpPacket::nts_poll_message(&cookie, 2, PollIntervalLimits::default().min);
1992        let response = NtpPacket::nts_timestamp_response(
1993            &SystemSnapshot::default(),
1994            packet,
1995            NtpTimestamp::from_fixed_int(0),
1996            &TestClock {
1997                now: NtpTimestamp::from_fixed_int(1),
1998            },
1999            &decoded,
2000            &keysetprovider.get(),
2001        );
2002        assert_eq!(response.new_cookies().count(), 2);
2003
2004        let (packet, _) =
2005            NtpPacket::nts_poll_message(&cookie, 3, PollIntervalLimits::default().min);
2006        let response = NtpPacket::nts_timestamp_response(
2007            &SystemSnapshot::default(),
2008            packet,
2009            NtpTimestamp::from_fixed_int(0),
2010            &TestClock {
2011                now: NtpTimestamp::from_fixed_int(1),
2012            },
2013            &decoded,
2014            &keysetprovider.get(),
2015        );
2016        assert_eq!(response.new_cookies().count(), 3);
2017
2018        let (packet, _) =
2019            NtpPacket::nts_poll_message(&cookie, 4, PollIntervalLimits::default().min);
2020        let response = NtpPacket::nts_timestamp_response(
2021            &SystemSnapshot::default(),
2022            packet,
2023            NtpTimestamp::from_fixed_int(0),
2024            &TestClock {
2025                now: NtpTimestamp::from_fixed_int(1),
2026            },
2027            &decoded,
2028            &keysetprovider.get(),
2029        );
2030        assert_eq!(response.new_cookies().count(), 4);
2031    }
2032
2033    #[test]
2034    fn test_deny_response() {
2035        let decoded = DecodedServerCookie {
2036            algorithm: AeadAlgorithm::AeadAesSivCmac256,
2037            s2c: Box::new(AesSivCmac256::new((0..32_u8).collect())),
2038            c2s: Box::new(AesSivCmac256::new((32..64_u8).collect())),
2039        };
2040        let keysetprovider = KeySetProvider::new(1);
2041        let cookie = keysetprovider.get().encode_cookie(&decoded);
2042
2043        let (packet, _) =
2044            NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
2045        let packet_id = packet
2046            .efdata
2047            .authenticated
2048            .iter()
2049            .find_map(|f| {
2050                if let ExtensionField::UniqueIdentifier(id) = f {
2051                    Some(id.clone().into_owned())
2052                } else {
2053                    None
2054                }
2055            })
2056            .unwrap();
2057        let response = NtpPacket::deny_response(packet);
2058        let response_id = response
2059            .efdata
2060            .untrusted
2061            .iter()
2062            .find_map(|f| {
2063                if let ExtensionField::UniqueIdentifier(id) = f {
2064                    Some(id.clone().into_owned())
2065                } else {
2066                    None
2067                }
2068            })
2069            .unwrap();
2070        assert_eq!(packet_id, response_id);
2071        assert_eq!(response.new_cookies().count(), 0);
2072        assert!(response.is_kiss_deny());
2073
2074        let (mut packet, _) =
2075            NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
2076        let packet_id = packet
2077            .efdata
2078            .authenticated
2079            .iter()
2080            .find_map(|f| {
2081                if let ExtensionField::UniqueIdentifier(id) = f {
2082                    Some(id.clone().into_owned())
2083                } else {
2084                    None
2085                }
2086            })
2087            .unwrap();
2088        std::mem::swap(
2089            &mut packet.efdata.authenticated,
2090            &mut packet.efdata.untrusted,
2091        );
2092        let response = NtpPacket::deny_response(packet);
2093        let response_id = response
2094            .efdata
2095            .untrusted
2096            .iter()
2097            .find_map(|f| {
2098                if let ExtensionField::UniqueIdentifier(id) = f {
2099                    Some(id.clone().into_owned())
2100                } else {
2101                    None
2102                }
2103            })
2104            .unwrap();
2105        assert_eq!(packet_id, response_id);
2106        assert_eq!(response.new_cookies().count(), 0);
2107        assert!(response.is_kiss_deny());
2108
2109        let (packet, _) =
2110            NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
2111        let packet_id = packet
2112            .efdata
2113            .authenticated
2114            .iter()
2115            .find_map(|f| {
2116                if let ExtensionField::UniqueIdentifier(id) = f {
2117                    Some(id.clone().into_owned())
2118                } else {
2119                    None
2120                }
2121            })
2122            .unwrap();
2123        let response = NtpPacket::nts_deny_response(packet);
2124        let response_id = response
2125            .efdata
2126            .authenticated
2127            .iter()
2128            .find_map(|f| {
2129                if let ExtensionField::UniqueIdentifier(id) = f {
2130                    Some(id.clone().into_owned())
2131                } else {
2132                    None
2133                }
2134            })
2135            .unwrap();
2136        assert_eq!(packet_id, response_id);
2137        assert_eq!(response.new_cookies().count(), 0);
2138        assert!(response.is_kiss_deny());
2139
2140        let (mut packet, _) =
2141            NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
2142        std::mem::swap(
2143            &mut packet.efdata.authenticated,
2144            &mut packet.efdata.untrusted,
2145        );
2146        let response = NtpPacket::nts_deny_response(packet);
2147        assert!(response
2148            .efdata
2149            .authenticated
2150            .iter()
2151            .find_map(|f| {
2152                if let ExtensionField::UniqueIdentifier(id) = f {
2153                    Some(id.clone().into_owned())
2154                } else {
2155                    None
2156                }
2157            })
2158            .is_none());
2159        assert_eq!(response.new_cookies().count(), 0);
2160        assert!(response.is_kiss_deny());
2161    }
2162
2163    #[test]
2164    fn test_rate_response() {
2165        let decoded = DecodedServerCookie {
2166            algorithm: AeadAlgorithm::AeadAesSivCmac256,
2167            s2c: Box::new(AesSivCmac256::new((0..32_u8).collect())),
2168            c2s: Box::new(AesSivCmac256::new((32..64_u8).collect())),
2169        };
2170        let keysetprovider = KeySetProvider::new(1);
2171        let cookie = keysetprovider.get().encode_cookie(&decoded);
2172
2173        let (packet, _) =
2174            NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
2175        let packet_id = packet
2176            .efdata
2177            .authenticated
2178            .iter()
2179            .find_map(|f| {
2180                if let ExtensionField::UniqueIdentifier(id) = f {
2181                    Some(id.clone().into_owned())
2182                } else {
2183                    None
2184                }
2185            })
2186            .unwrap();
2187        let response = NtpPacket::rate_limit_response(packet);
2188        let response_id = response
2189            .efdata
2190            .untrusted
2191            .iter()
2192            .find_map(|f| {
2193                if let ExtensionField::UniqueIdentifier(id) = f {
2194                    Some(id.clone().into_owned())
2195                } else {
2196                    None
2197                }
2198            })
2199            .unwrap();
2200        assert_eq!(packet_id, response_id);
2201        assert_eq!(response.new_cookies().count(), 0);
2202        assert!(response.is_kiss_rate(PollIntervalLimits::default().min));
2203
2204        let (mut packet, _) =
2205            NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
2206        let packet_id = packet
2207            .efdata
2208            .authenticated
2209            .iter()
2210            .find_map(|f| {
2211                if let ExtensionField::UniqueIdentifier(id) = f {
2212                    Some(id.clone().into_owned())
2213                } else {
2214                    None
2215                }
2216            })
2217            .unwrap();
2218        std::mem::swap(
2219            &mut packet.efdata.authenticated,
2220            &mut packet.efdata.untrusted,
2221        );
2222        let response = NtpPacket::rate_limit_response(packet);
2223        let response_id = response
2224            .efdata
2225            .untrusted
2226            .iter()
2227            .find_map(|f| {
2228                if let ExtensionField::UniqueIdentifier(id) = f {
2229                    Some(id.clone().into_owned())
2230                } else {
2231                    None
2232                }
2233            })
2234            .unwrap();
2235        assert_eq!(packet_id, response_id);
2236        assert_eq!(response.new_cookies().count(), 0);
2237        assert!(response.is_kiss_rate(PollIntervalLimits::default().min));
2238
2239        let (packet, _) =
2240            NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
2241        let packet_id = packet
2242            .efdata
2243            .authenticated
2244            .iter()
2245            .find_map(|f| {
2246                if let ExtensionField::UniqueIdentifier(id) = f {
2247                    Some(id.clone().into_owned())
2248                } else {
2249                    None
2250                }
2251            })
2252            .unwrap();
2253        let response = NtpPacket::nts_rate_limit_response(packet);
2254        let response_id = response
2255            .efdata
2256            .authenticated
2257            .iter()
2258            .find_map(|f| {
2259                if let ExtensionField::UniqueIdentifier(id) = f {
2260                    Some(id.clone().into_owned())
2261                } else {
2262                    None
2263                }
2264            })
2265            .unwrap();
2266        assert_eq!(packet_id, response_id);
2267        assert_eq!(response.new_cookies().count(), 0);
2268        assert!(response.is_kiss_rate(PollIntervalLimits::default().min));
2269
2270        let (mut packet, _) =
2271            NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
2272        std::mem::swap(
2273            &mut packet.efdata.authenticated,
2274            &mut packet.efdata.untrusted,
2275        );
2276        let response = NtpPacket::nts_rate_limit_response(packet);
2277        assert!(response
2278            .efdata
2279            .authenticated
2280            .iter()
2281            .find_map(|f| {
2282                if let ExtensionField::UniqueIdentifier(id) = f {
2283                    Some(id.clone().into_owned())
2284                } else {
2285                    None
2286                }
2287            })
2288            .is_none());
2289        assert_eq!(response.new_cookies().count(), 0);
2290        assert!(response.is_kiss_rate(PollIntervalLimits::default().min));
2291    }
2292
2293    #[test]
2294    fn test_new_cookies_only_from_encrypted() {
2295        let allowed: [u8; 16] = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
2296        let disallowed: [u8; 16] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
2297        let packet = NtpPacket {
2298            header: NtpHeader::V4(NtpHeaderV3V4::poll_message(PollIntervalLimits::default().min).0),
2299            efdata: ExtensionFieldData {
2300                authenticated: vec![ExtensionField::NtsCookie(Cow::Borrowed(&disallowed))],
2301                encrypted: vec![ExtensionField::NtsCookie(Cow::Borrowed(&allowed))],
2302                untrusted: vec![ExtensionField::NtsCookie(Cow::Borrowed(&disallowed))],
2303            },
2304            mac: None,
2305        };
2306
2307        assert_eq!(1, packet.new_cookies().count());
2308        for cookie in packet.new_cookies() {
2309            assert_eq!(&cookie, &allowed);
2310        }
2311    }
2312
2313    #[test]
2314    fn test_undersized_ef_in_encrypted_data() {
2315        let cipher = AesSivCmac256::new([0_u8; 32].into());
2316        let packet = [
2317            35, 2, 6, 232, 0, 0, 3, 255, 0, 0, 3, 125, 94, 198, 159, 15, 229, 246, 98, 152, 123,
2318            97, 185, 175, 229, 246, 99, 102, 123, 100, 153, 93, 229, 246, 99, 102, 129, 64, 85,
2319            144, 229, 246, 99, 168, 118, 29, 222, 72, 4, 4, 0, 44, 0, 16, 0, 18, 0, 0, 0, 0, 0, 0,
2320            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 24, 181, 156, 166, 35, 154, 207, 38, 150, 15, 190,
2321            152, 87, 142, 206, 254, 105, 0, 0,
2322        ];
2323        //should not crash
2324        assert!(NtpPacket::deserialize(&packet, &cipher).is_err());
2325    }
2326
2327    #[test]
2328    fn test_undersized_ef() {
2329        let packet = [
2330            35, 2, 6, 232, 0, 0, 3, 255, 0, 0, 3, 125, 94, 198, 159, 15, 229, 246, 98, 152, 123,
2331            97, 185, 175, 229, 246, 99, 102, 123, 100, 153, 93, 229, 246, 99, 102, 129, 64, 85,
2332            144, 229, 246, 99, 168, 118, 29, 222, 72, 4, 4,
2333        ];
2334        //should not crash
2335        assert!(NtpPacket::deserialize(&packet, &NoCipher).is_err());
2336    }
2337
2338    #[test]
2339    fn test_undersized_nonce() {
2340        let input = [
2341            32, 206, 206, 206, 77, 206, 206, 255, 216, 216, 216, 127, 0, 0, 0, 0, 0, 0, 0, 216,
2342            216, 216, 216, 206, 217, 216, 216, 216, 216, 216, 216, 206, 206, 206, 1, 0, 0, 0, 206,
2343            206, 206, 4, 44, 4, 4, 4, 4, 4, 4, 4, 0, 4, 206, 206, 222, 206, 206, 206, 206, 0, 0, 0,
2344            206, 206, 206, 0, 0, 0, 206, 206, 206, 206, 206, 206, 131, 206, 206,
2345        ];
2346        //should not crash
2347        assert!(NtpPacket::deserialize(&input, &NoCipher).is_err());
2348    }
2349
2350    #[test]
2351    fn test_undersized_encryption_ef() {
2352        let input = [
2353            32, 206, 206, 206, 77, 206, 216, 216, 127, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 216, 216, 216,
2354            216, 206, 217, 216, 216, 216, 216, 216, 216, 206, 206, 206, 1, 0, 0, 0, 206, 206, 206,
2355            4, 44, 4, 4, 4, 4, 4, 4, 4, 0, 4, 4, 0, 12, 206, 206, 222, 206, 206, 206, 206, 0, 0, 0,
2356            12, 206, 206, 222, 206, 206, 206, 206, 206, 206, 206, 206, 131, 206, 206,
2357        ];
2358        assert!(NtpPacket::deserialize(&input, &NoCipher).is_err());
2359    }
2360
2361    #[test]
2362    fn round_trip_with_ef() {
2363        let (mut p, _) = NtpPacket::poll_message(PollInterval::default());
2364        p.efdata.untrusted.push(ExtensionField::Unknown {
2365            type_id: 0x42,
2366            data: vec![].into(),
2367        });
2368
2369        let serialized = p.serialize_without_encryption_vec(None).unwrap();
2370
2371        let (mut out, _) = NtpPacket::deserialize(&serialized, &NoCipher).unwrap();
2372
2373        // Strip any padding
2374        let ExtensionField::Unknown { data, .. } = &mut out.efdata.untrusted[0] else {
2375            panic!("wrong ef");
2376        };
2377        assert!(data.iter().all(|&e| e == 0));
2378        *data = vec![].into();
2379
2380        assert_eq!(p, out);
2381    }
2382
2383    #[test]
2384    fn ef_with_missing_padding_v5() {
2385        let (packet, _) = NtpPacket::poll_message_v5(PollInterval::default());
2386        let mut data = packet.serialize_without_encryption_vec(None).unwrap();
2387        data.extend([
2388            0, 0, // Type = Unknown
2389            0, 6, // Length = 5
2390            1, 2, // Data
2391               // Missing 2 padding bytes
2392        ]);
2393
2394        assert!(matches!(
2395            NtpPacket::deserialize(&data, &NoCipher),
2396            Err(ParsingError::IncorrectLength)
2397        ));
2398    }
2399
2400    #[test]
2401    fn padding_v5() {
2402        for i in 10..40 {
2403            let packet = NtpPacket::poll_message_v5(PollInterval::default()).0;
2404
2405            let data = packet
2406                .serialize_without_encryption_vec(Some(4 * i))
2407                .unwrap();
2408
2409            assert_eq!(data.len(), 76.max(i * 4));
2410
2411            assert!(NtpPacket::deserialize(&data, &NoCipher).is_ok());
2412        }
2413    }
2414}