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).
1011use 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;
202122//------------ TcpKeepalive --------------------------------------------------
2324/// 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>);
3435impl TcpKeepalive {
36/// Creates a new value from an optional idle timeout.
37#[must_use]
38pub fn new(timeout: Option<IdleTimeout>) -> Self {
39 TcpKeepalive(timeout)
40 }
4142/// Returns the idle timeout.
43#[must_use]
44pub fn timeout(self) -> Option<IdleTimeout> {
45self.0
46}
4748/// Parses an option data value from its wire format.
49pub fn parse<Octs: AsRef<[u8]>>(
50 parser: &mut Parser<Octs>
51 ) -> Result<Self, ParseError> {
52if parser.remaining() == 0 {
53Ok(Self::new(None))
54 } else {
55 IdleTimeout::parse(parser).map(|v| Self::new(Some(v)))
56 }
57 }
58}
5960//--- OptData
6162impl OptData for TcpKeepalive {
63fn code(&self) -> OptionCode {
64 OptionCode::TcpKeepalive
65 }
66}
6768impl<'a, Octs: AsRef<[u8]>> ParseOptData<'a, Octs> for TcpKeepalive {
69fn parse_option(
70 code: OptionCode,
71 parser: &mut Parser<'a, Octs>,
72 ) -> Result<Option<Self>, ParseError> {
73if code == OptionCode::TcpKeepalive {
74Self::parse(parser).map(Some)
75 }
76else {
77Ok(None)
78 }
79 }
80}
8182impl ComposeOptData for TcpKeepalive {
83fn compose_len(&self) -> u16 {
84match self.0 {
85Some(_) => IdleTimeout::COMPOSE_LEN,
86None => 0,
87 }
88 }
8990fn compose_option<Target: OctetsBuilder + ?Sized>(
91&self, target: &mut Target
92 ) -> Result<(), Target::AppendError> {
93match self.0 {
94Some(v) => v.compose(target),
95None => Ok(()),
96 }
97 }
98}
99100//--- Display
101102impl fmt::Display for TcpKeepalive {
103fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
104match self.0 {
105Some(v) => write!(f, "{}", v),
106None => write!(f, ""),
107 }
108 }
109}
110111//--- Extended Opt and OptBuilder
112113impl<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.
118pub fn tcp_keepalive(&self) -> Option<TcpKeepalive> {
119self.first()
120 }
121}
122123impl<'a, Target: Composer> OptBuilder<'a, Target> {
124pub fn tcp_keepalive(
125&mut self, timeout: Option<IdleTimeout>
126 ) -> Result<(), Target::AppendError> {
127self.push(&TcpKeepalive::new(timeout))
128 }
129}
130131132//------------ IdleTimeout ---------------------------------------------------
133134/// 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);
141142impl IdleTimeout {
143/// The length in octets of the wire format.
144const COMPOSE_LEN: u16 = 2;
145146/// Parses a value from its wire format.
147fn parse<Octs: AsRef<[u8]>>(
148 parser: &mut Parser<Octs>
149 ) -> Result<Self, ParseError> {
150 u16::parse(parser).map(Self)
151 }
152153/// Appends a value in wire format to a target.
154fn compose<Target: OctetsBuilder + ?Sized>(
155&self, target: &mut Target
156 ) -> Result<(), Target::AppendError> {
157self.0.compose(target)
158 }
159}
160161//--- From and TryFrom
162163impl From<u16> for IdleTimeout {
164fn from(src: u16) -> Self {
165Self(src)
166 }
167}
168169impl From<IdleTimeout> for u16 {
170fn from(src: IdleTimeout) -> u16 {
171 src.0
172}
173}
174175impl TryFrom<Duration> for IdleTimeout {
176type Error = FromDurationError;
177178fn try_from(duration: Duration) -> Result<Self, Self::Error> {
179Ok(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}
189190impl From<IdleTimeout> for Duration {
191fn from(src: IdleTimeout) -> Self {
192 Duration::from_millis(u64::from(src.0) * 100)
193 }
194}
195196//--- Display
197198impl fmt::Display for IdleTimeout {
199fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
200self.0.fmt(f)
201 }
202}
203204205//------------ FromDurationError ---------------------------------------------
206207/// A duration value was too large to convert into a idle timeout.
208#[derive(Clone, Copy, Debug)]
209pub struct FromDurationError(());
210211impl fmt::Display for FromDurationError {
212fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213 f.write_str("duration too large")
214 }
215}
216217#[cfg(feature = "std")]
218impl std::error::Error for FromDurationError { }
219220221//============ Testing =======================================================
222223#[cfg(test)]
224#[cfg(all(feature = "std", feature = "bytes"))]
225mod test {
226use super::*;
227use super::super::test::test_option_compose_parse;
228229#[test]
230 #[allow(clippy::redundant_closure)] // lifetimes ...
231fn tcp_keepalive_compose_parse_none() {
232 test_option_compose_parse(
233&TcpKeepalive::new(None),
234 |parser| TcpKeepalive::parse(parser)
235 );
236 }
237238#[test]
239 #[allow(clippy::redundant_closure)] // lifetimes ...
240fn 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