Skip to main content

trueno/brick/connection/
mod.rs

1//! Connection Management
2//!
3//! AWP-06: Managed connections with TTL, idle timeout, and health tracking.
4//! AWP-10: Keep-Alive normalization for HTTP connections.
5//! AWP-12: Bitflags connection state for efficient state tracking.
6
7use std::time::{Duration, Instant};
8
9// ----------------------------------------------------------------------------
10// AWP-06: Connection TTL + Health Check
11// ----------------------------------------------------------------------------
12
13/// Connection with TTL and health tracking.
14#[derive(Debug)]
15pub struct ManagedConnection<T> {
16    /// The underlying connection
17    inner: T,
18    /// When the connection was created
19    created_at: Instant,
20    /// When the connection was last used
21    last_used: Instant,
22    /// Maximum lifetime (TTL)
23    max_lifetime: Duration,
24    /// Maximum idle time
25    max_idle: Duration,
26    /// Health check failures
27    health_failures: usize,
28}
29
30impl<T> ManagedConnection<T> {
31    /// Create a new managed connection.
32    pub fn new(inner: T, max_lifetime: Duration, max_idle: Duration) -> Self {
33        let now = Instant::now();
34        Self { inner, created_at: now, last_used: now, max_lifetime, max_idle, health_failures: 0 }
35    }
36
37    /// Check if the connection is still valid.
38    #[must_use]
39    pub fn is_valid(&self) -> bool {
40        let now = Instant::now();
41        let not_expired = now.duration_since(self.created_at) < self.max_lifetime;
42        let not_idle = now.duration_since(self.last_used) < self.max_idle;
43        let healthy = self.health_failures < 3;
44        not_expired && not_idle && healthy
45    }
46
47    /// Check if the connection has expired (TTL exceeded).
48    #[must_use]
49    pub fn is_expired(&self) -> bool {
50        self.created_at.elapsed() >= self.max_lifetime
51    }
52
53    /// Check if the connection is idle.
54    #[must_use]
55    pub fn is_idle(&self) -> bool {
56        self.last_used.elapsed() >= self.max_idle
57    }
58
59    /// Mark the connection as used.
60    pub fn touch(&mut self) {
61        self.last_used = Instant::now();
62    }
63
64    /// Record a health check failure.
65    pub fn record_health_failure(&mut self) {
66        self.health_failures += 1;
67    }
68
69    /// Reset health failure count.
70    pub fn reset_health(&mut self) {
71        self.health_failures = 0;
72    }
73
74    /// Get the underlying connection.
75    pub fn inner(&self) -> &T {
76        &self.inner
77    }
78
79    /// Get mutable access to the underlying connection.
80    pub fn inner_mut(&mut self) -> &mut T {
81        &mut self.inner
82    }
83
84    /// Consume and return the underlying connection.
85    pub fn into_inner(self) -> T {
86        self.inner
87    }
88
89    /// Get connection age.
90    #[must_use]
91    pub fn age(&self) -> Duration {
92        self.created_at.elapsed()
93    }
94
95    /// Get idle time.
96    #[must_use]
97    pub fn idle_time(&self) -> Duration {
98        self.last_used.elapsed()
99    }
100
101    /// Get health failure count (for testing/diagnostics).
102    #[must_use]
103    pub fn health_failures(&self) -> usize {
104        self.health_failures
105    }
106}
107
108// ----------------------------------------------------------------------------
109// AWP-10: Keep-Alive Normalization
110// ----------------------------------------------------------------------------
111
112/// Normalized keep-alive configuration.
113///
114/// Canonicalizes various keep-alive settings into a standard form.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub struct KeepAliveConfig {
117    /// Whether keep-alive is enabled
118    pub enabled: bool,
119    /// Timeout duration in seconds
120    pub timeout_secs: u32,
121    /// Maximum number of requests per connection
122    pub max_requests: u32,
123}
124
125impl KeepAliveConfig {
126    /// Create with default values.
127    pub fn new() -> Self {
128        Self { enabled: true, timeout_secs: 60, max_requests: 100 }
129    }
130
131    /// Disabled keep-alive.
132    pub fn disabled() -> Self {
133        Self { enabled: false, timeout_secs: 0, max_requests: 0 }
134    }
135
136    /// Parse from HTTP header value (e.g., "timeout=5, max=100").
137    pub fn from_header(header: &str) -> Self {
138        let mut config = Self::new();
139
140        for part in header.split(',') {
141            let part = part.trim();
142            if let Some((key, val)) = part.split_once('=') {
143                let key = key.trim().to_lowercase();
144                let val = val.trim();
145
146                match key.as_str() {
147                    "timeout" => {
148                        if let Ok(t) = val.parse() {
149                            config.timeout_secs = t;
150                        }
151                    }
152                    "max" => {
153                        if let Ok(m) = val.parse() {
154                            config.max_requests = m;
155                        }
156                    }
157                    _ => {} // Ignore unknown header parameters
158                }
159            }
160        }
161
162        config
163    }
164
165    /// Check if connection should be kept alive after n requests.
166    #[must_use]
167    pub fn should_keep_alive(&self, request_count: u32) -> bool {
168        self.enabled && request_count < self.max_requests
169    }
170}
171
172impl Default for KeepAliveConfig {
173    fn default() -> Self {
174        Self::new()
175    }
176}
177
178// ----------------------------------------------------------------------------
179// AWP-12: Bitflags Connection State
180// ----------------------------------------------------------------------------
181
182/// Compact connection state using bitflags.
183///
184/// Efficiently represents multiple boolean states in a single byte.
185#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
186pub struct ConnectionState(u8);
187
188impl ConnectionState {
189    /// Connection is open
190    pub const OPEN: u8 = 0b0000_0001;
191    /// Connection is readable
192    pub const READABLE: u8 = 0b0000_0010;
193    /// Connection is writable
194    pub const WRITABLE: u8 = 0b0000_0100;
195    /// Connection has pending data
196    pub const HAS_PENDING: u8 = 0b0000_1000;
197    /// Connection is in keep-alive mode
198    pub const KEEP_ALIVE: u8 = 0b0001_0000;
199    /// Connection upgrade requested (e.g., WebSocket)
200    pub const UPGRADE: u8 = 0b0010_0000;
201    /// Connection is closing
202    pub const CLOSING: u8 = 0b0100_0000;
203    /// Connection has error
204    pub const ERROR: u8 = 0b1000_0000;
205
206    /// Create new state with no flags set.
207    #[must_use]
208    pub fn new() -> Self {
209        Self(0)
210    }
211
212    /// Create state with initial open + writable.
213    #[must_use]
214    pub fn open_connection() -> Self {
215        Self(Self::OPEN | Self::WRITABLE)
216    }
217
218    /// Set a flag.
219    pub fn set(&mut self, flag: u8) {
220        self.0 |= flag;
221    }
222
223    /// Clear a flag.
224    pub fn clear(&mut self, flag: u8) {
225        self.0 &= !flag;
226    }
227
228    /// Check if flag is set.
229    #[must_use]
230    pub fn is_set(&self, flag: u8) -> bool {
231        self.0 & flag != 0
232    }
233
234    /// Check if connection is open and healthy.
235    #[must_use]
236    pub fn is_healthy(&self) -> bool {
237        self.is_set(Self::OPEN) && !self.is_set(Self::ERROR) && !self.is_set(Self::CLOSING)
238    }
239
240    /// Check if connection can read.
241    #[must_use]
242    pub fn can_read(&self) -> bool {
243        self.is_set(Self::OPEN) && self.is_set(Self::READABLE)
244    }
245
246    /// Check if connection can write.
247    #[must_use]
248    pub fn can_write(&self) -> bool {
249        self.is_set(Self::OPEN) && self.is_set(Self::WRITABLE) && !self.is_set(Self::CLOSING)
250    }
251
252    /// Get raw bits.
253    #[must_use]
254    pub fn bits(&self) -> u8 {
255        self.0
256    }
257}
258
259#[cfg(test)]
260mod tests;