tokio_listener/
tcp_keepalive_params.rs

1use std::{fmt::Display, str::FromStr, time::Duration};
2
3#[cfg(feature = "socket_options")]
4#[cfg_attr(docsrs_alt, doc(cfg(feature = "socket_options")))]
5/// Value of `--tcp-keepalive` option.
6///
7/// When parsed from string, it expects 0 to 3 colon-separated numbers:
8///
9/// * Timeout (in milliseconds)
10/// * Number of failed pings before failing the connection
11/// * Interval of pings (in milliseconds)
12///
13/// Specifying empty string or "" just requests to enable keepalives without configuring parameters.
14///
15/// On unsupported platforms, all or some details of keepalives may be ignored.
16///
17/// Example:
18///
19/// ```
20/// use tokio_listener::TcpKeepaliveParams;
21/// let k1 : TcpKeepaliveParams = "60000:3:5000".parse().unwrap();
22/// let k2 : TcpKeepaliveParams = "60000".parse().unwrap();
23/// let k3 : TcpKeepaliveParams = "".parse().unwrap();
24///
25/// assert_eq!(k1, TcpKeepaliveParams{timeout_ms:Some(60000), count:Some(3), interval_ms:Some(5000)});
26/// ```
27#[cfg_attr(
28    feature = "serde",
29    derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
30)]
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
32pub struct TcpKeepaliveParams {
33    /// Amount of time after which TCP keepalive probes will be sent on idle connections.
34    pub timeout_ms: Option<u32>,
35    /// Maximum number of TCP keepalive probes that will be sent before dropping a connection.
36    pub count: Option<u32>,
37    /// Time interval between TCP keepalive probes.
38    pub interval_ms: Option<u32>,
39}
40#[cfg(feature = "socket_options")]
41impl Display for TcpKeepaliveParams {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        if let Some(x) = self.timeout_ms {
44            x.fmt(f)?;
45        }
46        ':'.fmt(f)?;
47        if let Some(x) = self.count {
48            x.fmt(f)?;
49        }
50        ':'.fmt(f)?;
51        if let Some(x) = self.interval_ms {
52            x.fmt(f)?;
53        }
54        Ok(())
55    }
56}
57#[cfg(feature = "socket_options")]
58impl FromStr for TcpKeepaliveParams {
59    type Err = &'static str;
60
61    #[allow(clippy::get_first)]
62    fn from_str(input_string: &str) -> Result<Self, Self::Err> {
63        let input_chunks: Vec<&str> = input_string.split(':').collect();
64        if input_chunks.len() > 3 {
65            return Err("Too many colon-separated chunks");
66        }
67        let timeout_chunk = input_chunks.get(0).unwrap_or(&"").trim();
68        let ping_count_chunk = input_chunks.get(1).unwrap_or(&"").trim();
69        let interval_chunk = input_chunks.get(2).unwrap_or(&"").trim();
70
71        let mut ka_params = TcpKeepaliveParams::default();
72
73        if !timeout_chunk.is_empty() {
74            ka_params.timeout_ms = Some(
75                timeout_chunk
76                    .parse()
77                    .map_err(|_| "failed to parse timeout as a number")?,
78            );
79        }
80        if !ping_count_chunk.is_empty() {
81            ka_params.count = Some(
82                ping_count_chunk
83                    .parse()
84                    .map_err(|_| "failed to parse count as a number")?,
85            );
86        }
87        if !interval_chunk.is_empty() {
88            ka_params.interval_ms = Some(
89                interval_chunk
90                    .parse()
91                    .map_err(|_| "failed to parse interval as a number")?,
92            );
93        }
94
95        Ok(ka_params)
96    }
97}
98#[cfg(feature = "socket_options")]
99impl TcpKeepaliveParams {
100    /// Attempt to convert values of this struct to socket2 format.
101    ///
102    /// Some fields may be ignored depending on platform.
103    #[must_use]
104    pub fn to_socket2(&self) -> socket2::TcpKeepalive {
105        let mut k = socket2::TcpKeepalive::new();
106
107        if let Some(x) = self.timeout_ms {
108            k = k.with_time(Duration::from_millis(u64::from(x)));
109        }
110
111        #[cfg(any(
112            target_os = "android",
113            target_os = "dragonfly",
114            target_os = "freebsd",
115            target_os = "fuchsia",
116            target_os = "illumos",
117            target_os = "ios",
118            target_os = "linux",
119            target_os = "macos",
120            target_os = "netbsd",
121            target_os = "tvos",
122            target_os = "watchos",
123        ))]
124        if let Some(x) = self.count {
125            k = k.with_retries(x);
126        }
127
128        #[cfg(any(
129            target_os = "android",
130            target_os = "dragonfly",
131            target_os = "freebsd",
132            target_os = "fuchsia",
133            target_os = "illumos",
134            target_os = "ios",
135            target_os = "linux",
136            target_os = "macos",
137            target_os = "netbsd",
138            target_os = "tvos",
139            target_os = "watchos",
140            target_os = "windows",
141        ))]
142        if let Some(x) = self.interval_ms {
143            k = k.with_interval(Duration::from_millis(u64::from(x)));
144        }
145
146        k
147    }
148}