Skip to main content

core_crypto/mls/session/
mod.rs

1mod credential;
2pub(crate) mod e2e_identity;
3mod epoch_observer;
4mod error;
5mod history_observer;
6pub(crate) mod id;
7pub(crate) mod key_package;
8pub(crate) mod user_id;
9
10use std::sync::Arc;
11
12use async_lock::{Mutex, RwLock};
13pub use epoch_observer::EpochObserver;
14pub(crate) use error::{Error, Result};
15pub use history_observer::HistoryObserver;
16use openmls_traits::OpenMlsCryptoProvider;
17
18use crate::{
19    ClientId, HistorySecret, ImmutableDatabase, LeafError, MlsTransport, OpenMlsError, RecursiveError,
20    mls::{
21        conversation::{Conversation, ConversationIdRef},
22        conversation_cache::ConversationCache,
23    },
24    mls_provider::{CryptoProvider, EntropySeed},
25};
26
27/// A MLS Session enables a user device to communicate via the MLS protocol.
28///
29/// This closely maps to the `Client` term in [RFC 9720], but we avoid that term to avoid ambiguity;
30/// `Client` is very overloaded with distinct meanings.
31///
32/// There is one `Session` per user per device. A session can contain many MLS groups/conversations.
33///
34/// It is cheap to clone a `Session` because everything heavy is wrapped inside an [Arc].
35///
36/// [RFC 9720]: https://www.rfc-editor.org/rfc/rfc9420.html
37#[derive(Clone, derive_more::Debug)]
38pub struct Session {
39    id: ClientId,
40    pub(crate) crypto_provider: CryptoProvider,
41    pub(crate) transport: Arc<dyn MlsTransport + 'static>,
42    database: ImmutableDatabase,
43    #[debug("EpochObserver")]
44    pub(crate) epoch_observer: Arc<RwLock<Option<Arc<dyn EpochObserver + 'static>>>>,
45    #[debug("HistoryObserver")]
46    pub(crate) history_observer: Arc<RwLock<Option<Arc<dyn HistoryObserver + 'static>>>>,
47    /// LRU cache of live MLS conversations.
48    ///
49    /// Shared across transactions for cache reuse;
50    /// cleared on transaction rollback to avoid serving stale state.
51    pub(crate) conversation_cache: Arc<Mutex<ConversationCache>>,
52}
53
54impl Session {
55    /// Create a new `Session`
56    pub fn new(
57        id: ClientId,
58        crypto_provider: CryptoProvider,
59        database: ImmutableDatabase,
60        transport: Arc<dyn MlsTransport>,
61    ) -> Self {
62        Self {
63            id,
64            crypto_provider,
65            transport,
66            database,
67            epoch_observer: Arc::new(RwLock::new(None)),
68            history_observer: Arc::new(RwLock::new(None)),
69            conversation_cache: Arc::new(Mutex::new(ConversationCache::new())),
70        }
71    }
72
73    /// Get an immutable view of an MLS conversation.
74    ///
75    /// This may be faster than
76    /// [crate::transaction_context::TransactionContext::conversation].
77    pub async fn get_raw_conversation(&self, id: &ConversationIdRef) -> Result<Conversation> {
78        Conversation::load(self.clone(), id)
79            .await
80            .map_err(RecursiveError::mls_conversation("getting raw conversation by id"))?
81            .ok_or_else(|| LeafError::ConversationNotFound(id.to_owned()))
82            .map_err(Into::into)
83    }
84
85    /// Checks if a given conversation id exists locally
86    pub async fn conversation_exists(&self, id: &ConversationIdRef) -> Result<bool> {
87        match self.get_raw_conversation(id).await {
88            Ok(_) => Ok(true),
89            Err(Error::Leaf(LeafError::ConversationNotFound(_))) => Ok(false),
90            Err(e) => Err(e),
91        }
92    }
93
94    /// Generates a random byte array of the specified size
95    pub fn random_bytes(&self, len: usize) -> crate::mls::Result<Vec<u8>> {
96        use openmls_traits::random::OpenMlsRand as _;
97        self.crypto_provider
98            .rand()
99            .random_vec(len)
100            .map_err(OpenMlsError::wrap("generating random vector"))
101            .map_err(Into::into)
102    }
103
104    /// Waits for running transactions to finish, then closes the connection with the local KeyStore.
105    ///
106    /// # Errors
107    /// KeyStore errors, such as IO, and if there is more than one strong reference
108    /// to the connection.
109    pub async fn close(&self) -> crate::mls::Result<()> {
110        self.crypto_provider
111            .close()
112            .await
113            .map_err(OpenMlsError::wrap("closing connection with keystore"))
114            .map_err(Into::into)
115    }
116
117    /// Get read-only access to the database.
118    pub fn database(&self) -> &ImmutableDatabase {
119        &self.database
120    }
121
122    /// see [crate::mls_provider::CryptoProvider::reseed]
123    pub async fn reseed(&self, seed: Option<EntropySeed>) -> crate::mls::Result<()> {
124        self.crypto_provider
125            .reseed(seed)
126            .map_err(OpenMlsError::wrap("reseeding mls backend"))
127            .map_err(Into::into)
128    }
129
130    /// Restore from an external [`HistorySecret`].
131    pub(crate) async fn restore_from_history_secret(&self, history_secret: HistorySecret) -> Result<()> {
132        // store the key package
133        history_secret
134            .key_package
135            .store(&self.crypto_provider)
136            .await
137            .map_err(OpenMlsError::wrap("storing key package encapsulation"))?;
138
139        Ok(())
140    }
141
142    /// Retrieves the client's client id. This is free-form and not inspected.
143    pub fn id(&self) -> ClientId {
144        self.id.clone()
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use core_crypto_keystore::{entities::*, traits::FetchFromDatabase};
151
152    use super::*;
153    use crate::{KeystoreError, mls_provider::CryptoProvider, transaction_context::test_utils::EntitiesCount};
154
155    impl Session {
156        // test functions are not held to the same documentation standard as proper functions
157        #![allow(missing_docs)]
158
159        pub async fn find_keypackages(&self, backend: &CryptoProvider) -> Result<Vec<openmls::prelude::KeyPackage>> {
160            use core_crypto_keystore::CryptoKeystoreMls as _;
161            let kps = backend
162                .key_store()
163                .mls_fetch_key_packages::<openmls::prelude::KeyPackage>(u32::MAX)
164                .await
165                .map_err(KeystoreError::wrap("fetching mls keypackages"))?;
166            Ok(kps)
167        }
168
169        /// Count the entities
170        pub async fn count_entities(&self) -> EntitiesCount {
171            let keystore = &self.database;
172            let credential = keystore.count::<StoredCredential>().await.unwrap();
173            let encryption_keypair = keystore.count::<StoredEncryptionKeyPair>().await.unwrap();
174            let epoch_encryption_keypair = keystore.count::<StoredEpochEncryptionKeypair>().await.unwrap();
175            let enrollment = keystore.count::<StoredE2eiEnrollment>().await.unwrap();
176            let group = keystore.count::<PersistedMlsGroup>().await.unwrap();
177            let hpke_private_key = keystore.count::<StoredHpkePrivateKey>().await.unwrap();
178            let key_package = keystore.count::<StoredKeypackage>().await.unwrap();
179            let pending_group = keystore.count::<PersistedMlsPendingGroup>().await.unwrap();
180            let pending_messages = keystore.count::<MlsPendingMessage>().await.unwrap();
181            let psk_bundle = keystore.count::<StoredPskBundle>().await.unwrap();
182            EntitiesCount {
183                credential,
184                encryption_keypair,
185                epoch_encryption_keypair,
186                enrollment,
187                group,
188                hpke_private_key,
189                key_package,
190                pending_group,
191                pending_messages,
192                psk_bundle,
193            }
194        }
195    }
196}