layer_client/
session_backend.rs1use std::io;
12use std::path::PathBuf;
13use crate::session::{DcEntry, PersistedSession};
14
15pub trait SessionBackend: Send + Sync {
19 fn save(&self, session: &PersistedSession) -> io::Result<()>;
21
22 fn load(&self) -> io::Result<Option<PersistedSession>>;
24
25 fn delete(&self) -> io::Result<()>;
27
28 fn name(&self) -> &str;
30}
31
32pub struct BinaryFileBackend {
38 path: PathBuf,
39}
40
41impl BinaryFileBackend {
42 pub fn new(path: impl Into<PathBuf>) -> Self {
43 Self { path: path.into() }
44 }
45}
46
47impl SessionBackend for BinaryFileBackend {
48 fn save(&self, session: &PersistedSession) -> io::Result<()> {
49 session.save(&self.path)
50 }
51
52 fn load(&self) -> io::Result<Option<PersistedSession>> {
53 if !self.path.exists() {
54 return Ok(None);
55 }
56 PersistedSession::load(&self.path).map(Some)
57 }
58
59 fn delete(&self) -> io::Result<()> {
60 if self.path.exists() {
61 std::fs::remove_file(&self.path)?;
62 }
63 Ok(())
64 }
65
66 fn name(&self) -> &str { "binary-file" }
67}
68
69pub struct InMemoryBackend {
75 data: std::sync::Mutex<Option<PersistedSessionData>>,
76}
77
78#[derive(Clone)]
79struct PersistedSessionData {
80 home_dc_id: i32,
81 dcs: Vec<DcEntry>,
82}
83
84impl InMemoryBackend {
85 pub fn new() -> Self {
86 Self { data: std::sync::Mutex::new(None) }
87 }
88}
89
90impl Default for InMemoryBackend {
91 fn default() -> Self { Self::new() }
92}
93
94impl SessionBackend for InMemoryBackend {
95 fn save(&self, session: &PersistedSession) -> io::Result<()> {
96 let mut lock = self.data.lock().unwrap();
97 *lock = Some(PersistedSessionData {
98 home_dc_id: session.home_dc_id,
99 dcs: session.dcs.clone(),
100 });
101 Ok(())
102 }
103
104 fn load(&self) -> io::Result<Option<PersistedSession>> {
105 let lock = self.data.lock().unwrap();
106 Ok(lock.as_ref().map(|d| PersistedSession {
107 home_dc_id: d.home_dc_id,
108 dcs: d.dcs.clone(),
109 }))
110 }
111
112 fn delete(&self) -> io::Result<()> {
113 let mut lock = self.data.lock().unwrap();
114 *lock = None;
115 Ok(())
116 }
117
118 fn name(&self) -> &str { "in-memory" }
119}
120
121#[cfg(feature = "sqlite-session")]
124pub use sqlite_backend::SqliteBackend;
125
126#[cfg(feature = "sqlite-session")]
127mod sqlite_backend {
128 use super::*;
129 use rusqlite::{Connection, params};
130
131 pub struct SqliteBackend {
141 path: PathBuf,
142 }
143
144 impl SqliteBackend {
145 pub fn new(path: impl Into<PathBuf>) -> io::Result<Self> {
146 let path = path.into();
147 let conn = Connection::open(&path)
149 .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
150 conn.execute_batch(
151 "CREATE TABLE IF NOT EXISTS meta (
152 key TEXT PRIMARY KEY,
153 value TEXT NOT NULL
154 );
155 CREATE TABLE IF NOT EXISTS dc_entries (
156 dc_id INTEGER PRIMARY KEY,
157 addr TEXT NOT NULL,
158 auth_key BLOB,
159 first_salt INTEGER NOT NULL DEFAULT 0,
160 time_offset INTEGER NOT NULL DEFAULT 0
161 );",
162 ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
163 Ok(Self { path })
164 }
165 }
166
167 impl SessionBackend for SqliteBackend {
168 fn save(&self, session: &PersistedSession) -> io::Result<()> {
169 let conn = Connection::open(&self.path)
170 .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
171
172 conn.execute(
173 "INSERT OR REPLACE INTO meta (key, value) VALUES ('home_dc_id', ?1)",
174 params![session.home_dc_id.to_string()],
175 ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
176
177 for dc in &session.dcs {
178 let key_blob: Option<Vec<u8>> = dc.auth_key.map(|k| k.to_vec());
179 conn.execute(
180 "INSERT OR REPLACE INTO dc_entries
181 (dc_id, addr, auth_key, first_salt, time_offset)
182 VALUES (?1, ?2, ?3, ?4, ?5)",
183 params![
184 dc.dc_id,
185 dc.addr,
186 key_blob,
187 dc.first_salt,
188 dc.time_offset,
189 ],
190 ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
191 }
192 Ok(())
193 }
194
195 fn load(&self) -> io::Result<Option<PersistedSession>> {
196 if !self.path.exists() {
197 return Ok(None);
198 }
199 let conn = Connection::open(&self.path)
200 .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
201
202 let home_dc_id: Option<i32> = conn
203 .query_row(
204 "SELECT value FROM meta WHERE key = 'home_dc_id'",
205 [],
206 |row| {
207 let v: String = row.get(0)?;
208 Ok(v.parse::<i32>().unwrap_or(2))
209 },
210 )
211 .ok();
212
213 let home_dc_id = match home_dc_id {
214 Some(id) => id,
215 None => return Ok(None),
216 };
217
218 let mut stmt = conn
219 .prepare("SELECT dc_id, addr, auth_key, first_salt, time_offset FROM dc_entries")
220 .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
221
222 let dcs: Vec<DcEntry> = stmt
223 .query_map([], |row| {
224 let dc_id: i32 = row.get(0)?;
225 let addr: String = row.get(1)?;
226 let key_blob: Option<Vec<u8>> = row.get(2)?;
227 let first_salt: i64 = row.get(3)?;
228 let time_offset: i32 = row.get(4)?;
229 let auth_key = key_blob.and_then(|k| {
230 if k.len() == 256 {
231 let mut arr = [0u8; 256];
232 arr.copy_from_slice(&k);
233 Some(arr)
234 } else {
235 None
236 }
237 });
238 Ok(DcEntry { dc_id, addr, auth_key, first_salt, time_offset })
239 })
240 .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?
241 .filter_map(|r| r.ok())
242 .collect();
243
244 Ok(Some(PersistedSession { home_dc_id, dcs }))
245 }
246
247 fn delete(&self) -> io::Result<()> {
248 if self.path.exists() {
249 std::fs::remove_file(&self.path)?;
250 }
251 Ok(())
252 }
253
254 fn name(&self) -> &str { "sqlite" }
255 }
256}