1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! EDNS Options for signalling zone expire times.
//!
//! The option in this module, [`Expire`], allows a authoritative server to
//! signal when a zone expires independently of the SOA’s expire field. This
//! allows to determine the expire time when a secondary server is updating
//! a zone from another secondary server rather than directly from the
//! primary.
//!
//! This option is defined in [RFC 7314](https://tools.ietf.org/html/rfc7314).

use core::fmt;
use super::super::iana::OptionCode;
use super::super::message_builder::OptBuilder;
use super::super::wire::{Compose, Composer, Parse, ParseError};
use super::{Opt, OptData, ComposeOptData, ParseOptData};
use octseq::builder::OctetsBuilder;
use octseq::octets::Octets;
use octseq::parse::Parser;


//------------ Expire --------------------------------------------------------

/// Option data for the Expire EDNS option.
///
/// The option’s data consists of an optional `u32`. The value is omitted if
/// the option is added to a query to request it being included by the server
/// in an answer. In this answer the value should be present and indicates the
/// expire time of the zone on the server.
///
/// See [RFC 7314](https://tools.ietf.org/html/rfc7314) for details.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Expire(Option<u32>);

impl Expire {
    /// The option code for this option.
    pub(super) const CODE: OptionCode = OptionCode::EXPIRE;
    
    /// Creates a new expire option with the given optional expire value.
    #[must_use]
    pub fn new(expire: Option<u32>) -> Self {
        Expire(expire)
    }

    /// Returns the content of the optional expire value.
    #[must_use]
    pub fn expire(self) -> Option<u32> {
        self.0
    }

    /// Parses a value from its wire format.
    pub fn parse<Octs: AsRef<[u8]>>(
        parser: &mut Parser<Octs>
    ) -> Result<Self, ParseError> {
        if parser.remaining() == 0 {
            Ok(Expire::new(None))
        }
        else {
            u32::parse(parser).map(|res| Expire::new(Some(res)))
        }
    }

    /// Placeholder for unnecessary octets conversion.
    ///
    /// This method only exists for the `AllOptData` macro.
    pub(super) fn try_octets_from<E>(src: Self) -> Result<Self, E> {
        Ok(src)
    }
}

//--- OptData

impl OptData for Expire {
    fn code(&self) -> OptionCode {
        OptionCode::EXPIRE
    }
}

impl<'a, Octs: AsRef<[u8]>> ParseOptData<'a, Octs> for Expire {
    fn parse_option(
        code: OptionCode,
        parser: &mut Parser<'a, Octs>,
    ) -> Result<Option<Self>, ParseError> {
        if code == OptionCode::EXPIRE {
            Self::parse(parser).map(Some)
        }
        else {
            Ok(None)
        }
    }
}

impl ComposeOptData for Expire {
    fn compose_len(&self) -> u16 {
        match self.0 {
            Some(_) => u32::COMPOSE_LEN,
            None => 0,
        }
    }

    fn compose_option<Target: OctetsBuilder + ?Sized>(
        &self, target: &mut Target
    ) -> Result<(), Target::AppendError> {
        if let Some(value) = self.0 {
            value.compose(target)?;
        }
        Ok(())
    }
}

//--- Display

impl fmt::Display for Expire {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.0 {
            Some(expire) => expire.fmt(f),
            None => Ok(())
        }
    }
}

//--- Extended Opt and OptBuilder

impl<Octs: Octets> Opt<Octs> {
    /// Returns the content of the Expire option if present.
    ///
    /// The Expire option allows an authoritative server to signal its own
    /// expiry time of a zone.
    pub fn expire(&self) -> Option<Expire> {
        self.first()
    }
}

impl<'a, Target: Composer> OptBuilder<'a, Target> {
    /// Appends the Expire option.
    ///
    /// The Expire option allows an authoritative server to signal its own
    /// expiry time of a zone.
    pub fn expire(
        &mut self, expire: Option<u32>
    ) -> Result<(), Target::AppendError> {
        self.push(&Expire::new(expire))
    }
}


//============ Testing ======================================================

#[cfg(test)]
#[cfg(all(feature = "std", feature = "bytes"))]
mod test {
    use super::*;
    use super::super::test::test_option_compose_parse;
    
    #[test]
    #[allow(clippy::redundant_closure)] // lifetimes ...
    fn expire_compose_parse() {
        test_option_compose_parse(
            &Expire::new(None),
            |parser| Expire::parse(parser)
        );
        test_option_compose_parse(
            &Expire::new(Some(12)),
            |parser| Expire::parse(parser)
        );
    }
}