Skip to main content

aivpn_common/
recording.rs

1//! Auto Mask Recording — Shared Data Models
2//!
3//! Packet metadata, recording sessions, and incremental statistics
4//! for automatic mask generation from live traffic analysis.
5
6use serde::{Deserialize, Serialize};
7use std::time::Instant;
8
9/// Direction of packet flow
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum Direction {
12    Uplink,
13    Downlink,
14}
15
16/// Metadata extracted from a single packet (no payload — privacy-safe)
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct PacketMetadata {
19    /// Packet direction (uplink or downlink)
20    pub direction: Direction,
21    /// Total packet size in bytes
22    pub size: u16,
23    /// Inter-arrival time from previous packet in milliseconds
24    pub iat_ms: f64,
25    /// Shannon entropy of packet payload (0.0–8.0)
26    pub entropy: f32,
27    /// First 16 bytes of the mask-dependent header
28    pub header_prefix: Vec<u8>,
29    /// Monotonic timestamp in nanoseconds
30    pub timestamp_ns: u64,
31}
32
33/// Running statistics computed incrementally (O(1) per update)
34#[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    /// Update statistics with a new packet metadata entry
52    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    /// Get mean entropy across all recorded packets
74    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
82/// Active recording session on the server
83pub struct RecordingSession {
84    /// Unique session ID (from VPN session)
85    pub session_id: [u8; 16],
86    /// Service name being recorded (e.g. "yandex_telemost")
87    pub service: String,
88    /// Admin key ID that authorized this recording
89    pub admin_key_id: String,
90    /// When recording started
91    pub started_at: Instant,
92    /// When the most recent packet was captured
93    pub last_packet_at: Instant,
94    /// Collected packet metadata (capped at MAX_RECORDING_PACKETS)
95    pub packets: Vec<PacketMetadata>,
96    /// Total packets observed (may exceed stored packets)
97    pub total_packets: u64,
98    /// Incremental statistics (always up to date)
99    pub running_stats: RunningStats,
100}
101
102/// Maximum packets stored in a single recording session
103pub const MAX_RECORDING_PACKETS: usize = 100_000;
104
105/// Minimum packets required for mask generation
106pub const MIN_RECORDING_PACKETS: u64 = 500;
107
108/// Minimum recording duration in seconds
109pub const MIN_RECORDING_DURATION_SECS: u64 = 60;
110
111/// Idle timeout after which an inactive recording is auto-finished
112pub const RECORDING_IDLE_TIMEOUT_SECS: u64 = 15;
113
114impl RecordingSession {
115    /// Create a new recording session
116    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    /// Record a packet's metadata
130    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    /// Check if we have enough data for mask generation
140    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    /// Get recording duration in seconds
146    pub fn duration_secs(&self) -> u64 {
147        self.started_at.elapsed().as_secs()
148    }
149
150    /// Check whether the session has been idle long enough to auto-finish.
151    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}