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