Skip to main content

canlink_hal/monitor/
reconnect.rs

1//! Reconnection configuration (FR-010)
2
3use std::time::Duration;
4
5/// Reconnection configuration
6///
7/// Configures automatic reconnection behavior. By default, auto-reconnect
8/// is disabled to avoid masking hardware issues.
9///
10/// # Example
11///
12/// ```rust
13/// use canlink_hal::monitor::ReconnectConfig;
14/// use std::time::Duration;
15///
16/// let config = ReconnectConfig {
17///     max_retries: 5,
18///     retry_interval: Duration::from_secs(2),
19///     backoff_multiplier: 1.5,
20/// };
21/// ```
22#[derive(Debug, Clone)]
23pub struct ReconnectConfig {
24    /// Maximum number of reconnection attempts
25    ///
26    /// Set to 0 for unlimited retries.
27    pub max_retries: u32,
28
29    /// Initial interval between reconnection attempts
30    pub retry_interval: Duration,
31
32    /// Backoff multiplier for exponential backoff
33    ///
34    /// After each failed attempt, the interval is multiplied by this value.
35    /// Set to 1.0 for fixed intervals.
36    pub backoff_multiplier: f32,
37}
38
39impl Default for ReconnectConfig {
40    fn default() -> Self {
41        Self {
42            max_retries: 3,
43            retry_interval: Duration::from_secs(1),
44            backoff_multiplier: 2.0,
45        }
46    }
47}
48
49impl ReconnectConfig {
50    /// Create a new reconnect config with default values
51    #[must_use]
52    pub fn new() -> Self {
53        Self::default()
54    }
55
56    /// Create a config with fixed retry interval (no backoff)
57    #[must_use]
58    pub fn fixed_interval(max_retries: u32, interval: Duration) -> Self {
59        Self {
60            max_retries,
61            retry_interval: interval,
62            backoff_multiplier: 1.0,
63        }
64    }
65
66    /// Create a config with exponential backoff
67    #[must_use]
68    pub fn exponential_backoff(
69        max_retries: u32,
70        initial_interval: Duration,
71        multiplier: f32,
72    ) -> Self {
73        Self {
74            max_retries,
75            retry_interval: initial_interval,
76            backoff_multiplier: multiplier,
77        }
78    }
79
80    /// Calculate the interval for a given retry attempt
81    #[must_use]
82    #[allow(clippy::cast_possible_wrap)]
83    pub fn interval_for_attempt(&self, attempt: u32) -> Duration {
84        if attempt == 0 {
85            return self.retry_interval;
86        }
87
88        let multiplier = self.backoff_multiplier.powi(attempt as i32);
89        Duration::from_secs_f32(self.retry_interval.as_secs_f32() * multiplier)
90    }
91
92    /// Check if more retries are allowed
93    #[must_use]
94    pub fn should_retry(&self, current_attempt: u32) -> bool {
95        self.max_retries == 0 || current_attempt < self.max_retries
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_default_config() {
105        let config = ReconnectConfig::default();
106        assert_eq!(config.max_retries, 3);
107        assert_eq!(config.retry_interval, Duration::from_secs(1));
108        assert!((config.backoff_multiplier - 2.0).abs() < f32::EPSILON);
109    }
110
111    #[test]
112    fn test_fixed_interval() {
113        let config = ReconnectConfig::fixed_interval(5, Duration::from_millis(500));
114        assert_eq!(config.max_retries, 5);
115        assert!((config.backoff_multiplier - 1.0).abs() < f32::EPSILON);
116
117        // All attempts should have the same interval
118        assert_eq!(
119            config.interval_for_attempt(0),
120            config.interval_for_attempt(3)
121        );
122    }
123
124    #[test]
125    fn test_exponential_backoff() {
126        let config = ReconnectConfig::exponential_backoff(5, Duration::from_secs(1), 2.0);
127
128        assert_eq!(config.interval_for_attempt(0), Duration::from_secs(1));
129        assert_eq!(config.interval_for_attempt(1), Duration::from_secs(2));
130        assert_eq!(config.interval_for_attempt(2), Duration::from_secs(4));
131    }
132
133    #[test]
134    fn test_should_retry() {
135        let config = ReconnectConfig {
136            max_retries: 3,
137            ..Default::default()
138        };
139
140        assert!(config.should_retry(0));
141        assert!(config.should_retry(2));
142        assert!(!config.should_retry(3));
143    }
144
145    #[test]
146    fn test_unlimited_retries() {
147        let config = ReconnectConfig {
148            max_retries: 0, // Unlimited
149            ..Default::default()
150        };
151
152        assert!(config.should_retry(100));
153        assert!(config.should_retry(1000));
154    }
155}