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