xmtp_mls_validation/
group_membership.rs1use prost::{DecodeError, Message};
2use std::collections::HashMap;
3use xmtp_proto::xmtp::mls::message_contents::GroupMembership as GroupMembershipProto;
4
5#[derive(Debug, Clone, PartialEq)]
6pub struct GroupMembership {
7 pub members: HashMap<String, u64>,
8 pub failed_installations: Vec<Vec<u8>>,
9}
10
11impl GroupMembership {
12 pub fn new() -> Self {
13 GroupMembership {
14 members: HashMap::new(),
15 failed_installations: Vec::new(),
16 }
17 }
18
19 pub fn add(&mut self, inbox_id: String, last_sequence_id: u64) {
20 self.members.insert(inbox_id, last_sequence_id);
21 }
22
23 pub fn remove<InboxId: AsRef<str>>(&mut self, inbox_id: InboxId) {
24 self.members.remove(inbox_id.as_ref());
25 }
26
27 pub fn get<InboxId: AsRef<str>>(&self, inbox_id: InboxId) -> Option<&u64> {
28 self.members.get(inbox_id.as_ref())
29 }
30
31 pub fn inbox_ids(&self) -> Vec<&str> {
32 self.members.keys().map(AsRef::as_ref).collect()
33 }
34
35 pub fn to_filters(&self) -> Vec<(&str, i64)> {
37 self.members
38 .iter()
39 .map(|(inbox_id, sequence_id)| (inbox_id.as_str(), *sequence_id as i64))
40 .collect()
41 }
42
43 pub fn diff<'inbox_id>(
44 &'inbox_id self,
45 new_group_membership: &'inbox_id Self,
46 ) -> MembershipDiff<'inbox_id> {
47 let mut removed_inboxes: Vec<&String> = vec![];
48 let mut updated_inboxes: Vec<&String> = vec![];
49
50 for (inbox_id, last_sequence_id) in self.members.iter() {
51 match new_group_membership.get(inbox_id) {
52 Some(new_last_sequence_id) => {
53 if new_last_sequence_id.ne(last_sequence_id) {
54 updated_inboxes.push(inbox_id);
55 }
56 }
57 None => {
58 removed_inboxes.push(inbox_id);
59 }
60 }
61 }
62
63 let added_inboxes = new_group_membership
64 .members
65 .keys()
66 .filter(|&inbox_id| !self.members.contains_key(inbox_id))
67 .collect::<Vec<&String>>();
68
69 MembershipDiff {
70 added_inboxes,
71 removed_inboxes,
72 updated_inboxes,
73 }
74 }
75}
76
77impl Default for GroupMembership {
78 fn default() -> Self {
79 GroupMembership::new()
80 }
81}
82
83impl TryFrom<Vec<u8>> for GroupMembership {
84 type Error = DecodeError;
85
86 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
87 let membership_proto = GroupMembershipProto::decode(value.as_slice())?;
88
89 Ok(GroupMembership {
90 members: membership_proto.members,
91 failed_installations: membership_proto.failed_installations,
92 })
93 }
94}
95
96impl From<&GroupMembership> for Vec<u8> {
97 fn from(value: &GroupMembership) -> Self {
98 let membership_proto = GroupMembershipProto {
99 members: value.members.clone(),
100 failed_installations: value.failed_installations.clone(),
101 };
102
103 membership_proto.encode_to_vec()
104 }
105}
106
107#[derive(Debug, Clone)]
108pub struct MembershipDiff<'inbox_id> {
109 pub added_inboxes: Vec<&'inbox_id String>,
110 pub removed_inboxes: Vec<&'inbox_id String>,
111 pub updated_inboxes: Vec<&'inbox_id String>,
112}
113
114#[cfg(test)]
115pub(crate) mod tests {
116 use super::GroupMembership;
117
118 #[xmtp_common::test]
119 fn test_equality_works() {
120 let inbox_id_1 = "inbox_1".to_string();
121 let sequence_id_1: u64 = 1;
122 let mut member_map_1 = GroupMembership::new();
123 let mut member_map_2 = GroupMembership::new();
124
125 member_map_1.add(inbox_id_1.clone(), sequence_id_1);
126
127 assert!(member_map_1.ne(&member_map_2));
128
129 member_map_2.add(inbox_id_1.clone(), sequence_id_1);
130 assert!(member_map_1.eq(&member_map_2));
131
132 member_map_2.add(inbox_id_1.clone(), 2);
134 assert!(member_map_1.ne(&member_map_2));
135 }
136
137 #[xmtp_common::test]
138 fn test_diff() {
139 let mut initial_members = GroupMembership::new();
140 initial_members.add("inbox_1".into(), 1);
141 initial_members.add("inbox_2".into(), 1);
142
143 let mut updated_list = initial_members.clone();
144 updated_list.remove("inbox_1");
145 updated_list.add("inbox_2".into(), 2);
146 updated_list.add("inbox_3".into(), 1);
147
148 let diff = initial_members.diff(&updated_list);
149 assert_eq!(diff.added_inboxes, vec!["inbox_3"]);
150 assert_eq!(diff.updated_inboxes, vec!["inbox_2"]);
151 assert_eq!(diff.removed_inboxes, vec!["inbox_1"]);
152 }
153}