Skip to main content

xmtp_proto/impls/
update_dedupe.rs

1use crate::xmtp::mls::message_contents::GroupUpdated;
2use std::hash::{DefaultHasher, Hash, Hasher};
3
4macro_rules! hash_arm {
5    ($self:ident, $update:ident, $field:ident) => {
6        if !$update.$field.is_empty() {
7            let mut hasher = DefaultHasher::new();
8            $update.$field.hash(&mut hasher);
9            $self.$field = Some(hasher.finish());
10        }
11    };
12}
13
14macro_rules! match_arm {
15    ($self:ident, $other:ident, $field:ident) => {
16        match $self.$field.is_some() {
17            true => $self.$field == $other.$field,
18            false => true,
19        }
20    };
21}
22
23#[derive(Default)]
24pub struct GroupUpdateDeduper {
25    added_inboxes: Option<u64>,
26    removed_inboxes: Option<u64>,
27    metadata_field_changes: Option<u64>,
28    left_inboxes: Option<u64>,
29    added_admin_inboxes: Option<u64>,
30    removed_admin_inboxes: Option<u64>,
31    added_super_admin_inboxes: Option<u64>,
32    removed_super_admin_inboxes: Option<u64>,
33}
34
35impl GroupUpdateDeduper {
36    pub fn consume(&mut self, update: &GroupUpdated) {
37        hash_arm!(self, update, added_inboxes);
38        hash_arm!(self, update, removed_inboxes);
39        hash_arm!(self, update, metadata_field_changes);
40        hash_arm!(self, update, left_inboxes);
41        hash_arm!(self, update, added_admin_inboxes);
42        hash_arm!(self, update, removed_admin_inboxes);
43        hash_arm!(self, update, added_super_admin_inboxes);
44        hash_arm!(self, update, removed_super_admin_inboxes);
45    }
46
47    pub fn is_dupe(&self, update: &GroupUpdated) -> bool {
48        let mut hash = Self::default();
49        hash.consume(update);
50
51        match_arm!(hash, self, added_inboxes)
52            && match_arm!(hash, self, removed_inboxes)
53            && match_arm!(hash, self, metadata_field_changes)
54            && match_arm!(hash, self, left_inboxes)
55            && match_arm!(hash, self, added_admin_inboxes)
56            && match_arm!(hash, self, removed_admin_inboxes)
57            && match_arm!(hash, self, added_super_admin_inboxes)
58            && match_arm!(hash, self, removed_super_admin_inboxes)
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use crate::{
65        impls::update_dedupe::GroupUpdateDeduper,
66        xmtp::mls::message_contents::{
67            GroupUpdated,
68            group_updated::{Inbox, MetadataFieldChange},
69        },
70    };
71
72    #[xmtp_common::test(unwrap_try = true)]
73    async fn test_dedupe() {
74        let add_update = GroupUpdated {
75            added_inboxes: vec![Inbox {
76                inbox_id: "123".to_string(),
77            }],
78            ..Default::default()
79        };
80
81        let mut deduper = GroupUpdateDeduper::default();
82        deduper.consume(&add_update);
83
84        // An update should be a duplicate of itself.
85        assert!(deduper.is_dupe(&add_update));
86
87        // Change the added_inboxes, this should now be unique.
88        let mut another_add_update = add_update.clone();
89        another_add_update.added_inboxes = vec![Inbox {
90            inbox_id: "234".to_string(),
91        }];
92        assert!(!deduper.is_dupe(&another_add_update));
93
94        // Have the deduper consume the diff update
95        deduper.consume(&another_add_update);
96        // The diff update should now be a dupe.
97        assert!(deduper.is_dupe(&another_add_update));
98
99        // Now let's check with other update fields.
100        let remove_update = GroupUpdated {
101            removed_inboxes: vec![Inbox {
102                inbox_id: "123".to_string(),
103            }],
104            ..Default::default()
105        };
106        // This should of course be unique.
107        assert!(!deduper.is_dupe(&remove_update));
108
109        // The old add should still be a dupe.
110        assert!(deduper.is_dupe(&another_add_update));
111        // The original update should no longer be a dupe because new
112        // updates to that field have occurred since it was consumed.
113        assert!(!deduper.is_dupe(&add_update));
114
115        // Now let's check with multiple fields.
116        let multi_update = GroupUpdated {
117            removed_inboxes: vec![Inbox {
118                inbox_id: "234".to_string(),
119            }],
120            metadata_field_changes: vec![MetadataFieldChange {
121                field_name: "disappearing_msgs".to_string(),
122                ..Default::default()
123            }],
124            ..Default::default()
125        };
126
127        // Even though this contains the same removed_inboxes as the udpate before
128        // (duplicating that field), this should still be unique due to the metadata
129        // updates.
130        assert!(!deduper.is_dupe(&multi_update));
131
132        // Now consume the multi_upddate and ensure that it's marked as a dupe.
133        deduper.consume(&multi_update);
134        assert!(deduper.is_dupe(&multi_update));
135    }
136}