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)]
33pub struct TcpKeepalive(Option<IdleTimeout>);
34
35impl TcpKeepalive {
36    /// Creates a new value from an optional idle timeout.
37    #[must_use]
38    pub fn new(timeout: Option<IdleTimeout>) -> Self {
39        TcpKeepalive(timeout)
40    }
41
42    /// Returns the idle timeout.
43    #[must_use]
44    pub fn timeout(self) -> Option<IdleTimeout> {
45        self.0
46    }
47
48    /// Parses an option data value from its wire format.
49    pub fn parse<Octs: AsRef<[u8]>>(
50        parser: &mut Parser<Octs>
51    ) -> Result<Self, ParseError> {
52        if parser.remaining() == 0 {
53            Ok(Self::new(None))
54        } else {
55            IdleTimeout::parse(parser).map(|v| Self::new(Some(v)))
56        }
57    }
58}
59
60//--- OptData
61
62impl OptData for TcpKeepalive {
63    fn code(&self) -> OptionCode {
64        OptionCode::TcpKeepalive
65    }
66}
67
68impl<'a, Octs: AsRef<[u8]>> ParseOptData<'a, Octs> for TcpKeepalive {
69    fn parse_option(
70        code: OptionCode,
71        parser: &mut Parser<'a, Octs>,
72    ) -> Result<Option<Self>, ParseError> {
73        if code == OptionCode::TcpKeepalive {
74            Self::parse(parser).map(Some)
75        }
76        else {
77            Ok(None)
78        }
79    }
80}
81
82impl ComposeOptData for TcpKeepalive {
83    fn compose_len(&self) -> u16 {
84        match self.0 {
85            Some(_) => IdleTimeout::COMPOSE_LEN,
86            None => 0,
87        }
88    }
89
90    fn compose_option<Target: OctetsBuilder + ?Sized>(
91        &self, target: &mut Target
92    ) -> Result<(), Target::AppendError> {
93        match self.0 {
94            Some(v) => v.compose(target),
95            None => Ok(()),
96        }
97    }
98}
99
100//--- Display
101
102impl fmt::Display for TcpKeepalive {
103    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
104        match self.0 {
105            Some(v) => write!(f, "{}", v),
106            None => write!(f, ""),
107        }
108    }
109}
110
111//--- Extended Opt and OptBuilder
112
113impl<Octs: Octets> Opt<Octs> {
114    /// Returns the first edns-tcp-keepalive option if present.
115    ///
116    /// This option is used to signal a timeout to keep a TCP connection
117    /// open.
118    pub fn tcp_keepalive(&self) -> Option<TcpKeepalive> {
119        self.first()
120    }
121}
122
123impl<'a, Target: Composer> OptBuilder<'a, Target> {
124    pub fn tcp_keepalive(
125        &mut self, timeout: Option<IdleTimeout>
126    ) -> Result<(), Target::AppendError> {
127        self.push(&TcpKeepalive::new(timeout))
128    }
129}
130
131
132//------------ IdleTimeout ---------------------------------------------------
133
134/// The idle timeout value of a [`TcpKeepalive`] option.
135///
136/// This value is a `u16` carrying a time in units of 100 milliseconds. The
137/// type provides means to conver the value into its raw `u16` value or into
138/// a `Duration` value.
139#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
140pub struct IdleTimeout(u16);
141
142impl IdleTimeout {
143    /// The length in octets of the wire format.
144    const COMPOSE_LEN: u16 = 2;
145
146    /// Parses a value from its wire format.
147    fn parse<Octs: AsRef<[u8]>>(
148        parser: &mut Parser<Octs>
149    ) -> Result<Self, ParseError> {
150        u16::parse(parser).map(Self)
151    }
152
153    /// Appends a value in wire format to a target.
154    fn compose<Target: OctetsBuilder + ?Sized>(
155        &self, target: &mut Target
156    ) -> Result<(), Target::AppendError> {
157        self.0.compose(target)
158    }
159}
160
161//--- From and TryFrom
162
163impl From<u16> for IdleTimeout {
164    fn from(src: u16) -> Self {
165        Self(src)
166    }
167}
168
169impl From<IdleTimeout> for u16 {
170    fn from(src: IdleTimeout) -> u16 {
171        src.0
172    }
173}
174
175impl TryFrom<Duration> for IdleTimeout {
176    type Error = FromDurationError;
177
178    fn try_from(duration: Duration) -> Result<Self, Self::Error> {
179        Ok(Self(
180            u16::try_from(
181                duration.as_secs().checked_mul(10).ok_or(
182                    FromDurationError(())
183                )?
184                + u64::from(duration.subsec_millis() / 100)
185            ).map_err(|_| FromDurationError(()))?
186        ))
187    }
188}
189
190impl From<IdleTimeout> for Duration {
191    fn from(src: IdleTimeout) -> Self {
192        Duration::from_millis(u64::from(src.0) * 100)
193    }
194}
195
196//--- Display
197
198impl fmt::Display for IdleTimeout {
199    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
200        self.0.fmt(f)
201    }
202}
203
204
205//------------ FromDurationError ---------------------------------------------
206
207/// A duration value was too large to convert into a idle timeout.
208#[derive(Clone, Copy, Debug)]
209pub struct FromDurationError(());
210
211impl fmt::Display for FromDurationError {
212    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213        f.write_str("duration too large")
214    }
215}
216
217#[cfg(feature = "std")]
218impl std::error::Error for FromDurationError { }
219
220
221//============ Testing =======================================================
222
223#[cfg(test)]
224#[cfg(all(feature = "std", feature = "bytes"))]
225mod test {
226    use super::*;
227    use super::super::test::test_option_compose_parse;
228    
229    #[test]
230    #[allow(clippy::redundant_closure)] // lifetimes ...
231    fn tcp_keepalive_compose_parse_none() {
232        test_option_compose_parse(
233            &TcpKeepalive::new(None),
234            |parser| TcpKeepalive::parse(parser)
235        );
236    }
237
238    #[test]
239    #[allow(clippy::redundant_closure)] // lifetimes ...
240    fn tcp_keepalive_compose_parse_some() {
241        test_option_compose_parse(
242            &TcpKeepalive::new(Some(12.into())),
243            |parser| TcpKeepalive::parse(parser)
244        );
245    }
246}
247