core_crypto/mls/conversation/mutable/
wipe.rs1use core_crypto_keystore::{entities::PersistedMlsGroup, traits::EntityDeleteBorrowed};
2
3use super::Result;
4use crate::{KeystoreError, OpenMlsError, RecursiveError, mls::conversation::ConversationMut};
5
6impl ConversationMut {
7 pub async fn wipe(&mut self) -> Result<()> {
12 let provider = self.crypto_provider().await?;
14 let mut conversation_cache = self
15 .tx_context
16 .mls_groups()
17 .await
18 .map_err(RecursiveError::transaction("getting mls conversation cache"))?;
19
20 self.mutate_group(async |transaction, group, _| {
21 let _ = group.delete_previous_epoch_keypairs(&provider).await;
24
25 let proposals = group
28 .pending_proposals()
29 .map(|proposal| proposal.proposal_reference().to_owned())
30 .collect::<Vec<_>>();
31 for proposal in proposals {
32 group
34 .remove_pending_proposal(transaction, &proposal)
35 .await
36 .map_err(OpenMlsError::wrap("removing pending proposal"))?;
37 }
38
39 Ok(())
40 })
41 .await?;
42
43 let id = self.id();
44 let context = self
45 .tx_context
46 .inner()
47 .await
48 .map_err(RecursiveError::transaction("getting inner context"))?;
49 let tx = context.transaction();
50 PersistedMlsGroup::delete_borrowed(tx, id.as_ref()).map_err(KeystoreError::wrap("deleting mls group"))?;
51 let _ = conversation_cache.remove(id);
52
53 drop(conversation_cache);
56
57 self.tx_context
60 .clear_orphaned_conversation_buffers(id)
61 .await
62 .map_err(RecursiveError::transaction(
63 "clearing buffered messages and commits of a wiped conversation",
64 ))?;
65
66 Ok(())
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use crate::{mls::conversation::Error, test_utils::*};
73
74 #[apply(all_cred_cipher)]
85 async fn wipe_abandons_buffered_messages(case: TestContext) {
86 Box::pin(async move {
87 let [mut alice, bob] = case.sessions().await;
88 let conversation = case.create_conversation([&alice, &bob]).await;
89
90 let conversation = conversation
92 .acting_as(&bob)
93 .await
94 .update()
95 .await
96 .process_member_changes()
97 .await
98 .finish();
99 let app_msg = conversation
100 .guard_of(&bob)
101 .await
102 .encrypt_message(b"Hello Alice !")
103 .await
104 .unwrap();
105
106 let decrypt = conversation.guard_of(&alice).await.decrypt_message(app_msg).await;
109 assert!(matches!(decrypt.unwrap_err(), Error::BufferedFutureMessage { .. }));
110 assert_eq!(
111 alice.transaction.count_entities().await.pending_messages,
112 1,
113 "the message Alice could not decrypt must have been buffered"
114 );
115
116 conversation.guard_of(&alice).await.wipe().await.unwrap();
118
119 drop(conversation);
120 alice.commit_transaction().await;
121
122 let counts = alice.transaction.count_entities().await;
123 assert_eq!(counts.group, 0, "the wipe must have removed the conversation");
124 assert_eq!(
125 counts.pending_messages, 0,
126 "the buffered message belongs to a conversation which no longer exists, so wiping that \
127 conversation must have taken the message with it"
128 );
129 })
130 .await
131 }
132
133 #[apply(all_cred_cipher)]
140 async fn wipe_abandons_buffered_commits(case: TestContext) {
141 Box::pin(async move {
142 let [mut alice, bob, charlie] = case.sessions().await;
143 let conversation = case.create_conversation([&alice, &bob, &charlie]).await;
144
145 let conversation = conversation
147 .acting_as(&bob)
148 .await
149 .remove_proposal(&charlie)
150 .await
151 .finish();
152
153 let commit_guard = conversation.acting_as(&bob).await.commit_pending_proposals().await;
156 let (commit_guard, result) = commit_guard.notify_member_fallible(&alice).await;
157 assert!(matches!(result.unwrap_err(), Error::BufferedCommit));
158 let conversation = commit_guard.finish();
159
160 assert_eq!(
161 alice.transaction.count_entities().await.buffered_commits,
162 1,
163 "the commit Alice could not apply must have been buffered"
164 );
165
166 conversation.guard_of(&alice).await.wipe().await.unwrap();
168
169 drop(conversation);
170 alice.commit_transaction().await;
171
172 let counts = alice.transaction.count_entities().await;
173 assert_eq!(counts.group, 0, "the wipe must have removed the conversation");
174 assert_eq!(
175 counts.buffered_commits, 0,
176 "the buffered commit belongs to a conversation which no longer exists, so wiping that \
177 conversation must have taken the commit with it"
178 );
179 })
180 .await
181 }
182}