1pub(crate) mod state;
4
5use std::collections::{HashMap, HashSet};
6use std::fmt::Debug;
7use std::marker::PhantomData;
8
9use petgraph::prelude::DiGraphMap;
10use petgraph::visit::{DfsPostOrder, IntoNodeIdentifiers, NodeIndexable, Reversed};
11#[cfg(any(test, feature = "serde"))]
12use serde::de::DeserializeOwned;
13#[cfg(any(test, feature = "serde"))]
14use serde::{Deserialize, Serialize};
15use thiserror::Error;
16
17use crate::access::Access;
18use crate::group::{
19 GroupAction, GroupControlMessage, GroupMember, GroupMembersState, GroupMembershipError,
20};
21use crate::traits::{Conditions, IdentityHandle, Operation, OperationId, Orderer, Resolver};
22
23const MAX_NESTED_DEPTH: u32 = 1000;
29
30#[derive(Debug, Error)]
32pub enum GroupCrdtInnerError<OP> {
33 #[error("states {0:?} not found")]
34 StatesNotFound(Vec<OP>),
35}
36
37#[derive(Debug, Error)]
39pub enum GroupCrdtError<ID, OP, C, RS, ORD>
40where
41 ID: IdentityHandle,
42 OP: OperationId + Ord,
43 RS: Resolver<ID, OP, C, ORD::Operation>,
44 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
45{
46 #[error(transparent)]
47 Inner(#[from] GroupCrdtInnerError<OP>),
48
49 #[error("duplicate operation {0} processed in group {1}")]
50 DuplicateOperation(OP, ID),
51
52 #[error("group cycle detected adding {0} to {1} operation={2}")]
53 GroupCycle(ID, ID, OP),
54
55 #[error("state change error processing operation {0}: {1:?}")]
56 StateChangeError(OP, GroupMembershipError<GroupMember<ID>>),
57
58 #[error("attempted to add group {0} with manage access")]
59 ManagerGroupsNotAllowed(ID),
60
61 #[error("orderer error: {0}")]
62 Orderer(ORD::Error),
63
64 #[error("resolver error: {0}")]
65 Resolver(RS::Error),
66}
67
68pub(crate) type GroupStates<ID, C> = HashMap<ID, GroupMembersState<GroupMember<ID>, C>>;
69
70#[derive(Debug)]
73#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
74#[cfg_attr(any(test, feature = "serde"), derive(Deserialize, Serialize))]
75pub struct GroupCrdtInnerState<ID, OP, C, M>
76where
77 ID: IdentityHandle,
78 OP: OperationId + Ord,
79{
80 pub operations: HashMap<OP, M>,
82
83 pub ignore: HashSet<OP>,
85
86 pub mutual_removes: HashSet<OP>,
88
89 pub states: HashMap<OP, GroupStates<ID, C>>,
91
92 pub graph: DiGraphMap<OP, ()>,
94}
95
96impl<ID, OP, C, M> Default for GroupCrdtInnerState<ID, OP, C, M>
97where
98 ID: IdentityHandle,
99 OP: OperationId + Ord,
100{
101 fn default() -> Self {
102 Self {
103 operations: Default::default(),
104 ignore: Default::default(),
105 mutual_removes: Default::default(),
106 states: Default::default(),
107 graph: Default::default(),
108 }
109 }
110}
111
112impl<ID, OP, C, M> GroupCrdtInnerState<ID, OP, C, M>
113where
114 ID: IdentityHandle,
115 OP: OperationId + Ord,
116 C: Conditions,
117 M: Operation<ID, OP, GroupControlMessage<ID, C>>,
118{
119 pub fn heads(&self) -> HashSet<OP> {
121 self.graph
122 .clone()
130 .into_graph::<usize>()
131 .externals(petgraph::Direction::Outgoing)
132 .map(|idx| self.graph.from_index(idx.index()))
133 .collect::<HashSet<_>>()
134 }
135
136 pub fn current_state(&self) -> GroupStates<ID, C> {
141 self.merge_states(&self.heads())
142 .expect("states exist for processed operations")
143 }
144
145 pub fn state_at(
147 &self,
148 dependencies: &HashSet<OP>,
149 ) -> Result<GroupStates<ID, C>, GroupCrdtInnerError<OP>> {
150 self.merge_states(dependencies)
151 }
152
153 fn merge_states(
155 &self,
156 ids: &HashSet<OP>,
157 ) -> Result<GroupStates<ID, C>, GroupCrdtInnerError<OP>> {
158 let mut current_state = HashMap::new();
159 for id in ids {
160 let group_states = match self.states.get(id) {
162 Some(group_states) => group_states.clone(),
163 None => {
164 return Err(GroupCrdtInnerError::StatesNotFound(
165 ids.iter().cloned().collect(),
166 ));
167 }
168 };
169 for (id, state) in group_states.into_iter() {
170 current_state
171 .entry(id)
172 .and_modify(
173 |current_state: &mut GroupMembersState<GroupMember<ID>, C>| {
174 *current_state = state::merge(state.clone(), current_state.clone())
175 },
176 )
177 .or_insert(state);
178 }
179 }
180 Ok(current_state)
181 }
182
183 fn members_inner(
184 &self,
185 group_id: ID,
186 members: &mut HashMap<ID, Access<C>>,
187 root_access: Option<Access<C>>,
188 mut depth: u32,
189 ) {
190 if depth == MAX_NESTED_DEPTH {
192 return;
193 }
194 depth += 1;
195
196 let current_states = self.current_state();
197 let Some(group_state) = current_states.get(&group_id) else {
198 return;
199 };
200
201 for (member, access) in group_state.access_levels() {
202 let next_access = match root_access.clone() {
206 Some(root_access) => {
207 if access <= root_access {
208 access.clone()
209 } else {
210 root_access
211 }
212 }
213 None => access.clone(),
214 };
215
216 match member {
217 GroupMember::Individual(id) => {
218 members
220 .entry(id)
221 .and_modify(|current_access| {
222 if *current_access < next_access {
233 *current_access = next_access.clone();
234 }
235 })
236 .or_insert_with(|| next_access);
237 }
238 GroupMember::Group(id) => self.members_inner(id, members, Some(next_access), depth),
239 }
240 }
241 }
242
243 pub fn members(&self, group_id: ID) -> Vec<(ID, Access<C>)> {
245 let mut members = HashMap::new();
246 self.members_inner(group_id, &mut members, None, 0);
247 members.into_iter().collect()
248 }
249
250 pub(crate) fn would_create_cycle(&self, operation: &M) -> bool {
251 let control_message = operation.payload();
252 let parent_group_id = control_message.group_id();
253
254 if let GroupAction::Add {
255 member: GroupMember::Group(child_group_id),
256 ..
257 } = &operation.payload().action
258 {
259 let states = self.current_state();
260 let mut stack = vec![*child_group_id];
261 let mut visited = HashSet::new();
262
263 while let Some(child_group_id) = stack.pop() {
264 if !visited.insert(child_group_id) {
265 continue;
266 }
267 if child_group_id == parent_group_id {
268 return true;
270 }
271 if let Some(group_state) = states.get(&child_group_id) {
272 for (member, _) in group_state.access_levels() {
273 if let GroupMember::Group(id) = member {
274 stack.push(id);
275 }
276 }
277 }
278 }
279 }
280
281 false
282 }
283}
284
285#[derive(Debug)]
288#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
289#[cfg_attr(
290 any(test, feature = "serde"),
291 derive(Deserialize, Serialize),
292 serde(bound = "
293 ID: DeserializeOwned + Serialize,
294 OP: DeserializeOwned + Serialize,
295 C: DeserializeOwned + Serialize,
296 ORD::Operation: DeserializeOwned + Serialize,
297 ORD::State: DeserializeOwned + Serialize
298 ")
299)]
300pub struct GroupCrdtState<ID, OP, C, ORD>
301where
302 ID: IdentityHandle,
303 OP: OperationId + Ord,
304 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
305 ORD::Operation: Clone,
306{
307 pub inner: GroupCrdtInnerState<ID, OP, C, ORD::Operation>,
309
310 pub orderer_y: ORD::State,
312}
313
314impl<ID, OP, C, ORD> GroupCrdtState<ID, OP, C, ORD>
315where
316 ID: IdentityHandle,
317 OP: OperationId + Ord,
318 C: Conditions,
319 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
320 ORD::Operation: Clone,
321{
322 pub fn new(orderer_y: ORD::State) -> Self {
324 Self {
325 inner: GroupCrdtInnerState::default(),
326 orderer_y,
327 }
328 }
329
330 pub fn root_members(&self, group_id: ID) -> Vec<(GroupMember<ID>, Access<C>)> {
335 match self.inner.current_state().get(&group_id) {
336 Some(group_y) => group_y.access_levels(),
337 None => vec![],
338 }
339 }
340
341 pub fn members(&self, group_id: ID) -> Vec<(ID, Access<C>)> {
346 self.inner.members(group_id)
347 }
348
349 pub fn has_group(&self, group_id: ID) -> bool {
351 self.inner.current_state().contains_key(&group_id)
352 }
353}
354
355#[derive(Clone, Debug, Default)]
395pub struct GroupCrdt<ID, OP, C, RS, ORD> {
396 _phantom: PhantomData<(ID, OP, C, RS, ORD)>,
397}
398
399impl<ID, OP, C, RS, ORD> GroupCrdt<ID, OP, C, RS, ORD>
400where
401 ID: IdentityHandle,
402 OP: OperationId + Ord,
403 C: Conditions,
404 RS: Resolver<ID, OP, C, ORD::Operation, State = GroupCrdtInnerState<ID, OP, C, ORD::Operation>>,
405 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
406 ORD::Operation: Clone,
407{
408 pub fn init(orderer_y: ORD::State) -> GroupCrdtState<ID, OP, C, ORD> {
409 GroupCrdtState {
410 inner: GroupCrdtInnerState::default(),
411 orderer_y,
412 }
413 }
414
415 #[allow(clippy::type_complexity)]
420 pub fn prepare(
421 mut y: GroupCrdtState<ID, OP, C, ORD>,
422 action: &GroupControlMessage<ID, C>,
423 ) -> Result<(GroupCrdtState<ID, OP, C, ORD>, ORD::Operation), GroupCrdtError<ID, OP, C, RS, ORD>>
424 {
425 let ordering_y = y.orderer_y;
427 let (ordering_y, operation) =
428 ORD::next_message(ordering_y, action).map_err(GroupCrdtError::Orderer)?;
429 y.orderer_y = ordering_y;
430 Ok((y, operation))
431 }
432
433 #[allow(clippy::type_complexity)]
435 pub fn process(
436 mut y: GroupCrdtState<ID, OP, C, ORD>,
437 operation: &ORD::Operation,
438 ) -> Result<GroupCrdtState<ID, OP, C, ORD>, GroupCrdtError<ID, OP, C, RS, ORD>> {
439 let operation_id = operation.id();
440 let actor = operation.author();
441 let control_message = operation.payload();
442 let dependencies = HashSet::from_iter(operation.dependencies().clone());
443 let group_id = control_message.group_id();
444 let rebuild_required =
445 RS::rebuild_required(&y.inner, operation).map_err(GroupCrdtError::Resolver)?;
446
447 y = GroupCrdt::validate(y, operation)?;
454 y = Self::add_operation(y, operation);
455
456 if rebuild_required {
457 y.inner = RS::process(y.inner).map_err(GroupCrdtError::Resolver)?;
458 return Ok(y);
459 }
460
461 let mut groups_y = y.inner.state_at(&dependencies)?;
464 groups_y = apply_action(
465 groups_y,
466 group_id,
467 operation_id,
468 actor,
469 &control_message.action,
470 &y.inner.ignore,
471 )
472 .state()
473 .to_owned();
474
475 y.inner.states.insert(operation_id, groups_y);
476
477 Ok(y)
478 }
479
480 #[allow(clippy::type_complexity)]
492 pub(crate) fn validate(
493 y: GroupCrdtState<ID, OP, C, ORD>,
494 operation: &ORD::Operation,
495 ) -> Result<GroupCrdtState<ID, OP, C, ORD>, GroupCrdtError<ID, OP, C, RS, ORD>> {
496 if y.inner.operations.contains_key(&operation.id()) {
498 return Err(GroupCrdtError::DuplicateOperation(
500 operation.id(),
501 operation.payload().group_id(),
502 ));
503 }
504
505 match &operation.payload().action {
512 GroupAction::Add { member, access } | GroupAction::Promote { member, access } => {
513 if member.is_group() && access.is_manage() {
514 return Err(GroupCrdtError::ManagerGroupsNotAllowed(member.id()));
515 }
516 }
517 _ => (),
518 };
519
520 let last_graph = y.inner.graph.clone();
521 let last_ignore = y.inner.ignore.clone();
522 let last_mutual_removes = y.inner.mutual_removes.clone();
523 let last_states = y.inner.states.clone();
524
525 let dependencies = HashSet::from_iter(operation.dependencies().clone());
526
527 let temp_y = if y.inner.heads() != dependencies {
530 let mut temp_y = y;
531
532 let mut predecessors = HashSet::new();
534 for dependency in operation.dependencies() {
535 let reversed = Reversed(&temp_y.inner.graph);
536 let mut dfs_rev = DfsPostOrder::new(&reversed, dependency);
537 while let Some(id) = dfs_rev.next(&reversed) {
538 predecessors.insert(id);
539 }
540 }
541
542 let to_remove: Vec<_> = temp_y
544 .inner
545 .graph
546 .node_identifiers()
547 .filter(|n| !predecessors.contains(n))
548 .collect();
549
550 for node in &to_remove {
551 temp_y.inner.graph.remove_node(*node);
552 }
553
554 temp_y.inner = RS::process(temp_y.inner).map_err(GroupCrdtError::Resolver)?;
555 temp_y
556 } else {
557 y
558 };
559
560 if temp_y.inner.would_create_cycle(operation) {
562 let parent_group = operation.payload().group_id();
563
564 let GroupAction::Add {
566 member: sub_group, ..
567 } = operation.payload().action
568 else {
569 unreachable!()
570 };
571
572 return Err(GroupCrdtError::GroupCycle(
573 parent_group,
574 sub_group.id(),
575 operation.id(),
576 ));
577 }
578
579 let result = apply_action(
581 temp_y.inner.current_state(),
582 operation.payload().group_id(),
583 operation.id(),
584 operation.author(),
585 &operation.payload().action,
586 &temp_y.inner.ignore,
587 );
588
589 match result {
590 StateChangeResult::Ok { state } => state,
591 StateChangeResult::Error { error, .. } => {
592 return Err(GroupCrdtError::StateChangeError(operation.id(), error));
595 }
596 StateChangeResult::Filtered { .. } => {
597 unreachable!();
599 }
600 };
601
602 let mut y = temp_y;
603 y.inner.graph = last_graph;
604 y.inner.ignore = last_ignore;
605 y.inner.mutual_removes = last_mutual_removes;
606 y.inner.states = last_states;
607
608 Ok(y)
609 }
610
611 fn add_operation(
615 mut y: GroupCrdtState<ID, OP, C, ORD>,
616 operation: &ORD::Operation,
617 ) -> GroupCrdtState<ID, OP, C, ORD> {
618 let operation_id = operation.id();
619 let dependencies = operation.dependencies();
620
621 y.inner.graph.add_node(operation_id);
623 for dependency in &dependencies {
624 y.inner.graph.add_edge(*dependency, operation_id, ());
625 }
626
627 y.inner.operations.insert(operation_id, operation.clone());
629
630 y
631 }
632}
633
634pub(crate) fn apply_action<ID, OP, C>(
636 mut groups_y: GroupStates<ID, C>,
637 group_id: ID,
638 id: OP,
639 actor: ID,
640 action: &GroupAction<ID, C>,
641 filter: &HashSet<OP>,
642) -> StateChangeResult<ID, C>
643where
644 ID: IdentityHandle,
645 OP: OperationId + Ord,
646 C: Conditions,
647{
648 let members_y = if action.is_create() {
649 GroupMembersState::default()
650 } else {
651 groups_y
652 .remove(&group_id)
653 .expect("group already present in states map")
654 };
655
656 if filter.contains(&id) {
657 groups_y.insert(group_id, members_y);
658 return StateChangeResult::Filtered { state: groups_y };
659 }
660
661 let result = match action.clone() {
662 GroupAction::Add { member, access, .. } => state::add(
663 members_y.clone(),
664 GroupMember::Individual(actor),
665 member,
666 access,
667 ),
668 GroupAction::Remove { member, .. } => {
669 state::remove(members_y.clone(), GroupMember::Individual(actor), member)
670 }
671 GroupAction::Promote { member, access } => state::promote(
672 members_y.clone(),
673 GroupMember::Individual(actor),
674 member,
675 access,
676 ),
677 GroupAction::Demote { member, access } => state::demote(
678 members_y.clone(),
679 GroupMember::Individual(actor),
680 member,
681 access,
682 ),
683 GroupAction::Create { initial_members } => Ok(state::create(&initial_members)),
684 };
685
686 match result {
687 Ok(members_y_i) => {
688 groups_y.insert(group_id, members_y_i);
689 StateChangeResult::Ok { state: groups_y }
690 }
691 Err(err) => {
692 groups_y.insert(group_id, members_y);
706 StateChangeResult::Error {
707 state: groups_y,
708 error: err,
709 }
710 }
711 }
712}
713
714pub(crate) fn apply_remove_unsafe<ID, C>(
717 mut groups_y: GroupStates<ID, C>,
718 group_id: ID,
719 removed: GroupMember<ID>,
720) -> GroupStates<ID, C>
721where
722 ID: IdentityHandle,
723 C: Conditions,
724{
725 let mut members_y = groups_y
726 .remove(&group_id)
727 .expect("group already present in states map");
728
729 members_y.members.entry(removed).and_modify(|state| {
730 if state.member_counter % 2 != 0 {
731 state.member_counter += 1
732 }
733 });
734 groups_y.insert(group_id, members_y);
735 groups_y
736}
737
738pub enum StateChangeResult<ID, C>
740where
741 ID: IdentityHandle,
742 C: Conditions,
743{
744 Ok { state: GroupStates<ID, C> },
746
747 Error {
749 state: GroupStates<ID, C>,
750 #[allow(unused)]
751 error: GroupMembershipError<GroupMember<ID>>,
752 },
753
754 Filtered { state: GroupStates<ID, C> },
756}
757
758impl<ID, C> StateChangeResult<ID, C>
759where
760 ID: IdentityHandle,
761 C: Conditions,
762{
763 pub fn state(&self) -> &GroupStates<ID, C> {
764 match self {
765 StateChangeResult::Ok { state }
766 | StateChangeResult::Error { state, .. }
767 | StateChangeResult::Filtered { state } => state,
768 }
769 }
770}
771
772#[cfg(test)]
773pub(crate) mod tests {
774 use crate::Access;
775 use crate::group::{GroupCrdtError, GroupMember, GroupMembershipError};
776 use crate::test_utils::no_ord::{TestGroup, TestGroupState};
777 use crate::test_utils::{
778 add_member, create_group, demote_member, promote_member, remove_member,
779 };
780 use crate::traits::Operation;
781
782 const G1: char = '1';
783 const G2: char = '2';
784 const G3: char = '3';
785 const G4: char = '4';
786
787 const ALICE: char = 'A';
788 const BOB: char = 'B';
789 const CLAIRE: char = 'C';
790 const DAN: char = 'D';
791 const EVE: char = 'E';
792
793 #[test]
794 fn group_operations() {
795 let y = TestGroupState::new(());
796
797 let op1 = create_group(
798 ALICE,
799 0,
800 G1,
801 vec![(GroupMember::Individual(ALICE), Access::manage())],
802 vec![],
803 );
804
805 let y_i = TestGroup::process(y, &op1).unwrap();
806 let mut members = y_i.members(G1);
807 members.sort();
808 assert_eq!(members, vec![(ALICE, Access::manage())]);
809
810 let op2 = add_member(
811 ALICE,
812 1,
813 G1,
814 GroupMember::Individual(BOB),
815 Access::read(),
816 vec![op1.id()],
817 );
818
819 let y_ii = TestGroup::process(y_i, &op2).unwrap();
820 let mut members = y_ii.members(G1);
821 members.sort();
822 assert_eq!(
823 members,
824 vec![(ALICE, Access::manage()), (BOB, Access::read())]
825 );
826
827 let op3 = add_member(
828 ALICE,
829 2,
830 G1,
831 GroupMember::Individual(CLAIRE),
832 Access::write(),
833 vec![op2.id()],
834 );
835
836 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
837 let mut members = y_iii.members(G1);
838 members.sort();
839 assert_eq!(
840 members,
841 vec![
842 (ALICE, Access::manage()),
843 (BOB, Access::read()),
844 (CLAIRE, Access::write())
845 ]
846 );
847
848 let op4 = remove_member(ALICE, 3, G1, GroupMember::Individual(BOB), vec![op3.id()]);
849
850 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
851 let mut members = y_iv.members(G1);
852 members.sort();
853 assert_eq!(
854 members,
855 vec![(ALICE, Access::manage()), (CLAIRE, Access::write())]
856 );
857 }
858
859 #[test]
860 fn concurrent_removal() {
861 let y = TestGroupState::new(());
862
863 let op1 = create_group(
864 ALICE,
865 0,
866 G1,
867 vec![(GroupMember::Individual(ALICE), Access::manage())],
868 vec![],
869 );
870
871 let y_i = TestGroup::process(y, &op1).unwrap();
872 let mut members = y_i.members(G1);
873 members.sort();
874 assert_eq!(members, vec![(ALICE, Access::manage())]);
875
876 let op2 = add_member(
877 ALICE,
878 1,
879 G1,
880 GroupMember::Individual(BOB),
881 Access::manage(),
882 vec![op1.id()],
883 );
884
885 let y_ii = TestGroup::process(y_i, &op2).unwrap();
886 let mut members = y_ii.members(G1);
887 members.sort();
888 assert_eq!(
889 members,
890 vec![(ALICE, Access::manage()), (BOB, Access::manage())]
891 );
892
893 let op3 = add_member(
894 BOB,
895 2,
896 G1,
897 GroupMember::Individual(CLAIRE),
898 Access::write(),
899 vec![op2.id()],
900 );
901
902 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
903 let mut members = y_iii.members(G1);
904 members.sort();
905 assert_eq!(
906 members,
907 vec![
908 (ALICE, Access::manage()),
909 (BOB, Access::manage()),
910 (CLAIRE, Access::write())
911 ]
912 );
913
914 let op4 = remove_member(ALICE, 3, G1, GroupMember::Individual(BOB), vec![op2.id()]);
915
916 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
917 let mut members = y_iv.members(G1);
918 members.sort();
919 assert_eq!(members, vec![(ALICE, Access::manage())]);
920 }
921
922 #[test]
923 fn mutual_concurrent_removal() {
924 let y = TestGroupState::new(());
925
926 let op1 = create_group(
927 ALICE,
928 0,
929 G1,
930 vec![(GroupMember::Individual(ALICE), Access::manage())],
931 vec![],
932 );
933
934 let y_i = TestGroup::process(y, &op1).unwrap();
935 let mut members = y_i.members(G1);
936 members.sort();
937 assert_eq!(members, vec![(ALICE, Access::manage())]);
938
939 let op2 = add_member(
940 ALICE,
941 1,
942 G1,
943 GroupMember::Individual(BOB),
944 Access::manage(),
945 vec![op1.id()],
946 );
947
948 let y_ii = TestGroup::process(y_i, &op2).unwrap();
949 let mut members = y_ii.members(G1);
950 members.sort();
951 assert_eq!(
952 members,
953 vec![(ALICE, Access::manage()), (BOB, Access::manage())]
954 );
955
956 let op3 = add_member(
957 BOB,
958 2,
959 G1,
960 GroupMember::Individual(CLAIRE),
961 Access::manage(),
962 vec![op2.id()],
963 );
964
965 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
966 let mut members = y_iii.members(G1);
967 members.sort();
968 assert_eq!(
969 members,
970 vec![
971 (ALICE, Access::manage()),
972 (BOB, Access::manage()),
973 (CLAIRE, Access::manage())
974 ]
975 );
976
977 let op4 = remove_member(BOB, 3, G1, GroupMember::Individual(CLAIRE), vec![op3.id()]);
978
979 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
980 let mut members = y_iv.members(G1);
981 members.sort();
982 assert_eq!(
983 members,
984 vec![(ALICE, Access::manage()), (BOB, Access::manage())]
985 );
986
987 let op5 = remove_member(CLAIRE, 4, G1, GroupMember::Individual(BOB), vec![op3.id()]);
988
989 let y_v = TestGroup::process(y_iv, &op5).unwrap();
990 let mut members = y_v.members(G1);
991 members.sort();
992 assert_eq!(members, vec![(ALICE, Access::manage())]);
993 }
994
995 #[test]
996 fn nested_groups() {
997 let y = TestGroupState::new(());
998
999 let op1 = create_group(
1000 ALICE,
1001 0,
1002 G1,
1003 vec![(GroupMember::Individual(ALICE), Access::manage())],
1004 vec![],
1005 );
1006
1007 let y_i = TestGroup::process(y, &op1).unwrap();
1008 let mut members = y_i.members(G1);
1009 members.sort();
1010 assert_eq!(members, vec![(ALICE, Access::manage())]);
1011
1012 let op2 = create_group(
1013 BOB,
1014 1,
1015 G2,
1016 vec![(GroupMember::Individual(BOB), Access::manage())],
1017 vec![op1.id()],
1018 );
1019
1020 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1021 let mut members = y_ii.members(G2);
1022 members.sort();
1023 assert_eq!(members, vec![(BOB, Access::manage())]);
1024
1025 let op3 = add_member(
1026 ALICE,
1027 2,
1028 G1,
1029 GroupMember::Group(G2),
1030 Access::read(),
1031 vec![op2.id()],
1032 );
1033
1034 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
1035 let mut members = y_iii.members(G1);
1036 members.sort();
1037 assert_eq!(
1038 members,
1039 vec![(ALICE, Access::manage()), (BOB, Access::read())]
1040 );
1041 }
1042
1043 #[test]
1044 fn error_on_unauthorized_add() {
1045 let y = TestGroupState::new(());
1046
1047 let op1 = create_group(
1048 ALICE,
1049 0,
1050 G1,
1051 vec![(GroupMember::Individual(ALICE), Access::manage())],
1052 vec![],
1053 );
1054
1055 let y_i = TestGroup::process(y, &op1).unwrap();
1056
1057 let op2 = add_member(
1058 ALICE,
1059 1,
1060 G1,
1061 GroupMember::Individual(BOB),
1062 Access::read(),
1063 vec![op1.id()],
1064 );
1065
1066 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1067
1068 let op3 = add_member(
1069 BOB,
1070 2,
1071 G1,
1072 GroupMember::Individual(CLAIRE),
1073 Access::read(),
1074 vec![op2.id()],
1075 );
1076
1077 assert!(TestGroup::process(y_ii, &op3).is_err());
1078 }
1079
1080 #[test]
1081 fn error_on_remove_non_member() {
1082 let y = TestGroupState::new(());
1083
1084 let op1 = create_group(
1085 ALICE,
1086 0,
1087 G1,
1088 vec![(GroupMember::Individual(ALICE), Access::manage())],
1089 vec![],
1090 );
1091
1092 let y_i = TestGroup::process(y, &op1).unwrap();
1093
1094 let op2 = remove_member(ALICE, 1, G1, GroupMember::Individual(BOB), vec![op1.id()]);
1095
1096 assert!(TestGroup::process(y_i, &op2).is_err());
1097 }
1098
1099 #[test]
1100 fn error_on_promote_non_member() {
1101 let y = TestGroupState::new(());
1102
1103 let op1 = create_group(
1104 ALICE,
1105 0,
1106 G1,
1107 vec![(GroupMember::Individual(ALICE), Access::manage())],
1108 vec![],
1109 );
1110
1111 let y_i = TestGroup::process(y, &op1).unwrap();
1112
1113 let op2 = promote_member(
1114 ALICE,
1115 1,
1116 G1,
1117 GroupMember::Individual(BOB),
1118 Access::manage(),
1119 vec![op1.id()],
1120 );
1121
1122 assert!(TestGroup::process(y_i, &op2).is_err());
1123 }
1124
1125 #[test]
1126 fn error_on_add_manager_group() {
1127 let y = TestGroupState::new(());
1128
1129 let op1 = create_group(
1130 ALICE,
1131 0,
1132 G1,
1133 vec![(GroupMember::Individual(ALICE), Access::manage())],
1134 vec![],
1135 );
1136
1137 let y_i = TestGroup::process(y, &op1).unwrap();
1138
1139 let op2 = add_member(
1140 ALICE,
1141 1,
1142 G1,
1143 GroupMember::Group(BOB),
1144 Access::manage(),
1145 vec![op1.id()],
1146 );
1147
1148 assert!(TestGroup::process(y_i, &op2).is_err());
1149 }
1150
1151 #[test]
1152 fn error_on_demote_non_member() {
1153 let y = TestGroupState::new(());
1154
1155 let op1 = create_group(
1156 ALICE,
1157 0,
1158 G1,
1159 vec![(GroupMember::Individual(ALICE), Access::manage())],
1160 vec![],
1161 );
1162
1163 let y_i = TestGroup::process(y, &op1).unwrap();
1164
1165 let op2 = demote_member(
1166 ALICE,
1167 1,
1168 G1,
1169 GroupMember::Individual(BOB),
1170 Access::read(),
1171 vec![op1.id()],
1172 );
1173
1174 assert!(TestGroup::process(y_i, &op2).is_err());
1175 }
1176
1177 #[test]
1178 fn error_on_add_existing_member() {
1179 let y = TestGroupState::new(());
1180
1181 let op1 = create_group(
1182 ALICE,
1183 0,
1184 G1,
1185 vec![(GroupMember::Individual(ALICE), Access::manage())],
1186 vec![],
1187 );
1188
1189 let y_i = TestGroup::process(y, &op1).unwrap();
1190
1191 let op2 = add_member(
1192 ALICE,
1193 1,
1194 G1,
1195 GroupMember::Individual(ALICE),
1196 Access::manage(),
1197 vec![op1.id()],
1198 );
1199
1200 assert!(TestGroup::process(y_i, &op2).is_err());
1201 }
1202
1203 #[test]
1204 fn error_on_remove_nonexistent_subgroup() {
1205 let y = TestGroupState::new(());
1206
1207 let op1 = create_group(
1208 ALICE,
1209 0,
1210 G1,
1211 vec![(GroupMember::Individual(ALICE), Access::manage())],
1212 vec![],
1213 );
1214 let y_i = TestGroup::process(y, &op1).unwrap();
1215
1216 let op2 = remove_member(ALICE, 1, G1, GroupMember::Group(G2), vec![op1.id()]);
1218
1219 assert!(TestGroup::process(y_i, &op2).is_err());
1220 }
1221
1222 #[test]
1223 fn deeply_nested_groups_with_removals() {
1224 let y = TestGroupState::new(());
1225
1226 let op1 = create_group(
1228 ALICE,
1229 0,
1230 G1,
1231 vec![(GroupMember::Individual(ALICE), Access::manage())],
1232 vec![],
1233 );
1234 let y_i = TestGroup::process(y, &op1).unwrap();
1235
1236 let op2 = create_group(
1238 BOB,
1239 1,
1240 G2,
1241 vec![(GroupMember::Individual(BOB), Access::manage())],
1242 vec![op1.id()],
1243 );
1244 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1245
1246 let op3 = create_group(
1248 CLAIRE,
1249 2,
1250 G3,
1251 vec![(GroupMember::Individual(CLAIRE), Access::manage())],
1252 vec![op2.id()],
1253 );
1254 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
1255
1256 let op4 = create_group(
1258 DAN,
1259 3,
1260 G4,
1261 vec![(GroupMember::Individual(DAN), Access::write())],
1262 vec![op3.id()],
1263 );
1264 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
1265
1266 let op5 = add_member(
1268 CLAIRE,
1269 4,
1270 G3,
1271 GroupMember::Group(G4),
1272 Access::read(),
1273 vec![op4.id()],
1274 );
1275 let y_v = TestGroup::process(y_iv, &op5).unwrap();
1276
1277 let op6 = add_member(
1279 BOB,
1280 5,
1281 G2,
1282 GroupMember::Group(G3),
1283 Access::write(),
1284 vec![op5.id()],
1285 );
1286 let y_vi = TestGroup::process(y_v, &op6).unwrap();
1287
1288 let op7 = add_member(
1290 ALICE,
1291 6,
1292 G1,
1293 GroupMember::Group(G2),
1294 Access::read(),
1295 vec![op6.id()],
1296 );
1297 let y_vii = TestGroup::process(y_vi, &op7).unwrap();
1298
1299 let mut members = y_vii.members(G1);
1300 members.sort();
1301 assert_eq!(
1302 members,
1303 vec![
1304 (ALICE, Access::manage()),
1305 (BOB, Access::read()),
1306 (CLAIRE, Access::read()),
1307 (DAN, Access::read()),
1308 ]
1309 );
1310
1311 let op8 = remove_member(BOB, 7, G2, GroupMember::Group(G3), vec![op7.id()]);
1313 let y_viii = TestGroup::process(y_vii, &op8).unwrap();
1314
1315 let mut members_after_removal = y_viii.members(G1);
1316 members_after_removal.sort();
1317 assert_eq!(
1318 members_after_removal,
1319 vec![(ALICE, Access::manage()), (BOB, Access::read()),]
1320 );
1321 }
1322
1323 #[test]
1324 fn nested_groups_with_concurrent_removal_and_promotion() {
1325 let y = TestGroupState::new(());
1326
1327 let op1 = create_group(
1329 ALICE,
1330 0,
1331 G1,
1332 vec![(GroupMember::Individual(ALICE), Access::manage())],
1333 vec![],
1334 );
1335 let y_i = TestGroup::process(y, &op1).unwrap();
1336
1337 let op2 = create_group(
1339 BOB,
1340 1,
1341 G2,
1342 vec![(GroupMember::Individual(BOB), Access::manage())],
1343 vec![op1.id()],
1344 );
1345 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1346
1347 let op3 = create_group(
1349 CLAIRE,
1350 2,
1351 G3,
1352 vec![(GroupMember::Individual(CLAIRE), Access::manage())],
1353 vec![op2.id()],
1354 );
1355 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
1356
1357 let op4 = add_member(
1359 CLAIRE,
1360 3,
1361 G3,
1362 GroupMember::Individual(DAN),
1363 Access::write(),
1364 vec![op3.id()],
1365 );
1366 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
1367
1368 let op5 = add_member(
1370 BOB,
1371 4,
1372 G2,
1373 GroupMember::Group(G3),
1374 Access::write(),
1375 vec![op4.id()],
1376 );
1377 let y_v = TestGroup::process(y_iv, &op5).unwrap();
1378
1379 let op6 = add_member(
1381 BOB,
1382 5,
1383 G2,
1384 GroupMember::Individual(CLAIRE),
1385 Access::read(),
1386 vec![op5.id()],
1387 );
1388 let y_vi = TestGroup::process(y_v, &op6).unwrap();
1389
1390 let op7 = add_member(
1392 ALICE,
1393 6,
1394 G1,
1395 GroupMember::Group(G2),
1396 Access::read(),
1397 vec![op6.id()],
1398 );
1399 let y_vii = TestGroup::process(y_vi, &op7).unwrap();
1400
1401 let mut members = y_vii.members(G1);
1402 members.sort();
1403 assert_eq!(
1404 members,
1405 vec![
1406 (ALICE, Access::manage()),
1407 (BOB, Access::read()),
1408 (CLAIRE, Access::read()),
1409 (DAN, Access::read()),
1410 ]
1411 );
1412
1413 let op8_remove_g2 = remove_member(ALICE, 7, G1, GroupMember::Group(G2), vec![op7.id()]);
1415 let op9_promote_claire = promote_member(
1416 BOB,
1417 8,
1418 G2,
1419 GroupMember::Individual(CLAIRE),
1420 Access::manage(),
1421 vec![op7.id()],
1422 );
1423
1424 let y_after_remove = TestGroup::process(y_vii.clone(), &op8_remove_g2).unwrap();
1426 let mut members = y_after_remove.members(G1);
1427 members.sort();
1428 assert_eq!(members, vec![(ALICE, Access::manage())]);
1429
1430 let y_after_both = TestGroup::process(y_after_remove, &op9_promote_claire).unwrap();
1432 let mut g1_members = y_after_both.members(G1);
1433 g1_members.sort();
1434 assert_eq!(g1_members, vec![(ALICE, Access::manage())]);
1435
1436 let mut g2_members = y_after_both.members(G2);
1437 g2_members.sort();
1438 assert_eq!(
1439 g2_members,
1440 vec![
1441 (BOB, Access::manage()),
1442 (CLAIRE, Access::manage()),
1443 (DAN, Access::write()),
1444 ]
1445 );
1446 }
1447
1448 #[test]
1449 fn concurrent_removal_ooo_processing() {
1450 let y = TestGroupState::new(());
1451
1452 let op1 = create_group(
1454 ALICE,
1455 0,
1456 G1,
1457 vec![(GroupMember::Individual(ALICE), Access::manage())],
1458 vec![],
1459 );
1460 let y_i = TestGroup::process(y, &op1).unwrap();
1461
1462 let op2 = add_member(
1464 ALICE,
1465 1,
1466 G1,
1467 GroupMember::Individual(BOB),
1468 Access::manage(),
1469 vec![op1.id()],
1470 );
1471 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1472
1473 let op3 = add_member(
1475 BOB,
1476 2,
1477 G1,
1478 GroupMember::Individual(CLAIRE),
1479 Access::read(),
1480 vec![op2.id()],
1481 );
1482
1483 let op4 = remove_member(ALICE, 3, G1, GroupMember::Individual(BOB), vec![op2.id()]);
1485
1486 let y_iii_a = TestGroup::process(y_ii.clone(), &op3).unwrap();
1488 let y_iv_a = TestGroup::process(y_iii_a, &op4).unwrap();
1489
1490 let y_iii_b = TestGroup::process(y_ii.clone(), &op4).unwrap();
1492 let y_iv_b = TestGroup::process(y_iii_b, &op3).unwrap();
1493
1494 for (_, y) in [y_iv_a, y_iv_b].into_iter().enumerate() {
1495 let mut members = y.members(G1);
1496 members.sort();
1497 assert_eq!(members, vec![(ALICE, Access::manage())],);
1498 }
1499 }
1500
1501 #[test]
1502 fn concurrent_add_with_insufficient_access() {
1503 let y0 = TestGroupState::new(());
1504
1505 let op1 = create_group(
1507 ALICE,
1508 0,
1509 G1,
1510 vec![(GroupMember::Individual(ALICE), Access::manage())],
1511 vec![],
1512 );
1513 let y1 = TestGroup::process(y0, &op1).unwrap();
1514
1515 let op2 = add_member(
1517 ALICE,
1518 1,
1519 G1,
1520 GroupMember::Individual(BOB),
1521 Access::manage(),
1522 vec![op1.id()],
1523 );
1524
1525 let op3 = add_member(
1527 BOB,
1528 2,
1529 G1,
1530 GroupMember::Individual(EVE),
1531 Access::read(),
1532 vec![op1.id()],
1533 );
1534
1535 let result = TestGroup::process(y1.clone(), &op3);
1537 assert!(matches!(
1538 result,
1539 Err(GroupCrdtError::StateChangeError(
1540 _,
1541 GroupMembershipError::UnrecognisedActor(_)
1542 ))
1543 ));
1544
1545 let y1_alt = TestGroup::process(y1, &op2).unwrap();
1547 let result = TestGroup::process(y1_alt.clone(), &op3);
1548 assert!(matches!(
1549 result,
1550 Err(GroupCrdtError::StateChangeError(
1551 _,
1552 GroupMembershipError::UnrecognisedActor(_)
1553 ))
1554 ));
1555
1556 let mut members = y1_alt.members(G1);
1558 members.sort();
1559 assert_eq!(
1560 members,
1561 vec![(ALICE, Access::manage()), (BOB, Access::manage())]
1562 );
1563 }
1564
1565 #[test]
1566 fn add_group_with_concurrent_change() {
1567 let y = TestGroupState::new(());
1568
1569 let op1 = create_group(
1571 ALICE,
1572 0,
1573 G1,
1574 vec![(GroupMember::Individual(ALICE), Access::manage())],
1575 vec![],
1576 );
1577 let y_i = TestGroup::process(y, &op1).unwrap();
1578
1579 let op2 = create_group(
1581 BOB,
1582 1,
1583 G2,
1584 vec![(GroupMember::Individual(BOB), Access::manage())],
1585 vec![op1.id()],
1586 );
1587 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1588
1589 let op3a = add_member(
1591 ALICE,
1592 2,
1593 G1,
1594 GroupMember::Group(G2),
1595 Access::read(),
1596 vec![op2.id()],
1597 );
1598
1599 let op3b = add_member(
1601 BOB,
1602 3,
1603 G2,
1604 GroupMember::Individual(CLAIRE),
1605 Access::write(),
1606 vec![op2.id()],
1607 );
1608
1609 let y_iii = TestGroup::process(y_ii.clone(), &op3a).unwrap();
1611 let y_iv = TestGroup::process(y_iii, &op3b).unwrap();
1612
1613 let mut members_1 = y_iv.members(G1);
1614 members_1.sort();
1615 assert_eq!(
1616 members_1,
1617 vec![
1618 (ALICE, Access::manage()),
1619 (BOB, Access::read()),
1620 (CLAIRE, Access::read())
1621 ]
1622 );
1623
1624 let y_iii_alt = TestGroup::process(y_ii.clone(), &op3b).unwrap();
1626 let y_iv_alt = TestGroup::process(y_iii_alt, &op3a).unwrap();
1627
1628 let mut members_1 = y_iv_alt.members(G1);
1629 members_1.sort();
1630 assert_eq!(
1631 members_1,
1632 vec![
1633 (ALICE, Access::manage()),
1634 (BOB, Access::read()),
1635 (CLAIRE, Access::read())
1636 ]
1637 );
1638 }
1639
1640 #[test]
1641 fn nested_group_cycle_error() {
1642 let y = TestGroupState::new(());
1643
1644 let op1 = create_group(
1646 ALICE,
1647 0,
1648 G1,
1649 vec![(GroupMember::Individual(ALICE), Access::manage())],
1650 vec![],
1651 );
1652 let y_i = TestGroup::process(y, &op1).unwrap();
1653
1654 let op2 = create_group(
1656 BOB,
1657 1,
1658 G2,
1659 vec![
1660 (GroupMember::Individual(BOB), Access::manage()),
1661 (GroupMember::Group(G1), Access::read()),
1662 ],
1663 vec![op1.id()],
1664 );
1665 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1666
1667 let op3 = add_member(
1669 ALICE,
1670 2,
1671 G1,
1672 GroupMember::Group(G2),
1673 Access::read(),
1674 vec![op2.id()],
1675 );
1676
1677 let result = TestGroup::process(y_ii, &op3);
1679 assert!(
1680 result.is_err(),
1681 "Creating a group cycle should cause an error"
1682 );
1683 }
1684
1685 #[test]
1686 fn serde_to_from_bytes() {
1687 let y = TestGroupState::new(());
1688 let op1 = create_group(
1689 ALICE,
1690 0,
1691 G1,
1692 vec![(GroupMember::Individual(ALICE), Access::manage())],
1693 vec![],
1694 );
1695 let y_i = TestGroup::process(y, &op1).unwrap();
1696 let members = y_i.members(G1);
1697 assert_eq!(members, vec![(ALICE, Access::manage())]);
1698
1699 let mut bytes = vec![];
1701 ciborium::ser::into_writer(&y_i, &mut bytes).unwrap();
1702
1703 let y_i_de: TestGroupState = ciborium::from_reader(&bytes[..]).unwrap();
1705
1706 let members = y_i_de.members(G1);
1708 assert_eq!(members, vec![(ALICE, Access::manage())]);
1709 }
1710}