xmtp_proto/impls/
update_dedupe.rs

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