aivpn_common/
recording.rs1use serde::{Deserialize, Serialize};
7use std::time::Instant;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum Direction {
12 Uplink,
13 Downlink,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct PacketMetadata {
19 pub direction: Direction,
21 pub size: u16,
23 pub iat_ms: f64,
25 pub entropy: f32,
27 pub header_prefix: Vec<u8>,
29 pub timestamp_ns: u64,
31}
32
33#[derive(Debug, Clone, Default)]
35pub struct RunningStats {
36 pub uplink_count: u64,
37 pub uplink_sum: u64,
38 pub uplink_sum_sq: u64,
39 pub downlink_count: u64,
40 pub downlink_sum: u64,
41 pub downlink_sum_sq: u64,
42 pub uplink_iat_sum: f64,
43 pub uplink_iat_sum_sq: f64,
44 pub downlink_iat_sum: f64,
45 pub downlink_iat_sum_sq: f64,
46 pub entropy_sum: f64,
47 pub entropy_count: u64,
48}
49
50impl RunningStats {
51 pub fn update(&mut self, meta: &PacketMetadata) {
53 match meta.direction {
54 Direction::Uplink => {
55 self.uplink_count += 1;
56 self.uplink_sum += meta.size as u64;
57 self.uplink_sum_sq += (meta.size as u64).pow(2);
58 self.uplink_iat_sum += meta.iat_ms;
59 self.uplink_iat_sum_sq += meta.iat_ms.powi(2);
60 }
61 Direction::Downlink => {
62 self.downlink_count += 1;
63 self.downlink_sum += meta.size as u64;
64 self.downlink_sum_sq += (meta.size as u64).pow(2);
65 self.downlink_iat_sum += meta.iat_ms;
66 self.downlink_iat_sum_sq += meta.iat_ms.powi(2);
67 }
68 }
69 self.entropy_count += 1;
70 self.entropy_sum += meta.entropy as f64;
71 }
72
73 pub fn mean_entropy(&self) -> f64 {
75 if self.entropy_count == 0 {
76 return 0.0;
77 }
78 self.entropy_sum / self.entropy_count as f64
79 }
80}
81
82pub struct RecordingSession {
84 pub session_id: [u8; 16],
86 pub service: String,
88 pub admin_key_id: String,
90 pub started_at: Instant,
92 pub last_packet_at: Instant,
94 pub packets: Vec<PacketMetadata>,
96 pub total_packets: u64,
98 pub running_stats: RunningStats,
100}
101
102pub const MAX_RECORDING_PACKETS: usize = 100_000;
104
105pub const MIN_RECORDING_PACKETS: u64 = 500;
107
108pub const MIN_RECORDING_DURATION_SECS: u64 = 60;
110
111pub const RECORDING_IDLE_TIMEOUT_SECS: u64 = 15;
113
114impl RecordingSession {
115 pub fn new(session_id: [u8; 16], service: String, admin_key_id: String) -> Self {
117 Self {
118 session_id,
119 service,
120 admin_key_id,
121 started_at: Instant::now(),
122 last_packet_at: Instant::now(),
123 packets: Vec::with_capacity(50_000),
124 total_packets: 0,
125 running_stats: RunningStats::default(),
126 }
127 }
128
129 pub fn record(&mut self, meta: PacketMetadata) {
131 if self.packets.len() < MAX_RECORDING_PACKETS {
132 self.packets.push(meta.clone());
133 }
134 self.total_packets += 1;
135 self.last_packet_at = Instant::now();
136 self.running_stats.update(&meta);
137 }
138
139 pub fn has_enough_data(&self) -> bool {
141 self.total_packets >= MIN_RECORDING_PACKETS
142 && self.started_at.elapsed().as_secs() >= MIN_RECORDING_DURATION_SECS
143 }
144
145 pub fn duration_secs(&self) -> u64 {
147 self.started_at.elapsed().as_secs()
148 }
149
150 pub fn is_idle_timed_out(&self, idle_timeout_secs: u64) -> bool {
152 self.last_packet_at.elapsed().as_secs() >= idle_timeout_secs
153 }
154}