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 mut conversation_cache = self
14 .tx_context
15 .mls_groups()
16 .await
17 .map_err(RecursiveError::context("getting mls conversation cache"))?;
18
19 self.mutate_group(async |transaction, group, _| {
20 let proposals = group
23 .pending_proposals()
24 .map(|proposal| proposal.proposal_reference().to_owned())
25 .collect::<Vec<_>>();
26 for proposal in proposals {
27 group
29 .remove_pending_proposal(transaction, &proposal)
30 .await
31 .map_err(OpenMlsError::wrap("removing pending proposal"))?;
32 }
33
34 Ok(())
35 })
36 .await?;
37
38 let id = self.id();
39 let context = self
40 .tx_context
41 .inner()
42 .map_err(RecursiveError::context("getting inner context"))?;
43 let tx = context.transaction();
44 PersistedMlsGroup::delete_borrowed(tx, id.keystore()).map_err(KeystoreError::wrap("deleting mls group"))?;
45 let _ = conversation_cache.remove(id);
46
47 drop(conversation_cache);
50
51 Ok(())
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use crate::{mls::conversation::Error, test_utils::*};
58
59 #[apply(all_cred_cipher)]
61 async fn wipe_abandons_buffered_messages(case: TestContext) {
62 Box::pin(async move {
63 let [mut alice, bob] = case.sessions().await;
64 let conversation = case.create_conversation([&alice, &bob]).await;
65
66 let conversation = conversation
68 .acting_as(&bob)
69 .await
70 .update()
71 .await
72 .process_member_changes()
73 .await
74 .finish();
75 let app_msg = conversation
76 .guard_of(&bob)
77 .await
78 .encrypt_message(b"Hello Alice !")
79 .await
80 .unwrap();
81
82 let decrypt = conversation.guard_of(&alice).await.decrypt_message(app_msg).await;
85 assert!(matches!(decrypt.unwrap_err(), Error::BufferedFutureMessage { .. }));
86 assert_eq!(
87 alice.transaction.count_entities().await.pending_messages,
88 1,
89 "the message Alice could not decrypt must have been buffered"
90 );
91
92 conversation.guard_of(&alice).await.wipe().await.unwrap();
94
95 drop(conversation);
96 alice.commit_transaction().await;
97
98 let counts = alice.transaction.count_entities().await;
99 assert_eq!(counts.group, 0, "the wipe must have removed the conversation");
100 assert_eq!(
101 counts.pending_messages, 0,
102 "the buffered message belongs to a conversation which no longer exists, so wiping that \
103 conversation must have taken the message with it"
104 );
105 })
106 .await
107 }
108
109 #[apply(all_cred_cipher)]
111 async fn wipe_abandons_buffered_commits(case: TestContext) {
112 Box::pin(async move {
113 let [mut alice, bob, charlie] = case.sessions().await;
114 let conversation = case.create_conversation([&alice, &bob, &charlie]).await;
115
116 let conversation = conversation
118 .acting_as(&bob)
119 .await
120 .remove_proposal(&charlie)
121 .await
122 .finish();
123
124 let commit_guard = conversation.acting_as(&bob).await.commit_pending_proposals().await;
127 let (commit_guard, result) = commit_guard.notify_member_fallible(&alice).await;
128 assert!(matches!(result.unwrap_err(), Error::BufferedCommit));
129 let conversation = commit_guard.finish();
130
131 assert_eq!(
132 alice.transaction.count_entities().await.buffered_commits,
133 1,
134 "the commit Alice could not apply must have been buffered"
135 );
136
137 conversation.guard_of(&alice).await.wipe().await.unwrap();
139
140 drop(conversation);
141 alice.commit_transaction().await;
142
143 let counts = alice.transaction.count_entities().await;
144 assert_eq!(counts.group, 0, "the wipe must have removed the conversation");
145 assert_eq!(
146 counts.buffered_commits, 0,
147 "the buffered commit belongs to a conversation which no longer exists, so wiping that \
148 conversation must have taken the commit with it"
149 );
150 })
151 .await
152 }
153}