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 corroboration(
168 &self,
169 other_user_id: &str,
170 nonce: &[u8],
171 ) -> Result<Option<(u64, Vec<u8>)>> {
172 let session_id = self.get_session_id(other_user_id)?;
173 let Some(group) = self.group_manager.load_group(&session_id)? else {
174 return Ok(None);
175 };
176 let tag = group
177 .export_secret(
178 self.group_manager.provider().crypto(),
179 offline_protocol_sealed::SESSION_CORROBORATION_LABEL,
180 nonce,
181 offline_protocol_sealed::SESSION_CORROBORATION_TAG_LEN,
182 )
183 .map_err(|e| {
184 MlsError::Encryption(format!("session corroboration export failed: {e}"))
185 })?;
186 Ok(Some((group.epoch().as_u64(), tag)))
187 }
188
189 pub fn create_session(
191 &self,
192 other_user_id: &str,
193 their_key_package: KeyPackage,
194 credential_with_key: &CredentialWithKey,
195 signer: &impl Signer,
196 ) -> Result<WelcomeMessage> {
197 let session_id = self.get_session_id(other_user_id)?;
198
199 if self.group_manager.load_group(&session_id)?.is_some() {
200 return Err(MlsError::GroupCreation(format!(
201 "Session already exists with user: {}",
202 other_user_id
203 )));
204 }
205
206 let mut group =
207 self.group_manager
208 .create_group(&session_id, credential_with_key, signer)?;
209
210 let (_commit, welcome) =
211 self.group_manager
212 .add_member(&mut group, their_key_package, signer)?;
213
214 self.group_manager.save_group(&session_id, &group)?;
215
216 let welcome_bytes = welcome
217 .tls_serialize_detached()
218 .map_err(|e| MlsError::Serialization(e.to_string()))?;
219
220 info!(
221 session_id = %session_id,
222 other_user = %other_user_id,
223 "Created new 1:1 session"
224 );
225
226 self.restart_seal_progress(&session_id);
227 Ok(WelcomeMessage {
228 group_id: session_id,
229 welcome_data: welcome_bytes,
230 inviter_id: self.user_id.clone(),
231 group_name: None,
232 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
233 })
234 }
235
236 pub(crate) fn verify_welcome_slot(&self, welcome_msg: &WelcomeMessage) -> Result<()> {
258 let expected_id = GroupId::for_session(&self.user_id, &welcome_msg.inviter_id)?;
259 if welcome_msg.group_id != expected_id {
260 return Err(MlsError::WelcomeIdentityMismatch {
261 expected: expected_id.to_string(),
262 found: welcome_msg.group_id.to_string(),
263 });
264 }
265 Ok(())
266 }
267
268 pub fn join_session(&self, welcome_msg: &WelcomeMessage) -> Result<GroupInfo> {
278 self.verify_welcome_slot(welcome_msg)?;
281
282 let mls_msg = MlsMessageIn::tls_deserialize_exact(&welcome_msg.welcome_data)
284 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
285
286 let welcome = match mls_msg.extract() {
288 MlsMessageBodyIn::Welcome(w) => w,
289 _ => {
290 return Err(MlsError::WelcomeProcessing(
291 "Not a Welcome message".to_string(),
292 ))
293 }
294 };
295
296 let group = self
299 .group_manager
300 .join_group_replacing(welcome, &welcome_msg.group_id)?;
301 let info = self
302 .group_manager
303 .get_group_info(&group, &welcome_msg.group_id);
304
305 info!(
306 session_id = %welcome_msg.group_id,
307 inviter = %welcome_msg.inviter_id,
308 "Joined/adopted 1:1 session (non-destructive)"
309 );
310
311 self.restart_seal_progress(&welcome_msg.group_id);
312 Ok(info)
313 }
314
315 pub fn encrypt_message(
317 &self,
318 other_user_id: &str,
319 plaintext: &[u8],
320 signer: &impl Signer,
321 ) -> Result<EncryptedMessage> {
322 let session_id = self.get_session_id(other_user_id)?;
323
324 let mut group = self
325 .group_manager
326 .load_group(&session_id)?
327 .ok_or_else(|| MlsError::SessionNotFound(other_user_id.to_string()))?;
328
329 let mls_message = self
330 .group_manager
331 .encrypt_message(&mut group, plaintext, signer)?;
332
333 self.group_manager.save_group(&session_id, &group)?;
334 {
335 let mut progress = self.progress();
336 let entry = progress
337 .entry(session_id.to_string())
338 .or_insert_with(|| self.fresh_progress());
339 entry.sealed = entry.sealed.saturating_add(1);
340 }
341
342 let ciphertext = mls_message
343 .tls_serialize_detached()
344 .map_err(|e| MlsError::Serialization(e.to_string()))?;
345
346 Ok(EncryptedMessage {
347 group_id: session_id,
348 message_type: MlsMessageType::Application,
349 epoch: group.epoch().as_u64(),
350 ciphertext,
351 sender_id: self.user_id.clone(),
352 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
353 })
354 }
355
356 pub fn decrypt_message(
369 &self,
370 encrypted: &EncryptedMessage,
371 claimed_sender: &str,
372 ) -> Result<Option<Vec<u8>>> {
373 let expected_id = GroupId::for_session(&self.user_id, claimed_sender)?;
374 if encrypted.group_id != expected_id {
375 return Err(MlsError::SessionIdentityMismatch {
376 expected: expected_id.to_string(),
377 found: encrypted.group_id.to_string(),
378 });
379 }
380
381 let mut group = self
382 .group_manager
383 .load_group(&encrypted.group_id)?
384 .ok_or_else(|| MlsError::GroupNotFound(encrypted.group_id.to_string()))?;
385
386 let mls_message = MlsMessageIn::tls_deserialize_exact(&encrypted.ciphertext)
387 .map_err(|e| MlsError::Deserialization(e.to_string()))?;
388
389 let result = self.group_manager.decrypt_message(
390 &mut group,
391 &encrypted.group_id,
392 mls_message,
393 claimed_sender,
394 )?;
395
396 self.group_manager.save_group(&encrypted.group_id, &group)?;
397
398 Ok(result)
399 }
400
401 pub fn list_sessions(&self) -> Result<Vec<String>> {
411 let sessions: Vec<String> = self
412 .group_manager
413 .list_groups()?
414 .into_iter()
415 .filter_map(|g| {
416 let parts: Vec<&str> = g.as_str().split(':').collect();
417 match parts.as_slice() {
418 ["session", a, b] if *a == self.user_id => Some(b.to_string()),
419 ["session", a, b] if *b == self.user_id => Some(a.to_string()),
420 _ => None,
421 }
422 })
423 .collect();
424
425 Ok(sessions)
426 }
427
428 pub fn delete_session(&self, other_user_id: &str) -> Result<()> {
430 let session_id = self.get_session_id(other_user_id)?;
431 self.group_manager.delete_group(&session_id)?;
432 self.restart_seal_progress(&session_id);
433 debug!(session_id = %session_id, "Deleted session");
434 Ok(())
435 }
436
437 pub fn get_session_info(&self, other_user_id: &str) -> Result<Option<GroupInfo>> {
439 let session_id = self.get_session_id(other_user_id)?;
440 let group = self.group_manager.load_group(&session_id)?;
441 Ok(group.map(|g| self.group_manager.get_group_info(&g, &session_id)))
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448 use crate::storage::InMemoryStorage;
449 use crate::storage_adapter::MlsStorageAdapter;
450
451 fn create_test_session_manager(user_id: &str) -> SessionManager {
452 let storage = Arc::new(InMemoryStorage::new());
453 let adapter = MlsStorageAdapter::new(storage.clone());
454 let provider = MlsProvider::new(adapter);
455 SessionManager::new(user_id.to_string(), storage, provider)
456 }
457
458 #[test]
459 fn test_session_id_generation() {
460 let manager = create_test_session_manager("alice");
461 let id1 = manager.get_session_id("bob").unwrap();
462 let id2 = GroupId::for_session("bob", "alice").unwrap();
463 assert_eq!(id1, id2);
464 assert!(id1.as_str().starts_with("session:"));
465 }
466
467 #[test]
468 fn test_no_session_initially() {
469 let manager = create_test_session_manager("alice");
470 assert!(!manager.has_session("bob").unwrap());
471 assert!(manager.list_sessions().unwrap().is_empty());
472 }
473
474 #[test]
475 fn test_session_id_is_deterministic() {
476 let alice_manager = create_test_session_manager("alice");
478 let bob_manager = create_test_session_manager("bob");
479
480 let alice_to_bob = alice_manager.get_session_id("bob").unwrap();
481 let bob_to_alice = bob_manager.get_session_id("alice").unwrap();
482
483 assert_eq!(alice_to_bob, bob_to_alice);
485
486 assert_eq!(alice_to_bob.as_str(), "session:alice:bob");
488 }
489
490 #[test]
491 fn test_delete_session() {
492 let manager = create_test_session_manager("alice");
493
494 assert!(!manager.has_session("bob").unwrap());
496
497 let result = manager.delete_session("bob");
500 assert!(result.is_ok());
502 }
503}