Skip to main content

confium_coordinator/coordinator/
session_timeout.rs

1//! Signing session timeout — per-session deadline enforcement.
2
3use chrono::{DateTime, Duration, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::sync::Mutex;
7
8/// A session deadline.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct SessionDeadline {
11    pub session_id: String,
12    pub deadline: DateTime<Utc>,
13}
14
15/// Manages per-session deadlines.
16#[derive(Default)]
17pub struct SessionTimeoutManager {
18    deadlines: Mutex<HashMap<String, DateTime<Utc>>>,
19}
20
21impl SessionTimeoutManager {
22    pub fn new() -> Self {
23        Self::default()
24    }
25
26    /// Set a deadline for a session.
27    pub fn set_deadline(&self, session_id: &str, timeout: Duration) {
28        let deadline = Utc::now() + timeout;
29        self.deadlines
30            .lock()
31            .unwrap()
32            .insert(session_id.into(), deadline);
33    }
34
35    /// Remove a session's deadline (session completed).
36    pub fn clear(&self, session_id: &str) {
37        self.deadlines.lock().unwrap().remove(session_id);
38    }
39
40    /// Get all sessions that have exceeded their deadline.
41    pub fn expired_sessions(&self) -> Vec<SessionDeadline> {
42        let now = Utc::now();
43        self.deadlines
44            .lock()
45            .unwrap()
46            .iter()
47            .filter(|(_, deadline)| **deadline < now)
48            .map(|(sid, deadline)| SessionDeadline {
49                session_id: sid.clone(),
50                deadline: *deadline,
51            })
52            .collect()
53    }
54
55    /// Number of tracked sessions.
56    pub fn count(&self) -> usize {
57        self.deadlines.lock().unwrap().len()
58    }
59
60    /// Check if a specific session is expired.
61    pub fn is_expired(&self, session_id: &str) -> bool {
62        let now = Utc::now();
63        self.deadlines
64            .lock()
65            .unwrap()
66            .get(session_id)
67            .map(|d| *d < now)
68            .unwrap_or(false)
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn set_and_check_not_expired() {
78        let mgr = SessionTimeoutManager::new();
79        mgr.set_deadline("s1", Duration::minutes(5));
80        assert!(!mgr.is_expired("s1"));
81    }
82
83    #[test]
84    fn expired_after_deadline() {
85        let mgr = SessionTimeoutManager::new();
86        mgr.set_deadline("s1", Duration::seconds(-1)); // already past
87        assert!(mgr.is_expired("s1"));
88    }
89
90    #[test]
91    fn clear_removes_deadline() {
92        let mgr = SessionTimeoutManager::new();
93        mgr.set_deadline("s1", Duration::minutes(5));
94        mgr.clear("s1");
95        assert!(!mgr.is_expired("s1"));
96        assert_eq!(mgr.count(), 0);
97    }
98
99    #[test]
100    fn expired_sessions_returns_list() {
101        let mgr = SessionTimeoutManager::new();
102        mgr.set_deadline("s1", Duration::minutes(5));
103        mgr.set_deadline("s2", Duration::seconds(-1));
104        mgr.set_deadline("s3", Duration::seconds(-1));
105        let expired = mgr.expired_sessions();
106        assert_eq!(expired.len(), 2);
107    }
108
109    #[test]
110    fn count_tracks_sessions() {
111        let mgr = SessionTimeoutManager::new();
112        assert_eq!(mgr.count(), 0);
113        mgr.set_deadline("s1", Duration::minutes(5));
114        mgr.set_deadline("s2", Duration::minutes(5));
115        assert_eq!(mgr.count(), 2);
116    }
117
118    #[test]
119    fn unknown_session_not_expired() {
120        let mgr = SessionTimeoutManager::new();
121        assert!(!mgr.is_expired("unknown"));
122    }
123}