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