1use std::{collections::BTreeMap, sync::Arc};
2
3use ruma::{
4 CanonicalJsonObject, CanonicalJsonValue, EventId, UserId,
5 events::{
6 TimelineEventType,
7 receipt::ReceiptThread,
8 relation::RelationType,
9 room::{
10 encrypted::Relation,
11 member::{MembershipState, RoomMemberEventContent},
12 },
13 },
14};
15use tuwunel_core::{
16 Result, err, error, implement,
17 matrix::{
18 event::Event,
19 pdu::{PduCount, PduEvent, PduId, RawPduId},
20 room_version,
21 },
22 smallvec::SmallVec,
23 utils::{self, result::LogErr},
24};
25use tuwunel_database::Json;
26
27use super::{ExtractBody, ExtractRelatesTo, ExtractRelatesToEventId, RoomMutexGuard, bias_count};
28use crate::rooms::{
29 read_receipt::PrivateRead, short::ShortRoomId, state_accessor::plain_text_topic,
30 state_cache::MembershipUpdate, state_compressor::CompressedState,
31};
32
33type Band<'a> = SmallVec<[&'a EventId; 1]>;
34
35#[implement(super::Service)]
38#[tracing::instrument(
39 name = "append_incoming",
40 level = "debug",
41 skip_all,
42 ret(Debug)
43)]
44pub(crate) async fn append_incoming_pdu<'a, Leafs>(
45 &'a self,
46 pdu: &'a PduEvent,
47 pdu_json: CanonicalJsonObject,
48 new_room_leafs: Leafs,
49 state_ids_compressed: Arc<CompressedState>,
50 soft_fail: bool,
51 state_lock: &'a RoomMutexGuard,
52) -> Result<Option<RawPduId>>
53where
54 Leafs: Iterator<Item = &'a EventId> + Send + 'a,
55{
56 self.services
60 .state
61 .set_event_state(&pdu.event_id, &pdu.room_id, state_ids_compressed)
62 .await?;
63
64 if soft_fail {
65 self.services
66 .pdu_metadata
67 .mark_as_referenced(&pdu.room_id, pdu.prev_events.iter().map(AsRef::as_ref));
68
69 if let Some(new_room_leafs) = nonempty_band(new_room_leafs) {
72 self.services
73 .state
74 .set_forward_extremities(&pdu.room_id, new_room_leafs.into_iter(), state_lock)
75 .await;
76 }
77
78 return Ok(None);
79 }
80
81 let pdu_id = self
82 .append_pdu(pdu, pdu_json, new_room_leafs, state_lock)
83 .await?;
84
85 Ok(Some(pdu_id))
86}
87
88fn nonempty_band<'a, Leafs>(leafs: Leafs) -> Option<Band<'a>>
89where
90 Leafs: Iterator<Item = &'a EventId>,
91{
92 let leafs: Band<'_> = leafs.collect();
93
94 (!leafs.is_empty()).then_some(leafs)
95}
96
97#[implement(super::Service)]
104#[tracing::instrument(name = "append", level = "debug", skip_all, ret(Debug))]
105pub async fn append_pdu<'a, Leafs>(
106 &'a self,
107 pdu: &'a PduEvent,
108 mut pdu_json: CanonicalJsonObject,
109 leafs: Leafs,
110 state_lock: &'a RoomMutexGuard,
111) -> Result<RawPduId>
112where
113 Leafs: Iterator<Item = &'a EventId> + Send + 'a,
114{
115 let _cork = self.db.db.cork_and_flush();
117
118 let shortroomid = self
119 .services
120 .short
121 .get_shortroomid(pdu.room_id())
122 .await
123 .map_err(|_| err!(Database("Room does not exist")))?;
124
125 if let Some(state_key) = pdu.state_key() {
129 if let CanonicalJsonValue::Object(unsigned) = pdu_json
130 .entry("unsigned".into())
131 .or_insert_with(|| CanonicalJsonValue::Object(BTreeMap::default()))
132 {
133 if let Ok(shortstatehash) = self
134 .services
135 .state
136 .pdu_shortstatehash(pdu.event_id())
137 .await && let Ok(prev_state) = self
138 .services
139 .state_accessor
140 .state_get(shortstatehash, &pdu.kind().to_string().into(), state_key)
141 .await
142 {
143 unsigned.insert(
144 "prev_content".into(),
145 CanonicalJsonValue::Object(
146 utils::to_canonical_object(prev_state.get_content_as_value()).map_err(
147 |e| {
148 err!(Database(error!(
149 "Failed to convert prev_state to canonical JSON: {e}",
150 )))
151 },
152 )?,
153 ),
154 );
155 unsigned.insert(
156 "prev_sender".into(),
157 CanonicalJsonValue::String(prev_state.sender().to_string()),
158 );
159 unsigned.insert(
160 "replaces_state".into(),
161 CanonicalJsonValue::String(prev_state.event_id().to_string()),
162 );
163 }
164 } else {
165 error!("Invalid unsigned type in pdu.");
166 }
167 }
168
169 self.services
171 .pdu_metadata
172 .mark_as_referenced(pdu.room_id(), pdu.prev_events().map(AsRef::as_ref));
173
174 self.services
175 .state
176 .set_forward_extremities(pdu.room_id(), leafs, state_lock)
177 .await;
178
179 let insert_lock = self.mutex_insert.lock(pdu.room_id()).await;
180 let next_count = self.services.globals.next_count();
181
182 self.services
186 .read_receipt
187 .private_read_set(PrivateRead {
188 room_id: pdu.room_id(),
189 user_id: pdu.sender(),
190 count: *next_count,
191 ts: pdu.origin_server_ts(),
192 thread: &ReceiptThread::Unthreaded,
193 announce: false,
194 })
195 .await;
196
197 self.services
198 .pusher
199 .reset_notification_counts_for_thread(
200 pdu.sender(),
201 pdu.room_id(),
202 &ReceiptThread::Unthreaded,
203 )
204 .await;
205
206 let count = PduCount::Normal(*next_count);
207 let pdu_id: RawPduId = PduId { shortroomid, count }.into();
208
209 self.append_pdu_json(&pdu_id, pdu, &pdu_json);
211
212 drop(insert_lock);
213
214 if self.services.globals.user_is_local(pdu.sender()) {
216 self.services
217 .sending
218 .refresh_push_badge(pdu.sender())
219 .await
220 .log_err()
221 .ok();
222 }
223
224 self.services
225 .pusher
226 .append_pdu(pdu_id, pdu)
227 .await
228 .log_err()
229 .ok();
230
231 self.append_pdu_effects(pdu_id, pdu, shortroomid, count, state_lock)
232 .await?;
233
234 drop(next_count);
235
236 self.services
237 .appservice
238 .append_pdu(pdu_id, pdu)
239 .await
240 .log_err()
241 .ok();
242
243 Ok(pdu_id)
244}
245
246#[implement(super::Service)]
247async fn append_pdu_effects(
248 &self,
249 pdu_id: RawPduId,
250 pdu: &PduEvent,
251 shortroomid: ShortRoomId,
252 count: PduCount,
253 state_lock: &RoomMutexGuard,
254) -> Result {
255 match *pdu.kind() {
256 | TimelineEventType::RoomRedaction => {
257 let room_version = self
258 .services
259 .state
260 .get_room_version(pdu.room_id())
261 .await?;
262
263 let room_rules = room_version::rules(&room_version)?;
264
265 let redacts_id = pdu.redacts_id(&room_rules);
266
267 if let Some(redacts_id) = &redacts_id
268 && self
269 .services
270 .state_accessor
271 .user_can_redact(redacts_id, pdu.sender(), pdu.room_id(), false)
272 .await?
273 {
274 self.redact_pdu(redacts_id, pdu, shortroomid, state_lock)
275 .await?;
276 }
277 },
278 | TimelineEventType::RoomMember => {
279 if let Some(state_key) = pdu.state_key() {
280 let target_user_id =
282 UserId::parse(state_key).expect("This state_key was previously validated");
283
284 let content: RoomMemberEventContent = pdu.get_content()?;
285 let stripped_state = match content.membership {
286 | MembershipState::Invite | MembershipState::Knock => self
287 .services
288 .state
289 .summary_stripped(pdu)
290 .await
291 .into(),
292 | _ => None,
293 };
294
295 self.services
299 .state_cache
300 .update_membership(MembershipUpdate {
301 room_id: pdu.room_id(),
302 user_id: &target_user_id,
303 membership_event: content,
304 sender: pdu.sender(),
305 last_state: stripped_state,
306 invite_via: None,
307 update_joined_count: true,
308 count,
309 })
310 .await?;
311 }
312 },
313 | TimelineEventType::RoomMessage => {
314 let content: ExtractBody = pdu.get_content()?;
315 if let Some(body) = content.body {
316 self.services
317 .search
318 .index_pdu(shortroomid, &pdu_id, &body);
319
320 if self
321 .services
322 .admin
323 .is_admin_command(pdu, &body)
324 .await
325 {
326 self.services
327 .admin
328 .command(body, Some((pdu.event_id()).into()))
329 .await?;
330 }
331 }
332 },
333 | TimelineEventType::RoomTopic =>
334 if let Some(topic) = pdu.get_content().ok().and_then(plain_text_topic) {
335 self.services
336 .search
337 .index_pdu(shortroomid, &pdu_id, &topic);
338 },
339 | _ => {},
340 }
341
342 if pdu.state_key().is_some() {
344 self.services.spaces.cache_evict(pdu.room_id());
345 }
346
347 if let Ok(content) = pdu.get_content::<ExtractRelatesToEventId>()
348 && let Ok(related_pducount) = self
349 .get_pdu_count(&content.relates_to.event_id)
350 .await
351 {
352 self.services
353 .pdu_metadata
354 .add_relation(count, related_pducount);
355 }
356
357 if let Ok(content) = pdu.get_content::<ExtractRelatesTo>() {
358 match content.relates_to {
359 | Relation::Reply(ruma::events::relation::Reply { in_reply_to }) => {
360 if let Ok(related_pducount) = self.get_pdu_count(&in_reply_to.event_id).await {
363 self.services
364 .pdu_metadata
365 .add_relation(count, related_pducount);
366 }
367 },
368 | Relation::Thread(thread) => {
369 self.services
370 .threads
371 .add_to_thread(&thread.event_id, pdu_id, pdu)
372 .await?;
373 },
374 | Relation::Replacement(replacement) => {
375 self.services
376 .pdu_metadata
377 .add_typed_relation(
378 shortroomid,
379 count,
380 &replacement.event_id,
381 pdu,
382 RelationType::Replacement,
383 )
384 .await;
385 },
386 | Relation::Reference(reference) => {
387 self.services
388 .pdu_metadata
389 .add_typed_relation(
390 shortroomid,
391 count,
392 &reference.event_id,
393 pdu,
394 RelationType::Reference,
395 )
396 .await;
397 },
398 | _ => {}, }
400 }
401
402 Ok(())
403}
404
405#[implement(super::Service)]
406fn append_pdu_json(&self, pdu_id: &RawPduId, pdu: &PduEvent, json: &CanonicalJsonObject) {
407 debug_assert!(matches!(pdu_id.pdu_count(), PduCount::Normal(_)), "PduCount not Normal");
408
409 let mut txn = self.db.db.txn();
410
411 txn.raw_put(&self.db.pduid_pdu, pdu_id, Json(json));
412 txn.insert_raw(&self.db.eventid_pduid, pdu.event_id.as_bytes(), pdu_id);
413 txn.del_raw(&self.db.eventid_outlierpdu, pdu.event_id.as_bytes());
414
415 let count_key = bias_count(pdu_id.count());
416 let ts = u64::from(pdu.origin_server_ts);
417 let key = (pdu.room_id(), ts, count_key);
418 txn.put_raw(&self.db.roomid_tscount_pducount, key, pdu_id.count());
419
420 txn.execute();
421}
422
423#[cfg(test)]
424mod tests {
425 use std::iter::empty;
426
427 use ruma::event_id;
428
429 use super::*;
430
431 #[test]
432 fn empty_band_is_skipped() {
433 assert!(nonempty_band(empty::<&EventId>()).is_none());
434 }
435
436 #[test]
437 fn nonempty_band_preserves_all_leaves() {
438 let leaves = [event_id!("$a:test.local"), event_id!("$b:test.local")];
439
440 let kept: Vec<&EventId> = nonempty_band(leaves.iter().copied())
441 .expect("non-empty band retained")
442 .into_iter()
443 .collect();
444
445 assert_eq!(kept, leaves);
446 }
447}