bbx_net/websocket/
connection.rs1use std::time::Instant;
4
5use crate::address::NodeId;
6
7#[derive(Debug, Clone)]
9pub struct ConnectionState {
10 pub node_id: NodeId,
12 pub room_code: String,
14 pub client_name: Option<String>,
16 pub connected_at: Instant,
18 pub last_ping: Instant,
20 pub latency_us: u64,
22 pub reconnect_count: u32,
24 pub clock_offset: i64,
26}
27
28impl ConnectionState {
29 pub fn new(node_id: NodeId, room_code: String, client_name: Option<String>) -> Self {
31 let now = Instant::now();
32 Self {
33 node_id,
34 room_code,
35 client_name,
36 connected_at: now,
37 last_ping: now,
38 latency_us: 0,
39 reconnect_count: 0,
40 clock_offset: 0,
41 }
42 }
43
44 pub fn update_latency(&mut self, rtt_us: u64) {
50 self.latency_us = rtt_us / 2;
51 self.last_ping = Instant::now();
52 }
53
54 pub fn update_clock_offset(
59 &mut self,
60 client_send: u64,
61 server_receive: u64,
62 server_send: u64,
63 client_receive: u64,
64 ) {
65 let t1 = client_send as i64;
66 let t2 = server_receive as i64;
67 let t3 = server_send as i64;
68 let t4 = client_receive as i64;
69
70 self.clock_offset = ((t2 - t1) + (t3 - t4)) / 2;
71 self.latency_us = ((t4 - t1) - (t3 - t2)) as u64 / 2;
72 self.last_ping = Instant::now();
73 }
74
75 pub fn is_stale(&self, timeout: std::time::Duration) -> bool {
77 Instant::now().duration_since(self.last_ping) > timeout
78 }
79
80 pub fn mark_reconnected(&mut self) {
82 self.reconnect_count += 1;
83 self.last_ping = Instant::now();
84 }
85
86 pub fn client_to_server_time(&self, client_time: u64) -> u64 {
88 (client_time as i64 - self.clock_offset) as u64
89 }
90
91 pub fn server_to_client_time(&self, server_time: u64) -> u64 {
93 (server_time as i64 + self.clock_offset) as u64
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_connection_state_new() {
103 let node_id = NodeId::from_parts(1, 2);
104 let state = ConnectionState::new(node_id, "123456".to_string(), None);
105
106 assert_eq!(state.node_id, node_id);
107 assert_eq!(state.room_code, "123456");
108 assert_eq!(state.reconnect_count, 0);
109 assert_eq!(state.latency_us, 0);
110 }
111
112 #[test]
113 fn test_update_latency() {
114 let node_id = NodeId::from_parts(1, 2);
115 let mut state = ConnectionState::new(node_id, "123456".to_string(), None);
116
117 state.update_latency(20000);
118
119 assert_eq!(state.latency_us, 10000);
120 }
121
122 #[test]
123 fn test_update_clock_offset() {
124 let node_id = NodeId::from_parts(1, 2);
125 let mut state = ConnectionState::new(node_id, "123456".to_string(), None);
126
127 state.update_clock_offset(100, 200, 200, 300);
128
129 assert_eq!(state.clock_offset, 0);
130 }
131
132 #[test]
133 fn test_clock_offset_client_ahead() {
134 let node_id = NodeId::from_parts(1, 2);
135 let mut state = ConnectionState::new(node_id, "123456".to_string(), None);
136
137 state.update_clock_offset(200, 100, 100, 200);
138
139 assert!(state.clock_offset < 0);
140 }
141
142 #[test]
143 fn test_mark_reconnected() {
144 let node_id = NodeId::from_parts(1, 2);
145 let mut state = ConnectionState::new(node_id, "123456".to_string(), None);
146
147 state.mark_reconnected();
148 state.mark_reconnected();
149
150 assert_eq!(state.reconnect_count, 2);
151 }
152
153 #[test]
154 fn test_time_conversion() {
155 let node_id = NodeId::from_parts(1, 2);
156 let mut state = ConnectionState::new(node_id, "123456".to_string(), None);
157
158 state.clock_offset = 100;
159
160 let server_time = state.client_to_server_time(1000);
161 assert_eq!(server_time, 900);
162
163 let client_time = state.server_to_client_time(900);
164 assert_eq!(client_time, 1000);
165 }
166
167 #[test]
168 fn test_is_stale() {
169 use std::time::Duration;
170
171 let node_id = NodeId::from_parts(1, 2);
172 let state = ConnectionState::new(node_id, "123456".to_string(), None);
173
174 assert!(!state.is_stale(Duration::from_secs(60)));
175 }
176}