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