domain/base/opt/
keepalive.rs

1//! EDNS options to signal a variable TCP connection timeout.
2//!
3//! The option in this module – [`TcpKeepalive`] – allows a server to signal
4//! to a client how long it should hold on to a TCP connection after having
5//! received an answer.
6//!
7//! Note that his has nothing to do with the keepalive feature of TCP itself.
8//!
9//! This option is defined in [RFC 7829](https://tools.ietf.org/html/rfc7828).
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 core::time::Duration;
17use octseq::builder::OctetsBuilder;
18use octseq::octets::Octets;
19use octseq::parse::Parser;
20
21//------------ TcpKeepalive --------------------------------------------------
22
23/// Option data for the edns-tcp-keepalive option.
24///
25/// The edns-tcp-keepalive option can be used to determine a time a server
26/// would like a client to keep a TCP connection open after receiving an
27/// answer. The client includes the option without a value in its query to
28/// indicate support for the option. The server then includes the option in
29/// its response, including a 16-bit value that provides the idle time in
30/// units of 100 milliseconds.
31#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize))]
33pub struct TcpKeepalive(Option<IdleTimeout>);
34
35impl TcpKeepalive {
36    /// The option code for this option.
37    pub(super) const CODE: OptionCode = OptionCode::TCP_KEEPALIVE;
38
39    /// Creates a new value from an optional idle timeout.
40    #[must_use]
41    pub fn new(timeout: Option<IdleTimeout>) -> Self {
42        TcpKeepalive(timeout)
43    }
44
45    /// Returns the idle timeout.
46    #[must_use]
47    pub fn timeout(self) -> Option<IdleTimeout> {
48        self.0
49    }
50
51    /// Parses an option data value from its wire format.
52    pub fn parse<Octs: AsRef<[u8]>>(
53        parser: &mut Parser<'_, Octs>,
54    ) -> Result<Self, ParseError> {
55        if parser.remaining() == 0 {
56            Ok(Self::new(None))
57        } else {
58            IdleTimeout::parse(parser).map(|v| Self::new(Some(v)))
59        }
60    }
61
62    /// Placeholder for unnecessary octets conversion.
63    ///
64    /// This method only exists for the `AllOptData` macro.
65    pub(super) fn try_octets_from<E>(src: Self) -> Result<Self, E> {
66        Ok(src)
67    }
68}
69
70//--- OptData
71
72impl OptData for TcpKeepalive {
73    fn code(&self) -> OptionCode {
74        OptionCode::TCP_KEEPALIVE
75    }
76}
77
78impl<'a, Octs: AsRef<[u8]>> ParseOptData<'a, Octs> for TcpKeepalive {
79    fn parse_option(
80        code: OptionCode,
81        parser: &mut Parser<'a, Octs>,
82    ) -> Result<Option<Self>, ParseError> {
83        if code == OptionCode::TCP_KEEPALIVE {
84            Self::parse(parser).map(Some)
85        } else {
86            Ok(None)
87        }
88    }
89}
90
91impl ComposeOptData for TcpKeepalive {
92    fn compose_len(&self) -> u16 {
93        match self.0 {
94            Some(_) => IdleTimeout::COMPOSE_LEN,
95            None => 0,
96        }
97    }
98
99    fn compose_option<Target: OctetsBuilder + ?Sized>(
100        &self,
101        target: &mut Target,
102    ) -> Result<(), Target::AppendError> {
103        match self.0 {
104            Some(v) => v.compose(target),
105            None => Ok(()),
106        }
107    }
108}
109
110//--- Display
111
112impl fmt::Display for TcpKeepalive {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        match self.0 {
115            Some(v) => write!(f, "{}", v),
116            None => write!(f, ""),
117        }
118    }
119}
120
121//--- Extended Opt and OptBuilder
122
123impl<Octs: Octets> Opt<Octs> {
124    /// Returns the first edns-tcp-keepalive option if present.
125    ///
126    /// This option is used to signal a timeout to keep a TCP connection
127    /// open.
128    pub fn tcp_keepalive(&self) -> Option<TcpKeepalive> {
129        self.first()
130    }
131}
132
133impl<Target: Composer> OptBuilder<'_, Target> {
134    pub fn tcp_keepalive(
135        &mut self,
136        timeout: Option<IdleTimeout>,
137    ) -> Result<(), Target::AppendError> {
138        self.push(&TcpKeepalive::new(timeout))
139    }
140}
141
142//------------ IdleTimeout ---------------------------------------------------
143
144/// The idle timeout value of a [`TcpKeepalive`] option.
145///
146/// This value is a `u16` carrying a time in units of 100 milliseconds. The
147/// type provides means to conver the value into its raw `u16` value or into
148/// a [`Duration`] value.
149#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
150#[cfg_attr(feature = "serde", derive(serde::Serialize))]
151pub struct IdleTimeout(u16);
152
153impl IdleTimeout {
154    /// The length in octets of the wire format.
155    const COMPOSE_LEN: u16 = 2;
156
157    /// Parses a value from its wire format.
158    fn parse<Octs: AsRef<[u8]> + ?Sized>(
159        parser: &mut Parser<'_, Octs>,
160    ) -> Result<Self, ParseError> {
161        u16::parse(parser).map(Self)
162    }
163
164    /// Appends a value in wire format to a target.
165    fn compose<Target: OctetsBuilder + ?Sized>(
166        &self,
167        target: &mut Target,
168    ) -> Result<(), Target::AppendError> {
169        self.0.compose(target)
170    }
171}
172
173//--- From and TryFrom
174
175impl From<u16> for IdleTimeout {
176    fn from(src: u16) -> Self {
177        Self(src)
178    }
179}
180
181impl From<IdleTimeout> for u16 {
182    fn from(src: IdleTimeout) -> u16 {
183        src.0
184    }
185}
186
187impl TryFrom<Duration> for IdleTimeout {
188    type Error = FromDurationError;
189
190    fn try_from(duration: Duration) -> Result<Self, Self::Error> {
191        Ok(Self(
192            u16::try_from(
193                duration
194                    .as_secs()
195                    .checked_mul(10)
196                    .ok_or(FromDurationError(()))?
197                    + u64::from(duration.subsec_millis() / 100),
198            )
199            .map_err(|_| FromDurationError(()))?,
200        ))
201    }
202}
203
204impl From<IdleTimeout> for Duration {
205    fn from(src: IdleTimeout) -> Self {
206        Duration::from_millis(u64::from(src.0) * 100)
207    }
208}
209
210//--- Display
211
212impl fmt::Display for IdleTimeout {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        self.0.fmt(f)
215    }
216}
217
218//------------ FromDurationError ---------------------------------------------
219
220/// A duration value was too large to convert into a idle timeout.
221#[derive(Clone, Copy, Debug)]
222pub struct FromDurationError(());
223
224impl fmt::Display for FromDurationError {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        f.write_str("duration too large")
227    }
228}
229
230#[cfg(feature = "std")]
231impl std::error::Error for FromDurationError {}
232
233//============ Testing =======================================================
234
235#[cfg(test)]
236#[cfg(all(feature = "std", feature = "bytes"))]
237mod test {
238    use super::super::test::test_option_compose_parse;
239    use super::*;
240
241    #[test]
242    #[allow(clippy::redundant_closure)] // lifetimes ...
243    fn tcp_keepalive_compose_parse_none() {
244        test_option_compose_parse(&TcpKeepalive::new(None), |parser| {
245            TcpKeepalive::parse(parser)
246        });
247    }
248
249    #[test]
250    #[allow(clippy::redundant_closure)] // lifetimes ...
251    fn tcp_keepalive_compose_parse_some() {
252        test_option_compose_parse(
253            &TcpKeepalive::new(Some(12.into())),
254            |parser| TcpKeepalive::parse(parser),
255        );
256    }
257}