tuwunel_api/client/sync/v5/rooms/
bump_stamp.rs1use futures::{StreamExt, pin_mut};
2use ruma::{
3 RoomId, UInt, UserId,
4 events::TimelineEventType::{
5 self, Beacon, CallInvite, PollStart, RoomEncrypted, RoomMessage, Sticker,
6 },
7};
8use tuwunel_core::{
9 is_equal_to,
10 matrix::{
11 Event,
12 pdu::{PduCount, PduEvent},
13 },
14 utils::stream::ReadyExt,
15};
16use tuwunel_service::Services;
17
18static DEFAULT_BUMP_TYPES: [TimelineEventType; 6] = [
20 CallInvite, PollStart, RoomEncrypted, RoomMessage, Sticker, Beacon, ];
27
28pub(super) async fn room_bump_stamp(
29 services: &Services,
30 sender_user: &UserId,
31 room_id: &RoomId,
32 roomsince: PduCount,
33 next_batch: PduCount,
34 last_timeline_count: PduCount,
35) -> Option<UInt> {
36 if last_timeline_count <= roomsince {
37 return None;
38 }
39
40 let bumpable_pdus = services
41 .timeline
42 .pdus_rev(Some(sender_user), room_id, None)
43 .ready_filter_map(Result::ok)
44 .ready_skip_while(|&(pdu_count, _)| pdu_count > next_batch)
45 .ready_take_while(|&(pdu_count, _)| pdu_count > roomsince)
46 .ready_filter_map(|(pdu_count, pdu)| {
47 is_bumpable_pdu(&pdu, sender_user)
48 .then(|| pdu_count.into_signed().try_into().ok())
49 .flatten()
50 });
51
52 pin_mut!(bumpable_pdus);
53 bumpable_pdus.next().await
54}
55
56fn is_bumpable_pdu(pdu: &PduEvent, sender_user: &UserId) -> bool {
57 if pdu.is_redacted() {
58 return false;
59 }
60
61 if *pdu.event_type() == TimelineEventType::RoomMember {
62 return pdu
63 .state_key()
64 .is_some_and(is_equal_to!(sender_user.as_str()));
65 }
66
67 DEFAULT_BUMP_TYPES
68 .binary_search(pdu.event_type())
69 .is_ok()
70}
71
72#[cfg_attr(debug_assertions, tuwunel_core::ctor)]
73fn _is_sorted() {
74 debug_assert!(
75 DEFAULT_BUMP_TYPES.is_sorted(),
76 "DEFAULT_BUMP_TYPES must be sorted by the developer"
77 );
78}
79
80#[cfg(test)]
81mod tests {
82 use ruma::{
83 CanonicalJsonObject, event_id, events::TimelineEventType, room_id, serde::Raw, uint,
84 user_id,
85 };
86 use serde_json::{json, value::to_raw_value};
87 use tuwunel_core::matrix::{StateKey, pdu::PduEvent};
88
89 use super::{DEFAULT_BUMP_TYPES, is_bumpable_pdu};
90
91 fn pdu(kind: TimelineEventType, state_key: Option<StateKey>, redacted: bool) -> PduEvent {
92 let unsigned = redacted
93 .then(|| to_raw_value(&json!({ "redacted_because": {} })).expect("valid unsigned"));
94
95 PduEvent {
96 kind,
97 content: Raw::from_json(
98 to_raw_value(&CanonicalJsonObject::new()).expect("valid content"),
99 ),
100 event_id: event_id!("$event:example.com").to_owned(),
101 room_id: room_id!("!room:example.com").to_owned(),
102 sender: user_id!("@alice:example.com").to_owned(),
103 state_key,
104 redacts: None,
105 prev_events: Default::default(),
106 auth_events: Default::default(),
107 origin_server_ts: uint!(1),
108 depth: uint!(1),
109 hashes: Default::default(),
110 origin: None,
111 unsigned,
112 signatures: None,
113 }
114 }
115
116 #[test]
117 fn default_bump_types_are_sorted() {
118 assert!(DEFAULT_BUMP_TYPES.is_sorted());
119 }
120
121 #[test]
122 fn default_bump_types_bump() {
123 let sender = user_id!("@alice:example.com");
124
125 for kind in DEFAULT_BUMP_TYPES.iter().cloned() {
126 assert!(is_bumpable_pdu(&pdu(kind, None, false), sender));
127 }
128 }
129
130 #[test]
131 fn non_bump_type_does_not_bump() {
132 let sender = user_id!("@alice:example.com");
133 let pdu = pdu(TimelineEventType::RoomName, Some("".into()), false);
134
135 assert!(!is_bumpable_pdu(&pdu, sender));
136 }
137
138 #[test]
139 fn own_membership_bumps() {
140 let sender = user_id!("@alice:example.com");
141 let pdu = pdu(TimelineEventType::RoomMember, Some(sender.as_str().into()), false);
142
143 assert!(is_bumpable_pdu(&pdu, sender));
144 }
145
146 #[test]
147 fn other_membership_does_not_bump() {
148 let sender = user_id!("@alice:example.com");
149 let pdu = pdu(TimelineEventType::RoomMember, Some("@bob:example.com".into()), false);
150
151 assert!(!is_bumpable_pdu(&pdu, sender));
152 }
153
154 #[test]
155 fn redacted_pdu_does_not_bump() {
156 let sender = user_id!("@alice:example.com");
157 let pdu = pdu(TimelineEventType::RoomMessage, None, true);
158
159 assert!(!is_bumpable_pdu(&pdu, sender));
160 }
161}