mqtt5_protocol/
keepalive.rs

1use crate::time::Duration;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub struct KeepaliveConfig {
5    pub ping_interval_percent: u8,
6    pub timeout_percent: u8,
7}
8
9impl Default for KeepaliveConfig {
10    fn default() -> Self {
11        Self {
12            ping_interval_percent: 75,
13            timeout_percent: 150,
14        }
15    }
16}
17
18impl KeepaliveConfig {
19    #[must_use]
20    pub const fn new(ping_interval_percent: u8, timeout_percent: u8) -> Self {
21        Self {
22            ping_interval_percent,
23            timeout_percent,
24        }
25    }
26
27    #[must_use]
28    pub const fn conservative() -> Self {
29        Self {
30            ping_interval_percent: 50,
31            timeout_percent: 150,
32        }
33    }
34
35    #[must_use]
36    #[allow(
37        clippy::cast_possible_truncation,
38        clippy::cast_sign_loss,
39        clippy::cast_precision_loss
40    )]
41    pub fn ping_interval(&self, keepalive: Duration) -> Duration {
42        let millis = keepalive.as_millis() as u64;
43        let ping_millis = millis * u64::from(self.ping_interval_percent) / 100;
44        Duration::from_millis(ping_millis)
45    }
46
47    #[must_use]
48    #[allow(
49        clippy::cast_possible_truncation,
50        clippy::cast_sign_loss,
51        clippy::cast_precision_loss
52    )]
53    pub fn timeout_duration(&self, keepalive: Duration) -> Duration {
54        let millis = keepalive.as_millis() as u64;
55        let timeout_millis = millis * u64::from(self.timeout_percent) / 100;
56        Duration::from_millis(timeout_millis)
57    }
58}
59
60#[must_use]
61#[allow(clippy::cast_possible_truncation)]
62pub fn calculate_ping_interval(keepalive: Duration, percent: u8) -> Duration {
63    let millis = keepalive.as_millis() as u64;
64    let ping_millis = millis * u64::from(percent) / 100;
65    Duration::from_millis(ping_millis)
66}
67
68#[must_use]
69pub fn is_keepalive_timeout(
70    time_since_last_ping: Duration,
71    last_pong_received: bool,
72    keepalive: Duration,
73    timeout_percent: u8,
74) -> bool {
75    let config = KeepaliveConfig::new(0, timeout_percent);
76    let timeout = config.timeout_duration(keepalive);
77    !last_pong_received && time_since_last_ping > timeout
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_default_config() {
86        let config = KeepaliveConfig::default();
87        assert_eq!(config.ping_interval_percent, 75);
88        assert_eq!(config.timeout_percent, 150);
89    }
90
91    #[test]
92    fn test_conservative_config() {
93        let config = KeepaliveConfig::conservative();
94        assert_eq!(config.ping_interval_percent, 50);
95        assert_eq!(config.timeout_percent, 150);
96    }
97
98    #[test]
99    fn test_ping_interval_calculation() {
100        let config = KeepaliveConfig::default();
101        let keepalive = Duration::from_secs(60);
102        let ping_interval = config.ping_interval(keepalive);
103        assert_eq!(ping_interval, Duration::from_secs(45));
104    }
105
106    #[test]
107    fn test_ping_interval_50_percent() {
108        let config = KeepaliveConfig::conservative();
109        let keepalive = Duration::from_secs(60);
110        let ping_interval = config.ping_interval(keepalive);
111        assert_eq!(ping_interval, Duration::from_secs(30));
112    }
113
114    #[test]
115    fn test_timeout_duration() {
116        let config = KeepaliveConfig::default();
117        let keepalive = Duration::from_secs(60);
118        let timeout = config.timeout_duration(keepalive);
119        assert_eq!(timeout, Duration::from_secs(90));
120    }
121
122    #[test]
123    fn test_calculate_ping_interval_function() {
124        let keepalive = Duration::from_secs(60);
125        assert_eq!(
126            calculate_ping_interval(keepalive, 75),
127            Duration::from_secs(45)
128        );
129        assert_eq!(
130            calculate_ping_interval(keepalive, 50),
131            Duration::from_secs(30)
132        );
133    }
134
135    #[test]
136    fn test_is_keepalive_timeout_no_pong() {
137        let keepalive = Duration::from_secs(60);
138        let time_since_ping = Duration::from_secs(100);
139        assert!(is_keepalive_timeout(time_since_ping, false, keepalive, 150));
140    }
141
142    #[test]
143    fn test_is_keepalive_timeout_with_pong() {
144        let keepalive = Duration::from_secs(60);
145        let time_since_ping = Duration::from_secs(100);
146        assert!(!is_keepalive_timeout(time_since_ping, true, keepalive, 150));
147    }
148
149    #[test]
150    fn test_is_keepalive_timeout_not_expired() {
151        let keepalive = Duration::from_secs(60);
152        let time_since_ping = Duration::from_secs(80);
153        assert!(!is_keepalive_timeout(
154            time_since_ping,
155            false,
156            keepalive,
157            150
158        ));
159    }
160}