1use crate::error::{MlsError, Result};
7use crate::group::GroupManager;
8use crate::provider::MlsProvider;
9use crate::storage::MlsStorage;
10use crate::types::{EncryptedMessage, GroupId, GroupInfo, MlsMessageType, WelcomeMessage};
11
12use openmls::prelude::tls_codec::{Deserialize as TlsDeserialize, Serialize as TlsSerialize};
13use openmls::prelude::*;
14use openmls_traits::signatures::Signer;
15use std::collections::HashMap;
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::sync::{Arc, Mutex};
18use tracing::{debug, info};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct SealMark {
28 pub incarnation: u64,
31 pub index: u64,
33}
34
35#[derive(Debug, Clone, Copy)]
38struct SealProgress {
39 incarnation: u64,
40 sealed: u64,
41 acknowledged: u64,
42}
43
44pub struct SessionManager {
46 user_id: String,
48
49 group_manager: GroupManager,
51
52 seal_progress: Mutex<HashMap<String, SealProgress>>,
57
58 next_incarnation: AtomicU64,
60}
61
62impl SessionManager {
63 pub fn new(user_id: String, storage: Arc<dyn MlsStorage>, provider: MlsProvider) -> Self {
65 let group_manager = GroupManager::new(storage, provider);
66 Self {
67 user_id,
68 group_manager,
69 seal_progress: Mutex::new(HashMap::new()),
70 next_incarnation: AtomicU64::new(1),
71 }
72 }
73
74 fn progress(&self) -> std::sync::MutexGuard<'_, HashMap<String, SealProgress>> {
75 self.seal_progress
76 .lock()
77 .unwrap_or_else(std::sync::PoisonError::into_inner)
78 }
79
80 fn fresh_progress(&self) -> SealProgress {
81 SealProgress {
82 incarnation: self.next_incarnation.fetch_add(1, Ordering::Relaxed),
83 sealed: 0,
84 acknowledged: 0,
85 }
86 }
87
88 fn restart_seal_progress(&self, session_id: &GroupId) {
91 let fresh = self.fresh_progress();
92 self.progress().insert(session_id.to_string(), fresh);
93 }
94
95 pub fn last_seal_mark(&self, other_user_id: &str) -> Option<SealMark> {
99 let session_id = self.get_session_id(other_user_id).ok()?;
100 let progress = self.progress();
101 let entry = progress.get(session_id.as_str())?;
102 (entry.sealed > 0).then_some(SealMark {
103 incarnation: entry.incarnation,
104 index: entry.sealed,
105 })
106 }
107
108 pub fn note_acknowledged_seal(&self, other_user_id: &str, mark: SealMark) {
112 let Ok(session_id) = self.get_session_id(other_user_id) else {
113 return;
114 };
115 if let Some(entry) = self.progress().get_mut(session_id.as_str()) {
116 if entry.incarnation == mark.incarnation {
117 entry.acknowledged = entry.acknowledged.max(mark.index);
118 }
119 }
120 }
121
122 pub fn unacknowledged_seals(&self, other_user_id: &str) -> u64 {
130 let Ok(session_id) = self.get_session_id(other_user_id) else {
131 return 0;
132 };
133 self.progress()
134 .get(session_id.as_str())
135 .map_or(0, |entry| entry.sealed.saturating_sub(entry.acknowledged))
136 }
137
138 pub fn group_manager(&self) -> &GroupManager {
140 &self.group_manager
141 }
142
143 pub fn get_session_id(&self, other_user_id: &str) -> Result<GroupId> {
149 GroupId::for_session(&self.user_id, other_user_id).map_err(MlsError::from)
150 }
151
152 pub fn has_session(&self, other_user_id: &str) -> Result<bool> {
154 let session_id = self.get_session_id(other_user_id)?;
155 let group = self.group_manager.load_group(&session_id)?;
156 Ok(group.is_some())
157 }
158
159 pub fn create_session(
161 &self,
162 other_user_id: &str,
163 their_key_package: KeyPackage,
164 credential_with_key: &CredentialWithKey,
165 signer: &impl Signer,
166 ) -> Result<WelcomeMessage> {
167 let session_id = self.get_session_id(other_user_id)?;
168
169 if self.group_manager.load_group(&session_id)?.is_some() {
170 return Err(MlsError::GroupCreation(format!(
171 "Session already exists with user: {}",
172 other_user_id
173 )));
174 }
175
176 let mut group =
177 self.group_manager
178 .create_group(&session_id, credential_with_key, signer)?;
179
180 let (_commit, welcome) =
181 self.group_manager
182 .add_member(&mut group, their_key_package, signer)?;
183
184 self.group_manager.save_group(&session_id, &group)?;
185
186 let welcome_bytes = welcome
187 .tls_serialize_detached()
188 .map_err(|e| MlsError::Serialization(e.to_string()))?;
189
190 info!(
191 session_id = %session_id,
192 other_user = %other_user_id,
193 "Created new 1:1 session"
194 );
195
196 self.restart_seal_progress(&session_id);
197 Ok(WelcomeMessage {
198 group_id: session_id,
199 welcome_data: welcome_bytes,
200 inviter_id: self.user_id.clone(),
201 group_name: None,
202 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
203 })
204 }
205
206 pub(crate) fn verify_welcome_slot(&self, welcome_msg: &WelcomeMessage) -> Result<()> {
228 let expected_id = GroupId::for_session(&self.user_id, &welcome_msg.inviter_id)?;
229 if welcome_msg.group_id != expected_id {
230 return Err(MlsError::WelcomeIdentityMismatch {
231 expected: expected_id.to_string(),
232 found: welcome_msg.group_id.to_string(),
233 });
234 }
235 Ok(())
236 }
237
238 pub fn join_session(&self, welcome_msg: &WelcomeMessage) -> Result<GroupInfo> {
248 self.verify_welcome_slot(welcome_msg)?;
251
252 let mls_msg = MlsMessageIn::tls_deserialize_exact(&welcome_msg.welcome_data)
254 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
255
256 let welcome = match mls_msg.extract() {
258 MlsMessageBodyIn::Welcome(w) => w,
259 _ => {
260 return Err(MlsError::WelcomeProcessing(
261 "Not a Welcome message".to_string(),
262 ))
263 }
264 };
265
266 let group = self
269 .group_manager
270 .join_group_replacing(welcome, &welcome_msg.group_id)?;
271 let info = self
272 .group_manager
273 .get_group_info(&group, &welcome_msg.group_id);
274
275 info!(
276 session_id = %welcome_msg.group_id,
277 inviter = %welcome_msg.inviter_id,
278 "Joined/adopted 1:1 session (non-destructive)"
279 );
280
281 self.restart_seal_progress(&welcome_msg.group_id);
282 Ok(info)
283 }
284
285 pub fn encrypt_message(
287 &self,
288 other_user_id: &str,
289 plaintext: &[u8],
290 signer: &impl Signer,
291 ) -> Result<EncryptedMessage> {
292 let session_id = self.get_session_id(other_user_id)?;
293
294 let mut group = self
295 .group_manager
296 .load_group(&session_id)?
297 .ok_or_else(|| MlsError::SessionNotFound(other_user_id.to_string()))?;
298
299 let mls_message = self
300 .group_manager
301 .encrypt_message(&mut group, plaintext, signer)?;
302
303 self.group_manager.save_group(&session_id, &group)?;
304 {
305 let mut progress = self.progress();
306 let entry = progress
307 .entry(session_id.to_string())
308 .or_insert_with(|| self.fresh_progress());
309 entry.sealed = entry.sealed.saturating_add(1);
310 }
311
312 let ciphertext = mls_message
313 .tls_serialize_detached()
314 .map_err(|e| MlsError::Serialization(e.to_string()))?;
315
316 Ok(EncryptedMessage {
317 group_id: session_id,
318 message_type: MlsMessageType::Application,
319 epoch: group.epoch().as_u64(),
320 ciphertext,
321 sender_id: self.user_id.clone(),
322 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
323 })
324 }
325
326 pub fn decrypt_message(
339 &self,
340 encrypted: &EncryptedMessage,
341 claimed_sender: &str,
342 ) -> Result<Option<Vec<u8>>> {
343 let expected_id = GroupId::for_session(&self.user_id, claimed_sender)?;
344 if encrypted.group_id != expected_id {
345 return Err(MlsError::SessionIdentityMismatch {
346 expected: expected_id.to_string(),
347 found: encrypted.group_id.to_string(),
348 });
349 }
350
351 let mut group = self
352 .group_manager
353 .load_group(&encrypted.group_id)?
354 .ok_or_else(|| MlsError::GroupNotFound(encrypted.group_id.to_string()))?;
355
356 let mls_message = MlsMessageIn::tls_deserialize_exact(&encrypted.ciphertext)
357 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
358
359 let result = self.group_manager.decrypt_message(
360 &mut group,
361 &encrypted.group_id,
362 mls_message,
363 claimed_sender,
364 )?;
365
366 self.group_manager.save_group(&encrypted.group_id, &group)?;
367
368 Ok(result)
369 }
370
371 pub fn list_sessions(&self) -> Result<Vec<String>> {
381 let sessions: Vec<String> = self
382 .group_manager
383 .list_groups()?
384 .into_iter()
385 .filter_map(|g| {
386 let parts: Vec<&str> = g.as_str().split(':').collect();
387 match parts.as_slice() {
388 ["session", a, b] if *a == self.user_id => Some(b.to_string()),
389 ["session", a, b] if *b == self.user_id => Some(a.to_string()),
390 _ => None,
391 }
392 })
393 .collect();
394
395 Ok(sessions)
396 }
397
398 pub fn delete_session(&self, other_user_id: &str) -> Result<()> {
400 let session_id = self.get_session_id(other_user_id)?;
401 self.group_manager.delete_group(&session_id)?;
402 self.restart_seal_progress(&session_id);
403 debug!(session_id = %session_id, "Deleted session");
404 Ok(())
405 }
406
407 pub fn get_session_info(&self, other_user_id: &str) -> Result<Option<GroupInfo>> {
409 let session_id = self.get_session_id(other_user_id)?;
410 let group = self.group_manager.load_group(&session_id)?;
411 Ok(group.map(|g| self.group_manager.get_group_info(&g, &session_id)))
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418 use crate::storage::InMemoryStorage;
419 use crate::storage_adapter::MlsStorageAdapter;
420
421 fn create_test_session_manager(user_id: &str) -> SessionManager {
422 let storage = Arc::new(InMemoryStorage::new());
423 let adapter = MlsStorageAdapter::new(storage.clone());
424 let provider = MlsProvider::new(adapter);
425 SessionManager::new(user_id.to_string(), storage, provider)
426 }
427
428 #[test]
429 fn test_session_id_generation() {
430 let manager = create_test_session_manager("alice");
431 let id1 = manager.get_session_id("bob").unwrap();
432 let id2 = GroupId::for_session("bob", "alice").unwrap();
433 assert_eq!(id1, id2);
434 assert!(id1.as_str().starts_with("session:"));
435 }
436
437 #[test]
438 fn test_no_session_initially() {
439 let manager = create_test_session_manager("alice");
440 assert!(!manager.has_session("bob").unwrap());
441 assert!(manager.list_sessions().unwrap().is_empty());
442 }
443
444 #[test]
445 fn test_session_id_is_deterministic() {
446 let alice_manager = create_test_session_manager("alice");
448 let bob_manager = create_test_session_manager("bob");
449
450 let alice_to_bob = alice_manager.get_session_id("bob").unwrap();
451 let bob_to_alice = bob_manager.get_session_id("alice").unwrap();
452
453 assert_eq!(alice_to_bob, bob_to_alice);
455
456 assert_eq!(alice_to_bob.as_str(), "session:alice:bob");
458 }
459
460 #[test]
461 fn test_delete_session() {
462 let manager = create_test_session_manager("alice");
463
464 assert!(!manager.has_session("bob").unwrap());
466
467 let result = manager.delete_session("bob");
470 assert!(result.is_ok());
472 }
473}