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