tuwunel_api/client/sync/v5/rooms/
bump_stamp.rs1use futures::{TryStreamExt, 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 Result, is_equal_to,
10 matrix::{
11 Event,
12 pdu::{PduCount, PduEvent},
13 },
14 utils::TryReadyExt,
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) -> Result<Option<UInt>> {
36 if last_timeline_count <= roomsince {
37 return Ok(None);
38 }
39
40 let bumpable_pdus = services
41 .timeline
42 .pdus_rev(Some(sender_user), room_id, Some(next_batch.saturating_add(1)))
43 .ready_try_take_while(|&(pdu_count, _)| Ok(pdu_count > roomsince))
44 .ready_try_filter_map(|(pdu_count, pdu)| {
45 Ok(is_bumpable_pdu(&pdu, sender_user)
46 .then(|| pdu_count.into_signed().try_into().ok())
47 .flatten())
48 });
49
50 pin_mut!(bumpable_pdus);
51 bumpable_pdus.try_next().await
52}
53
54fn is_bumpable_pdu(pdu: &PduEvent, sender_user: &UserId) -> bool {
55 if pdu.is_redacted() {
56 return false;
57 }
58
59 if *pdu.event_type() == TimelineEventType::RoomMember {
60 return pdu
61 .state_key()
62 .is_some_and(is_equal_to!(sender_user.as_str()));
63 }
64
65 DEFAULT_BUMP_TYPES
66 .binary_search(pdu.event_type())
67 .is_ok()
68}
69
70#[cfg_attr(debug_assertions, tuwunel_core::ctor(unsafe))]
71fn _is_sorted() {
72 debug_assert!(
73 DEFAULT_BUMP_TYPES.is_sorted(),
74 "DEFAULT_BUMP_TYPES must be sorted by the developer"
75 );
76}
77
78#[cfg(test)]
79mod tests {
80 use ruma::{
81 CanonicalJsonObject, event_id, events::TimelineEventType, room_id, serde::Raw, uint,
82 user_id,
83 };
84 use serde_json::{json, value::to_raw_value};
85 use tuwunel_core::matrix::{StateKey, pdu::PduEvent};
86
87 use super::{DEFAULT_BUMP_TYPES, is_bumpable_pdu};
88
89 fn pdu(kind: TimelineEventType, state_key: Option<StateKey>, redacted: bool) -> PduEvent {
90 let unsigned = redacted.then(|| {
91 to_raw_value(&json!({ "redacted_because": {} }))
92 .expect("valid unsigned")
93 .into()
94 });
95
96 PduEvent {
97 kind,
98 content: Raw::from_json(
99 to_raw_value(&CanonicalJsonObject::new()).expect("valid content"),
100 ),
101 event_id: event_id!("$event:example.com").to_owned(),
102 room_id: room_id!("!room:example.com").to_owned(),
103 sender: user_id!("@alice:example.com").to_owned(),
104 state_key,
105 redacts: None,
106 prev_events: Default::default(),
107 auth_events: Default::default(),
108 origin_server_ts: uint!(1),
109 depth: uint!(1),
110 hashes: Default::default(),
111 origin: None,
112 unsigned,
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}