Skip to main content

core_crypto/transaction_context/conversation/
welcome.rs

1//! This module contains transactional conversation operations that are related to processing welcome messages.
2
3use openmls::prelude::{MlsMessageIn, MlsMessageInBody};
4
5use super::{Error, Result, TransactionContext};
6use crate::{ConversationConfiguration, ConversationId};
7
8impl TransactionContext {
9    /// Create a conversation from a received MLS Welcome message
10    ///
11    /// # Arguments
12    /// * `welcome` - a `Welcome` message received as a result of a commit adding new members to a group
13    ///
14    /// # Return type
15    /// This function will return the conversation/group id
16    ///
17    /// # Errors
18    /// Errors can be originating from the KeyStore of from OpenMls:
19    /// * if no [openmls::key_packages::KeyPackage] can be read from the KeyStore
20    /// * if the message can't be decrypted
21    #[cfg_attr(test, crate::dispotent)]
22    pub async fn process_welcome_message(&self, welcome: impl Into<MlsMessageIn>) -> Result<ConversationId> {
23        let MlsMessageInBody::Welcome(welcome) = welcome.into().extract() else {
24            return Err(Error::CallerError(
25                "the message provided to process_welcome_message was not a welcome message",
26            ));
27        };
28
29        let configuration = ConversationConfiguration {
30            cipher_suite: welcome.ciphersuite().into(),
31            ..Default::default()
32        };
33
34        let conversation = self
35            .persist_conversation_from_welcome_message(welcome, configuration)
36            .await?;
37
38        let id = conversation.id().to_owned();
39
40        Ok(id)
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use crate::test_utils::*;
47
48    #[apply(all_cred_cipher)]
49    async fn joining_from_welcome_should_prune_local_key_material(case: TestContext) {
50        let [alice, bob] = case.sessions().await;
51        Box::pin(async move {
52            // has to be before the original key_package count because it creates one
53            // Create a conversation from alice, where she invites bob
54            let commit_guard = case.create_conversation([&alice]).await.invite([&bob]).await;
55
56            // Keep track of the whatever amount was initially generated
57            let prev_count = bob.transaction.count_entities().await;
58            // Bob accepts the welcome message, and as such, it should prune the used keypackage from the store
59            commit_guard.notify_members().await;
60
61            // Ensure we're left with 1 less keypackage bundle in the store, because it was consumed with the OpenMLS
62            // Welcome message
63            let next_count = bob.transaction.count_entities().await;
64            assert_eq!(next_count.key_package, prev_count.key_package - 1);
65            assert_eq!(next_count.hpke_private_key, prev_count.hpke_private_key - 1);
66            assert_eq!(next_count.encryption_keypair, prev_count.encryption_keypair - 1);
67        })
68        .await;
69    }
70
71    #[apply(all_cred_cipher)]
72    async fn process_welcome_should_fail_when_already_exists(case: TestContext) {
73        use crate::LeafError;
74
75        let [alice, bob] = case.sessions().await;
76        Box::pin(async move {
77            let credential_ref = &bob.initial_credential;
78            let commit = case.create_conversation([&alice]).await.invite([&bob]).await;
79            let conversation = commit.conversation();
80            let id = conversation.id().clone();
81                // Meanwhile Bob creates a conversation with the exact same id as the one he's trying to join
82                bob
83                    .transaction
84                    .new_conversation(&id, credential_ref, case.cfg.clone())
85                    .await
86                    .unwrap();
87
88                let welcome = conversation.transport().await.latest_welcome_message().await;
89                let join_welcome = bob
90                    .transaction
91                    .process_welcome_message(welcome)
92                    .await;
93                assert!(innermost_source_matches!(join_welcome.unwrap_err(), LeafError::ConversationAlreadyExists(i) if i == &id));
94            })
95        .await;
96    }
97}