ipfrs_network/
background_mode.rs

1//! Background mode support with pause/resume functionality
2//!
3//! This module provides functionality to pause and resume network operations
4//! when an application goes to the background (e.g., on mobile devices).
5//! This helps save battery and network resources while the app is not active.
6
7use parking_lot::RwLock;
8use std::sync::Arc;
9use std::time::{Duration, Instant};
10use thiserror::Error;
11use tracing::{debug, info};
12
13/// Background mode state
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum BackgroundState {
16    /// Network is running normally (foreground)
17    Active,
18    /// Network is paused (background)
19    Paused,
20    /// Network is in the process of pausing
21    Pausing,
22    /// Network is in the process of resuming
23    Resuming,
24}
25
26/// Configuration for background mode behavior
27#[derive(Debug, Clone)]
28pub struct BackgroundModeConfig {
29    /// Whether to pause DHT queries in background
30    pub pause_dht_queries: bool,
31    /// Whether to pause provider announcements in background
32    pub pause_provider_announcements: bool,
33    /// Whether to close idle connections when entering background
34    pub close_idle_connections: bool,
35    /// Minimum connection age to be considered for closing (when close_idle_connections is true)
36    pub idle_connection_threshold: Duration,
37    /// Whether to maintain a minimal set of connections in background
38    pub keep_minimal_connections: bool,
39    /// Number of connections to keep when in background mode
40    pub minimal_connection_count: usize,
41    /// Whether to reduce DHT query frequency in background
42    pub reduce_dht_frequency: bool,
43    /// DHT query interval when in background (if reduce_dht_frequency is true)
44    pub background_dht_interval: Duration,
45}
46
47impl Default for BackgroundModeConfig {
48    fn default() -> Self {
49        Self {
50            pause_dht_queries: false,
51            pause_provider_announcements: true,
52            close_idle_connections: true,
53            idle_connection_threshold: Duration::from_secs(300), // 5 minutes
54            keep_minimal_connections: true,
55            minimal_connection_count: 3,
56            reduce_dht_frequency: true,
57            background_dht_interval: Duration::from_secs(300), // 5 minutes
58        }
59    }
60}
61
62impl BackgroundModeConfig {
63    /// Mobile configuration - aggressive power saving
64    pub fn mobile() -> Self {
65        Self {
66            pause_dht_queries: true, // Pause all DHT queries
67            pause_provider_announcements: true,
68            close_idle_connections: true,
69            idle_connection_threshold: Duration::from_secs(60), // 1 minute
70            keep_minimal_connections: true,
71            minimal_connection_count: 2, // Keep only 2 connections
72            reduce_dht_frequency: true,
73            background_dht_interval: Duration::from_secs(600), // 10 minutes
74        }
75    }
76
77    /// Server configuration - minimal impact
78    pub fn server() -> Self {
79        Self {
80            pause_dht_queries: false,
81            pause_provider_announcements: false,
82            close_idle_connections: false,
83            idle_connection_threshold: Duration::from_secs(3600), // 1 hour
84            keep_minimal_connections: false,
85            minimal_connection_count: 0,
86            reduce_dht_frequency: false,
87            background_dht_interval: Duration::from_secs(60),
88        }
89    }
90
91    /// Balanced configuration
92    pub fn balanced() -> Self {
93        Self::default()
94    }
95}
96
97/// Background mode manager
98pub struct BackgroundModeManager {
99    config: BackgroundModeConfig,
100    state: Arc<RwLock<BackgroundState>>,
101    stats: Arc<RwLock<BackgroundModeStats>>,
102    /// Time when last state transition occurred
103    last_transition: Arc<RwLock<Option<Instant>>>,
104    /// Time spent in each state
105    time_in_active: Arc<RwLock<Duration>>,
106    time_in_paused: Arc<RwLock<Duration>>,
107}
108
109/// Background mode statistics
110#[derive(Debug, Clone, Default)]
111pub struct BackgroundModeStats {
112    /// Number of times the network was paused
113    pub pause_count: usize,
114    /// Number of times the network was resumed
115    pub resume_count: usize,
116    /// Total time spent in background mode
117    pub total_background_time: Duration,
118    /// Total time spent in foreground mode
119    pub total_foreground_time: Duration,
120    /// Number of connections closed when entering background
121    pub connections_closed_on_pause: usize,
122    /// Number of DHT queries skipped in background
123    pub dht_queries_skipped: usize,
124}
125
126/// Errors that can occur during background mode operations
127#[derive(Debug, Error)]
128pub enum BackgroundModeError {
129    #[error("Invalid state transition from {from:?} to {to:?}")]
130    InvalidStateTransition {
131        from: BackgroundState,
132        to: BackgroundState,
133    },
134
135    #[error("Operation not allowed in current state: {0:?}")]
136    OperationNotAllowed(BackgroundState),
137}
138
139impl BackgroundModeManager {
140    /// Create a new background mode manager
141    pub fn new(config: BackgroundModeConfig) -> Self {
142        Self {
143            config,
144            state: Arc::new(RwLock::new(BackgroundState::Active)),
145            stats: Arc::new(RwLock::new(BackgroundModeStats::default())),
146            last_transition: Arc::new(RwLock::new(Some(Instant::now()))),
147            time_in_active: Arc::new(RwLock::new(Duration::ZERO)),
148            time_in_paused: Arc::new(RwLock::new(Duration::ZERO)),
149        }
150    }
151
152    /// Get current background state
153    pub fn state(&self) -> BackgroundState {
154        *self.state.read()
155    }
156
157    /// Check if the network is paused
158    pub fn is_paused(&self) -> bool {
159        matches!(self.state(), BackgroundState::Paused)
160    }
161
162    /// Check if the network is active
163    pub fn is_active(&self) -> bool {
164        matches!(self.state(), BackgroundState::Active)
165    }
166
167    /// Pause network operations (enter background mode)
168    pub fn pause(&self) -> Result<(), BackgroundModeError> {
169        let current_state = *self.state.read();
170
171        match current_state {
172            BackgroundState::Active => {
173                info!("Pausing network for background mode");
174                *self.state.write() = BackgroundState::Pausing;
175
176                // Update time tracking
177                self.update_time_tracking(current_state);
178
179                // Perform pause operations
180                self.perform_pause_operations();
181
182                *self.state.write() = BackgroundState::Paused;
183                *self.last_transition.write() = Some(Instant::now());
184
185                let mut stats = self.stats.write();
186                stats.pause_count += 1;
187
188                debug!("Network paused successfully");
189                Ok(())
190            }
191            BackgroundState::Paused => {
192                // Already paused, no-op
193                Ok(())
194            }
195            state => Err(BackgroundModeError::InvalidStateTransition {
196                from: state,
197                to: BackgroundState::Paused,
198            }),
199        }
200    }
201
202    /// Resume network operations (enter foreground mode)
203    pub fn resume(&self) -> Result<(), BackgroundModeError> {
204        let current_state = *self.state.read();
205
206        match current_state {
207            BackgroundState::Paused => {
208                info!("Resuming network from background mode");
209                *self.state.write() = BackgroundState::Resuming;
210
211                // Update time tracking
212                self.update_time_tracking(current_state);
213
214                // Perform resume operations
215                self.perform_resume_operations();
216
217                *self.state.write() = BackgroundState::Active;
218                *self.last_transition.write() = Some(Instant::now());
219
220                let mut stats = self.stats.write();
221                stats.resume_count += 1;
222
223                debug!("Network resumed successfully");
224                Ok(())
225            }
226            BackgroundState::Active => {
227                // Already active, no-op
228                Ok(())
229            }
230            state => Err(BackgroundModeError::InvalidStateTransition {
231                from: state,
232                to: BackgroundState::Active,
233            }),
234        }
235    }
236
237    /// Perform operations when entering background mode
238    fn perform_pause_operations(&self) {
239        // These are hooks that the NetworkNode can use
240        debug!(
241            "Background mode config: pause_dht={}, pause_announcements={}, close_idle={}",
242            self.config.pause_dht_queries,
243            self.config.pause_provider_announcements,
244            self.config.close_idle_connections
245        );
246
247        // Actual implementation would be in NetworkNode
248        // which would call these methods and perform the operations
249    }
250
251    /// Perform operations when resuming from background mode
252    fn perform_resume_operations(&self) {
253        debug!("Resuming network operations");
254
255        // Actual implementation would be in NetworkNode
256        // which would call these methods and perform the operations
257    }
258
259    /// Update time tracking when state changes
260    fn update_time_tracking(&self, old_state: BackgroundState) {
261        if let Some(last_transition) = *self.last_transition.read() {
262            let elapsed = last_transition.elapsed();
263
264            match old_state {
265                BackgroundState::Active => {
266                    *self.time_in_active.write() += elapsed;
267                    let mut stats = self.stats.write();
268                    stats.total_foreground_time += elapsed;
269                }
270                BackgroundState::Paused => {
271                    *self.time_in_paused.write() += elapsed;
272                    let mut stats = self.stats.write();
273                    stats.total_background_time += elapsed;
274                }
275                _ => {}
276            }
277        }
278    }
279
280    /// Check if a DHT query should be allowed in current state
281    pub fn should_allow_dht_query(&self) -> bool {
282        match self.state() {
283            BackgroundState::Active | BackgroundState::Resuming => true,
284            BackgroundState::Paused | BackgroundState::Pausing => !self.config.pause_dht_queries,
285        }
286    }
287
288    /// Check if provider announcements should be allowed in current state
289    pub fn should_allow_provider_announcements(&self) -> bool {
290        match self.state() {
291            BackgroundState::Active | BackgroundState::Resuming => true,
292            BackgroundState::Paused | BackgroundState::Pausing => {
293                !self.config.pause_provider_announcements
294            }
295        }
296    }
297
298    /// Get configuration
299    pub fn config(&self) -> &BackgroundModeConfig {
300        &self.config
301    }
302
303    /// Get statistics
304    pub fn stats(&self) -> BackgroundModeStats {
305        // Update current state time before returning stats
306        let current_state = *self.state.read();
307        self.update_time_tracking(current_state);
308
309        self.stats.read().clone()
310    }
311
312    /// Reset statistics
313    pub fn reset_stats(&self) {
314        *self.stats.write() = BackgroundModeStats::default();
315        *self.time_in_active.write() = Duration::ZERO;
316        *self.time_in_paused.write() = Duration::ZERO;
317        *self.last_transition.write() = Some(Instant::now());
318    }
319
320    /// Increment DHT queries skipped counter
321    pub fn record_dht_query_skipped(&self) {
322        self.stats.write().dht_queries_skipped += 1;
323    }
324
325    /// Record connections closed on pause
326    pub fn record_connections_closed(&self, count: usize) {
327        self.stats.write().connections_closed_on_pause += count;
328    }
329}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334
335    #[test]
336    fn test_background_mode_creation() {
337        let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
338        assert_eq!(manager.state(), BackgroundState::Active);
339        assert!(manager.is_active());
340        assert!(!manager.is_paused());
341    }
342
343    #[test]
344    fn test_pause_resume() {
345        let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
346
347        // Pause
348        assert!(manager.pause().is_ok());
349        assert_eq!(manager.state(), BackgroundState::Paused);
350        assert!(manager.is_paused());
351        assert!(!manager.is_active());
352
353        // Resume
354        assert!(manager.resume().is_ok());
355        assert_eq!(manager.state(), BackgroundState::Active);
356        assert!(manager.is_active());
357        assert!(!manager.is_paused());
358    }
359
360    #[test]
361    fn test_pause_when_already_paused() {
362        let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
363
364        assert!(manager.pause().is_ok());
365        assert!(manager.pause().is_ok()); // Should be ok to pause when already paused
366        assert_eq!(manager.state(), BackgroundState::Paused);
367    }
368
369    #[test]
370    fn test_resume_when_already_active() {
371        let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
372
373        assert!(manager.resume().is_ok()); // Should be ok to resume when already active
374        assert_eq!(manager.state(), BackgroundState::Active);
375    }
376
377    #[test]
378    fn test_dht_query_allowed_in_active_state() {
379        let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
380        assert!(manager.should_allow_dht_query());
381    }
382
383    #[test]
384    fn test_dht_query_behavior_in_paused_state() {
385        let config = BackgroundModeConfig {
386            pause_dht_queries: true,
387            ..Default::default()
388        };
389        let manager = BackgroundModeManager::new(config);
390
391        manager.pause().unwrap();
392        assert!(!manager.should_allow_dht_query());
393    }
394
395    #[test]
396    fn test_dht_query_allowed_when_not_paused_in_background() {
397        let config = BackgroundModeConfig {
398            pause_dht_queries: false,
399            ..Default::default()
400        };
401        let manager = BackgroundModeManager::new(config);
402
403        manager.pause().unwrap();
404        assert!(manager.should_allow_dht_query());
405    }
406
407    #[test]
408    fn test_provider_announcements_in_active_state() {
409        let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
410        assert!(manager.should_allow_provider_announcements());
411    }
412
413    #[test]
414    fn test_provider_announcements_in_paused_state() {
415        let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
416        manager.pause().unwrap();
417        // Default config pauses announcements
418        assert!(!manager.should_allow_provider_announcements());
419    }
420
421    #[test]
422    fn test_statistics_tracking() {
423        let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
424
425        manager.pause().unwrap();
426        manager.resume().unwrap();
427        manager.pause().unwrap();
428
429        let stats = manager.stats();
430        assert_eq!(stats.pause_count, 2);
431        assert_eq!(stats.resume_count, 1);
432    }
433
434    #[test]
435    fn test_mobile_config() {
436        let config = BackgroundModeConfig::mobile();
437        assert!(config.pause_dht_queries);
438        assert!(config.pause_provider_announcements);
439        assert!(config.close_idle_connections);
440        assert_eq!(config.minimal_connection_count, 2);
441    }
442
443    #[test]
444    fn test_server_config() {
445        let config = BackgroundModeConfig::server();
446        assert!(!config.pause_dht_queries);
447        assert!(!config.pause_provider_announcements);
448        assert!(!config.close_idle_connections);
449    }
450
451    #[test]
452    fn test_record_dht_query_skipped() {
453        let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
454        manager.record_dht_query_skipped();
455        manager.record_dht_query_skipped();
456
457        let stats = manager.stats();
458        assert_eq!(stats.dht_queries_skipped, 2);
459    }
460
461    #[test]
462    fn test_record_connections_closed() {
463        let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
464        manager.record_connections_closed(5);
465
466        let stats = manager.stats();
467        assert_eq!(stats.connections_closed_on_pause, 5);
468    }
469
470    #[test]
471    fn test_reset_stats() {
472        let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
473
474        manager.pause().unwrap();
475        manager.resume().unwrap();
476        manager.record_dht_query_skipped();
477
478        manager.reset_stats();
479
480        let stats = manager.stats();
481        assert_eq!(stats.pause_count, 0);
482        assert_eq!(stats.resume_count, 0);
483        assert_eq!(stats.dht_queries_skipped, 0);
484    }
485}