domain/base/opt/
cookie.rs

1//! EDNS Option for DNS cookies.
2//!
3//! The option in this module – [`Cookie`] –  is part of a simple mechanism
4//! that helps DNS servers to mitigate denial-of-service and amplification
5//! attacks called DNS cookies.
6//!
7//! In this mechanism, the client creates a client cookie and includes it in
8//! its request to a server. When answering, the server generates a server
9//! cookie from the client cookie and a secret and includes it in the
10//! response. When the client sends subsequent queries to the same server,
11//! it includes both the same client cookie as before and the server cookie
12//! it received, thus identifying itself as having sent a query before.
13//! Because server cookies are deterministic for a given client cookie, the
14//! server doesn’t need to keep any state other than the secret.
15//!
16//! The DNS Cookie mechanism is defined in [RFC 7873]. Guidance for creating
17//! client and server cookies is provided by [RFC 9018].
18//!
19//! [RFC 7873]: https://tools.ietf.org/html/rfc7873
20//! [RFC 9018]: https://tools.ietf.org/html/rfc9018
21
22use super::super::iana::OptionCode;
23use super::super::message_builder::OptBuilder;
24use super::super::wire::{Composer, ParseError};
25use super::{ComposeOptData, Opt, OptData, ParseOptData};
26use crate::base::Serial;
27use crate::utils::base16;
28use core::{fmt, hash};
29use octseq::array::Array;
30use octseq::builder::OctetsBuilder;
31use octseq::octets::Octets;
32use octseq::parse::Parser;
33
34//------------ Cookie --------------------------------------------------------
35
36/// Option data for a DNS cookie.
37///
38/// A value of this type carries two parts: A mandatory [`ClientCookie`] and
39/// an optional [`ServerCookie`]. The client cookie is chosen by, yes, the
40/// client and added to a request when contacting a specific server for the
41/// first time. When responding, a server calculates a server cookie from the
42/// client cookie and adds both of them to the response. The client remembers
43/// both and includes them in subsequent requests. The server can now check
44/// that the the server cookie was indeed calculated by it and treat the
45/// repeat customer differently.
46///
47/// While you can create a new cookie using the [`new`][Self::new] method,
48/// shortcuts are available for the standard workflow. A new initial cookie
49/// can be created via [`create_initial`][Self::create_initial]. As this will
50/// be a random client cookie, it needs the `rand` feature. The server can
51/// check whether a received cookie includes a server cookie created by it
52/// via the
53#[cfg_attr(
54    feature = "siphasher",
55    doc = "[`check_server_hash`](Self::check_server_hash)"
56)]
57#[cfg_attr(not(feature = "siphasher"), doc = "`check_server_hash`")]
58/// method. It needs the SipHash-2-4 algorithm and is thus available if the
59/// `siphasher` feature is enabled. The same feature also enables the
60#[cfg_attr(
61    feature = "siphasher",
62    doc = "[`create_response`](Self::create_response)"
63)]
64#[cfg_attr(not(feature = "siphasher"), doc = "`create_response`")]
65/// method which creates the server
66/// cookie to be included in a response.
67#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
68#[cfg_attr(feature = "rand", derive(Default))]
69#[cfg_attr(feature = "serde", derive(serde::Serialize))]
70pub struct Cookie {
71    /// The client cookie.
72    client: ClientCookie,
73
74    /// The optional server cookie.
75    server: Option<ServerCookie>,
76}
77
78impl Cookie {
79    /// The option code for this option.
80    pub(super) const CODE: OptionCode = OptionCode::COOKIE;
81
82    /// Creates a new cookie from client and optional server cookie.
83    #[must_use]
84    pub fn new(client: ClientCookie, server: Option<ServerCookie>) -> Self {
85        Cookie { client, server }
86    }
87
88    /// Returns the client cookie.
89    #[must_use]
90    pub fn client(&self) -> ClientCookie {
91        self.client
92    }
93
94    /// Returns a reference to the server cookie if present.
95    #[must_use]
96    pub fn server(&self) -> Option<&ServerCookie> {
97        self.server.as_ref()
98    }
99
100    /// Parses the cookie from its wire format.
101    pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
102        parser: &mut Parser<'_, Octs>,
103    ) -> Result<Self, ParseError> {
104        Ok(Cookie::new(
105            ClientCookie::parse(parser)?,
106            ServerCookie::parse_opt(parser)?,
107        ))
108    }
109
110    /// Returns whether the standard server cookie’s hash is correct.
111    ///
112    /// The `client_ip` is the source IP address of a request. The `secret`
113    /// is the server cookie secret. The timestamp is checked via the
114    /// `timestamp_ok` closure which is given the timestamp and should return
115    /// whether it is acceptable.
116    ///
117    /// Returns `false` if the cookie is not a server cookie, if it is but
118    /// not a standard server cookie, or if it is but either the timestamp
119    /// is not acceptable or the hash differs from what it should be.
120    ///
121    /// Thus, if this method returns `false`, there is no valid server cookie
122    /// and the server can proceed as if there was no server cookie as
123    /// described in section 5.2.3 of [RFC 7873].
124    ///
125    /// [RFC 7873]: https://tools.ietf.org/html/rfc7873
126    #[cfg(feature = "siphasher")]
127    pub fn check_server_hash(
128        &self,
129        client_ip: crate::base::net::IpAddr,
130        secret: &[u8; 16],
131        timestamp_ok: impl FnOnce(Serial) -> bool,
132    ) -> bool {
133        self.server
134            .as_ref()
135            .and_then(|server| server.try_to_standard())
136            .and_then(|server| {
137                timestamp_ok(server.timestamp()).then_some(server)
138            })
139            .map(|server| server.check_hash(self.client(), client_ip, secret))
140            .unwrap_or(false)
141    }
142
143    /// Creates a random client cookie for including in an initial request.
144    #[cfg(feature = "rand")]
145    #[must_use]
146    pub fn create_initial() -> Self {
147        Self::new(ClientCookie::new_random(), None)
148    }
149
150    /// Creates a standard format cookie option for sending a response.
151    ///
152    /// This method uses the client cookie and the additional values provided
153    /// to produce a cookie option that should be included in a response.
154    #[cfg(feature = "siphasher")]
155    pub fn create_response(
156        &self,
157        timestamp: Serial,
158        client_ip: crate::base::net::IpAddr,
159        secret: &[u8; 16],
160    ) -> Self {
161        Self::new(
162            self.client,
163            Some(
164                StandardServerCookie::calculate(
165                    self.client,
166                    timestamp,
167                    client_ip,
168                    secret,
169                )
170                .into(),
171            ),
172        )
173    }
174
175    /// Placeholder for unnecessary octets conversion.
176    ///
177    /// This method only exists for the `AllOptData` macro.
178    pub(super) fn try_octets_from<E>(src: Self) -> Result<Self, E> {
179        Ok(src)
180    }
181}
182
183//--- OptData
184
185impl OptData for Cookie {
186    fn code(&self) -> OptionCode {
187        OptionCode::COOKIE
188    }
189}
190
191impl<'a, Octs: AsRef<[u8]> + ?Sized> ParseOptData<'a, Octs> for Cookie {
192    fn parse_option(
193        code: OptionCode,
194        parser: &mut Parser<'a, Octs>,
195    ) -> Result<Option<Self>, ParseError> {
196        if code == OptionCode::COOKIE {
197            Self::parse(parser).map(Some)
198        } else {
199            Ok(None)
200        }
201    }
202}
203
204impl ComposeOptData for Cookie {
205    fn compose_len(&self) -> u16 {
206        match self.server.as_ref() {
207            Some(server) => ClientCookie::COMPOSE_LEN
208                .checked_add(server.compose_len())
209                .expect("long server cookie"),
210            None => ClientCookie::COMPOSE_LEN,
211        }
212    }
213
214    fn compose_option<Target: OctetsBuilder + ?Sized>(
215        &self,
216        target: &mut Target,
217    ) -> Result<(), Target::AppendError> {
218        self.client.compose(target)?;
219        if let Some(server) = self.server.as_ref() {
220            server.compose(target)?;
221        }
222        Ok(())
223    }
224}
225
226impl fmt::Display for Cookie {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        fmt::Display::fmt(&self.client, f)?;
229        if let Some(server) = self.server.as_ref() {
230            fmt::Display::fmt(server, f)?;
231        }
232        Ok(())
233    }
234}
235
236//--- Extending Opt and OptBuilder
237
238impl<Octs: Octets> Opt<Octs> {
239    /// Returns the first cookie option if present.
240    pub fn cookie(&self) -> Option<Cookie> {
241        self.first()
242    }
243}
244
245impl<Target: Composer> OptBuilder<'_, Target> {
246    /// Appends a new cookie option.
247    pub fn cookie(
248        &mut self,
249        cookie: Cookie,
250    ) -> Result<(), Target::AppendError> {
251        self.push(&cookie)
252    }
253
254    /// Appends a new initial client cookie.
255    ///
256    /// The appened cookie will have a random client cookie portion and no
257    /// server cookie. See [`Cookie`] for more information about cookies.
258    #[cfg(feature = "rand")]
259    pub fn initial_cookie(&mut self) -> Result<(), Target::AppendError> {
260        self.push(&Cookie::create_initial())
261    }
262}
263
264//------------ ClientCookie --------------------------------------------------
265
266/// A client cookie for DNS cookies.
267///
268/// The client cookies consists of exactly 8 octets. It is generated by a
269/// client for each server it sends queries to. It is important to use a
270/// different cookie for every server so a server cannot spoof answers for
271/// other servers.
272///
273/// Originally, it was suggested to include the client’s IP address when
274/// generating the cookie, but since the address may not be known when
275/// originating a request, this has been relaxed and it is now suggested that
276/// the cookies is just random data. If the `rand` feature is enabled, the
277/// `new`
278#[cfg_attr(
279    feature = "rand",
280    doc = "[`new_random`][ClientCookie::new_random]"
281)]
282#[cfg_attr(not(feature = "rand"), doc = "`new_random`")]
283/// constructor can be used to generate such a random cookie. Otherwise,
284/// it needs to be created from the octets via
285/// [`from_octets`][ClientCookie::from_octets]. Similarly, the `Default`
286/// implementation will create a random cookie and is thus only available if
287/// the `rand` feature is enabled.
288#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
289pub struct ClientCookie([u8; 8]);
290
291#[cfg(feature = "serde")]
292impl serde::Serialize for ClientCookie {
293    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
294    where
295        S: serde::Serializer,
296    {
297        use octseq::serde::SerializeOctets;
298        self.0.serialize_octets(serializer)
299    }
300}
301
302impl ClientCookie {
303    /// Creates a new client cookie from the given octets.
304    #[must_use]
305    pub const fn from_octets(octets: [u8; 8]) -> Self {
306        Self(octets)
307    }
308
309    /// Creates a new random client cookie.
310    #[cfg(feature = "rand")]
311    #[must_use]
312    pub fn new_random() -> Self {
313        Self(rand::random())
314    }
315
316    /// Converts the cookie into its octets.
317    #[must_use]
318    pub fn into_octets(self) -> [u8; 8] {
319        self.0
320    }
321
322    /// Parses a client cookie from its wire format.
323    pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
324        parser: &mut Parser<'_, Octs>,
325    ) -> Result<Self, ParseError> {
326        let mut res = Self::from_octets([0; 8]);
327        parser.parse_buf(res.as_mut())?;
328        Ok(res)
329    }
330
331    /// The length of the wire format of a client cookie.
332    pub const COMPOSE_LEN: u16 = 8;
333
334    /// Appends the wire format of the client cookie to the target.
335    pub fn compose<Target: OctetsBuilder + ?Sized>(
336        &self,
337        target: &mut Target,
338    ) -> Result<(), Target::AppendError> {
339        target.append_slice(&self.0)
340    }
341}
342
343//--- Default
344
345#[cfg(feature = "rand")]
346impl Default for ClientCookie {
347    fn default() -> Self {
348        Self::new_random()
349    }
350}
351
352//--- From
353
354impl From<[u8; 8]> for ClientCookie {
355    fn from(src: [u8; 8]) -> Self {
356        Self::from_octets(src)
357    }
358}
359
360impl From<ClientCookie> for [u8; 8] {
361    fn from(src: ClientCookie) -> Self {
362        src.0
363    }
364}
365
366//--- AsRef and AsMut
367
368impl AsRef<[u8]> for ClientCookie {
369    fn as_ref(&self) -> &[u8] {
370        self.0.as_ref()
371    }
372}
373
374impl AsMut<[u8]> for ClientCookie {
375    fn as_mut(&mut self) -> &mut [u8] {
376        self.0.as_mut()
377    }
378}
379
380//--- Hash
381
382impl hash::Hash for ClientCookie {
383    fn hash<H: hash::Hasher>(&self, state: &mut H) {
384        state.write(&self.0)
385    }
386}
387
388//--- Display
389
390impl fmt::Display for ClientCookie {
391    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392        base16::display(self.0.as_ref(), f)
393    }
394}
395
396//------------ ServerCookie --------------------------------------------------
397
398/// A server cookie for DNS cookies.
399///
400/// In the original specification, the server cookie was of variable length
401/// between 8 and 32 octets. It was supposed to be generated via some sort
402/// of message authentication code from the client cookie and a server secret.
403/// Leaving the concrete mechanism to the implementer resulted in
404/// interoperability problems if servers from multiple vendors were placed
405/// behind the same public address. Thus, [RFC 9018] defined a standard
406/// mechanism of the content and generation of the cookie.
407///
408/// This standard server cookie consists of a 1 octet version number
409/// (currently 1), 3 reserved octets that must be zero, a 4 octet timestamp
410/// as seconds since the Unix epoch, and 8 octets of hash value.
411///
412/// In version 1, the hash is calculated feeding the SipHash-2-4 that has been
413/// initialized with a server secret the concatenation of client cookie,
414/// version, reserved, timestamp, client IP address.
415///
416/// [RFC 9018]: https://tools.ietf.org/html/rfc9018
417#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
418pub struct ServerCookie(Array<32>);
419
420#[cfg(feature = "serde")]
421impl serde::Serialize for ServerCookie {
422    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
423    where
424        S: serde::Serializer,
425    {
426        use octseq::serde::SerializeOctets;
427        self.0.serialize_octets(serializer)
428    }
429}
430
431impl ServerCookie {
432    /// Creates a new server cookie from the given octets.
433    ///
434    /// # Panics
435    ///
436    /// The function panics if `octets` is shorter than 8 octets or longer
437    /// than 32.
438    #[must_use]
439    pub fn from_octets(slice: &[u8]) -> Self {
440        assert!(slice.len() >= 8, "server cookie shorter than 8 octets");
441        let mut res = Array::new();
442        res.append_slice(slice)
443            .expect("server cookie longer tha 32 octets");
444        Self(res)
445    }
446
447    /// Parses a server cookie from its wire format.
448    pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
449        parser: &mut Parser<'_, Octs>,
450    ) -> Result<Self, ParseError> {
451        if parser.remaining() < 8 {
452            return Err(ParseError::form_error("short server cookie"));
453        }
454        let mut res = Array::new();
455        res.resize_raw(parser.remaining())
456            .map_err(|_| ParseError::form_error("long server cookie"))?;
457        parser.parse_buf(res.as_slice_mut())?;
458        Ok(Self(res))
459    }
460
461    /// Parses an optional server cookie from its wire format.
462    pub fn parse_opt<Octs: AsRef<[u8]> + ?Sized>(
463        parser: &mut Parser<'_, Octs>,
464    ) -> Result<Option<Self>, ParseError> {
465        if parser.remaining() > 0 {
466            Self::parse(parser).map(Some)
467        } else {
468            Ok(None)
469        }
470    }
471
472    /// Converts the cookie into a standard cookie if possible.
473    ///
474    /// This is possible if the length of the cookie is 16 octets. Returns
475    /// `None` otherwise.
476    pub fn try_to_standard(&self) -> Option<StandardServerCookie> {
477        TryFrom::try_from(self.0.as_slice())
478            .map(StandardServerCookie)
479            .ok()
480    }
481
482    /// Returns the length of the wire format of the cookie.
483    #[must_use]
484    pub fn compose_len(&self) -> u16 {
485        u16::try_from(self.0.len()).expect("long server cookie")
486    }
487
488    /// Appends the wire format of the cookie to the target.
489    pub fn compose<Target: OctetsBuilder + ?Sized>(
490        &self,
491        target: &mut Target,
492    ) -> Result<(), Target::AppendError> {
493        target.append_slice(self.0.as_ref())
494    }
495}
496
497//--- From
498
499impl From<StandardServerCookie> for ServerCookie {
500    fn from(src: StandardServerCookie) -> Self {
501        Self::from_octets(&src.0)
502    }
503}
504
505//--- AsRef
506
507impl AsRef<[u8]> for ServerCookie {
508    fn as_ref(&self) -> &[u8] {
509        self.0.as_ref()
510    }
511}
512
513//--- Display
514
515impl fmt::Display for ServerCookie {
516    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517        base16::display(self.0.as_ref(), f)
518    }
519}
520
521//------------ StandardServerCookie ------------------------------------------
522
523/// An interoperable server cookie for DNS cookies.
524///
525/// In the original specification, the server cookie was of variable length
526/// and rules for its generation were left to the server implementers. This
527/// resulted in interoperability problems if servers from multiple vendors
528/// were placed behind the same public address. Thus, [RFC 9018] defined a
529/// standard mechanism of the content and generation of the cookie. This
530/// type is such a standard server cookie.
531///
532/// This standard server cookie consists of a 1 octet version number
533/// (currently 1), 3 reserved octets that must be zero, a 4 octet timestamp
534/// as seconds since the Unix epoch, and 8 octets of hash value.
535///
536/// In version 1, the hash is calculated feeding the SipHash-2-4 that has been
537/// initialized with a server secret the concatenation of client cookie,
538/// version, reserved, timestamp, client IP address. Generatin and checking
539/// the hash is available if the `siphasher` feature is enabled.
540///
541/// [RFC 9018]: https://tools.ietf.org/html/rfc9018
542#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
543pub struct StandardServerCookie(
544    // We let this type wrap a u8 array so we can provide AsRef<[u8]> it.
545    // This makes reading the timestamp a tiny bit expensive on certain
546    // systems, but so be it.
547    [u8; 16],
548);
549
550impl StandardServerCookie {
551    /// Creates a new server cookie from the provided components.
552    #[must_use]
553    pub fn new(
554        version: u8,
555        reserved: [u8; 3],
556        timestamp: Serial,
557        hash: [u8; 8],
558    ) -> Self {
559        let ts = timestamp.into_int().to_be_bytes();
560        Self([
561            version,
562            reserved[0],
563            reserved[1],
564            reserved[2],
565            ts[0],
566            ts[1],
567            ts[2],
568            ts[3],
569            hash[0],
570            hash[1],
571            hash[2],
572            hash[3],
573            hash[4],
574            hash[5],
575            hash[6],
576            hash[7],
577        ])
578    }
579
580    /// Calculates the server cookie for the given components.
581    #[cfg(feature = "siphasher")]
582    pub fn calculate(
583        client_cookie: ClientCookie,
584        timestamp: Serial,
585        client_ip: crate::base::net::IpAddr,
586        secret: &[u8; 16],
587    ) -> Self {
588        let mut res = Self::new(1, [0; 3], timestamp, [0; 8]);
589        res.set_hash(res.calculate_hash(client_cookie, client_ip, secret));
590        res
591    }
592
593    /// Returns the version field of the cookie.
594    #[must_use]
595    pub fn version(self) -> u8 {
596        self.0[0]
597    }
598
599    /// Returns the reserved field of the cookie.
600    #[must_use]
601    pub fn reserved(self) -> [u8; 3] {
602        TryFrom::try_from(&self.0[1..4]).expect("bad slicing")
603    }
604
605    /// Returns the timestamp field of the cookie.
606    #[must_use]
607    pub fn timestamp(self) -> Serial {
608        Serial::from_be_bytes(
609            TryFrom::try_from(&self.0[4..8]).expect("bad slicing"),
610        )
611    }
612
613    /// Returns the hash field of the cookie.
614    #[must_use]
615    pub fn hash(self) -> [u8; 8] {
616        TryFrom::try_from(&self.0[8..]).expect("bad slicing")
617    }
618
619    /// Sets the hash field to the given value.
620    pub fn set_hash(&mut self, hash: [u8; 8]) {
621        self.0[8..].copy_from_slice(&hash);
622    }
623
624    /// Returns whether the hash matches the given client cookie and secret.
625    #[cfg(feature = "siphasher")]
626    pub fn check_hash(
627        self,
628        client_cookie: ClientCookie,
629        client_ip: crate::base::net::IpAddr,
630        secret: &[u8; 16],
631    ) -> bool {
632        self.calculate_hash(client_cookie, client_ip, secret) == self.hash()
633    }
634
635    /// Calculates the hash value.
636    ///
637    /// The method takes the version, reserved, and timestamp fields from
638    /// `self` and the rest from the arguments. It returns the hash as an
639    /// octets array.
640    //
641    // XXX The hash implementation for SipHash-2-4 returns the result as
642    // a `u64` whereas RFC 9018 assumes it is returned as an octets array in
643    // a standard ordering. Somewhat surprisingly, this ordering turns out to
644    // be little endian.
645    #[cfg(feature = "siphasher")]
646    fn calculate_hash(
647        self,
648        client_cookie: ClientCookie,
649        client_ip: crate::base::net::IpAddr,
650        secret: &[u8; 16],
651    ) -> [u8; 8] {
652        use crate::base::net::IpAddr;
653        use core::hash::{Hash, Hasher};
654
655        let mut hasher = siphasher::sip::SipHasher24::new_with_key(secret);
656        client_cookie.hash(&mut hasher);
657        hasher.write(&self.0[..8]);
658        match client_ip {
659            IpAddr::V4(addr) => hasher.write(&addr.octets()),
660            IpAddr::V6(addr) => hasher.write(&addr.octets()),
661        }
662        hasher.finish().to_le_bytes()
663    }
664}
665
666//--- Display
667
668impl fmt::Display for StandardServerCookie {
669    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
670        base16::display(self.0.as_ref(), f)
671    }
672}
673
674//============ Tests =========================================================
675
676#[cfg(test)]
677mod test {
678    #[allow(unused_imports)]
679    use super::*;
680
681    /// Tests from Appendix A of RFC 9018.
682    #[cfg(all(feature = "siphasher", feature = "std"))]
683    mod standard_server {
684        use super::*;
685        use crate::base::net::{IpAddr, Ipv4Addr, Ipv6Addr};
686        use crate::base::wire::{compose_vec, parse_slice};
687
688        const CLIENT_1: IpAddr = IpAddr::V4(Ipv4Addr::new(198, 51, 100, 100));
689        const CLIENT_2: IpAddr = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 203));
690        const CLIENT_6: IpAddr = IpAddr::V6(Ipv6Addr::new(
691            0x2001, 0xdb8, 0x220, 0x1, 0x59de, 0xd0f4, 0x8769, 0x82b8,
692        ));
693
694        const SECRET: [u8; 16] = [
695            0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f, 0x48, 0xe7, 0xdc,
696            0x84, 0x9e, 0x37, 0xbf, 0xcf,
697        ];
698
699        /// A.1. Learning a New Server Cookie
700        #[test]
701        fn new_cookie() {
702            let request = Cookie::new(
703                ClientCookie::from_octets([
704                    0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57,
705                ]),
706                None,
707            );
708            assert_eq!(
709                compose_vec(|vec| request.compose_option(vec)),
710                base16::decode_vec("2464c4abcf10c957").unwrap()
711            );
712
713            assert_eq!(
714                compose_vec(|vec| {
715                    request
716                        .create_response(
717                            Serial(1559731985),
718                            CLIENT_1,
719                            &SECRET,
720                        )
721                        .compose_option(vec)
722                }),
723                base16::decode_vec(
724                    "2464c4abcf10c957010000005cf79f111f8130c3eee29480"
725                )
726                .unwrap()
727            );
728        }
729
730        /// A.2.  The Same Client Learning a Renewed (Fresh) Server Cookie
731        #[test]
732        fn renew_cookie() {
733            let request = parse_slice(
734                &base16::decode_vec(
735                    "2464c4abcf10c957010000005cf79f111f8130c3eee29480",
736                )
737                .unwrap(),
738                Cookie::parse,
739            )
740            .unwrap();
741            assert!(request
742                .check_server_hash(CLIENT_1, &SECRET, |serial| serial
743                    == Serial(1559731985)));
744
745            assert_eq!(
746                compose_vec(|vec| {
747                    request
748                        .create_response(
749                            Serial(1559734385),
750                            CLIENT_1,
751                            &SECRET,
752                        )
753                        .compose_option(vec)
754                }),
755                base16::decode_vec(
756                    "2464c4abcf10c957010000005cf7a871d4a564a1442aca77"
757                )
758                .unwrap()
759            );
760        }
761
762        /// A.3.  Another Client Learning a Renewed Server Cookie
763        #[test]
764        fn non_zero_reserved() {
765            let request = parse_slice(
766                &base16::decode_vec(
767                    "fc93fc62807ddb8601abcdef5cf78f71a314227b6679ebf5",
768                )
769                .unwrap(),
770                Cookie::parse,
771            )
772            .unwrap();
773            assert!(request
774                .check_server_hash(CLIENT_2, &SECRET, |serial| serial
775                    == Serial(1559727985)));
776
777            assert_eq!(
778                compose_vec(|vec| {
779                    request
780                        .create_response(
781                            Serial(1559734700),
782                            CLIENT_2,
783                            &SECRET,
784                        )
785                        .compose_option(vec)
786                }),
787                base16::decode_vec(
788                    "fc93fc62807ddb86010000005cf7a9acf73a7810aca2381e"
789                )
790                .unwrap()
791            );
792        }
793
794        /// A.4.  IPv6 Query with Rolled Over Secret
795        #[test]
796        fn new_secret() {
797            const OLD_SECRET: [u8; 16] = [
798                0xdd, 0x3b, 0xdf, 0x93, 0x44, 0xb6, 0x78, 0xb1, 0x85, 0xa6,
799                0xf5, 0xcb, 0x60, 0xfc, 0xa7, 0x15,
800            ];
801            const NEW_SECRET: [u8; 16] = [
802                0x44, 0x55, 0x36, 0xbc, 0xd2, 0x51, 0x32, 0x98, 0x07, 0x5a,
803                0x5d, 0x37, 0x96, 0x63, 0xc9, 0x62,
804            ];
805
806            let request = parse_slice(
807                &base16::decode_vec(
808                    "22681ab97d52c298010000005cf7c57926556bd0934c72f8",
809                )
810                .unwrap(),
811                Cookie::parse,
812            )
813            .unwrap();
814            assert!(!request.check_server_hash(
815                CLIENT_6,
816                &NEW_SECRET,
817                |serial| serial == Serial(1559741817)
818            ));
819            assert!(request.check_server_hash(
820                CLIENT_6,
821                &OLD_SECRET,
822                |serial| serial == Serial(1559741817)
823            ));
824
825            assert_eq!(
826                compose_vec(|vec| {
827                    request
828                        .create_response(
829                            Serial(1559741961),
830                            CLIENT_6,
831                            &NEW_SECRET,
832                        )
833                        .compose_option(vec)
834                }),
835                base16::decode_vec(
836                    "22681ab97d52c298010000005cf7c609a6bb79d16625507a"
837                )
838                .unwrap()
839            );
840        }
841    }
842}