1use std::net::IpAddr;
4
5use clickhouse::Row;
6use serde::{Deserialize, Serialize};
7use time::OffsetDateTime;
8
9#[derive(Debug, Clone, Row, Serialize, Deserialize)]
11pub struct ConnectionEvent {
12 #[serde(with = "clickhouse::serde::time::datetime64::millis")]
15 pub timestamp: OffsetDateTime,
16
17 pub duration_ms: u64,
19
20 pub conn_id: u64,
23
24 pub peer_ip: IpAddr,
26
27 pub peer_port: u16,
29
30 pub user_id: String,
33
34 pub auth_result: AuthResult,
36
37 pub target_type: TargetType,
40
41 pub target_host: String,
43
44 pub target_port: u16,
46
47 pub sni: String,
49
50 pub bytes_sent: u64,
53
54 pub bytes_recv: u64,
56
57 pub packets_sent: u64,
59
60 pub packets_recv: u64,
62
63 pub protocol: Protocol,
66
67 pub transport: Transport,
69
70 pub close_reason: CloseReason,
72
73 pub is_fallback: bool,
75
76 pub peer_country: String,
79
80 pub peer_region: String,
82
83 pub peer_city: String,
85
86 pub peer_asn: u32,
88
89 pub peer_org: String,
91
92 pub peer_longitude: f64,
94
95 pub peer_latitude: f64,
97
98 pub server_id: String,
101}
102
103impl ConnectionEvent {
104 pub fn new(conn_id: u64, peer_ip: IpAddr, peer_port: u16) -> Self {
106 Self {
107 timestamp: OffsetDateTime::now_utc(),
108 duration_ms: 0,
109 conn_id,
110 peer_ip,
111 peer_port,
112 user_id: String::new(),
113 auth_result: AuthResult::Skipped,
114 target_type: TargetType::Domain,
115 target_host: String::new(),
116 target_port: 0,
117 sni: String::new(),
118 bytes_sent: 0,
119 bytes_recv: 0,
120 packets_sent: 0,
121 packets_recv: 0,
122 protocol: Protocol::Tcp,
123 transport: Transport::Direct,
124 close_reason: CloseReason::Normal,
125 is_fallback: false,
126 peer_country: String::new(),
127 peer_region: String::new(),
128 peer_city: String::new(),
129 peer_asn: 0,
130 peer_org: String::new(),
131 peer_longitude: 0.0,
132 peer_latitude: 0.0,
133 server_id: String::new(),
134 }
135 }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
140#[serde(rename_all = "snake_case")]
141pub enum AuthResult {
142 Success,
144 Failed,
146 Skipped,
148}
149
150impl From<AuthResult> for &'static str {
151 fn from(r: AuthResult) -> Self {
152 match r {
153 AuthResult::Success => "success",
154 AuthResult::Failed => "failed",
155 AuthResult::Skipped => "skipped",
156 }
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
162#[serde(rename_all = "snake_case")]
163pub enum TargetType {
164 Ipv4,
166 Ipv6,
168 Domain,
170}
171
172impl From<TargetType> for &'static str {
173 fn from(t: TargetType) -> Self {
174 match t {
175 TargetType::Ipv4 => "ipv4",
176 TargetType::Ipv6 => "ipv6",
177 TargetType::Domain => "domain",
178 }
179 }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
184#[serde(rename_all = "snake_case")]
185pub enum Protocol {
186 Tcp,
188 Udp,
190}
191
192impl From<Protocol> for &'static str {
193 fn from(p: Protocol) -> Self {
194 match p {
195 Protocol::Tcp => "tcp",
196 Protocol::Udp => "udp",
197 }
198 }
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
203#[serde(rename_all = "snake_case")]
204pub enum Transport {
205 Direct,
207 WebSocket,
209}
210
211impl From<Transport> for &'static str {
212 fn from(t: Transport) -> Self {
213 match t {
214 Transport::Direct => "direct",
215 Transport::WebSocket => "websocket",
216 }
217 }
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
222#[serde(rename_all = "snake_case")]
223pub enum CloseReason {
224 Normal,
226 Timeout,
228 Error,
230 Reset,
232 ServerShutdown,
234}
235
236impl From<CloseReason> for &'static str {
237 fn from(r: CloseReason) -> Self {
238 match r {
239 CloseReason::Normal => "normal",
240 CloseReason::Timeout => "timeout",
241 CloseReason::Error => "error",
242 CloseReason::Reset => "reset",
243 CloseReason::ServerShutdown => "shutdown",
244 }
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use std::net::{IpAddr, Ipv4Addr};
252
253 #[test]
254 fn connection_event_new_defaults() {
255 let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
256 let event = ConnectionEvent::new(42, ip, 8080);
257
258 assert_eq!(event.conn_id, 42);
259 assert_eq!(event.peer_ip, ip);
260 assert_eq!(event.peer_port, 8080);
261 assert_eq!(event.duration_ms, 0);
262 assert!(event.user_id.is_empty());
263 assert_eq!(event.auth_result, AuthResult::Skipped);
264 assert_eq!(event.target_type, TargetType::Domain);
265 assert!(event.target_host.is_empty());
266 assert_eq!(event.target_port, 0);
267 assert_eq!(event.bytes_sent, 0);
268 assert_eq!(event.bytes_recv, 0);
269 assert_eq!(event.protocol, Protocol::Tcp);
270 assert_eq!(event.transport, Transport::Direct);
271 assert_eq!(event.close_reason, CloseReason::Normal);
272 assert!(!event.is_fallback);
273 assert!(event.peer_country.is_empty());
275 assert!(event.peer_region.is_empty());
276 assert!(event.peer_city.is_empty());
277 assert_eq!(event.peer_asn, 0);
278 assert!(event.peer_org.is_empty());
279 assert_eq!(event.peer_longitude, 0.0);
280 assert_eq!(event.peer_latitude, 0.0);
281 assert!(event.server_id.is_empty());
282 }
283
284 #[test]
285 fn enum_into_str() {
286 let s: &str = AuthResult::Success.into();
287 assert_eq!(s, "success");
288 let s: &str = AuthResult::Failed.into();
289 assert_eq!(s, "failed");
290 let s: &str = AuthResult::Skipped.into();
291 assert_eq!(s, "skipped");
292
293 let s: &str = TargetType::Ipv4.into();
294 assert_eq!(s, "ipv4");
295 let s: &str = TargetType::Ipv6.into();
296 assert_eq!(s, "ipv6");
297 let s: &str = TargetType::Domain.into();
298 assert_eq!(s, "domain");
299
300 let s: &str = Protocol::Tcp.into();
301 assert_eq!(s, "tcp");
302 let s: &str = Protocol::Udp.into();
303 assert_eq!(s, "udp");
304
305 let s: &str = Transport::Direct.into();
306 assert_eq!(s, "direct");
307 let s: &str = Transport::WebSocket.into();
308 assert_eq!(s, "websocket");
309
310 let s: &str = CloseReason::Normal.into();
311 assert_eq!(s, "normal");
312 let s: &str = CloseReason::Timeout.into();
313 assert_eq!(s, "timeout");
314 let s: &str = CloseReason::Error.into();
315 assert_eq!(s, "error");
316 let s: &str = CloseReason::Reset.into();
317 assert_eq!(s, "reset");
318 let s: &str = CloseReason::ServerShutdown.into();
319 assert_eq!(s, "shutdown");
320 }
321
322 #[test]
323 fn enum_serde_roundtrip() {
324 let auth = AuthResult::Failed;
325 let json = serde_json::to_string(&auth).unwrap();
326 assert_eq!(json, "\"failed\"");
327 let back: AuthResult = serde_json::from_str(&json).unwrap();
328 assert_eq!(back, AuthResult::Failed);
329
330 let proto = Protocol::Udp;
331 let json = serde_json::to_string(&proto).unwrap();
332 assert_eq!(json, "\"udp\"");
333 let back: Protocol = serde_json::from_str(&json).unwrap();
334 assert_eq!(back, Protocol::Udp);
335
336 let transport = Transport::WebSocket;
337 let json = serde_json::to_string(&transport).unwrap();
338 assert_eq!(json, "\"web_socket\"");
339 let back: Transport = serde_json::from_str(&json).unwrap();
340 assert_eq!(back, Transport::WebSocket);
341 }
342}