use serde::{Deserialize, Serialize};
use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Direction {
Uplink,
Downlink,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PacketMetadata {
pub direction: Direction,
pub size: u16,
pub iat_ms: f64,
pub entropy: f32,
pub header_prefix: Vec<u8>,
pub timestamp_ns: u64,
}
#[derive(Debug, Clone, Default)]
pub struct RunningStats {
pub uplink_count: u64,
pub uplink_sum: u64,
pub uplink_sum_sq: u64,
pub downlink_count: u64,
pub downlink_sum: u64,
pub downlink_sum_sq: u64,
pub uplink_iat_sum: f64,
pub uplink_iat_sum_sq: f64,
pub downlink_iat_sum: f64,
pub downlink_iat_sum_sq: f64,
pub entropy_sum: f64,
pub entropy_count: u64,
}
impl RunningStats {
pub fn update(&mut self, meta: &PacketMetadata) {
match meta.direction {
Direction::Uplink => {
self.uplink_count += 1;
self.uplink_sum += meta.size as u64;
self.uplink_sum_sq += (meta.size as u64).pow(2);
self.uplink_iat_sum += meta.iat_ms;
self.uplink_iat_sum_sq += meta.iat_ms.powi(2);
}
Direction::Downlink => {
self.downlink_count += 1;
self.downlink_sum += meta.size as u64;
self.downlink_sum_sq += (meta.size as u64).pow(2);
self.downlink_iat_sum += meta.iat_ms;
self.downlink_iat_sum_sq += meta.iat_ms.powi(2);
}
}
self.entropy_count += 1;
self.entropy_sum += meta.entropy as f64;
}
pub fn mean_entropy(&self) -> f64 {
if self.entropy_count == 0 {
return 0.0;
}
self.entropy_sum / self.entropy_count as f64
}
}
pub struct RecordingSession {
pub session_id: [u8; 16],
pub service: String,
pub admin_key_id: String,
pub started_at: Instant,
pub last_packet_at: Instant,
pub packets: Vec<PacketMetadata>,
pub total_packets: u64,
pub running_stats: RunningStats,
}
pub const MAX_RECORDING_PACKETS: usize = 100_000;
pub const MIN_RECORDING_PACKETS: u64 = 500;
pub const MIN_RECORDING_DURATION_SECS: u64 = 60;
pub const RECORDING_IDLE_TIMEOUT_SECS: u64 = 15;
impl RecordingSession {
pub fn new(session_id: [u8; 16], service: String, admin_key_id: String) -> Self {
Self {
session_id,
service,
admin_key_id,
started_at: Instant::now(),
last_packet_at: Instant::now(),
packets: Vec::with_capacity(50_000),
total_packets: 0,
running_stats: RunningStats::default(),
}
}
pub fn record(&mut self, meta: PacketMetadata) {
if self.packets.len() < MAX_RECORDING_PACKETS {
self.packets.push(meta.clone());
}
self.total_packets += 1;
self.last_packet_at = Instant::now();
self.running_stats.update(&meta);
}
pub fn has_enough_data(&self) -> bool {
self.total_packets >= MIN_RECORDING_PACKETS
&& self.started_at.elapsed().as_secs() >= MIN_RECORDING_DURATION_SECS
}
pub fn duration_secs(&self) -> u64 {
self.started_at.elapsed().as_secs()
}
pub fn is_idle_timed_out(&self, idle_timeout_secs: u64) -> bool {
self.last_packet_at.elapsed().as_secs() >= idle_timeout_secs
}
}