1use std::fmt;
8use std::time::SystemTime;
9
10use bytes::Bytes;
11use serde::{Deserialize, Serialize};
12
13use crate::types::{Alert, LogEntry, PacketInfo, Severity};
14
15pub const MODULE_EBPF: &str = "ebpf-engine";
19pub const MODULE_LOG_PIPELINE: &str = "log-pipeline";
21pub const MODULE_CONTAINER_GUARD: &str = "container-guard";
23pub const MODULE_SBOM_SCANNER: &str = "sbom-scanner";
25
26pub const EVENT_TYPE_PACKET: &str = "packet";
30pub const EVENT_TYPE_LOG: &str = "log";
32pub const EVENT_TYPE_ALERT: &str = "alert";
34pub const EVENT_TYPE_ACTION: &str = "action";
36pub const EVENT_TYPE_SCAN: &str = "scan";
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct EventMetadata {
45 pub timestamp: SystemTime,
47 pub source_module: String,
49 pub trace_id: String,
51}
52
53impl EventMetadata {
54 pub fn new(source_module: impl Into<String>, trace_id: impl Into<String>) -> Self {
58 Self {
59 timestamp: SystemTime::now(),
60 source_module: source_module.into(),
61 trace_id: trace_id.into(),
62 }
63 }
64
65 pub fn with_new_trace(source_module: impl Into<String>) -> Self {
69 Self {
70 timestamp: SystemTime::now(),
71 source_module: source_module.into(),
72 trace_id: uuid::Uuid::new_v4().to_string(),
73 }
74 }
75}
76
77impl fmt::Display for EventMetadata {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(
80 f,
81 "[{}] source={} trace={}",
82 unix_timestamp_str(self.timestamp),
83 self.source_module,
84 self.trace_id,
85 )
86 }
87}
88
89pub trait Event: Send + Sync + 'static {
95 fn event_id(&self) -> &str;
97
98 fn metadata(&self) -> &EventMetadata;
100
101 fn event_type(&self) -> &str;
103}
104
105#[derive(Debug, Clone)]
110pub struct PacketEvent {
111 pub id: String,
113 pub metadata: EventMetadata,
115 pub packet_info: PacketInfo,
117 pub raw_data: Bytes,
119}
120
121impl PacketEvent {
122 pub fn new(packet_info: PacketInfo, raw_data: Bytes) -> Self {
124 Self {
125 id: uuid::Uuid::new_v4().to_string(),
126 metadata: EventMetadata::with_new_trace(MODULE_EBPF),
127 packet_info,
128 raw_data,
129 }
130 }
131
132 pub fn with_trace(
134 packet_info: PacketInfo,
135 raw_data: Bytes,
136 trace_id: impl Into<String>,
137 ) -> Self {
138 Self {
139 id: uuid::Uuid::new_v4().to_string(),
140 metadata: EventMetadata::new(MODULE_EBPF, trace_id),
141 packet_info,
142 raw_data,
143 }
144 }
145}
146
147impl Event for PacketEvent {
148 fn event_id(&self) -> &str {
149 &self.id
150 }
151
152 fn metadata(&self) -> &EventMetadata {
153 &self.metadata
154 }
155
156 fn event_type(&self) -> &str {
157 EVENT_TYPE_PACKET
158 }
159}
160
161impl fmt::Display for PacketEvent {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 write!(
164 f,
165 "PacketEvent[{}] {}:{} -> {}:{} proto={} size={}",
166 &self.id[..8.min(self.id.len())],
167 self.packet_info.src_ip,
168 self.packet_info.src_port,
169 self.packet_info.dst_ip,
170 self.packet_info.dst_port,
171 self.packet_info.protocol,
172 self.packet_info.size,
173 )
174 }
175}
176
177#[derive(Debug, Clone)]
181pub struct LogEvent {
182 pub id: String,
184 pub metadata: EventMetadata,
186 pub entry: LogEntry,
188}
189
190impl LogEvent {
191 pub fn new(entry: LogEntry) -> Self {
193 Self {
194 id: uuid::Uuid::new_v4().to_string(),
195 metadata: EventMetadata::with_new_trace(MODULE_LOG_PIPELINE),
196 entry,
197 }
198 }
199
200 pub fn with_trace(entry: LogEntry, trace_id: impl Into<String>) -> Self {
202 Self {
203 id: uuid::Uuid::new_v4().to_string(),
204 metadata: EventMetadata::new(MODULE_LOG_PIPELINE, trace_id),
205 entry,
206 }
207 }
208}
209
210impl Event for LogEvent {
211 fn event_id(&self) -> &str {
212 &self.id
213 }
214
215 fn metadata(&self) -> &EventMetadata {
216 &self.metadata
217 }
218
219 fn event_type(&self) -> &str {
220 EVENT_TYPE_LOG
221 }
222}
223
224impl fmt::Display for LogEvent {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 write!(
227 f,
228 "LogEvent[{}] source={} host={} severity={}",
229 &self.id[..8.min(self.id.len())],
230 self.entry.source,
231 self.entry.hostname,
232 self.entry.severity,
233 )
234 }
235}
236
237#[derive(Debug, Clone)]
241pub struct AlertEvent {
242 pub id: String,
244 pub metadata: EventMetadata,
246 pub alert: Alert,
248 pub severity: Severity,
250}
251
252impl AlertEvent {
253 pub fn new(alert: Alert, severity: Severity) -> Self {
257 Self {
258 id: uuid::Uuid::new_v4().to_string(),
259 metadata: EventMetadata::with_new_trace(MODULE_LOG_PIPELINE),
260 alert,
261 severity,
262 }
263 }
264
265 pub fn with_source(alert: Alert, severity: Severity, source_module: &'static str) -> Self {
276 Self {
277 id: uuid::Uuid::new_v4().to_string(),
278 metadata: EventMetadata::with_new_trace(source_module),
279 alert,
280 severity,
281 }
282 }
283
284 pub fn with_trace(alert: Alert, severity: Severity, trace_id: impl Into<String>) -> Self {
288 Self {
289 id: uuid::Uuid::new_v4().to_string(),
290 metadata: EventMetadata::new(MODULE_LOG_PIPELINE, trace_id),
291 alert,
292 severity,
293 }
294 }
295}
296
297impl Event for AlertEvent {
298 fn event_id(&self) -> &str {
299 &self.id
300 }
301
302 fn metadata(&self) -> &EventMetadata {
303 &self.metadata
304 }
305
306 fn event_type(&self) -> &str {
307 EVENT_TYPE_ALERT
308 }
309}
310
311impl fmt::Display for AlertEvent {
312 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313 write!(
314 f,
315 "AlertEvent[{}] rule={} severity={} title={}",
316 &self.id[..8.min(self.id.len())],
317 self.alert.rule_name,
318 self.severity,
319 self.alert.title,
320 )
321 }
322}
323
324#[derive(Debug, Clone)]
328pub struct ActionEvent {
329 pub id: String,
331 pub metadata: EventMetadata,
333 pub action_type: String,
335 pub target: String,
337 pub success: bool,
339}
340
341impl ActionEvent {
342 pub fn new(action_type: impl Into<String>, target: impl Into<String>, success: bool) -> Self {
344 Self {
345 id: uuid::Uuid::new_v4().to_string(),
346 metadata: EventMetadata::with_new_trace(MODULE_CONTAINER_GUARD),
347 action_type: action_type.into(),
348 target: target.into(),
349 success,
350 }
351 }
352
353 pub fn with_trace(
355 action_type: impl Into<String>,
356 target: impl Into<String>,
357 success: bool,
358 trace_id: impl Into<String>,
359 ) -> Self {
360 Self {
361 id: uuid::Uuid::new_v4().to_string(),
362 metadata: EventMetadata::new(MODULE_CONTAINER_GUARD, trace_id),
363 action_type: action_type.into(),
364 target: target.into(),
365 success,
366 }
367 }
368}
369
370impl Event for ActionEvent {
371 fn event_id(&self) -> &str {
372 &self.id
373 }
374
375 fn metadata(&self) -> &EventMetadata {
376 &self.metadata
377 }
378
379 fn event_type(&self) -> &str {
380 EVENT_TYPE_ACTION
381 }
382}
383
384impl fmt::Display for ActionEvent {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 let status = if self.success { "OK" } else { "FAILED" };
387 write!(
388 f,
389 "ActionEvent[{}] type={} target={} status={}",
390 &self.id[..8.min(self.id.len())],
391 self.action_type,
392 self.target,
393 status,
394 )
395 }
396}
397
398fn unix_timestamp_str(time: SystemTime) -> String {
400 match time.duration_since(SystemTime::UNIX_EPOCH) {
401 Ok(duration) => {
402 let secs = duration.as_secs();
403 format!("{secs}")
404 }
405 Err(_) => "unknown".to_owned(),
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use std::net::IpAddr;
413
414 fn sample_packet_info() -> PacketInfo {
415 PacketInfo {
416 src_ip: "192.168.1.1".parse::<IpAddr>().unwrap(),
417 dst_ip: "10.0.0.1".parse::<IpAddr>().unwrap(),
418 src_port: 12345,
419 dst_port: 80,
420 protocol: 6,
421 size: 1500,
422 timestamp: SystemTime::now(),
423 }
424 }
425
426 fn sample_log_entry() -> LogEntry {
427 LogEntry {
428 source: "/var/log/syslog".to_owned(),
429 timestamp: SystemTime::now(),
430 hostname: "server-01".to_owned(),
431 process: "sshd".to_owned(),
432 message: "Failed password for root".to_owned(),
433 severity: Severity::High,
434 fields: vec![("pid".to_owned(), "1234".to_owned())],
435 }
436 }
437
438 fn sample_alert() -> Alert {
439 Alert {
440 id: "alert-001".to_owned(),
441 title: "Brute force detected".to_owned(),
442 description: "Multiple failed SSH login attempts".to_owned(),
443 severity: Severity::High,
444 rule_name: "ssh_brute_force".to_owned(),
445 source_ip: Some("192.168.1.100".parse().unwrap()),
446 target_ip: Some("10.0.0.1".parse().unwrap()),
447 created_at: SystemTime::now(),
448 }
449 }
450
451 #[test]
452 fn event_metadata_new_preserves_trace_id() {
453 let meta = EventMetadata::new("test-module", "trace-abc-123");
454 assert_eq!(meta.source_module, "test-module");
455 assert_eq!(meta.trace_id, "trace-abc-123");
456 assert!(meta.timestamp <= SystemTime::now());
457 }
458
459 #[test]
460 fn event_metadata_with_new_trace_generates_uuid() {
461 let meta = EventMetadata::with_new_trace("test-module");
462 assert_eq!(meta.source_module, "test-module");
463 assert!(!meta.trace_id.is_empty());
464 assert_eq!(meta.trace_id.len(), 36);
466 assert_eq!(meta.trace_id.chars().filter(|c| *c == '-').count(), 4);
467 }
468
469 #[test]
470 fn event_metadata_display() {
471 let meta = EventMetadata::new("ebpf-engine", "trace-xyz");
472 let display = meta.to_string();
473 assert!(display.contains("ebpf-engine"));
474 assert!(display.contains("trace-xyz"));
475 }
476
477 #[test]
478 fn packet_event_implements_event_trait() {
479 let event = PacketEvent::new(sample_packet_info(), Bytes::from_static(b"raw-data"));
480 assert_eq!(event.event_type(), "packet");
481 assert!(!event.event_id().is_empty());
482 assert_eq!(event.metadata().source_module, "ebpf-engine");
483 }
484
485 #[test]
486 fn packet_event_with_trace_preserves_trace_id() {
487 let event = PacketEvent::with_trace(
488 sample_packet_info(),
489 Bytes::from_static(b"data"),
490 "my-trace-id",
491 );
492 assert_eq!(event.metadata().trace_id, "my-trace-id");
493 }
494
495 #[test]
496 fn packet_event_display() {
497 let event = PacketEvent::new(sample_packet_info(), Bytes::from_static(b"data"));
498 let display = event.to_string();
499 assert!(display.contains("192.168.1.1"));
500 assert!(display.contains("10.0.0.1"));
501 assert!(display.contains("PacketEvent"));
502 }
503
504 #[test]
505 fn log_event_implements_event_trait() {
506 let event = LogEvent::new(sample_log_entry());
507 assert_eq!(event.event_type(), "log");
508 assert!(!event.event_id().is_empty());
509 assert_eq!(event.metadata().source_module, "log-pipeline");
510 }
511
512 #[test]
513 fn log_event_with_trace() {
514 let event = LogEvent::with_trace(sample_log_entry(), "existing-trace");
515 assert_eq!(event.metadata().trace_id, "existing-trace");
516 }
517
518 #[test]
519 fn alert_event_implements_event_trait() {
520 let event = AlertEvent::new(sample_alert(), Severity::High);
521 assert_eq!(event.event_type(), "alert");
522 assert_eq!(event.severity, Severity::High);
523 assert!(!event.event_id().is_empty());
524 }
525
526 #[test]
527 fn alert_event_display() {
528 let event = AlertEvent::new(sample_alert(), Severity::High);
529 let display = event.to_string();
530 assert!(display.contains("ssh_brute_force"));
531 assert!(display.contains("High"));
532 }
533
534 #[test]
535 fn action_event_implements_event_trait() {
536 let event = ActionEvent::new("container_isolate", "container-abc", true);
537 assert_eq!(event.event_type(), "action");
538 assert_eq!(event.action_type, "container_isolate");
539 assert_eq!(event.target, "container-abc");
540 assert!(event.success);
541 }
542
543 #[test]
544 fn action_event_with_trace() {
545 let event = ActionEvent::with_trace("block_ip", "192.168.1.100", false, "trace-from-alert");
546 assert_eq!(event.metadata().trace_id, "trace-from-alert");
547 assert!(!event.success);
548 }
549
550 #[test]
551 fn action_event_display_success() {
552 let event = ActionEvent::new("container_isolate", "abc", true);
553 assert!(event.to_string().contains("OK"));
554 }
555
556 #[test]
557 fn action_event_display_failure() {
558 let event = ActionEvent::new("container_isolate", "abc", false);
559 assert!(event.to_string().contains("FAILED"));
560 }
561
562 #[test]
563 fn events_are_send_sync() {
564 fn assert_send_sync<T: Send + Sync + 'static>() {}
565 assert_send_sync::<PacketEvent>();
566 assert_send_sync::<LogEvent>();
567 assert_send_sync::<AlertEvent>();
568 assert_send_sync::<ActionEvent>();
569 }
570}