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