1use crate::crypto::account::OlmAccountState;
107use crate::crypto::cross_signing::CrossSigningState;
108use crate::crypto::device_tracker::{DeviceTracker, StoredDevice};
109use crate::crypto::group_sessions::{GroupDecryptError, GroupSessionManager, RoomEventPlaintext};
110use crate::crypto::olm_sessions::{DecryptedToDevice, OlmDecryptError, OlmSessionManager};
111use crate::error::MessengerError;
112use crate::ids::{DeviceId, EventId, RequestId, RoomId, TxnId, UserId};
113use crate::outgoing_queue::{Jitter, Lane, OutgoingQueue, PendingRequest, ResponseOutcome};
114use crate::persist::{FlushBatch, SealedRecord};
115use crate::room::state::RoomState;
116use crate::room::timeline::{
117 interpret_content, ForwardRefusal, ItemContent, SendState, Timeline, KEY_FORWARDED, KEY_FORWARDED_FROM,
118};
119use crate::room::RoomKind;
120use crate::store::sealed::SealedRecordCodec;
121use crate::store::{CryptoStore, RecordCodec, StateStore, Store};
122use crate::wire::events::{
123 DirectContent, ForwardedRoomKeyContent, InReplyTo, MegolmEncryptedContent, Membership, RawEvent, ReceiptContent,
124 RelatesTo, RoomEncryptedContent, RoomKeyContent, RoomKeyRequestAction, RoomKeyRequestBody, RoomKeyRequestContent,
125 RoomKeyWithheldContent, StrippedStateEvent, TagContent, TagInfo, TextLikeMessageContent, ToDeviceEvent,
126 TypingContent, Unsigned,
127};
128use crate::wire::sync::{parse_sync_response, InvitedRoom, JoinedRoom, LeftRoom};
129use crate::wire::{percent_decode_segment, HttpResponseDescriptor, OutgoingRequest, OutgoingRequestKind};
130use serde::{Deserialize, Serialize};
131use std::collections::{BTreeMap, BTreeSet, VecDeque};
132use zeroize::Zeroizing;
133
134pub const SYNC_TIMEOUT_MS: u64 = 30_000;
137const LOAD_OLDER_PAGE_SIZE: u32 = 50;
140const TYPING_TIMEOUT_MS: u64 = 30_000;
143const UNKNOWN_SENDER_RETRY_CAPACITY: usize = 256;
148const TYPING_DEBOUNCE_MS: i64 = 4_000;
152const ROOM_KEY_SHARE_EVENT_TYPE: &str = "m.room.encrypted";
160
161#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
165pub struct Counters {
166 pub next_request_id: u64,
168 pub next_txn_id: u64,
173}
174
175fn fresh_txn_seed() -> u64 {
189 std::time::SystemTime::now()
190 .duration_since(std::time::UNIX_EPOCH)
191 .map(|d| d.as_micros() as u64)
192 .unwrap_or(0)
193}
194
195fn restore_counter(persisted_next: u64, max_used_seed: Option<u64>) -> u64 {
196 match max_used_seed {
197 Some(seed) => persisted_next.max(seed.saturating_add(1)),
198 None => persisted_next,
199 }
200}
201
202#[derive(Clone, Debug, PartialEq, Eq)]
206pub struct CoreConfig {
207 pub user_id: UserId,
209 pub device_id: DeviceId,
211 pub server_name: String,
219}
220
221#[derive(Default)]
230pub struct CoreSecrets {
231 pub store_seal_key: Option<Zeroizing<[u8; 32]>>,
239 pub backup_key: Option<Zeroizing<[u8; 32]>>,
243}
244
245#[derive(Clone, Copy, Debug, PartialEq, Eq)]
248pub enum MessageKind {
249 Text,
251 Notice,
253 Emote,
255}
256
257impl MessageKind {
258 fn msgtype(self) -> &'static str {
259 match self {
260 MessageKind::Text => "m.text",
261 MessageKind::Notice => "m.notice",
262 MessageKind::Emote => "m.emote",
263 }
264 }
265}
266
267#[derive(Clone, Debug, PartialEq, Eq)]
278pub struct OutgoingMessage {
279 pub kind: MessageKind,
281 pub body: String,
284 pub reply_to: Option<EventId>,
286 pub edit_of: Option<EventId>,
289}
290
291#[derive(Clone, Debug, PartialEq, Eq)]
297pub enum CreateRoomKind {
298 Dm {
302 peer: UserId,
304 },
305 Group {
307 name: String,
309 invite: Vec<UserId>,
311 members_can_invite: bool,
315 },
316 Channel {
320 name: String,
322 topic: Option<String>,
324 },
325}
326
327#[derive(Clone, Debug, PartialEq)]
333pub enum MessengerCommand {
334 SendMessage {
340 room_id: RoomId,
342 message: OutgoingMessage,
344 txn_id: Option<TxnId>,
347 },
348 Forward {
374 from_room: RoomId,
376 event_id: EventId,
378 to_room: RoomId,
380 txn_id: Option<TxnId>,
382 },
383 React {
387 room_id: RoomId,
389 target: EventId,
391 key: String,
393 },
394 Redact {
396 room_id: RoomId,
398 target: EventId,
400 reason: Option<String>,
402 },
403 RetrySend {
409 room_id: RoomId,
411 txn_id: TxnId,
413 },
414 CreateRoom {
416 kind: CreateRoomKind,
418 },
419 JoinRoom {
422 room_id: RoomId,
424 },
425 LeaveRoom {
427 room_id: RoomId,
429 },
430 Invite {
432 room_id: RoomId,
434 user_id: UserId,
436 },
437 Kick {
442 room_id: RoomId,
444 user_id: UserId,
446 reason: Option<String>,
448 },
449 SetPowerLevel {
452 room_id: RoomId,
454 user_id: UserId,
456 level: i64,
458 },
459 SetEventsDefault {
463 room_id: RoomId,
465 level: i64,
467 },
468 SetTag {
470 room_id: RoomId,
472 tag: String,
474 order: Option<f64>,
476 },
477 RemoveTag {
479 room_id: RoomId,
481 tag: String,
483 },
484 MarkRead {
487 room_id: RoomId,
489 event_id: EventId,
491 },
492 SetTyping {
495 room_id: RoomId,
497 typing: bool,
499 },
500 RetryDecryption {
505 room_id: RoomId,
507 event_id: EventId,
509 },
510 LoadOlder {
515 room_id: RoomId,
517 },
518 SetAccountData {
524 event_type: String,
526 content: serde_json::Value,
528 },
529 SetRoomAccountData {
538 room_id: RoomId,
540 event_type: String,
542 content: serde_json::Value,
544 },
545 SearchPublicRooms {
550 term: String,
552 },
553 SearchUsers {
558 term: String,
560 },
561}
562
563#[derive(Clone, Debug, PartialEq, Eq)]
568pub enum MessengerEvent {
569 RoomsChanged,
572 TimelineChanged {
574 room_id: RoomId,
576 },
577 TypingChanged {
579 room_id: RoomId,
581 },
582 ReceiptsChanged {
584 room_id: RoomId,
586 },
587 UnreadChanged {
589 room_id: RoomId,
591 },
592 DeviceKeyChanged {
596 user_id: UserId,
598 device_id: DeviceId,
600 },
601 PublicRoomsChanged,
605 UserSearchChanged,
609}
610
611#[derive(Clone)]
616struct PendingDecryptItem {
617 session_id: String,
618 sender: UserId,
619 origin_server_ts: i64,
620 content: MegolmEncryptedContent,
621}
622
623#[derive(Deserialize)]
629struct RoomMessagesResponseBody {
630 #[serde(default)]
631 chunk: Vec<RawEvent>,
632 #[serde(default)]
633 end: Option<String>,
634}
635
636#[derive(Deserialize)]
638struct SendResponseBody {
639 event_id: EventId,
640}
641
642#[derive(Deserialize)]
644struct CreateRoomResponseBody {
645 room_id: RoomId,
646}
647
648#[derive(Clone, Debug, PartialEq)]
651pub struct PublicRoomsResultEntry {
652 pub room_id: RoomId,
654 pub name: Option<String>,
656 pub topic: Option<String>,
658 pub num_joined_members: u64,
660}
661
662#[derive(Deserialize)]
664struct PublicRoomsResponseBody {
665 #[serde(default)]
666 chunk: Vec<PublicRoomsChunkEntry>,
667}
668
669#[derive(Deserialize)]
670struct PublicRoomsChunkEntry {
671 room_id: RoomId,
672 #[serde(default)]
673 name: Option<String>,
674 #[serde(default)]
675 topic: Option<String>,
676 #[serde(default)]
677 num_joined_members: u64,
678}
679
680#[derive(Clone, Debug, PartialEq)]
683pub struct UserDirectoryResultEntry {
684 pub user_id: UserId,
686 pub display_name: Option<String>,
688}
689
690#[derive(Deserialize)]
693struct UserDirectorySearchResponseBody {
694 #[serde(default)]
695 results: Vec<UserDirectorySearchResultEntry>,
696}
697
698#[derive(Deserialize)]
699struct UserDirectorySearchResultEntry {
700 user_id: UserId,
701 #[serde(default)]
702 display_name: Option<String>,
703}
704
705#[derive(Clone)]
710enum SendPayload {
711 Message(OutgoingMessage),
714 Event {
718 event_type: String,
720 content: serde_json::Value,
722 },
723 Reaction {
725 target: EventId,
727 key: String,
729 },
730 Redaction {
732 target: EventId,
734 reason: Option<String>,
736 },
737}
738
739struct RoomEventWire {
743 event_type: String,
744 content: serde_json::Value,
745 relates_to: Option<RelatesTo>,
748}
749
750impl SendPayload {
751 fn room_event(&self) -> Option<RoomEventWire> {
754 match self {
755 SendPayload::Message(message) => Some(RoomEventWire {
756 event_type: "m.room.message".to_string(),
757 content: message_wire_content(message),
758 relates_to: message_relates_to(message),
759 }),
760 SendPayload::Event { event_type, content } => {
761 Some(RoomEventWire { event_type: event_type.clone(), content: content.clone(), relates_to: None })
762 }
763 SendPayload::Reaction { .. } | SendPayload::Redaction { .. } => None,
764 }
765 }
766
767 fn is_room_event(&self) -> bool {
768 matches!(self, SendPayload::Message(_) | SendPayload::Event { .. })
769 }
770}
771
772#[derive(Clone)]
778enum WireSend {
779 Event {
781 event_type: String,
784 content: serde_json::Value,
786 },
787 Redact {
789 target: EventId,
791 reason: Option<String>,
793 },
794}
795
796#[derive(Clone)]
801enum SendPhase {
802 Queued,
806 AwaitingKeysQuery,
812 AwaitingKeysClaim {
815 devices: Vec<StoredDevice>,
819 },
820 AwaitingKeyShare {
825 pending: BTreeSet<RequestId>,
827 },
828 AwaitingSend,
830 Failed {
835 errcode: String,
837 },
838}
839
840#[derive(Clone)]
846struct PendingSend {
847 room_id: RoomId,
850 payload: SendPayload,
852 phase: SendPhase,
854 wire: Option<WireSend>,
856}
857
858type AccountDataKey = (Option<RoomId>, String);
861
862struct AccountDataGuard {
874 pending: u32,
876 server_value: Option<serde_json::Value>,
880}
881
882fn account_data_key_from_path(path: &str) -> Option<AccountDataKey> {
885 let segments: Vec<&str> = path.split('/').collect();
886 let n = segments.len();
887 if n < 4 || segments[n - 2] != "account_data" {
888 return None;
889 }
890 let event_type = percent_decode_segment(segments[n - 1])?;
891 if segments[n - 4] == "user" {
892 return Some((None, event_type));
893 }
894 if n >= 6 && segments[n - 4] == "rooms" && segments[n - 6] == "user" {
895 let room_id = RoomId::parse(percent_decode_segment(segments[n - 3])?).ok()?;
896 return Some((Some(room_id), event_type));
897 }
898 None
899}
900
901pub struct MessengerCore<C: RecordCodec> {
903 config: CoreConfig,
904 store: Store<C>,
905 account: OlmAccountState,
906 xsign: Option<CrossSigningState>,
908 xsign_inflight: bool,
909 xsign_attempts: u8,
910 outgoing: OutgoingQueue,
911 counters: Counters,
912 jitter: Box<dyn Jitter>,
913
914 rooms: BTreeMap<RoomId, RoomState>,
915 timelines: BTreeMap<RoomId, Timeline>,
916 direct_account_data: Option<DirectContent>,
917 global_account_data: BTreeMap<String, serde_json::Value>,
918 room_account_data: BTreeMap<RoomId, BTreeMap<String, serde_json::Value>>,
919 typing: BTreeMap<RoomId, Vec<UserId>>,
920 receipts: BTreeMap<RoomId, ReceiptContent>,
921
922 public_rooms_result: Vec<PublicRoomsResultEntry>,
925 latest_public_rooms_request: Option<RequestId>,
930 user_search_result: Vec<UserDirectoryResultEntry>,
933 latest_user_search_request: Option<RequestId>,
936
937 account_data_guard: BTreeMap<AccountDataKey, AccountDataGuard>,
941 account_data_write_keys: BTreeMap<RequestId, AccountDataKey>,
943
944 pending_decrypt: BTreeMap<RoomId, BTreeMap<EventId, PendingDecryptItem>>,
945 key_requests_sent: BTreeSet<(RoomId, String)>,
949 history_claims: BTreeMap<RequestId, Vec<StoredDevice>>,
952 history_claim_tried: BTreeSet<(UserId, DeviceId)>,
954 withheld_sessions: BTreeSet<(RoomId, String)>,
956 key_requests_answered: BTreeSet<(UserId, DeviceId, String)>,
958 unknown_sender_retry: VecDeque<ToDeviceEvent>,
959
960 typing_debounce: BTreeMap<RoomId, i64>,
965 pending_sends: BTreeMap<RoomId, VecDeque<TxnId>>,
971 send_state: BTreeMap<TxnId, PendingSend>,
976 send_request_owner: BTreeMap<RequestId, TxnId>,
980 pending_create_room: BTreeMap<RequestId, CreateRoomKind>,
984
985 pending_kinds: BTreeMap<RequestId, OutgoingRequestKind>,
991 pending_room_messages: BTreeMap<RequestId, RoomId>,
994 sync_request_id: Option<RequestId>,
997 sync_caught_up: bool,
1002 pending_flush_batches: VecDeque<FlushBatch>,
1009
1010 events: Vec<MessengerEvent>,
1011 change_counter: u64,
1012 ingest_error: Option<String>,
1017}
1018
1019impl<C: RecordCodec> MessengerCore<C> {
1020 pub fn sign_scoped(&self, domain: &str, payload: &[u8]) -> Result<(String, String), MessengerError> {
1023 self.account.sign_scoped(domain, payload)
1024 }
1025
1026 pub fn open(
1039 records: impl IntoIterator<Item = SealedRecord>,
1040 codec: C,
1041 config: CoreConfig,
1042 secrets: CoreSecrets,
1043 _now_ms: i64,
1044 jitter: Box<dyn Jitter>,
1045 ) -> Result<Self, MessengerError> {
1046 let _ = secrets;
1050 let mut store = Store::load(records, codec, config.device_id.clone())?;
1051 let mut outgoing = OutgoingQueue::load(&store)?;
1052 let account = OlmAccountState::load_or_create(&mut store)?;
1053
1054 let mut rooms = BTreeMap::new();
1055 let mut timelines = BTreeMap::new();
1056 for room_id in store.room_ids()? {
1057 if let Some(bytes) = store.room_state(&room_id)? {
1058 let state: RoomState = serde_json::from_slice(bytes)
1059 .map_err(|source| MessengerError::Crypto(format!("decode room state for {room_id}: {source}")))?;
1060 rooms.insert(room_id.clone(), state);
1061 timelines.insert(room_id, Timeline::new());
1062 }
1063 }
1064
1065 let pending_requests = store.pending_requests()?;
1066 let max_pending_request_seed = pending_requests.iter().filter_map(|req| req.request.id.as_seed()).max();
1067 outgoing.discard_kind(&mut store, OutgoingRequestKind::Sync)?;
1074 let persisted_counters = store.counters()?.unwrap_or_default();
1075 let mut counters = Counters {
1076 next_request_id: restore_counter(persisted_counters.next_request_id, max_pending_request_seed),
1077 next_txn_id: restore_counter(persisted_counters.next_txn_id, None),
1078 };
1079 if counters != persisted_counters {
1080 store.save_counters(counters)?;
1086 }
1087 counters.next_txn_id = counters.next_txn_id.max(fresh_txn_seed());
1093
1094 let mut core = Self {
1095 config,
1096 store,
1097 account,
1098 outgoing,
1099 counters,
1100 jitter,
1101 rooms,
1102 timelines,
1103 direct_account_data: None,
1104 global_account_data: BTreeMap::new(),
1105 room_account_data: BTreeMap::new(),
1106 typing: BTreeMap::new(),
1107 receipts: BTreeMap::new(),
1108 public_rooms_result: Vec::new(),
1109 latest_public_rooms_request: None,
1110 user_search_result: Vec::new(),
1111 latest_user_search_request: None,
1112 account_data_guard: BTreeMap::new(),
1113 account_data_write_keys: BTreeMap::new(),
1114 pending_decrypt: BTreeMap::new(),
1115 key_requests_sent: BTreeSet::new(),
1116 history_claims: BTreeMap::new(),
1117 history_claim_tried: BTreeSet::new(),
1118 withheld_sessions: BTreeSet::new(),
1119 key_requests_answered: BTreeSet::new(),
1120 unknown_sender_retry: VecDeque::new(),
1121 typing_debounce: BTreeMap::new(),
1122 pending_sends: BTreeMap::new(),
1123 send_state: BTreeMap::new(),
1124 send_request_owner: BTreeMap::new(),
1125 pending_create_room: BTreeMap::new(),
1126 pending_kinds: BTreeMap::new(),
1127 pending_room_messages: BTreeMap::new(),
1128 sync_request_id: None,
1129 sync_caught_up: false,
1130 pending_flush_batches: VecDeque::new(),
1131 events: Vec::new(),
1132 change_counter: 0,
1133 ingest_error: None,
1134 xsign: None,
1135 xsign_inflight: false,
1136 xsign_attempts: 0,
1137 };
1138 core.rebuild_account_data_guard(&pending_requests);
1139 Ok(core)
1140 }
1141
1142 fn rebuild_account_data_guard(&mut self, pending_requests: &[PendingRequest]) {
1150 let mut writes: Vec<&PendingRequest> = pending_requests
1151 .iter()
1152 .filter(|record| matches!(record.request.kind, OutgoingRequestKind::AccountData | OutgoingRequestKind::RoomAccountData))
1153 .collect();
1154 writes.sort_by(|a, b| a.request.id.cmp(&b.request.id));
1155 for record in writes {
1156 let Some(key) = account_data_key_from_path(&record.request.path) else { continue };
1157 let Some(body) = record.request.body.clone() else { continue };
1158 let guard = self.account_data_guard.entry(key.clone()).or_insert(AccountDataGuard { pending: 0, server_value: None });
1159 guard.pending += 1;
1160 self.store_account_data_cache(&key, Some(body));
1161 self.account_data_write_keys.insert(record.request.id.clone(), key);
1162 }
1163 }
1164
1165 fn next_request_id(&mut self) -> Result<RequestId, MessengerError> {
1169 let seed = self.counters.next_request_id;
1170 self.counters.next_request_id = seed.saturating_add(1);
1171 self.store.save_counters(self.counters)?;
1172 Ok(RequestId::next(seed))
1173 }
1174
1175 fn next_txn_id(&mut self) -> Result<TxnId, MessengerError> {
1180 let seed = self.counters.next_txn_id;
1181 self.counters.next_txn_id = seed.saturating_add(1);
1182 self.store.save_counters(self.counters)?;
1183 Ok(TxnId::new(seed))
1184 }
1185
1186 fn enqueue_request(&mut self, request: OutgoingRequest, lane: Lane) -> Result<(), MessengerError> {
1201 let id = request.id.clone();
1202 let kind = request.kind;
1203 if let Some(batch) = self.outgoing.enqueue(&mut self.store, request, lane)? {
1204 self.pending_flush_batches.push_back(batch);
1205 }
1206 self.pending_kinds.insert(id, kind);
1207 Ok(())
1208 }
1209
1210 fn emit(&mut self, event: MessengerEvent) {
1211 self.change_counter = self.change_counter.wrapping_add(1);
1212 self.events.push(event);
1213 }
1214
1215 fn drain_events(&mut self) -> Vec<MessengerEvent> {
1216 std::mem::take(&mut self.events)
1217 }
1218
1219 fn start_new_send(
1235 &mut self,
1236 room_id: RoomId,
1237 payload: SendPayload,
1238 caller_txn_id: Option<TxnId>,
1239 now_ms: i64,
1240 ) -> Result<(), MessengerError> {
1241 let txn_id = match caller_txn_id {
1242 Some(txn_id) => txn_id,
1243 None => self.next_txn_id()?,
1244 };
1245
1246 if let Some(wire) = payload.room_event() {
1247 if self.typing_debounce.remove(&room_id).is_some() {
1248 if let Ok(request_id) = self.next_request_id() {
1249 let request = OutgoingRequest::typing(request_id, &room_id, &self.config.user_id, false, None);
1250 let _ = self.enqueue_request(request, Lane::Other);
1251 }
1252 }
1253 let content = match &payload {
1254 SendPayload::Message(message) => message_local_echo_content(message),
1255 _ => interpret_content(&wire.event_type, None, &wire.content),
1256 };
1257 let timeline = self.timelines.entry(room_id.clone()).or_default();
1258 timeline.push_local_echo(
1259 txn_id.clone(),
1260 self.config.user_id.clone(),
1261 now_ms,
1262 &wire.event_type,
1263 content,
1264 wire.content,
1265 );
1266 timeline.mark_send_state(&txn_id, SendState::Sending);
1267 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
1268 }
1269
1270 self.send_state.insert(txn_id.clone(), PendingSend { room_id: room_id.clone(), payload, phase: SendPhase::Queued, wire: None });
1271 let queue = self.pending_sends.entry(room_id).or_default();
1272 let was_empty = queue.is_empty();
1273 queue.push_back(txn_id.clone());
1274 if was_empty {
1275 self.start_send_pipeline(&txn_id, now_ms)?;
1276 }
1277 Ok(())
1278 }
1279
1280 fn retry_send(&mut self, room_id: &RoomId, txn_id: &TxnId, now_ms: i64) -> Result<(), MessengerError> {
1285 let Some(pending) = self.send_state.get(txn_id) else { return Ok(()) };
1286 if pending.room_id != *room_id || !matches!(pending.phase, SendPhase::Failed { .. }) {
1287 return Ok(());
1288 }
1289 self.set_send_phase(txn_id, SendPhase::Queued);
1290 let queue = self.pending_sends.entry(room_id.clone()).or_default();
1291 let was_empty = queue.is_empty();
1292 queue.push_back(txn_id.clone());
1293 if was_empty {
1294 self.start_send_pipeline(txn_id, now_ms)?;
1295 }
1296 Ok(())
1297 }
1298
1299 fn set_send_phase(&mut self, txn_id: &TxnId, phase: SendPhase) {
1300 if let Some(entry) = self.send_state.get_mut(txn_id) {
1301 entry.phase = phase;
1302 }
1303 }
1304
1305 fn start_send_pipeline(&mut self, txn_id: &TxnId, now_ms: i64) -> Result<(), MessengerError> {
1314 let Some(pending) = self.send_state.get(txn_id) else { return Ok(()) };
1315 let room_id = pending.room_id.clone();
1316
1317 if pending.wire.is_some() {
1318 return self.begin_awaiting_send(txn_id, &room_id);
1319 }
1320
1321 let is_room_event = pending.payload.is_room_event();
1322 let encrypted_room = self.rooms.get(&room_id).is_some_and(|room| room.encryption.is_some());
1323
1324 if is_room_event && encrypted_room {
1325 self.begin_key_pipeline(txn_id, &room_id, now_ms)
1326 } else {
1327 self.build_plaintext_wire_body(txn_id);
1328 self.begin_awaiting_send(txn_id, &room_id)
1329 }
1330 }
1331
1332 fn encrypted_room_member_ids(&self, room_id: &RoomId) -> BTreeSet<UserId> {
1337 let mut ids: BTreeSet<UserId> = self
1338 .rooms
1339 .get(room_id)
1340 .map(|state| {
1341 state
1342 .members
1343 .iter()
1344 .filter(|(_, member)| matches!(member.membership, Membership::Join | Membership::Invite))
1345 .map(|(user_id, _)| user_id.clone())
1346 .collect()
1347 })
1348 .unwrap_or_default();
1349 ids.insert(self.config.user_id.clone());
1350 ids
1351 }
1352
1353 fn mark_outdated_or_untracked(&mut self, member_ids: &BTreeSet<UserId>) -> Result<Vec<UserId>, MessengerError> {
1359 let tracked: BTreeMap<UserId, bool> = self.store.tracked_users()?.into_iter().collect();
1360 let mut outdated = Vec::new();
1361 let mut newly_tracked = Vec::new();
1362 for user_id in member_ids {
1363 match tracked.get(user_id) {
1364 Some(true) => outdated.push(user_id.clone()),
1365 Some(false) => {}
1366 None => newly_tracked.push(user_id.clone()),
1367 }
1368 }
1369 if !newly_tracked.is_empty() {
1370 DeviceTracker::on_device_lists(&mut self.store, &newly_tracked, &[])?;
1371 outdated.extend(newly_tracked);
1372 }
1373 Ok(outdated)
1374 }
1375
1376 fn known_devices_for(&self, member_ids: &BTreeSet<UserId>) -> Result<Vec<StoredDevice>, MessengerError> {
1379 let mut all = Vec::new();
1380 for user_id in member_ids {
1381 all.extend(DeviceTracker::devices_for_user(&self.store, user_id)?);
1382 }
1383 Ok(all)
1384 }
1385
1386 fn candidate_recipient_devices(&self, member_ids: &BTreeSet<UserId>, known_devices: &[StoredDevice]) -> Vec<StoredDevice> {
1390 known_devices
1391 .iter()
1392 .filter(|device| member_ids.contains(&device.user_id) && !device.blocked)
1393 .filter(|device| !(device.user_id == self.config.user_id && device.device_id == self.config.device_id))
1394 .cloned()
1395 .collect()
1396 }
1397
1398 fn begin_key_pipeline(&mut self, txn_id: &TxnId, room_id: &RoomId, now_ms: i64) -> Result<(), MessengerError> {
1403 let members = self.encrypted_room_member_ids(room_id);
1404 let outdated = self.mark_outdated_or_untracked(&members)?;
1405 if !outdated.is_empty() {
1406 let request_id = self.next_request_id()?;
1407 if let Some(request) = DeviceTracker::keys_query_request(&self.store, request_id.clone())? {
1408 self.send_request_owner.insert(request_id.clone(), txn_id.clone());
1409 self.enqueue_request(request, Lane::Other)?;
1410 self.set_send_phase(txn_id, SendPhase::AwaitingKeysQuery);
1411 return Ok(());
1412 }
1413 }
1414 self.begin_keys_claim_or_share(txn_id, room_id, now_ms)
1415 }
1416
1417 fn begin_keys_claim_or_share(&mut self, txn_id: &TxnId, room_id: &RoomId, now_ms: i64) -> Result<(), MessengerError> {
1421 let members = self.encrypted_room_member_ids(room_id);
1422 let known_devices = self.known_devices_for(&members)?;
1423 let candidates = self.candidate_recipient_devices(&members, &known_devices);
1424 let missing: Vec<StoredDevice> =
1425 OlmSessionManager::sessions_missing_for(&self.store, &candidates)?.into_iter().cloned().collect();
1426 if !missing.is_empty() {
1427 let request_id = self.next_request_id()?;
1428 let refs: Vec<&StoredDevice> = missing.iter().collect();
1429 if let Some(request) = OlmSessionManager::keys_claim_request(request_id.clone(), &refs) {
1430 self.send_request_owner.insert(request_id.clone(), txn_id.clone());
1431 self.enqueue_request(request, Lane::Other)?;
1432 self.set_send_phase(txn_id, SendPhase::AwaitingKeysClaim { devices: missing });
1433 return Ok(());
1434 }
1435 }
1436 self.begin_share_or_encrypt(txn_id, room_id, now_ms)
1437 }
1438
1439 fn begin_share_or_encrypt(&mut self, txn_id: &TxnId, room_id: &RoomId, now_ms: i64) -> Result<(), MessengerError> {
1452 let Some(encryption) = self.rooms.get(room_id).and_then(|room| room.encryption.clone()) else {
1453 self.build_plaintext_wire_body(txn_id);
1454 return self.begin_awaiting_send(txn_id, room_id);
1455 };
1456 let members = self.encrypted_room_member_ids(room_id);
1457 let known_devices = self.known_devices_for(&members)?;
1458 let update = GroupSessionManager::ensure_outbound_session(
1459 &mut self.store,
1460 room_id,
1461 &self.config.user_id,
1462 &self.config.device_id,
1463 &encryption,
1464 &members,
1465 &known_devices,
1466 now_ms,
1467 )?;
1468 let own_keys = self.account.identity_keys();
1469 GroupSessionManager::adopt_own_outbound_session(
1470 &mut self.store,
1471 room_id,
1472 &self.config.user_id,
1473 own_keys.curve25519,
1474 own_keys.ed25519,
1475 &update,
1476 )?;
1477
1478 if let Some(room_key_content) = update.room_key_content.clone() {
1479 let missing_ids: BTreeSet<(UserId, DeviceId)> = OlmSessionManager::sessions_missing_for(&self.store, &update.new_recipients)?
1480 .into_iter()
1481 .map(|device| (device.user_id.clone(), device.device_id.clone()))
1482 .collect();
1483 let sessioned: Vec<StoredDevice> = update
1484 .new_recipients
1485 .into_iter()
1486 .filter(|device| !missing_ids.contains(&(device.user_id.clone(), device.device_id.clone())))
1487 .collect();
1488
1489 let mut pending_ids = BTreeSet::new();
1490 for chunk in GroupSessionManager::chunk_recipients_for_send_to_device(&sessioned) {
1491 let body = GroupSessionManager::build_room_key_send_to_device_body(
1492 &mut self.store,
1493 &self.account,
1494 &self.config.user_id,
1495 &self.config.device_id,
1496 chunk,
1497 &room_key_content,
1498 )?;
1499 let request_id = self.next_request_id()?;
1500 let share_txn = self.next_txn_id()?;
1501 let request = OutgoingRequest::send_to_device(request_id.clone(), ROOM_KEY_SHARE_EVENT_TYPE, &share_txn, body);
1502 self.send_request_owner.insert(request_id.clone(), txn_id.clone());
1503 self.enqueue_request(request, Lane::ToDevice)?;
1504 pending_ids.insert(request_id);
1505 }
1506 if !pending_ids.is_empty() {
1507 self.set_send_phase(txn_id, SendPhase::AwaitingKeyShare { pending: pending_ids });
1508 return Ok(());
1509 }
1510 }
1511
1512 self.finish_encrypt_and_send(txn_id, room_id)?;
1513 self.begin_awaiting_send(txn_id, room_id)
1514 }
1515
1516 fn finish_encrypt_and_send(&mut self, txn_id: &TxnId, room_id: &RoomId) -> Result<(), MessengerError> {
1523 let Some(wire) = self.send_state.get(txn_id).and_then(|pending| pending.payload.room_event()) else {
1524 return Ok(());
1525 };
1526 let sender_curve = self.account.identity_keys().curve25519.to_base64();
1527 let encrypted = GroupSessionManager::encrypt_event(
1528 &mut self.store,
1529 room_id,
1530 &sender_curve,
1531 &self.config.device_id,
1532 &wire.event_type,
1533 wire.content,
1534 wire.relates_to,
1535 )?;
1536 let wire_content = serde_json::to_value(RoomEncryptedContent::Megolm(encrypted))?;
1537 if let Some(entry) = self.send_state.get_mut(txn_id) {
1538 entry.wire = Some(WireSend::Event { event_type: "m.room.encrypted".to_string(), content: wire_content });
1539 }
1540 Ok(())
1541 }
1542
1543 fn build_plaintext_wire_body(&mut self, txn_id: &TxnId) {
1549 let Some(pending) = self.send_state.get(txn_id) else { return };
1550 let wire = match &pending.payload {
1551 SendPayload::Message(_) | SendPayload::Event { .. } => match pending.payload.room_event() {
1552 Some(room_event) => WireSend::Event { event_type: room_event.event_type, content: room_event.content },
1553 None => return,
1554 },
1555 SendPayload::Reaction { target, key } => WireSend::Event {
1556 event_type: "m.reaction".to_string(),
1557 content: serde_json::json!({
1558 "m.relates_to": { "rel_type": "m.annotation", "event_id": target.as_str(), "key": key }
1559 }),
1560 },
1561 SendPayload::Redaction { target, reason } => WireSend::Redact { target: target.clone(), reason: reason.clone() },
1562 };
1563 if let Some(entry) = self.send_state.get_mut(txn_id) {
1564 entry.wire = Some(wire);
1565 }
1566 }
1567
1568 fn begin_awaiting_send(&mut self, txn_id: &TxnId, room_id: &RoomId) -> Result<(), MessengerError> {
1573 let Some(wire) = self.send_state.get(txn_id).and_then(|pending| pending.wire.clone()) else { return Ok(()) };
1574 let request_id = self.next_request_id()?;
1575 let request = match &wire {
1576 WireSend::Event { event_type, content } => {
1577 OutgoingRequest::room_send(request_id.clone(), room_id, event_type, txn_id, content.clone())
1578 }
1579 WireSend::Redact { target, reason } => {
1580 let body = match reason {
1581 Some(reason) => serde_json::json!({ "reason": reason }),
1582 None => serde_json::json!({}),
1583 };
1584 OutgoingRequest::room_redact(request_id.clone(), room_id, target, txn_id, body)
1585 }
1586 };
1587 self.send_request_owner.insert(request_id, txn_id.clone());
1588 self.enqueue_request(request, Lane::Room(room_id.clone()))?;
1589 self.set_send_phase(txn_id, SendPhase::AwaitingSend);
1590 Ok(())
1591 }
1592
1593 fn advance_send_on_success(
1599 &mut self,
1600 txn_id: &TxnId,
1601 request_id: &RequestId,
1602 kind: Option<OutgoingRequestKind>,
1603 response: &HttpResponseDescriptor,
1604 now_ms: i64,
1605 ) {
1606 match kind {
1607 Some(OutgoingRequestKind::KeysQuery) => {
1608 let Some(room_id) = self.send_state.get(txn_id).map(|pending| pending.room_id.clone()) else { return };
1609 let _ = self.begin_keys_claim_or_share(txn_id, &room_id, now_ms);
1610 }
1611 Some(OutgoingRequestKind::KeysClaim) => {
1612 let Some(pending) = self.send_state.get(txn_id) else { return };
1613 let room_id = pending.room_id.clone();
1614 if let SendPhase::AwaitingKeysClaim { devices, .. } = &pending.phase {
1615 let devices = devices.clone();
1616 let refs: Vec<&StoredDevice> = devices.iter().collect();
1617 let _ = OlmSessionManager::on_keys_claim_response(&mut self.store, &self.account, &refs, &response.body);
1618 }
1619 let _ = self.begin_share_or_encrypt(txn_id, &room_id, now_ms);
1620 }
1621 Some(OutgoingRequestKind::SendToDevice) => {
1622 let mut remaining = None;
1623 if let Some(entry) = self.send_state.get_mut(txn_id) {
1624 if let SendPhase::AwaitingKeyShare { pending } = &mut entry.phase {
1625 pending.remove(request_id);
1626 remaining = Some(pending.len());
1627 }
1628 }
1629 if remaining == Some(0) {
1630 let Some(room_id) = self.send_state.get(txn_id).map(|pending| pending.room_id.clone()) else { return };
1631 let _ = self.finish_encrypt_and_send(txn_id, &room_id);
1632 let _ = self.begin_awaiting_send(txn_id, &room_id);
1633 }
1634 }
1635 Some(OutgoingRequestKind::RoomSend) | Some(OutgoingRequestKind::RoomRedact) => {
1636 self.complete_send_success(txn_id, response, now_ms);
1637 }
1638 _ => {}
1639 }
1640 }
1641
1642 fn complete_send_success(&mut self, txn_id: &TxnId, response: &HttpResponseDescriptor, now_ms: i64) {
1648 let Some(pending) = self.send_state.get(txn_id) else { return };
1649 let room_id = pending.room_id.clone();
1650 if pending.payload.is_room_event() {
1651 if let Ok(body) = serde_json::from_slice::<SendResponseBody>(&response.body) {
1652 if let Some(timeline) = self.timelines.get_mut(&room_id) {
1653 timeline.set_echo_event_id(txn_id, body.event_id);
1654 }
1655 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
1656 }
1657 }
1658 self.send_state.remove(txn_id);
1659 self.finish_active_send(&room_id, txn_id, now_ms);
1660 }
1661
1662 fn fail_send(&mut self, txn_id: &TxnId, errcode: String, now_ms: i64) {
1668 let Some(pending) = self.send_state.get(txn_id) else { return };
1669 let room_id = pending.room_id.clone();
1670 if pending.payload.is_room_event() {
1671 if let Some(timeline) = self.timelines.get_mut(&room_id) {
1672 timeline.mark_send_state(txn_id, SendState::Failed { reason: errcode.clone() });
1673 }
1674 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
1675 }
1676 self.set_send_phase(txn_id, SendPhase::Failed { errcode });
1677 self.finish_active_send(&room_id, txn_id, now_ms);
1678 }
1679
1680 fn finish_active_send(&mut self, room_id: &RoomId, txn_id: &TxnId, now_ms: i64) {
1686 if let Some(queue) = self.pending_sends.get_mut(room_id) {
1687 if queue.front() == Some(txn_id) {
1688 queue.pop_front();
1689 } else {
1690 queue.retain(|id| id != txn_id);
1691 }
1692 }
1693 let next = self.pending_sends.get(room_id).and_then(|queue| queue.front().cloned());
1694 if let Some(next_txn) = next {
1695 let _ = self.start_send_pipeline(&next_txn, now_ms);
1696 }
1697 }
1698
1699 pub fn releasable_requests(&mut self, now_ms: i64) -> Vec<OutgoingRequest> {
1719 self.ensure_sync_enqueued();
1720 self.outgoing.releasable(&self.store, now_ms)
1721 }
1722
1723 fn ensure_sync_enqueued(&mut self) {
1724 if self.sync_request_id.is_some() {
1725 return;
1726 }
1727 let Ok(request_id) = self.next_request_id() else { return };
1728 let since = self.store.sync_token().ok().flatten().map(str::to_string);
1729 let timeout_ms = if since.is_some() && self.sync_caught_up { SYNC_TIMEOUT_MS } else { 0 };
1739 let request = OutgoingRequest::sync(request_id.clone(), since.as_deref(), Some(timeout_ms));
1740 if self.enqueue_request(request, Lane::Sync).is_ok() {
1741 self.sync_request_id = Some(request_id);
1742 }
1743 }
1744
1745 pub fn on_response(&mut self, request_id: RequestId, resp: HttpResponseDescriptor, now_ms: i64) -> Vec<MessengerEvent> {
1751 let kind = self.pending_kinds.get(&request_id).copied();
1752 let room_messages_room = self.pending_room_messages.get(&request_id).cloned();
1753 let create_room_kind = self.pending_create_room.get(&request_id).cloned();
1754 let send_owner = self.send_request_owner.get(&request_id).cloned();
1755
1756 let outcome = self.outgoing.on_response(&mut self.store, &request_id, resp, now_ms, self.jitter.as_mut());
1757 let (is_terminal, terminal_response, failed_errcode) = match outcome {
1758 Ok(Some(ResponseOutcome::Done(response))) => (true, Some(response), None),
1759 Ok(Some(ResponseOutcome::Failed { errcode, .. })) => (true, None, Some(errcode)),
1760 _ => (false, None, None),
1761 };
1762
1763 if is_terminal {
1764 self.pending_kinds.remove(&request_id);
1765 if matches!(kind, Some(OutgoingRequestKind::Sync)) {
1766 self.sync_request_id = None;
1767 if terminal_response.is_some() {
1768 self.sync_caught_up = true;
1769 }
1770 }
1771 if room_messages_room.is_some() {
1772 self.pending_room_messages.remove(&request_id);
1773 }
1774 self.pending_create_room.remove(&request_id);
1775 self.send_request_owner.remove(&request_id);
1776 if let Some(key) = self.account_data_write_keys.remove(&request_id) {
1777 self.settle_account_data_write(&key, terminal_response.is_some());
1778 }
1779 }
1780
1781 if let Some(response) = &terminal_response {
1782 if let Err(error) = self.handle_terminal_success(&request_id, kind, room_messages_room, create_room_kind, response) {
1789 self.ingest_error = Some(match kind {
1790 Some(kind) => format!("{kind:?} response not ingested: {error}"),
1791 None => format!("response not ingested: {error}"),
1792 });
1793 }
1794 }
1795
1796 if is_terminal
1797 && terminal_response.is_none()
1798 && matches!(kind, Some(OutgoingRequestKind::SigningKeysUpload) | Some(OutgoingRequestKind::SignaturesUpload))
1799 {
1800 self.xsign_inflight = false;
1801 }
1802
1803 if let Some(txn_id) = send_owner {
1804 if let Some(response) = &terminal_response {
1805 self.advance_send_on_success(&txn_id, &request_id, kind, response, now_ms);
1806 } else if let Some(errcode) = failed_errcode.clone() {
1807 self.fail_send(&txn_id, errcode, now_ms);
1808 }
1809 }
1810
1811 if matches!(kind, Some(OutgoingRequestKind::KeysUpload)) {
1815 if let Some(errcode) = failed_errcode {
1816 self.ingest_error = Some(format!("keys/upload failed: {errcode}"));
1817 }
1818 }
1819
1820 self.drain_events()
1821 }
1822
1823 pub fn on_transport_error(&mut self, request_id: &RequestId, now_ms: i64) {
1828 let _ = self.outgoing.on_transport_error(request_id, now_ms, self.jitter.as_mut());
1829 }
1830
1831 fn handle_terminal_success(
1832 &mut self,
1833 request_id: &RequestId,
1834 kind: Option<OutgoingRequestKind>,
1835 room_messages_room: Option<RoomId>,
1836 create_room_kind: Option<CreateRoomKind>,
1837 response: &HttpResponseDescriptor,
1838 ) -> Result<(), MessengerError> {
1839 match kind {
1840 Some(OutgoingRequestKind::Sync) => {
1841 let parsed = parse_sync_response(&response.body)?;
1842 self.ingest_sync(&parsed)?;
1843 }
1844 Some(OutgoingRequestKind::KeysUpload) => {
1845 self.account.on_keys_upload_response(&mut self.store)?;
1846 }
1847 Some(OutgoingRequestKind::KeysClaim) if self.history_claims.contains_key(request_id) => {
1848 if let Some(devices) = self.history_claims.remove(request_id) {
1849 let refs: Vec<&StoredDevice> = devices.iter().collect();
1850 let _ = OlmSessionManager::on_keys_claim_response(&mut self.store, &self.account, &refs, &response.body);
1851 }
1852 self.retry_missing_room_keys();
1853 }
1854 Some(OutgoingRequestKind::SigningKeysUpload) => {
1855 self.xsign_inflight = false;
1856 if let Some(state) = self.xsign.as_mut() {
1857 state.uploaded = true;
1858 let snapshot = state.clone();
1859 snapshot.save(&mut self.store)?;
1860 }
1861 self.maybe_enqueue_cross_signing()?;
1862 }
1863 Some(OutgoingRequestKind::SignaturesUpload) => {
1864 self.xsign_inflight = false;
1865 let failures_empty = serde_json::from_slice::<serde_json::Value>(&response.body)
1866 .ok()
1867 .and_then(|v| v.get("failures").and_then(|f| f.as_object()).map(|f| f.is_empty()))
1868 .unwrap_or(true);
1869 if failures_empty {
1870 if let Some(state) = self.xsign.as_mut() {
1871 state.device_signed = true;
1872 let snapshot = state.clone();
1873 snapshot.save(&mut self.store)?;
1874 }
1875 }
1876 }
1877 Some(OutgoingRequestKind::KeysQuery) => {
1878 let outcome = DeviceTracker::on_keys_query_response(&mut self.store, &response.body)?;
1879 if !outcome.key_changes.is_empty() {
1880 let room_ids: Vec<RoomId> = self.rooms.keys().cloned().collect();
1881 for change in &outcome.key_changes {
1882 let _ = GroupSessionManager::forget_shared_device(
1883 &mut self.store,
1884 &room_ids,
1885 &change.user_id,
1886 &change.device_id,
1887 );
1888 self.emit(MessengerEvent::DeviceKeyChanged {
1889 user_id: change.user_id.clone(),
1890 device_id: change.device_id.clone(),
1891 });
1892 }
1893 }
1894 self.retry_unknown_sender_queue()?;
1895 }
1896 Some(OutgoingRequestKind::RoomMessages) => {
1897 if let Some(room_id) = room_messages_room {
1898 self.ingest_room_messages_response(&room_id, &response.body)?;
1899 }
1900 }
1901 Some(OutgoingRequestKind::CreateRoom) => {
1902 if let Some(kind) = create_room_kind {
1903 self.on_create_room_response(kind, response)?;
1904 }
1905 }
1906 Some(OutgoingRequestKind::PublicRooms) if self.latest_public_rooms_request.as_ref() == Some(request_id) => {
1911 let body: PublicRoomsResponseBody = serde_json::from_slice(&response.body)?;
1912 self.public_rooms_result = body
1913 .chunk
1914 .into_iter()
1915 .map(|entry| PublicRoomsResultEntry {
1916 room_id: entry.room_id,
1917 name: entry.name,
1918 topic: entry.topic,
1919 num_joined_members: entry.num_joined_members,
1920 })
1921 .collect();
1922 self.emit(MessengerEvent::PublicRoomsChanged);
1923 }
1924 Some(OutgoingRequestKind::UserDirectorySearch) if self.latest_user_search_request.as_ref() == Some(request_id) => {
1926 let body: UserDirectorySearchResponseBody = serde_json::from_slice(&response.body)?;
1927 self.user_search_result = body
1928 .results
1929 .into_iter()
1930 .map(|entry| UserDirectoryResultEntry { user_id: entry.user_id, display_name: entry.display_name })
1931 .collect();
1932 self.emit(MessengerEvent::UserSearchChanged);
1933 }
1934 _ => {}
1935 }
1936 Ok(())
1937 }
1938
1939 fn on_create_room_response(&mut self, kind: CreateRoomKind, response: &HttpResponseDescriptor) -> Result<(), MessengerError> {
1945 let CreateRoomKind::Dm { peer } = kind else { return Ok(()) };
1946 let body: CreateRoomResponseBody = serde_json::from_slice(&response.body)?;
1947 self.merge_direct_account_data(body.room_id, peer)
1948 }
1949
1950 fn merge_direct_account_data(&mut self, room_id: RoomId, peer: UserId) -> Result<(), MessengerError> {
1962 let mut direct = self.direct_account_data.clone().unwrap_or_default();
1963 let rooms_for_peer = direct.0.entry(peer).or_default();
1964 if !rooms_for_peer.contains(&room_id) {
1965 rooms_for_peer.push(room_id);
1966 }
1967 let value = serde_json::to_value(&direct)?;
1968 self.write_account_data(None, "m.direct".to_string(), value)
1969 }
1970
1971 fn cached_account_data(&self, key: &AccountDataKey) -> Option<&serde_json::Value> {
1972 match &key.0 {
1973 Some(room_id) => self.room_account_data(room_id, &key.1),
1974 None => self.global_account_data(&key.1),
1975 }
1976 }
1977
1978 fn store_account_data_cache(&mut self, key: &AccountDataKey, value: Option<serde_json::Value>) {
1981 if key.0.is_none() && key.1 == "m.direct" {
1982 self.direct_account_data = value.as_ref().and_then(|v| serde_json::from_value::<DirectContent>(v.clone()).ok());
1983 }
1984 match (&key.0, value) {
1985 (None, Some(v)) => {
1986 self.global_account_data.insert(key.1.clone(), v);
1987 }
1988 (None, None) => {
1989 self.global_account_data.remove(&key.1);
1990 }
1991 (Some(room_id), Some(v)) => {
1992 self.room_account_data.entry(room_id.clone()).or_default().insert(key.1.clone(), v);
1993 }
1994 (Some(room_id), None) => {
1995 if let Some(by_type) = self.room_account_data.get_mut(room_id) {
1996 by_type.remove(&key.1);
1997 }
1998 }
1999 }
2000 }
2001
2002 fn write_account_data(&mut self, room_id: Option<RoomId>, event_type: String, value: serde_json::Value) -> Result<(), MessengerError> {
2008 let request_id = self.next_request_id()?;
2009 let request = match &room_id {
2010 Some(room_id) => OutgoingRequest::room_account_data(request_id.clone(), &self.config.user_id, room_id, &event_type, value.clone()),
2011 None => OutgoingRequest::account_data(request_id.clone(), &self.config.user_id, &event_type, value.clone()),
2012 };
2013 let key: AccountDataKey = (room_id, event_type);
2014 let known = self.cached_account_data(&key).cloned();
2015 let guard = self.account_data_guard.entry(key.clone()).or_insert(AccountDataGuard { pending: 0, server_value: known });
2016 guard.pending += 1;
2017 self.store_account_data_cache(&key, Some(value));
2018 match self.enqueue_request(request, Lane::AccountData) {
2019 Ok(()) => {
2020 self.account_data_write_keys.insert(request_id, key);
2021 Ok(())
2022 }
2023 Err(error) => {
2024 self.settle_account_data_write(&key, false);
2025 Err(error)
2026 }
2027 }
2028 }
2029
2030 fn settle_account_data_write(&mut self, key: &AccountDataKey, succeeded: bool) {
2035 let Some(guard) = self.account_data_guard.get_mut(key) else { return };
2036 guard.pending = guard.pending.saturating_sub(1);
2037 if guard.pending > 0 {
2038 return;
2039 }
2040 let Some(guard) = self.account_data_guard.remove(key) else { return };
2041 if !succeeded {
2042 self.store_account_data_cache(key, guard.server_value);
2043 self.emit(MessengerEvent::RoomsChanged);
2044 }
2045 }
2046
2047 fn apply_synced_account_data(&mut self, room_id: Option<&RoomId>, event_type: &str, content: serde_json::Value) {
2051 let key: AccountDataKey = (room_id.cloned(), event_type.to_string());
2052 if let Some(guard) = self.account_data_guard.get_mut(&key) {
2053 guard.server_value = Some(content);
2054 return;
2055 }
2056 self.store_account_data_cache(&key, Some(content));
2057 }
2058
2059 pub fn take_flush_batch(&mut self) -> Option<FlushBatch> {
2062 if let Some(batch) = self.pending_flush_batches.pop_front() {
2063 return Some(batch);
2064 }
2065 self.store.take_flush_batch()
2066 }
2067
2068 pub fn ack_flush(&mut self, id: u64) {
2070 self.store.ack_flush(id);
2071 }
2072
2073 pub fn events(&mut self) -> Vec<MessengerEvent> {
2076 self.drain_events()
2077 }
2078
2079 pub fn take_ingest_error(&mut self) -> Option<String> {
2082 self.ingest_error.take()
2083 }
2084
2085 pub fn change_counter(&self) -> u64 {
2090 self.change_counter
2091 }
2092
2093 pub fn room_ids(&self) -> impl Iterator<Item = &RoomId> {
2095 self.rooms.keys()
2096 }
2097
2098 pub fn room_state(&self, room_id: &RoomId) -> Option<&RoomState> {
2100 self.rooms.get(room_id)
2101 }
2102
2103 pub fn timeline(&self, room_id: &RoomId) -> Option<&Timeline> {
2105 self.timelines.get(room_id)
2106 }
2107
2108 pub fn room_kind(&self, room_id: &RoomId) -> Option<RoomKind> {
2112 self.rooms.get(room_id).map(|state| state.derive_room_kind(room_id, self.direct_account_data.as_ref()))
2113 }
2114
2115 pub fn typing_users(&self, room_id: &RoomId) -> &[UserId] {
2119 self.typing.get(room_id).map(Vec::as_slice).unwrap_or(&[])
2120 }
2121
2122 pub fn receipts(&self, room_id: &RoomId) -> Option<&ReceiptContent> {
2124 self.receipts.get(room_id)
2125 }
2126
2127 pub fn room_account_data(&self, room_id: &RoomId, event_type: &str) -> Option<&serde_json::Value> {
2130 self.room_account_data.get(room_id).and_then(|by_type| by_type.get(event_type))
2131 }
2132
2133 pub fn global_account_data(&self, event_type: &str) -> Option<&serde_json::Value> {
2136 self.global_account_data.get(event_type)
2137 }
2138
2139 pub fn key_request_count(&self) -> usize {
2141 self.key_requests_sent.len()
2142 }
2143
2144 pub fn user_id(&self) -> &UserId {
2149 &self.config.user_id
2150 }
2151
2152 pub fn has_tracked_device(&self, user_id: &UserId) -> bool {
2158 DeviceTracker::devices_for_user(&self.store, user_id).map(|devices| !devices.is_empty()).unwrap_or(false)
2159 }
2160
2161 pub fn public_rooms_result(&self) -> &[PublicRoomsResultEntry] {
2164 &self.public_rooms_result
2165 }
2166
2167 pub fn user_search_result(&self) -> &[UserDirectoryResultEntry] {
2170 &self.user_search_result
2171 }
2172
2173 pub fn send_failure_reason(&self, txn_id: &TxnId) -> Option<&str> {
2178 match self.send_state.get(txn_id).map(|pending| &pending.phase) {
2179 Some(SendPhase::Failed { errcode }) => Some(errcode.as_str()),
2180 _ => None,
2181 }
2182 }
2183
2184 pub fn dispatch(&mut self, cmd: MessengerCommand, now_ms: i64) -> Result<(), MessengerError> {
2191 match cmd {
2192 MessengerCommand::SendMessage { room_id, message, txn_id } => {
2193 if let Some(edit_of) = &message.edit_of {
2194 let owned = self
2195 .timelines
2196 .get(&room_id)
2197 .and_then(|timeline| timeline.item_by_event_id(edit_of))
2198 .is_some_and(|item| item.sender == self.config.user_id);
2199 if !owned {
2200 return Err(MessengerError::EditNotOwned { event_id: edit_of.clone() });
2201 }
2202 }
2203 self.start_new_send(room_id, SendPayload::Message(message), txn_id, now_ms)?;
2204 }
2205 MessengerCommand::Forward { from_room, event_id, to_room, txn_id } => {
2206 let payload = self.build_forward_payload(&from_room, &event_id, &to_room)?;
2207 self.start_new_send(to_room, payload, txn_id, now_ms)?;
2208 }
2209 MessengerCommand::React { room_id, target, key } => {
2210 self.start_new_send(room_id, SendPayload::Reaction { target, key }, None, now_ms)?;
2211 }
2212 MessengerCommand::Redact { room_id, target, reason } => {
2213 self.start_new_send(room_id, SendPayload::Redaction { target, reason }, None, now_ms)?;
2214 }
2215 MessengerCommand::RetrySend { room_id, txn_id } => {
2216 self.retry_send(&room_id, &txn_id, now_ms)?;
2217 }
2218 MessengerCommand::CreateRoom { kind } => {
2219 self.dispatch_create_room(kind)?;
2220 }
2221 MessengerCommand::JoinRoom { room_id } => {
2222 let request_id = self.next_request_id()?;
2223 let request = OutgoingRequest::join_room(request_id, room_id.as_str());
2224 self.enqueue_request(request, Lane::Other)?;
2225
2226 let dm_peer = self.rooms.get(&room_id).and_then(|room| {
2233 let invited_as_direct = room.members.get(&self.config.user_id).is_some_and(|member| member.is_direct);
2234 if !invited_as_direct {
2235 return None;
2236 }
2237 room.members.keys().find(|user_id| **user_id != self.config.user_id).cloned()
2238 });
2239 if let Some(peer) = dm_peer {
2240 self.merge_direct_account_data(room_id, peer)?;
2241 }
2242 }
2243 MessengerCommand::LeaveRoom { room_id } => {
2244 let request_id = self.next_request_id()?;
2245 let request = OutgoingRequest::leave_room(request_id, &room_id);
2246 self.enqueue_request(request, Lane::Other)?;
2247 }
2248 MessengerCommand::Invite { room_id, user_id } => {
2249 let request_id = self.next_request_id()?;
2250 let request = OutgoingRequest::invite(request_id, &room_id, &user_id);
2251 self.enqueue_request(request, Lane::Other)?;
2252 }
2253 MessengerCommand::Kick { room_id, user_id, reason } => {
2254 let request_id = self.next_request_id()?;
2255 let request = OutgoingRequest::kick(request_id, &room_id, &user_id, reason.as_deref());
2256 self.enqueue_request(request, Lane::Other)?;
2257 }
2258 MessengerCommand::SetPowerLevel { room_id, user_id, level } => {
2259 let mut content = self.room_state(&room_id).map(|st| st.power_levels.clone()).ok_or_else(|| MessengerError::IntentRefused(format!("unknown room {}", room_id.as_str())))?;
2260 content.users.insert(user_id, level);
2261 let body = serde_json::to_value(&content)?;
2262 let request_id = self.next_request_id()?;
2263 let request = OutgoingRequest::set_state(request_id, &room_id, "m.room.power_levels", "", body);
2264 self.enqueue_request(request, Lane::Other)?;
2265 }
2266 MessengerCommand::SetEventsDefault { room_id, level } => {
2267 let mut content = self.room_state(&room_id).map(|st| st.power_levels.clone()).ok_or_else(|| MessengerError::IntentRefused(format!("unknown room {}", room_id.as_str())))?;
2268 content.events_default = level;
2269 let body = serde_json::to_value(&content)?;
2270 let request_id = self.next_request_id()?;
2271 let request = OutgoingRequest::set_state(request_id, &room_id, "m.room.power_levels", "", body);
2272 self.enqueue_request(request, Lane::Other)?;
2273 }
2274 MessengerCommand::SetTag { room_id, tag, order } => {
2275 let mut content = self.current_tag_content(&room_id);
2276 content.tags.insert(tag, TagInfo { order });
2277 self.enqueue_tag_update(room_id, content)?;
2278 }
2279 MessengerCommand::RemoveTag { room_id, tag } => {
2280 let mut content = self.current_tag_content(&room_id);
2281 content.tags.remove(&tag);
2282 self.enqueue_tag_update(room_id, content)?;
2283 }
2284 MessengerCommand::MarkRead { room_id, event_id } => {
2285 let request_id = self.next_request_id()?;
2286 let body = serde_json::json!({ "m.fully_read": event_id.as_str(), "m.read": event_id.as_str() });
2287 let request = OutgoingRequest::read_markers(request_id, &room_id, body);
2288 self.enqueue_request(request, Lane::Other)?;
2289 }
2290 MessengerCommand::SetTyping { room_id, typing } => {
2291 if typing {
2292 let should_send = match self.typing_debounce.get(&room_id) {
2293 Some(&last_sent_ms) => now_ms.saturating_sub(last_sent_ms) >= TYPING_DEBOUNCE_MS,
2294 None => true,
2295 };
2296 if !should_send {
2297 return Ok(());
2298 }
2299 self.typing_debounce.insert(room_id.clone(), now_ms);
2300 } else {
2301 self.typing_debounce.remove(&room_id);
2302 }
2303 let request_id = self.next_request_id()?;
2304 let timeout_ms = typing.then_some(TYPING_TIMEOUT_MS);
2305 let request = OutgoingRequest::typing(request_id, &room_id, &self.config.user_id, typing, timeout_ms);
2306 self.enqueue_request(request, Lane::Other)?;
2307 }
2308 MessengerCommand::RetryDecryption { room_id, event_id } => {
2309 if self.retry_decrypt_one(&room_id, &event_id) {
2310 self.emit(MessengerEvent::TimelineChanged { room_id });
2311 }
2312 }
2313 MessengerCommand::LoadOlder { room_id } => {
2314 let from = self
2315 .timelines
2316 .get(&room_id)
2317 .and_then(|timeline| {
2318 timeline
2319 .older_token()
2320 .map(str::to_string)
2321 .or_else(|| timeline.gap().and_then(|gap| gap.prev_batch.clone()))
2322 })
2323 .or_else(|| {
2329 if self.rooms.contains_key(&room_id) {
2330 self.store.sync_token().ok().flatten().map(str::to_string)
2331 } else {
2332 None
2333 }
2334 });
2335 let Some(from) = from else { return Ok(()) };
2336 let request_id = self.next_request_id()?;
2337 let request =
2338 OutgoingRequest::room_messages(request_id.clone(), &room_id, &from, "b", Some(LOAD_OLDER_PAGE_SIZE));
2339 self.enqueue_request(request, Lane::Room(room_id.clone()))?;
2340 self.pending_room_messages.insert(request_id, room_id);
2341 }
2342 MessengerCommand::SetAccountData { event_type, content } => {
2343 self.write_account_data(None, event_type, content)?;
2344 }
2345 MessengerCommand::SetRoomAccountData { room_id, event_type, content } => {
2346 self.write_account_data(Some(room_id), event_type, content)?;
2347 }
2348 MessengerCommand::SearchPublicRooms { term } => {
2349 let request_id = self.next_request_id()?;
2350 let body = serde_json::json!({ "filter": { "generic_search_term": term }, "limit": 50 });
2351 let request = OutgoingRequest::public_rooms(request_id.clone(), body);
2352 self.latest_public_rooms_request = Some(request_id);
2353 self.enqueue_request(request, Lane::Other)?;
2354 }
2355 MessengerCommand::SearchUsers { term } => {
2356 let request_id = self.next_request_id()?;
2357 let body = serde_json::json!({ "search_term": term, "limit": 20 });
2358 let request = OutgoingRequest::user_directory_search(request_id.clone(), body);
2359 self.latest_user_search_request = Some(request_id);
2360 self.enqueue_request(request, Lane::Other)?;
2361 }
2362 }
2363 Ok(())
2364 }
2365
2366 fn dispatch_create_room(&mut self, kind: CreateRoomKind) -> Result<(), MessengerError> {
2371 let body = match &kind {
2372 CreateRoomKind::Dm { peer } => serde_json::json!({
2373 "is_direct": true,
2374 "invite": [peer.as_str()],
2375 }),
2376 CreateRoomKind::Group { name, invite, members_can_invite } => serde_json::json!({
2377 "visibility": "private",
2378 "is_direct": false,
2379 "name": name,
2380 "invite": invite.iter().map(UserId::as_str).collect::<Vec<_>>(),
2381 "members_can_invite": members_can_invite,
2382 }),
2383 CreateRoomKind::Channel { name, topic } => {
2384 let mut value = serde_json::json!({
2385 "visibility": "public",
2386 "is_direct": false,
2387 "name": name,
2388 });
2389 if let Some(topic) = topic {
2390 value["topic"] = serde_json::Value::String(topic.clone());
2391 }
2392 value
2393 }
2394 };
2395 let request_id = self.next_request_id()?;
2396 let request = OutgoingRequest::create_room(request_id.clone(), body);
2397 self.pending_create_room.insert(request_id.clone(), kind);
2398 self.enqueue_request(request, Lane::Other)?;
2399 Ok(())
2400 }
2401
2402 fn current_tag_content(&self, room_id: &RoomId) -> TagContent {
2407 self.room_account_data(room_id, "m.tag")
2408 .and_then(|value| serde_json::from_value(value.clone()).ok())
2409 .unwrap_or_default()
2410 }
2411
2412 fn enqueue_tag_update(&mut self, room_id: RoomId, content: TagContent) -> Result<(), MessengerError> {
2418 let value = serde_json::to_value(&content)?;
2419 self.write_account_data(Some(room_id), "m.tag".to_string(), value)
2420 }
2421
2422 fn build_forward_payload(
2427 &self,
2428 from_room: &RoomId,
2429 event_id: &EventId,
2430 to_room: &RoomId,
2431 ) -> Result<SendPayload, MessengerError> {
2432 let refuse = |reason: &dyn std::fmt::Display| {
2433 MessengerError::IntentRefused(format!("cannot forward {event_id} from {from_room}: {reason}"))
2434 };
2435 if !self.rooms.contains_key(to_room) {
2436 return Err(refuse(&format!("the target room {to_room} is unknown")));
2437 }
2438 let source = match self.timelines.get(from_room) {
2439 Some(timeline) => timeline.forward_source(event_id),
2440 None => Err(ForwardRefusal::Missing),
2441 }
2442 .map_err(|reason| refuse(&reason))?;
2443 let (marker_key, marker) = self.forward_marker(from_room);
2444 Ok(SendPayload::Event {
2445 event_type: source.event_type,
2446 content: forwardable_content(source.content, marker_key, marker),
2447 })
2448 }
2449
2450 fn forward_marker(&self, from_room: &RoomId) -> (&'static str, serde_json::Value) {
2455 let public_channel = self.room_kind(from_room) == Some(RoomKind::Channel);
2456 if public_channel {
2457 let room_name = self.forward_room_name(from_room);
2458 (KEY_FORWARDED_FROM, serde_json::json!({ "room_id": from_room.as_str(), "room_name": room_name }))
2459 } else {
2460 (KEY_FORWARDED, serde_json::Value::Bool(true))
2461 }
2462 }
2463
2464 fn forward_room_name(&self, room_id: &RoomId) -> String {
2468 if let Some(name) = self.rooms.get(room_id).and_then(|room| room.name.clone()) {
2469 return name;
2470 }
2471 match self.room_kind(room_id) {
2472 Some(RoomKind::Dm) => "Direct",
2473 Some(RoomKind::Channel) => "Channel",
2474 Some(RoomKind::Group) | None => "Group",
2475 }
2476 .to_string()
2477 }
2478
2479 fn retry_decrypt_one(&mut self, room_id: &RoomId, event_id: &EventId) -> bool {
2485 let Some(item) = self.pending_decrypt.get(room_id).and_then(|by_event| by_event.get(event_id)).cloned() else {
2486 return false;
2487 };
2488 match GroupSessionManager::decrypt_event(
2489 &mut self.store,
2490 room_id,
2491 event_id,
2492 item.origin_server_ts,
2493 &item.sender,
2494 &item.content,
2495 ) {
2496 Ok(plaintext) => {
2497 self.apply_decrypted_plaintext(room_id, event_id, &item.sender, item.origin_server_ts, &plaintext);
2498 }
2499 Err(GroupDecryptError::MissingSession { .. }) => {
2500 let _ = self.request_missing_room_key(room_id, &item.sender, &item.content);
2501 return false;
2502 }
2503 Err(_) => return false,
2504 }
2505 if let Some(by_event) = self.pending_decrypt.get_mut(room_id) {
2506 by_event.remove(event_id);
2507 }
2508 true
2509 }
2510
2511 pub fn pull_latest_page(&mut self, room_id: RoomId) -> Result<(), MessengerError> {
2514 let request_id = self.next_request_id()?;
2515 let request = OutgoingRequest::room_messages_latest(request_id.clone(), &room_id, 20);
2516 self.enqueue_request(request, Lane::Room(room_id.clone()))?;
2517 self.pending_room_messages.insert(request_id, room_id);
2518 Ok(())
2519 }
2520
2521 pub fn decrypt_loaded_timeline(&mut self) {
2525 let pending: Vec<(RoomId, Vec<EventId>)> = self
2526 .pending_decrypt
2527 .iter()
2528 .map(|(room_id, by_event)| (room_id.clone(), by_event.keys().cloned().collect()))
2529 .collect();
2530 for (room_id, event_ids) in pending {
2531 for event_id in event_ids {
2532 self.retry_decrypt_one(&room_id, &event_id);
2533 }
2534 }
2535 let rooms: Vec<RoomId> = self.timelines.keys().cloned().collect();
2536 for room_id in rooms {
2537 let sealed: Vec<(EventId, i64, UserId, MegolmEncryptedContent)> = {
2538 let Some(timeline) = self.timelines.get(&room_id) else {
2539 continue;
2540 };
2541 timeline
2542 .items()
2543 .iter()
2544 .filter_map(|item| {
2545 let event_id = item.event_id.clone()?;
2546 let (session_id, ciphertext) = match &item.content {
2547 ItemContent::Encrypted { session_id, ciphertext_b64 } => {
2548 (session_id.clone(), ciphertext_b64.clone())
2549 }
2550 _ => return None,
2551 };
2552 Some((
2553 event_id,
2554 item.origin_server_ts,
2555 item.sender.clone(),
2556 MegolmEncryptedContent {
2557 ciphertext,
2558 session_id,
2559 sender_key: None,
2560 device_id: None,
2561 relates_to: None,
2562 },
2563 ))
2564 })
2565 .collect()
2566 };
2567 for (event_id, origin_server_ts, sender, content) in sealed {
2568 let plaintext = match GroupSessionManager::decrypt_event(
2569 &mut self.store,
2570 &room_id,
2571 &event_id,
2572 origin_server_ts,
2573 &sender,
2574 &content,
2575 ) {
2576 Ok(plaintext) => plaintext,
2577 Err(GroupDecryptError::MissingSession { .. }) => {
2578 let _ = self.request_missing_room_key(&room_id, &sender, &content);
2579 continue;
2580 }
2581 Err(_) => continue,
2582 };
2583 self.apply_decrypted_plaintext(
2584 &room_id,
2585 &event_id,
2586 &sender,
2587 origin_server_ts,
2588 &plaintext,
2589 );
2590 }
2591 }
2592 }
2593
2594 fn apply_decrypted_plaintext(
2607 &mut self,
2608 room_id: &RoomId,
2609 event_id: &EventId,
2610 sender: &UserId,
2611 origin_server_ts: i64,
2612 plaintext: &RoomEventPlaintext,
2613 ) {
2614 let Some(timeline) = self.timelines.get_mut(room_id) else { return };
2615 if let Some(relation) = RelatesTo::from_content(&plaintext.content) {
2616 if matches!(relation, RelatesTo::Annotation { .. } | RelatesTo::Replace { .. }) {
2617 let new_content = plaintext.content.get("m.new_content").cloned();
2618 timeline.apply_decrypted_relation(event_id, sender.clone(), origin_server_ts, relation, new_content);
2619 return;
2620 }
2621 }
2622 timeline.set_decrypted_event(event_id, &plaintext.event_type, &plaintext.content);
2623 }
2624
2625 fn retry_pending_for_session(&mut self, room_id: &RoomId, session_id: &str) {
2626 let Some(by_event) = self.pending_decrypt.get(room_id) else { return };
2627 let candidates: Vec<EventId> =
2628 by_event.iter().filter(|(_, item)| item.session_id == session_id).map(|(event_id, _)| event_id.clone()).collect();
2629 let mut changed = false;
2630 for event_id in candidates {
2631 if self.retry_decrypt_one(room_id, &event_id) {
2632 changed = true;
2633 }
2634 }
2635 if changed {
2636 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
2637 }
2638 }
2639
2640 pub fn republish_device_keys(&mut self) {
2643 self.account.forget_published_device_keys();
2644 }
2645
2646 fn ingest_sync(&mut self, response: &crate::wire::sync::SyncResponse) -> Result<(), MessengerError> {
2647 let mut newly_received_sessions: Vec<(RoomId, String)> = Vec::new();
2648
2649 for event in &response.to_device.events {
2651 self.process_to_device_event(event, &mut newly_received_sessions)?;
2652 }
2653
2654 DeviceTracker::on_device_lists(&mut self.store, &response.device_lists.changed, &response.device_lists.left)?;
2656
2657 let published_otk_count = response.device_one_time_keys_count.get("signed_curve25519").copied().unwrap_or(0);
2659 self.account.on_sync_counts(&mut self.store, published_otk_count, &response.device_unused_fallback_key_types)?;
2660 let keys_upload_request_id = self.next_request_id()?;
2661 if let Some(request) =
2662 self.account.keys_upload_request(keys_upload_request_id, &self.config.user_id, &self.config.device_id)?
2663 {
2664 self.enqueue_request(request, Lane::Other)?;
2665 }
2666 self.maybe_enqueue_cross_signing()?;
2667
2668 let outdated_before_rooms = self.outdated_tracked_users()?;
2670 if !outdated_before_rooms.is_empty() {
2671 self.enqueue_keys_query()?;
2672 }
2673
2674 let rooms_touched =
2676 !response.rooms.join.is_empty() || !response.rooms.invite.is_empty() || !response.rooms.leave.is_empty();
2677 for (room_id, joined) in &response.rooms.join {
2678 self.ingest_joined_room(room_id, joined)?;
2679 }
2680 for (room_id, invited) in &response.rooms.invite {
2681 self.ingest_invited_room(room_id, invited)?;
2682 }
2683 for (room_id, left) in &response.rooms.leave {
2684 self.ingest_left_room(room_id, left)?;
2685 }
2686 if rooms_touched {
2687 self.emit(MessengerEvent::RoomsChanged);
2688 }
2689 if !self.outdated_tracked_users()?.is_subset(&outdated_before_rooms) {
2693 self.enqueue_keys_query()?;
2694 }
2695
2696 for event in &response.account_data.events {
2698 self.apply_synced_account_data(None, &event.event_type, event.content.clone());
2699 }
2700
2701 for (room_id, session_id) in &newly_received_sessions {
2702 self.retry_pending_for_session(room_id, session_id);
2703 }
2704
2705 self.store.save_sync_token(response.next_batch.clone())?;
2708 Ok(())
2709 }
2710
2711 fn process_to_device_event(
2712 &mut self,
2713 event: &ToDeviceEvent,
2714 newly_received_sessions: &mut Vec<(RoomId, String)>,
2715 ) -> Result<(), MessengerError> {
2716 if event.event_type != "m.room.encrypted" {
2717 return Ok(());
2718 }
2719 let Ok(RoomEncryptedContent::Olm(content)) = serde_json::from_value::<RoomEncryptedContent>(event.content.clone())
2720 else {
2721 return Ok(());
2722 };
2723 match OlmSessionManager::decrypt_to_device(&mut self.store, &mut self.account, &self.config.user_id, &event.sender, &content) {
2724 Ok(decrypted) => self.route_decrypted_to_device(decrypted, newly_received_sessions),
2725 Err(OlmDecryptError::UnknownSenderDevice { .. }) => {
2726 self.queue_unknown_sender_retry(event.clone());
2727 Ok(())
2728 }
2729 Err(_) => Ok(()),
2736 }
2737 }
2738
2739 fn route_decrypted_to_device(
2740 &mut self,
2741 decrypted: DecryptedToDevice,
2742 newly_received_sessions: &mut Vec<(RoomId, String)>,
2743 ) -> Result<(), MessengerError> {
2744 match decrypted.event_type.as_str() {
2745 "m.room_key" => {
2746 if let Ok(content) = serde_json::from_value::<RoomKeyContent>(decrypted.content.clone()) {
2747 if content.algorithm == "m.megolm.v1.aes-sha2" {
2748 GroupSessionManager::receive_room_key(
2749 &mut self.store,
2750 &decrypted.sender,
2751 decrypted.sender_device_curve25519,
2752 decrypted.sender_ed25519,
2753 &content,
2754 )?;
2755 newly_received_sessions.push((content.room_id, content.session_id));
2756 }
2757 }
2758 }
2759 "m.forwarded_room_key" => {
2760 if let Ok(content) = serde_json::from_value::<ForwardedRoomKeyContent>(decrypted.content.clone()) {
2761 if let Some((user_id, curve, ed)) = self.forward_binding(&decrypted, &content)? {
2762 let stored = GroupSessionManager::receive_forwarded_room_key(
2763 &mut self.store,
2764 &user_id,
2765 curve,
2766 ed,
2767 &content,
2768 )?;
2769 if stored {
2770 newly_received_sessions.push((content.room_id, content.session_id));
2771 }
2772 }
2773 }
2774 }
2775 "m.room_key_request" => {
2776 if let Ok(content) = serde_json::from_value::<RoomKeyRequestContent>(decrypted.content.clone()) {
2777 self.answer_room_key_request(&decrypted, &content)?;
2778 }
2779 }
2780 "m.room_key.withheld" => {
2781 if let Ok(content) = serde_json::from_value::<RoomKeyWithheldContent>(decrypted.content.clone()) {
2782 if let (Some(room_id), Some(session_id)) = (content.room_id, content.session_id) {
2783 self.withheld_sessions.insert((room_id, session_id));
2784 }
2785 }
2786 }
2787 _ => {}
2788 }
2789 Ok(())
2790 }
2791
2792 fn forward_binding(
2798 &self,
2799 decrypted: &DecryptedToDevice,
2800 content: &ForwardedRoomKeyContent,
2801 ) -> Result<Option<(UserId, mail4agent_vodozemac::Curve25519PublicKey, mail4agent_vodozemac::Ed25519PublicKey)>, MessengerError>
2802 {
2803 let members = self.encrypted_room_member_ids(&content.room_id);
2804 let forwarder_is_us = decrypted.sender == self.config.user_id
2807 || (self.history_forwarding_allowed(&content.room_id) && self.room_admins(&content.room_id).contains(&decrypted.sender));
2808 if !forwarder_is_us && !members.contains(&decrypted.sender) {
2809 return Ok(None);
2810 }
2811 for user_id in &members {
2812 for device in DeviceTracker::devices_for_user(&self.store, user_id)? {
2813 if device.blocked || device.curve25519.to_base64() != content.sender_key {
2814 continue;
2815 }
2816 let same_user = decrypted.sender == device.user_id;
2817 if same_user || forwarder_is_us {
2818 return Ok(Some((device.user_id, device.curve25519, device.ed25519)));
2819 }
2820 }
2821 }
2822 if decrypted.sender_device_curve25519.to_base64() == content.sender_key {
2823 return Ok(Some((
2824 decrypted.sender.clone(),
2825 decrypted.sender_device_curve25519,
2826 decrypted.sender_ed25519,
2827 )));
2828 }
2829 Ok(None)
2830 }
2831
2832 fn answer_room_key_request(
2837 &mut self,
2838 decrypted: &DecryptedToDevice,
2839 content: &RoomKeyRequestContent,
2840 ) -> Result<(), MessengerError> {
2841 if matches!(content.action, RoomKeyRequestAction::RequestCancellation) {
2842 self.key_requests_answered.remove(&(
2843 decrypted.sender.clone(),
2844 content.requesting_device_id.clone(),
2845 content.request_id.clone(),
2846 ));
2847 return Ok(());
2848 }
2849 if !matches!(content.action, RoomKeyRequestAction::Request) {
2850 return Ok(());
2851 }
2852 let Some(body) = content.body.as_ref() else { return Ok(()) };
2853 if body.algorithm != "m.megolm.v1.aes-sha2" {
2854 return Ok(());
2855 }
2856 let members = self.encrypted_room_member_ids(&body.room_id);
2857 if !members.contains(&decrypted.sender) {
2858 return Ok(());
2859 }
2860 if decrypted.sender != self.config.user_id && !self.history_forwarding_allowed(&body.room_id) {
2861 return Ok(());
2862 }
2863 if decrypted.sender == self.config.user_id && content.requesting_device_id == self.config.device_id {
2864 return Ok(());
2865 }
2866 let dedup = (decrypted.sender.clone(), content.requesting_device_id.clone(), content.request_id.clone());
2867 if self.key_requests_answered.contains(&dedup) {
2868 return Ok(());
2869 }
2870 let Some(target) = self.requester_device(decrypted, &content.requesting_device_id)? else {
2871 return Ok(());
2872 };
2873 let our_curve = self.account.identity_keys().curve25519.to_base64();
2874 let (inner_type, payload) =
2875 if let Some(forwarded) = GroupSessionManager::forwarded_room_key_content(
2876 &self.store,
2877 &body.room_id,
2878 &body.session_id,
2879 &our_curve,
2880 )? {
2881 ("m.forwarded_room_key", serde_json::to_value(&forwarded)?)
2882 } else if let Some(room_key) =
2883 GroupSessionManager::outbound_room_key_content(&self.store, &body.room_id, &body.session_id)?
2884 {
2885 ("m.room_key", serde_json::to_value(&room_key)?)
2886 } else {
2887 return Ok(());
2888 };
2889 let sent = self.enqueue_olm_to_devices(std::slice::from_ref(&target), inner_type, payload)?;
2890 if sent > 0 {
2891 self.key_requests_answered.insert(dedup);
2892 }
2893 Ok(())
2894 }
2895
2896 fn requester_device(
2900 &self,
2901 decrypted: &DecryptedToDevice,
2902 requesting_device_id: &DeviceId,
2903 ) -> Result<Option<StoredDevice>, MessengerError> {
2904 let devices = DeviceTracker::devices_for_user(&self.store, &decrypted.sender)?;
2905 if let Some(device) = devices.iter().find(|device| &device.device_id == requesting_device_id) {
2906 return Ok((!device.blocked).then(|| device.clone()));
2907 }
2908 let curve = decrypted.sender_device_curve25519.to_base64();
2909 if let Some(device) = devices.iter().find(|device| device.curve25519.to_base64() == curve) {
2910 return Ok((!device.blocked).then(|| device.clone()));
2911 }
2912 Ok(None)
2913 }
2914
2915 fn request_missing_room_key(
2920 &mut self,
2921 room_id: &RoomId,
2922 sender: &UserId,
2923 content: &MegolmEncryptedContent,
2924 ) -> Result<(), MessengerError> {
2925 let key = (room_id.clone(), content.session_id.clone());
2926 if self.key_requests_sent.contains(&key) || self.withheld_sessions.contains(&key) {
2927 return Ok(());
2928 }
2929 let sender_key = match content.sender_key.clone() {
2930 Some(sender_key) => sender_key,
2931 None => {
2932 let devices = DeviceTracker::devices_for_user(&self.store, sender)?;
2933 let found = content
2934 .device_id
2935 .as_ref()
2936 .and_then(|device_id| devices.iter().find(|device| &device.device_id == device_id))
2937 .or_else(|| devices.iter().find(|device| !device.blocked));
2938 match found {
2939 Some(device) => device.curve25519.to_base64(),
2940 None => return Ok(()),
2941 }
2942 }
2943 };
2944 let mut targets = DeviceTracker::devices_for_user(&self.store, sender)?;
2945 targets.extend(DeviceTracker::devices_for_user(&self.store, &self.config.user_id)?);
2946 if self.history_forwarding_allowed(room_id) {
2949 for admin in self.room_admins(room_id) {
2950 if &admin != sender && admin != self.config.user_id {
2951 targets.extend(DeviceTracker::devices_for_user(&self.store, &admin)?);
2952 }
2953 }
2954 }
2955 if targets.is_empty() {
2956 return Ok(());
2957 }
2958 let missing: Vec<StoredDevice> = OlmSessionManager::sessions_missing_for(&self.store, &targets)?
2962 .into_iter()
2963 .filter(|device| {
2964 !device.blocked
2965 && !(device.user_id == self.config.user_id && device.device_id == self.config.device_id)
2966 && !self.history_claim_tried.contains(&(device.user_id.clone(), device.device_id.clone()))
2967 })
2968 .cloned()
2969 .collect();
2970 if !missing.is_empty() {
2971 let claim_id = self.next_request_id()?;
2972 let refs: Vec<&StoredDevice> = missing.iter().collect();
2973 if let Some(request) = OlmSessionManager::keys_claim_request(claim_id.clone(), &refs) {
2974 for device in &missing {
2975 self.history_claim_tried.insert((device.user_id.clone(), device.device_id.clone()));
2976 }
2977 self.history_claims.insert(claim_id, missing);
2978 self.enqueue_request(request, Lane::Other)?;
2979 return Ok(());
2980 }
2981 }
2982 let request_id = self.next_txn_id()?;
2983 let body = RoomKeyRequestContent {
2984 action: RoomKeyRequestAction::Request,
2985 body: Some(RoomKeyRequestBody {
2986 algorithm: "m.megolm.v1.aes-sha2".to_string(),
2987 room_id: room_id.clone(),
2988 session_id: content.session_id.clone(),
2989 sender_key,
2990 }),
2991 request_id: request_id.as_str().to_string(),
2992 requesting_device_id: self.config.device_id.clone(),
2993 };
2994 let sent = self.enqueue_olm_to_devices(&targets, "m.room_key_request", serde_json::to_value(&body)?)?;
2995 if sent > 0 {
2996 self.key_requests_sent.insert(key);
2997 }
2998 Ok(())
2999 }
3000
3001 fn enqueue_olm_to_devices(
3006 &mut self,
3007 devices: &[StoredDevice],
3008 inner_type: &str,
3009 content: serde_json::Value,
3010 ) -> Result<usize, MessengerError> {
3011 let mut unique = Vec::new();
3012 for device in devices {
3013 if device.blocked || (device.user_id == self.config.user_id && device.device_id == self.config.device_id) {
3014 continue;
3015 }
3016 if unique.iter().any(|have: &StoredDevice| have.user_id == device.user_id && have.device_id == device.device_id)
3017 {
3018 continue;
3019 }
3020 unique.push(device.clone());
3021 }
3022 let missing = OlmSessionManager::sessions_missing_for(&self.store, &unique)?;
3023 let missing_ids: BTreeSet<(UserId, DeviceId)> =
3024 missing.into_iter().map(|device| (device.user_id.clone(), device.device_id.clone())).collect();
3025 let ready: Vec<StoredDevice> = unique
3026 .into_iter()
3027 .filter(|device| !missing_ids.contains(&(device.user_id.clone(), device.device_id.clone())))
3028 .collect();
3029 let mut sent = 0;
3030 for chunk in GroupSessionManager::chunk_recipients_for_send_to_device(&ready) {
3031 let body = GroupSessionManager::build_encrypted_to_device_body(
3032 &mut self.store,
3033 &self.account,
3034 &self.config.user_id,
3035 &self.config.device_id,
3036 chunk,
3037 inner_type,
3038 content.clone(),
3039 )?;
3040 let request_id = self.next_request_id()?;
3041 let share_txn = self.next_txn_id()?;
3042 let request = OutgoingRequest::send_to_device(request_id, ROOM_KEY_SHARE_EVENT_TYPE, &share_txn, body);
3043 self.enqueue_request(request, Lane::ToDevice)?;
3044 sent += chunk.len();
3045 }
3046 Ok(sent)
3047 }
3048
3049 fn queue_unknown_sender_retry(&mut self, event: ToDeviceEvent) {
3050 self.unknown_sender_retry.push_back(event);
3051 while self.unknown_sender_retry.len() > UNKNOWN_SENDER_RETRY_CAPACITY {
3052 self.unknown_sender_retry.pop_front();
3053 }
3054 }
3055
3056 fn retry_unknown_sender_queue(&mut self) -> Result<(), MessengerError> {
3057 let pending: Vec<ToDeviceEvent> = std::mem::take(&mut self.unknown_sender_retry).into_iter().collect();
3058 let mut newly_received_sessions = Vec::new();
3059 for event in pending {
3060 self.process_to_device_event(&event, &mut newly_received_sessions)?;
3061 }
3062 for (room_id, session_id) in &newly_received_sessions {
3063 self.retry_pending_for_session(room_id, session_id);
3064 }
3065 Ok(())
3066 }
3067
3068 fn outdated_tracked_users(&self) -> Result<BTreeSet<UserId>, MessengerError> {
3070 Ok(self.store.tracked_users()?.into_iter().filter_map(|(user_id, outdated)| outdated.then_some(user_id)).collect())
3071 }
3072
3073 fn enqueue_keys_query(&mut self) -> Result<(), MessengerError> {
3075 let request_id = self.next_request_id()?;
3076 if let Some(request) = DeviceTracker::keys_query_request(&self.store, request_id)? {
3077 self.enqueue_request(request, Lane::Other)?;
3078 }
3079 Ok(())
3080 }
3081
3082 fn retry_missing_room_keys(&mut self) {
3092 let work: Vec<(RoomId, EventId)> = self
3093 .pending_decrypt
3094 .iter()
3095 .flat_map(|(room, by_event)| by_event.keys().map(move |event| (room.clone(), event.clone())))
3096 .collect();
3097 for (room_id, event_id) in work {
3098 let _ = self.retry_decrypt_one(&room_id, &event_id);
3099 }
3100 }
3101
3102 fn room_admins(&self, room_id: &RoomId) -> BTreeSet<UserId> {
3107 let Some(state) = self.room_state(room_id) else { return BTreeSet::new() };
3108 self.encrypted_room_member_ids(room_id)
3109 .into_iter()
3110 .filter(|u| state.power_levels.users.get(u).copied().unwrap_or(state.power_levels.users_default) >= 100)
3111 .collect()
3112 }
3113
3114 fn history_forwarding_allowed(&self, room_id: &RoomId) -> bool {
3115 !matches!(
3116 self.rooms.get(room_id).and_then(|room| room.history_visibility.clone()),
3117 Some(crate::wire::events::HistoryVisibility::Joined) | Some(crate::wire::events::HistoryVisibility::Invited)
3118 )
3119 }
3120
3121 fn maybe_enqueue_cross_signing(&mut self) -> Result<(), MessengerError> {
3124 if self.xsign_inflight || self.xsign_attempts >= 4 {
3125 return Ok(());
3126 }
3127 if self.xsign.is_none() {
3128 self.xsign = Some(CrossSigningState::load_or_create(&mut self.store)?);
3129 }
3130 let Some(state) = self.xsign.clone() else { return Ok(()) };
3131 let user_id = self.config.user_id.clone();
3132 let request_id;
3133 let request = if !state.uploaded {
3134 request_id = self.next_request_id()?;
3135 OutgoingRequest::signing_keys_upload(request_id, state.upload_body(&user_id)?)
3136 } else if !state.device_signed {
3137 let device_id = self.config.device_id.clone();
3138 let device_keys = self.account.device_keys_json(&user_id, &device_id)?;
3139 request_id = self.next_request_id()?;
3140 OutgoingRequest::signatures_upload(request_id, state.device_signature_body(&user_id, &device_id, device_keys)?)
3141 } else {
3142 return Ok(());
3143 };
3144 self.xsign_attempts += 1;
3145 self.xsign_inflight = true;
3146 self.enqueue_request(request, Lane::Other)
3147 }
3148
3149 fn track_joined_members<'a>(&mut self, room_id: &RoomId, events: impl Iterator<Item = &'a RawEvent>) -> Result<(), MessengerError> {
3150 if self.rooms.get(room_id).is_none_or(|room| room.encryption.is_none()) {
3151 return Ok(());
3152 }
3153 let joined: Vec<UserId> = events
3154 .filter(|event| event.event_type == "m.room.member")
3155 .filter(|event| event.content.get("membership").and_then(serde_json::Value::as_str) == Some("join"))
3156 .filter_map(|event| event.state_key.as_deref().and_then(|state_key| UserId::parse(state_key).ok()))
3157 .filter(|user_id| *user_id != self.config.user_id)
3158 .collect();
3159 if joined.is_empty() {
3160 return Ok(());
3161 }
3162 DeviceTracker::on_device_lists(&mut self.store, &joined, &[])
3163 }
3164
3165 fn ingest_joined_room(&mut self, room_id: &RoomId, joined: &JoinedRoom) -> Result<(), MessengerError> {
3166 for event in joined.state.events.iter().chain(joined.timeline.events.iter()) {
3167 self.rooms.entry(room_id.clone()).or_default().apply_state_event(event)?;
3168 }
3169
3170 self.track_joined_members(room_id, joined.state.events.iter().chain(joined.timeline.events.iter()))?;
3171
3172 let unread_before = self.rooms.get(room_id).map(RoomState::unread_count);
3173 self.rooms.entry(room_id.clone()).or_default().apply_summary(&joined.summary, &joined.unread_notifications);
3174 let unread_after = self.rooms.get(room_id).map(RoomState::unread_count);
3175 if unread_before != unread_after {
3176 self.emit(MessengerEvent::UnreadChanged { room_id: room_id.clone() });
3177 }
3178
3179 for event in &joined.account_data.events {
3180 self.apply_synced_account_data(Some(room_id), &event.event_type, event.content.clone());
3181 }
3182
3183 if let Some(typing_event) = joined.ephemeral.events.iter().find(|event| event.event_type == "m.typing") {
3184 if let Ok(typing) = serde_json::from_value::<TypingContent>(typing_event.content.clone()) {
3185 self.typing.insert(room_id.clone(), typing.user_ids);
3186 self.emit(MessengerEvent::TypingChanged { room_id: room_id.clone() });
3187 }
3188 }
3189 if let Some(receipt_event) = joined.ephemeral.events.iter().find(|event| event.event_type == "m.receipt") {
3190 if let Ok(receipts) = serde_json::from_value::<ReceiptContent>(receipt_event.content.clone()) {
3191 self.receipts.insert(room_id.clone(), receipts);
3192 self.emit(MessengerEvent::ReceiptsChanged { room_id: room_id.clone() });
3193 }
3194 }
3195
3196 if !joined.timeline.events.is_empty() {
3197 self.timelines.entry(room_id.clone()).or_default().apply_timeline_batch(
3198 &joined.timeline.events,
3199 joined.timeline.limited,
3200 joined.timeline.prev_batch.clone(),
3201 );
3202 self.decrypt_new_timeline_events(room_id, &joined.timeline.events);
3203 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
3204 }
3205
3206 self.persist_room_state(room_id)
3207 }
3208
3209 fn ingest_invited_room(&mut self, room_id: &RoomId, invited: &InvitedRoom) -> Result<(), MessengerError> {
3210 for stripped in &invited.invite_state.events {
3211 let synthetic = stripped_state_to_raw_event(stripped);
3212 self.rooms.entry(room_id.clone()).or_default().apply_state_event(&synthetic)?;
3213 }
3214 self.persist_room_state(room_id)
3215 }
3216
3217 fn ingest_left_room(&mut self, room_id: &RoomId, left: &LeftRoom) -> Result<(), MessengerError> {
3218 for event in left.state.events.iter().chain(left.timeline.events.iter()) {
3219 self.rooms.entry(room_id.clone()).or_default().apply_state_event(event)?;
3220 }
3221 for event in &left.account_data.events {
3222 self.apply_synced_account_data(Some(room_id), &event.event_type, event.content.clone());
3223 }
3224 if !left.timeline.events.is_empty() {
3225 self.timelines.entry(room_id.clone()).or_default().apply_timeline_batch(
3226 &left.timeline.events,
3227 left.timeline.limited,
3228 left.timeline.prev_batch.clone(),
3229 );
3230 self.decrypt_new_timeline_events(room_id, &left.timeline.events);
3231 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
3232 }
3233 self.persist_room_state(room_id)
3234 }
3235
3236 fn persist_room_state(&mut self, room_id: &RoomId) -> Result<(), MessengerError> {
3237 let Some(state) = self.rooms.get(room_id) else { return Ok(()) };
3238 let bytes = serde_json::to_vec(state)
3239 .map_err(|source| MessengerError::Crypto(format!("encode room state for {room_id}: {source}")))?;
3240 self.store.save_room_state(room_id, bytes)?;
3241 Ok(())
3242 }
3243
3244 fn decrypt_new_timeline_events(&mut self, room_id: &RoomId, events: &[RawEvent]) {
3252 for event in events {
3253 if event.event_type != "m.room.encrypted" || event.state_key.is_some() || event.unsigned.redacted_because.is_some() {
3254 continue;
3255 }
3256 let Ok(RoomEncryptedContent::Megolm(content)) = serde_json::from_value::<RoomEncryptedContent>(event.content.clone())
3257 else {
3258 continue;
3259 };
3260 match GroupSessionManager::decrypt_event(
3261 &mut self.store,
3262 room_id,
3263 &event.event_id,
3264 event.origin_server_ts,
3265 &event.sender,
3266 &content,
3267 ) {
3268 Ok(plaintext) => {
3269 self.apply_decrypted_plaintext(room_id, &event.event_id, &event.sender, event.origin_server_ts, &plaintext);
3270 if let Some(by_event) = self.pending_decrypt.get_mut(room_id) {
3271 by_event.remove(&event.event_id);
3272 }
3273 }
3274 Err(err) => {
3275 if matches!(err, GroupDecryptError::MissingSession { .. }) {
3276 let _ = self.request_missing_room_key(room_id, &event.sender, &content);
3277 }
3278 let reason = format!("{:?}", err.utd_reason());
3287 if let Some(timeline) = self.timelines.get_mut(room_id) {
3288 timeline.set_decrypted(&event.event_id, ItemContent::Undecryptable { reason });
3289 }
3290 self.pending_decrypt.entry(room_id.clone()).or_default().insert(
3291 event.event_id.clone(),
3292 PendingDecryptItem {
3293 session_id: content.session_id.clone(),
3294 sender: event.sender.clone(),
3295 origin_server_ts: event.origin_server_ts,
3296 content,
3297 },
3298 );
3299 }
3300 }
3301 }
3302 }
3303
3304 fn ingest_room_messages_response(&mut self, room_id: &RoomId, body: &[u8]) -> Result<(), MessengerError> {
3305 let parsed: RoomMessagesResponseBody = serde_json::from_slice(body)?;
3306 let mut events = parsed.chunk;
3307 events.reverse();
3308 self.timelines.entry(room_id.clone()).or_default().prepend_back_page(&events, parsed.end);
3309 self.decrypt_new_timeline_events(room_id, &events);
3310 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
3311 self.persist_room_state(room_id)
3312 }
3313}
3314
3315impl MessengerCore<SealedRecordCodec> {
3316 pub fn open_sealed(
3327 records: impl IntoIterator<Item = SealedRecord>,
3328 secrets: CoreSecrets,
3329 config: CoreConfig,
3330 now_ms: i64,
3331 jitter: Box<dyn Jitter>,
3332 ) -> Result<Self, MessengerError> {
3333 let seal_key = secrets.store_seal_key.clone().ok_or_else(|| {
3334 MessengerError::Crypto(
3335 "MessengerCore::open_sealed requires CoreSecrets::store_seal_key"
3336 .to_string(),
3337 )
3338 })?;
3339 let codec = SealedRecordCodec::new(seal_key);
3340 Self::open(records, codec, config, secrets, now_ms, jitter)
3341 }
3342}
3343
3344fn stripped_placeholder_event_id() -> EventId {
3350 EventId::parse("$stripped-preview").expect("this literal is a well-formed, hardcoded $-prefixed opaque id")
3351}
3352
3353fn stripped_state_to_raw_event(stripped: &StrippedStateEvent) -> RawEvent {
3358 RawEvent {
3359 event_id: stripped_placeholder_event_id(),
3360 event_type: stripped.event_type.clone(),
3361 sender: stripped.sender.clone(),
3362 origin_server_ts: 0,
3363 state_key: Some(stripped.state_key.clone()),
3364 content: stripped.content.clone(),
3365 unsigned: Unsigned::default(),
3366 }
3367}
3368
3369fn forwardable_content(mut content: serde_json::Value, marker_key: &str, marker: serde_json::Value) -> serde_json::Value {
3376 if let Some(object) = content.as_object_mut() {
3377 for key in ["m.relates_to", "m.mentions", "m.new_content", KEY_FORWARDED, KEY_FORWARDED_FROM] {
3378 object.remove(key);
3379 }
3380 object.insert(marker_key.to_string(), marker);
3381 }
3382 content
3383}
3384
3385fn message_local_echo_content(message: &OutgoingMessage) -> ItemContent {
3392 let body = TextLikeMessageContent { body: message.body.clone(), format: None, formatted_body: None };
3393 match message.kind {
3394 MessageKind::Text => ItemContent::Text(body),
3395 MessageKind::Notice => ItemContent::Notice(body),
3396 MessageKind::Emote => ItemContent::Emote(body),
3397 }
3398}
3399
3400fn message_wire_content(message: &OutgoingMessage) -> serde_json::Value {
3409 let msgtype = message.kind.msgtype();
3410 let new_content = serde_json::json!({ "msgtype": msgtype, "body": message.body });
3411 let mut content = new_content.clone();
3412 if let Some(edit_of) = &message.edit_of {
3413 content["body"] = serde_json::Value::String(format!("* {}", message.body));
3414 content["m.new_content"] = new_content;
3415 content["m.relates_to"] = serde_json::json!({ "rel_type": "m.replace", "event_id": edit_of.as_str() });
3416 } else if let Some(reply_to) = &message.reply_to {
3417 content["m.relates_to"] = serde_json::json!({ "m.in_reply_to": { "event_id": reply_to.as_str() } });
3418 }
3419 content
3420}
3421
3422fn message_relates_to(message: &OutgoingMessage) -> Option<RelatesTo> {
3428 if let Some(edit_of) = &message.edit_of {
3429 Some(RelatesTo::Replace { event_id: edit_of.clone() })
3430 } else {
3431 message.reply_to.clone().map(|event_id| RelatesTo::InReplyTo(InReplyTo { event_id }))
3432 }
3433}
3434
3435#[cfg(test)]
3436mod tests {
3437 use super::*;
3438 use crate::room::timeline::Forwarded;
3439 use crate::store::InsecurePlainCodecForTests;
3440 use crate::wire::HttpMethod;
3441
3442 struct FixedJitter(f64);
3443
3444 impl Jitter for FixedJitter {
3445 fn next_unit(&mut self) -> f64 {
3446 self.0
3447 }
3448 }
3449
3450 fn device_id() -> DeviceId {
3451 DeviceId::parse("DEV1").expect("valid device id")
3452 }
3453
3454 fn user_id() -> UserId {
3455 UserId::parse("@alice:example.org").expect("valid user id")
3456 }
3457
3458 fn test_config() -> CoreConfig {
3459 CoreConfig { user_id: user_id(), device_id: device_id(), server_name: "example.org".to_string() }
3460 }
3461
3462 fn open_fresh_core() -> MessengerCore<InsecurePlainCodecForTests> {
3463 MessengerCore::open(Vec::new(), InsecurePlainCodecForTests, test_config(), CoreSecrets::default(), 0, Box::new(FixedJitter(0.0)))
3464 .expect("open succeeds on a brand-new device")
3465 }
3466
3467 fn flush_and_ack(core: &mut MessengerCore<InsecurePlainCodecForTests>) {
3475 while let Some(batch) = core.take_flush_batch() {
3476 core.ack_flush(batch.id);
3477 }
3478 }
3479
3480 #[test]
3481 fn counters_survive_restart_and_never_repeat_txn_ids() {
3482 let device = device_id();
3483 let mut store = Store::new(device.clone(), InsecurePlainCodecForTests);
3484 store.save_counters(Counters { next_request_id: 3, next_txn_id: 7 }).expect("save");
3485 let batch = store.take_flush_batch().expect("counters dirtied the store");
3486 store.ack_flush(batch.id);
3487
3488 let reloaded = Store::load(batch.records, InsecurePlainCodecForTests, device).expect("reload");
3489 let restored = reloaded.counters().expect("no error").expect("counters were persisted");
3490 assert_eq!(restored.next_txn_id, 7, "a restart resumes the txn-id sequence exactly where it left off");
3491 assert_eq!(restored.next_request_id, 3);
3492
3493 assert_eq!(restore_counter(3, Some(10)), 11, "resumes past the highest pending id, never repeating it");
3498 assert_eq!(restore_counter(3, None), 3, "nothing pending: the persisted value alone is authoritative");
3499 }
3500
3501 #[test]
3502 fn open_restores_the_request_counter_past_a_still_pending_requests_id() {
3503 let device = device_id();
3504 let mut store = Store::new(device.clone(), InsecurePlainCodecForTests);
3505 let mut queue = OutgoingQueue::new();
3506 let pending = OutgoingRequest::sync(RequestId::next(5), None, None);
3507 queue.enqueue(&mut store, pending, Lane::Sync).expect("enqueue");
3508 store.save_counters(Counters { next_request_id: 2, next_txn_id: 0 }).expect("save");
3511 let batch = store.take_flush_batch().expect("pending");
3512 store.ack_flush(batch.id);
3513
3514 let core = MessengerCore::open(
3515 batch.records,
3516 InsecurePlainCodecForTests,
3517 test_config(),
3518 CoreSecrets::default(),
3519 0,
3520 Box::new(FixedJitter(0.0)),
3521 )
3522 .expect("open succeeds");
3523 assert_eq!(
3524 core.counters.next_request_id, 6,
3525 "resumes past the highest id already in flight, not the stale persisted value"
3526 );
3527 }
3528
3529 #[test]
3530 fn sync_token_persisted_after_everything_it_covers() {
3531 let mut core = open_fresh_core();
3532 flush_and_ack(&mut core);
3533 core.releasable_requests(0);
3538 flush_and_ack(&mut core);
3539 let requests = core.releasable_requests(0);
3540 let sync_request = requests.iter().find(|r| r.kind == OutgoingRequestKind::Sync).expect("a sync request was enqueued");
3541
3542 assert!(core.store.sync_token().expect("no error").is_none(), "no token before the first response");
3543
3544 let body = serde_json::json!({
3545 "next_batch": "s1",
3546 "device_one_time_keys_count": { "signed_curve25519": 0 },
3547 "device_unused_fallback_key_types": []
3548 })
3549 .to_string();
3550 core.on_response(sync_request.id.clone(), HttpResponseDescriptor { status: 200, body: body.into_bytes() }, 0);
3551
3552 assert_eq!(core.store.sync_token().expect("no error"), Some("s1"));
3553 }
3554
3555 #[test]
3556 fn only_one_sync_in_flight() {
3557 let mut core = open_fresh_core();
3558 flush_and_ack(&mut core);
3559 core.releasable_requests(0);
3560 flush_and_ack(&mut core);
3561 let first = core.releasable_requests(0);
3562 assert_eq!(first.iter().filter(|r| r.kind == OutgoingRequestKind::Sync).count(), 1);
3563
3564 let second = core.releasable_requests(0);
3568 assert!(second.iter().all(|r| r.kind != OutgoingRequestKind::Sync), "no second sync request released");
3569 }
3570
3571 #[test]
3572 fn first_sync_of_a_session_never_long_polls_then_the_loop_does() {
3573 let mut core = open_fresh_core();
3574 flush_and_ack(&mut core);
3575 core.releasable_requests(0);
3576 flush_and_ack(&mut core);
3577 let first = core.releasable_requests(0);
3578 let first_sync = first.iter().find(|r| r.kind == OutgoingRequestKind::Sync).expect("a sync request was enqueued");
3579 assert!(first_sync.query.contains(&("timeout".to_string(), "0".to_string())), "the initial sync must answer at once");
3580 assert!(first_sync.query.iter().all(|(key, _)| key != "since"), "a fresh device has no since token");
3581
3582 let body = serde_json::json!({
3583 "next_batch": "s1",
3584 "device_one_time_keys_count": { "signed_curve25519": 0 },
3585 "device_unused_fallback_key_types": []
3586 })
3587 .to_string();
3588 core.on_response(first_sync.id.clone(), HttpResponseDescriptor { status: 200, body: body.into_bytes() }, 0);
3589 flush_and_ack(&mut core);
3590
3591 core.releasable_requests(0);
3594 flush_and_ack(&mut core);
3595 let second = core.releasable_requests(0);
3596 let second_sync = second.iter().find(|r| r.kind == OutgoingRequestKind::Sync).expect("the follow-up sync was enqueued");
3597 assert!(second_sync.query.contains(&("since".to_string(), "s1".to_string())));
3598 assert!(second_sync.query.contains(&("timeout".to_string(), SYNC_TIMEOUT_MS.to_string())), "once caught up the loop long-polls");
3599 }
3600
3601 #[test]
3602 fn room_key_arrival_retries_undecryptable_items() {
3603 use crate::crypto::group_sessions::GroupSessionManager;
3604 use mail4agent_vodozemac::megolm::{GroupSession, SessionConfig};
3605 use mail4agent_vodozemac::olm::Account;
3606
3607 let mut core = open_fresh_core();
3608 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3609 let sender = UserId::parse("@bob:example.org").expect("valid user id");
3610 let identity = Account::new().identity_keys();
3611
3612 let mut group_session = GroupSession::new(SessionConfig::version_1());
3613 let session_id = group_session.session_id();
3614 let session_key = group_session.session_key();
3619 let plaintext = crate::crypto::group_sessions::RoomEventPlaintext {
3620 event_type: "m.room.message".to_string(),
3621 content: serde_json::json!({ "msgtype": "m.text", "body": "hi" }),
3622 room_id: room_id.clone(),
3623 };
3624 let message = group_session.encrypt(serde_json::to_vec(&plaintext).expect("valid JSON"));
3625
3626 let event: RawEvent = serde_json::from_value(serde_json::json!({
3627 "event_id": "$evt:example.org",
3628 "type": "m.room.encrypted",
3629 "sender": sender.as_str(),
3630 "origin_server_ts": 1,
3631 "content": {
3632 "algorithm": "m.megolm.v1.aes-sha2",
3633 "ciphertext": message.to_base64(),
3634 "session_id": session_id,
3635 }
3636 }))
3637 .expect("valid event");
3638
3639 let mut timeline = Timeline::new();
3645 timeline.apply_timeline_batch(std::slice::from_ref(&event), false, None);
3646 core.timelines.insert(room_id.clone(), timeline);
3647 core.decrypt_new_timeline_events(&room_id, std::slice::from_ref(&event));
3648 {
3649 let timeline = core.timeline(&room_id).expect("timeline exists");
3650 let item = timeline.item_by_event_id(&EventId::parse("$evt:example.org").expect("valid event id")).expect("item present");
3651 assert!(matches!(item.content, ItemContent::Undecryptable { .. }));
3652 }
3653
3654 let content = RoomKeyContent {
3658 algorithm: "m.megolm.v1.aes-sha2".to_string(),
3659 room_id: room_id.clone(),
3660 session_id: session_id.clone(),
3661 session_key: session_key.to_base64(),
3662 };
3663 GroupSessionManager::receive_room_key(&mut core.store, &sender, identity.curve25519, identity.ed25519, &content)
3664 .expect("receive room key");
3665 core.retry_pending_for_session(&room_id, &session_id);
3666
3667 let timeline = core.timeline(&room_id).expect("timeline exists");
3668 let item = timeline.item_by_event_id(&EventId::parse("$evt:example.org").expect("valid event id")).expect("item present");
3669 match &item.content {
3670 ItemContent::Text(text) => assert_eq!(text.body, "hi"),
3671 other => panic!("expected the retried decrypt to succeed, got {other:?}"),
3672 }
3673 }
3674
3675 #[test]
3676 fn redelivered_to_device_after_restart_is_dropped_silently() {
3677 use crate::crypto::olm_sessions::OlmSessionManager;
3678
3679 let alice_user = UserId::parse("@alice:example.org").expect("valid user id");
3681 let alice_device = DeviceId::parse("ALICEDEV").expect("valid device id");
3682 let mut alice_store = Store::new(alice_device.clone(), InsecurePlainCodecForTests);
3683 let alice_account = OlmAccountState::load_or_create(&mut alice_store).expect("create alice's account");
3684 let alice_device_keys =
3687 alice_account.device_keys_json(&alice_user, &alice_device).expect("sign alice's device_keys");
3688
3689 let mut bob_store = Store::new(device_id(), InsecurePlainCodecForTests);
3691 let mut bob_account = OlmAccountState::load_or_create(&mut bob_store).expect("create bob's account");
3692 bob_account.on_sync_counts(&mut bob_store, 0, &["signed_curve25519".to_string()]).expect("top up bob's OTKs");
3693 let upload = bob_account
3694 .keys_upload_request(RequestId::next(0), &user_id(), &device_id())
3695 .expect("builds a request")
3696 .expect("something to upload");
3697 let one_time_keys = upload.body.expect("upload has a body");
3698 let one_time_keys = one_time_keys.get("one_time_keys").and_then(serde_json::Value::as_object).expect("OTKs present");
3699 let (key_id, key_value) = one_time_keys.iter().next().expect("at least one OTK");
3700 let claim_body = serde_json::to_vec(&serde_json::json!({
3701 "one_time_keys": { user_id().as_str(): { device_id().as_str(): { key_id: key_value } } }
3702 }))
3703 .expect("valid JSON");
3704 let bob_device_stored = crate::crypto::device_tracker::StoredDevice {
3705 user_id: user_id(),
3706 device_id: device_id(),
3707 curve25519: bob_account.identity_keys().curve25519,
3708 ed25519: bob_account.identity_keys().ed25519,
3709 algorithms: vec!["m.olm.v1.curve25519-aes-sha2".to_string(), "m.megolm.v1.aes-sha2".to_string()],
3710 display_name: None,
3711 verified: false,
3712 blocked: false,
3713 };
3714 OlmSessionManager::on_keys_claim_response(&mut alice_store, &alice_account, &[&bob_device_stored], &claim_body)
3715 .expect("alice establishes a session with bob");
3716
3717 DeviceTracker::on_keys_query_response(
3723 &mut bob_store,
3724 &serde_json::to_vec(&serde_json::json!({
3725 "device_keys": { alice_user.as_str(): { alice_device.as_str(): alice_device_keys } }
3726 }))
3727 .expect("valid JSON"),
3728 )
3729 .expect("bob learns alice's device");
3730
3731 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3732 let group_session = mail4agent_vodozemac::megolm::GroupSession::new(mail4agent_vodozemac::megolm::SessionConfig::version_1());
3733 let room_key_content = RoomKeyContent {
3734 algorithm: "m.megolm.v1.aes-sha2".to_string(),
3735 room_id,
3736 session_id: group_session.session_id(),
3737 session_key: group_session.session_key().to_base64(),
3738 };
3739 let encrypted = OlmSessionManager::encrypt_to_device(
3740 &mut alice_store,
3741 &alice_account,
3742 &alice_user,
3743 &alice_device,
3744 &bob_device_stored,
3745 "m.room_key",
3746 serde_json::to_value(&room_key_content).expect("valid JSON"),
3747 )
3748 .expect("encrypt to bob");
3749 let to_device_event = ToDeviceEvent {
3750 sender: alice_user.clone(),
3751 event_type: "m.room.encrypted".to_string(),
3752 content: serde_json::to_value(RoomEncryptedContent::Olm(encrypted)).expect("valid JSON"),
3753 };
3754
3755 let mut core =
3756 MessengerCore::open(Vec::new(), InsecurePlainCodecForTests, test_config(), CoreSecrets::default(), 0, Box::new(FixedJitter(0.0)))
3757 .expect("open succeeds");
3758 core.store = bob_store;
3759 core.account = bob_account;
3760
3761 let mut sessions = Vec::new();
3762 core.process_to_device_event(&to_device_event, &mut sessions).expect("first decrypt succeeds");
3763 assert_eq!(
3764 sessions,
3765 vec![(room_key_content.room_id.clone(), room_key_content.session_id.clone())],
3766 "the first, genuine delivery is fully validated and accepted"
3767 );
3768
3769 let batch = core.store.take_flush_batch().expect("decrypting dirtied the store");
3773 core.store.ack_flush(batch.id);
3774 let reloaded_store = Store::load(batch.records, InsecurePlainCodecForTests, device_id()).expect("reload succeeds");
3775 core.store = reloaded_store;
3776 core.account = OlmAccountState::load_or_create(&mut core.store).expect("reload reuses the persisted account");
3777
3778 let mut sessions_after_restart = Vec::new();
3782 let result = core.process_to_device_event(&to_device_event, &mut sessions_after_restart);
3783 assert!(result.is_ok(), "a redelivered to-device event is dropped, not surfaced as an ingestion error");
3784 assert!(sessions_after_restart.is_empty(), "the replay never produces a second accepted room key");
3785 }
3786
3787 #[test]
3788 fn unknown_sender_device_is_retried_after_keys_query() {
3789 use crate::crypto::olm_sessions::OlmSessionManager;
3790
3791 let alice_user = UserId::parse("@alice:example.org").expect("valid user id");
3793 let alice_device = DeviceId::parse("ALICEDEV").expect("valid device id");
3794 let mut alice_store = Store::new(alice_device.clone(), InsecurePlainCodecForTests);
3795 let alice_account = OlmAccountState::load_or_create(&mut alice_store).expect("create alice's account");
3796 let alice_device_keys =
3804 alice_account.device_keys_json(&alice_user, &alice_device).expect("sign alice's device_keys");
3805
3806 let bob_user = UserId::parse("@bob:example.org").expect("valid user id");
3814 let bob_device = DeviceId::parse("BOBDEV").expect("valid device id");
3815 let mut bob_store = Store::new(bob_device.clone(), InsecurePlainCodecForTests);
3816 let mut bob_account = OlmAccountState::load_or_create(&mut bob_store).expect("create bob's account");
3817 bob_account.on_sync_counts(&mut bob_store, 0, &["signed_curve25519".to_string()]).expect("top up bob's OTKs");
3818 let upload = bob_account
3819 .keys_upload_request(RequestId::next(0), &bob_user, &bob_device)
3820 .expect("builds a request")
3821 .expect("something to upload");
3822 let one_time_keys = upload.body.expect("upload has a body");
3823 let one_time_keys = one_time_keys.get("one_time_keys").and_then(serde_json::Value::as_object).expect("OTKs present");
3824 let (key_id, key_value) = one_time_keys.iter().next().expect("at least one OTK");
3825 let claim_body = serde_json::to_vec(&serde_json::json!({
3826 "one_time_keys": { bob_user.as_str(): { bob_device.as_str(): { key_id: key_value } } }
3827 }))
3828 .expect("valid JSON");
3829 let bob_device_stored = crate::crypto::device_tracker::StoredDevice {
3830 user_id: bob_user.clone(),
3831 device_id: bob_device.clone(),
3832 curve25519: bob_account.identity_keys().curve25519,
3833 ed25519: bob_account.identity_keys().ed25519,
3834 algorithms: vec!["m.olm.v1.curve25519-aes-sha2".to_string(), "m.megolm.v1.aes-sha2".to_string()],
3835 display_name: None,
3836 verified: false,
3837 blocked: false,
3838 };
3839 OlmSessionManager::on_keys_claim_response(&mut alice_store, &alice_account, &[&bob_device_stored], &claim_body)
3840 .expect("alice establishes a session with bob");
3841 let encrypted = OlmSessionManager::encrypt_to_device(
3842 &mut alice_store,
3843 &alice_account,
3844 &alice_user,
3845 &alice_device,
3846 &bob_device_stored,
3847 "m.dummy",
3848 serde_json::json!({}),
3849 )
3850 .expect("encrypt to bob");
3851 let to_device_event = ToDeviceEvent {
3852 sender: alice_user.clone(),
3853 event_type: "m.room.encrypted".to_string(),
3854 content: serde_json::to_value(RoomEncryptedContent::Olm(encrypted)).expect("valid JSON"),
3855 };
3856
3857 let bob_config = CoreConfig { user_id: bob_user.clone(), device_id: bob_device.clone(), server_name: "example.org".to_string() };
3858 let mut core =
3859 MessengerCore::open(Vec::new(), InsecurePlainCodecForTests, bob_config, CoreSecrets::default(), 0, Box::new(FixedJitter(0.0)))
3860 .expect("open succeeds");
3861 core.store = bob_store;
3862 core.account = bob_account;
3863
3864 let mut sessions = Vec::new();
3868 core.process_to_device_event(&to_device_event, &mut sessions).expect("queues for retry, does not error");
3869 assert_eq!(core.unknown_sender_retry.len(), 1, "queued for a retry after the next keys/query");
3870 let alice_curve = alice_account.identity_keys().curve25519.to_base64();
3871 assert!(
3872 core.store.olm_sessions_for_device(&alice_curve).expect("no error").is_empty(),
3873 "the refused attempt created no Olm session (it would have consumed the one-time key)"
3874 );
3875
3876 let query_body = serde_json::to_vec(&serde_json::json!({
3881 "device_keys": { alice_user.as_str(): { alice_device.as_str(): alice_device_keys } }
3882 }))
3883 .expect("valid JSON");
3884 DeviceTracker::on_keys_query_response(&mut core.store, &query_body).expect("bob learns alice's device");
3885 core.retry_unknown_sender_queue().expect("no error");
3886 assert!(core.unknown_sender_retry.is_empty(), "the queued entry is drained by the retry attempt");
3887 assert_eq!(
3888 core.store.olm_sessions_for_device(&alice_curve).expect("no error").len(),
3889 1,
3890 "the retry decrypted the very same pre-key message and established the inbound session"
3891 );
3892 }
3893
3894 #[test]
3895 fn dispatch_load_older_produces_an_outgoing_request() {
3896 let mut core = open_fresh_core();
3897 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3898 let mut timeline = Timeline::new();
3899 let event: RawEvent = serde_json::from_value(serde_json::json!({
3900 "event_id": "$1:example.org",
3901 "type": "m.room.message",
3902 "sender": "@alice:example.org",
3903 "origin_server_ts": 1,
3904 "content": { "msgtype": "m.text", "body": "hi" }
3905 }))
3906 .expect("valid event");
3907 timeline.apply_timeline_batch(&[event], true, Some("t1".to_string()));
3908 core.timelines.insert(room_id.clone(), timeline);
3909 flush_and_ack(&mut core);
3910
3911 core.dispatch(MessengerCommand::LoadOlder { room_id: room_id.clone() }, 0).expect("dispatch succeeds");
3912 flush_and_ack(&mut core);
3913 let requests = core.releasable_requests(0);
3914 let load_older = requests.iter().find(|r| r.kind == OutgoingRequestKind::RoomMessages);
3915 assert!(load_older.is_some(), "a RoomMessages request was enqueued using the room's own gap token");
3916 }
3917
3918 #[test]
3919 fn load_older_on_a_resumed_room_pages_back_from_the_sync_token() {
3920 let mut core = open_fresh_core();
3921 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3922 core.store.save_sync_token("s42_7".to_string()).expect("save the sync token");
3923 flush_and_ack(&mut core);
3924
3925 core.dispatch(MessengerCommand::LoadOlder { room_id: room_id.clone() }, 0).expect("dispatch succeeds");
3927 flush_and_ack(&mut core);
3928 assert!(core.releasable_requests(0).iter().all(|r| r.kind != OutgoingRequestKind::RoomMessages));
3929
3930 core.rooms.insert(room_id.clone(), RoomState::default());
3932 core.dispatch(MessengerCommand::LoadOlder { room_id: room_id.clone() }, 0).expect("dispatch succeeds");
3933 flush_and_ack(&mut core);
3934 let requests = core.releasable_requests(0);
3935 let load_older = requests.iter().find(|r| r.kind == OutgoingRequestKind::RoomMessages).expect("a RoomMessages request was enqueued");
3936 assert!(load_older.query.contains(&("from".to_string(), "s42_7".to_string())), "pages back from the sync token: {:?}", load_older.query);
3937 }
3938
3939 fn bob_joins_room_body(encrypted: bool) -> serde_json::Value {
3942 let mut state = Vec::new();
3943 if encrypted {
3944 state.push(serde_json::json!({
3945 "event_id": "$enc:example.org", "type": "m.room.encryption", "sender": "@alice:example.org",
3946 "origin_server_ts": 1, "state_key": "", "content": { "algorithm": "m.megolm.v1.aes-sha2" }
3947 }));
3948 }
3949 serde_json::json!({
3950 "next_batch": "s1",
3951 "rooms": { "join": { "!r:example.org": {
3952 "state": { "events": state },
3953 "timeline": { "events": [{
3954 "event_id": "$join:example.org", "type": "m.room.member", "sender": "@bob:example.org",
3955 "origin_server_ts": 2, "state_key": "@bob:example.org", "content": { "membership": "join" }
3956 }] }
3957 } } }
3958 })
3959 }
3960
3961 #[test]
3962 fn a_member_joining_an_encrypted_room_is_tracked_and_queried_right_away() {
3963 let mut core = open_fresh_core();
3964 let bob = UserId::parse("@bob:example.org").expect("valid user id");
3965 deliver_sync(&mut core, bob_joins_room_body(true));
3966 let tracked = core.store.tracked_users().expect("no error");
3967 assert!(tracked.iter().any(|(user_id, outdated)| *user_id == bob && *outdated), "bob is tracked with an outdated device list: {tracked:?}");
3968
3969 flush_and_ack(&mut core);
3970 let requests = core.releasable_requests(0);
3971 let query = requests.iter().find(|r| r.kind == OutgoingRequestKind::KeysQuery).expect("a /keys/query was enqueued for the new member");
3972 assert!(query.body.as_ref().is_some_and(|body| body["device_keys"].get(bob.as_str()).is_some()), "the query names bob: {:?}", query.body);
3973 }
3974
3975 #[test]
3976 fn a_member_joining_a_plaintext_room_is_not_tracked() {
3977 let mut core = open_fresh_core();
3978 deliver_sync(&mut core, bob_joins_room_body(false));
3979 assert!(core.store.tracked_users().expect("no error").is_empty());
3980 flush_and_ack(&mut core);
3981 assert!(core.releasable_requests(0).iter().all(|r| r.kind != OutgoingRequestKind::KeysQuery));
3982 }
3983
3984 #[test]
3985 fn dispatch_mark_read_and_set_typing_produce_outgoing_requests() {
3986 let mut core = open_fresh_core();
3987 flush_and_ack(&mut core);
3988 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3989 let event_id = EventId::parse("$1:example.org").expect("valid event id");
3990
3991 core.dispatch(MessengerCommand::MarkRead { room_id: room_id.clone(), event_id }, 0).expect("dispatch succeeds");
3992 core.dispatch(MessengerCommand::SetTyping { room_id: room_id.clone(), typing: true }, 0).expect("dispatch succeeds");
3993 flush_and_ack(&mut core);
3994
3995 let requests = core.releasable_requests(0);
3996 assert!(requests.iter().any(|r| r.kind == OutgoingRequestKind::ReadMarkers));
3997 assert!(requests.iter().any(|r| r.kind == OutgoingRequestKind::Typing));
3998 }
3999
4000 #[test]
4001 fn snapshot_reflects_the_latest_applied_sync() {
4002 let mut core = open_fresh_core();
4003 flush_and_ack(&mut core);
4004 core.releasable_requests(0);
4005 flush_and_ack(&mut core);
4006 let requests = core.releasable_requests(0);
4007 let sync_request = requests.iter().find(|r| r.kind == OutgoingRequestKind::Sync).expect("sync enqueued");
4008
4009 let body = serde_json::json!({
4010 "next_batch": "s1",
4011 "rooms": {
4012 "join": {
4013 "!r:example.org": {
4014 "state": { "events": [] },
4015 "timeline": {
4016 "events": [
4017 {
4018 "event_id": "$1:example.org",
4019 "type": "m.room.message",
4020 "sender": "@alice:example.org",
4021 "origin_server_ts": 1,
4022 "content": { "msgtype": "m.text", "body": "hi" }
4023 }
4024 ]
4025 },
4026 "unread_notifications": { "notification_count": 1, "highlight_count": 0 }
4027 }
4028 }
4029 }
4030 })
4031 .to_string();
4032 let events = core.on_response(sync_request.id.clone(), HttpResponseDescriptor { status: 200, body: body.into_bytes() }, 0);
4033
4034 assert!(events.contains(&MessengerEvent::RoomsChanged));
4035 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4036 assert!(events.contains(&MessengerEvent::TimelineChanged { room_id: room_id.clone() }));
4037 assert!(events.contains(&MessengerEvent::UnreadChanged { room_id: room_id.clone() }));
4038
4039 let timeline = core.timeline(&room_id).expect("timeline present");
4040 assert_eq!(timeline.items().len(), 1);
4041 assert_eq!(core.room_state(&room_id).expect("room present").unread_count(), 1);
4042 assert_eq!(core.change_counter(), events.len() as u64);
4043 assert!(core.events().is_empty());
4046 }
4047
4048 #[test]
4049 fn dispatch_set_account_data_updates_optimistically_and_sends_a_put() {
4050 let mut core = open_fresh_core();
4051 flush_and_ack(&mut core);
4052 let content = serde_json::json!({ "note": "keep" });
4053 core.dispatch(
4054 MessengerCommand::SetAccountData { event_type: "com.example.prefs".to_string(), content: content.clone() },
4055 0,
4056 )
4057 .expect("dispatch succeeds");
4058
4059 assert_eq!(
4060 core.global_account_data("com.example.prefs"),
4061 Some(&content),
4062 "SetAccountData updates the core's own cache before any round trip"
4063 );
4064
4065 flush_and_ack(&mut core);
4066 let requests = core.releasable_requests(0);
4067 let request = requests.iter().find(|r| r.kind == OutgoingRequestKind::AccountData).expect("an AccountData request was enqueued");
4068 assert_eq!(request.method, HttpMethod::Put);
4069 assert_eq!(request.path, "/_matrix/client/v3/user/%40alice%3Aexample.org/account_data/com.example.prefs");
4070 assert_eq!(request.body.as_ref(), Some(&content));
4071 }
4072
4073 #[test]
4074 fn dispatch_set_room_account_data_updates_optimistically_and_sends_a_put() {
4075 let mut core = open_fresh_core();
4076 flush_and_ack(&mut core);
4077 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4078 let content = serde_json::json!({ "flag": true });
4079 core.dispatch(
4080 MessengerCommand::SetRoomAccountData {
4081 room_id: room_id.clone(),
4082 event_type: "com.example.flag".to_string(),
4083 content: content.clone(),
4084 },
4085 0,
4086 )
4087 .expect("dispatch succeeds");
4088
4089 assert_eq!(
4090 core.room_account_data(&room_id, "com.example.flag"),
4091 Some(&content),
4092 "SetRoomAccountData updates the core's own cache before any round trip"
4093 );
4094
4095 flush_and_ack(&mut core);
4096 let requests = core.releasable_requests(0);
4097 let request =
4098 requests.iter().find(|r| r.kind == OutgoingRequestKind::RoomAccountData).expect("a RoomAccountData request was enqueued");
4099 assert_eq!(request.method, HttpMethod::Put);
4100 assert_eq!(
4101 request.path,
4102 "/_matrix/client/v3/user/%40alice%3Aexample.org/rooms/%21r%3Aexample.org/account_data/com.example.flag"
4103 );
4104 assert_eq!(request.body.as_ref(), Some(&content));
4105 }
4106
4107 #[test]
4108 fn dispatch_search_public_rooms_sends_the_expected_request() {
4109 let mut core = open_fresh_core();
4110 flush_and_ack(&mut core);
4111 core.dispatch(MessengerCommand::SearchPublicRooms { term: "trading".to_string() }, 0).expect("dispatch succeeds");
4112 flush_and_ack(&mut core);
4113 let requests = core.releasable_requests(0);
4114 let request = requests.iter().find(|r| r.kind == OutgoingRequestKind::PublicRooms).expect("a PublicRooms request was enqueued");
4115 assert_eq!(request.method, HttpMethod::Post);
4116 assert_eq!(request.path, "/_matrix/client/v3/publicRooms");
4117 assert_eq!(request.body, Some(serde_json::json!({ "filter": { "generic_search_term": "trading" }, "limit": 50 })));
4118 }
4119
4120 #[test]
4121 fn dispatch_search_users_sends_the_expected_request() {
4122 let mut core = open_fresh_core();
4123 flush_and_ack(&mut core);
4124 core.dispatch(MessengerCommand::SearchUsers { term: "bob".to_string() }, 0).expect("dispatch succeeds");
4125 flush_and_ack(&mut core);
4126 let requests = core.releasable_requests(0);
4127 let request =
4128 requests.iter().find(|r| r.kind == OutgoingRequestKind::UserDirectorySearch).expect("a UserDirectorySearch request was enqueued");
4129 assert_eq!(request.method, HttpMethod::Post);
4130 assert_eq!(request.path, "/_matrix/client/v3/user_directory/search");
4131 assert_eq!(request.body, Some(serde_json::json!({ "search_term": "bob", "limit": 20 })));
4132 }
4133
4134 #[test]
4135 fn public_rooms_response_superseded_by_a_later_search_is_dropped() {
4136 let mut core = open_fresh_core();
4137 flush_and_ack(&mut core);
4138 core.dispatch(MessengerCommand::SearchPublicRooms { term: "first".to_string() }, 0).expect("dispatch succeeds");
4139 flush_and_ack(&mut core);
4140 let first_request = core
4141 .releasable_requests(0)
4142 .into_iter()
4143 .find(|r| r.kind == OutgoingRequestKind::PublicRooms)
4144 .expect("the first search's own request");
4145
4146 core.dispatch(MessengerCommand::SearchPublicRooms { term: "second".to_string() }, 0).expect("dispatch succeeds");
4149 flush_and_ack(&mut core);
4150 let second_request = core
4151 .releasable_requests(0)
4152 .into_iter()
4153 .find(|r| r.kind == OutgoingRequestKind::PublicRooms)
4154 .expect("the second search's own request");
4155
4156 let stale_body = serde_json::json!({ "chunk": [{ "room_id": "!stale:example.org", "num_joined_members": 1 }] });
4158 core.on_response(first_request.id, HttpResponseDescriptor { status: 200, body: serde_json::to_vec(&stale_body).expect("json") }, 0);
4159 assert!(core.public_rooms_result().is_empty(), "a response to a superseded search is dropped");
4160
4161 let fresh_body = serde_json::json!({ "chunk": [{ "room_id": "!fresh:example.org", "num_joined_members": 2 }] });
4163 core.on_response(second_request.id, HttpResponseDescriptor { status: 200, body: serde_json::to_vec(&fresh_body).expect("json") }, 0);
4164 assert_eq!(core.public_rooms_result().len(), 1);
4165 assert_eq!(core.public_rooms_result()[0].room_id, RoomId::parse("!fresh:example.org").expect("valid room id"));
4166 }
4167
4168 fn is_account_data_write(request: &OutgoingRequest) -> bool {
4169 matches!(request.kind, OutgoingRequestKind::AccountData | OutgoingRequestKind::RoomAccountData)
4170 }
4171
4172 #[test]
4173 fn pin_then_archive_back_to_back_keeps_both_tags() {
4174 let mut core = open_fresh_core();
4175 flush_and_ack(&mut core);
4176 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4177 for tag in ["m.favourite", "u.example.archived"] {
4178 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: tag.to_string(), order: None }, 0)
4179 .expect("dispatch succeeds");
4180 }
4181
4182 let cached = core.room_account_data(&room_id, "m.tag").expect("m.tag cached");
4184 assert!(cached["tags"].get("m.favourite").is_some());
4185 assert!(cached["tags"].get("u.example.archived").is_some());
4186
4187 flush_and_ack(&mut core);
4188 let first_release = core.releasable_requests(0);
4189 let first_writes: Vec<&OutgoingRequest> = first_release.iter().filter(|r| is_account_data_write(r)).collect();
4190 assert_eq!(first_writes.len(), 1, "the second PUT is not released while the first is in flight");
4191 let first_body = first_writes[0].body.clone().expect("a body");
4192 assert!(first_body["tags"].get("m.favourite").is_some());
4193 assert!(first_body["tags"].get("u.example.archived").is_none(), "the first PUT carries only what existed when it was built");
4194
4195 core.on_response(first_writes[0].id.clone(), HttpResponseDescriptor { status: 200, body: b"{}".to_vec() }, 0);
4196 flush_and_ack(&mut core);
4197 let second_release = core.releasable_requests(0);
4198 let second = second_release.iter().find(|r| is_account_data_write(r)).expect("the second PUT is released after the first completed");
4199 let second_body = second.body.clone().expect("a body");
4200 assert!(second_body["tags"].get("m.favourite").is_some(), "the later PUT still carries the earlier tag");
4201 assert!(second_body["tags"].get("u.example.archived").is_some());
4202 }
4203
4204 #[test]
4205 fn account_data_lane_is_fifo_single_flight() {
4206 let mut core = open_fresh_core();
4207 flush_and_ack(&mut core);
4208 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4209 core.dispatch(MessengerCommand::SetAccountData { event_type: "org.t.one".to_string(), content: serde_json::json!({}) }, 0)
4210 .expect("dispatch succeeds");
4211 core.dispatch(
4212 MessengerCommand::SetRoomAccountData {
4213 room_id: room_id.clone(),
4214 event_type: "org.t.two".to_string(),
4215 content: serde_json::json!({}),
4216 },
4217 0,
4218 )
4219 .expect("dispatch succeeds");
4220 core.dispatch(MessengerCommand::SetAccountData { event_type: "org.t.three".to_string(), content: serde_json::json!({}) }, 0)
4221 .expect("dispatch succeeds");
4222 core.dispatch(MessengerCommand::RemoveTag { room_id: room_id.clone(), tag: "m.favourite".to_string() }, 0)
4223 .expect("dispatch succeeds");
4224 flush_and_ack(&mut core);
4225
4226 let mut released_paths = Vec::new();
4227 for _ in 0..4 {
4228 let release = core.releasable_requests(0);
4229 let writes: Vec<&OutgoingRequest> = release.iter().filter(|r| is_account_data_write(r)).collect();
4230 assert_eq!(writes.len(), 1, "exactly one account-data write in flight at a time");
4231 released_paths.push(writes[0].path.clone());
4232 core.on_response(writes[0].id.clone(), HttpResponseDescriptor { status: 200, body: b"{}".to_vec() }, 0);
4233 flush_and_ack(&mut core);
4234 }
4235 assert!(released_paths[0].ends_with("/account_data/org.t.one"));
4236 assert!(released_paths[1].ends_with("/account_data/org.t.two"));
4237 assert!(released_paths[2].ends_with("/account_data/org.t.three"));
4238 assert!(released_paths[3].ends_with("/account_data/m.tag"));
4239 assert!(core.releasable_requests(0).iter().all(|r| !is_account_data_write(r)), "the lane is drained");
4240 }
4241
4242 fn deliver_sync(core: &mut MessengerCore<InsecurePlainCodecForTests>, body: serde_json::Value) -> Vec<OutgoingRequest> {
4247 let mut others = Vec::new();
4248 for _ in 0..3 {
4249 flush_and_ack(core);
4250 let mut sync_request = None;
4251 for request in core.releasable_requests(0) {
4252 if request.kind == OutgoingRequestKind::Sync && sync_request.is_none() {
4253 sync_request = Some(request);
4254 } else {
4255 others.push(request);
4256 }
4257 }
4258 if let Some(sync_request) = sync_request {
4259 let body = serde_json::to_vec(&body).expect("valid JSON");
4260 core.on_response(sync_request.id, HttpResponseDescriptor { status: 200, body }, 0);
4261 return others;
4262 }
4263 }
4264 panic!("no sync request became releasable");
4265 }
4266
4267 fn tag_sync_body(room_id: &str, tags: serde_json::Value, next_batch: &str) -> serde_json::Value {
4268 let mut join = serde_json::Map::new();
4269 join.insert(
4270 room_id.to_string(),
4271 serde_json::json!({ "account_data": { "events": [{ "type": "m.tag", "content": { "tags": tags } }] } }),
4272 );
4273 serde_json::json!({ "next_batch": next_batch, "rooms": { "join": serde_json::Value::Object(join) } })
4274 }
4275
4276 fn ok_response() -> HttpResponseDescriptor {
4277 HttpResponseDescriptor { status: 200, body: b"{}".to_vec() }
4278 }
4279
4280 #[test]
4281 fn sync_echo_of_an_older_tag_write_does_not_clobber_a_newer_optimistic_value() {
4282 let mut core = open_fresh_core();
4283 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4284 for tag in ["m.favourite", "u.example.archived"] {
4285 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: tag.to_string(), order: None }, 0)
4286 .expect("dispatch succeeds");
4287 }
4288
4289 let released = deliver_sync(&mut core, tag_sync_body("!r:example.org", serde_json::json!({ "m.favourite": {} }), "s1"));
4291 let cached = core.room_account_data(&room_id, "m.tag").expect("m.tag cached");
4292 assert!(cached["tags"].get("u.example.archived").is_some(), "the older echo must not clobber the newer optimistic value");
4293 assert!(cached["tags"].get("m.favourite").is_some());
4294 let key = (Some(room_id.clone()), "m.tag".to_string());
4295 assert_eq!(
4296 core.account_data_guard.get(&key).expect("writes are outstanding").server_value,
4297 Some(serde_json::json!({ "tags": { "m.favourite": {} } })),
4298 "the echo is remembered as the server's value"
4299 );
4300
4301 let first = released.iter().find(|r| r.kind == OutgoingRequestKind::RoomAccountData).expect("first write released");
4304 core.on_response(first.id.clone(), ok_response(), 0);
4305 flush_and_ack(&mut core);
4306 let second = core
4307 .releasable_requests(0)
4308 .into_iter()
4309 .find(|r| r.kind == OutgoingRequestKind::RoomAccountData)
4310 .expect("second write released after the first");
4311 core.on_response(second.id, ok_response(), 0);
4312 assert!(core.account_data_guard.is_empty());
4313 let cached = core.room_account_data(&room_id, "m.tag").expect("m.tag cached");
4314 assert!(cached["tags"].get("m.favourite").is_some() && cached["tags"].get("u.example.archived").is_some());
4315
4316 deliver_sync(
4318 &mut core,
4319 tag_sync_body("!r:example.org", serde_json::json!({ "m.favourite": {}, "u.example.archived": {} }), "s2"),
4320 );
4321 let cached = core.room_account_data(&room_id, "m.tag").expect("m.tag cached");
4322 assert!(cached["tags"].get("u.example.archived").is_some());
4323 }
4324
4325 #[test]
4326 fn terminal_account_data_failure_restores_the_server_value() {
4327 let mut core = open_fresh_core();
4328 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4329 deliver_sync(&mut core, tag_sync_body("!r:example.org", serde_json::json!({ "m.favourite": {} }), "s1"));
4330
4331 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: "u.example.archived".to_string(), order: None }, 0)
4332 .expect("dispatch succeeds");
4333 assert!(core.room_account_data(&room_id, "m.tag").expect("cached")["tags"].get("u.example.archived").is_some());
4334 flush_and_ack(&mut core);
4335 let write = core
4336 .releasable_requests(0)
4337 .into_iter()
4338 .find(|r| r.kind == OutgoingRequestKind::RoomAccountData)
4339 .expect("the write is released");
4340
4341 let counter_before = core.change_counter();
4342 let forbidden =
4343 HttpResponseDescriptor { status: 403, body: br#"{"errcode":"M_FORBIDDEN","error":"nope"}"#.to_vec() };
4344 let events = core.on_response(write.id, forbidden, 0);
4345 assert!(core.account_data_guard.is_empty());
4346 let restored = core.room_account_data(&room_id, "m.tag").expect("the server's value is back");
4347 assert!(restored["tags"].get("u.example.archived").is_none(), "the failed write's optimistic value is gone");
4348 assert!(restored["tags"].get("m.favourite").is_some());
4349 assert!(events.contains(&MessengerEvent::RoomsChanged));
4350 assert!(core.change_counter() > counter_before, "the UI is told to re-render");
4351
4352 for tag in ["u.a", "u.b"] {
4356 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: tag.to_string(), order: None }, 0)
4357 .expect("dispatch succeeds");
4358 }
4359 flush_and_ack(&mut core);
4360 let first = core
4361 .releasable_requests(0)
4362 .into_iter()
4363 .find(|r| r.kind == OutgoingRequestKind::RoomAccountData)
4364 .expect("first write released");
4365 let forbidden =
4366 HttpResponseDescriptor { status: 403, body: br#"{"errcode":"M_FORBIDDEN","error":"nope"}"#.to_vec() };
4367 core.on_response(first.id, forbidden, 0);
4368 let cached = core.room_account_data(&room_id, "m.tag").expect("cached");
4369 assert!(cached["tags"].get("u.a").is_some() && cached["tags"].get("u.b").is_some(), "cache untouched while a write is pending");
4370 flush_and_ack(&mut core);
4371 let second = core
4372 .releasable_requests(0)
4373 .into_iter()
4374 .find(|r| r.kind == OutgoingRequestKind::RoomAccountData)
4375 .expect("second write released");
4376 core.on_response(second.id, ok_response(), 0);
4377 let cached = core.room_account_data(&room_id, "m.tag").expect("cached");
4378 assert!(cached["tags"].get("u.b").is_some(), "the last write succeeded: its optimistic value stands");
4379 }
4380
4381 #[test]
4382 fn pending_account_data_writes_rebuild_their_guard_after_a_restart() {
4383 let mut core = open_fresh_core();
4384 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4385 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: "m.favourite".to_string(), order: None }, 0)
4386 .expect("dispatch succeeds");
4387 core.dispatch(
4388 MessengerCommand::SetAccountData { event_type: "org.t.one".to_string(), content: serde_json::json!({ "n": 1 }) },
4389 0,
4390 )
4391 .expect("dispatch succeeds");
4392
4393 let mut records = Vec::new();
4395 while let Some(batch) = core.take_flush_batch() {
4396 records.extend(batch.records.clone());
4397 core.ack_flush(batch.id);
4398 }
4399 let reopened = MessengerCore::open(
4400 records,
4401 InsecurePlainCodecForTests,
4402 test_config(),
4403 CoreSecrets::default(),
4404 0,
4405 Box::new(FixedJitter(0.0)),
4406 )
4407 .expect("reopen succeeds");
4408
4409 assert_eq!(reopened.account_data_guard.len(), 2, "one guard per key with a pending write");
4410 assert!(reopened.account_data_guard.values().all(|guard| guard.pending == 1 && guard.server_value.is_none()));
4411 assert_eq!(reopened.global_account_data("org.t.one"), Some(&serde_json::json!({ "n": 1 })));
4412 assert!(reopened.room_account_data(&room_id, "m.tag").expect("cached")["tags"].get("m.favourite").is_some());
4413 }
4414
4415 type TestCore = MessengerCore<InsecurePlainCodecForTests>;
4420
4421 fn room(id: &str) -> RoomId {
4422 RoomId::parse(id).expect("valid room id")
4423 }
4424
4425 fn event(id: &str) -> EventId {
4426 EventId::parse(id).expect("valid event id")
4427 }
4428
4429 fn add_room(core: &mut TestCore, room_id: &RoomId, encrypted: bool, name: Option<&str>) {
4432 let mut state = RoomState::new();
4433 if encrypted {
4434 state.encryption = Some(crate::wire::events::RoomEncryptionContent {
4435 algorithm: "m.megolm.v1.aes-sha2".to_string(),
4436 rotation_period_ms: 604_800_000,
4437 rotation_period_msgs: 100,
4438 });
4439 }
4440 state.name = name.map(str::to_string);
4441 core.rooms.insert(room_id.clone(), state);
4442 core.timelines.entry(room_id.clone()).or_default();
4443 }
4444
4445 #[test]
4448 fn set_events_default_opens_a_channel_for_every_member() {
4449 let mut core = open_fresh_core();
4450 let chan = room("!chan:example.org");
4451 add_room(&mut core, &chan, false, Some("Talk"));
4452 mark_as_channel(&mut core, &chan);
4453 core.dispatch(MessengerCommand::SetEventsDefault { room_id: chan.clone(), level: 0 }, 1_000).expect("dispatch");
4454 core.releasable_requests(1_000);
4455 flush_and_ack(&mut core);
4456 let sent = core.releasable_requests(1_000);
4457 let put = sent.iter().find(|r| r.kind == OutgoingRequestKind::SetState).expect("a state PUT");
4458 assert!(put.path.ends_with("/state/m.room.power_levels/"), "{}", put.path);
4459 let body = put.body.clone().expect("body");
4460 assert_eq!(body["events_default"], 0);
4461 assert_eq!(body["users_default"], 0);
4462 }
4463
4464 #[test]
4465 fn set_events_default_refuses_an_unknown_room() {
4466 let mut core = open_fresh_core();
4467 let err = core.dispatch(MessengerCommand::SetEventsDefault { room_id: room("!nope:example.org"), level: 0 }, 1_000);
4468 assert!(err.is_err());
4469 }
4470
4471 fn mark_as_channel(core: &mut TestCore, room_id: &RoomId) {
4472 let Some(state) = core.rooms.get_mut(room_id) else { return };
4473 state
4474 .apply_state_event(&state_event_raw(
4475 "m.room.join_rules",
4476 "",
4477 "@alice:example.org",
4478 serde_json::json!({ "join_rule": "public" }),
4479 ))
4480 .expect("join_rules");
4481 state
4482 .apply_state_event(&state_event_raw(
4483 "m.room.power_levels",
4484 "",
4485 "@alice:example.org",
4486 serde_json::json!({ "events_default": 50, "users_default": 0 }),
4487 ))
4488 .expect("power_levels");
4489 }
4490
4491 fn state_event_raw(event_type: &str, state_key: &str, sender: &str, content: serde_json::Value) -> RawEvent {
4492 serde_json::from_value(serde_json::json!({
4493 "event_id": format!("${event_type}:example.org"),
4494 "type": event_type,
4495 "sender": sender,
4496 "origin_server_ts": 1,
4497 "state_key": state_key,
4498 "content": content,
4499 }))
4500 .expect("valid raw event")
4501 }
4502
4503 fn raw_event(event_id: &str, event_type: &str, sender: &str, content: serde_json::Value) -> RawEvent {
4504 serde_json::from_value(serde_json::json!({
4505 "event_id": event_id,
4506 "type": event_type,
4507 "sender": sender,
4508 "origin_server_ts": 1,
4509 "content": content,
4510 }))
4511 .expect("valid raw event")
4512 }
4513
4514 fn seed_plain(core: &mut TestCore, room_id: &RoomId, event_id: &str, sender: &str, event_type: &str, content: serde_json::Value) {
4516 let raw = raw_event(event_id, event_type, sender, content);
4517 core.timelines.entry(room_id.clone()).or_default().apply_timeline_batch(&[raw], false, None);
4518 }
4519
4520 fn seed_sealed(core: &mut TestCore, room_id: &RoomId, event_id: &str, sender: &str) {
4522 let content = serde_json::json!({ "algorithm": "m.megolm.v1.aes-sha2", "ciphertext": "AAAA", "session_id": "s1" });
4523 seed_plain(core, room_id, event_id, sender, "m.room.encrypted", content);
4524 }
4525
4526 fn seed_decrypted(
4529 core: &mut TestCore,
4530 room_id: &RoomId,
4531 event_id_str: &str,
4532 sender: &str,
4533 event_type: &str,
4534 content: serde_json::Value,
4535 ) {
4536 seed_sealed(core, room_id, event_id_str, sender);
4537 if let Some(timeline) = core.timelines.get_mut(room_id) {
4538 timeline.set_decrypted_event(&event(event_id_str), event_type, &content);
4539 }
4540 }
4541
4542 fn sent_room_event(core: &mut TestCore) -> (String, serde_json::Value) {
4546 flush_and_ack(core);
4547 let request = core
4548 .releasable_requests(0)
4549 .into_iter()
4550 .find(|request| request.kind == OutgoingRequestKind::RoomSend)
4551 .expect("a room send is releasable");
4552 let confirmed = format!("$sent{}:example.org", core.counters.next_request_id);
4553 let body = serde_json::json!({ "event_id": confirmed }).to_string().into_bytes();
4554 core.on_response(request.id.clone(), HttpResponseDescriptor { status: 200, body }, 0);
4555 (request.path, request.body.expect("a room send has a body"))
4556 }
4557
4558 fn forward(core: &mut TestCore, from: &RoomId, event_id: &str, to: &RoomId) -> Result<(), MessengerError> {
4559 core.dispatch(
4560 MessengerCommand::Forward { from_room: from.clone(), event_id: event(event_id), to_room: to.clone(), txn_id: None },
4561 0,
4562 )
4563 }
4564
4565 fn last_item(core: &TestCore, room_id: &RoomId) -> crate::room::timeline::TimelineItem {
4566 core.timeline(room_id).and_then(|timeline| timeline.items().last()).expect("the room has an item").clone()
4567 }
4568
4569 #[test]
4570 fn forward_from_encrypted_room_carries_only_the_forwarded_flag() {
4571 let mut core = open_fresh_core();
4572 let dm = room("!dm:example.org");
4573 let target = room("!target:example.org");
4574 add_room(&mut core, &dm, true, Some("Secret DM name"));
4575 add_room(&mut core, &target, false, None);
4576 seed_decrypted(
4577 &mut core,
4578 &dm,
4579 "$src:example.org",
4580 "@bob:example.org",
4581 "m.room.message",
4582 serde_json::json!({
4583 "msgtype": "m.text",
4584 "body": "quarterly numbers",
4585 "m.relates_to": { "m.in_reply_to": { "event_id": "$parent:example.org" } },
4586 "m.mentions": { "user_ids": ["@carol:example.org"] },
4587 }),
4588 );
4589
4590 forward(&mut core, &dm, "$src:example.org", &target).expect("a decrypted message is forwardable");
4591
4592 let (path, body) = sent_room_event(&mut core);
4593 assert!(path.contains("/send/m.room.message/"), "the event type is kept: {path}");
4594 assert_eq!(
4595 body,
4596 serde_json::json!({ "msgtype": "m.text", "body": "quarterly numbers", "forwarded": true }),
4597 "the text plus the bare flag; the reply pointer and the mentions are stripped"
4598 );
4599 let wire = body.to_string();
4600 for leaked in ["bob", "carol", "!dm", "Secret DM name", "$parent"] {
4601 assert!(!wire.contains(leaked), "{leaked:?} must not travel with a forward out of an encrypted room: {wire}");
4602 }
4603 let echo = last_item(&core, &target);
4604 assert_eq!(echo.forwarded, Some(Forwarded::Hidden), "the forwarder's own echo shows the same marker");
4605 }
4606
4607 #[test]
4608 fn forward_from_public_channel_carries_room_id_and_name_no_sender() {
4609 let mut core = open_fresh_core();
4610 let channel = room("!chan:example.org");
4611 let target = room("!target:example.org");
4612 add_room(&mut core, &channel, true, Some("Announcements"));
4613 mark_as_channel(&mut core, &channel);
4614 add_room(&mut core, &target, false, None);
4615 seed_plain(
4616 &mut core,
4617 &channel,
4618 "$post:example.org",
4619 "@dave:example.org",
4620 "m.room.message",
4621 serde_json::json!({
4622 "msgtype": "m.text",
4623 "body": "BTC breaks out",
4624 "m.mentions": { "room": true },
4625 "m.relates_to": { "m.in_reply_to": { "event_id": "$older:example.org" } },
4626 }),
4627 );
4628
4629 forward(&mut core, &channel, "$post:example.org", &target).expect("a channel post is forwardable");
4630
4631 let (_, body) = sent_room_event(&mut core);
4632 assert_eq!(
4633 body,
4634 serde_json::json!({
4635 "msgtype": "m.text",
4636 "body": "BTC breaks out",
4637 "forwarded_from": { "room_id": "!chan:example.org", "room_name": "Announcements" },
4638 })
4639 );
4640 assert!(!body.to_string().contains("dave"), "the original sender never travels");
4641 assert_eq!(
4642 last_item(&core, &target).forwarded,
4643 Some(Forwarded::Channel { room_id: channel.clone(), room_name: "Announcements".to_string() })
4644 );
4645 }
4646
4647 #[test]
4648 fn forward_from_an_unnamed_room_never_lists_member_names() {
4649 let mut core = open_fresh_core();
4650 let channel = room("!chan:example.org");
4651 let target = room("!target:example.org");
4652 add_room(&mut core, &channel, true, None);
4653 mark_as_channel(&mut core, &channel);
4654 if let Some(state) = core.rooms.get_mut(&channel) {
4655 state.members.insert(
4656 UserId::parse("@erin:example.org").expect("valid user id"),
4657 crate::room::state::MemberState {
4658 membership: Membership::Join,
4659 displayname: Some("Erin Private".to_string()),
4660 is_direct: false,
4661 invited_by: None,
4662 },
4663 );
4664 }
4665 add_room(&mut core, &target, false, None);
4666 seed_plain(&mut core, &channel, "$post:example.org", "@erin:example.org", "m.room.message", serde_json::json!({ "msgtype": "m.text", "body": "hi" }));
4667
4668 forward(&mut core, &channel, "$post:example.org", &target).expect("forwardable");
4669
4670 let (_, body) = sent_room_event(&mut core);
4671 let name = body["forwarded_from"]["room_name"].as_str().expect("a name is sent");
4672 assert!(!name.contains("Erin"), "an unnamed room is attributed by a generic label, not by its members: {name}");
4673 }
4674
4675 #[test]
4676 fn forward_replaces_the_sources_own_forward_marker() {
4677 let mut core = open_fresh_core();
4678 let dm = room("!dm:example.org");
4679 let target = room("!target:example.org");
4680 add_room(&mut core, &dm, true, None);
4681 add_room(&mut core, &target, false, None);
4682 seed_decrypted(
4683 &mut core,
4684 &dm,
4685 "$src:example.org",
4686 "@bob:example.org",
4687 "m.room.message",
4688 serde_json::json!({
4689 "msgtype": "m.text",
4690 "body": "second hop",
4691 "forwarded_from": { "room_id": "!older:example.org", "room_name": "Older channel" },
4692 }),
4693 );
4694
4695 forward(&mut core, &dm, "$src:example.org", &target).expect("forwardable");
4696
4697 let (_, body) = sent_room_event(&mut core);
4698 assert_eq!(body, serde_json::json!({ "msgtype": "m.text", "body": "second hop", "forwarded": true }));
4699 }
4700
4701 #[test]
4702 fn forward_of_edited_item_sends_the_latest_text() {
4703 let mut core = open_fresh_core();
4704 let channel = room("!chan:example.org");
4705 let dm = room("!dm:example.org");
4706 let target = room("!target:example.org");
4707 add_room(&mut core, &channel, false, Some("Announcements"));
4708 add_room(&mut core, &dm, true, None);
4709 add_room(&mut core, &target, false, None);
4710
4711 seed_plain(&mut core, &channel, "$orig:example.org", "@dave:example.org", "m.room.message", serde_json::json!({ "msgtype": "m.text", "body": "typo text" }));
4713 seed_plain(
4714 &mut core,
4715 &channel,
4716 "$edit:example.org",
4717 "@dave:example.org",
4718 "m.room.message",
4719 serde_json::json!({
4720 "msgtype": "m.text",
4721 "body": "* fixed text",
4722 "m.new_content": { "msgtype": "m.text", "body": "fixed text" },
4723 "m.relates_to": { "rel_type": "m.replace", "event_id": "$orig:example.org" },
4724 }),
4725 );
4726 forward(&mut core, &channel, "$orig:example.org", &target).expect("forwardable");
4727 let (_, body) = sent_room_event(&mut core);
4728 assert_eq!(body["body"], "fixed text", "the latest edit's text, not the original and not the `* ` fallback");
4729 assert!(body.get("m.new_content").is_none() && body.get("m.relates_to").is_none());
4730
4731 seed_decrypted(&mut core, &dm, "$secret:example.org", "@bob:example.org", "m.room.message", serde_json::json!({ "msgtype": "m.text", "body": "typo secret" }));
4733 seed_sealed(&mut core, &dm, "$secret-edit:example.org", "@bob:example.org");
4734 let applied = core.timelines.get_mut(&dm).expect("timeline").apply_decrypted_relation(
4735 &event("$secret-edit:example.org"),
4736 UserId::parse("@bob:example.org").expect("valid user id"),
4737 2,
4738 RelatesTo::Replace { event_id: event("$secret:example.org") },
4739 Some(serde_json::json!({ "msgtype": "m.text", "body": "fixed secret" })),
4740 );
4741 assert!(applied, "the decrypted edit folds onto its target");
4742 forward(&mut core, &dm, "$secret:example.org", &target).expect("forwardable");
4743 let (_, body) = sent_room_event(&mut core);
4744 assert_eq!(body, serde_json::json!({ "msgtype": "m.text", "body": "fixed secret", "forwarded": true }));
4745 }
4746
4747 #[test]
4748 fn forward_of_unknown_msgtype_keeps_its_fields() {
4749 let mut core = open_fresh_core();
4750 let dm = room("!dm:example.org");
4751 let target = room("!target:example.org");
4752 add_room(&mut core, &dm, true, None);
4753 add_room(&mut core, &target, false, None);
4754 let card = serde_json::json!({
4755 "msgtype": "com.example.card",
4756 "body": "opaque",
4757 "note": "kept raw",
4758 });
4759 seed_decrypted(&mut core, &dm, "$card:example.org", "@bob:example.org", "m.room.message", card.clone());
4760
4761 forward(&mut core, &dm, "$card:example.org", &target).expect("an unrecognized msgtype is still an m.room.message");
4762
4763 let (path, body) = sent_room_event(&mut core);
4764 assert!(path.contains("/send/m.room.message/"));
4765 let mut expected = card;
4766 expected["forwarded"] = serde_json::Value::Bool(true);
4767 assert_eq!(body, expected, "unrecognized msgtype fields pass through plus the marker");
4768 assert!(matches!(last_item(&core, &target).content, ItemContent::Unknown), "the echo does not invent a card type");
4769 }
4770
4771 #[test]
4772 fn forward_refuses_redacted_sealed_unknown_and_missing() {
4773 let mut core = open_fresh_core();
4774 let dm = room("!dm:example.org");
4775 let target = room("!target:example.org");
4776 add_room(&mut core, &dm, true, None);
4777 add_room(&mut core, &target, false, None);
4778
4779 seed_decrypted(&mut core, &dm, "$redacted:example.org", "@bob:example.org", "m.room.message", serde_json::json!({ "msgtype": "m.text", "body": "gone" }));
4780 core.timelines.get_mut(&dm).expect("timeline").apply_redaction(&event("$redacted:example.org"));
4781 seed_sealed(&mut core, &dm, "$sealed:example.org", "@bob:example.org");
4782 seed_sealed(&mut core, &dm, "$utd:example.org", "@bob:example.org");
4783 core.timelines
4784 .get_mut(&dm)
4785 .expect("timeline")
4786 .set_decrypted(&event("$utd:example.org"), ItemContent::Undecryptable { reason: "MissingSession".to_string() });
4787 seed_plain(&mut core, &dm, "$call:example.org", "@bob:example.org", "m.call.invite", serde_json::json!({ "call_id": "c1" }));
4788
4789 for id in ["$redacted:example.org", "$sealed:example.org", "$utd:example.org", "$call:example.org", "$missing:example.org"] {
4790 match forward(&mut core, &dm, id, &target) {
4791 Err(MessengerError::IntentRefused(reason)) => {
4792 assert!(reason.contains(id), "the refusal names the event {id}: {reason}");
4793 }
4794 other => panic!("forwarding {id} must be refused, got {other:?}"),
4795 }
4796 }
4797 match forward(&mut core, &room("!nowhere:example.org"), "$redacted:example.org", &target) {
4798 Err(MessengerError::IntentRefused(reason)) => assert!(reason.contains("$redacted:example.org"), "{reason}"),
4799 other => panic!("an unknown source room means a missing event, got {other:?}"),
4800 }
4801 match forward(&mut core, &dm, "$sealed:example.org", &room("!nowhere:example.org")) {
4802 Err(MessengerError::IntentRefused(reason)) => assert!(reason.contains("unknown"), "{reason}"),
4803 other => panic!("an unknown target room is refused, got {other:?}"),
4804 }
4805
4806 assert!(core.send_state.is_empty(), "a refused forward queues nothing");
4807 assert!(core.timeline(&target).is_some_and(|timeline| timeline.items().is_empty()), "and leaves no echo");
4808 }
4809}