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(unsafe))]
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.then(|| {
93 to_raw_value(&json!({ "redacted_because": {} }))
94 .expect("valid unsigned")
95 .into()
96 });
97
98 PduEvent {
99 kind,
100 content: Raw::from_json(
101 to_raw_value(&CanonicalJsonObject::new()).expect("valid content"),
102 ),
103 event_id: event_id!("$event:example.com").to_owned(),
104 room_id: room_id!("!room:example.com").to_owned(),
105 sender: user_id!("@alice:example.com").to_owned(),
106 state_key,
107 redacts: None,
108 prev_events: Default::default(),
109 auth_events: Default::default(),
110 origin_server_ts: uint!(1),
111 depth: uint!(1),
112 hashes: Default::default(),
113 origin: None,
114 unsigned,
115 }
116 }
117
118 #[test]
119 fn default_bump_types_are_sorted() {
120 assert!(DEFAULT_BUMP_TYPES.is_sorted());
121 }
122
123 #[test]
124 fn default_bump_types_bump() {
125 let sender = user_id!("@alice:example.com");
126
127 for kind in DEFAULT_BUMP_TYPES.iter().cloned() {
128 assert!(is_bumpable_pdu(&pdu(kind, None, false), sender));
129 }
130 }
131
132 #[test]
133 fn non_bump_type_does_not_bump() {
134 let sender = user_id!("@alice:example.com");
135 let pdu = pdu(TimelineEventType::RoomName, Some("".into()), false);
136
137 assert!(!is_bumpable_pdu(&pdu, sender));
138 }
139
140 #[test]
141 fn own_membership_bumps() {
142 let sender = user_id!("@alice:example.com");
143 let pdu = pdu(TimelineEventType::RoomMember, Some(sender.as_str().into()), false);
144
145 assert!(is_bumpable_pdu(&pdu, sender));
146 }
147
148 #[test]
149 fn other_membership_does_not_bump() {
150 let sender = user_id!("@alice:example.com");
151 let pdu = pdu(TimelineEventType::RoomMember, Some("@bob:example.com".into()), false);
152
153 assert!(!is_bumpable_pdu(&pdu, sender));
154 }
155
156 #[test]
157 fn redacted_pdu_does_not_bump() {
158 let sender = user_id!("@alice:example.com");
159 let pdu = pdu(TimelineEventType::RoomMessage, None, true);
160
161 assert!(!is_bumpable_pdu(&pdu, sender));
162 }
163}