1use crate::utils::clock::Instant;
4use std::collections::HashMap;
5use std::net::SocketAddr;
6use std::path::PathBuf;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
10pub struct ClientId(u32);
11
12impl ClientId {
13 pub fn new(id: u32) -> Self {
15 Self(id)
16 }
17
18 pub fn as_u32(&self) -> u32 {
20 self.0
21 }
22
23 pub fn from_string(s: &str) -> Option<Self> {
25 let s = s.trim();
26 let id_str = s.strip_prefix("client-").unwrap_or(s);
27 id_str.parse::<u32>().ok().map(Self)
28 }
29}
30
31impl std::fmt::Display for ClientId {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 write!(f, "client-{}", self.0)
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum ClientStatus {
40 Connecting,
42 Connected,
44 Disconnected,
46 Error(String),
48}
49
50impl ClientStatus {
51 pub fn as_str(&self) -> &str {
52 match self {
53 Self::Connecting => "Connecting",
54 Self::Connected => "Connected",
55 Self::Disconnected => "Disconnected",
56 Self::Error(_) => "Error",
57 }
58 }
59}
60
61impl std::fmt::Display for ClientStatus {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 Self::Connecting => write!(f, "Connecting"),
65 Self::Connected => write!(f, "Connected"),
66 Self::Disconnected => write!(f, "Disconnected"),
67 Self::Error(msg) => write!(f, "Error: {}", msg),
68 }
69 }
70}
71
72#[derive(Debug, Clone)]
74pub struct ClientConnectionState {
75 pub id: ClientId,
77 pub remote_addr: String,
79 pub connected_addr: Option<SocketAddr>,
81 pub local_addr: Option<SocketAddr>,
83 pub bytes_sent: u64,
85 pub bytes_received: u64,
87 pub packets_sent: u64,
89 pub packets_received: u64,
91 pub last_activity: Instant,
93 pub status: ClientStatus,
95 pub status_changed_at: Instant,
97 pub protocol_info: super::server::ProtocolConnectionInfo,
99}
100
101pub const CLIENT_CONNECTION_HISTORY_CAPACITY: usize = 20;
104
105#[derive(Debug, Clone, serde::Serialize)]
109pub struct ClientConnectionAttempt {
110 pub remote_addr: String,
111 pub started_unix_ms: u64,
112 pub ended_unix_ms: Option<u64>,
114 pub outcome: String,
116}
117
118fn now_unix_ms() -> u64 {
119 crate::utils::clock::SystemTime::now()
120 .duration_since(crate::utils::clock::UNIX_EPOCH)
121 .map(|d| d.as_millis() as u64)
122 .unwrap_or(0)
123}
124
125#[derive(Debug, Clone)]
130pub struct ClientInstance {
131 pub id: ClientId,
133 pub remote_addr: String,
135 pub protocol_name: String,
137 pub instruction: String,
139 pub memory: String,
141 pub status: ClientStatus,
143 pub connection: Option<ClientConnectionState>,
145 pub created_at: Instant,
147 pub status_changed_at: Instant,
149 pub startup_params: Option<serde_json::Value>,
151 pub event_handler_config: Option<crate::scripting::EventHandlerConfig>,
153 pub protocol_data: serde_json::Value,
159 pub log_files: HashMap<String, PathBuf>,
161 pub feedback_instructions: Option<String>,
164 pub feedback_buffer: Vec<serde_json::Value>,
167 pub last_feedback_processed: Option<Instant>,
169 pub connection_history: std::collections::VecDeque<ClientConnectionAttempt>,
173}
174
175impl ClientInstance {
176 pub fn new(
178 id: ClientId,
179 remote_addr: String,
180 protocol_name: String,
181 instruction: String,
182 ) -> Self {
183 let now = Instant::now();
184 Self {
185 id,
186 remote_addr,
187 protocol_name,
188 instruction,
189 memory: String::new(),
190 status: ClientStatus::Connecting,
191 connection: None,
192 created_at: now,
193 status_changed_at: now,
194 startup_params: None,
195 event_handler_config: None,
196 protocol_data: serde_json::Value::Object(serde_json::Map::new()),
197 log_files: HashMap::new(),
198 feedback_instructions: None,
199 feedback_buffer: Vec::new(),
200 last_feedback_processed: None,
201 connection_history: std::collections::VecDeque::new(),
202 }
203 }
204
205 pub fn record_status_transition(&mut self, status: &ClientStatus) {
209 let live_tail = self
210 .connection_history
211 .back_mut()
212 .filter(|attempt| attempt.ended_unix_ms.is_none());
213 match status {
214 ClientStatus::Connecting => {
215 if let Some(attempt) = live_tail {
218 attempt.ended_unix_ms = Some(now_unix_ms());
219 }
220 self.connection_history.push_back(ClientConnectionAttempt {
221 remote_addr: self.remote_addr.clone(),
222 started_unix_ms: now_unix_ms(),
223 ended_unix_ms: None,
224 outcome: "connecting".to_string(),
225 });
226 let excess = self
227 .connection_history
228 .len()
229 .saturating_sub(CLIENT_CONNECTION_HISTORY_CAPACITY);
230 for _ in 0..excess {
231 self.connection_history.pop_front();
232 }
233 }
234 ClientStatus::Connected => {
235 if let Some(attempt) = live_tail {
236 attempt.outcome = "connected".to_string();
237 } else {
238 self.connection_history.push_back(ClientConnectionAttempt {
242 remote_addr: self.remote_addr.clone(),
243 started_unix_ms: now_unix_ms(),
244 ended_unix_ms: None,
245 outcome: "connected".to_string(),
246 });
247 }
248 }
249 ClientStatus::Disconnected => {
250 if let Some(attempt) = live_tail {
251 attempt.ended_unix_ms = Some(now_unix_ms());
252 attempt.outcome = "disconnected".to_string();
253 }
254 }
255 ClientStatus::Error(message) => {
256 if let Some(attempt) = live_tail {
257 attempt.ended_unix_ms = Some(now_unix_ms());
258 attempt.outcome = format!("error: {message}");
259 }
260 }
261 }
262 }
263
264 pub fn get_protocol_data<T: serde::de::DeserializeOwned>(
266 &self,
267 ) -> Result<T, serde_json::Error> {
268 serde_json::from_value(self.protocol_data.clone())
269 }
270
271 pub fn set_protocol_data<T: serde::Serialize>(
273 &mut self,
274 data: T,
275 ) -> Result<(), serde_json::Error> {
276 self.protocol_data = serde_json::to_value(data)?;
277 Ok(())
278 }
279
280 pub fn get_protocol_field(&self, key: &str) -> Option<&serde_json::Value> {
282 self.protocol_data.get(key)
283 }
284
285 pub fn set_protocol_field(&mut self, key: String, value: serde_json::Value) {
287 if let Some(obj) = self.protocol_data.as_object_mut() {
288 obj.insert(key, value);
289 } else {
290 let mut map = serde_json::Map::new();
292 map.insert(key, value);
293 self.protocol_data = serde_json::Value::Object(map);
294 }
295 }
296
297 pub fn get_or_create_log_path(&mut self, output_name: &str) -> anyhow::Result<PathBuf> {
304 crate::state::server::validate_log_output_name(output_name)?;
305 if let Some(path) = self.log_files.get(output_name) {
306 return Ok(path.clone());
307 }
308
309 let now = crate::utils::clock::SystemTime::now();
312 let elapsed = self.created_at.elapsed();
313 let created_system_time = now - elapsed;
314
315 let timestamp = crate::utils::clock::to_local_datetime(created_system_time);
317 let timestamp_str = timestamp.format("%Y_%m_%d_%H_%M_%S").to_string();
318
319 let log_filename = format!("netget_{}_{}.log", output_name, timestamp_str);
320 let log_path = PathBuf::from(log_filename);
321
322 self.log_files
323 .insert(output_name.to_string(), log_path.clone());
324 Ok(log_path)
325 }
326
327 pub fn summary(&self) -> String {
329 let duration = self.created_at.elapsed();
330 let duration_str = if duration.as_secs() < 60 {
331 format!("{}s", duration.as_secs())
332 } else if duration.as_secs() < 3600 {
333 format!("{}m", duration.as_secs() / 60)
334 } else {
335 format!("{}h", duration.as_secs() / 3600)
336 };
337
338 format!(
339 "#{} {} → {} ({}) - {}",
340 self.id.as_u32(),
341 self.protocol_name,
342 self.remote_addr,
343 self.status.as_str(),
344 duration_str
345 )
346 }
347}