Skip to main content

leyline/core/device/
state.rs

1use std::time::{Duration, SystemTime, UNIX_EPOCH};
2
3use base64::Engine as _;
4use base64::engine::general_purpose::STANDARD;
5use serde::{Deserialize, Serialize};
6
7use crate::core::Session;
8use crate::util::epoch_plus;
9
10#[derive(Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
11#[non_exhaustive]
12pub struct SessionState {
13    #[serde(default)]
14    tls_sessions: Vec<TlsTicket>,
15    #[serde(default)]
16    alt_svc: Vec<AltSvcEntry>,
17    #[serde(default)]
18    hsts: Vec<HstsEntry>,
19}
20
21#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
22struct TlsTicket {
23    key: String,
24    session: String,
25}
26
27#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
28struct AltSvcEntry {
29    host: String,
30    port: u16,
31    expires: u64,
32}
33
34#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
35struct HstsEntry {
36    host: String,
37    #[serde(default)]
38    include_subdomains: bool,
39    expires: u64,
40}
41
42pub(crate) struct StateParts {
43    pub(crate) tls_sessions: Vec<(String, Vec<u8>)>,
44    pub(crate) alt_svc: Vec<(String, u16, SystemTime)>,
45    pub(crate) hsts: Vec<(String, bool, SystemTime)>,
46}
47
48impl SessionState {
49    pub fn restore_into(&self, session: &Session) {
50        session.restore_state(&self.parts());
51    }
52
53    #[must_use]
54    pub fn is_empty(&self) -> bool {
55        self.tls_sessions.is_empty() && self.alt_svc.is_empty() && self.hsts.is_empty()
56    }
57
58    pub(crate) fn from_parts(parts: StateParts) -> Self {
59        Self {
60            tls_sessions: parts
61                .tls_sessions
62                .into_iter()
63                .map(|(key, der)| TlsTicket {
64                    key,
65                    session: STANDARD.encode(der),
66                })
67                .collect(),
68            alt_svc: parts
69                .alt_svc
70                .into_iter()
71                .map(|(host, port, at)| AltSvcEntry {
72                    host,
73                    port,
74                    expires: unix_secs(at),
75                })
76                .collect(),
77            hsts: parts
78                .hsts
79                .into_iter()
80                .map(|(host, include_subdomains, at)| HstsEntry {
81                    host,
82                    include_subdomains,
83                    expires: unix_secs(at),
84                })
85                .collect(),
86        }
87    }
88
89    fn parts(&self) -> StateParts {
90        StateParts {
91            tls_sessions: self
92                .tls_sessions
93                .iter()
94                .filter_map(|t| Some((t.key.clone(), STANDARD.decode(&t.session).ok()?)))
95                .collect(),
96            alt_svc: self
97                .alt_svc
98                .iter()
99                .filter_map(|e| Some((e.host.clone(), e.port, at_secs(e.expires)?)))
100                .collect(),
101            hsts: self
102                .hsts
103                .iter()
104                .filter_map(|e| Some((e.host.clone(), e.include_subdomains, at_secs(e.expires)?)))
105                .collect(),
106        }
107    }
108}
109
110impl std::fmt::Debug for SessionState {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        f.debug_struct("SessionState")
113            .field("tls_sessions", &self.tls_sessions.len())
114            .field("alt_svc", &self.alt_svc.len())
115            .field("hsts", &self.hsts.len())
116            .finish()
117    }
118}
119
120pub(crate) fn unix_secs(at: SystemTime) -> u64 {
121    at.duration_since(UNIX_EPOCH)
122        .map_or(0, |since| since.as_secs())
123}
124
125fn at_secs(secs: u64) -> Option<SystemTime> {
126    epoch_plus(Duration::from_secs(secs))
127}