1mod data;
2mod dest;
3mod sender;
4#[cfg(test)]
5mod tests;
6
7use std::{
8 fmt::Debug,
9 hash::{DefaultHasher, Hash, Hasher},
10 io::Write,
11 iter::{once, repeat_with},
12 mem::take,
13 pin::pin,
14 sync::{Arc, Mutex as StdMutex},
15};
16
17use async_trait::async_trait;
18use futures::{FutureExt, Stream, StreamExt};
19use loole::unbounded;
20use ruma::{DeviceId, OwnedRoomId, RoomId, ServerName, UserId};
21use serde::Serialize;
22use tokio::{
23 task,
24 task::{JoinError, JoinSet},
25};
26use tuwunel_core::{
27 Result, Server, debug, debug_warn, err, error,
28 smallvec::SmallVec,
29 utils::{
30 IterStream, ReadyExt, TryReadyExt, available_parallelism, future::BoolExt,
31 math::usize_from_u64_truncated, result::LogErr,
32 },
33 warn,
34};
35
36pub use self::{
37 data::Data,
38 dest::Destination,
39 sender::{EDU_LIMIT, PDU_LIMIT},
40};
41use crate::{appservice::RegistrationInfo, rooms::timeline::RawPduId};
42
43pub struct Service {
44 pub db: Data,
45 server: Arc<Server>,
46 services: Arc<crate::services::OnceServices>,
47 channels: Vec<(loole::Sender<Msg>, loole::Receiver<Msg>)>,
48
49 flushes: StdMutex<JoinSet<()>>,
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
54struct Msg {
55 dest: Destination,
56 event: SendingEvent,
57 queue_id: Vec<u8>,
58}
59
60#[expect(clippy::module_name_repetitions)]
61#[derive(Clone, Debug, PartialEq, Eq, Hash)]
62pub enum SendingEvent {
63 Pdu(RawPduId), Edu(EduBuf), ToDevice(EduBuf), DeviceListChanged(EduBuf), BadgeRefresh,
71 Flush, }
73
74pub type EduBuf = SmallVec<[u8; EDU_BUF_CAP]>;
75pub type EduVec = SmallVec<[EduBuf; EDU_VEC_CAP]>;
76
77const EDU_BUF_CAP: usize = 128 - 16;
78const EDU_VEC_CAP: usize = 1;
79
80const TAG_TO_DEVICE: u8 = 0x01;
83const TAG_DEVICE_LIST_CHANGED: u8 = 0x02;
84const TAG_BADGE_REFRESH: u8 = 0x03;
85const TAG_PREFIX_LEN: usize = 1 + size_of::<u64>();
86
87impl SendingEvent {
88 pub(super) fn value_bytes(&self) -> &[u8] {
93 match self {
94 | Self::Edu(bytes) | Self::ToDevice(bytes) | Self::DeviceListChanged(bytes) => bytes,
95 | Self::BadgeRefresh => &[TAG_BADGE_REFRESH],
96 | Self::Pdu(_) | Self::Flush => &[],
97 }
98 }
99}
100
101#[derive(Serialize)]
106struct AsToDeviceEvent<'a> {
107 #[serde(rename = "type")]
108 kind: &'a str,
109 sender: &'a UserId,
110 content: &'a serde_json::Value,
111 to_user_id: &'a UserId,
112 to_device_id: &'a DeviceId,
113}
114
115#[async_trait]
116impl crate::Service for Service {
117 fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
118 let num_senders = num_senders(args);
119 Ok(Arc::new(Self {
120 db: Data::new(args),
121 server: args.server.clone(),
122 services: args.services.clone(),
123 channels: repeat_with(unbounded).take(num_senders).collect(),
124 flushes: JoinSet::new().into(),
125 }))
126 }
127
128 async fn worker(self: Arc<Self>) -> Result {
129 let mut senders =
130 self.channels
131 .iter()
132 .enumerate()
133 .fold(JoinSet::new(), |mut joinset, (id, _)| {
134 let self_ = self.clone();
135 let worker = self_.sender(id);
136 let worker = if self.unconstrained() {
137 task::unconstrained(worker).boxed()
138 } else {
139 worker.boxed()
140 };
141
142 let runtime = self.server.runtime();
143 let _abort = joinset.spawn_on(worker, runtime);
144 joinset
145 });
146
147 while let Some(ret) = senders.join_next_with_id().await {
148 match ret {
149 | Ok((id, _)) => {
150 debug!(?id, "sender worker finished");
151 },
152 | Err(error) => {
153 error!(id = ?error.id(), ?error, "sender worker finished");
154 },
155 }
156 }
157
158 let mut flushes = take(&mut *self.flushes.lock().expect("locked"));
159
160 flushes.abort_all();
161 while let Some(result) = flushes.join_next().await {
162 log_flush(result);
163 }
164
165 Ok(())
166 }
167
168 async fn interrupt(&self) {
169 self.flushes.lock().expect("locked").abort_all();
170
171 for (sender, _) in &self.channels {
172 if !sender.is_closed() {
173 sender.close();
174 }
175 }
176 }
177
178 fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
179
180 fn unconstrained(&self) -> bool { true }
181}
182
183impl Service {
184 #[tracing::instrument(skip(self, pdu_id, user, pushkey), level = "debug")]
185 pub fn send_pdu_push(&self, pdu_id: &RawPduId, user: &UserId, pushkey: String) -> Result {
186 let dest = Destination::Push(user.to_owned(), pushkey);
187 let event = SendingEvent::Pdu(*pdu_id);
188 let _cork = self.db.db.cork();
189
190 self.queue_and_dispatch(dest, event)
191 }
192
193 fn queue_and_dispatch(&self, dest: Destination, event: SendingEvent) -> Result {
195 let keys = self.db.queue_requests(once((&event, &dest)));
196
197 self.dispatch(Msg {
198 dest,
199 event,
200 queue_id: keys
201 .into_iter()
202 .next()
203 .expect("request queue key"),
204 })
205 }
206
207 #[tracing::instrument(level = "debug", skip(self))]
211 pub async fn refresh_push_badge(&self, user_id: &UserId) -> Result {
212 self.services
213 .pusher
214 .get_pushkeys(user_id)
215 .map(Ok)
216 .ready_try_for_each(|pushkey| {
217 let dest = Destination::Push(user_id.to_owned(), pushkey.to_owned());
218
219 self.queue_and_dispatch(dest, SendingEvent::BadgeRefresh)
220 })
221 .await
222 }
223
224 #[tracing::instrument(skip(self), level = "debug")]
225 pub fn send_pdu_appservice(&self, appservice_id: String, pdu_id: RawPduId) -> Result {
226 let dest = Destination::Appservice(appservice_id);
227 let event = SendingEvent::Pdu(pdu_id);
228 let _cork = self.db.db.cork();
229
230 self.queue_and_dispatch(dest, event)
231 }
232
233 #[tracing::instrument(skip(self, room_id, pdu_id), level = "debug")]
234 pub async fn send_pdu_room(&self, room_id: &RoomId, pdu_id: &RawPduId) -> Result {
235 let servers = self
236 .services
237 .state_cache
238 .room_servers(room_id)
239 .ready_filter(|server_name| !self.services.globals.server_is_ours(server_name));
240
241 self.send_pdu_servers(servers, pdu_id).await
242 }
243
244 #[tracing::instrument(skip(self, servers, pdu_id), level = "debug")]
245 pub async fn send_pdu_servers<'a, S>(&self, servers: S, pdu_id: &RawPduId) -> Result
246 where
247 S: Stream<Item = &'a ServerName> + Send + 'a,
248 {
249 let requests = servers
250 .map(|server| {
251 (Destination::Federation(server.into()), SendingEvent::Pdu(pdu_id.to_owned()))
252 })
253 .collect::<Vec<_>>()
254 .await;
255
256 let _cork = self.db.db.cork();
257 let keys = self
258 .db
259 .queue_requests(requests.iter().map(|(o, e)| (e, o)));
260
261 for ((dest, event), queue_id) in requests.into_iter().zip(keys) {
262 self.dispatch(Msg { dest, event, queue_id })?;
263 }
264
265 Ok(())
266 }
267
268 #[tracing::instrument(skip(self, server, serialized), level = "debug")]
269 pub fn send_edu_server(&self, server: &ServerName, serialized: EduBuf) -> Result {
270 let dest = Destination::Federation(server.to_owned());
271 let event = SendingEvent::Edu(serialized);
272 let _cork = self.db.db.cork();
273
274 self.queue_and_dispatch(dest, event)
275 }
276
277 #[tracing::instrument(skip(self, room_id, serialized), level = "debug")]
278 pub async fn send_edu_room(&self, room_id: &RoomId, serialized: EduBuf) -> Result {
279 let servers = self
280 .services
281 .state_cache
282 .room_servers(room_id)
283 .ready_filter(|server_name| !self.services.globals.server_is_ours(server_name));
284
285 self.send_edu_servers(servers, serialized).await
286 }
287
288 #[tracing::instrument(skip(self, serialized), level = "debug")]
290 pub fn send_edu_appservice(&self, appservice_id: String, serialized: EduBuf) -> Result {
291 let dest = Destination::Appservice(appservice_id);
292 let event = SendingEvent::Edu(serialized);
293 let _cork = self.db.db.cork();
294
295 self.queue_and_dispatch(dest, event)
296 }
297
298 #[expect(closure_returning_async_block)]
304 #[tracing::instrument(skip(self, serializer), level = "debug")]
305 pub async fn send_edu_room_appservices<'a, F>(
306 &self,
307 room_id: &RoomId,
308 serializer: F,
309 ) -> Result
310 where
311 F: Fn(&mut dyn Write) -> Result + Send + 'a,
312 &'a F: Send + Sync,
313 {
314 self.services
315 .appservice
316 .read()
317 .await
318 .values()
319 .stream()
320 .filter(|&appservice| async move {
321 if !appservice.registration.receive_ephemeral {
322 return false;
323 }
324
325 if appservice.rooms.is_match(room_id.as_str()) {
326 return true;
327 }
328
329 let appservice_in_room = self
330 .services
331 .state_cache
332 .appservice_in_room(room_id, appservice);
333
334 let matching_aliases = self
335 .services
336 .alias
337 .local_aliases_for_room(room_id)
338 .ready_any(|room_alias| appservice.aliases.is_match(room_alias.as_str()));
339
340 pin!(appservice_in_room)
341 .or(pin!(matching_aliases))
342 .await
343 })
344 .map(Ok)
345 .ready_try_for_each(|appservice| {
346 let mut buf = EduBuf::new();
347
348 serializer(&mut buf)?;
349 self.send_edu_appservice(appservice.registration.id.clone(), buf)
350 .log_err()
351 .ok();
352
353 Ok(())
354 })
355 .await
356 }
357
358 #[tracing::instrument(
363 skip(self, deliveries, content),
364 level = "debug",
365 fields(
366 %target_user,
367 ),
368 )]
369 pub async fn send_to_device_appservices<'a, I>(
370 &self,
371 sender: &UserId,
372 target_user: &UserId,
373 deliveries: I,
374 event_type: &str,
375 content: &serde_json::Value,
376 ) -> Result
377 where
378 I: Iterator<Item = (&'a DeviceId, u64)> + Clone + Send,
379 {
380 let registrations = self.services.appservice.read().await;
381 let _cork = self.db.db.cork();
382
383 let mut payloads: Option<EduVec> = None;
384 for info in registrations.values() {
385 if !info.is_user_match(target_user) {
386 continue;
387 }
388
389 let payloads = payloads.get_or_insert_with(|| {
390 to_device_payloads(sender, target_user, deliveries.clone(), event_type, content)
391 });
392
393 for buf in &*payloads {
394 let dest = Destination::Appservice(info.registration.id.clone());
395 let event = SendingEvent::ToDevice(buf.clone());
396
397 self.queue_and_dispatch(dest, event)?;
398 }
399 }
400
401 Ok(())
402 }
403
404 #[tracing::instrument(
409 skip(self),
410 level = "debug",
411 fields(
412 %user_id,
413 ),
414 )]
415 pub async fn send_device_list_appservices(&self, user_id: &UserId, count: u64) -> Result {
416 let registrations = self.services.appservice.read().await;
417
418 if !registrations
420 .values()
421 .any(|info| info.registration.msc3202_transaction_extensions)
422 {
423 return Ok(());
424 }
425
426 let _cork = self.db.db.cork();
427
428 let mut payload = None;
429 for info in registrations.values() {
430 if !info.registration.msc3202_transaction_extensions {
431 continue;
432 }
433
434 if !info.is_user_match(user_id) && !self.shares_device_list_room(user_id, info).await
435 {
436 continue;
437 }
438
439 let payload = payload.get_or_insert_with(|| device_list_payload(user_id, count));
440
441 let dest = Destination::Appservice(info.registration.id.clone());
442 let event = SendingEvent::DeviceListChanged(payload.clone());
443
444 self.queue_and_dispatch(dest, event)?;
445 }
446
447 Ok(())
448 }
449
450 async fn shares_device_list_room(&self, user_id: &UserId, info: &RegistrationInfo) -> bool {
454 let update_all_rooms = !self
455 .services
456 .config
457 .device_key_update_encrypted_rooms_only;
458
459 self.services
460 .state_cache
461 .rooms_joined(user_id)
462 .map(ToOwned::to_owned)
463 .any(async |room_id: OwnedRoomId| {
464 (update_all_rooms
465 || self
466 .services
467 .state_accessor
468 .is_encrypted_room(&room_id)
469 .await) && self
470 .services
471 .state_cache
472 .appservice_in_room(&room_id, info)
473 .await
474 })
475 .await
476 }
477
478 #[tracing::instrument(skip(self, servers, serialized), level = "debug")]
479 pub async fn send_edu_servers<'a, S>(&self, servers: S, serialized: EduBuf) -> Result
480 where
481 S: Stream<Item = &'a ServerName> + Send + 'a,
482 {
483 let requests = servers
484 .map(|server| {
485 (
486 Destination::Federation(server.to_owned()),
487 SendingEvent::Edu(serialized.clone()),
488 )
489 })
490 .collect::<Vec<_>>()
491 .await;
492
493 let _cork = self.db.db.cork();
494 let keys = self
495 .db
496 .queue_requests(requests.iter().map(|(o, e)| (e, o)));
497
498 for ((dest, event), queue_id) in requests.into_iter().zip(keys) {
499 self.dispatch(Msg { dest, event, queue_id })?;
500 }
501
502 Ok(())
503 }
504
505 #[tracing::instrument(skip(self, room_id), level = "debug")]
506 pub async fn flush_room(&self, room_id: &RoomId) -> Result {
507 let servers = self
508 .services
509 .state_cache
510 .room_servers(room_id)
511 .ready_filter(|server_name| !self.services.globals.server_is_ours(server_name));
512
513 self.flush_servers(servers).await
514 }
515
516 #[tracing::instrument(skip(self, servers), level = "debug")]
517 pub async fn flush_servers<'a, S>(&self, servers: S) -> Result
518 where
519 S: Stream<Item = &'a ServerName> + Send + 'a,
520 {
521 servers
522 .map(ToOwned::to_owned)
523 .map(Destination::Federation)
524 .map(Ok)
525 .ready_try_for_each(|dest| {
526 self.dispatch(Msg {
527 dest,
528 event: SendingEvent::Flush,
529 queue_id: Vec::<u8>::new(),
530 })
531 })
532 .await
533 }
534
535 #[tracing::instrument(skip(self), level = "debug")]
536 pub fn flush_appservice(&self, appservice_id: String) -> Result {
537 self.dispatch(Msg {
538 dest: Destination::Appservice(appservice_id),
539 event: SendingEvent::Flush,
540 queue_id: Vec::<u8>::new(),
541 })
542 }
543
544 #[tracing::instrument(
548 level = "debug",
549 skip(self),
550 fields(
551 %server,
552 ),
553 )]
554 pub async fn notify_peer_alive(&self, server: &ServerName) -> bool {
555 let sad = self
556 .services
557 .federation
558 .note_peer_alive(server)
559 .await;
560
561 if sad {
562 self.dispatch(Msg {
563 dest: Destination::Federation(server.to_owned()),
564 event: SendingEvent::Flush,
565 queue_id: Vec::<u8>::new(),
566 })
567 .log_err()
568 .ok();
569 }
570
571 sad
572 }
573
574 #[tracing::instrument(skip(self), level = "debug")]
579 pub async fn cleanup_events(
580 &self,
581 appservice_id: Option<&str>,
582 user_id: Option<&UserId>,
583 push_key: Option<&str>,
584 ) -> Result {
585 match (appservice_id, user_id, push_key) {
586 | (None, Some(user_id), Some(push_key)) =>
587 self.db
588 .delete_all_requests_for(&Destination::Push(
589 user_id.to_owned(),
590 push_key.to_owned(),
591 ))
592 .await,
593 | (Some(appservice_id), None, None) =>
594 self.db
595 .delete_all_requests_for(&Destination::Appservice(appservice_id.to_owned()))
596 .await,
597 | _ => debug_warn!("cleanup_events called with too many or too few arguments"),
598 }
599
600 Ok(())
601 }
602
603 fn dispatch(&self, msg: Msg) -> Result {
604 let shard = self.shard_id(&msg.dest);
605 let sender = &self
606 .channels
607 .get(shard)
608 .expect("missing sender worker channels")
609 .0;
610
611 debug_assert!(!sender.is_full(), "channel full");
612 debug_assert!(!sender.is_closed(), "channel closed");
613 sender.send(msg).map_err(|e| err!("{e}"))
614 }
615
616 pub(super) fn shard_id(&self, dest: &Destination) -> usize {
617 if self.channels.len() <= 1 {
618 return 0;
619 }
620
621 let mut hash = DefaultHasher::default();
622 dest.hash(&mut hash);
623
624 let hash: u64 = hash.finish();
625 let hash = usize_from_u64_truncated(hash);
626
627 let chans = self.channels.len().max(1);
628 hash.overflowing_rem(chans).0
629 }
630}
631
632fn to_device_payloads<'a, I>(
633 sender: &UserId,
634 target_user: &UserId,
635 deliveries: I,
636 event_type: &str,
637 content: &serde_json::Value,
638) -> EduVec
639where
640 I: Iterator<Item = (&'a DeviceId, u64)>,
641{
642 deliveries
643 .map(|(to_device_id, count)| {
644 let mut buf = EduBuf::new();
645 buf.push(TAG_TO_DEVICE);
646 buf.extend_from_slice(&count.to_be_bytes());
647
648 let event = AsToDeviceEvent {
649 kind: event_type,
650 sender,
651 content,
652 to_user_id: target_user,
653 to_device_id,
654 };
655
656 serde_json::to_writer(&mut buf, &event)
657 .expect("to-device appservice event serializes");
658
659 buf
660 })
661 .collect()
662}
663
664fn device_list_payload(user_id: &UserId, count: u64) -> EduBuf {
665 let mut buf = EduBuf::new();
666 buf.push(TAG_DEVICE_LIST_CHANGED);
667 buf.extend_from_slice(&count.to_be_bytes());
668 buf.extend_from_slice(user_id.as_bytes());
669
670 buf
671}
672
673fn num_senders(args: &crate::Args<'_>) -> usize {
674 const MIN_SENDERS: usize = 1;
675 let max_senders = args
678 .server
679 .metrics
680 .num_workers()
681 .min(available_parallelism());
682
683 args.server
686 .config
687 .sender_workers
688 .clamp(MIN_SENDERS, max_senders)
689}
690
691fn reap_flushes(flushes: &mut JoinSet<()>) {
692 while let Some(result) = flushes.try_join_next() {
693 log_flush(result);
694 }
695}
696
697fn log_flush(result: Result<(), JoinError>) {
700 if let Err(error) = result
701 && error.is_panic()
702 {
703 error!(?error, "Suppressed push flush panicked");
704 }
705}