Skip to main content

tuwunel_api/client/sync/v5/rooms/
bump_stamp.rs

1use 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
18/// MUST be sorted by `TimelineEventType::event_type_str()` for `binary_search`.
19static DEFAULT_BUMP_TYPES: [TimelineEventType; 6] = [
20	CallInvite,    // m.call.invite
21	PollStart,     // m.poll.start
22	RoomEncrypted, // m.room.encrypted
23	RoomMessage,   // m.room.message
24	Sticker,       // m.sticker
25	Beacon,        // org.matrix.msc3672.beacon
26];
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}