1use std::{
2 collections::BTreeMap,
3 iter::once,
4 net::IpAddr,
5 sync::atomic::{AtomicBool, Ordering},
6 time::{Duration, Instant},
7};
8
9use axum::extract::State;
10use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
11use ruma::{
12 CanonicalJsonObject, CanonicalJsonValue, MilliSecondsSinceUnixEpoch, OwnedDeviceId,
13 OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, ServerName, TransactionId, UserId,
14 api::{
15 error::ErrorKind,
16 federation::transactions::{
17 edu::{
18 DeviceListUpdateContent, DirectDeviceContent, Edu, PresenceContent,
19 PresenceUpdate, ReceiptContent, ReceiptData, ReceiptMap, SigningKeyUpdateContent,
20 TypingContent,
21 },
22 send_transaction_message,
23 },
24 },
25 events::receipt::{ReceiptEvent, ReceiptEventContent, ReceiptType},
26 int,
27 serde::Raw,
28 to_device::DeviceIdOrAllDevices,
29 uint,
30};
31use tuwunel_core::{
32 Err, Error, Result, debug,
33 debug::INFO_SPAN_LEVEL,
34 debug_warn, defer, err, error,
35 itertools::Itertools,
36 result::LogErr,
37 smallvec::SmallVec,
38 trace,
39 utils::{
40 debug::str_truncated,
41 future::TryExtExt,
42 millis_since_unix_epoch,
43 stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, automatic_width},
44 },
45 warn,
46};
47use tuwunel_service::{
48 Services,
49 rooms::state_res::{is_topologically_sorted_in_place, topological_sort},
50 sending::{EDU_LIMIT, PDU_LIMIT},
51};
52
53use crate::{ClientIp, Ruma};
54
55type ResolvedMap = BTreeMap<OwnedEventId, Result>;
56type RoomsPdus = SmallVec<[RoomPdus; 1]>;
57type RoomPdus = (OwnedRoomId, TxnPdus);
58type TxnPdus = SmallVec<[(usize, Pdu); 1]>;
59type Pdu = (OwnedRoomId, OwnedEventId, CanonicalJsonObject);
60
61type Deliveries = SmallVec<[(OwnedDeviceId, u64); 1]>;
64
65#[tracing::instrument(
69 name = "txn",
70 level = INFO_SPAN_LEVEL,
71 skip_all,
72 fields(
73 txn = str_truncated(body.transaction_id.as_str(), 20),
74 origin = body.origin().as_str(),
75 %client,
76 ),
77)]
78pub(crate) async fn send_transaction_message_route(
79 State(services): State<crate::State>,
80 ClientIp(client): ClientIp,
81 body: Ruma<send_transaction_message::v1::Request>,
82) -> Result<send_transaction_message::v1::Response> {
83 if body.origin() != body.body.origin {
84 return Err!(Request(Forbidden(
85 "Not allowed to send transactions on behalf of other servers"
86 )));
87 }
88
89 if body.pdus.len() > PDU_LIMIT {
90 return Err!(Request(Forbidden(
91 "Not allowed to send more than {PDU_LIMIT} PDUs in one transaction"
92 )));
93 }
94
95 if body.edus.len() > EDU_LIMIT {
96 return Err!(Request(Forbidden(
97 "Not allowed to send more than {EDU_LIMIT} EDUs in one transaction"
98 )));
99 }
100
101 services
103 .sending
104 .notify_peer_alive(body.origin())
105 .await;
106
107 let txn_start_time = Instant::now();
108 trace!(
109 pdus = body.pdus.len(),
110 edus = body.edus.len(),
111 elapsed = ?txn_start_time.elapsed(),
112 "Starting txn",
113 );
114
115 let pdus = body
116 .pdus
117 .iter()
118 .stream()
119 .enumerate()
120 .broad_filter_map(|(i, pdu)| {
121 services
122 .event_handler
123 .parse_incoming_pdu(pdu)
124 .inspect_err(move |e| debug_warn!("Could not parse PDU[{i}]: {e}"))
125 .map_ok(move |pdu| (i, pdu))
126 .ok()
127 });
128
129 let edus = body
130 .edus
131 .iter()
132 .stream()
133 .enumerate()
134 .ready_filter_map(|(i, edu)| {
135 serde_json::from_str(edu.json().get())
136 .inspect_err(|e| debug_warn!("Could not parse EDU[{i}]: {e}"))
137 .map(|edu| (i, edu))
138 .ok()
139 });
140
141 let results = handle(
142 &services,
143 &client,
144 body.origin(),
145 &body.transaction_id,
146 txn_start_time,
147 pdus,
148 edus,
149 )
150 .await?;
151
152 debug!(
153 pdus = body.pdus.len(),
154 edus = body.edus.len(),
155 elapsed = ?txn_start_time.elapsed(),
156 "Finished txn",
157 );
158
159 for (id, result) in &results {
160 if let Err(e) = result
161 && matches!(e, Error::BadRequest(ErrorKind::NotFound, _))
162 {
163 warn!("Incoming PDU failed {id}: {e:?}");
164 }
165 }
166
167 Ok(send_transaction_message::v1::Response {
168 pdus: results
169 .into_iter()
170 .map(|(e, r)| (e, r.map_err(error::sanitized_message)))
171 .collect(),
172 })
173}
174
175async fn handle(
176 services: &Services,
177 client: &IpAddr,
178 origin: &ServerName,
179 txn_id: &TransactionId,
180 started: Instant,
181 pdus: impl Stream<Item = (usize, Pdu)> + Send,
182 edus: impl Stream<Item = (usize, Edu)> + Send,
183) -> Result<ResolvedMap> {
184 let results = handle_pdus(services, client, origin, txn_id, started, pdus).await?;
185
186 handle_edus(services, client, origin, txn_id, edus).await?;
187
188 Ok(results)
189}
190
191async fn handle_pdus(
192 services: &Services,
193 client: &IpAddr,
194 origin: &ServerName,
195 txn_id: &TransactionId,
196 started: Instant,
197 pdus: impl Stream<Item = (usize, Pdu)> + Send,
198) -> Result<ResolvedMap> {
199 pdus.collect()
200 .map(Ok)
201 .map_ok(|pdus: TxnPdus| {
202 pdus.into_iter()
203 .sorted_by(|(_, (room_a, ..)), (_, (room_b, ..))| room_a.cmp(room_b))
204 .into_grouping_map_by(|(_, (room_id, ..))| room_id.clone())
205 .collect()
206 .into_iter()
207 .try_stream()
208 })
209 .try_flatten_stream()
210 .try_collect::<RoomsPdus>()
211 .map_ok(IntoIterator::into_iter)
212 .map_ok(IterStream::try_stream)
213 .try_flatten_stream()
214 .broad_and_then(async |(room_id, pdus)| {
215 handle_room(services, client, origin, txn_id, started, room_id, pdus)
216 .map_ok(ResolvedMap::into_iter)
217 .map_ok(IterStream::try_stream)
218 .await
219 })
220 .try_flatten()
221 .try_collect()
222 .await
223}
224
225#[tracing::instrument(
226 name = "room",
227 level = INFO_SPAN_LEVEL,
228 skip_all,
229 fields(%room_id)
230)]
231async fn handle_room(
232 services: &Services,
233 _client: &IpAddr,
234 origin: &ServerName,
235 txn_id: &TransactionId,
236 txn_start_time: Instant,
237 ref room_id: OwnedRoomId,
238 pdus: TxnPdus,
239) -> Result<ResolvedMap> {
240 let pdus = sort_pdus(pdus).await;
241
242 services
243 .event_handler
244 .mutex_federation
245 .lock(room_id)
246 .then(async |_lock| {
247 pdus.into_iter()
248 .enumerate()
249 .try_stream()
250 .and_then(async |pdu| {
251 services.server.check_running().map(|()| pdu) })
253 .and_then(|(ri, (ti, (room_id, event_id, value)))| {
254 let meta = (origin, txn_id, txn_start_time, ti);
255 let pdu = (ri, (room_id, event_id, value));
256 handle_pdu(services, meta, pdu).map(Ok)
257 })
258 .try_collect()
259 .await
260 })
261 .await
262}
263
264async fn sort_pdus(mut pdus: TxnPdus) -> TxnPdus {
269 if already_sorted(&pdus) {
270 return pdus;
271 }
272
273 let event_ids: BTreeMap<&str, &OwnedEventId> = pdus
274 .iter()
275 .map(|(_, (_, event_id, _))| (event_id.as_str(), event_id))
276 .collect();
277
278 let graph = pdus
279 .iter()
280 .map(|(_, (_, event_id, value))| {
281 let references = prev_event_ids(value)
282 .filter_map(|prev| event_ids.get(prev).copied())
283 .map(ToOwned::to_owned)
284 .collect();
285
286 (event_id.clone(), references)
287 })
288 .collect();
289
290 let query = async |_event_id: OwnedEventId| {
292 Ok((int!(0).into(), MilliSecondsSinceUnixEpoch(uint!(0))))
293 };
294
295 let Ok(order) = topological_sort(graph, &query).await else {
296 return pdus;
297 };
298
299 let position: BTreeMap<&str, usize> = order
300 .iter()
301 .enumerate()
302 .map(|(i, event_id)| (event_id.as_str(), i))
303 .collect();
304
305 pdus.sort_by_key(|(_, (_, event_id, _))| position.get(event_id.as_str()).copied());
306 pdus
307}
308
309fn already_sorted(pdus: &[(usize, Pdu)]) -> bool {
312 is_topologically_sorted_in_place(
313 pdus,
314 |(_, (_, id, _))| id.as_str(),
315 |(_, (_, _, value))| prev_event_ids(value),
316 )
317}
318
319fn prev_event_ids(value: &CanonicalJsonObject) -> impl Iterator<Item = &str> + '_ {
321 value
322 .get("prev_events")
323 .and_then(CanonicalJsonValue::as_array)
324 .into_iter()
325 .flatten()
326 .filter_map(CanonicalJsonValue::as_str)
327}
328
329#[tracing::instrument(
330 name = "pdu",
331 level = INFO_SPAN_LEVEL,
332 skip_all,
333 fields(%event_id, %ti, %ri)
334)]
335async fn handle_pdu(
336 services: &Services,
337 (origin, txn_id, txn_start_time, ti): (&ServerName, &TransactionId, Instant, usize),
338 (ri, (ref room_id, event_id, value)): (usize, Pdu),
339) -> (OwnedEventId, Result) {
340 let pdu_start_time = Instant::now();
341 let completed: AtomicBool = Default::default();
342 defer! {{
343 if completed.load(Ordering::Acquire) {
344 return;
345 }
346
347 if pdu_start_time.elapsed() >= Duration::from_secs(services.config.client_request_timeout) {
348 error!(
349 %origin, %txn_id, %room_id, %event_id, %ri, %ti,
350 elapsed = ?pdu_start_time.elapsed(),
351 "Incoming transaction processing timed out.",
352 );
353 } else {
354 debug_warn!(
355 %origin, %txn_id, %room_id, %event_id, %ri, %ti,
356 elapsed = ?pdu_start_time.elapsed(),
357 "Incoming transaction processing interrupted.",
358 );
359 }
360 }}
361
362 let result = services
363 .event_handler
364 .handle_incoming_pdu(origin, room_id, &event_id, value, true)
365 .map_ok(|_| ())
366 .await;
367
368 completed.store(true, Ordering::Release);
369 debug!(
370 %event_id, ri, ti,
371 pdu_elapsed = ?pdu_start_time.elapsed(),
372 txn_elapsed = ?txn_start_time.elapsed(),
373 "Finished PDU",
374 );
375
376 (event_id.clone(), result)
377}
378
379#[tracing::instrument(name = "edus", level = "debug", skip_all)]
380async fn handle_edus(
381 services: &Services,
382 client: &IpAddr,
383 origin: &ServerName,
384 txn_id: &TransactionId,
385 edus: impl Stream<Item = (usize, Edu)> + Send,
386) -> Result {
387 edus.for_each_concurrent(automatic_width(), |(i, edu)| {
388 handle_edu(services, client, origin, txn_id, i, edu)
389 })
390 .await;
391
392 Ok(())
393}
394
395#[tracing::instrument(
396 name = "edu",
397 level = "debug",
398 skip_all,
399 fields(%i),
400)]
401async fn handle_edu(
402 services: &Services,
403 client: &IpAddr,
404 origin: &ServerName,
405 _txn_id: &TransactionId,
406 i: usize,
407 edu: Edu,
408) {
409 match edu {
410 | Edu::Presence(presence) if services.server.config.allow_incoming_presence =>
411 handle_edu_presence(services, client, origin, presence).await,
412
413 | Edu::Receipt(receipt)
414 if services
415 .server
416 .config
417 .allow_incoming_read_receipts =>
418 handle_edu_receipt(services, client, origin, receipt).await,
419
420 | Edu::Typing(typing) if services.server.config.allow_incoming_typing =>
421 handle_edu_typing(services, client, origin, typing).await,
422
423 | Edu::DeviceListUpdate(content) =>
424 handle_edu_device_list_update(services, client, origin, content).await,
425
426 | Edu::DirectToDevice(content) =>
427 handle_edu_direct_to_device(services, client, origin, content).await,
428
429 | Edu::SigningKeyUpdate(content) =>
430 handle_edu_signing_key_update(services, client, origin, content).await,
431
432 | Edu::_Custom(ref _custom) => debug_warn!(?i, ?edu, "received custom/unknown EDU"),
433
434 | _ => trace!(?i, ?edu, "skipped"),
435 }
436}
437
438async fn handle_edu_presence(
439 services: &Services,
440 _client: &IpAddr,
441 origin: &ServerName,
442 presence: PresenceContent,
443) {
444 presence
445 .push
446 .into_iter()
447 .stream()
448 .for_each_concurrent(automatic_width(), |update| {
449 handle_edu_presence_update(services, origin, update)
450 })
451 .await;
452}
453
454async fn handle_edu_presence_update(
455 services: &Services,
456 origin: &ServerName,
457 update: PresenceUpdate,
458) {
459 if update.user_id.server_name() != origin {
460 debug_warn!(
461 %update.user_id, %origin,
462 "received presence EDU for user not belonging to origin"
463 );
464 return;
465 }
466
467 services
468 .presence
469 .set_presence_from_federation(
470 &update.user_id,
471 &update.presence,
472 update.currently_active,
473 update.last_active_ago,
474 update.status_msg.clone(),
475 )
476 .await
477 .log_err()
478 .ok();
479}
480
481async fn handle_edu_receipt(
482 services: &Services,
483 _client: &IpAddr,
484 origin: &ServerName,
485 receipt: ReceiptContent,
486) {
487 receipt
488 .receipts
489 .into_iter()
490 .stream()
491 .for_each_concurrent(automatic_width(), |(room_id, room_updates)| {
492 handle_edu_receipt_room(services, origin, room_id, room_updates)
493 })
494 .await;
495}
496
497async fn handle_edu_receipt_room(
498 services: &Services,
499 origin: &ServerName,
500 room_id: OwnedRoomId,
501 room_updates: ReceiptMap,
502) {
503 if services
504 .event_handler
505 .acl_check(origin, &room_id)
506 .await
507 .is_err()
508 {
509 debug_warn!(
510 %origin, %room_id,
511 "received read receipt EDU from ACL'd server"
512 );
513 return;
514 }
515
516 let room_id = &room_id;
517 room_updates
518 .read
519 .into_iter()
520 .stream()
521 .for_each_concurrent(automatic_width(), async |(user_id, user_updates)| {
522 handle_edu_receipt_room_user(services, origin, room_id, &user_id, user_updates).await;
523 })
524 .await;
525}
526
527async fn handle_edu_receipt_room_user(
528 services: &Services,
529 origin: &ServerName,
530 room_id: &RoomId,
531 user_id: &UserId,
532 user_updates: ReceiptData,
533) {
534 if user_id.server_name() != origin {
535 debug_warn!(
536 %user_id, %origin,
537 "received read receipt EDU for user not belonging to origin"
538 );
539 return;
540 }
541
542 if !services
543 .state_cache
544 .server_in_room(origin, room_id)
545 .await
546 {
547 debug_warn!(
548 %user_id, %room_id, %origin,
549 "received read receipt EDU from server who does not have a member in the room",
550 );
551 return;
552 }
553
554 let data = &user_updates.data;
555 user_updates
556 .event_ids
557 .into_iter()
558 .stream()
559 .for_each_concurrent(automatic_width(), async |event_id| {
560 let user_data = [(user_id.to_owned(), data.clone())];
561 let receipts = [(ReceiptType::Read, BTreeMap::from(user_data))];
562 let content = [(event_id.clone(), BTreeMap::from(receipts))];
563 services
564 .read_receipt
565 .readreceipt_update(user_id, room_id, &ReceiptEvent {
566 content: ReceiptEventContent(content.into()),
567 room_id: room_id.to_owned(),
568 })
569 .await;
570 })
571 .await;
572}
573
574async fn handle_edu_typing(
575 services: &Services,
576 _client: &IpAddr,
577 origin: &ServerName,
578 typing: TypingContent,
579) {
580 if typing.user_id.server_name() != origin {
581 debug_warn!(
582 %typing.user_id, %origin,
583 "received typing EDU for user not belonging to origin"
584 );
585 return;
586 }
587
588 if services
589 .event_handler
590 .acl_check(typing.user_id.server_name(), &typing.room_id)
591 .await
592 .is_err()
593 {
594 debug_warn!(
595 %typing.user_id, %typing.room_id, %origin,
596 "received typing EDU for ACL'd user's server"
597 );
598 return;
599 }
600
601 if !services
602 .state_cache
603 .is_joined(&typing.user_id, &typing.room_id)
604 .await
605 {
606 debug_warn!(
607 %typing.user_id, %typing.room_id, %origin,
608 "received typing EDU for user not in room"
609 );
610 return;
611 }
612
613 if typing.typing {
614 let secs = services.server.config.typing_federation_timeout_s;
615 let timeout = millis_since_unix_epoch().saturating_add(secs.saturating_mul(1000));
616
617 services
618 .typing
619 .typing_add(&typing.user_id, &typing.room_id, timeout)
620 .await
621 .log_err()
622 .ok();
623 } else {
624 services
625 .typing
626 .typing_remove(&typing.user_id, &typing.room_id)
627 .await
628 .log_err()
629 .ok();
630 }
631}
632
633async fn handle_edu_device_list_update(
634 services: &Services,
635 _client: &IpAddr,
636 origin: &ServerName,
637 content: DeviceListUpdateContent,
638) {
639 let DeviceListUpdateContent { user_id, .. } = content;
640
641 if user_id.server_name() != origin {
642 debug_warn!(
643 %user_id, %origin,
644 "received device list update EDU for user not belonging to origin"
645 );
646 return;
647 }
648
649 services
650 .users
651 .mark_device_key_update(&user_id)
652 .await;
653}
654
655async fn handle_edu_direct_to_device(
656 services: &Services,
657 _client: &IpAddr,
658 origin: &ServerName,
659 content: DirectDeviceContent,
660) {
661 let DirectDeviceContent {
662 ref sender,
663 ref ev_type,
664 ref message_id,
665 messages,
666 } = content;
667
668 if sender.server_name() != origin {
669 debug_warn!(
670 %sender, %origin,
671 "received direct to device EDU for user not belonging to origin"
672 );
673 return;
674 }
675
676 if services
678 .transaction_ids
679 .existing_txnid(sender, None, message_id)
680 .await
681 .is_ok()
682 {
683 return;
684 }
685
686 let ev_type = ev_type.to_string();
687
688 messages
689 .into_iter()
690 .stream()
691 .broad_filter_map(async |(target_user_id, map)| {
692 to_device_deliverable(services, &target_user_id)
693 .await
694 .then_some((target_user_id, map))
695 })
696 .for_each_concurrent(automatic_width(), |(target_user_id, map)| {
697 handle_edu_direct_to_device_user(services, target_user_id, sender, &ev_type, map)
698 })
699 .await;
700
701 services
703 .transaction_ids
704 .add_txnid(sender, None, message_id, &[]);
705}
706
707async fn to_device_deliverable(services: &Services, user_id: &UserId) -> bool {
711 services.globals.user_is_local(user_id)
712 && (services.users.is_active(user_id).await
713 || services
714 .appservice
715 .is_interested_in_user(user_id)
716 .await)
717}
718
719async fn handle_edu_direct_to_device_user<Event: Send + Sync>(
720 services: &Services,
721 target_user_id: OwnedUserId,
722 sender: &UserId,
723 ev_type: &str,
724 map: BTreeMap<DeviceIdOrAllDevices, Raw<Event>>,
725) {
726 map.into_iter()
727 .stream()
728 .ready_filter_map(|(tid, raw)| {
729 raw.deserialize_as()
730 .map_err(|e| {
731 err!(Request(InvalidParam(error!("To-Device event is invalid: {e}"))))
732 })
733 .ok()
734 .map(|ev| (tid, ev))
735 })
736 .for_each_concurrent(automatic_width(), |(tid, ev)| {
737 handle_edu_direct_to_device_event(services, &target_user_id, sender, tid, ev_type, ev)
738 })
739 .await;
740}
741
742async fn handle_edu_direct_to_device_event(
743 services: &Services,
744 target_user_id: &UserId,
745 sender: &UserId,
746 target_device_id_maybe: DeviceIdOrAllDevices,
747 ev_type: &str,
748 event: serde_json::Value,
749) {
750 match target_device_id_maybe {
751 | DeviceIdOrAllDevices::DeviceId(ref target_device_id) => {
752 let count = services.users.add_to_device_event(
753 sender,
754 target_user_id,
755 target_device_id,
756 ev_type,
757 &event,
758 );
759
760 services
761 .sending
762 .send_to_device_appservices(
763 sender,
764 target_user_id,
765 once((&**target_device_id, count)),
766 ev_type,
767 &event,
768 )
769 .await
770 .log_err()
771 .ok();
772 },
773
774 | DeviceIdOrAllDevices::AllDevices => {
775 let interested = services
776 .appservice
777 .is_interested_in_user(target_user_id)
778 .await;
779
780 let deliveries: Deliveries = services
781 .users
782 .all_device_ids(target_user_id)
783 .map(|target_device_id| {
784 let count = services.users.add_to_device_event(
785 sender,
786 target_user_id,
787 target_device_id,
788 ev_type,
789 &event,
790 );
791
792 (target_device_id, count)
793 })
794 .ready_filter_map(|(target_device_id, count)| {
795 interested.then(|| (target_device_id.to_owned(), count))
796 })
797 .collect()
798 .await;
799
800 if !deliveries.is_empty() {
801 services
802 .sending
803 .send_to_device_appservices(
804 sender,
805 target_user_id,
806 deliveries
807 .iter()
808 .map(|(device_id, count)| (&**device_id, *count)),
809 ev_type,
810 &event,
811 )
812 .await
813 .log_err()
814 .ok();
815 }
816 },
817 }
818}
819
820async fn handle_edu_signing_key_update(
821 services: &Services,
822 _client: &IpAddr,
823 origin: &ServerName,
824 content: SigningKeyUpdateContent,
825) {
826 let SigningKeyUpdateContent { user_id, master_key, self_signing_key } = content;
827
828 if user_id.server_name() != origin {
829 debug_warn!(
830 %user_id, %origin,
831 "received signing key update EDU from server that does not belong to user's server"
832 );
833 return;
834 }
835
836 services
837 .users
838 .add_cross_signing_keys(&user_id, &master_key, &self_signing_key, &None, true)
839 .await
840 .log_err()
841 .ok();
842}
843
844#[cfg(test)]
845mod tests {
846 use ruma::{CanonicalJsonObject, OwnedEventId, event_id, room_id};
847 use serde_json::json;
848
849 use super::{Pdu, TxnPdus, already_sorted, prev_event_ids, sort_pdus};
850
851 fn pdu(index: usize, id: &OwnedEventId, prev: &[&OwnedEventId]) -> (usize, Pdu) {
852 let prev_events: Vec<&str> = prev.iter().map(|e| e.as_str()).collect();
853 let value: CanonicalJsonObject =
854 serde_json::from_value(json!({ "prev_events": prev_events }))
855 .expect("valid canonical json");
856
857 (index, (room_id!("!r:example.com").to_owned(), id.clone(), value))
858 }
859
860 fn ids() -> (OwnedEventId, OwnedEventId, OwnedEventId) {
861 (
862 event_id!("$a:example.com").to_owned(),
863 event_id!("$b:example.com").to_owned(),
864 event_id!("$c:example.com").to_owned(),
865 )
866 }
867
868 fn order(pdus: &[(usize, Pdu)]) -> Vec<&str> {
869 pdus.iter()
870 .map(|(_, (_, id, _))| id.as_str())
871 .collect()
872 }
873
874 #[test]
875 fn sorted_when_parents_lead() {
876 let (a, b, c) = ids();
877 let pdus = [pdu(0, &a, &[]), pdu(1, &b, &[&a]), pdu(2, &c, &[&b])];
878
879 assert!(already_sorted(&pdus));
880 }
881
882 #[test]
883 fn unsorted_when_child_leads() {
884 let (a, b, _c) = ids();
885 let pdus = [pdu(0, &b, &[&a]), pdu(1, &a, &[])];
886
887 assert!(!already_sorted(&pdus));
888 }
889
890 #[test]
891 fn sorted_ignores_out_of_batch_references() {
892 let (a, b, c) = ids();
893 let pdus = [pdu(0, &b, &[&c]), pdu(1, &a, &[&c])];
894
895 assert!(already_sorted(&pdus));
896 }
897
898 #[tokio::test]
899 async fn sort_orders_parents_before_children() {
900 let (a, b, c) = ids();
901 let pdus: TxnPdus = [pdu(0, &c, &[&b]), pdu(1, &b, &[&a]), pdu(2, &a, &[])]
902 .into_iter()
903 .collect();
904
905 let sorted = sort_pdus(pdus).await;
906
907 assert_eq!(order(&sorted), ["$a:example.com", "$b:example.com", "$c:example.com"]);
908 }
909
910 #[tokio::test]
911 async fn sort_is_noop_when_already_ordered() {
912 let (a, b, c) = ids();
913 let pdus: TxnPdus = [pdu(0, &a, &[]), pdu(1, &b, &[&a]), pdu(2, &c, &[&b])]
914 .into_iter()
915 .collect();
916
917 let sorted = sort_pdus(pdus.clone()).await;
918
919 assert_eq!(order(&sorted), order(&pdus));
920 }
921
922 #[tokio::test]
923 async fn sort_preserves_duplicates() {
924 let (a, b, _c) = ids();
925 let pdus: TxnPdus = [pdu(0, &b, &[&a]), pdu(1, &a, &[]), pdu(2, &b, &[&a])]
926 .into_iter()
927 .collect();
928
929 let sorted = sort_pdus(pdus).await;
930
931 assert_eq!(sorted.len(), 3);
932 }
933
934 #[tokio::test]
935 async fn sort_preserves_a_cycle() {
936 let (a, b, _c) = ids();
937 let pdus: TxnPdus = [pdu(0, &a, &[&b]), pdu(1, &b, &[&a])]
938 .into_iter()
939 .collect();
940
941 let sorted = sort_pdus(pdus).await;
942
943 assert_eq!(sorted.len(), 2);
944 }
945
946 #[test]
947 fn prev_event_ids_reads_the_array() {
948 let (a, b, _c) = ids();
949 let (_, (_, _, value)) = pdu(0, &a, &[&b]);
950
951 let prev: Vec<&str> = prev_event_ids(&value).collect();
952
953 assert_eq!(prev, ["$b:example.com"]);
954 }
955
956 #[test]
957 fn prev_event_ids_empty_when_absent() {
958 let value = CanonicalJsonObject::new();
959
960 assert_eq!(prev_event_ids(&value).count(), 0);
961 }
962}