1use std::collections::{hash_map::Entry, HashMap, HashSet};
2
3use openmls_traits::crypto::OpenMlsCrypto;
4use openmls_traits::types::Ciphersuite;
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 binary_tree::array_representation::LeafNodeIndex,
9 ciphersuite::hash_ref::ProposalRef,
10 error::LibraryError,
11 framing::{mls_auth_content::AuthenticatedContent, mls_content::FramedContentBody, Sender},
12 group::errors::*,
13 messages::proposals::{
14 AddProposal, PreSharedKeyProposal, Proposal, ProposalOrRef, ProposalOrRefType,
15 ProposalType, RemoveProposal, UpdateProposal,
16 },
17 utils::vector_converter,
18};
19
20#[cfg(feature = "extensions-draft-08")]
21use crate::messages::proposals::AppEphemeralProposal;
22
23#[derive(Debug, Clone)]
24pub(crate) struct SelfRemoveInStore {
25 pub(crate) sender: LeafNodeIndex,
26 pub(crate) proposal_ref: ProposalRef,
27}
28
29#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
32#[cfg_attr(any(test, feature = "test-utils"), derive(Clone))]
33pub struct ProposalStore {
34 queued_proposals: Vec<QueuedProposal>,
35}
36
37impl ProposalStore {
38 pub fn new() -> Self {
40 Self {
41 queued_proposals: Vec::new(),
42 }
43 }
44 #[cfg(test)]
45 pub(crate) fn from_queued_proposal(queued_proposal: QueuedProposal) -> Self {
46 Self {
47 queued_proposals: vec![queued_proposal],
48 }
49 }
50 pub(crate) fn add(&mut self, queued_proposal: QueuedProposal) {
51 self.queued_proposals.push(queued_proposal);
52 }
53 pub(crate) fn proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
54 self.queued_proposals.iter()
55 }
56 pub(crate) fn is_empty(&self) -> bool {
57 self.queued_proposals.is_empty()
58 }
59 pub(crate) fn empty(&mut self) {
60 self.queued_proposals.clear();
61 }
62
63 pub(crate) fn remove(&mut self, proposal_ref: &ProposalRef) -> Option<()> {
66 let index = self
67 .queued_proposals
68 .iter()
69 .position(|p| &p.proposal_reference() == proposal_ref)?;
70 self.queued_proposals.remove(index);
71 Some(())
72 }
73
74 pub(crate) fn self_removes(&self) -> Vec<SelfRemoveInStore> {
75 self.queued_proposals
76 .iter()
77 .filter_map(|queued_proposal| {
78 match (queued_proposal.proposal(), queued_proposal.sender()) {
79 (Proposal::SelfRemove, Sender::Member(sender_index)) => {
80 Some(SelfRemoveInStore {
81 sender: *sender_index,
82 proposal_ref: queued_proposal.proposal_reference(),
83 })
84 }
85 _ => None,
86 }
87 })
88 .collect()
89 }
90}
91
92#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
95pub struct QueuedProposal {
96 proposal: Proposal,
97 proposal_reference: ProposalRef,
98 sender: Sender,
99 proposal_or_ref_type: ProposalOrRefType,
100}
101
102impl QueuedProposal {
103 pub(crate) fn from_authenticated_content_by_ref(
105 ciphersuite: Ciphersuite,
106 crypto: &impl OpenMlsCrypto,
107 public_message: AuthenticatedContent,
108 ) -> Result<Self, LibraryError> {
109 Self::from_authenticated_content(
110 ciphersuite,
111 crypto,
112 public_message,
113 ProposalOrRefType::Reference,
114 )
115 }
116
117 pub(crate) fn from_authenticated_content(
119 ciphersuite: Ciphersuite,
120 crypto: &impl OpenMlsCrypto,
121 public_message: AuthenticatedContent,
122 proposal_or_ref_type: ProposalOrRefType,
123 ) -> Result<Self, LibraryError> {
124 let proposal_reference =
125 ProposalRef::from_authenticated_content_by_ref(crypto, ciphersuite, &public_message)
126 .map_err(|_| LibraryError::custom("Could not calculate `ProposalRef`."))?;
127
128 let (body, sender) = public_message.into_body_and_sender();
129
130 let proposal = match body {
131 FramedContentBody::Proposal(p) => p,
132 _ => return Err(LibraryError::custom("Wrong content type")),
133 };
134
135 Ok(Self {
136 proposal,
137 proposal_reference,
138 sender,
139 proposal_or_ref_type,
140 })
141 }
142
143 pub(crate) fn from_proposal_and_sender(
149 ciphersuite: Ciphersuite,
150 crypto: &impl OpenMlsCrypto,
151 proposal: Proposal,
152 sender: &Sender,
153 ) -> Result<Self, LibraryError> {
154 let proposal_reference = ProposalRef::from_raw_proposal(ciphersuite, crypto, &proposal)?;
155 Ok(Self {
156 proposal,
157 proposal_reference,
158 sender: sender.clone(),
159 proposal_or_ref_type: ProposalOrRefType::Proposal,
160 })
161 }
162
163 pub fn proposal(&self) -> &Proposal {
165 &self.proposal
166 }
167 pub(crate) fn proposal_reference(&self) -> ProposalRef {
169 self.proposal_reference.clone()
170 }
171
172 pub(crate) fn proposal_reference_ref(&self) -> &ProposalRef {
174 &self.proposal_reference
175 }
176
177 pub fn proposal_or_ref_type(&self) -> ProposalOrRefType {
179 self.proposal_or_ref_type
180 }
181 pub fn sender(&self) -> &Sender {
183 &self.sender
184 }
185}
186
187struct OrderedProposalRefs {
190 proposal_refs: HashSet<ProposalRef>,
191 ordered_proposal_refs: Vec<ProposalRef>,
192}
193
194impl OrderedProposalRefs {
195 fn new() -> Self {
196 Self {
197 proposal_refs: HashSet::new(),
198 ordered_proposal_refs: Vec::new(),
199 }
200 }
201
202 fn add(&mut self, proposal_ref: ProposalRef) {
205 if self.proposal_refs.insert(proposal_ref.clone()) {
208 self.ordered_proposal_refs.push(proposal_ref);
209 }
210 }
211
212 fn iter(&self) -> impl Iterator<Item = &ProposalRef> {
215 self.ordered_proposal_refs.iter()
216 }
217}
218
219#[derive(Default, Debug, Serialize, Deserialize)]
225#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
226pub struct ProposalQueue {
227 proposal_references: Vec<ProposalRef>,
230 #[serde(with = "vector_converter")]
233 queued_proposals: HashMap<ProposalRef, QueuedProposal>,
234}
235
236impl ProposalQueue {
237 pub(crate) fn is_empty(&self) -> bool {
240 self.proposal_references.is_empty()
241 }
242
243 pub(crate) fn from_committed_proposals(
248 ciphersuite: Ciphersuite,
249 crypto: &impl OpenMlsCrypto,
250 committed_proposals: Vec<ProposalOrRef>,
251 proposal_store: &ProposalStore,
252 sender: &Sender,
253 ) -> Result<Self, FromCommittedProposalsError> {
254 log::debug!("from_committed_proposals");
255 let mut proposals_by_reference_queue: HashMap<ProposalRef, QueuedProposal> = HashMap::new();
258 for queued_proposal in proposal_store.proposals() {
259 proposals_by_reference_queue.insert(
260 queued_proposal.proposal_reference(),
261 queued_proposal.clone(),
262 );
263 }
264 log::trace!(" known proposals:\n{proposals_by_reference_queue:#?}");
265 let mut proposal_queue = ProposalQueue::default();
267
268 log::trace!(" committed proposals ...");
270 for proposal_or_ref in committed_proposals.into_iter() {
271 log::trace!(" proposal_or_ref:\n{proposal_or_ref:#?}");
272 let queued_proposal = match proposal_or_ref {
273 ProposalOrRef::Proposal(proposal) => {
274 if proposal
276 .as_remove()
277 .and_then(|remove_proposal| {
278 sender.as_member().filter(|leaf_index| {
279 remove_proposal.removed() == *leaf_index
281 })
282 })
283 .is_some()
284 {
285 return Err(FromCommittedProposalsError::SelfRemoval);
286 };
287
288 QueuedProposal::from_proposal_and_sender(
289 ciphersuite,
290 crypto,
291 *proposal,
292 sender,
293 )?
294 }
295 ProposalOrRef::Reference(ref proposal_reference) => {
296 match proposals_by_reference_queue.get(proposal_reference) {
297 Some(queued_proposal) => {
298 if let Proposal::Remove(ref remove_proposal) = queued_proposal.proposal
300 {
301 if let Sender::Member(leaf_index) = sender {
302 if remove_proposal.removed() == *leaf_index {
303 return Err(FromCommittedProposalsError::SelfRemoval);
304 }
305 }
306 }
307
308 queued_proposal.clone()
309 }
310 None => return Err(FromCommittedProposalsError::ProposalNotFound),
311 }
312 }
313 };
314 proposal_queue.add(queued_proposal);
315 }
316
317 Ok(proposal_queue)
318 }
319
320 pub fn get(&self, proposal_reference: &ProposalRef) -> Option<&QueuedProposal> {
322 self.queued_proposals.get(proposal_reference)
323 }
324
325 pub(crate) fn add(&mut self, queued_proposal: QueuedProposal) {
327 let proposal_reference = queued_proposal.proposal_reference();
328 if let Entry::Vacant(entry) = self.queued_proposals.entry(proposal_reference.clone()) {
330 self.proposal_references.push(proposal_reference);
332 entry.insert(queued_proposal);
334 }
335 }
336
337 pub(crate) fn filtered_by_type(
340 &self,
341 proposal_type: ProposalType,
342 ) -> impl Iterator<Item = &QueuedProposal> {
343 self.proposal_references
345 .iter()
346 .filter(move |&pr| match self.queued_proposals.get(pr) {
347 Some(p) => p.proposal.is_type(proposal_type),
348 None => false,
349 })
350 .filter_map(move |reference| self.get(reference))
351 }
352
353 pub(crate) fn queued_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
356 self.proposal_references
358 .iter()
359 .filter_map(move |reference| self.get(reference))
360 }
361
362 pub(crate) fn add_proposals(&self) -> impl Iterator<Item = QueuedAddProposal<'_>> {
365 self.queued_proposals().filter_map(|queued_proposal| {
366 if let Proposal::Add(add_proposal) = queued_proposal.proposal() {
367 let sender = queued_proposal.sender();
368 Some(QueuedAddProposal {
369 add_proposal,
370 sender,
371 })
372 } else {
373 None
374 }
375 })
376 }
377
378 pub(crate) fn remove_proposals(&self) -> impl Iterator<Item = QueuedRemoveProposal<'_>> {
381 self.queued_proposals().filter_map(|queued_proposal| {
382 if let Proposal::Remove(remove_proposal) = queued_proposal.proposal() {
383 let sender = queued_proposal.sender();
384 Some(QueuedRemoveProposal {
385 remove_proposal,
386 sender,
387 })
388 } else {
389 None
390 }
391 })
392 }
393
394 pub(crate) fn update_proposals(&self) -> impl Iterator<Item = QueuedUpdateProposal<'_>> {
397 self.queued_proposals().filter_map(|queued_proposal| {
398 if let Proposal::Update(update_proposal) = queued_proposal.proposal() {
399 let sender = queued_proposal.sender();
400 Some(QueuedUpdateProposal {
401 update_proposal,
402 sender,
403 })
404 } else {
405 None
406 }
407 })
408 }
409
410 pub(crate) fn psk_proposals(&self) -> impl Iterator<Item = QueuedPskProposal<'_>> {
413 self.queued_proposals().filter_map(|queued_proposal| {
414 if let Proposal::PreSharedKey(psk_proposal) = queued_proposal.proposal() {
415 let sender = queued_proposal.sender();
416 Some(QueuedPskProposal {
417 psk_proposal,
418 sender,
419 })
420 } else {
421 None
422 }
423 })
424 }
425
426 #[cfg(feature = "extensions-draft-08")]
427 pub(crate) fn app_ephemeral_proposals(
430 &self,
431 ) -> impl Iterator<Item = QueuedAppEphemeralProposal<'_>> {
432 self.queued_proposals().filter_map(|queued_proposal| {
433 if let Proposal::AppEphemeral(app_ephemeral_proposal) = queued_proposal.proposal() {
434 let sender = queued_proposal.sender();
435 Some(QueuedAppEphemeralProposal {
436 app_ephemeral_proposal,
437 sender,
438 })
439 } else {
440 None
441 }
442 })
443 }
444
445 pub(crate) fn filter_proposals(
468 iter: impl IntoIterator<Item = QueuedProposal>,
469 own_index: LeafNodeIndex,
470 ) -> Result<(Self, bool), ProposalQueueError> {
471 let mut adds: OrderedProposalRefs = OrderedProposalRefs::new();
474 let mut valid_proposals: OrderedProposalRefs = OrderedProposalRefs::new();
475 let mut proposal_pool: HashMap<ProposalRef, QueuedProposal> = HashMap::new();
476 let mut contains_own_updates = false;
477 let mut contains_external_init = false;
478
479 let mut member_specific_proposals: HashMap<LeafNodeIndex, QueuedProposal> = HashMap::new();
480 let mut register_member_specific_proposal =
481 |member: LeafNodeIndex, proposal: QueuedProposal| {
482 match member_specific_proposals.entry(member) {
484 Entry::Vacant(vacant_entry) => {
486 vacant_entry.insert(proposal);
487 }
488 Entry::Occupied(mut occupied_entry)
491 if occupied_entry
492 .get()
493 .proposal()
494 .has_lower_priority_than(&proposal.proposal) =>
495 {
496 occupied_entry.insert(proposal);
497 }
498 Entry::Occupied(_) => {}
500 }
501 };
502
503 for queued_proposal in iter {
505 proposal_pool.insert(
506 queued_proposal.proposal_reference(),
507 queued_proposal.clone(),
508 );
509 match queued_proposal.proposal {
510 Proposal::Add(_) => {
511 adds.add(queued_proposal.proposal_reference());
512 }
513 Proposal::Update(_) => {
514 let Sender::Member(sender_index) = queued_proposal.sender() else {
517 return Err(ProposalQueueError::UpdateFromExternalSender);
518 };
519 if sender_index == &own_index {
520 contains_own_updates = true;
521 continue;
522 }
523 register_member_specific_proposal(*sender_index, queued_proposal);
524 }
525 Proposal::Remove(ref remove_proposal) => {
526 let removed = remove_proposal.removed();
527 register_member_specific_proposal(removed, queued_proposal);
528 }
529 Proposal::PreSharedKey(_) => {
530 valid_proposals.add(queued_proposal.proposal_reference());
531 }
532 Proposal::ReInit(_) => {
533 }
535 Proposal::ExternalInit(_) => {
536 if !contains_external_init {
538 valid_proposals.add(queued_proposal.proposal_reference());
539 contains_external_init = true;
540 }
541 }
542 Proposal::GroupContextExtensions(_) => {
543 valid_proposals.add(queued_proposal.proposal_reference());
544 }
545 Proposal::SelfRemove => {
546 let Sender::Member(removed) = queued_proposal.sender() else {
547 return Err(ProposalQueueError::SelfRemoveFromNonMember);
548 };
549 register_member_specific_proposal(*removed, queued_proposal);
550 }
551 #[cfg(feature = "extensions-draft-08")]
552 Proposal::AppEphemeral(_) => {
553 valid_proposals.add(queued_proposal.proposal_reference());
554 }
555 Proposal::Custom(_) => {
556 valid_proposals.add(queued_proposal.proposal_reference());
559 }
560 }
561 }
562
563 for proposal in member_specific_proposals.values() {
565 valid_proposals.add(proposal.proposal_reference());
566 }
567
568 let mut proposal_queue = ProposalQueue::default();
570 for proposal_reference in adds.iter().chain(valid_proposals.iter()) {
571 let queued_proposal = proposal_pool
572 .get(proposal_reference)
573 .cloned()
574 .ok_or(ProposalQueueError::ProposalNotFound)?;
575 proposal_queue.add(queued_proposal);
576 }
577 Ok((proposal_queue, contains_own_updates))
578 }
579
580 #[cfg(test)]
583 pub(crate) fn contains(&self, proposal_reference_list: &[ProposalRef]) -> bool {
584 for proposal_reference in proposal_reference_list {
585 if !self.queued_proposals.contains_key(proposal_reference) {
586 return false;
587 }
588 }
589 true
590 }
591
592 pub(crate) fn commit_list(&self) -> Vec<ProposalOrRef> {
594 self.proposal_references
596 .iter()
597 .filter_map(|proposal_reference| self.queued_proposals.get(proposal_reference))
598 .map(|queued_proposal| {
599 match queued_proposal.proposal_or_ref_type {
601 ProposalOrRefType::Proposal => {
602 ProposalOrRef::proposal(queued_proposal.proposal.clone())
603 }
604 ProposalOrRefType::Reference => {
605 ProposalOrRef::reference(queued_proposal.proposal_reference.clone())
606 }
607 }
608 })
609 .collect::<Vec<ProposalOrRef>>()
610 }
611}
612
613impl Extend<QueuedProposal> for ProposalQueue {
614 fn extend<T: IntoIterator<Item = QueuedProposal>>(&mut self, iter: T) {
615 for proposal in iter {
616 self.add(proposal)
617 }
618 }
619}
620
621impl IntoIterator for ProposalQueue {
622 type Item = QueuedProposal;
623
624 type IntoIter = std::collections::hash_map::IntoValues<ProposalRef, QueuedProposal>;
625
626 fn into_iter(self) -> Self::IntoIter {
627 self.queued_proposals.into_values()
628 }
629}
630
631impl<'a> IntoIterator for &'a ProposalQueue {
632 type Item = &'a QueuedProposal;
633
634 type IntoIter = std::collections::hash_map::Values<'a, ProposalRef, QueuedProposal>;
635
636 fn into_iter(self) -> Self::IntoIter {
637 self.queued_proposals.values()
638 }
639}
640
641impl FromIterator<QueuedProposal> for ProposalQueue {
642 fn from_iter<T: IntoIterator<Item = QueuedProposal>>(iter: T) -> Self {
643 let mut out = Self::default();
644 out.extend(iter);
645 out
646 }
647}
648
649#[derive(PartialEq, Debug)]
651pub struct QueuedAddProposal<'a> {
652 add_proposal: &'a AddProposal,
653 sender: &'a Sender,
654}
655
656impl QueuedAddProposal<'_> {
657 pub fn add_proposal(&self) -> &AddProposal {
659 self.add_proposal
660 }
661
662 pub fn sender(&self) -> &Sender {
664 self.sender
665 }
666}
667
668#[derive(PartialEq, Eq, Debug)]
670pub struct QueuedRemoveProposal<'a> {
671 remove_proposal: &'a RemoveProposal,
672 sender: &'a Sender,
673}
674
675impl QueuedRemoveProposal<'_> {
676 pub fn remove_proposal(&self) -> &RemoveProposal {
678 self.remove_proposal
679 }
680
681 pub fn sender(&self) -> &Sender {
683 self.sender
684 }
685}
686
687#[derive(PartialEq, Eq, Debug)]
689pub struct QueuedUpdateProposal<'a> {
690 update_proposal: &'a UpdateProposal,
691 sender: &'a Sender,
692}
693
694impl QueuedUpdateProposal<'_> {
695 pub fn update_proposal(&self) -> &UpdateProposal {
697 self.update_proposal
698 }
699
700 pub fn sender(&self) -> &Sender {
702 self.sender
703 }
704}
705
706#[derive(PartialEq, Eq, Debug)]
708pub struct QueuedPskProposal<'a> {
709 psk_proposal: &'a PreSharedKeyProposal,
710 sender: &'a Sender,
711}
712
713impl QueuedPskProposal<'_> {
714 pub fn psk_proposal(&self) -> &PreSharedKeyProposal {
716 self.psk_proposal
717 }
718
719 pub fn sender(&self) -> &Sender {
721 self.sender
722 }
723}
724
725#[cfg(feature = "extensions-draft-08")]
726#[derive(PartialEq, Debug)]
728pub struct QueuedAppEphemeralProposal<'a> {
729 app_ephemeral_proposal: &'a AppEphemeralProposal,
730 sender: &'a Sender,
731}
732
733#[cfg(feature = "extensions-draft-08")]
734impl QueuedAppEphemeralProposal<'_> {
735 pub fn app_ephemeral_proposal(&self) -> &AppEphemeralProposal {
737 self.app_ephemeral_proposal
738 }
739
740 pub fn sender(&self) -> &Sender {
742 self.sender
743 }
744}