leyline/core/device/
state.rs1use 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}