1use errors::EmptyInputError;
6use openmls_traits::{signatures::Signer, storage::StorageProvider as _};
7use proposal_store::QueuedRemoveProposal;
8
9use super::{
10 errors::{AddMembersError, LeaveGroupError, RemoveMembersError},
11 *,
12};
13use crate::{
14 binary_tree::array_representation::LeafNodeIndex,
15 group::{SwapMembersError, WelcomeCommitMessages},
16 key_packages::KeyPackage,
17 messages::group_info::GroupInfo,
18 storage::OpenMlsProvider,
19 treesync::LeafNode,
20};
21
22impl MlsGroup {
23 #[allow(clippy::type_complexity)]
41 pub fn add_members<Provider: OpenMlsProvider>(
42 &mut self,
43 provider: &Provider,
44 signer: &impl Signer,
45 key_packages: &[KeyPackage],
46 ) -> Result<
47 (MlsMessageOut, MlsMessageOut, Option<GroupInfo>),
48 AddMembersError<Provider::StorageError>,
49 > {
50 self.add_members_internal(provider, signer, key_packages, true)
51 }
52
53 pub fn swap_members<Provider: OpenMlsProvider>(
64 &mut self,
65 provider: &Provider,
66 signer: &impl Signer,
67 members: &[LeafNodeIndex],
68 key_packages: &[KeyPackage],
69 ) -> Result<WelcomeCommitMessages, SwapMembersError<Provider::StorageError>> {
70 self.is_operational()?;
71
72 if members.is_empty() {
73 return Err(EmptyInputError::RemoveMembers.into());
74 }
75
76 if key_packages.is_empty() {
77 return Err(EmptyInputError::AddMembers.into());
78 }
79
80 if members.len() != key_packages.len() {
81 return Err(SwapMembersError::InvalidInput);
82 }
83
84 let bundle = self
85 .commit_builder()
86 .propose_removals(members.iter().cloned())
87 .propose_adds(key_packages.iter().cloned())
88 .load_psks(provider.storage())?
89 .build(provider.rand(), provider.crypto(), signer, |_| true)?
90 .stage_commit(provider)?;
91
92 self.reset_aad();
93
94 Ok(bundle.try_into()?)
95 }
96
97 #[allow(clippy::type_complexity)]
118 pub fn add_members_without_update<Provider: OpenMlsProvider>(
119 &mut self,
120 provider: &Provider,
121 signer: &impl Signer,
122 key_packages: &[KeyPackage],
123 ) -> Result<
124 (MlsMessageOut, MlsMessageOut, Option<GroupInfo>),
125 AddMembersError<Provider::StorageError>,
126 > {
127 self.add_members_internal(provider, signer, key_packages, false)
128 }
129
130 #[allow(clippy::type_complexity)]
131 fn add_members_internal<Provider: OpenMlsProvider>(
132 &mut self,
133 provider: &Provider,
134 signer: &impl Signer,
135 key_packages: &[KeyPackage],
136 force_self_update: bool,
137 ) -> Result<
138 (MlsMessageOut, MlsMessageOut, Option<GroupInfo>),
139 AddMembersError<Provider::StorageError>,
140 > {
141 self.is_operational()?;
142
143 if key_packages.is_empty() {
144 return Err(AddMembersError::EmptyInput(EmptyInputError::AddMembers));
145 }
146
147 let bundle = self
148 .commit_builder()
149 .propose_adds(key_packages.iter().cloned())
150 .force_self_update(force_self_update)
151 .load_psks(provider.storage())?
152 .build(provider.rand(), provider.crypto(), signer, |_| true)?
153 .stage_commit(provider)?;
154
155 let welcome: MlsMessageOut = bundle.to_welcome_msg().ok_or(LibraryError::custom(
156 "No secrets to generate commit message.",
157 ))?;
158 let (commit, _, group_info) = bundle.into_contents();
159
160 self.reset_aad();
161
162 Ok((commit, welcome, group_info))
163 }
164
165 pub fn own_leaf(&self) -> Option<&LeafNode> {
167 self.public_group().leaf(self.own_leaf_index())
168 }
169
170 #[allow(clippy::type_complexity)]
186 pub fn remove_members<Provider: OpenMlsProvider>(
187 &mut self,
188 provider: &Provider,
189 signer: &impl Signer,
190 members: &[LeafNodeIndex],
191 ) -> Result<
192 (MlsMessageOut, Option<MlsMessageOut>, Option<GroupInfo>),
193 RemoveMembersError<Provider::StorageError>,
194 > {
195 self.is_operational()?;
196
197 if members.is_empty() {
198 return Err(RemoveMembersError::EmptyInput(
199 EmptyInputError::RemoveMembers,
200 ));
201 }
202
203 let bundle = self
204 .commit_builder()
205 .propose_removals(members.iter().cloned())
206 .load_psks(provider.storage())?
207 .build(provider.rand(), provider.crypto(), signer, |_| true)?
208 .stage_commit(provider)?;
209
210 let welcome = bundle.to_welcome_msg();
211 let (commit, _, group_info) = bundle.into_contents();
212
213 provider
214 .storage()
215 .write_group_state(self.group_id(), &self.group_state)
216 .map_err(RemoveMembersError::StorageError)?;
217
218 self.reset_aad();
219 Ok((commit, welcome, group_info))
220 }
221
222 pub fn leave_group<Provider: OpenMlsProvider>(
229 &mut self,
230 provider: &Provider,
231 signer: &impl Signer,
232 ) -> Result<MlsMessageOut, LeaveGroupError<Provider::StorageError>> {
233 self.is_operational()?;
234
235 let removed = self.own_leaf_index();
236 let remove_proposal = self
237 .create_remove_proposal(self.framing_parameters(), removed, signer)
238 .map_err(|_| LibraryError::custom("Creating a self removal should not fail"))?;
239
240 let ciphersuite = self.ciphersuite();
241 let queued_remove_proposal = QueuedProposal::from_authenticated_content_by_ref(
242 ciphersuite,
243 provider.crypto(),
244 remove_proposal.clone(),
245 )?;
246
247 provider
248 .storage()
249 .queue_proposal(
250 self.group_id(),
251 &queued_remove_proposal.proposal_reference(),
252 &queued_remove_proposal,
253 )
254 .map_err(LeaveGroupError::StorageError)?;
255
256 self.proposal_store_mut().add(queued_remove_proposal);
257
258 self.reset_aad();
259 Ok(self.content_to_mls_message(remove_proposal, provider)?)
260 }
261
262 pub fn leave_group_via_self_remove<Provider: OpenMlsProvider>(
274 &mut self,
275 provider: &Provider,
276 signer: &impl Signer,
277 ) -> Result<MlsMessageOut, LeaveGroupError<Provider::StorageError>> {
278 self.is_operational()?;
279
280 if matches!(
281 self.configuration().wire_format_policy().outgoing(),
282 OutgoingWireFormatPolicy::AlwaysCiphertext
283 ) {
284 return Err(LeaveGroupError::CannotSelfRemoveWithPureCiphertext);
285 }
286 let self_remove_proposal =
287 self.create_self_remove_proposal(self.framing_parameters().aad(), signer)?;
288
289 let ciphersuite = self.ciphersuite();
290 let queued_self_remove_proposal = QueuedProposal::from_authenticated_content_by_ref(
291 ciphersuite,
292 provider.crypto(),
293 self_remove_proposal.clone(),
294 )?;
295
296 provider
297 .storage()
298 .queue_proposal(
299 self.group_id(),
300 &queued_self_remove_proposal.proposal_reference(),
301 &queued_self_remove_proposal,
302 )
303 .map_err(LeaveGroupError::StorageError)?;
304
305 self.proposal_store_mut().add(queued_self_remove_proposal);
306
307 self.reset_aad();
308 Ok(self.content_to_mls_message(self_remove_proposal, provider)?)
309 }
310
311 pub fn members(&self) -> impl Iterator<Item = Member> + '_ {
313 self.public_group().members()
314 }
315
316 pub fn member_leaf_index(&self, credential: &Credential) -> Option<LeafNodeIndex> {
319 self.members()
320 .find(|m| &m.credential == credential)
321 .map(|m| m.index)
322 }
323
324 pub fn member(&self, leaf_index: LeafNodeIndex) -> Option<&Credential> {
327 self.public_group()
328 .leaf(leaf_index)
330 .map(|leaf| leaf.credential())
331 }
332
333 pub fn member_at(&self, leaf_index: LeafNodeIndex) -> Option<Member> {
336 self.public_group()
337 .leaf(leaf_index)
339 .map(|leaf_node| {
340 Member::new(
341 leaf_index,
342 leaf_node.encryption_key().as_slice().to_vec(),
343 leaf_node.signature_key().as_slice().to_vec(),
344 leaf_node.credential().clone(),
345 )
346 })
347 }
348}
349
350#[derive(Debug)]
354pub enum RemoveOperation {
355 WeLeft,
358 WeWereRemovedBy(Sender),
360 TheyLeft(LeafNodeIndex),
364 TheyWereRemovedBy((LeafNodeIndex, Sender)),
366 WeRemovedThem(LeafNodeIndex),
368}
369
370impl RemoveOperation {
371 pub fn new(
374 queued_remove_proposal: QueuedRemoveProposal,
375 group: &MlsGroup,
376 ) -> Result<Self, LibraryError> {
377 let own_index = group.own_leaf_index();
378 let sender = queued_remove_proposal.sender();
379 let removed = queued_remove_proposal.remove_proposal().removed();
380
381 if let Sender::Member(leaf_index) = sender {
383 if *leaf_index == own_index {
385 if removed == own_index {
386 return Ok(Self::WeLeft);
388 } else {
389 return Ok(Self::WeRemovedThem(removed));
391 }
392 }
393
394 if removed == *leaf_index {
396 return Ok(Self::TheyLeft(removed));
397 }
398 }
399
400 if removed == own_index {
404 Ok(Self::WeWereRemovedBy(sender.clone()))
406 } else {
407 Ok(Self::TheyWereRemovedBy((removed, sender.clone())))
409 }
410 }
411}