1pub(crate) mod state;
4
5use std::collections::{HashMap, HashSet};
6use std::fmt::{Debug, Display};
7use std::marker::PhantomData;
8
9use petgraph::algo::toposort;
10use petgraph::prelude::DiGraphMap;
11use petgraph::visit::{DfsPostOrder, IntoNodeIdentifiers, NodeIndexable, Reversed};
12use thiserror::Error;
13
14use crate::access::Access;
15use crate::group::{
16 GroupAction, GroupControlMessage, GroupMember, GroupMembersState, GroupMembershipError,
17};
18use crate::traits::{
19 GroupMembership, GroupStore, IdentityHandle, Operation, OperationId, Orderer, Resolver,
20};
21
22#[derive(Debug, Error)]
24pub enum GroupCrdtError<ID, OP, C, RS, ORD, GS>
25where
26 ID: IdentityHandle,
27 OP: OperationId + Ord,
28 RS: Resolver<ID, OP, C, ORD, GS>,
29 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>>,
30 GS: GroupStore<ID, OP, C, RS, ORD>,
31{
32 #[error("duplicate operation {0} processed in group {1}")]
33 DuplicateOperation(OP, ID),
34
35 #[error("state change error processing operation {0}: {1:?}")]
36 StateChangeError(OP, GroupMembershipError<GroupMember<ID>>),
37
38 #[error("expected sub-group {0} to exist in the store")]
39 MissingSubGroup(ID),
40
41 #[error("ordering error: {0}")]
42 OrderingError(ORD::Error),
43
44 #[error("group store error: {0}")]
45 GroupStoreError(GS::Error),
46
47 #[error("states {0:?} not found in group {1}")]
48 StatesNotFound(Vec<OP>, ID),
49
50 #[error("expected dependencies {0:?} not found in group {1}")]
51 DependenciesNotFound(Vec<OP>, ID),
52
53 #[error("operation for group {0} processed in group {1}")]
54 IncorrectGroupId(ID, ID),
55
56 #[error("operation id {0} exists in the graph but the corresponding operation was not found")]
57 MissingOperation(OP),
58
59 #[error("state not found for group member {0} in group {1}")]
61 MemberNotFound(ID, ID),
62}
63
64#[derive(Debug)]
68#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
69pub struct GroupCrdtState<ID, OP, C, RS, ORD, GS>
70where
71 ID: IdentityHandle,
72 OP: OperationId,
73 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>>,
74 GS: GroupStore<ID, OP, C, RS, ORD>,
75{
76 pub my_id: ID,
78
79 pub group_id: ID,
81
82 pub states: HashMap<OP, GroupMembersState<GroupMember<ID>, C>>,
84
85 pub operations: HashMap<OP, ORD::Operation>,
87
88 pub ignore: HashSet<OP>,
90
91 pub graph: DiGraphMap<OP, ()>,
93
94 pub orderer_y: ORD::State,
96
97 pub group_store: GS,
99
100 _phantom: PhantomData<RS>,
101}
102
103impl<ID, OP, C, RS, ORD, GS> GroupCrdtState<ID, OP, C, RS, ORD, GS>
104where
105 ID: IdentityHandle,
106 OP: OperationId + Ord,
107 C: Clone + Debug + PartialEq + PartialOrd,
108 RS: Resolver<ID, OP, C, ORD, GS> + Debug,
109 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
110 GS: GroupStore<ID, OP, C, RS, ORD> + Debug,
111{
112 pub fn new(my_id: ID, group_id: ID, group_store: GS, orderer_y: ORD::State) -> Self {
114 Self {
115 my_id,
116 group_id,
117 states: Default::default(),
118 operations: Default::default(),
119 ignore: Default::default(),
120 graph: Default::default(),
121 group_store,
122 orderer_y,
123 _phantom: PhantomData,
124 }
125 }
126
127 pub fn id(&self) -> ID {
129 self.group_id
130 }
131
132 pub fn heads(&self) -> HashSet<OP> {
134 self.graph
135 .clone()
143 .into_graph::<usize>()
144 .externals(petgraph::Direction::Outgoing)
145 .map(|idx| self.graph.from_index(idx.index()))
146 .collect::<HashSet<_>>()
147 }
148
149 #[allow(clippy::type_complexity)]
151 pub fn transitive_heads(&self) -> Result<HashSet<OP>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
152 let mut transitive_heads = self.heads();
153 for (member, ..) in self.members() {
154 if let GroupMember::Group(id) = member {
155 let sub_group = self.get_sub_group(id)?;
156 transitive_heads.extend(sub_group.transitive_heads()?);
157 }
158 }
159
160 Ok(transitive_heads)
161 }
162
163 pub fn current_state(&self) -> GroupMembersState<GroupMember<ID>, C> {
168 let mut current_state = GroupMembersState::default();
169 for state in self.heads() {
170 let state = self.states.get(&state).unwrap();
172 current_state = state::merge(state.clone(), current_state);
173 }
174 current_state
175 }
176
177 fn state_at_inner(&self, dependencies: &HashSet<OP>) -> GroupMembersState<GroupMember<ID>, C> {
178 let mut y = GroupMembersState::default();
179 for id in dependencies.iter() {
180 let Some(previous_y) = self.states.get(id) else {
181 continue;
184 };
185 y = state::merge(previous_y.clone(), y);
187 }
188
189 y
190 }
191
192 pub fn state_at(&self, dependencies: &HashSet<OP>) -> GroupMembersState<GroupMember<ID>, C> {
194 self.state_at_inner(dependencies)
195 }
196
197 fn members_at_inner(&self, dependencies: &HashSet<OP>) -> Vec<(GroupMember<ID>, Access<C>)> {
198 let y = self.state_at_inner(dependencies);
199 y.members
200 .into_iter()
201 .filter_map(|(id, state)| {
202 if state.is_member() {
203 Some((id, state.access))
204 } else {
205 None
206 }
207 })
208 .collect::<Vec<_>>()
209 }
210
211 pub fn members_at(&self, dependencies: &HashSet<OP>) -> Vec<(GroupMember<ID>, Access<C>)> {
213 self.members_at_inner(dependencies)
214 }
215
216 #[allow(clippy::type_complexity)]
217 fn transitive_members_at_inner(
218 &self,
219 dependencies: &HashSet<OP>,
220 ) -> Result<Vec<(ID, Access<C>)>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
221 let mut members: HashMap<ID, Access<C>> = HashMap::new();
222
223 for (member, root_access) in self.members_at_inner(dependencies) {
225 match member {
226 GroupMember::Individual(id) => {
227 members.insert(id, root_access.clone());
229 }
230 GroupMember::Group(id) => {
231 let sub_group = self.get_sub_group(id)?;
235
236 let transitive_members = sub_group.transitive_members_at_inner(dependencies)?;
239
240 for (transitive_member, transitive_access) in transitive_members {
245 let root_access_copy = root_access.clone();
246 members
247 .entry(transitive_member)
248 .and_modify(|access| {
249 if transitive_access > *access
255 && transitive_access <= root_access_copy
256 {
257 *access = transitive_access.clone()
258 }
259 })
260 .or_insert_with(|| {
261 if transitive_access <= root_access_copy {
262 transitive_access
263 } else {
264 root_access_copy
265 }
266 });
267 }
268 }
269 }
270 }
271
272 Ok(members.into_iter().collect())
273 }
274
275 #[allow(clippy::type_complexity)]
280 pub fn transitive_members_at(
281 &self,
282 dependencies: &HashSet<OP>,
283 ) -> Result<Vec<(ID, Access<C>)>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
284 let members = self.transitive_members_at_inner(dependencies)?;
285 Ok(members)
286 }
287
288 pub fn members(&self) -> Vec<(GroupMember<ID>, Access<C>)> {
290 self.current_state()
291 .members
292 .into_iter()
293 .filter_map(|(id, state)| {
294 if state.is_member() {
295 Some((id, state.access))
296 } else {
297 None
298 }
299 })
300 .collect::<Vec<_>>()
301 }
302
303 #[allow(clippy::type_complexity)]
308 pub fn transitive_members(
309 &self,
310 ) -> Result<Vec<(ID, Access<C>)>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
311 let heads = self.transitive_heads()?;
312 let members = self.transitive_members_at(&heads)?;
313
314 Ok(members)
315 }
316
317 #[allow(clippy::type_complexity)]
319 pub(crate) fn get_sub_group(
320 &self,
321 id: ID,
322 ) -> Result<GroupCrdtState<ID, OP, C, RS, ORD, GS>, GroupCrdtError<ID, OP, C, RS, ORD, GS>>
323 {
324 let y = self
325 .group_store
326 .get(&id)
327 .map_err(|error| GroupCrdtError::GroupStoreError(error))?;
328
329 let Some(y) = y else {
333 return Err(GroupCrdtError::MissingSubGroup(id));
334 };
335
336 Ok(y)
337 }
338
339 #[allow(clippy::type_complexity)]
347 pub fn max_access_identity(
348 &self,
349 actor: ID,
350 dependencies: &HashSet<OP>,
351 ) -> Result<Option<(GroupMember<ID>, Access<C>)>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
352 let mut access_levels = Vec::new();
353
354 for (member, root_access) in self.members_at(dependencies) {
356 match member {
357 GroupMember::Individual(id) => {
360 if id == actor {
361 access_levels.push((member, root_access));
362 }
363 }
364 GroupMember::Group(id) => {
367 let sub_group = self.get_sub_group(id)?;
368 if let Some((_, transitive_access)) = sub_group
369 .transitive_members_at(dependencies)
374 .unwrap()
375 .iter()
376 .find(|(member, _)| *member == actor)
377 {
378 let actual_access = if transitive_access < &root_access {
381 transitive_access.clone()
382 } else {
383 root_access.clone()
384 };
385 access_levels.push((member, actual_access));
386 }
387 }
388 }
389 }
390
391 let mut max_access: Option<(GroupMember<ID>, Access<C>)> = None;
392 for (id, access) in access_levels {
393 match max_access.clone() {
394 Some((_, prev_access)) => {
395 if prev_access < access {
396 max_access = Some((id, access))
397 }
398 }
399 None => max_access = Some((id, access)),
400 }
401 }
402 Ok(max_access)
403 }
404}
405
406impl<ID, OP, C, RS, ORD, GS> GroupMembership<ID, OP, C> for GroupCrdtState<ID, OP, C, RS, ORD, GS>
407where
408 ID: IdentityHandle + Display,
409 OP: OperationId + Ord + Display,
410 C: Clone + Debug + PartialEq + PartialOrd,
411 RS: Resolver<ID, OP, C, ORD, GS> + Debug,
412 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
413 GS: GroupStore<ID, OP, C, RS, ORD> + Debug,
414{
415 type State = GroupCrdtState<ID, OP, C, RS, ORD, GS>;
416
417 type Error = GroupCrdtError<ID, OP, C, RS, ORD, GS>;
418
419 fn access(
423 y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
424 member: &ID,
425 ) -> Result<Access<C>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
426 let member_state = y
427 .transitive_members()?
428 .into_iter()
429 .find(|(member_id, _state)| member_id == member);
430
431 if let Some(state) = member_state {
432 let access = state.1.to_owned();
433
434 Ok(access)
435 } else {
436 Err(GroupCrdtError::MemberNotFound(y.group_id, *member))
437 }
438 }
439
440 fn member_ids(
442 y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
443 ) -> Result<HashSet<ID>, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
444 let member_ids = y
445 .transitive_members()?
446 .into_iter()
447 .map(|(member_id, _state)| member_id)
448 .collect();
449
450 Ok(member_ids)
451 }
452
453 fn is_member(
455 y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
456 member: &ID,
457 ) -> Result<bool, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
458 let member_state = y
459 .transitive_members()?
460 .into_iter()
461 .find(|(member_id, _state)| member_id == member);
462
463 let is_member = member_state.is_some();
464
465 Ok(is_member)
466 }
467
468 fn is_puller(
470 y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
471 member: &ID,
472 ) -> Result<bool, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
473 Ok(GroupCrdtState::access(y, member)?.is_pull())
474 }
475
476 fn is_reader(
478 y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
479 member: &ID,
480 ) -> Result<bool, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
481 Ok(GroupCrdtState::access(y, member)?.is_read())
482 }
483
484 fn is_writer(
486 y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
487 member: &ID,
488 ) -> Result<bool, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
489 Ok(GroupCrdtState::access(y, member)?.is_write())
490 }
491
492 fn is_manager(
494 y: &GroupCrdtState<ID, OP, C, RS, ORD, GS>,
495 member: &ID,
496 ) -> Result<bool, GroupCrdtError<ID, OP, C, RS, ORD, GS>> {
497 Ok(GroupCrdtState::access(y, member)?.is_manage())
498 }
499}
500
501#[derive(Clone, Debug, Default)]
539pub struct GroupCrdt<ID, OP, C, RS, ORD, GS> {
540 _phantom: PhantomData<(ID, OP, C, RS, ORD, GS)>,
541}
542
543impl<ID, OP, C, RS, ORD, GS> GroupCrdt<ID, OP, C, RS, ORD, GS>
544where
545 ID: IdentityHandle + Display,
546 OP: OperationId + Ord + Display,
547 C: Clone + Debug + PartialEq + PartialOrd,
548 RS: Resolver<ID, OP, C, ORD, GS> + Debug,
549 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
550 ORD::Operation: Clone,
551 GS: GroupStore<ID, OP, C, RS, ORD> + Clone + Debug,
552{
553 #[allow(clippy::type_complexity)]
560 pub fn prepare(
561 mut y: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
562 action: &GroupControlMessage<ID, C>,
563 ) -> Result<
564 (GroupCrdtState<ID, OP, C, RS, ORD, GS>, ORD::Operation),
565 GroupCrdtError<ID, OP, C, RS, ORD, GS>,
566 > {
567 let ordering_y = y.orderer_y;
570 let (ordering_y, operation) = match ORD::next_message(ordering_y, action) {
571 Ok(operation) => operation,
572 Err(_) => panic!(),
573 };
574
575 y.orderer_y = ordering_y;
576 Ok((y, operation))
577 }
578
579 #[allow(clippy::type_complexity)]
581 pub fn process(
582 mut y: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
583 operation: &ORD::Operation,
584 ) -> Result<GroupCrdtState<ID, OP, C, RS, ORD, GS>, GroupCrdtError<ID, OP, C, RS, ORD, GS>>
585 {
586 let operation_id = operation.id();
587 let actor = operation.author();
588 let control_message = operation.payload();
589 let previous_operations = operation.previous();
590 let dependencies = HashSet::from_iter(operation.dependencies().clone());
591 let group_id = control_message.group_id();
592
593 if y.group_id != group_id {
594 return Err(GroupCrdtError::IncorrectGroupId(group_id, y.group_id));
596 }
597
598 if y.operations.contains_key(&operation_id) {
599 return Err(GroupCrdtError::DuplicateOperation(operation_id, group_id));
601 }
602
603 let rebuild_required = RS::rebuild_required(&y, operation)?;
606
607 y.graph.add_node(operation_id);
610 for previous in &previous_operations {
611 y.graph.add_edge(*previous, operation_id, ());
612 }
613
614 y.operations.insert(operation_id, operation.clone());
615
616 let y_i = if rebuild_required {
617 y = GroupCrdt::validate_concurrent_action(y, operation)?;
622
623 RS::process(y)?
627 } else {
628 match Self::apply_action(
633 y,
634 operation_id,
635 actor,
636 &dependencies,
637 &control_message.action,
638 )? {
639 StateChangeResult::Ok { state } => state,
640 StateChangeResult::Noop { error, .. } => {
641 return Err(GroupCrdtError::StateChangeError(operation_id, error));
642 }
643 StateChangeResult::Filtered { .. } => {
644 unreachable!()
646 }
647 }
648 };
649
650 y_i.group_store
652 .insert(&group_id, &y_i)
653 .map_err(|error| GroupCrdtError::GroupStoreError(error))?;
654
655 Ok(y_i)
656 }
657
658 #[allow(clippy::type_complexity)]
660 pub(crate) fn apply_action(
661 mut y: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
662 id: OP,
663 actor: ID,
664 dependencies: &HashSet<OP>,
665 action: &GroupAction<ID, C>,
666 ) -> Result<StateChangeResult<ID, OP, C, RS, ORD, GS>, GroupCrdtError<ID, OP, C, RS, ORD, GS>>
667 {
668 let members_y = if dependencies.is_empty() {
671 GroupMembersState::default()
672 } else {
673 y.state_at(dependencies)
674 };
675
676 let max_access = y.max_access_identity(actor, dependencies)?;
678 let member_id = match max_access {
679 Some((id, _)) => id,
680 None => GroupMember::Individual(actor),
681 };
682
683 if !y.ignore.contains(&id) {
686 let result = match action.clone() {
687 GroupAction::Add { member, access, .. } => {
688 state::add(members_y.clone(), member_id, member, access)
689 }
690 GroupAction::Remove { member, .. } => {
691 state::remove(members_y.clone(), member_id, member)
692 }
693 GroupAction::Promote { member, access } => {
694 state::promote(members_y.clone(), member_id, member, access)
695 }
696 GroupAction::Demote { member, access } => {
697 state::demote(members_y.clone(), member_id, member, access)
698 }
699 GroupAction::Create { initial_members } => Ok(state::create(&initial_members)),
700 };
701
702 match result {
703 Ok(members_y_i) => y.states.insert(id, members_y_i),
704 Err(err) => {
705 y.states.insert(id, members_y);
719 return Ok(StateChangeResult::Noop {
720 state: y,
721 error: err,
722 });
723 }
724 };
725 } else {
726 y.states.insert(id, members_y);
727 return Ok(StateChangeResult::Filtered { state: y });
728 }
729 Ok(StateChangeResult::Ok { state: y })
730 }
731
732 #[allow(clippy::type_complexity)]
744 pub(crate) fn validate_concurrent_action(
745 mut y: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
746 operation: &ORD::Operation,
747 ) -> Result<GroupCrdtState<ID, OP, C, RS, ORD, GS>, GroupCrdtError<ID, OP, C, RS, ORD, GS>>
748 {
749 let last_graph = y.graph.clone();
751 let last_ignore = y.ignore.clone();
752 let last_states = y.states.clone();
753
754 let mut predecessors = HashSet::new();
756 for previous in operation.previous() {
757 let reversed = Reversed(&y.graph);
758 let mut dfs_rev = DfsPostOrder::new(&reversed, previous);
759 while let Some(id) = dfs_rev.next(&reversed) {
760 predecessors.insert(id);
761 }
762 }
763
764 let to_remove: Vec<_> = y
766 .graph
767 .node_identifiers()
768 .filter(|n| !predecessors.contains(n))
769 .collect();
770
771 for node in &to_remove {
772 y.graph.remove_node(*node);
773 }
774
775 y = RS::process(y)?;
776
777 let dependencies = HashSet::from_iter(operation.dependencies().clone());
778
779 let mut y_i = match Self::apply_action(
780 y,
781 operation.id(),
782 operation.author(),
783 &dependencies,
784 &operation.payload().action,
785 )? {
786 StateChangeResult::Ok { state } => state,
787 StateChangeResult::Noop { error, .. } => {
788 return Err(GroupCrdtError::StateChangeError(operation.id(), error));
789 }
790 StateChangeResult::Filtered { .. } => {
791 unreachable!()
793 }
794 };
795
796 y_i.graph = last_graph;
797 y_i.ignore = last_ignore;
798 y_i.states = last_states;
799
800 Ok(y_i)
801 }
802
803 #[allow(clippy::type_complexity)]
811 pub(crate) fn rebuild(
812 y: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
813 ) -> Result<GroupCrdtState<ID, OP, C, RS, ORD, GS>, GroupCrdtError<ID, OP, C, RS, ORD, GS>>
814 {
815 let mut y_i = GroupCrdtState::new(y.my_id, y.group_id, y.group_store, y.orderer_y);
816 y_i.ignore = y.ignore;
817 let operations = y.operations;
818
819 let topo_sort =
820 toposort(&y.graph, None).expect("group operation sets can be ordered topologically");
821
822 let mut create_found = false;
824 for operation_id in topo_sort {
825 let operation = operations
826 .get(&operation_id)
827 .expect("all processed operations exist");
828 let actor = operation.author();
829 let operation_id = operation.id();
830 let control_message = operation.payload();
831 let group_id = control_message.group_id();
832 let dependencies = HashSet::from_iter(operation.dependencies().clone());
833
834 assert_eq!(y_i.group_id, group_id);
836
837 if create_found {
839 assert!(!control_message.is_create())
840 } else {
841 assert!(control_message.is_create())
842 }
843
844 create_found = true;
845
846 y_i = match Self::apply_action(
847 y_i,
848 operation_id,
849 actor,
850 &dependencies,
851 &control_message.action,
852 )? {
853 StateChangeResult::Ok { state } => state,
854 StateChangeResult::Noop { state, .. } => {
855 state
859 }
860 StateChangeResult::Filtered { state } => state,
861 };
862
863 y_i.graph.add_node(operation_id);
865 for previous in &operation.previous() {
866 y_i.graph.add_edge(*previous, operation_id, ());
867 }
868 }
869
870 y_i.operations = operations;
871 Ok(y_i)
872 }
873}
874
875pub enum StateChangeResult<ID, OP, C, RS, ORD, GS>
877where
878 ID: IdentityHandle + Display,
879 OP: OperationId + Ord + Display,
880 C: Clone + Debug + PartialEq + PartialOrd,
881 RS: Resolver<ID, OP, C, ORD, GS> + Debug,
882 ORD: Orderer<ID, OP, GroupControlMessage<ID, C>> + Debug,
883 ORD::Operation: Clone,
884 GS: GroupStore<ID, OP, C, RS, ORD> + Clone + Debug,
885{
886 Ok {
888 state: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
889 },
890
891 Noop {
893 state: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
894 #[allow(unused)]
895 error: GroupMembershipError<GroupMember<ID>>,
896 },
897
898 Filtered {
900 state: GroupCrdtState<ID, OP, C, RS, ORD, GS>,
901 },
902}
903
904#[cfg(test)]
905pub(crate) mod tests {
906
907 use rand::SeedableRng;
908 use rand::rngs::StdRng;
909
910 use crate::Access;
911 use crate::group::{
912 GroupAction, GroupControlMessage, GroupCrdt, GroupCrdtError, GroupCrdtState, GroupMember,
913 GroupMembershipError,
914 };
915 use crate::test_utils::{
916 MessageId, Network, TestGroup, TestGroupState, TestGroupStore, TestOperation,
917 TestOrdererState,
918 };
919
920 pub(crate) fn from_create(
921 actor_id: char,
922 group_id: char,
923 op_create: &TestOperation,
924 rng: &mut StdRng,
925 ) -> TestGroupState {
926 let store = TestGroupStore::default();
927 let orderer = TestOrdererState::new(actor_id, store.clone(), StdRng::from_rng(rng));
928 let group = TestGroupState::new(actor_id, group_id, store, orderer);
929 TestGroup::process(group, op_create).unwrap()
930 }
931
932 pub(crate) fn create_group(
933 actor_id: char,
934 group_id: char,
935 members: Vec<(char, Access<()>)>,
936 rng: &mut StdRng,
937 ) -> (TestGroupState, TestOperation) {
938 let store = TestGroupStore::default();
939 let orderer = TestOrdererState::new(actor_id, store.clone(), StdRng::from_rng(rng));
940 let group = TestGroupState::new(actor_id, group_id, store, orderer);
941 let control_message = GroupControlMessage {
942 group_id,
943 action: GroupAction::Create {
944 initial_members: members
945 .into_iter()
946 .map(|(id, access)| (GroupMember::Individual(id), access))
947 .collect(),
948 },
949 };
950 let (group, op) = TestGroup::prepare(group, &control_message).unwrap();
951 let group = TestGroup::process(group, &op).unwrap();
952 (group, op)
953 }
954
955 pub(crate) fn add_member(
956 group: TestGroupState,
957 group_id: char,
958 member: char,
959 access: Access<()>,
960 ) -> (TestGroupState, TestOperation) {
961 let control_message = GroupControlMessage {
962 group_id,
963 action: GroupAction::Add {
964 member: GroupMember::Individual(member),
965 access,
966 },
967 };
968 let (group, op) = TestGroup::prepare(group, &control_message).unwrap();
969 let group = TestGroup::process(group, &op).unwrap();
970 (group, op)
971 }
972
973 pub(crate) fn remove_member(
974 group: TestGroupState,
975 group_id: char,
976 member: char,
977 ) -> (TestGroupState, TestOperation) {
978 let control_message = GroupControlMessage {
979 group_id,
980 action: GroupAction::Remove {
981 member: GroupMember::Individual(member),
982 },
983 };
984 let (group, op) = TestGroup::prepare(group, &control_message).unwrap();
985 let group = TestGroup::process(group, &op).unwrap();
986 (group, op)
987 }
988
989 pub(crate) fn sync(group: TestGroupState, ops: &[TestOperation]) -> TestGroupState {
990 ops.iter()
991 .fold(group, |g, op| TestGroup::process(g, op).unwrap())
992 }
993
994 pub(crate) fn assert_members(
995 group: &TestGroupState,
996 expected: &[(GroupMember<char>, Access<()>)],
997 ) {
998 let mut actual = group.members();
999 let mut expected = expected.to_vec();
1000 actual.sort();
1001 expected.sort();
1002 assert_eq!(actual, expected);
1003 }
1004
1005 #[test]
1006 fn basic_group() {
1007 let group_id = '1';
1008 let alice = 'A';
1009 let store = TestGroupStore::default();
1010 let rng = StdRng::from_os_rng();
1011 let orderer_y = TestOrdererState::new(alice, store.clone(), rng);
1012 let group_y = TestGroupState::new(alice, group_id, store, orderer_y);
1013
1014 let control_message_001 = GroupControlMessage {
1016 group_id,
1017 action: GroupAction::Create {
1018 initial_members: vec![(GroupMember::Individual(alice), Access::manage())],
1019 },
1020 };
1021 let (group_y, operation_001) = TestGroup::prepare(group_y, &control_message_001).unwrap();
1022 let group_y = TestGroup::process(group_y, &operation_001).unwrap();
1023
1024 let mut members = group_y.members();
1025 members.sort();
1026 assert_eq!(
1027 members,
1028 vec![(GroupMember::Individual(alice), Access::manage())]
1029 );
1030
1031 let bob = 'B';
1033 let control_message_002 = GroupControlMessage {
1034 group_id,
1035 action: GroupAction::Add {
1036 member: GroupMember::Individual(bob),
1037 access: Access::read(),
1038 },
1039 };
1040 let (group_y, operation_002) = TestGroup::prepare(group_y, &control_message_002).unwrap();
1041 let group_y = TestGroup::process(group_y, &operation_002).unwrap();
1042
1043 let mut members = group_y.members();
1044 members.sort();
1045 assert_eq!(
1046 members,
1047 vec![
1048 (GroupMember::Individual(alice), Access::manage()),
1049 (GroupMember::Individual(bob), Access::read())
1050 ]
1051 );
1052
1053 let claire = 'C';
1055 let control_message_003 = GroupControlMessage {
1056 group_id,
1057 action: GroupAction::Add {
1058 member: GroupMember::Individual(claire),
1059 access: Access::write(),
1060 },
1061 };
1062 let (group_y, operation_003) = TestGroup::prepare(group_y, &control_message_003).unwrap();
1063 let group_y = TestGroup::process(group_y, &operation_003).unwrap();
1064
1065 let mut members = group_y.members();
1066 members.sort();
1067 assert_eq!(
1068 members,
1069 vec![
1070 (GroupMember::Individual(alice), Access::manage()),
1071 (GroupMember::Individual(bob), Access::read()),
1072 (GroupMember::Individual(claire), Access::write())
1073 ]
1074 );
1075
1076 let control_message_004 = GroupControlMessage {
1078 group_id,
1079 action: GroupAction::Promote {
1080 member: GroupMember::Individual(claire),
1081 access: Access::manage(),
1082 },
1083 };
1084 let (group_y, operation_004) = TestGroup::prepare(group_y, &control_message_004).unwrap();
1085 let group_y = TestGroup::process(group_y, &operation_004).unwrap();
1086
1087 let mut members = group_y.members();
1088 members.sort();
1089 assert_eq!(
1090 members,
1091 vec![
1092 (GroupMember::Individual(alice), Access::manage()),
1093 (GroupMember::Individual(bob), Access::read()),
1094 (GroupMember::Individual(claire), Access::manage())
1095 ]
1096 );
1097
1098 let control_message_005 = GroupControlMessage {
1100 group_id,
1101 action: GroupAction::Demote {
1102 member: GroupMember::Individual(bob),
1103 access: Access::pull(),
1104 },
1105 };
1106 let (group_y, operation_005) = TestGroup::prepare(group_y, &control_message_005).unwrap();
1107 let group_y = TestGroup::process(group_y, &operation_005).unwrap();
1108
1109 let mut members = group_y.members();
1110 members.sort();
1111 assert_eq!(
1112 members,
1113 vec![
1114 (GroupMember::Individual(alice), Access::manage()),
1115 (GroupMember::Individual(bob), Access::pull()),
1116 (GroupMember::Individual(claire), Access::manage())
1117 ]
1118 );
1119
1120 let control_message_006 = GroupControlMessage {
1122 group_id,
1123 action: GroupAction::Remove {
1124 member: GroupMember::Individual(bob),
1125 },
1126 };
1127 let (group_y, operation_006) = TestGroup::prepare(group_y, &control_message_006).unwrap();
1128 let group_y = TestGroup::process(group_y, &operation_006).unwrap();
1129
1130 let mut members = group_y.members();
1131 members.sort();
1132 assert_eq!(
1133 members,
1134 vec![
1135 (GroupMember::Individual(alice), Access::manage()),
1136 (GroupMember::Individual(claire), Access::manage())
1137 ]
1138 );
1139 }
1140
1141 #[test]
1142 fn nested_groups() {
1143 let alice = 'A';
1144 let alice_mobile = 'M';
1145 let alice_laptop = 'L';
1146
1147 let alice_devices_group = 'D';
1148 let alice_team_group = 'T';
1149
1150 let store = TestGroupStore::default();
1152 let rng = StdRng::from_os_rng();
1153 let alice_orderer_y = TestOrdererState::new(alice, store.clone(), rng);
1154
1155 let devices_group_y = GroupCrdtState::new(
1157 alice,
1158 alice_devices_group,
1159 store.clone(),
1160 alice_orderer_y.clone(),
1161 );
1162
1163 let team_group_y =
1165 GroupCrdtState::new(alice, alice_team_group, store.clone(), alice_orderer_y);
1166
1167 let control_message_001 = GroupControlMessage {
1169 group_id: devices_group_y.id(),
1170 action: GroupAction::Create {
1171 initial_members: vec![
1172 (GroupMember::Individual(alice), Access::manage()),
1173 (GroupMember::Individual(alice_laptop), Access::manage()),
1174 (GroupMember::Individual(alice_mobile), Access::write()),
1175 ],
1176 },
1177 };
1178
1179 let (devices_group_y, operation_001) =
1181 TestGroup::prepare(devices_group_y, &control_message_001).unwrap();
1182
1183 let devices_group_y = TestGroup::process(devices_group_y, &operation_001).unwrap();
1185
1186 let mut members = devices_group_y.members();
1188 members.sort();
1189 assert_eq!(
1190 members,
1191 vec![
1192 (GroupMember::Individual(alice), Access::manage()),
1193 (GroupMember::Individual(alice_laptop), Access::manage()),
1194 (GroupMember::Individual(alice_mobile), Access::write()),
1195 ],
1196 );
1197
1198 let control_message_002 = GroupControlMessage {
1200 group_id: team_group_y.id(),
1201 action: GroupAction::Create {
1202 initial_members: vec![(GroupMember::Individual(alice), Access::manage())],
1203 },
1204 };
1205
1206 let (team_group_y, operation_002) =
1208 TestGroup::prepare(team_group_y, &control_message_002).unwrap();
1209
1210 let team_group_y = TestGroup::process(team_group_y, &operation_002).unwrap();
1212
1213 let control_message_003 = GroupControlMessage {
1215 group_id: team_group_y.id(),
1216 action: GroupAction::Add {
1217 member: GroupMember::Group(devices_group_y.id()),
1218 access: Access::read(),
1219 },
1220 };
1221 let (team_group_y, operation_003) =
1222 TestGroup::prepare(team_group_y, &control_message_003).unwrap();
1223 let team_group_y = TestGroup::process(team_group_y, &operation_003).unwrap();
1224
1225 let mut members = team_group_y.members();
1227 members.sort();
1228 assert_eq!(
1229 members,
1230 vec![
1231 (GroupMember::Individual(alice), Access::manage()),
1232 (GroupMember::Group(alice_devices_group), Access::read())
1233 ]
1234 );
1235
1236 let mut transitive_members = team_group_y.transitive_members().unwrap();
1239 transitive_members.sort();
1240 assert_eq!(
1241 transitive_members,
1242 vec![
1243 (alice, Access::manage()),
1244 (alice_laptop, Access::read()),
1245 (alice_mobile, Access::read()),
1246 ]
1247 );
1248 }
1249
1250 #[test]
1251 fn multi_user() {
1252 let alice = 'A';
1253 let bob = 'B';
1254 let claire = 'C';
1255
1256 let alice_mobile = 'M';
1257 let alice_laptop = 'L';
1258
1259 let alice_devices_group = 'D';
1260 let alice_team_group = 'T';
1261
1262 let rng = StdRng::from_os_rng();
1263 let mut network = Network::new([alice, bob, claire], rng);
1266
1267 network.create(
1269 alice_team_group,
1270 alice,
1271 vec![(GroupMember::Individual(alice), Access::manage())],
1272 );
1273
1274 network.add(
1276 alice,
1277 GroupMember::Individual(bob),
1278 alice_team_group,
1279 Access::manage(),
1280 );
1281
1282 network.process();
1284
1285 let alice_members = network.members(&alice, &alice_team_group);
1286 let bob_members = network.members(&bob, &alice_team_group);
1287 let claire_members = network.members(&claire, &alice_team_group);
1288 assert_eq!(
1289 alice_members,
1290 vec![
1291 (GroupMember::Individual('A'), Access::manage()),
1292 (GroupMember::Individual('B'), Access::manage()),
1293 ]
1294 );
1295 assert_eq!(alice_members, claire_members);
1296 assert_eq!(alice_members, bob_members);
1297
1298 let alice_transitive_members = network.transitive_members(&alice, &alice_team_group);
1299 let bob_transitive_members = network.transitive_members(&bob, &alice_team_group);
1300 let claire_transitive_members = network.transitive_members(&claire, &alice_team_group);
1301 assert_eq!(
1302 alice_transitive_members,
1303 vec![('A', Access::manage()), ('B', Access::manage()),]
1304 );
1305 assert_eq!(alice_transitive_members, bob_transitive_members);
1306 assert_eq!(alice_transitive_members, claire_transitive_members);
1307
1308 network.add(
1310 bob,
1311 GroupMember::Individual(claire),
1312 alice_team_group,
1313 Access::read(),
1314 );
1315
1316 network.create(
1318 alice_devices_group,
1319 alice,
1320 vec![
1321 (GroupMember::Individual(alice_mobile), Access::write()),
1322 (GroupMember::Individual(alice_laptop), Access::manage()),
1323 ],
1324 );
1325
1326 network.add(
1328 alice,
1329 GroupMember::Group(alice_devices_group),
1330 alice_team_group,
1331 Access::manage(),
1332 );
1333
1334 network.process();
1336
1337 let alice_members = network.members(&alice, &alice_team_group);
1338 let bob_members = network.members(&bob, &alice_team_group);
1339 let claire_members = network.members(&claire, &alice_team_group);
1340 assert_eq!(
1341 alice_members,
1342 vec![
1343 (GroupMember::Individual('A'), Access::manage()),
1344 (GroupMember::Individual('B'), Access::manage()),
1345 (GroupMember::Individual('C'), Access::read()),
1346 (GroupMember::Group('D'), Access::manage())
1347 ]
1348 );
1349 assert_eq!(alice_members, bob_members);
1350 assert_eq!(alice_members, claire_members);
1351
1352 let alice_transitive_members = network.transitive_members(&alice, &alice_team_group);
1353 let bob_transitive_members = network.transitive_members(&bob, &alice_team_group);
1354 let claire_transitive_members = network.transitive_members(&claire, &alice_team_group);
1355 assert_eq!(
1356 alice_transitive_members,
1357 vec![
1358 ('A', Access::manage()),
1359 ('B', Access::manage()),
1360 ('C', Access::read()),
1361 ('L', Access::manage()),
1362 ('M', Access::write())
1363 ]
1364 );
1365 assert_eq!(alice_transitive_members, bob_transitive_members);
1366 assert_eq!(alice_transitive_members, claire_transitive_members);
1367 }
1368
1369 #[test]
1370 fn ooo() {
1371 let alice = 'A';
1372 let bob = 'B';
1373 let claire = 'C';
1374
1375 let alice_friends = vec!['D', 'E', 'F'];
1376 let bob_friends = vec!['G', 'H', 'I'];
1377 let claire_friends = vec!['J', 'K', 'L'];
1378
1379 let friends_group = 'T';
1380
1381 let rng = StdRng::from_os_rng();
1382 let mut network = Network::new([alice, bob, claire], rng);
1385
1386 network.create(
1388 friends_group,
1389 alice,
1390 vec![
1391 (GroupMember::Individual(alice), Access::manage()),
1392 (GroupMember::Individual(bob), Access::manage()),
1393 (GroupMember::Individual(claire), Access::manage()),
1394 ],
1395 );
1396
1397 network.process();
1398
1399 for friend in &alice_friends {
1401 network.add(
1402 alice,
1403 GroupMember::Individual(*friend),
1404 friends_group,
1405 Access::read(),
1406 );
1407 }
1408
1409 network.remove(
1410 alice,
1411 GroupMember::Individual(alice_friends[0]),
1412 friends_group,
1413 );
1414
1415 for friend in &bob_friends {
1416 network.add(
1417 bob,
1418 GroupMember::Individual(*friend),
1419 friends_group,
1420 Access::read(),
1421 );
1422 }
1423
1424 network.remove(bob, GroupMember::Individual(bob_friends[0]), friends_group);
1425
1426 for friend in &claire_friends {
1427 network.add(
1428 claire,
1429 GroupMember::Individual(*friend),
1430 friends_group,
1431 Access::read(),
1432 );
1433 }
1434
1435 network.remove(
1436 claire,
1437 GroupMember::Individual(claire_friends[0]),
1438 friends_group,
1439 );
1440
1441 network.process_ooo();
1443
1444 let alice_members = network.members(&alice, &friends_group);
1445 let bob_members = network.members(&bob, &friends_group);
1446 let claire_members = network.members(&claire, &friends_group);
1447 assert_eq!(
1448 alice_members,
1449 vec![
1450 (GroupMember::Individual('A'), Access::manage()),
1451 (GroupMember::Individual('B'), Access::manage()),
1452 (GroupMember::Individual('C'), Access::manage()),
1453 (GroupMember::Individual('E'), Access::read()),
1455 (GroupMember::Individual('F'), Access::read()),
1456 (GroupMember::Individual('H'), Access::read()),
1458 (GroupMember::Individual('I'), Access::read()),
1459 (GroupMember::Individual('K'), Access::read()),
1461 (GroupMember::Individual('L'), Access::read()),
1462 ]
1463 );
1464 assert_eq!(alice_members, claire_members);
1465 assert_eq!(alice_members, bob_members);
1466 }
1467
1468 #[test]
1469 fn add_remove_add() {
1470 let alice = 'A';
1471 let bob = 'B';
1472
1473 let friends_group = 'T';
1474
1475 let rng = StdRng::from_os_rng();
1476 let mut network = Network::new([alice, bob], rng);
1479
1480 network.create(
1481 friends_group,
1482 alice,
1483 vec![(GroupMember::Individual(alice), Access::manage())],
1484 );
1485
1486 network.add(
1487 alice,
1488 GroupMember::Individual(bob),
1489 friends_group,
1490 Access::read(),
1491 );
1492
1493 network.remove(alice, GroupMember::Individual(bob), friends_group);
1494
1495 let members = network.members(&alice, &friends_group);
1496 assert_eq!(
1497 members,
1498 vec![(GroupMember::Individual('A'), Access::manage()),]
1499 );
1500
1501 network.add(
1502 alice,
1503 GroupMember::Individual(bob),
1504 friends_group,
1505 Access::read(),
1506 );
1507
1508 network.process();
1509
1510 let members = network.members(&alice, &friends_group);
1511 assert_eq!(
1512 members,
1513 vec![
1514 (GroupMember::Individual('A'), Access::manage()),
1515 (GroupMember::Individual('B'), Access::read()),
1516 ]
1517 );
1518 }
1519
1520 const ALICE: char = 'A';
1521 const BOB: char = 'B';
1522 const CHARLIE: char = 'C';
1523 const EDITH: char = 'E';
1524 const BOB_MOBILE: char = 'M';
1525 const BOB_LAPTOP: char = 'L';
1526
1527 const BOB_DEVICES_GROUP: char = 'D';
1528 const CHARLIE_TEAM_GROUP: char = 'T';
1529 const ALICE_ORG_GROUP: char = 'O';
1530
1531 fn test_groups(rng: StdRng) -> (Network, Vec<MessageId>) {
1534 let mut network = Network::new([ALICE, BOB, CHARLIE], rng);
1535 let mut operations = vec![];
1536
1537 let id = network.create(
1538 BOB_DEVICES_GROUP,
1539 BOB,
1540 vec![
1541 (GroupMember::Individual(BOB), Access::manage()),
1542 (GroupMember::Individual(BOB_LAPTOP), Access::write()),
1543 ],
1544 );
1545 operations.push(id);
1546
1547 let id = network.add(
1548 BOB,
1549 GroupMember::Individual(BOB_MOBILE),
1550 BOB_DEVICES_GROUP,
1551 Access::read(),
1552 );
1553 operations.push(id);
1554
1555 network.process();
1556
1557 let id = network.create(
1558 CHARLIE_TEAM_GROUP,
1559 CHARLIE,
1560 vec![
1561 (GroupMember::Individual(CHARLIE), Access::manage()),
1562 (GroupMember::Individual(EDITH), Access::read()),
1563 ],
1564 );
1565 operations.push(id);
1566
1567 let id = network.create(
1568 ALICE_ORG_GROUP,
1569 ALICE,
1570 vec![(GroupMember::Individual(ALICE), Access::manage())],
1571 );
1572 operations.push(id);
1573
1574 network.process();
1575
1576 let id = network.add(
1577 CHARLIE,
1578 GroupMember::Group(BOB_DEVICES_GROUP),
1579 CHARLIE_TEAM_GROUP,
1580 Access::manage(),
1581 );
1582 operations.push(id);
1583
1584 network.process();
1585
1586 let id = network.add(
1587 ALICE,
1588 GroupMember::Group(CHARLIE_TEAM_GROUP),
1589 ALICE_ORG_GROUP,
1590 Access::write(),
1591 );
1592 operations.push(id);
1593
1594 network.process();
1595
1596 (network, operations)
1597 }
1598
1599 #[test]
1600 fn transitive_members() {
1601 let rng = StdRng::from_os_rng();
1602 let (network, _) = test_groups(rng);
1603
1604 let expected_bob_devices_group_direct_members = vec![
1605 (GroupMember::Individual(BOB), Access::manage()),
1606 (GroupMember::Individual(BOB_LAPTOP), Access::write()),
1607 (GroupMember::Individual(BOB_MOBILE), Access::read()),
1608 ];
1609
1610 let expected_bob_devices_group_transitive_members = vec![
1611 (BOB, Access::manage()),
1612 (BOB_LAPTOP, Access::write()),
1613 (BOB_MOBILE, Access::read()),
1614 ];
1615
1616 let expected_charlie_team_group_direct_members = vec![
1617 (GroupMember::Individual(CHARLIE), Access::manage()),
1618 (GroupMember::Individual(EDITH), Access::read()),
1619 (GroupMember::Group(BOB_DEVICES_GROUP), Access::manage()),
1620 ];
1621
1622 let expected_charlie_team_group_transitive_members = vec![
1623 (BOB, Access::manage()),
1624 (CHARLIE, Access::manage()),
1625 (EDITH, Access::read()),
1626 (BOB_LAPTOP, Access::write()),
1627 (BOB_MOBILE, Access::read()),
1628 ];
1629
1630 let expected_alice_org_group_direct_members = vec![
1631 (GroupMember::Individual(ALICE), Access::manage()),
1632 (GroupMember::Group(CHARLIE_TEAM_GROUP), Access::write()),
1633 ];
1634
1635 let expected_alice_org_group_transitive_members = vec![
1636 (ALICE, Access::manage()),
1637 (BOB, Access::write()),
1638 (CHARLIE, Access::write()),
1639 (EDITH, Access::read()),
1640 (BOB_LAPTOP, Access::write()),
1641 (BOB_MOBILE, Access::read()),
1642 ];
1643
1644 let members = network.members(&BOB, &BOB_DEVICES_GROUP);
1645 assert_eq!(members, expected_bob_devices_group_direct_members);
1646
1647 let transitive_members = network.transitive_members(&BOB, &BOB_DEVICES_GROUP);
1648 assert_eq!(
1649 transitive_members,
1650 expected_bob_devices_group_transitive_members
1651 );
1652
1653 let members = network.members(&CHARLIE, &CHARLIE_TEAM_GROUP);
1654 assert_eq!(members, expected_charlie_team_group_direct_members);
1655
1656 let transitive_members = network.transitive_members(&CHARLIE, &CHARLIE_TEAM_GROUP);
1657 assert_eq!(
1658 transitive_members,
1659 expected_charlie_team_group_transitive_members
1660 );
1661
1662 let members = network.members(&ALICE, &ALICE_ORG_GROUP);
1663 assert_eq!(members, expected_alice_org_group_direct_members);
1664
1665 let transitive_members = network.transitive_members(&ALICE, &ALICE_ORG_GROUP);
1666 assert_eq!(
1667 transitive_members,
1668 expected_alice_org_group_transitive_members
1669 );
1670 }
1671
1672 #[test]
1673 fn members_at() {
1674 let rng = StdRng::from_os_rng();
1675 let (network, operations) = test_groups(rng);
1676
1677 let create_devices_op_id = operations[0];
1678 let add_mobile_to_devices_op_id = operations[1];
1679 let create_team_op_id = operations[2];
1680 let create_org_op_id = operations[3];
1681 let add_devices_to_team_op_id = operations[4];
1682 let add_team_to_org_op_id = operations[5];
1683
1684 let members =
1686 network.transitive_members_at(&ALICE, &ALICE_ORG_GROUP, &vec![create_org_op_id]);
1687 assert_eq!(members, vec![(ALICE, Access::manage())]);
1688
1689 let members = network.transitive_members_at(
1691 &ALICE,
1692 &ALICE_ORG_GROUP,
1693 &vec![add_team_to_org_op_id, create_team_op_id],
1694 );
1695 assert_eq!(
1696 members,
1697 vec![
1698 (ALICE, Access::manage()),
1699 (CHARLIE, Access::write()),
1700 (EDITH, Access::read())
1701 ]
1702 );
1703
1704 let members = network.transitive_members_at(
1706 &ALICE,
1707 &ALICE_ORG_GROUP,
1708 &vec![
1709 add_team_to_org_op_id,
1710 create_devices_op_id,
1711 add_devices_to_team_op_id,
1712 ],
1713 );
1714 assert_eq!(
1715 members,
1716 vec![
1717 (ALICE, Access::manage()),
1718 (BOB, Access::write()),
1719 (CHARLIE, Access::write()),
1720 (EDITH, Access::read()),
1721 (BOB_LAPTOP, Access::write()),
1722 ]
1723 );
1724
1725 let members_at_most_recent_heads = network.transitive_members_at(
1727 &ALICE,
1728 &ALICE_ORG_GROUP,
1729 &vec![
1730 add_team_to_org_op_id,
1731 add_mobile_to_devices_op_id,
1732 add_devices_to_team_op_id,
1733 ],
1734 );
1735 assert_eq!(
1736 members_at_most_recent_heads,
1737 vec![
1738 (ALICE, Access::manage()),
1739 (BOB, Access::write()),
1740 (CHARLIE, Access::write()),
1741 (EDITH, Access::read()),
1742 (BOB_LAPTOP, Access::write()),
1743 (BOB_MOBILE, Access::read()),
1744 ]
1745 );
1746
1747 let current_members = network.transitive_members(&ALICE, &ALICE_ORG_GROUP);
1749 let members_by_all_known_operations =
1752 network.transitive_members_at(&ALICE, &ALICE_ORG_GROUP, &operations);
1753
1754 assert_eq!(members_at_most_recent_heads, current_members);
1755 assert_eq!(
1756 members_at_most_recent_heads,
1757 members_by_all_known_operations
1758 );
1759 }
1760 #[test]
1761 fn error_cases() {
1762 let group_id = '0';
1763 let alice = 'A';
1764 let bob = 'B';
1765 let claire = 'C';
1766 let dave = 'D';
1767 let eve = 'E';
1768
1769 let mut rng = StdRng::from_os_rng();
1770
1771 let (y_i, _) = create_group(
1772 alice,
1773 group_id,
1774 vec![
1775 (alice, Access::manage()),
1776 (bob, Access::read()),
1777 (claire, Access::read()),
1778 ],
1779 &mut rng,
1780 );
1781
1782 let previous: Vec<u32> = y_i.heads().into_iter().collect();
1783
1784 let op = TestOperation {
1786 id: 1,
1787 author: alice,
1788 dependencies: previous.clone(),
1789 previous: previous.clone(),
1790 payload: GroupControlMessage {
1791 group_id,
1792 action: GroupAction::Add {
1793 member: GroupMember::Individual(bob),
1794 access: Access::read(),
1795 },
1796 },
1797 };
1798 assert!(matches!(
1799 GroupCrdt::process(y_i.clone(), &op),
1800 Err(GroupCrdtError::StateChangeError(
1801 _,
1802 GroupMembershipError::AlreadyAdded(GroupMember::Individual('B'))
1803 ))
1804 ));
1805
1806 let y_ii = GroupCrdt::process(
1808 y_i,
1809 &TestOperation {
1810 id: 2,
1811 author: alice,
1812 dependencies: previous.clone(),
1813 previous: previous.clone(),
1814 payload: GroupControlMessage {
1815 group_id,
1816 action: GroupAction::Remove {
1817 member: GroupMember::Individual(claire),
1818 },
1819 },
1820 },
1821 )
1822 .unwrap();
1823
1824 let previous: Vec<u32> = y_ii.heads().into_iter().collect();
1825
1826 let op = TestOperation {
1828 id: 3,
1829 author: alice,
1830 dependencies: previous.clone(),
1831 previous: previous.clone(),
1832 payload: GroupControlMessage {
1833 group_id,
1834 action: GroupAction::Remove {
1835 member: GroupMember::Individual(claire),
1836 },
1837 },
1838 };
1839 assert!(matches!(
1840 GroupCrdt::process(y_ii.clone(), &op),
1841 Err(GroupCrdtError::StateChangeError(
1842 _,
1843 GroupMembershipError::AlreadyRemoved(GroupMember::Individual('C'))
1844 ))
1845 ));
1846
1847 let op = TestOperation {
1849 id: 4,
1850 author: bob,
1851 dependencies: previous.clone(),
1852 previous: previous.clone(),
1853 payload: GroupControlMessage {
1854 group_id,
1855 action: GroupAction::Add {
1856 member: GroupMember::Individual(dave),
1857 access: Access::read(),
1858 },
1859 },
1860 };
1861 assert!(matches!(
1862 GroupCrdt::process(y_ii.clone(), &op),
1863 Err(GroupCrdtError::StateChangeError(
1864 _,
1865 GroupMembershipError::InsufficientAccess(GroupMember::Individual('B'))
1866 ))
1867 ));
1868
1869 let y_iii = GroupCrdt::process(
1871 y_ii,
1872 &TestOperation {
1873 id: 5,
1874 author: alice,
1875 dependencies: previous.clone(),
1876 previous: previous.clone(),
1877 payload: GroupControlMessage {
1878 group_id,
1879 action: GroupAction::Remove {
1880 member: GroupMember::Individual(bob),
1881 },
1882 },
1883 },
1884 )
1885 .unwrap();
1886
1887 let previous: Vec<u32> = y_iii.heads().into_iter().collect();
1888
1889 let op = TestOperation {
1891 id: 6,
1892 author: bob,
1893 dependencies: previous.clone(),
1894 previous: previous.clone(),
1895 payload: GroupControlMessage {
1896 group_id,
1897 action: GroupAction::Add {
1898 member: GroupMember::Individual(dave),
1899 access: Access::read(),
1900 },
1901 },
1902 };
1903 assert!(matches!(
1904 GroupCrdt::process(y_iii.clone(), &op),
1905 Err(GroupCrdtError::StateChangeError(
1906 _,
1907 GroupMembershipError::InactiveActor(GroupMember::Individual('B'))
1908 ))
1909 ));
1910
1911 let op = TestOperation {
1913 id: 7,
1914 author: alice,
1915 dependencies: previous.clone(),
1916 previous: previous.clone(),
1917 payload: GroupControlMessage {
1918 group_id,
1919 action: GroupAction::Promote {
1920 member: GroupMember::Individual(claire),
1921 access: Access::write(),
1922 },
1923 },
1924 };
1925 assert!(matches!(
1926 GroupCrdt::process(y_iii.clone(), &op),
1927 Err(GroupCrdtError::StateChangeError(
1928 _,
1929 GroupMembershipError::InactiveMember(GroupMember::Individual('C'))
1930 ))
1931 ));
1932
1933 let op = TestOperation {
1935 id: 8,
1936 author: eve,
1937 dependencies: previous.clone(),
1938 previous: previous.clone(),
1939 payload: GroupControlMessage {
1940 group_id,
1941 action: GroupAction::Add {
1942 member: GroupMember::Individual(dave),
1943 access: Access::read(),
1944 },
1945 },
1946 };
1947 assert!(matches!(
1948 GroupCrdt::process(y_iii.clone(), &op),
1949 Err(GroupCrdtError::StateChangeError(
1950 _,
1951 GroupMembershipError::UnrecognisedActor(GroupMember::Individual('E'))
1952 ))
1953 ));
1954
1955 let op = TestOperation {
1957 id: 9,
1958 author: alice,
1959 dependencies: previous.clone(),
1960 previous: previous.clone(),
1961 payload: GroupControlMessage {
1962 group_id,
1963 action: GroupAction::Promote {
1964 member: GroupMember::Individual(eve),
1965 access: Access::write(),
1966 },
1967 },
1968 };
1969 assert!(matches!(
1970 GroupCrdt::process(y_iii.clone(), &op),
1971 Err(GroupCrdtError::StateChangeError(
1972 _,
1973 GroupMembershipError::UnrecognisedMember(GroupMember::Individual('E'))
1974 ))
1975 ));
1976 }
1977 #[test]
1978 fn error_cases_resolver() {
1979 let group_id = '0';
1980 let alice = 'A';
1981 let bob = 'B';
1982 let claire = 'C';
1983 let dave = 'D';
1984 let eve = 'E';
1985
1986 let mut rng = StdRng::from_os_rng();
1987
1988 let (y_i, _) = create_group(
1989 alice,
1990 group_id,
1991 vec![
1992 (alice, Access::manage()),
1993 (bob, Access::read()),
1994 (claire, Access::read()),
1995 ],
1996 &mut rng,
1997 );
1998
1999 let previous: Vec<u32> = y_i.heads().into_iter().collect();
2000
2001 let (mut y_ii, _) = remove_member(y_i, group_id, bob);
2003 (y_ii, _) = remove_member(y_ii, group_id, claire);
2004 (y_ii, _) = add_member(y_ii, group_id, dave, Access::manage());
2005 (y_ii, _) = add_member(y_ii, group_id, eve, Access::manage());
2006 (y_ii, _) = remove_member(y_ii, group_id, alice);
2007
2008 let mut members = y_ii.members();
2009 members.sort();
2010 assert_eq!(
2011 members,
2012 vec![
2013 (GroupMember::Individual(dave), Access::manage()),
2014 (GroupMember::Individual(eve), Access::manage())
2015 ]
2016 );
2017
2018 let op = TestOperation {
2024 id: 1,
2025 author: alice,
2026 dependencies: previous.clone(),
2027 previous: previous.clone(),
2028 payload: GroupControlMessage {
2029 group_id,
2030 action: GroupAction::Add {
2031 member: GroupMember::Individual(bob),
2032 access: Access::read(),
2033 },
2034 },
2035 };
2036 assert!(matches!(
2037 GroupCrdt::process(y_ii.clone(), &op),
2038 Err(GroupCrdtError::StateChangeError(
2039 _,
2040 GroupMembershipError::AlreadyAdded(GroupMember::Individual('B'))
2041 ))
2042 ));
2043
2044 let op = TestOperation {
2046 id: 2,
2047 author: alice,
2048 dependencies: previous.clone(),
2049 previous: previous.clone(),
2050 payload: GroupControlMessage {
2051 group_id,
2052 action: GroupAction::Remove {
2053 member: GroupMember::Individual(claire),
2054 },
2055 },
2056 };
2057 let y_iii = GroupCrdt::process(y_ii.clone(), &op).unwrap();
2058
2059 let previous = vec![op.id];
2061
2062 let op = TestOperation {
2064 id: 3,
2065 author: alice,
2066 dependencies: previous.clone(),
2067 previous: previous.clone(),
2068 payload: GroupControlMessage {
2069 group_id,
2070 action: GroupAction::Remove {
2071 member: GroupMember::Individual(claire),
2072 },
2073 },
2074 };
2075 assert!(matches!(
2076 GroupCrdt::process(y_iii.clone(), &op),
2077 Err(GroupCrdtError::StateChangeError(
2078 _,
2079 GroupMembershipError::AlreadyRemoved(GroupMember::Individual('C'))
2080 ))
2081 ));
2082
2083 let op = TestOperation {
2085 id: 4,
2086 author: bob,
2087 dependencies: previous.clone(),
2088 previous: previous.clone(),
2089 payload: GroupControlMessage {
2090 group_id,
2091 action: GroupAction::Add {
2092 member: GroupMember::Individual(dave),
2093 access: Access::read(),
2094 },
2095 },
2096 };
2097 assert!(matches!(
2098 GroupCrdt::process(y_iii.clone(), &op),
2099 Err(GroupCrdtError::StateChangeError(
2100 _,
2101 GroupMembershipError::InsufficientAccess(GroupMember::Individual('B'))
2102 ))
2103 ));
2104
2105 let op = TestOperation {
2107 id: 5,
2108 author: alice,
2109 dependencies: previous.clone(),
2110 previous: previous.clone(),
2111 payload: GroupControlMessage {
2112 group_id,
2113 action: GroupAction::Remove {
2114 member: GroupMember::Individual(bob),
2115 },
2116 },
2117 };
2118 let y_iv = GroupCrdt::process(y_iii.clone(), &op).unwrap();
2119
2120 let previous = vec![op.id];
2122
2123 let op = TestOperation {
2125 id: 6,
2126 author: bob,
2127 dependencies: previous.clone(),
2128 previous: previous.clone(),
2129 payload: GroupControlMessage {
2130 group_id,
2131 action: GroupAction::Add {
2132 member: GroupMember::Individual(dave),
2133 access: Access::read(),
2134 },
2135 },
2136 };
2137 assert!(matches!(
2138 GroupCrdt::process(y_iv.clone(), &op),
2139 Err(GroupCrdtError::StateChangeError(
2140 _,
2141 GroupMembershipError::InactiveActor(GroupMember::Individual('B'))
2142 ))
2143 ));
2144
2145 let op = TestOperation {
2147 id: 7,
2148 author: alice,
2149 dependencies: previous.clone(),
2150 previous: previous.clone(),
2151 payload: GroupControlMessage {
2152 group_id,
2153 action: GroupAction::Promote {
2154 member: GroupMember::Individual(claire),
2155 access: Access::write(),
2156 },
2157 },
2158 };
2159 assert!(matches!(
2160 GroupCrdt::process(y_iv.clone(), &op),
2161 Err(GroupCrdtError::StateChangeError(
2162 _,
2163 GroupMembershipError::InactiveMember(GroupMember::Individual('C'))
2164 ))
2165 ));
2166
2167 let op = TestOperation {
2169 id: 8,
2170 author: eve,
2171 dependencies: previous.clone(),
2172 previous: previous.clone(),
2173 payload: GroupControlMessage {
2174 group_id,
2175 action: GroupAction::Add {
2176 member: GroupMember::Individual(dave),
2177 access: Access::read(),
2178 },
2179 },
2180 };
2181 assert!(matches!(
2182 GroupCrdt::process(y_iv.clone(), &op),
2183 Err(GroupCrdtError::StateChangeError(
2184 _,
2185 GroupMembershipError::UnrecognisedActor(GroupMember::Individual('E'))
2186 ))
2187 ));
2188
2189 let op = TestOperation {
2191 id: 9,
2192 author: alice,
2193 dependencies: previous.clone(),
2194 previous: previous.clone(),
2195 payload: GroupControlMessage {
2196 group_id,
2197 action: GroupAction::Promote {
2198 member: GroupMember::Individual(eve),
2199 access: Access::write(),
2200 },
2201 },
2202 };
2203 assert!(matches!(
2204 GroupCrdt::process(y_iv.clone(), &op),
2205 Err(GroupCrdtError::StateChangeError(
2206 _,
2207 GroupMembershipError::UnrecognisedMember(GroupMember::Individual('E'))
2208 ))
2209 ));
2210 }
2211}