domain/base/opt/
expire.rs

1//! EDNS Options for signalling zone expire times.
2//!
3//! The option in this module, [`Expire`], allows a authoritative server to
4//! signal when a zone expires independently of the SOA’s expire field. This
5//! allows to determine the expire time when a secondary server is updating
6//! a zone from another secondary server rather than directly from the
7//! primary.
8//!
9//! This option is defined in [RFC 7314](https://tools.ietf.org/html/rfc7314).
10
11use super::super::iana::OptionCode;
12use super::super::message_builder::OptBuilder;
13use super::super::wire::{Compose, Composer, Parse, ParseError};
14use super::{ComposeOptData, Opt, OptData, ParseOptData};
15use core::fmt;
16use octseq::builder::OctetsBuilder;
17use octseq::octets::Octets;
18use octseq::parse::Parser;
19
20//------------ Expire --------------------------------------------------------
21
22/// Option data for the Expire EDNS option.
23///
24/// The option’s data consists of an optional `u32`. The value is omitted if
25/// the option is added to a query to request it being included by the server
26/// in an answer. In this answer the value should be present and indicates the
27/// expire time of the zone on the server.
28///
29/// See [RFC 7314](https://tools.ietf.org/html/rfc7314) for details.
30#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize))]
32pub struct Expire(Option<u32>);
33
34impl Expire {
35    /// The option code for this option.
36    pub(super) const CODE: OptionCode = OptionCode::EXPIRE;
37
38    /// Creates a new expire option with the given optional expire value.
39    #[must_use]
40    pub fn new(expire: Option<u32>) -> Self {
41        Expire(expire)
42    }
43
44    /// Returns the content of the optional expire value.
45    #[must_use]
46    pub fn expire(self) -> Option<u32> {
47        self.0
48    }
49
50    /// Parses a value from its wire format.
51    pub fn parse<Octs: AsRef<[u8]>>(
52        parser: &mut Parser<'_, Octs>,
53    ) -> Result<Self, ParseError> {
54        if parser.remaining() == 0 {
55            Ok(Expire::new(None))
56        } else {
57            u32::parse(parser).map(|res| Expire::new(Some(res)))
58        }
59    }
60
61    /// Placeholder for unnecessary octets conversion.
62    ///
63    /// This method only exists for the `AllOptData` macro.
64    pub(super) fn try_octets_from<E>(src: Self) -> Result<Self, E> {
65        Ok(src)
66    }
67}
68
69//--- OptData
70
71impl OptData for Expire {
72    fn code(&self) -> OptionCode {
73        OptionCode::EXPIRE
74    }
75}
76
77impl<'a, Octs: AsRef<[u8]>> ParseOptData<'a, Octs> for Expire {
78    fn parse_option(
79        code: OptionCode,
80        parser: &mut Parser<'a, Octs>,
81    ) -> Result<Option<Self>, ParseError> {
82        if code == OptionCode::EXPIRE {
83            Self::parse(parser).map(Some)
84        } else {
85            Ok(None)
86        }
87    }
88}
89
90impl ComposeOptData for Expire {
91    fn compose_len(&self) -> u16 {
92        match self.0 {
93            Some(_) => u32::COMPOSE_LEN,
94            None => 0,
95        }
96    }
97
98    fn compose_option<Target: OctetsBuilder + ?Sized>(
99        &self,
100        target: &mut Target,
101    ) -> Result<(), Target::AppendError> {
102        if let Some(value) = self.0 {
103            value.compose(target)?;
104        }
105        Ok(())
106    }
107}
108
109//--- Display
110
111impl fmt::Display for Expire {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        match self.0 {
114            Some(expire) => expire.fmt(f),
115            None => Ok(()),
116        }
117    }
118}
119
120//--- Extended Opt and OptBuilder
121
122impl<Octs: Octets> Opt<Octs> {
123    /// Returns the content of the Expire option if present.
124    ///
125    /// The Expire option allows an authoritative server to signal its own
126    /// expiry time of a zone.
127    pub fn expire(&self) -> Option<Expire> {
128        self.first()
129    }
130}
131
132impl<Target: Composer> OptBuilder<'_, Target> {
133    /// Appends the Expire option.
134    ///
135    /// The Expire option allows an authoritative server to signal its own
136    /// expiry time of a zone.
137    pub fn expire(
138        &mut self,
139        expire: Option<u32>,
140    ) -> Result<(), Target::AppendError> {
141        self.push(&Expire::new(expire))
142    }
143}
144
145//============ Testing ======================================================
146
147#[cfg(test)]
148#[cfg(all(feature = "std", feature = "bytes"))]
149mod test {
150    use super::super::test::test_option_compose_parse;
151    use super::*;
152
153    #[test]
154    #[allow(clippy::redundant_closure)] // lifetimes ...
155    fn expire_compose_parse() {
156        test_option_compose_parse(&Expire::new(None), |parser| {
157            Expire::parse(parser)
158        });
159        test_option_compose_parse(&Expire::new(Some(12)), |parser| {
160            Expire::parse(parser)
161        });
162    }
163}