1pub(crate) mod state;
4
5use std::collections::{HashMap, HashSet};
6use std::fmt::Debug;
7use std::marker::PhantomData;
8
9use p2panda_core::traits::{Author, OperationId};
10use petgraph::prelude::DiGraphMap;
11use petgraph::visit::{Bfs, DfsPostOrder, IntoNodeIdentifiers, NodeIndexable, Reversed};
12#[cfg(any(test, feature = "serde"))]
13use serde::{Deserialize, Serialize};
14use thiserror::Error;
15
16use crate::access::Access;
17use crate::group::{GroupAction, GroupMember, GroupMembersState, GroupMembershipError};
18use crate::traits::{Conditions, Operation, Resolver};
19
20const MAX_NESTED_DEPTH: u32 = 1000;
26
27#[derive(Debug, Error)]
29pub enum GroupCrdtInnerError<OP> {
30 #[error("states {0:?} not found")]
31 StatesNotFound(Vec<OP>),
32}
33
34#[derive(Debug, Error)]
36pub enum GroupCrdtError<ID, OP, M, C, RS>
37where
38 ID: Author,
39 OP: OperationId + Ord,
40 RS: Resolver<ID, OP, M, C>,
41{
42 #[error(transparent)]
43 Inner(#[from] GroupCrdtInnerError<OP>),
44
45 #[error("duplicate operation {0} processed in group {1}")]
46 DuplicateOperation(OP, ID),
47
48 #[error("group cycle detected adding {0} to {1} operation={2}")]
49 GroupCycle(ID, ID, OP),
50
51 #[error("state change error processing operation {0}: {1:?}")]
52 StateChangeError(OP, GroupMembershipError<GroupMember<ID>>),
53
54 #[error("attempted to add group {0} with manage access")]
55 ManagerGroupsNotAllowed(ID),
56
57 #[error("resolver error: {0}")]
58 Resolver(RS::Error),
59}
60
61pub(crate) type GroupStates<ID, C> = HashMap<ID, GroupMembersState<GroupMember<ID>, C>>;
62
63#[derive(Debug)]
66#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
67#[cfg_attr(
68 any(test, feature = "serde"),
69 derive(Deserialize, Serialize),
70 serde(bound(
71 deserialize = "
72 OP: Deserialize<'de>,
73 M: Deserialize<'de>,
74 C: Deserialize<'de>,
75 ",
76 serialize = "
77 OP: Serialize,
78 M: Serialize,
79 C: Serialize,
80 "
81 ))
82)]
83pub struct GroupCrdtInnerState<ID, OP, M, C>
84where
85 ID: Author,
86 OP: OperationId + Ord,
87{
88 pub operations: HashMap<OP, M>,
90
91 pub ignore: HashSet<OP>,
93
94 pub mutual_removes: HashSet<OP>,
96
97 pub states: HashMap<OP, GroupStates<ID, C>>,
99
100 pub graph: DiGraphMap<OP, ()>,
102}
103
104impl<ID, OP, M, C> Default for GroupCrdtInnerState<ID, OP, M, C>
105where
106 ID: Author,
107 OP: OperationId + Ord,
108{
109 fn default() -> Self {
110 Self {
111 operations: Default::default(),
112 ignore: Default::default(),
113 mutual_removes: Default::default(),
114 states: Default::default(),
115 graph: Default::default(),
116 }
117 }
118}
119
120impl<ID, OP, M, C> GroupCrdtInnerState<ID, OP, M, C>
121where
122 ID: Author,
123 OP: OperationId + Ord,
124 M: Operation<ID, OP, C>,
125 C: Conditions,
126{
127 pub fn heads(&self) -> HashSet<OP> {
129 self.graph
130 .clone()
138 .into_graph::<usize>()
139 .externals(petgraph::Direction::Outgoing)
140 .map(|idx| self.graph.from_index(idx.index()))
141 .collect::<HashSet<_>>()
142 }
143
144 pub fn heads_filtered(&self, groups: &[ID]) -> HashSet<OP> {
147 let global_heads = self.heads();
148 global_heads
149 .into_iter()
150 .filter(|id| {
151 let reversed = Reversed(&self.graph);
152 let mut bfs = Bfs::new(&reversed, *id);
153 while let Some(inner_id) = bfs.next(&reversed) {
154 let operation = self
155 .operations
156 .get(&inner_id)
157 .expect("operation is present in map");
158 if operation.action().is_create() && groups.contains(&operation.group_id()) {
159 return true;
160 }
161 }
162 false
163 })
164 .collect()
165 }
166
167 pub fn current_state(&self) -> GroupStates<ID, C> {
172 self.merge_states(&self.heads())
173 .expect("states exist for processed operations")
174 }
175
176 pub fn state_at(
178 &self,
179 dependencies: &HashSet<OP>,
180 ) -> Result<GroupStates<ID, C>, GroupCrdtInnerError<OP>> {
181 self.merge_states(dependencies)
182 }
183
184 fn merge_states(
186 &self,
187 ids: &HashSet<OP>,
188 ) -> Result<GroupStates<ID, C>, GroupCrdtInnerError<OP>> {
189 let mut current_state = HashMap::new();
190 for id in ids {
191 let group_states = match self.states.get(id) {
193 Some(group_states) => group_states.clone(),
194 None => {
195 return Err(GroupCrdtInnerError::StatesNotFound(
196 ids.iter().cloned().collect(),
197 ));
198 }
199 };
200 for (id, state) in group_states.into_iter() {
201 current_state
202 .entry(id)
203 .and_modify(
204 |current_state: &mut GroupMembersState<GroupMember<ID>, C>| {
205 *current_state = state::merge(state.clone(), current_state.clone())
206 },
207 )
208 .or_insert(state);
209 }
210 }
211 Ok(current_state)
212 }
213
214 fn members_inner(
215 &self,
216 group_id: ID,
217 members: &mut HashMap<GroupMember<ID>, Access<C>>,
218 root_access: Option<Access<C>>,
219 mut depth: u32,
220 ) {
221 if depth == MAX_NESTED_DEPTH {
223 return;
224 }
225 depth += 1;
226
227 let current_states = self.current_state();
228 let Some(group_state) = current_states.get(&group_id) else {
229 return;
230 };
231
232 for (member, access) in group_state.access_levels() {
233 let next_access = match root_access.clone() {
237 Some(root_access) => {
238 if access <= root_access {
239 access.clone()
240 } else {
241 root_access
242 }
243 }
244 None => access.clone(),
245 };
246
247 members
248 .entry(member)
249 .and_modify(|current_access| {
250 if *current_access < next_access {
260 *current_access = next_access.clone();
261 }
262 })
263 .or_insert_with(|| next_access.clone());
264
265 if let GroupMember::Group(id) = member {
266 self.members_inner(id, members, Some(next_access), depth)
267 }
268 }
269 }
270
271 pub fn members(&self, group_id: ID) -> Vec<(ID, Access<C>)> {
273 self.traverse_members(group_id, 0)
274 .into_iter()
275 .filter_map(|(member, access)| {
276 if member.is_individual() {
277 Some((member.id(), access))
278 } else {
279 None
280 }
281 })
282 .collect()
283 }
284
285 pub fn groups(&self, group_id: ID) -> Vec<(ID, Access<C>)> {
287 self.traverse_members(group_id, 0)
288 .into_iter()
289 .filter_map(|(member, access)| {
290 if member.is_group() {
291 Some((member.id(), access))
292 } else {
293 None
294 }
295 })
296 .collect()
297 }
298
299 pub fn traverse_members(&self, group_id: ID, depth: u32) -> Vec<(GroupMember<ID>, Access<C>)> {
304 let mut members = HashMap::new();
305 self.members_inner(group_id, &mut members, None, depth);
306 members.into_iter().collect()
307 }
308
309 pub(crate) fn would_create_cycle(&self, operation: &M) -> bool {
310 let parent_group_id = operation.group_id();
311
312 if let GroupAction::Add {
313 member: GroupMember::Group(child_group_id),
314 ..
315 } = &operation.action()
316 {
317 let states = self.current_state();
318 let mut stack = vec![*child_group_id];
319 let mut visited = HashSet::new();
320
321 while let Some(child_group_id) = stack.pop() {
322 if !visited.insert(child_group_id) {
323 continue;
324 }
325 if child_group_id == parent_group_id {
326 return true;
328 }
329 if let Some(group_state) = states.get(&child_group_id) {
330 for (member, _) in group_state.access_levels() {
331 if let GroupMember::Group(id) = member {
332 stack.push(id);
333 }
334 }
335 }
336 }
337 }
338
339 false
340 }
341}
342
343#[derive(Debug)]
346#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
347#[cfg_attr(
348 any(test, feature = "serde"),
349 derive(Deserialize, Serialize),
350 serde(bound(
351 deserialize = "
352 OP: Deserialize<'de>,
353 M: Deserialize<'de>,
354 C: Deserialize<'de>,
355 ",
356 serialize = "
357 OP: Serialize,
358 M: Serialize,
359 C: Serialize,
360 "
361 ))
362)]
363pub struct GroupCrdtState<ID, OP, M, C>
364where
365 ID: Author,
366 OP: OperationId + Ord,
367{
368 pub inner: GroupCrdtInnerState<ID, OP, M, C>,
370}
371
372impl<ID, OP, M, C> Default for GroupCrdtState<ID, OP, M, C>
373where
374 ID: Author,
375 OP: OperationId + Ord,
376 M: Operation<ID, OP, C>,
377 C: Conditions,
378{
379 fn default() -> Self {
380 Self {
381 inner: Default::default(),
382 }
383 }
384}
385
386impl<ID, OP, M, C> GroupCrdtState<ID, OP, M, C>
387where
388 ID: Author,
389 OP: OperationId + Ord,
390 M: Operation<ID, OP, C>,
391 C: Conditions,
392{
393 pub fn new() -> Self {
395 Self::default()
396 }
397
398 pub fn root_members(&self, group_id: ID) -> Vec<(GroupMember<ID>, Access<C>)> {
403 match self.inner.current_state().get(&group_id) {
404 Some(group_y) => group_y.access_levels(),
405 None => vec![],
406 }
407 }
408
409 pub fn members(&self, group_id: ID) -> Vec<(ID, Access<C>)> {
414 self.inner.members(group_id)
415 }
416
417 pub fn groups(&self, group_id: ID) -> Vec<(ID, Access<C>)> {
422 self.inner.groups(group_id)
423 }
424
425 pub fn traverse_members(&self, group_id: ID, depth: u32) -> Vec<(GroupMember<ID>, Access<C>)> {
430 self.inner.traverse_members(group_id, depth)
431 }
432
433 pub fn has_group(&self, group_id: ID) -> bool {
435 self.inner.current_state().contains_key(&group_id)
436 }
437
438 pub fn heads(&self) -> Vec<OP> {
440 self.inner.heads().into_iter().collect()
441 }
442
443 pub fn heads_filtered(&self, groups: &[ID]) -> Vec<OP> {
446 self.inner.heads_filtered(groups).into_iter().collect()
447 }
448}
449
450#[derive(Clone, Debug, Default)]
490pub struct GroupCrdt<ID, OP, M, C, RS> {
491 _phantom: PhantomData<(ID, OP, M, C, RS)>,
492}
493
494impl<ID, OP, M, C, RS> GroupCrdt<ID, OP, M, C, RS>
495where
496 ID: Author,
497 OP: OperationId + Ord,
498 M: Operation<ID, OP, C> + Clone,
499 C: Conditions,
500 RS: Resolver<ID, OP, M, C, State = GroupCrdtInnerState<ID, OP, M, C>>,
501{
502 pub fn init() -> GroupCrdtState<ID, OP, M, C> {
503 GroupCrdtState {
504 inner: GroupCrdtInnerState::default(),
505 }
506 }
507
508 #[allow(clippy::type_complexity)]
510 pub fn process(
511 mut y: GroupCrdtState<ID, OP, M, C>,
512 operation: &M,
513 ) -> Result<GroupCrdtState<ID, OP, M, C>, GroupCrdtError<ID, OP, M, C, RS>> {
514 let operation_id = operation.id();
515 let actor = operation.author();
516 let dependencies = HashSet::from_iter(operation.dependencies().clone());
517 let group_id = operation.group_id();
518 let rebuild_required =
519 RS::rebuild_required(&y.inner, operation).map_err(GroupCrdtError::Resolver)?;
520
521 y = GroupCrdt::validate(y, operation)?;
528 y = Self::add_operation(y, operation);
529
530 if rebuild_required {
531 y.inner = RS::process(y.inner).map_err(GroupCrdtError::Resolver)?;
532 return Ok(y);
533 }
534
535 let mut groups_y = y.inner.state_at(&dependencies)?;
538 groups_y = apply_action(
539 groups_y,
540 group_id,
541 operation_id,
542 actor,
543 &operation.action(),
544 &y.inner.ignore,
545 )
546 .state()
547 .to_owned();
548
549 y.inner.states.insert(operation_id, groups_y);
550
551 Ok(y)
552 }
553
554 #[allow(clippy::type_complexity)]
566 pub(crate) fn validate(
567 y: GroupCrdtState<ID, OP, M, C>,
568 operation: &M,
569 ) -> Result<GroupCrdtState<ID, OP, M, C>, GroupCrdtError<ID, OP, M, C, RS>> {
570 if y.inner.operations.contains_key(&operation.id()) {
572 return Err(GroupCrdtError::DuplicateOperation(
574 operation.id(),
575 operation.group_id(),
576 ));
577 }
578
579 match &operation.action() {
586 GroupAction::Add { member, access } | GroupAction::Promote { member, access }
587 if member.is_group() && access.is_manage() =>
588 {
589 return Err(GroupCrdtError::ManagerGroupsNotAllowed(member.id()));
590 }
591 _ => (),
592 };
593
594 let last_graph = y.inner.graph.clone();
595 let last_ignore = y.inner.ignore.clone();
596 let last_mutual_removes = y.inner.mutual_removes.clone();
597 let last_states = y.inner.states.clone();
598
599 let dependencies = HashSet::from_iter(operation.dependencies().clone());
600
601 let temp_y = if y.inner.heads() != dependencies {
604 let mut temp_y = y;
605
606 let mut predecessors = HashSet::new();
608 for dependency in operation.dependencies() {
609 let reversed = Reversed(&temp_y.inner.graph);
610 let mut dfs_rev = DfsPostOrder::new(&reversed, dependency);
611 while let Some(id) = dfs_rev.next(&reversed) {
612 predecessors.insert(id);
613 }
614 }
615
616 let to_remove: Vec<_> = temp_y
618 .inner
619 .graph
620 .node_identifiers()
621 .filter(|n| !predecessors.contains(n))
622 .collect();
623
624 for node in &to_remove {
625 temp_y.inner.graph.remove_node(*node);
626 }
627
628 temp_y.inner = RS::process(temp_y.inner).map_err(GroupCrdtError::Resolver)?;
629 temp_y
630 } else {
631 y
632 };
633
634 if temp_y.inner.would_create_cycle(operation) {
636 let parent_group = operation.group_id();
637
638 let GroupAction::Add {
640 member: sub_group, ..
641 } = operation.action()
642 else {
643 unreachable!()
644 };
645
646 return Err(GroupCrdtError::GroupCycle(
647 parent_group,
648 sub_group.id(),
649 operation.id(),
650 ));
651 }
652
653 let result = apply_action(
655 temp_y.inner.current_state(),
656 operation.group_id(),
657 operation.id(),
658 operation.author(),
659 &operation.action(),
660 &temp_y.inner.ignore,
661 );
662
663 match result {
664 StateChangeResult::Ok { state } => state,
665 StateChangeResult::Error { error, .. } => {
666 return Err(GroupCrdtError::StateChangeError(operation.id(), error));
669 }
670 StateChangeResult::Filtered { .. } => {
671 unreachable!();
673 }
674 };
675
676 let mut y = temp_y;
677 y.inner.graph = last_graph;
678 y.inner.ignore = last_ignore;
679 y.inner.mutual_removes = last_mutual_removes;
680 y.inner.states = last_states;
681
682 Ok(y)
683 }
684
685 fn add_operation(
689 mut y: GroupCrdtState<ID, OP, M, C>,
690 operation: &M,
691 ) -> GroupCrdtState<ID, OP, M, C> {
692 let operation_id = operation.id();
693 let dependencies = operation.dependencies();
694
695 y.inner.graph.add_node(operation_id);
697 for dependency in &dependencies {
698 y.inner.graph.add_edge(*dependency, operation_id, ());
699 }
700
701 y.inner.operations.insert(operation_id, operation.clone());
703
704 y
705 }
706}
707
708pub(crate) fn apply_action<ID, OP, C>(
710 mut groups_y: GroupStates<ID, C>,
711 group_id: ID,
712 id: OP,
713 actor: ID,
714 action: &GroupAction<ID, C>,
715 filter: &HashSet<OP>,
716) -> StateChangeResult<ID, C>
717where
718 ID: Author,
719 OP: OperationId + Ord,
720 C: Conditions,
721{
722 let members_y = if action.is_create() {
723 GroupMembersState::default()
724 } else {
725 groups_y
726 .remove(&group_id)
727 .expect("group already present in states map")
728 };
729
730 if filter.contains(&id) {
731 groups_y.insert(group_id, members_y);
732 return StateChangeResult::Filtered { state: groups_y };
733 }
734
735 let result = match action.clone() {
736 GroupAction::Add { member, access, .. } => state::add(
737 members_y.clone(),
738 GroupMember::Individual(actor),
739 member,
740 access,
741 ),
742 GroupAction::Remove { member, .. } => {
743 state::remove(members_y.clone(), GroupMember::Individual(actor), member)
744 }
745 GroupAction::Promote { member, access } => state::promote(
746 members_y.clone(),
747 GroupMember::Individual(actor),
748 member,
749 access,
750 ),
751 GroupAction::Demote { member, access } => state::demote(
752 members_y.clone(),
753 GroupMember::Individual(actor),
754 member,
755 access,
756 ),
757 GroupAction::Create { initial_members } => Ok(state::create(&initial_members)),
758 };
759
760 match result {
761 Ok(members_y_i) => {
762 groups_y.insert(group_id, members_y_i);
763 StateChangeResult::Ok { state: groups_y }
764 }
765 Err(err) => {
766 groups_y.insert(group_id, members_y);
780 StateChangeResult::Error {
781 state: groups_y,
782 error: err,
783 }
784 }
785 }
786}
787
788pub(crate) fn apply_remove_unsafe<ID, C>(
791 mut groups_y: GroupStates<ID, C>,
792 group_id: ID,
793 removed: GroupMember<ID>,
794) -> GroupStates<ID, C>
795where
796 ID: Author,
797 C: Conditions,
798{
799 let mut members_y = groups_y
800 .remove(&group_id)
801 .expect("group already present in states map");
802
803 members_y.members.entry(removed).and_modify(|state| {
804 if state.member_counter % 2 != 0 {
805 state.member_counter += 1
806 }
807 });
808 groups_y.insert(group_id, members_y);
809 groups_y
810}
811
812pub enum StateChangeResult<ID, C>
814where
815 ID: Author,
816 C: Conditions,
817{
818 Ok { state: GroupStates<ID, C> },
820
821 Error {
823 state: GroupStates<ID, C>,
824 #[allow(unused)]
825 error: GroupMembershipError<GroupMember<ID>>,
826 },
827
828 Filtered { state: GroupStates<ID, C> },
830}
831
832impl<ID, C> StateChangeResult<ID, C>
833where
834 ID: Author,
835 C: Conditions,
836{
837 pub fn state(&self) -> &GroupStates<ID, C> {
838 match self {
839 StateChangeResult::Ok { state }
840 | StateChangeResult::Error { state, .. }
841 | StateChangeResult::Filtered { state } => state,
842 }
843 }
844}
845
846#[cfg(test)]
847pub(crate) mod tests {
848 pub use p2panda_core::cbor::{decode_cbor, encode_cbor};
849
850 use crate::Access;
851 use crate::group::{GroupCrdtError, GroupMember, GroupMembershipError};
852 use crate::test_utils::{
853 TestGroup, TestGroupState, add_member, create_group, demote_member, promote_member,
854 remove_member,
855 };
856 use crate::traits::Operation;
857
858 const G1: char = '1';
859 const G2: char = '2';
860 const G3: char = '3';
861 const G4: char = '4';
862
863 const ALICE: char = 'A';
864 const BOB: char = 'B';
865 const CLAIRE: char = 'C';
866 const DAN: char = 'D';
867 const EVE: char = 'E';
868
869 #[test]
870 fn group_operations() {
871 let y = TestGroupState::new();
872
873 let op1 = create_group(
874 ALICE,
875 0,
876 G1,
877 vec![(GroupMember::Individual(ALICE), Access::manage())],
878 vec![],
879 );
880
881 let y_i = TestGroup::process(y, &op1).unwrap();
882 let mut members = y_i.members(G1);
883 members.sort();
884 assert_eq!(members, vec![(ALICE, Access::manage())]);
885
886 let op2 = add_member(
887 ALICE,
888 1,
889 G1,
890 GroupMember::Individual(BOB),
891 Access::read(),
892 vec![op1.id()],
893 );
894
895 let y_ii = TestGroup::process(y_i, &op2).unwrap();
896 let mut members = y_ii.members(G1);
897 members.sort();
898 assert_eq!(
899 members,
900 vec![(ALICE, Access::manage()), (BOB, Access::read())]
901 );
902
903 let op3 = add_member(
904 ALICE,
905 2,
906 G1,
907 GroupMember::Individual(CLAIRE),
908 Access::write(),
909 vec![op2.id()],
910 );
911
912 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
913 let mut members = y_iii.members(G1);
914 members.sort();
915 assert_eq!(
916 members,
917 vec![
918 (ALICE, Access::manage()),
919 (BOB, Access::read()),
920 (CLAIRE, Access::write())
921 ]
922 );
923
924 let op4 = remove_member(ALICE, 3, G1, GroupMember::Individual(BOB), vec![op3.id()]);
925
926 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
927 let mut members = y_iv.members(G1);
928 members.sort();
929 assert_eq!(
930 members,
931 vec![(ALICE, Access::manage()), (CLAIRE, Access::write())]
932 );
933 }
934
935 #[test]
936 fn self_remove() {
937 let y = TestGroupState::new();
938
939 let op1 = create_group(
940 ALICE,
941 0,
942 G1,
943 vec![(GroupMember::Individual(ALICE), Access::manage())],
944 vec![],
945 );
946
947 let y_i = TestGroup::process(y, &op1).unwrap();
948 let mut members = y_i.members(G1);
949 members.sort();
950 assert_eq!(members, vec![(ALICE, Access::manage())]);
951
952 let op2 = remove_member(ALICE, 1, G1, GroupMember::Individual(ALICE), vec![op1.id()]);
953
954 let y_i = TestGroup::process(y_i, &op2).unwrap();
955 let mut members = y_i.members(G1);
956 members.sort();
957 assert_eq!(members, vec![]);
958 }
959
960 #[test]
961 fn concurrent_removal() {
962 let y = TestGroupState::new();
963
964 let op1 = create_group(
965 ALICE,
966 0,
967 G1,
968 vec![(GroupMember::Individual(ALICE), Access::manage())],
969 vec![],
970 );
971
972 let y_i = TestGroup::process(y, &op1).unwrap();
973 let mut members = y_i.members(G1);
974 members.sort();
975 assert_eq!(members, vec![(ALICE, Access::manage())]);
976
977 let op2 = add_member(
978 ALICE,
979 1,
980 G1,
981 GroupMember::Individual(BOB),
982 Access::manage(),
983 vec![op1.id()],
984 );
985
986 let y_ii = TestGroup::process(y_i, &op2).unwrap();
987 let mut members = y_ii.members(G1);
988 members.sort();
989 assert_eq!(
990 members,
991 vec![(ALICE, Access::manage()), (BOB, Access::manage())]
992 );
993
994 let op3 = add_member(
995 BOB,
996 2,
997 G1,
998 GroupMember::Individual(CLAIRE),
999 Access::write(),
1000 vec![op2.id()],
1001 );
1002
1003 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
1004 let mut members = y_iii.members(G1);
1005 members.sort();
1006 assert_eq!(
1007 members,
1008 vec![
1009 (ALICE, Access::manage()),
1010 (BOB, Access::manage()),
1011 (CLAIRE, Access::write())
1012 ]
1013 );
1014
1015 let op4 = remove_member(ALICE, 3, G1, GroupMember::Individual(BOB), vec![op2.id()]);
1016
1017 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
1018 let mut members = y_iv.members(G1);
1019 members.sort();
1020 assert_eq!(members, vec![(ALICE, Access::manage())]);
1021 }
1022
1023 #[test]
1024 fn mutual_concurrent_removal() {
1025 let y = TestGroupState::new();
1026
1027 let op1 = create_group(
1028 ALICE,
1029 0,
1030 G1,
1031 vec![(GroupMember::Individual(ALICE), Access::manage())],
1032 vec![],
1033 );
1034
1035 let y_i = TestGroup::process(y, &op1).unwrap();
1036 let mut members = y_i.members(G1);
1037 members.sort();
1038 assert_eq!(members, vec![(ALICE, Access::manage())]);
1039
1040 let op2 = add_member(
1041 ALICE,
1042 1,
1043 G1,
1044 GroupMember::Individual(BOB),
1045 Access::manage(),
1046 vec![op1.id()],
1047 );
1048
1049 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1050 let mut members = y_ii.members(G1);
1051 members.sort();
1052 assert_eq!(
1053 members,
1054 vec![(ALICE, Access::manage()), (BOB, Access::manage())]
1055 );
1056
1057 let op3 = add_member(
1058 BOB,
1059 2,
1060 G1,
1061 GroupMember::Individual(CLAIRE),
1062 Access::manage(),
1063 vec![op2.id()],
1064 );
1065
1066 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
1067 let mut members = y_iii.members(G1);
1068 members.sort();
1069 assert_eq!(
1070 members,
1071 vec![
1072 (ALICE, Access::manage()),
1073 (BOB, Access::manage()),
1074 (CLAIRE, Access::manage())
1075 ]
1076 );
1077
1078 let op4 = remove_member(BOB, 3, G1, GroupMember::Individual(CLAIRE), vec![op3.id()]);
1079
1080 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
1081 let mut members = y_iv.members(G1);
1082 members.sort();
1083 assert_eq!(
1084 members,
1085 vec![(ALICE, Access::manage()), (BOB, Access::manage())]
1086 );
1087
1088 let op5 = remove_member(CLAIRE, 4, G1, GroupMember::Individual(BOB), vec![op3.id()]);
1089
1090 let y_v = TestGroup::process(y_iv, &op5).unwrap();
1091 let mut members = y_v.members(G1);
1092 members.sort();
1093 assert_eq!(members, vec![(ALICE, Access::manage())]);
1094 }
1095
1096 #[test]
1097 fn nested_groups() {
1098 let y = TestGroupState::new();
1099
1100 let op1 = create_group(
1101 ALICE,
1102 0,
1103 G1,
1104 vec![(GroupMember::Individual(ALICE), Access::manage())],
1105 vec![],
1106 );
1107
1108 let y_i = TestGroup::process(y, &op1).unwrap();
1109 let mut members = y_i.members(G1);
1110 members.sort();
1111 assert_eq!(members, vec![(ALICE, Access::manage())]);
1112
1113 let op2 = create_group(
1114 BOB,
1115 1,
1116 G2,
1117 vec![(GroupMember::Individual(BOB), Access::manage())],
1118 vec![op1.id()],
1119 );
1120
1121 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1122 let mut members = y_ii.members(G2);
1123 members.sort();
1124 assert_eq!(members, vec![(BOB, Access::manage())]);
1125
1126 let op3 = add_member(
1127 ALICE,
1128 2,
1129 G1,
1130 GroupMember::Group(G2),
1131 Access::read(),
1132 vec![op2.id()],
1133 );
1134
1135 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
1136 let mut individuals = y_iii.members(G1);
1137 individuals.sort();
1138 assert_eq!(
1139 individuals,
1140 vec![(ALICE, Access::manage()), (BOB, Access::read())]
1141 );
1142
1143 let mut groups = y_iii.groups(G1);
1144 groups.sort();
1145 assert_eq!(groups, vec![(G2, Access::read())]);
1146 }
1147
1148 #[test]
1149 fn error_on_unauthorized_add() {
1150 let y = TestGroupState::new();
1151
1152 let op1 = create_group(
1153 ALICE,
1154 0,
1155 G1,
1156 vec![(GroupMember::Individual(ALICE), Access::manage())],
1157 vec![],
1158 );
1159
1160 let y_i = TestGroup::process(y, &op1).unwrap();
1161
1162 let op2 = add_member(
1163 ALICE,
1164 1,
1165 G1,
1166 GroupMember::Individual(BOB),
1167 Access::read(),
1168 vec![op1.id()],
1169 );
1170
1171 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1172
1173 let op3 = add_member(
1174 BOB,
1175 2,
1176 G1,
1177 GroupMember::Individual(CLAIRE),
1178 Access::read(),
1179 vec![op2.id()],
1180 );
1181
1182 assert!(TestGroup::process(y_ii, &op3).is_err());
1183 }
1184
1185 #[test]
1186 fn error_on_remove_non_member() {
1187 let y = TestGroupState::new();
1188
1189 let op1 = create_group(
1190 ALICE,
1191 0,
1192 G1,
1193 vec![(GroupMember::Individual(ALICE), Access::manage())],
1194 vec![],
1195 );
1196
1197 let y_i = TestGroup::process(y, &op1).unwrap();
1198
1199 let op2 = remove_member(ALICE, 1, G1, GroupMember::Individual(BOB), vec![op1.id()]);
1200
1201 assert!(TestGroup::process(y_i, &op2).is_err());
1202 }
1203
1204 #[test]
1205 fn error_on_promote_non_member() {
1206 let y = TestGroupState::new();
1207
1208 let op1 = create_group(
1209 ALICE,
1210 0,
1211 G1,
1212 vec![(GroupMember::Individual(ALICE), Access::manage())],
1213 vec![],
1214 );
1215
1216 let y_i = TestGroup::process(y, &op1).unwrap();
1217
1218 let op2 = promote_member(
1219 ALICE,
1220 1,
1221 G1,
1222 GroupMember::Individual(BOB),
1223 Access::manage(),
1224 vec![op1.id()],
1225 );
1226
1227 assert!(TestGroup::process(y_i, &op2).is_err());
1228 }
1229
1230 #[test]
1231 fn error_on_add_manager_group() {
1232 let y = TestGroupState::new();
1233
1234 let op1 = create_group(
1235 ALICE,
1236 0,
1237 G1,
1238 vec![(GroupMember::Individual(ALICE), Access::manage())],
1239 vec![],
1240 );
1241
1242 let y_i = TestGroup::process(y, &op1).unwrap();
1243
1244 let op2 = add_member(
1245 ALICE,
1246 1,
1247 G1,
1248 GroupMember::Group(BOB),
1249 Access::manage(),
1250 vec![op1.id()],
1251 );
1252
1253 assert!(TestGroup::process(y_i, &op2).is_err());
1254 }
1255
1256 #[test]
1257 fn error_on_demote_non_member() {
1258 let y = TestGroupState::new();
1259
1260 let op1 = create_group(
1261 ALICE,
1262 0,
1263 G1,
1264 vec![(GroupMember::Individual(ALICE), Access::manage())],
1265 vec![],
1266 );
1267
1268 let y_i = TestGroup::process(y, &op1).unwrap();
1269
1270 let op2 = demote_member(
1271 ALICE,
1272 1,
1273 G1,
1274 GroupMember::Individual(BOB),
1275 Access::read(),
1276 vec![op1.id()],
1277 );
1278
1279 assert!(TestGroup::process(y_i, &op2).is_err());
1280 }
1281
1282 #[test]
1283 fn error_on_add_existing_member() {
1284 let y = TestGroupState::new();
1285
1286 let op1 = create_group(
1287 ALICE,
1288 0,
1289 G1,
1290 vec![(GroupMember::Individual(ALICE), Access::manage())],
1291 vec![],
1292 );
1293
1294 let y_i = TestGroup::process(y, &op1).unwrap();
1295
1296 let op2 = add_member(
1297 ALICE,
1298 1,
1299 G1,
1300 GroupMember::Individual(ALICE),
1301 Access::manage(),
1302 vec![op1.id()],
1303 );
1304
1305 assert!(TestGroup::process(y_i, &op2).is_err());
1306 }
1307
1308 #[test]
1309 fn error_on_remove_nonexistent_subgroup() {
1310 let y = TestGroupState::new();
1311
1312 let op1 = create_group(
1313 ALICE,
1314 0,
1315 G1,
1316 vec![(GroupMember::Individual(ALICE), Access::manage())],
1317 vec![],
1318 );
1319 let y_i = TestGroup::process(y, &op1).unwrap();
1320
1321 let op2 = remove_member(ALICE, 1, G1, GroupMember::Group(G2), vec![op1.id()]);
1323
1324 assert!(TestGroup::process(y_i, &op2).is_err());
1325 }
1326
1327 #[test]
1328 fn deeply_nested_groups_with_removals() {
1329 let y = TestGroupState::new();
1330
1331 let op1 = create_group(
1333 ALICE,
1334 0,
1335 G1,
1336 vec![(GroupMember::Individual(ALICE), Access::manage())],
1337 vec![],
1338 );
1339 let y_i = TestGroup::process(y, &op1).unwrap();
1340
1341 let op2 = create_group(
1343 BOB,
1344 1,
1345 G2,
1346 vec![(GroupMember::Individual(BOB), Access::manage())],
1347 vec![op1.id()],
1348 );
1349 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1350
1351 let op3 = create_group(
1353 CLAIRE,
1354 2,
1355 G3,
1356 vec![(GroupMember::Individual(CLAIRE), Access::manage())],
1357 vec![op2.id()],
1358 );
1359 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
1360
1361 let op4 = create_group(
1363 DAN,
1364 3,
1365 G4,
1366 vec![(GroupMember::Individual(DAN), Access::write())],
1367 vec![op3.id()],
1368 );
1369 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
1370
1371 let op5 = add_member(
1373 CLAIRE,
1374 4,
1375 G3,
1376 GroupMember::Group(G4),
1377 Access::read(),
1378 vec![op4.id()],
1379 );
1380 let y_v = TestGroup::process(y_iv, &op5).unwrap();
1381
1382 let op6 = add_member(
1384 BOB,
1385 5,
1386 G2,
1387 GroupMember::Group(G3),
1388 Access::write(),
1389 vec![op5.id()],
1390 );
1391 let y_vi = TestGroup::process(y_v, &op6).unwrap();
1392
1393 let op7 = add_member(
1395 ALICE,
1396 6,
1397 G1,
1398 GroupMember::Group(G2),
1399 Access::read(),
1400 vec![op6.id()],
1401 );
1402 let y_vii = TestGroup::process(y_vi, &op7).unwrap();
1403
1404 let mut members = y_vii.members(G1);
1405 members.sort();
1406 assert_eq!(
1407 members,
1408 vec![
1409 (ALICE, Access::manage()),
1410 (BOB, Access::read()),
1411 (CLAIRE, Access::read()),
1412 (DAN, Access::read()),
1413 ]
1414 );
1415
1416 let op8 = remove_member(BOB, 7, G2, GroupMember::Group(G3), vec![op7.id()]);
1418 let y_viii = TestGroup::process(y_vii, &op8).unwrap();
1419
1420 let mut members_after_removal = y_viii.members(G1);
1421 members_after_removal.sort();
1422 assert_eq!(
1423 members_after_removal,
1424 vec![(ALICE, Access::manage()), (BOB, Access::read()),]
1425 );
1426 }
1427
1428 #[test]
1429 fn nested_groups_with_concurrent_removal_and_promotion() {
1430 let y = TestGroupState::new();
1431
1432 let op1 = create_group(
1434 ALICE,
1435 0,
1436 G1,
1437 vec![(GroupMember::Individual(ALICE), Access::manage())],
1438 vec![],
1439 );
1440 let y_i = TestGroup::process(y, &op1).unwrap();
1441
1442 let op2 = create_group(
1444 BOB,
1445 1,
1446 G2,
1447 vec![(GroupMember::Individual(BOB), Access::manage())],
1448 vec![op1.id()],
1449 );
1450 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1451
1452 let op3 = create_group(
1454 CLAIRE,
1455 2,
1456 G3,
1457 vec![(GroupMember::Individual(CLAIRE), Access::manage())],
1458 vec![op2.id()],
1459 );
1460 let y_iii = TestGroup::process(y_ii, &op3).unwrap();
1461
1462 let op4 = add_member(
1464 CLAIRE,
1465 3,
1466 G3,
1467 GroupMember::Individual(DAN),
1468 Access::write(),
1469 vec![op3.id()],
1470 );
1471 let y_iv = TestGroup::process(y_iii, &op4).unwrap();
1472
1473 let op5 = add_member(
1475 BOB,
1476 4,
1477 G2,
1478 GroupMember::Group(G3),
1479 Access::write(),
1480 vec![op4.id()],
1481 );
1482 let y_v = TestGroup::process(y_iv, &op5).unwrap();
1483
1484 let op6 = add_member(
1486 BOB,
1487 5,
1488 G2,
1489 GroupMember::Individual(CLAIRE),
1490 Access::read(),
1491 vec![op5.id()],
1492 );
1493 let y_vi = TestGroup::process(y_v, &op6).unwrap();
1494
1495 let op7 = add_member(
1497 ALICE,
1498 6,
1499 G1,
1500 GroupMember::Group(G2),
1501 Access::read(),
1502 vec![op6.id()],
1503 );
1504 let y_vii = TestGroup::process(y_vi, &op7).unwrap();
1505
1506 let mut members = y_vii.members(G1);
1507 members.sort();
1508 assert_eq!(
1509 members,
1510 vec![
1511 (ALICE, Access::manage()),
1512 (BOB, Access::read()),
1513 (CLAIRE, Access::read()),
1514 (DAN, Access::read()),
1515 ]
1516 );
1517
1518 let op8_remove_g2 = remove_member(ALICE, 7, G1, GroupMember::Group(G2), vec![op7.id()]);
1520 let op9_promote_claire = promote_member(
1521 BOB,
1522 8,
1523 G2,
1524 GroupMember::Individual(CLAIRE),
1525 Access::manage(),
1526 vec![op7.id()],
1527 );
1528
1529 let y_after_remove = TestGroup::process(y_vii.clone(), &op8_remove_g2).unwrap();
1531 let mut members = y_after_remove.members(G1);
1532 members.sort();
1533 assert_eq!(members, vec![(ALICE, Access::manage())]);
1534
1535 let y_after_both = TestGroup::process(y_after_remove, &op9_promote_claire).unwrap();
1537 let mut g1_members = y_after_both.members(G1);
1538 g1_members.sort();
1539 assert_eq!(g1_members, vec![(ALICE, Access::manage())]);
1540
1541 let mut g2_members = y_after_both.members(G2);
1542 g2_members.sort();
1543 assert_eq!(
1544 g2_members,
1545 vec![
1546 (BOB, Access::manage()),
1547 (CLAIRE, Access::manage()),
1548 (DAN, Access::write()),
1549 ]
1550 );
1551 }
1552
1553 #[test]
1554 fn concurrent_removal_ooo_processing() {
1555 let y = TestGroupState::new();
1556
1557 let op1 = create_group(
1559 ALICE,
1560 0,
1561 G1,
1562 vec![(GroupMember::Individual(ALICE), Access::manage())],
1563 vec![],
1564 );
1565 let y_i = TestGroup::process(y, &op1).unwrap();
1566
1567 let op2 = add_member(
1569 ALICE,
1570 1,
1571 G1,
1572 GroupMember::Individual(BOB),
1573 Access::manage(),
1574 vec![op1.id()],
1575 );
1576 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1577
1578 let op3 = add_member(
1580 BOB,
1581 2,
1582 G1,
1583 GroupMember::Individual(CLAIRE),
1584 Access::read(),
1585 vec![op2.id()],
1586 );
1587
1588 let op4 = remove_member(ALICE, 3, G1, GroupMember::Individual(BOB), vec![op2.id()]);
1590
1591 let y_iii_a = TestGroup::process(y_ii.clone(), &op3).unwrap();
1593 let y_iv_a = TestGroup::process(y_iii_a, &op4).unwrap();
1594
1595 let y_iii_b = TestGroup::process(y_ii.clone(), &op4).unwrap();
1597 let y_iv_b = TestGroup::process(y_iii_b, &op3).unwrap();
1598
1599 for (_, y) in [y_iv_a, y_iv_b].into_iter().enumerate() {
1600 let mut members = y.members(G1);
1601 members.sort();
1602 assert_eq!(members, vec![(ALICE, Access::manage())],);
1603 }
1604 }
1605
1606 #[test]
1607 fn concurrent_add_with_insufficient_access() {
1608 let y0 = TestGroupState::new();
1609
1610 let op1 = create_group(
1612 ALICE,
1613 0,
1614 G1,
1615 vec![(GroupMember::Individual(ALICE), Access::manage())],
1616 vec![],
1617 );
1618 let y1 = TestGroup::process(y0, &op1).unwrap();
1619
1620 let op2 = add_member(
1622 ALICE,
1623 1,
1624 G1,
1625 GroupMember::Individual(BOB),
1626 Access::manage(),
1627 vec![op1.id()],
1628 );
1629
1630 let op3 = add_member(
1632 BOB,
1633 2,
1634 G1,
1635 GroupMember::Individual(EVE),
1636 Access::read(),
1637 vec![op1.id()],
1638 );
1639
1640 let result = TestGroup::process(y1.clone(), &op3);
1642 std::assert_matches!(
1643 result,
1644 Err(GroupCrdtError::StateChangeError(
1645 _,
1646 GroupMembershipError::UnrecognisedActor(_)
1647 ))
1648 );
1649
1650 let y1_alt = TestGroup::process(y1, &op2).unwrap();
1652 let result = TestGroup::process(y1_alt.clone(), &op3);
1653 std::assert_matches!(
1654 result,
1655 Err(GroupCrdtError::StateChangeError(
1656 _,
1657 GroupMembershipError::UnrecognisedActor(_)
1658 ))
1659 );
1660
1661 let mut members = y1_alt.members(G1);
1663 members.sort();
1664 assert_eq!(
1665 members,
1666 vec![(ALICE, Access::manage()), (BOB, Access::manage())]
1667 );
1668 }
1669
1670 #[test]
1671 fn add_group_with_concurrent_change() {
1672 let y = TestGroupState::new();
1673
1674 let op1 = create_group(
1676 ALICE,
1677 0,
1678 G1,
1679 vec![(GroupMember::Individual(ALICE), Access::manage())],
1680 vec![],
1681 );
1682 let y_i = TestGroup::process(y, &op1).unwrap();
1683
1684 let op2 = create_group(
1686 BOB,
1687 1,
1688 G2,
1689 vec![(GroupMember::Individual(BOB), Access::manage())],
1690 vec![op1.id()],
1691 );
1692 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1693
1694 let op3a = add_member(
1696 ALICE,
1697 2,
1698 G1,
1699 GroupMember::Group(G2),
1700 Access::read(),
1701 vec![op2.id()],
1702 );
1703
1704 let op3b = add_member(
1706 BOB,
1707 3,
1708 G2,
1709 GroupMember::Individual(CLAIRE),
1710 Access::write(),
1711 vec![op2.id()],
1712 );
1713
1714 let y_iii = TestGroup::process(y_ii.clone(), &op3a).unwrap();
1716 let y_iv = TestGroup::process(y_iii, &op3b).unwrap();
1717
1718 let mut members_1 = y_iv.members(G1);
1719 members_1.sort();
1720 assert_eq!(
1721 members_1,
1722 vec![
1723 (ALICE, Access::manage()),
1724 (BOB, Access::read()),
1725 (CLAIRE, Access::read())
1726 ]
1727 );
1728
1729 let y_iii_alt = TestGroup::process(y_ii.clone(), &op3b).unwrap();
1731 let y_iv_alt = TestGroup::process(y_iii_alt, &op3a).unwrap();
1732
1733 let mut members_1 = y_iv_alt.members(G1);
1734 members_1.sort();
1735 assert_eq!(
1736 members_1,
1737 vec![
1738 (ALICE, Access::manage()),
1739 (BOB, Access::read()),
1740 (CLAIRE, Access::read())
1741 ]
1742 );
1743 }
1744
1745 #[test]
1746 fn nested_group_cycle_error() {
1747 let y = TestGroupState::new();
1748
1749 let op1 = create_group(
1751 ALICE,
1752 0,
1753 G1,
1754 vec![(GroupMember::Individual(ALICE), Access::manage())],
1755 vec![],
1756 );
1757 let y_i = TestGroup::process(y, &op1).unwrap();
1758
1759 let op2 = create_group(
1761 BOB,
1762 1,
1763 G2,
1764 vec![
1765 (GroupMember::Individual(BOB), Access::manage()),
1766 (GroupMember::Group(G1), Access::read()),
1767 ],
1768 vec![op1.id()],
1769 );
1770 let y_ii = TestGroup::process(y_i, &op2).unwrap();
1771
1772 let op3 = add_member(
1774 ALICE,
1775 2,
1776 G1,
1777 GroupMember::Group(G2),
1778 Access::read(),
1779 vec![op2.id()],
1780 );
1781
1782 let result = TestGroup::process(y_ii, &op3);
1784 assert!(
1785 result.is_err(),
1786 "Creating a group cycle should cause an error"
1787 );
1788 }
1789
1790 #[test]
1791 fn serde_to_from_bytes() {
1792 let y = TestGroupState::new();
1793 let op1 = create_group(
1794 ALICE,
1795 0,
1796 G1,
1797 vec![(GroupMember::Individual(ALICE), Access::manage())],
1798 vec![],
1799 );
1800 let y_i = TestGroup::process(y, &op1).unwrap();
1801 let members = y_i.members(G1);
1802 assert_eq!(members, vec![(ALICE, Access::manage())]);
1803
1804 let bytes = encode_cbor(&y_i).unwrap();
1806
1807 let y_i_de: TestGroupState = decode_cbor(&bytes[..]).unwrap();
1809
1810 let members = y_i_de.members(G1);
1812 assert_eq!(members, vec![(ALICE, Access::manage())]);
1813 }
1814}