use std::time::Duration;
pub const KEEP_ALIVE_INTERVAL_MIN: Duration = Duration::from_secs(15);
pub const KEEP_ALIVE_INTERVAL_MAX: Duration = Duration::from_secs(30);
pub const KEEP_ALIVE_RESPONSE_DEADLINE: Duration = Duration::from_secs(20);
pub const DEAD_SOCKET_TIME: Duration = Duration::from_secs(20);
pub fn ms_since(timestamp_ms: u64) -> Option<u64> {
ms_since_at(timestamp_ms, now_ms())
}
pub fn ms_since_at(timestamp_ms: u64, now_ms: u64) -> Option<u64> {
if timestamp_ms == 0 {
return None;
}
Some(now_ms.saturating_sub(timestamp_ms))
}
pub fn now_ms() -> u64 {
crate::time::now_millis().max(0) as u64
}
pub fn is_dead_socket(armed_ms: u64, last_received_ms: u64) -> bool {
is_dead_socket_at(armed_ms, last_received_ms, now_ms())
}
pub fn is_dead_socket_at(armed_ms: u64, last_received_ms: u64, now_ms: u64) -> bool {
if armed_ms == 0 {
return false;
}
if last_received_ms >= armed_ms {
return false;
}
ms_since_at(armed_ms, now_ms)
.map(|elapsed| elapsed > DEAD_SOCKET_TIME.as_millis() as u64)
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ms_since_never_set() {
assert_eq!(ms_since(0), None);
}
#[test]
fn ms_since_recent() {
let now_ms = crate::time::now_millis().max(0) as u64;
let elapsed = ms_since(now_ms).unwrap();
assert!(elapsed < 100, "should be near-zero, got {elapsed}ms");
}
#[test]
fn ms_since_stale() {
let thirty_sec_ago = (crate::time::now_millis().max(0) as u64).saturating_sub(30_000);
let elapsed = ms_since(thirty_sec_ago).unwrap();
assert!(
(29_000..=31_000).contains(&elapsed),
"should be ~30s, got {elapsed}ms"
);
}
#[test]
fn dead_socket_never_sent() {
assert!(!is_dead_socket(0, 0));
}
#[test]
fn dead_socket_received_after_send() {
let t = crate::time::now_millis().max(0) as u64;
assert!(!is_dead_socket(t, t + 1));
}
#[test]
fn dead_socket_sent_recently() {
let now = crate::time::now_millis().max(0) as u64;
assert!(!is_dead_socket(now, 0));
}
#[test]
fn dead_socket_sent_long_ago_no_reply() {
let thirty_ago = (crate::time::now_millis().max(0) as u64).saturating_sub(30_000);
assert!(is_dead_socket(thirty_ago, 0));
}
#[test]
fn dead_socket_sent_long_ago_old_reply() {
let thirty_ago = (crate::time::now_millis().max(0) as u64).saturating_sub(30_000);
let thirty_one_ago = thirty_ago.saturating_sub(1_000);
assert!(is_dead_socket(thirty_ago, thirty_one_ago));
}
#[test]
fn dead_socket_sent_long_ago_recent_reply() {
let thirty_ago = (crate::time::now_millis().max(0) as u64).saturating_sub(30_000);
let one_ago = (crate::time::now_millis().max(0) as u64).saturating_sub(1_000);
assert!(!is_dead_socket(thirty_ago, one_ago));
}
#[test]
fn dead_socket_boundary_is_exact() {
let armed = 1_000_000u64;
let dead_at = armed + DEAD_SOCKET_TIME.as_millis() as u64;
assert!(!is_dead_socket_at(armed, 0, dead_at));
assert!(is_dead_socket_at(armed, 0, dead_at + 1));
}
#[test]
fn continued_sends_do_not_hide_a_silent_socket() {
let stats = crate::stats::SessionStats::new();
stats.record_frame_sent(10);
let armed = stats.first_send_since_recv_ms();
for _ in 0..100 {
stats.record_frame_sent(10);
}
assert_eq!(stats.first_send_since_recv_ms(), armed);
let now = armed + DEAD_SOCKET_TIME.as_millis() as u64 + 1;
assert!(is_dead_socket_at(armed, 0, now));
}
#[test]
fn constants_match_wa_web() {
assert_eq!(KEEP_ALIVE_INTERVAL_MIN, Duration::from_secs(15));
assert_eq!(KEEP_ALIVE_INTERVAL_MAX, Duration::from_secs(30));
assert_eq!(DEAD_SOCKET_TIME, Duration::from_secs(20));
}
}