ironfix_session/
heartbeat.rs1use std::time::{Duration, Instant};
15
16#[derive(Debug)]
18pub struct HeartbeatManager {
19 interval: Duration,
21 last_sent: Instant,
23 last_received: Instant,
25 test_request_pending: Option<String>,
27 test_request_sent_at: Option<Instant>,
29}
30
31impl HeartbeatManager {
32 #[must_use]
37 pub fn new(interval: Duration) -> Self {
38 let now = Instant::now();
39 Self {
40 interval,
41 last_sent: now,
42 last_received: now,
43 test_request_pending: None,
44 test_request_sent_at: None,
45 }
46 }
47
48 #[inline]
50 pub fn on_message_sent(&mut self) {
51 self.last_sent = Instant::now();
52 }
53
54 pub fn on_message_received(&mut self, is_heartbeat: bool, test_req_id: Option<&str>) {
63 self.last_received = Instant::now();
64
65 if is_heartbeat
66 && let (Some(pending), Some(received)) = (&self.test_request_pending, test_req_id)
67 && pending == received
68 {
69 self.test_request_pending = None;
70 self.test_request_sent_at = None;
71 }
72 }
73
74 #[must_use]
78 pub fn should_send_heartbeat(&self) -> bool {
79 self.last_sent.elapsed() >= self.interval
80 }
81
82 #[must_use]
87 pub fn should_send_test_request(&self) -> bool {
88 if self.test_request_pending.is_some() {
89 return false;
90 }
91
92 let grace = Duration::from_secs(1);
93 self.last_received.elapsed() >= self.interval + grace
94 }
95
96 #[must_use]
101 pub fn is_timed_out(&self) -> bool {
102 if let Some(sent_at) = self.test_request_sent_at {
103 sent_at.elapsed() >= self.interval
104 } else {
105 false
106 }
107 }
108
109 pub fn on_test_request_sent(&mut self, test_req_id: String) {
114 self.test_request_pending = Some(test_req_id);
115 self.test_request_sent_at = Some(Instant::now());
116 self.last_sent = Instant::now();
117 }
118
119 #[must_use]
121 pub fn pending_test_request(&self) -> Option<&str> {
122 self.test_request_pending.as_deref()
123 }
124
125 #[must_use]
127 pub fn time_since_last_received(&self) -> Duration {
128 self.last_received.elapsed()
129 }
130
131 #[must_use]
133 pub fn time_since_last_sent(&self) -> Duration {
134 self.last_sent.elapsed()
135 }
136
137 #[must_use]
139 pub const fn interval(&self) -> Duration {
140 self.interval
141 }
142
143 pub fn reset(&mut self) {
145 let now = Instant::now();
146 self.last_sent = now;
147 self.last_received = now;
148 self.test_request_pending = None;
149 self.test_request_sent_at = None;
150 }
151}
152
153#[must_use]
157pub fn generate_test_req_id() -> String {
158 use std::time::{SystemTime, UNIX_EPOCH};
159
160 let nanos = SystemTime::now()
161 .duration_since(UNIX_EPOCH)
162 .unwrap_or_default()
163 .as_nanos();
164
165 format!("TEST{}", nanos)
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use std::thread::sleep;
172
173 #[test]
174 fn test_heartbeat_manager_new() {
175 let mgr = HeartbeatManager::new(Duration::from_secs(30));
176 assert_eq!(mgr.interval(), Duration::from_secs(30));
177 assert!(mgr.pending_test_request().is_none());
178 }
179
180 #[test]
181 fn test_should_send_heartbeat() {
182 let mgr = HeartbeatManager::new(Duration::from_millis(10));
183 assert!(!mgr.should_send_heartbeat());
184
185 sleep(Duration::from_millis(15));
186 assert!(mgr.should_send_heartbeat());
187 }
188
189 #[test]
190 fn test_on_message_sent() {
191 let mut mgr = HeartbeatManager::new(Duration::from_millis(10));
192 sleep(Duration::from_millis(15));
193 assert!(mgr.should_send_heartbeat());
194
195 mgr.on_message_sent();
196 assert!(!mgr.should_send_heartbeat());
197 }
198
199 #[test]
200 fn test_test_request_pending() {
201 let mut mgr = HeartbeatManager::new(Duration::from_secs(30));
202
203 mgr.on_test_request_sent("TEST123".to_string());
204 assert_eq!(mgr.pending_test_request(), Some("TEST123"));
205
206 mgr.on_message_received(true, Some("TEST123"));
207 assert!(mgr.pending_test_request().is_none());
208 }
209
210 #[test]
211 fn test_generate_test_req_id() {
212 let id1 = generate_test_req_id();
213 std::thread::sleep(std::time::Duration::from_nanos(1));
214 let id2 = generate_test_req_id();
215
216 assert!(id1.starts_with("TEST"));
217 assert!(id2.starts_with("TEST"));
218 assert!(id1.len() > 4);
221 assert!(id2.len() > 4);
222 }
223}