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
134const SYNC_TIMEOUT_MS: u64 = 30_000;
136const LOAD_OLDER_PAGE_SIZE: u32 = 50;
139const TYPING_TIMEOUT_MS: u64 = 30_000;
142const UNKNOWN_SENDER_RETRY_CAPACITY: usize = 256;
147const TYPING_DEBOUNCE_MS: i64 = 4_000;
151const ROOM_KEY_SHARE_EVENT_TYPE: &str = "m.room.encrypted";
159
160#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
164pub struct Counters {
165 pub next_request_id: u64,
167 pub next_txn_id: u64,
172}
173
174fn fresh_txn_seed() -> u64 {
188 std::time::SystemTime::now()
189 .duration_since(std::time::UNIX_EPOCH)
190 .map(|d| d.as_micros() as u64)
191 .unwrap_or(0)
192}
193
194fn restore_counter(persisted_next: u64, max_used_seed: Option<u64>) -> u64 {
195 match max_used_seed {
196 Some(seed) => persisted_next.max(seed.saturating_add(1)),
197 None => persisted_next,
198 }
199}
200
201#[derive(Clone, Debug, PartialEq, Eq)]
205pub struct CoreConfig {
206 pub user_id: UserId,
208 pub device_id: DeviceId,
210 pub server_name: String,
218}
219
220#[derive(Default)]
229pub struct CoreSecrets {
230 pub store_seal_key: Option<Zeroizing<[u8; 32]>>,
238 pub backup_key: Option<Zeroizing<[u8; 32]>>,
242}
243
244#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub enum MessageKind {
248 Text,
250 Notice,
252 Emote,
254}
255
256impl MessageKind {
257 fn msgtype(self) -> &'static str {
258 match self {
259 MessageKind::Text => "m.text",
260 MessageKind::Notice => "m.notice",
261 MessageKind::Emote => "m.emote",
262 }
263 }
264}
265
266#[derive(Clone, Debug, PartialEq, Eq)]
277pub struct OutgoingMessage {
278 pub kind: MessageKind,
280 pub body: String,
283 pub reply_to: Option<EventId>,
285 pub edit_of: Option<EventId>,
288}
289
290#[derive(Clone, Debug, PartialEq, Eq)]
296pub enum CreateRoomKind {
297 Dm {
301 peer: UserId,
303 },
304 Group {
306 name: String,
308 invite: Vec<UserId>,
310 members_can_invite: bool,
314 },
315 Channel {
319 name: String,
321 topic: Option<String>,
323 },
324}
325
326#[derive(Clone, Debug, PartialEq)]
332pub enum MessengerCommand {
333 SendMessage {
339 room_id: RoomId,
341 message: OutgoingMessage,
343 txn_id: Option<TxnId>,
346 },
347 Forward {
373 from_room: RoomId,
375 event_id: EventId,
377 to_room: RoomId,
379 txn_id: Option<TxnId>,
381 },
382 React {
386 room_id: RoomId,
388 target: EventId,
390 key: String,
392 },
393 Redact {
395 room_id: RoomId,
397 target: EventId,
399 reason: Option<String>,
401 },
402 RetrySend {
408 room_id: RoomId,
410 txn_id: TxnId,
412 },
413 CreateRoom {
415 kind: CreateRoomKind,
417 },
418 JoinRoom {
421 room_id: RoomId,
423 },
424 LeaveRoom {
426 room_id: RoomId,
428 },
429 Invite {
431 room_id: RoomId,
433 user_id: UserId,
435 },
436 Kick {
441 room_id: RoomId,
443 user_id: UserId,
445 reason: Option<String>,
447 },
448 SetPowerLevel {
451 room_id: RoomId,
453 user_id: UserId,
455 level: i64,
457 },
458 SetEventsDefault {
462 room_id: RoomId,
464 level: i64,
466 },
467 SetTag {
469 room_id: RoomId,
471 tag: String,
473 order: Option<f64>,
475 },
476 RemoveTag {
478 room_id: RoomId,
480 tag: String,
482 },
483 MarkRead {
486 room_id: RoomId,
488 event_id: EventId,
490 },
491 SetTyping {
494 room_id: RoomId,
496 typing: bool,
498 },
499 RetryDecryption {
504 room_id: RoomId,
506 event_id: EventId,
508 },
509 LoadOlder {
514 room_id: RoomId,
516 },
517 SetAccountData {
523 event_type: String,
525 content: serde_json::Value,
527 },
528 SetRoomAccountData {
537 room_id: RoomId,
539 event_type: String,
541 content: serde_json::Value,
543 },
544 SearchPublicRooms {
549 term: String,
551 },
552 SearchUsers {
557 term: String,
559 },
560}
561
562#[derive(Clone, Debug, PartialEq, Eq)]
567pub enum MessengerEvent {
568 RoomsChanged,
571 TimelineChanged {
573 room_id: RoomId,
575 },
576 TypingChanged {
578 room_id: RoomId,
580 },
581 ReceiptsChanged {
583 room_id: RoomId,
585 },
586 UnreadChanged {
588 room_id: RoomId,
590 },
591 DeviceKeyChanged {
595 user_id: UserId,
597 device_id: DeviceId,
599 },
600 PublicRoomsChanged,
604 UserSearchChanged,
608}
609
610#[derive(Clone)]
615struct PendingDecryptItem {
616 session_id: String,
617 sender: UserId,
618 origin_server_ts: i64,
619 content: MegolmEncryptedContent,
620}
621
622#[derive(Deserialize)]
628struct RoomMessagesResponseBody {
629 #[serde(default)]
630 chunk: Vec<RawEvent>,
631 #[serde(default)]
632 end: Option<String>,
633}
634
635#[derive(Deserialize)]
637struct SendResponseBody {
638 event_id: EventId,
639}
640
641#[derive(Deserialize)]
643struct CreateRoomResponseBody {
644 room_id: RoomId,
645}
646
647#[derive(Clone, Debug, PartialEq)]
650pub struct PublicRoomsResultEntry {
651 pub room_id: RoomId,
653 pub name: Option<String>,
655 pub topic: Option<String>,
657 pub num_joined_members: u64,
659}
660
661#[derive(Deserialize)]
663struct PublicRoomsResponseBody {
664 #[serde(default)]
665 chunk: Vec<PublicRoomsChunkEntry>,
666}
667
668#[derive(Deserialize)]
669struct PublicRoomsChunkEntry {
670 room_id: RoomId,
671 #[serde(default)]
672 name: Option<String>,
673 #[serde(default)]
674 topic: Option<String>,
675 #[serde(default)]
676 num_joined_members: u64,
677}
678
679#[derive(Clone, Debug, PartialEq)]
682pub struct UserDirectoryResultEntry {
683 pub user_id: UserId,
685 pub display_name: Option<String>,
687}
688
689#[derive(Deserialize)]
692struct UserDirectorySearchResponseBody {
693 #[serde(default)]
694 results: Vec<UserDirectorySearchResultEntry>,
695}
696
697#[derive(Deserialize)]
698struct UserDirectorySearchResultEntry {
699 user_id: UserId,
700 #[serde(default)]
701 display_name: Option<String>,
702}
703
704#[derive(Clone)]
709enum SendPayload {
710 Message(OutgoingMessage),
713 Event {
717 event_type: String,
719 content: serde_json::Value,
721 },
722 Reaction {
724 target: EventId,
726 key: String,
728 },
729 Redaction {
731 target: EventId,
733 reason: Option<String>,
735 },
736}
737
738struct RoomEventWire {
742 event_type: String,
743 content: serde_json::Value,
744 relates_to: Option<RelatesTo>,
747}
748
749impl SendPayload {
750 fn room_event(&self) -> Option<RoomEventWire> {
753 match self {
754 SendPayload::Message(message) => Some(RoomEventWire {
755 event_type: "m.room.message".to_string(),
756 content: message_wire_content(message),
757 relates_to: message_relates_to(message),
758 }),
759 SendPayload::Event { event_type, content } => {
760 Some(RoomEventWire { event_type: event_type.clone(), content: content.clone(), relates_to: None })
761 }
762 SendPayload::Reaction { .. } | SendPayload::Redaction { .. } => None,
763 }
764 }
765
766 fn is_room_event(&self) -> bool {
767 matches!(self, SendPayload::Message(_) | SendPayload::Event { .. })
768 }
769}
770
771#[derive(Clone)]
777enum WireSend {
778 Event {
780 event_type: String,
783 content: serde_json::Value,
785 },
786 Redact {
788 target: EventId,
790 reason: Option<String>,
792 },
793}
794
795#[derive(Clone)]
800enum SendPhase {
801 Queued,
805 AwaitingKeysQuery,
811 AwaitingKeysClaim {
814 devices: Vec<StoredDevice>,
818 },
819 AwaitingKeyShare {
824 pending: BTreeSet<RequestId>,
826 },
827 AwaitingSend,
829 Failed {
834 errcode: String,
836 },
837}
838
839#[derive(Clone)]
845struct PendingSend {
846 room_id: RoomId,
849 payload: SendPayload,
851 phase: SendPhase,
853 wire: Option<WireSend>,
855}
856
857type AccountDataKey = (Option<RoomId>, String);
860
861struct AccountDataGuard {
873 pending: u32,
875 server_value: Option<serde_json::Value>,
879}
880
881fn account_data_key_from_path(path: &str) -> Option<AccountDataKey> {
884 let segments: Vec<&str> = path.split('/').collect();
885 let n = segments.len();
886 if n < 4 || segments[n - 2] != "account_data" {
887 return None;
888 }
889 let event_type = percent_decode_segment(segments[n - 1])?;
890 if segments[n - 4] == "user" {
891 return Some((None, event_type));
892 }
893 if n >= 6 && segments[n - 4] == "rooms" && segments[n - 6] == "user" {
894 let room_id = RoomId::parse(percent_decode_segment(segments[n - 3])?).ok()?;
895 return Some((Some(room_id), event_type));
896 }
897 None
898}
899
900pub struct MessengerCore<C: RecordCodec> {
902 config: CoreConfig,
903 store: Store<C>,
904 account: OlmAccountState,
905 xsign: Option<CrossSigningState>,
907 xsign_inflight: bool,
908 xsign_attempts: u8,
909 outgoing: OutgoingQueue,
910 counters: Counters,
911 jitter: Box<dyn Jitter>,
912
913 rooms: BTreeMap<RoomId, RoomState>,
914 timelines: BTreeMap<RoomId, Timeline>,
915 direct_account_data: Option<DirectContent>,
916 global_account_data: BTreeMap<String, serde_json::Value>,
917 room_account_data: BTreeMap<RoomId, BTreeMap<String, serde_json::Value>>,
918 typing: BTreeMap<RoomId, Vec<UserId>>,
919 receipts: BTreeMap<RoomId, ReceiptContent>,
920
921 public_rooms_result: Vec<PublicRoomsResultEntry>,
924 latest_public_rooms_request: Option<RequestId>,
929 user_search_result: Vec<UserDirectoryResultEntry>,
932 latest_user_search_request: Option<RequestId>,
935
936 account_data_guard: BTreeMap<AccountDataKey, AccountDataGuard>,
940 account_data_write_keys: BTreeMap<RequestId, AccountDataKey>,
942
943 pending_decrypt: BTreeMap<RoomId, BTreeMap<EventId, PendingDecryptItem>>,
944 key_requests_sent: BTreeSet<(RoomId, String)>,
948 history_claims: BTreeMap<RequestId, Vec<StoredDevice>>,
951 history_claim_tried: BTreeSet<(UserId, DeviceId)>,
953 withheld_sessions: BTreeSet<(RoomId, String)>,
955 key_requests_answered: BTreeSet<(UserId, DeviceId, String)>,
957 unknown_sender_retry: VecDeque<ToDeviceEvent>,
958
959 typing_debounce: BTreeMap<RoomId, i64>,
964 pending_sends: BTreeMap<RoomId, VecDeque<TxnId>>,
970 send_state: BTreeMap<TxnId, PendingSend>,
975 send_request_owner: BTreeMap<RequestId, TxnId>,
979 pending_create_room: BTreeMap<RequestId, CreateRoomKind>,
983
984 pending_kinds: BTreeMap<RequestId, OutgoingRequestKind>,
990 pending_room_messages: BTreeMap<RequestId, RoomId>,
993 sync_request_id: Option<RequestId>,
996 sync_caught_up: bool,
1001 pending_flush_batches: VecDeque<FlushBatch>,
1008
1009 events: Vec<MessengerEvent>,
1010 change_counter: u64,
1011 ingest_error: Option<String>,
1016}
1017
1018impl<C: RecordCodec> MessengerCore<C> {
1019 pub fn open(
1032 records: impl IntoIterator<Item = SealedRecord>,
1033 codec: C,
1034 config: CoreConfig,
1035 secrets: CoreSecrets,
1036 _now_ms: i64,
1037 jitter: Box<dyn Jitter>,
1038 ) -> Result<Self, MessengerError> {
1039 let _ = secrets;
1043 let mut store = Store::load(records, codec, config.device_id.clone())?;
1044 let mut outgoing = OutgoingQueue::load(&store)?;
1045 let account = OlmAccountState::load_or_create(&mut store)?;
1046
1047 let mut rooms = BTreeMap::new();
1048 let mut timelines = BTreeMap::new();
1049 for room_id in store.room_ids()? {
1050 if let Some(bytes) = store.room_state(&room_id)? {
1051 let state: RoomState = serde_json::from_slice(bytes)
1052 .map_err(|source| MessengerError::Crypto(format!("decode room state for {room_id}: {source}")))?;
1053 rooms.insert(room_id.clone(), state);
1054 timelines.insert(room_id, Timeline::new());
1055 }
1056 }
1057
1058 let pending_requests = store.pending_requests()?;
1059 let max_pending_request_seed = pending_requests.iter().filter_map(|req| req.request.id.as_seed()).max();
1060 outgoing.discard_kind(&mut store, OutgoingRequestKind::Sync)?;
1067 let persisted_counters = store.counters()?.unwrap_or_default();
1068 let mut counters = Counters {
1069 next_request_id: restore_counter(persisted_counters.next_request_id, max_pending_request_seed),
1070 next_txn_id: restore_counter(persisted_counters.next_txn_id, None),
1071 };
1072 if counters != persisted_counters {
1073 store.save_counters(counters)?;
1079 }
1080 counters.next_txn_id = counters.next_txn_id.max(fresh_txn_seed());
1086
1087 let mut core = Self {
1088 config,
1089 store,
1090 account,
1091 outgoing,
1092 counters,
1093 jitter,
1094 rooms,
1095 timelines,
1096 direct_account_data: None,
1097 global_account_data: BTreeMap::new(),
1098 room_account_data: BTreeMap::new(),
1099 typing: BTreeMap::new(),
1100 receipts: BTreeMap::new(),
1101 public_rooms_result: Vec::new(),
1102 latest_public_rooms_request: None,
1103 user_search_result: Vec::new(),
1104 latest_user_search_request: None,
1105 account_data_guard: BTreeMap::new(),
1106 account_data_write_keys: BTreeMap::new(),
1107 pending_decrypt: BTreeMap::new(),
1108 key_requests_sent: BTreeSet::new(),
1109 history_claims: BTreeMap::new(),
1110 history_claim_tried: BTreeSet::new(),
1111 withheld_sessions: BTreeSet::new(),
1112 key_requests_answered: BTreeSet::new(),
1113 unknown_sender_retry: VecDeque::new(),
1114 typing_debounce: BTreeMap::new(),
1115 pending_sends: BTreeMap::new(),
1116 send_state: BTreeMap::new(),
1117 send_request_owner: BTreeMap::new(),
1118 pending_create_room: BTreeMap::new(),
1119 pending_kinds: BTreeMap::new(),
1120 pending_room_messages: BTreeMap::new(),
1121 sync_request_id: None,
1122 sync_caught_up: false,
1123 pending_flush_batches: VecDeque::new(),
1124 events: Vec::new(),
1125 change_counter: 0,
1126 ingest_error: None,
1127 xsign: None,
1128 xsign_inflight: false,
1129 xsign_attempts: 0,
1130 };
1131 core.rebuild_account_data_guard(&pending_requests);
1132 Ok(core)
1133 }
1134
1135 fn rebuild_account_data_guard(&mut self, pending_requests: &[PendingRequest]) {
1143 let mut writes: Vec<&PendingRequest> = pending_requests
1144 .iter()
1145 .filter(|record| matches!(record.request.kind, OutgoingRequestKind::AccountData | OutgoingRequestKind::RoomAccountData))
1146 .collect();
1147 writes.sort_by(|a, b| a.request.id.cmp(&b.request.id));
1148 for record in writes {
1149 let Some(key) = account_data_key_from_path(&record.request.path) else { continue };
1150 let Some(body) = record.request.body.clone() else { continue };
1151 let guard = self.account_data_guard.entry(key.clone()).or_insert(AccountDataGuard { pending: 0, server_value: None });
1152 guard.pending += 1;
1153 self.store_account_data_cache(&key, Some(body));
1154 self.account_data_write_keys.insert(record.request.id.clone(), key);
1155 }
1156 }
1157
1158 fn next_request_id(&mut self) -> Result<RequestId, MessengerError> {
1162 let seed = self.counters.next_request_id;
1163 self.counters.next_request_id = seed.saturating_add(1);
1164 self.store.save_counters(self.counters)?;
1165 Ok(RequestId::next(seed))
1166 }
1167
1168 fn next_txn_id(&mut self) -> Result<TxnId, MessengerError> {
1173 let seed = self.counters.next_txn_id;
1174 self.counters.next_txn_id = seed.saturating_add(1);
1175 self.store.save_counters(self.counters)?;
1176 Ok(TxnId::new(seed))
1177 }
1178
1179 fn enqueue_request(&mut self, request: OutgoingRequest, lane: Lane) -> Result<(), MessengerError> {
1194 let id = request.id.clone();
1195 let kind = request.kind;
1196 if let Some(batch) = self.outgoing.enqueue(&mut self.store, request, lane)? {
1197 self.pending_flush_batches.push_back(batch);
1198 }
1199 self.pending_kinds.insert(id, kind);
1200 Ok(())
1201 }
1202
1203 fn emit(&mut self, event: MessengerEvent) {
1204 self.change_counter = self.change_counter.wrapping_add(1);
1205 self.events.push(event);
1206 }
1207
1208 fn drain_events(&mut self) -> Vec<MessengerEvent> {
1209 std::mem::take(&mut self.events)
1210 }
1211
1212 fn start_new_send(
1228 &mut self,
1229 room_id: RoomId,
1230 payload: SendPayload,
1231 caller_txn_id: Option<TxnId>,
1232 now_ms: i64,
1233 ) -> Result<(), MessengerError> {
1234 let txn_id = match caller_txn_id {
1235 Some(txn_id) => txn_id,
1236 None => self.next_txn_id()?,
1237 };
1238
1239 if let Some(wire) = payload.room_event() {
1240 if self.typing_debounce.remove(&room_id).is_some() {
1241 if let Ok(request_id) = self.next_request_id() {
1242 let request = OutgoingRequest::typing(request_id, &room_id, &self.config.user_id, false, None);
1243 let _ = self.enqueue_request(request, Lane::Other);
1244 }
1245 }
1246 let content = match &payload {
1247 SendPayload::Message(message) => message_local_echo_content(message),
1248 _ => interpret_content(&wire.event_type, None, &wire.content),
1249 };
1250 let timeline = self.timelines.entry(room_id.clone()).or_default();
1251 timeline.push_local_echo(
1252 txn_id.clone(),
1253 self.config.user_id.clone(),
1254 now_ms,
1255 &wire.event_type,
1256 content,
1257 wire.content,
1258 );
1259 timeline.mark_send_state(&txn_id, SendState::Sending);
1260 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
1261 }
1262
1263 self.send_state.insert(txn_id.clone(), PendingSend { room_id: room_id.clone(), payload, phase: SendPhase::Queued, wire: None });
1264 let queue = self.pending_sends.entry(room_id).or_default();
1265 let was_empty = queue.is_empty();
1266 queue.push_back(txn_id.clone());
1267 if was_empty {
1268 self.start_send_pipeline(&txn_id, now_ms)?;
1269 }
1270 Ok(())
1271 }
1272
1273 fn retry_send(&mut self, room_id: &RoomId, txn_id: &TxnId, now_ms: i64) -> Result<(), MessengerError> {
1278 let Some(pending) = self.send_state.get(txn_id) else { return Ok(()) };
1279 if pending.room_id != *room_id || !matches!(pending.phase, SendPhase::Failed { .. }) {
1280 return Ok(());
1281 }
1282 self.set_send_phase(txn_id, SendPhase::Queued);
1283 let queue = self.pending_sends.entry(room_id.clone()).or_default();
1284 let was_empty = queue.is_empty();
1285 queue.push_back(txn_id.clone());
1286 if was_empty {
1287 self.start_send_pipeline(txn_id, now_ms)?;
1288 }
1289 Ok(())
1290 }
1291
1292 fn set_send_phase(&mut self, txn_id: &TxnId, phase: SendPhase) {
1293 if let Some(entry) = self.send_state.get_mut(txn_id) {
1294 entry.phase = phase;
1295 }
1296 }
1297
1298 fn start_send_pipeline(&mut self, txn_id: &TxnId, now_ms: i64) -> Result<(), MessengerError> {
1307 let Some(pending) = self.send_state.get(txn_id) else { return Ok(()) };
1308 let room_id = pending.room_id.clone();
1309
1310 if pending.wire.is_some() {
1311 return self.begin_awaiting_send(txn_id, &room_id);
1312 }
1313
1314 let is_room_event = pending.payload.is_room_event();
1315 let encrypted_room = self.rooms.get(&room_id).is_some_and(|room| room.encryption.is_some());
1316
1317 if is_room_event && encrypted_room {
1318 self.begin_key_pipeline(txn_id, &room_id, now_ms)
1319 } else {
1320 self.build_plaintext_wire_body(txn_id);
1321 self.begin_awaiting_send(txn_id, &room_id)
1322 }
1323 }
1324
1325 fn encrypted_room_member_ids(&self, room_id: &RoomId) -> BTreeSet<UserId> {
1330 let mut ids: BTreeSet<UserId> = self
1331 .rooms
1332 .get(room_id)
1333 .map(|state| {
1334 state
1335 .members
1336 .iter()
1337 .filter(|(_, member)| matches!(member.membership, Membership::Join | Membership::Invite))
1338 .map(|(user_id, _)| user_id.clone())
1339 .collect()
1340 })
1341 .unwrap_or_default();
1342 ids.insert(self.config.user_id.clone());
1343 ids
1344 }
1345
1346 fn mark_outdated_or_untracked(&mut self, member_ids: &BTreeSet<UserId>) -> Result<Vec<UserId>, MessengerError> {
1352 let tracked: BTreeMap<UserId, bool> = self.store.tracked_users()?.into_iter().collect();
1353 let mut outdated = Vec::new();
1354 let mut newly_tracked = Vec::new();
1355 for user_id in member_ids {
1356 match tracked.get(user_id) {
1357 Some(true) => outdated.push(user_id.clone()),
1358 Some(false) => {}
1359 None => newly_tracked.push(user_id.clone()),
1360 }
1361 }
1362 if !newly_tracked.is_empty() {
1363 DeviceTracker::on_device_lists(&mut self.store, &newly_tracked, &[])?;
1364 outdated.extend(newly_tracked);
1365 }
1366 Ok(outdated)
1367 }
1368
1369 fn known_devices_for(&self, member_ids: &BTreeSet<UserId>) -> Result<Vec<StoredDevice>, MessengerError> {
1372 let mut all = Vec::new();
1373 for user_id in member_ids {
1374 all.extend(DeviceTracker::devices_for_user(&self.store, user_id)?);
1375 }
1376 Ok(all)
1377 }
1378
1379 fn candidate_recipient_devices(&self, member_ids: &BTreeSet<UserId>, known_devices: &[StoredDevice]) -> Vec<StoredDevice> {
1383 known_devices
1384 .iter()
1385 .filter(|device| member_ids.contains(&device.user_id) && !device.blocked)
1386 .filter(|device| !(device.user_id == self.config.user_id && device.device_id == self.config.device_id))
1387 .cloned()
1388 .collect()
1389 }
1390
1391 fn begin_key_pipeline(&mut self, txn_id: &TxnId, room_id: &RoomId, now_ms: i64) -> Result<(), MessengerError> {
1396 let members = self.encrypted_room_member_ids(room_id);
1397 let outdated = self.mark_outdated_or_untracked(&members)?;
1398 if !outdated.is_empty() {
1399 let request_id = self.next_request_id()?;
1400 if let Some(request) = DeviceTracker::keys_query_request(&self.store, request_id.clone())? {
1401 self.send_request_owner.insert(request_id.clone(), txn_id.clone());
1402 self.enqueue_request(request, Lane::Other)?;
1403 self.set_send_phase(txn_id, SendPhase::AwaitingKeysQuery);
1404 return Ok(());
1405 }
1406 }
1407 self.begin_keys_claim_or_share(txn_id, room_id, now_ms)
1408 }
1409
1410 fn begin_keys_claim_or_share(&mut self, txn_id: &TxnId, room_id: &RoomId, now_ms: i64) -> Result<(), MessengerError> {
1414 let members = self.encrypted_room_member_ids(room_id);
1415 let known_devices = self.known_devices_for(&members)?;
1416 let candidates = self.candidate_recipient_devices(&members, &known_devices);
1417 let missing: Vec<StoredDevice> =
1418 OlmSessionManager::sessions_missing_for(&self.store, &candidates)?.into_iter().cloned().collect();
1419 if !missing.is_empty() {
1420 let request_id = self.next_request_id()?;
1421 let refs: Vec<&StoredDevice> = missing.iter().collect();
1422 if let Some(request) = OlmSessionManager::keys_claim_request(request_id.clone(), &refs) {
1423 self.send_request_owner.insert(request_id.clone(), txn_id.clone());
1424 self.enqueue_request(request, Lane::Other)?;
1425 self.set_send_phase(txn_id, SendPhase::AwaitingKeysClaim { devices: missing });
1426 return Ok(());
1427 }
1428 }
1429 self.begin_share_or_encrypt(txn_id, room_id, now_ms)
1430 }
1431
1432 fn begin_share_or_encrypt(&mut self, txn_id: &TxnId, room_id: &RoomId, now_ms: i64) -> Result<(), MessengerError> {
1445 let Some(encryption) = self.rooms.get(room_id).and_then(|room| room.encryption.clone()) else {
1446 self.build_plaintext_wire_body(txn_id);
1447 return self.begin_awaiting_send(txn_id, room_id);
1448 };
1449 let members = self.encrypted_room_member_ids(room_id);
1450 let known_devices = self.known_devices_for(&members)?;
1451 let update = GroupSessionManager::ensure_outbound_session(
1452 &mut self.store,
1453 room_id,
1454 &self.config.user_id,
1455 &self.config.device_id,
1456 &encryption,
1457 &members,
1458 &known_devices,
1459 now_ms,
1460 )?;
1461 let own_keys = self.account.identity_keys();
1462 GroupSessionManager::adopt_own_outbound_session(
1463 &mut self.store,
1464 room_id,
1465 &self.config.user_id,
1466 own_keys.curve25519,
1467 own_keys.ed25519,
1468 &update,
1469 )?;
1470
1471 if let Some(room_key_content) = update.room_key_content.clone() {
1472 let missing_ids: BTreeSet<(UserId, DeviceId)> = OlmSessionManager::sessions_missing_for(&self.store, &update.new_recipients)?
1473 .into_iter()
1474 .map(|device| (device.user_id.clone(), device.device_id.clone()))
1475 .collect();
1476 let sessioned: Vec<StoredDevice> = update
1477 .new_recipients
1478 .into_iter()
1479 .filter(|device| !missing_ids.contains(&(device.user_id.clone(), device.device_id.clone())))
1480 .collect();
1481
1482 let mut pending_ids = BTreeSet::new();
1483 for chunk in GroupSessionManager::chunk_recipients_for_send_to_device(&sessioned) {
1484 let body = GroupSessionManager::build_room_key_send_to_device_body(
1485 &mut self.store,
1486 &self.account,
1487 &self.config.user_id,
1488 &self.config.device_id,
1489 chunk,
1490 &room_key_content,
1491 )?;
1492 let request_id = self.next_request_id()?;
1493 let share_txn = self.next_txn_id()?;
1494 let request = OutgoingRequest::send_to_device(request_id.clone(), ROOM_KEY_SHARE_EVENT_TYPE, &share_txn, body);
1495 self.send_request_owner.insert(request_id.clone(), txn_id.clone());
1496 self.enqueue_request(request, Lane::ToDevice)?;
1497 pending_ids.insert(request_id);
1498 }
1499 if !pending_ids.is_empty() {
1500 self.set_send_phase(txn_id, SendPhase::AwaitingKeyShare { pending: pending_ids });
1501 return Ok(());
1502 }
1503 }
1504
1505 self.finish_encrypt_and_send(txn_id, room_id)?;
1506 self.begin_awaiting_send(txn_id, room_id)
1507 }
1508
1509 fn finish_encrypt_and_send(&mut self, txn_id: &TxnId, room_id: &RoomId) -> Result<(), MessengerError> {
1516 let Some(wire) = self.send_state.get(txn_id).and_then(|pending| pending.payload.room_event()) else {
1517 return Ok(());
1518 };
1519 let sender_curve = self.account.identity_keys().curve25519.to_base64();
1520 let encrypted = GroupSessionManager::encrypt_event(
1521 &mut self.store,
1522 room_id,
1523 &sender_curve,
1524 &self.config.device_id,
1525 &wire.event_type,
1526 wire.content,
1527 wire.relates_to,
1528 )?;
1529 let wire_content = serde_json::to_value(RoomEncryptedContent::Megolm(encrypted))?;
1530 if let Some(entry) = self.send_state.get_mut(txn_id) {
1531 entry.wire = Some(WireSend::Event { event_type: "m.room.encrypted".to_string(), content: wire_content });
1532 }
1533 Ok(())
1534 }
1535
1536 fn build_plaintext_wire_body(&mut self, txn_id: &TxnId) {
1542 let Some(pending) = self.send_state.get(txn_id) else { return };
1543 let wire = match &pending.payload {
1544 SendPayload::Message(_) | SendPayload::Event { .. } => match pending.payload.room_event() {
1545 Some(room_event) => WireSend::Event { event_type: room_event.event_type, content: room_event.content },
1546 None => return,
1547 },
1548 SendPayload::Reaction { target, key } => WireSend::Event {
1549 event_type: "m.reaction".to_string(),
1550 content: serde_json::json!({
1551 "m.relates_to": { "rel_type": "m.annotation", "event_id": target.as_str(), "key": key }
1552 }),
1553 },
1554 SendPayload::Redaction { target, reason } => WireSend::Redact { target: target.clone(), reason: reason.clone() },
1555 };
1556 if let Some(entry) = self.send_state.get_mut(txn_id) {
1557 entry.wire = Some(wire);
1558 }
1559 }
1560
1561 fn begin_awaiting_send(&mut self, txn_id: &TxnId, room_id: &RoomId) -> Result<(), MessengerError> {
1566 let Some(wire) = self.send_state.get(txn_id).and_then(|pending| pending.wire.clone()) else { return Ok(()) };
1567 let request_id = self.next_request_id()?;
1568 let request = match &wire {
1569 WireSend::Event { event_type, content } => {
1570 OutgoingRequest::room_send(request_id.clone(), room_id, event_type, txn_id, content.clone())
1571 }
1572 WireSend::Redact { target, reason } => {
1573 let body = match reason {
1574 Some(reason) => serde_json::json!({ "reason": reason }),
1575 None => serde_json::json!({}),
1576 };
1577 OutgoingRequest::room_redact(request_id.clone(), room_id, target, txn_id, body)
1578 }
1579 };
1580 self.send_request_owner.insert(request_id, txn_id.clone());
1581 self.enqueue_request(request, Lane::Room(room_id.clone()))?;
1582 self.set_send_phase(txn_id, SendPhase::AwaitingSend);
1583 Ok(())
1584 }
1585
1586 fn advance_send_on_success(
1592 &mut self,
1593 txn_id: &TxnId,
1594 request_id: &RequestId,
1595 kind: Option<OutgoingRequestKind>,
1596 response: &HttpResponseDescriptor,
1597 now_ms: i64,
1598 ) {
1599 match kind {
1600 Some(OutgoingRequestKind::KeysQuery) => {
1601 let Some(room_id) = self.send_state.get(txn_id).map(|pending| pending.room_id.clone()) else { return };
1602 let _ = self.begin_keys_claim_or_share(txn_id, &room_id, now_ms);
1603 }
1604 Some(OutgoingRequestKind::KeysClaim) => {
1605 let Some(pending) = self.send_state.get(txn_id) else { return };
1606 let room_id = pending.room_id.clone();
1607 if let SendPhase::AwaitingKeysClaim { devices, .. } = &pending.phase {
1608 let devices = devices.clone();
1609 let refs: Vec<&StoredDevice> = devices.iter().collect();
1610 let _ = OlmSessionManager::on_keys_claim_response(&mut self.store, &self.account, &refs, &response.body);
1611 }
1612 let _ = self.begin_share_or_encrypt(txn_id, &room_id, now_ms);
1613 }
1614 Some(OutgoingRequestKind::SendToDevice) => {
1615 let mut remaining = None;
1616 if let Some(entry) = self.send_state.get_mut(txn_id) {
1617 if let SendPhase::AwaitingKeyShare { pending } = &mut entry.phase {
1618 pending.remove(request_id);
1619 remaining = Some(pending.len());
1620 }
1621 }
1622 if remaining == Some(0) {
1623 let Some(room_id) = self.send_state.get(txn_id).map(|pending| pending.room_id.clone()) else { return };
1624 let _ = self.finish_encrypt_and_send(txn_id, &room_id);
1625 let _ = self.begin_awaiting_send(txn_id, &room_id);
1626 }
1627 }
1628 Some(OutgoingRequestKind::RoomSend) | Some(OutgoingRequestKind::RoomRedact) => {
1629 self.complete_send_success(txn_id, response, now_ms);
1630 }
1631 _ => {}
1632 }
1633 }
1634
1635 fn complete_send_success(&mut self, txn_id: &TxnId, response: &HttpResponseDescriptor, now_ms: i64) {
1641 let Some(pending) = self.send_state.get(txn_id) else { return };
1642 let room_id = pending.room_id.clone();
1643 if pending.payload.is_room_event() {
1644 if let Ok(body) = serde_json::from_slice::<SendResponseBody>(&response.body) {
1645 if let Some(timeline) = self.timelines.get_mut(&room_id) {
1646 timeline.set_echo_event_id(txn_id, body.event_id);
1647 }
1648 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
1649 }
1650 }
1651 self.send_state.remove(txn_id);
1652 self.finish_active_send(&room_id, txn_id, now_ms);
1653 }
1654
1655 fn fail_send(&mut self, txn_id: &TxnId, errcode: String, now_ms: i64) {
1661 let Some(pending) = self.send_state.get(txn_id) else { return };
1662 let room_id = pending.room_id.clone();
1663 if pending.payload.is_room_event() {
1664 if let Some(timeline) = self.timelines.get_mut(&room_id) {
1665 timeline.mark_send_state(txn_id, SendState::Failed { reason: errcode.clone() });
1666 }
1667 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
1668 }
1669 self.set_send_phase(txn_id, SendPhase::Failed { errcode });
1670 self.finish_active_send(&room_id, txn_id, now_ms);
1671 }
1672
1673 fn finish_active_send(&mut self, room_id: &RoomId, txn_id: &TxnId, now_ms: i64) {
1679 if let Some(queue) = self.pending_sends.get_mut(room_id) {
1680 if queue.front() == Some(txn_id) {
1681 queue.pop_front();
1682 } else {
1683 queue.retain(|id| id != txn_id);
1684 }
1685 }
1686 let next = self.pending_sends.get(room_id).and_then(|queue| queue.front().cloned());
1687 if let Some(next_txn) = next {
1688 let _ = self.start_send_pipeline(&next_txn, now_ms);
1689 }
1690 }
1691
1692 pub fn releasable_requests(&mut self, now_ms: i64) -> Vec<OutgoingRequest> {
1712 self.ensure_sync_enqueued();
1713 self.outgoing.releasable(&self.store, now_ms)
1714 }
1715
1716 fn ensure_sync_enqueued(&mut self) {
1717 if self.sync_request_id.is_some() {
1718 return;
1719 }
1720 let Ok(request_id) = self.next_request_id() else { return };
1721 let since = self.store.sync_token().ok().flatten().map(str::to_string);
1722 let timeout_ms = if since.is_some() && self.sync_caught_up { SYNC_TIMEOUT_MS } else { 0 };
1732 let request = OutgoingRequest::sync(request_id.clone(), since.as_deref(), Some(timeout_ms));
1733 if self.enqueue_request(request, Lane::Sync).is_ok() {
1734 self.sync_request_id = Some(request_id);
1735 }
1736 }
1737
1738 pub fn on_response(&mut self, request_id: RequestId, resp: HttpResponseDescriptor, now_ms: i64) -> Vec<MessengerEvent> {
1744 let kind = self.pending_kinds.get(&request_id).copied();
1745 let room_messages_room = self.pending_room_messages.get(&request_id).cloned();
1746 let create_room_kind = self.pending_create_room.get(&request_id).cloned();
1747 let send_owner = self.send_request_owner.get(&request_id).cloned();
1748
1749 let outcome = self.outgoing.on_response(&mut self.store, &request_id, resp, now_ms, self.jitter.as_mut());
1750 let (is_terminal, terminal_response, failed_errcode) = match outcome {
1751 Ok(Some(ResponseOutcome::Done(response))) => (true, Some(response), None),
1752 Ok(Some(ResponseOutcome::Failed { errcode, .. })) => (true, None, Some(errcode)),
1753 _ => (false, None, None),
1754 };
1755
1756 if is_terminal {
1757 self.pending_kinds.remove(&request_id);
1758 if matches!(kind, Some(OutgoingRequestKind::Sync)) {
1759 self.sync_request_id = None;
1760 if terminal_response.is_some() {
1761 self.sync_caught_up = true;
1762 }
1763 }
1764 if room_messages_room.is_some() {
1765 self.pending_room_messages.remove(&request_id);
1766 }
1767 self.pending_create_room.remove(&request_id);
1768 self.send_request_owner.remove(&request_id);
1769 if let Some(key) = self.account_data_write_keys.remove(&request_id) {
1770 self.settle_account_data_write(&key, terminal_response.is_some());
1771 }
1772 }
1773
1774 if let Some(response) = &terminal_response {
1775 if let Err(error) = self.handle_terminal_success(&request_id, kind, room_messages_room, create_room_kind, response) {
1782 self.ingest_error = Some(match kind {
1783 Some(kind) => format!("{kind:?} response not ingested: {error}"),
1784 None => format!("response not ingested: {error}"),
1785 });
1786 }
1787 }
1788
1789 if is_terminal
1790 && terminal_response.is_none()
1791 && matches!(kind, Some(OutgoingRequestKind::SigningKeysUpload) | Some(OutgoingRequestKind::SignaturesUpload))
1792 {
1793 self.xsign_inflight = false;
1794 }
1795
1796 if let Some(txn_id) = send_owner {
1797 if let Some(response) = &terminal_response {
1798 self.advance_send_on_success(&txn_id, &request_id, kind, response, now_ms);
1799 } else if let Some(errcode) = failed_errcode.clone() {
1800 self.fail_send(&txn_id, errcode, now_ms);
1801 }
1802 }
1803
1804 if matches!(kind, Some(OutgoingRequestKind::KeysUpload)) {
1808 if let Some(errcode) = failed_errcode {
1809 self.ingest_error = Some(format!("keys/upload failed: {errcode}"));
1810 }
1811 }
1812
1813 self.drain_events()
1814 }
1815
1816 pub fn on_transport_error(&mut self, request_id: &RequestId, now_ms: i64) {
1821 let _ = self.outgoing.on_transport_error(request_id, now_ms, self.jitter.as_mut());
1822 }
1823
1824 fn handle_terminal_success(
1825 &mut self,
1826 request_id: &RequestId,
1827 kind: Option<OutgoingRequestKind>,
1828 room_messages_room: Option<RoomId>,
1829 create_room_kind: Option<CreateRoomKind>,
1830 response: &HttpResponseDescriptor,
1831 ) -> Result<(), MessengerError> {
1832 match kind {
1833 Some(OutgoingRequestKind::Sync) => {
1834 let parsed = parse_sync_response(&response.body)?;
1835 self.ingest_sync(&parsed)?;
1836 }
1837 Some(OutgoingRequestKind::KeysUpload) => {
1838 self.account.on_keys_upload_response(&mut self.store)?;
1839 }
1840 Some(OutgoingRequestKind::KeysClaim) if self.history_claims.contains_key(request_id) => {
1841 if let Some(devices) = self.history_claims.remove(request_id) {
1842 let refs: Vec<&StoredDevice> = devices.iter().collect();
1843 let _ = OlmSessionManager::on_keys_claim_response(&mut self.store, &self.account, &refs, &response.body);
1844 }
1845 self.retry_missing_room_keys();
1846 }
1847 Some(OutgoingRequestKind::SigningKeysUpload) => {
1848 self.xsign_inflight = false;
1849 if let Some(state) = self.xsign.as_mut() {
1850 state.uploaded = true;
1851 let snapshot = state.clone();
1852 snapshot.save(&mut self.store)?;
1853 }
1854 self.maybe_enqueue_cross_signing()?;
1855 }
1856 Some(OutgoingRequestKind::SignaturesUpload) => {
1857 self.xsign_inflight = false;
1858 let failures_empty = serde_json::from_slice::<serde_json::Value>(&response.body)
1859 .ok()
1860 .and_then(|v| v.get("failures").and_then(|f| f.as_object()).map(|f| f.is_empty()))
1861 .unwrap_or(true);
1862 if failures_empty {
1863 if let Some(state) = self.xsign.as_mut() {
1864 state.device_signed = true;
1865 let snapshot = state.clone();
1866 snapshot.save(&mut self.store)?;
1867 }
1868 }
1869 }
1870 Some(OutgoingRequestKind::KeysQuery) => {
1871 let outcome = DeviceTracker::on_keys_query_response(&mut self.store, &response.body)?;
1872 if !outcome.key_changes.is_empty() {
1873 let room_ids: Vec<RoomId> = self.rooms.keys().cloned().collect();
1874 for change in &outcome.key_changes {
1875 let _ = GroupSessionManager::forget_shared_device(
1876 &mut self.store,
1877 &room_ids,
1878 &change.user_id,
1879 &change.device_id,
1880 );
1881 self.emit(MessengerEvent::DeviceKeyChanged {
1882 user_id: change.user_id.clone(),
1883 device_id: change.device_id.clone(),
1884 });
1885 }
1886 }
1887 self.retry_unknown_sender_queue()?;
1888 }
1889 Some(OutgoingRequestKind::RoomMessages) => {
1890 if let Some(room_id) = room_messages_room {
1891 self.ingest_room_messages_response(&room_id, &response.body)?;
1892 }
1893 }
1894 Some(OutgoingRequestKind::CreateRoom) => {
1895 if let Some(kind) = create_room_kind {
1896 self.on_create_room_response(kind, response)?;
1897 }
1898 }
1899 Some(OutgoingRequestKind::PublicRooms) if self.latest_public_rooms_request.as_ref() == Some(request_id) => {
1904 let body: PublicRoomsResponseBody = serde_json::from_slice(&response.body)?;
1905 self.public_rooms_result = body
1906 .chunk
1907 .into_iter()
1908 .map(|entry| PublicRoomsResultEntry {
1909 room_id: entry.room_id,
1910 name: entry.name,
1911 topic: entry.topic,
1912 num_joined_members: entry.num_joined_members,
1913 })
1914 .collect();
1915 self.emit(MessengerEvent::PublicRoomsChanged);
1916 }
1917 Some(OutgoingRequestKind::UserDirectorySearch) if self.latest_user_search_request.as_ref() == Some(request_id) => {
1919 let body: UserDirectorySearchResponseBody = serde_json::from_slice(&response.body)?;
1920 self.user_search_result = body
1921 .results
1922 .into_iter()
1923 .map(|entry| UserDirectoryResultEntry { user_id: entry.user_id, display_name: entry.display_name })
1924 .collect();
1925 self.emit(MessengerEvent::UserSearchChanged);
1926 }
1927 _ => {}
1928 }
1929 Ok(())
1930 }
1931
1932 fn on_create_room_response(&mut self, kind: CreateRoomKind, response: &HttpResponseDescriptor) -> Result<(), MessengerError> {
1938 let CreateRoomKind::Dm { peer } = kind else { return Ok(()) };
1939 let body: CreateRoomResponseBody = serde_json::from_slice(&response.body)?;
1940 self.merge_direct_account_data(body.room_id, peer)
1941 }
1942
1943 fn merge_direct_account_data(&mut self, room_id: RoomId, peer: UserId) -> Result<(), MessengerError> {
1955 let mut direct = self.direct_account_data.clone().unwrap_or_default();
1956 let rooms_for_peer = direct.0.entry(peer).or_default();
1957 if !rooms_for_peer.contains(&room_id) {
1958 rooms_for_peer.push(room_id);
1959 }
1960 let value = serde_json::to_value(&direct)?;
1961 self.write_account_data(None, "m.direct".to_string(), value)
1962 }
1963
1964 fn cached_account_data(&self, key: &AccountDataKey) -> Option<&serde_json::Value> {
1965 match &key.0 {
1966 Some(room_id) => self.room_account_data(room_id, &key.1),
1967 None => self.global_account_data(&key.1),
1968 }
1969 }
1970
1971 fn store_account_data_cache(&mut self, key: &AccountDataKey, value: Option<serde_json::Value>) {
1974 if key.0.is_none() && key.1 == "m.direct" {
1975 self.direct_account_data = value.as_ref().and_then(|v| serde_json::from_value::<DirectContent>(v.clone()).ok());
1976 }
1977 match (&key.0, value) {
1978 (None, Some(v)) => {
1979 self.global_account_data.insert(key.1.clone(), v);
1980 }
1981 (None, None) => {
1982 self.global_account_data.remove(&key.1);
1983 }
1984 (Some(room_id), Some(v)) => {
1985 self.room_account_data.entry(room_id.clone()).or_default().insert(key.1.clone(), v);
1986 }
1987 (Some(room_id), None) => {
1988 if let Some(by_type) = self.room_account_data.get_mut(room_id) {
1989 by_type.remove(&key.1);
1990 }
1991 }
1992 }
1993 }
1994
1995 fn write_account_data(&mut self, room_id: Option<RoomId>, event_type: String, value: serde_json::Value) -> Result<(), MessengerError> {
2001 let request_id = self.next_request_id()?;
2002 let request = match &room_id {
2003 Some(room_id) => OutgoingRequest::room_account_data(request_id.clone(), &self.config.user_id, room_id, &event_type, value.clone()),
2004 None => OutgoingRequest::account_data(request_id.clone(), &self.config.user_id, &event_type, value.clone()),
2005 };
2006 let key: AccountDataKey = (room_id, event_type);
2007 let known = self.cached_account_data(&key).cloned();
2008 let guard = self.account_data_guard.entry(key.clone()).or_insert(AccountDataGuard { pending: 0, server_value: known });
2009 guard.pending += 1;
2010 self.store_account_data_cache(&key, Some(value));
2011 match self.enqueue_request(request, Lane::AccountData) {
2012 Ok(()) => {
2013 self.account_data_write_keys.insert(request_id, key);
2014 Ok(())
2015 }
2016 Err(error) => {
2017 self.settle_account_data_write(&key, false);
2018 Err(error)
2019 }
2020 }
2021 }
2022
2023 fn settle_account_data_write(&mut self, key: &AccountDataKey, succeeded: bool) {
2028 let Some(guard) = self.account_data_guard.get_mut(key) else { return };
2029 guard.pending = guard.pending.saturating_sub(1);
2030 if guard.pending > 0 {
2031 return;
2032 }
2033 let Some(guard) = self.account_data_guard.remove(key) else { return };
2034 if !succeeded {
2035 self.store_account_data_cache(key, guard.server_value);
2036 self.emit(MessengerEvent::RoomsChanged);
2037 }
2038 }
2039
2040 fn apply_synced_account_data(&mut self, room_id: Option<&RoomId>, event_type: &str, content: serde_json::Value) {
2044 let key: AccountDataKey = (room_id.cloned(), event_type.to_string());
2045 if let Some(guard) = self.account_data_guard.get_mut(&key) {
2046 guard.server_value = Some(content);
2047 return;
2048 }
2049 self.store_account_data_cache(&key, Some(content));
2050 }
2051
2052 pub fn take_flush_batch(&mut self) -> Option<FlushBatch> {
2055 if let Some(batch) = self.pending_flush_batches.pop_front() {
2056 return Some(batch);
2057 }
2058 self.store.take_flush_batch()
2059 }
2060
2061 pub fn ack_flush(&mut self, id: u64) {
2063 self.store.ack_flush(id);
2064 }
2065
2066 pub fn events(&mut self) -> Vec<MessengerEvent> {
2069 self.drain_events()
2070 }
2071
2072 pub fn take_ingest_error(&mut self) -> Option<String> {
2075 self.ingest_error.take()
2076 }
2077
2078 pub fn change_counter(&self) -> u64 {
2083 self.change_counter
2084 }
2085
2086 pub fn room_ids(&self) -> impl Iterator<Item = &RoomId> {
2088 self.rooms.keys()
2089 }
2090
2091 pub fn room_state(&self, room_id: &RoomId) -> Option<&RoomState> {
2093 self.rooms.get(room_id)
2094 }
2095
2096 pub fn timeline(&self, room_id: &RoomId) -> Option<&Timeline> {
2098 self.timelines.get(room_id)
2099 }
2100
2101 pub fn room_kind(&self, room_id: &RoomId) -> Option<RoomKind> {
2105 self.rooms.get(room_id).map(|state| state.derive_room_kind(room_id, self.direct_account_data.as_ref()))
2106 }
2107
2108 pub fn typing_users(&self, room_id: &RoomId) -> &[UserId] {
2112 self.typing.get(room_id).map(Vec::as_slice).unwrap_or(&[])
2113 }
2114
2115 pub fn receipts(&self, room_id: &RoomId) -> Option<&ReceiptContent> {
2117 self.receipts.get(room_id)
2118 }
2119
2120 pub fn room_account_data(&self, room_id: &RoomId, event_type: &str) -> Option<&serde_json::Value> {
2123 self.room_account_data.get(room_id).and_then(|by_type| by_type.get(event_type))
2124 }
2125
2126 pub fn global_account_data(&self, event_type: &str) -> Option<&serde_json::Value> {
2129 self.global_account_data.get(event_type)
2130 }
2131
2132 pub fn key_request_count(&self) -> usize {
2134 self.key_requests_sent.len()
2135 }
2136
2137 pub fn user_id(&self) -> &UserId {
2142 &self.config.user_id
2143 }
2144
2145 pub fn has_tracked_device(&self, user_id: &UserId) -> bool {
2151 DeviceTracker::devices_for_user(&self.store, user_id).map(|devices| !devices.is_empty()).unwrap_or(false)
2152 }
2153
2154 pub fn public_rooms_result(&self) -> &[PublicRoomsResultEntry] {
2157 &self.public_rooms_result
2158 }
2159
2160 pub fn user_search_result(&self) -> &[UserDirectoryResultEntry] {
2163 &self.user_search_result
2164 }
2165
2166 pub fn send_failure_reason(&self, txn_id: &TxnId) -> Option<&str> {
2171 match self.send_state.get(txn_id).map(|pending| &pending.phase) {
2172 Some(SendPhase::Failed { errcode }) => Some(errcode.as_str()),
2173 _ => None,
2174 }
2175 }
2176
2177 pub fn dispatch(&mut self, cmd: MessengerCommand, now_ms: i64) -> Result<(), MessengerError> {
2184 match cmd {
2185 MessengerCommand::SendMessage { room_id, message, txn_id } => {
2186 if let Some(edit_of) = &message.edit_of {
2187 let owned = self
2188 .timelines
2189 .get(&room_id)
2190 .and_then(|timeline| timeline.item_by_event_id(edit_of))
2191 .is_some_and(|item| item.sender == self.config.user_id);
2192 if !owned {
2193 return Err(MessengerError::EditNotOwned { event_id: edit_of.clone() });
2194 }
2195 }
2196 self.start_new_send(room_id, SendPayload::Message(message), txn_id, now_ms)?;
2197 }
2198 MessengerCommand::Forward { from_room, event_id, to_room, txn_id } => {
2199 let payload = self.build_forward_payload(&from_room, &event_id, &to_room)?;
2200 self.start_new_send(to_room, payload, txn_id, now_ms)?;
2201 }
2202 MessengerCommand::React { room_id, target, key } => {
2203 self.start_new_send(room_id, SendPayload::Reaction { target, key }, None, now_ms)?;
2204 }
2205 MessengerCommand::Redact { room_id, target, reason } => {
2206 self.start_new_send(room_id, SendPayload::Redaction { target, reason }, None, now_ms)?;
2207 }
2208 MessengerCommand::RetrySend { room_id, txn_id } => {
2209 self.retry_send(&room_id, &txn_id, now_ms)?;
2210 }
2211 MessengerCommand::CreateRoom { kind } => {
2212 self.dispatch_create_room(kind)?;
2213 }
2214 MessengerCommand::JoinRoom { room_id } => {
2215 let request_id = self.next_request_id()?;
2216 let request = OutgoingRequest::join_room(request_id, room_id.as_str());
2217 self.enqueue_request(request, Lane::Other)?;
2218
2219 let dm_peer = self.rooms.get(&room_id).and_then(|room| {
2226 let invited_as_direct = room.members.get(&self.config.user_id).is_some_and(|member| member.is_direct);
2227 if !invited_as_direct {
2228 return None;
2229 }
2230 room.members.keys().find(|user_id| **user_id != self.config.user_id).cloned()
2231 });
2232 if let Some(peer) = dm_peer {
2233 self.merge_direct_account_data(room_id, peer)?;
2234 }
2235 }
2236 MessengerCommand::LeaveRoom { room_id } => {
2237 let request_id = self.next_request_id()?;
2238 let request = OutgoingRequest::leave_room(request_id, &room_id);
2239 self.enqueue_request(request, Lane::Other)?;
2240 }
2241 MessengerCommand::Invite { room_id, user_id } => {
2242 let request_id = self.next_request_id()?;
2243 let request = OutgoingRequest::invite(request_id, &room_id, &user_id);
2244 self.enqueue_request(request, Lane::Other)?;
2245 }
2246 MessengerCommand::Kick { room_id, user_id, reason } => {
2247 let request_id = self.next_request_id()?;
2248 let request = OutgoingRequest::kick(request_id, &room_id, &user_id, reason.as_deref());
2249 self.enqueue_request(request, Lane::Other)?;
2250 }
2251 MessengerCommand::SetPowerLevel { room_id, user_id, level } => {
2252 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())))?;
2253 content.users.insert(user_id, level);
2254 let body = serde_json::to_value(&content)?;
2255 let request_id = self.next_request_id()?;
2256 let request = OutgoingRequest::set_state(request_id, &room_id, "m.room.power_levels", "", body);
2257 self.enqueue_request(request, Lane::Other)?;
2258 }
2259 MessengerCommand::SetEventsDefault { room_id, level } => {
2260 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())))?;
2261 content.events_default = level;
2262 let body = serde_json::to_value(&content)?;
2263 let request_id = self.next_request_id()?;
2264 let request = OutgoingRequest::set_state(request_id, &room_id, "m.room.power_levels", "", body);
2265 self.enqueue_request(request, Lane::Other)?;
2266 }
2267 MessengerCommand::SetTag { room_id, tag, order } => {
2268 let mut content = self.current_tag_content(&room_id);
2269 content.tags.insert(tag, TagInfo { order });
2270 self.enqueue_tag_update(room_id, content)?;
2271 }
2272 MessengerCommand::RemoveTag { room_id, tag } => {
2273 let mut content = self.current_tag_content(&room_id);
2274 content.tags.remove(&tag);
2275 self.enqueue_tag_update(room_id, content)?;
2276 }
2277 MessengerCommand::MarkRead { room_id, event_id } => {
2278 let request_id = self.next_request_id()?;
2279 let body = serde_json::json!({ "m.fully_read": event_id.as_str(), "m.read": event_id.as_str() });
2280 let request = OutgoingRequest::read_markers(request_id, &room_id, body);
2281 self.enqueue_request(request, Lane::Other)?;
2282 }
2283 MessengerCommand::SetTyping { room_id, typing } => {
2284 if typing {
2285 let should_send = match self.typing_debounce.get(&room_id) {
2286 Some(&last_sent_ms) => now_ms.saturating_sub(last_sent_ms) >= TYPING_DEBOUNCE_MS,
2287 None => true,
2288 };
2289 if !should_send {
2290 return Ok(());
2291 }
2292 self.typing_debounce.insert(room_id.clone(), now_ms);
2293 } else {
2294 self.typing_debounce.remove(&room_id);
2295 }
2296 let request_id = self.next_request_id()?;
2297 let timeout_ms = typing.then_some(TYPING_TIMEOUT_MS);
2298 let request = OutgoingRequest::typing(request_id, &room_id, &self.config.user_id, typing, timeout_ms);
2299 self.enqueue_request(request, Lane::Other)?;
2300 }
2301 MessengerCommand::RetryDecryption { room_id, event_id } => {
2302 if self.retry_decrypt_one(&room_id, &event_id) {
2303 self.emit(MessengerEvent::TimelineChanged { room_id });
2304 }
2305 }
2306 MessengerCommand::LoadOlder { room_id } => {
2307 let from = self
2308 .timelines
2309 .get(&room_id)
2310 .and_then(|timeline| {
2311 timeline
2312 .older_token()
2313 .map(str::to_string)
2314 .or_else(|| timeline.gap().and_then(|gap| gap.prev_batch.clone()))
2315 })
2316 .or_else(|| {
2322 if self.rooms.contains_key(&room_id) {
2323 self.store.sync_token().ok().flatten().map(str::to_string)
2324 } else {
2325 None
2326 }
2327 });
2328 let Some(from) = from else { return Ok(()) };
2329 let request_id = self.next_request_id()?;
2330 let request =
2331 OutgoingRequest::room_messages(request_id.clone(), &room_id, &from, "b", Some(LOAD_OLDER_PAGE_SIZE));
2332 self.enqueue_request(request, Lane::Room(room_id.clone()))?;
2333 self.pending_room_messages.insert(request_id, room_id);
2334 }
2335 MessengerCommand::SetAccountData { event_type, content } => {
2336 self.write_account_data(None, event_type, content)?;
2337 }
2338 MessengerCommand::SetRoomAccountData { room_id, event_type, content } => {
2339 self.write_account_data(Some(room_id), event_type, content)?;
2340 }
2341 MessengerCommand::SearchPublicRooms { term } => {
2342 let request_id = self.next_request_id()?;
2343 let body = serde_json::json!({ "filter": { "generic_search_term": term }, "limit": 50 });
2344 let request = OutgoingRequest::public_rooms(request_id.clone(), body);
2345 self.latest_public_rooms_request = Some(request_id);
2346 self.enqueue_request(request, Lane::Other)?;
2347 }
2348 MessengerCommand::SearchUsers { term } => {
2349 let request_id = self.next_request_id()?;
2350 let body = serde_json::json!({ "search_term": term, "limit": 20 });
2351 let request = OutgoingRequest::user_directory_search(request_id.clone(), body);
2352 self.latest_user_search_request = Some(request_id);
2353 self.enqueue_request(request, Lane::Other)?;
2354 }
2355 }
2356 Ok(())
2357 }
2358
2359 fn dispatch_create_room(&mut self, kind: CreateRoomKind) -> Result<(), MessengerError> {
2364 let body = match &kind {
2365 CreateRoomKind::Dm { peer } => serde_json::json!({
2366 "is_direct": true,
2367 "invite": [peer.as_str()],
2368 }),
2369 CreateRoomKind::Group { name, invite, members_can_invite } => serde_json::json!({
2370 "visibility": "private",
2371 "is_direct": false,
2372 "name": name,
2373 "invite": invite.iter().map(UserId::as_str).collect::<Vec<_>>(),
2374 "members_can_invite": members_can_invite,
2375 }),
2376 CreateRoomKind::Channel { name, topic } => {
2377 let mut value = serde_json::json!({
2378 "visibility": "public",
2379 "is_direct": false,
2380 "name": name,
2381 });
2382 if let Some(topic) = topic {
2383 value["topic"] = serde_json::Value::String(topic.clone());
2384 }
2385 value
2386 }
2387 };
2388 let request_id = self.next_request_id()?;
2389 let request = OutgoingRequest::create_room(request_id.clone(), body);
2390 self.pending_create_room.insert(request_id.clone(), kind);
2391 self.enqueue_request(request, Lane::Other)?;
2392 Ok(())
2393 }
2394
2395 fn current_tag_content(&self, room_id: &RoomId) -> TagContent {
2400 self.room_account_data(room_id, "m.tag")
2401 .and_then(|value| serde_json::from_value(value.clone()).ok())
2402 .unwrap_or_default()
2403 }
2404
2405 fn enqueue_tag_update(&mut self, room_id: RoomId, content: TagContent) -> Result<(), MessengerError> {
2411 let value = serde_json::to_value(&content)?;
2412 self.write_account_data(Some(room_id), "m.tag".to_string(), value)
2413 }
2414
2415 fn build_forward_payload(
2420 &self,
2421 from_room: &RoomId,
2422 event_id: &EventId,
2423 to_room: &RoomId,
2424 ) -> Result<SendPayload, MessengerError> {
2425 let refuse = |reason: &dyn std::fmt::Display| {
2426 MessengerError::IntentRefused(format!("cannot forward {event_id} from {from_room}: {reason}"))
2427 };
2428 if !self.rooms.contains_key(to_room) {
2429 return Err(refuse(&format!("the target room {to_room} is unknown")));
2430 }
2431 let source = match self.timelines.get(from_room) {
2432 Some(timeline) => timeline.forward_source(event_id),
2433 None => Err(ForwardRefusal::Missing),
2434 }
2435 .map_err(|reason| refuse(&reason))?;
2436 let (marker_key, marker) = self.forward_marker(from_room);
2437 Ok(SendPayload::Event {
2438 event_type: source.event_type,
2439 content: forwardable_content(source.content, marker_key, marker),
2440 })
2441 }
2442
2443 fn forward_marker(&self, from_room: &RoomId) -> (&'static str, serde_json::Value) {
2448 let public_channel = self.room_kind(from_room) == Some(RoomKind::Channel);
2449 if public_channel {
2450 let room_name = self.forward_room_name(from_room);
2451 (KEY_FORWARDED_FROM, serde_json::json!({ "room_id": from_room.as_str(), "room_name": room_name }))
2452 } else {
2453 (KEY_FORWARDED, serde_json::Value::Bool(true))
2454 }
2455 }
2456
2457 fn forward_room_name(&self, room_id: &RoomId) -> String {
2461 if let Some(name) = self.rooms.get(room_id).and_then(|room| room.name.clone()) {
2462 return name;
2463 }
2464 match self.room_kind(room_id) {
2465 Some(RoomKind::Dm) => "Direct",
2466 Some(RoomKind::Channel) => "Channel",
2467 Some(RoomKind::Group) | None => "Group",
2468 }
2469 .to_string()
2470 }
2471
2472 fn retry_decrypt_one(&mut self, room_id: &RoomId, event_id: &EventId) -> bool {
2478 let Some(item) = self.pending_decrypt.get(room_id).and_then(|by_event| by_event.get(event_id)).cloned() else {
2479 return false;
2480 };
2481 match GroupSessionManager::decrypt_event(
2482 &mut self.store,
2483 room_id,
2484 event_id,
2485 item.origin_server_ts,
2486 &item.sender,
2487 &item.content,
2488 ) {
2489 Ok(plaintext) => {
2490 self.apply_decrypted_plaintext(room_id, event_id, &item.sender, item.origin_server_ts, &plaintext);
2491 }
2492 Err(GroupDecryptError::MissingSession { .. }) => {
2493 let _ = self.request_missing_room_key(room_id, &item.sender, &item.content);
2494 return false;
2495 }
2496 Err(_) => return false,
2497 }
2498 if let Some(by_event) = self.pending_decrypt.get_mut(room_id) {
2499 by_event.remove(event_id);
2500 }
2501 true
2502 }
2503
2504 pub fn pull_latest_page(&mut self, room_id: RoomId) -> Result<(), MessengerError> {
2507 let request_id = self.next_request_id()?;
2508 let request = OutgoingRequest::room_messages_latest(request_id.clone(), &room_id, 20);
2509 self.enqueue_request(request, Lane::Room(room_id.clone()))?;
2510 self.pending_room_messages.insert(request_id, room_id);
2511 Ok(())
2512 }
2513
2514 pub fn decrypt_loaded_timeline(&mut self) {
2518 let pending: Vec<(RoomId, Vec<EventId>)> = self
2519 .pending_decrypt
2520 .iter()
2521 .map(|(room_id, by_event)| (room_id.clone(), by_event.keys().cloned().collect()))
2522 .collect();
2523 for (room_id, event_ids) in pending {
2524 for event_id in event_ids {
2525 self.retry_decrypt_one(&room_id, &event_id);
2526 }
2527 }
2528 let rooms: Vec<RoomId> = self.timelines.keys().cloned().collect();
2529 for room_id in rooms {
2530 let sealed: Vec<(EventId, i64, UserId, MegolmEncryptedContent)> = {
2531 let Some(timeline) = self.timelines.get(&room_id) else {
2532 continue;
2533 };
2534 timeline
2535 .items()
2536 .iter()
2537 .filter_map(|item| {
2538 let event_id = item.event_id.clone()?;
2539 let (session_id, ciphertext) = match &item.content {
2540 ItemContent::Encrypted { session_id, ciphertext_b64 } => {
2541 (session_id.clone(), ciphertext_b64.clone())
2542 }
2543 _ => return None,
2544 };
2545 Some((
2546 event_id,
2547 item.origin_server_ts,
2548 item.sender.clone(),
2549 MegolmEncryptedContent {
2550 ciphertext,
2551 session_id,
2552 sender_key: None,
2553 device_id: None,
2554 relates_to: None,
2555 },
2556 ))
2557 })
2558 .collect()
2559 };
2560 for (event_id, origin_server_ts, sender, content) in sealed {
2561 let plaintext = match GroupSessionManager::decrypt_event(
2562 &mut self.store,
2563 &room_id,
2564 &event_id,
2565 origin_server_ts,
2566 &sender,
2567 &content,
2568 ) {
2569 Ok(plaintext) => plaintext,
2570 Err(GroupDecryptError::MissingSession { .. }) => {
2571 let _ = self.request_missing_room_key(&room_id, &sender, &content);
2572 continue;
2573 }
2574 Err(_) => continue,
2575 };
2576 self.apply_decrypted_plaintext(
2577 &room_id,
2578 &event_id,
2579 &sender,
2580 origin_server_ts,
2581 &plaintext,
2582 );
2583 }
2584 }
2585 }
2586
2587 fn apply_decrypted_plaintext(
2600 &mut self,
2601 room_id: &RoomId,
2602 event_id: &EventId,
2603 sender: &UserId,
2604 origin_server_ts: i64,
2605 plaintext: &RoomEventPlaintext,
2606 ) {
2607 let Some(timeline) = self.timelines.get_mut(room_id) else { return };
2608 if let Some(relation) = RelatesTo::from_content(&plaintext.content) {
2609 if matches!(relation, RelatesTo::Annotation { .. } | RelatesTo::Replace { .. }) {
2610 let new_content = plaintext.content.get("m.new_content").cloned();
2611 timeline.apply_decrypted_relation(event_id, sender.clone(), origin_server_ts, relation, new_content);
2612 return;
2613 }
2614 }
2615 timeline.set_decrypted_event(event_id, &plaintext.event_type, &plaintext.content);
2616 }
2617
2618 fn retry_pending_for_session(&mut self, room_id: &RoomId, session_id: &str) {
2619 let Some(by_event) = self.pending_decrypt.get(room_id) else { return };
2620 let candidates: Vec<EventId> =
2621 by_event.iter().filter(|(_, item)| item.session_id == session_id).map(|(event_id, _)| event_id.clone()).collect();
2622 let mut changed = false;
2623 for event_id in candidates {
2624 if self.retry_decrypt_one(room_id, &event_id) {
2625 changed = true;
2626 }
2627 }
2628 if changed {
2629 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
2630 }
2631 }
2632
2633 pub fn republish_device_keys(&mut self) {
2636 self.account.forget_published_device_keys();
2637 }
2638
2639 fn ingest_sync(&mut self, response: &crate::wire::sync::SyncResponse) -> Result<(), MessengerError> {
2640 let mut newly_received_sessions: Vec<(RoomId, String)> = Vec::new();
2641
2642 for event in &response.to_device.events {
2644 self.process_to_device_event(event, &mut newly_received_sessions)?;
2645 }
2646
2647 DeviceTracker::on_device_lists(&mut self.store, &response.device_lists.changed, &response.device_lists.left)?;
2649
2650 let published_otk_count = response.device_one_time_keys_count.get("signed_curve25519").copied().unwrap_or(0);
2652 self.account.on_sync_counts(&mut self.store, published_otk_count, &response.device_unused_fallback_key_types)?;
2653 let keys_upload_request_id = self.next_request_id()?;
2654 if let Some(request) =
2655 self.account.keys_upload_request(keys_upload_request_id, &self.config.user_id, &self.config.device_id)?
2656 {
2657 self.enqueue_request(request, Lane::Other)?;
2658 }
2659 self.maybe_enqueue_cross_signing()?;
2660
2661 let outdated_before_rooms = self.outdated_tracked_users()?;
2663 if !outdated_before_rooms.is_empty() {
2664 self.enqueue_keys_query()?;
2665 }
2666
2667 let rooms_touched =
2669 !response.rooms.join.is_empty() || !response.rooms.invite.is_empty() || !response.rooms.leave.is_empty();
2670 for (room_id, joined) in &response.rooms.join {
2671 self.ingest_joined_room(room_id, joined)?;
2672 }
2673 for (room_id, invited) in &response.rooms.invite {
2674 self.ingest_invited_room(room_id, invited)?;
2675 }
2676 for (room_id, left) in &response.rooms.leave {
2677 self.ingest_left_room(room_id, left)?;
2678 }
2679 if rooms_touched {
2680 self.emit(MessengerEvent::RoomsChanged);
2681 }
2682 if !self.outdated_tracked_users()?.is_subset(&outdated_before_rooms) {
2686 self.enqueue_keys_query()?;
2687 }
2688
2689 for event in &response.account_data.events {
2691 self.apply_synced_account_data(None, &event.event_type, event.content.clone());
2692 }
2693
2694 for (room_id, session_id) in &newly_received_sessions {
2695 self.retry_pending_for_session(room_id, session_id);
2696 }
2697
2698 self.store.save_sync_token(response.next_batch.clone())?;
2701 Ok(())
2702 }
2703
2704 fn process_to_device_event(
2705 &mut self,
2706 event: &ToDeviceEvent,
2707 newly_received_sessions: &mut Vec<(RoomId, String)>,
2708 ) -> Result<(), MessengerError> {
2709 if event.event_type != "m.room.encrypted" {
2710 return Ok(());
2711 }
2712 let Ok(RoomEncryptedContent::Olm(content)) = serde_json::from_value::<RoomEncryptedContent>(event.content.clone())
2713 else {
2714 return Ok(());
2715 };
2716 match OlmSessionManager::decrypt_to_device(&mut self.store, &mut self.account, &self.config.user_id, &event.sender, &content) {
2717 Ok(decrypted) => self.route_decrypted_to_device(decrypted, newly_received_sessions),
2718 Err(OlmDecryptError::UnknownSenderDevice { .. }) => {
2719 self.queue_unknown_sender_retry(event.clone());
2720 Ok(())
2721 }
2722 Err(_) => Ok(()),
2729 }
2730 }
2731
2732 fn route_decrypted_to_device(
2733 &mut self,
2734 decrypted: DecryptedToDevice,
2735 newly_received_sessions: &mut Vec<(RoomId, String)>,
2736 ) -> Result<(), MessengerError> {
2737 match decrypted.event_type.as_str() {
2738 "m.room_key" => {
2739 if let Ok(content) = serde_json::from_value::<RoomKeyContent>(decrypted.content.clone()) {
2740 if content.algorithm == "m.megolm.v1.aes-sha2" {
2741 GroupSessionManager::receive_room_key(
2742 &mut self.store,
2743 &decrypted.sender,
2744 decrypted.sender_device_curve25519,
2745 decrypted.sender_ed25519,
2746 &content,
2747 )?;
2748 newly_received_sessions.push((content.room_id, content.session_id));
2749 }
2750 }
2751 }
2752 "m.forwarded_room_key" => {
2753 if let Ok(content) = serde_json::from_value::<ForwardedRoomKeyContent>(decrypted.content.clone()) {
2754 if let Some((user_id, curve, ed)) = self.forward_binding(&decrypted, &content)? {
2755 let stored = GroupSessionManager::receive_forwarded_room_key(
2756 &mut self.store,
2757 &user_id,
2758 curve,
2759 ed,
2760 &content,
2761 )?;
2762 if stored {
2763 newly_received_sessions.push((content.room_id, content.session_id));
2764 }
2765 }
2766 }
2767 }
2768 "m.room_key_request" => {
2769 if let Ok(content) = serde_json::from_value::<RoomKeyRequestContent>(decrypted.content.clone()) {
2770 self.answer_room_key_request(&decrypted, &content)?;
2771 }
2772 }
2773 "m.room_key.withheld" => {
2774 if let Ok(content) = serde_json::from_value::<RoomKeyWithheldContent>(decrypted.content.clone()) {
2775 if let (Some(room_id), Some(session_id)) = (content.room_id, content.session_id) {
2776 self.withheld_sessions.insert((room_id, session_id));
2777 }
2778 }
2779 }
2780 _ => {}
2781 }
2782 Ok(())
2783 }
2784
2785 fn forward_binding(
2791 &self,
2792 decrypted: &DecryptedToDevice,
2793 content: &ForwardedRoomKeyContent,
2794 ) -> Result<Option<(UserId, mail4agent_vodozemac::Curve25519PublicKey, mail4agent_vodozemac::Ed25519PublicKey)>, MessengerError>
2795 {
2796 let members = self.encrypted_room_member_ids(&content.room_id);
2797 let forwarder_is_us = decrypted.sender == self.config.user_id
2800 || (self.history_forwarding_allowed(&content.room_id) && self.room_admins(&content.room_id).contains(&decrypted.sender));
2801 if !forwarder_is_us && !members.contains(&decrypted.sender) {
2802 return Ok(None);
2803 }
2804 for user_id in &members {
2805 for device in DeviceTracker::devices_for_user(&self.store, user_id)? {
2806 if device.blocked || device.curve25519.to_base64() != content.sender_key {
2807 continue;
2808 }
2809 let same_user = decrypted.sender == device.user_id;
2810 if same_user || forwarder_is_us {
2811 return Ok(Some((device.user_id, device.curve25519, device.ed25519)));
2812 }
2813 }
2814 }
2815 if decrypted.sender_device_curve25519.to_base64() == content.sender_key {
2816 return Ok(Some((
2817 decrypted.sender.clone(),
2818 decrypted.sender_device_curve25519,
2819 decrypted.sender_ed25519,
2820 )));
2821 }
2822 Ok(None)
2823 }
2824
2825 fn answer_room_key_request(
2830 &mut self,
2831 decrypted: &DecryptedToDevice,
2832 content: &RoomKeyRequestContent,
2833 ) -> Result<(), MessengerError> {
2834 if matches!(content.action, RoomKeyRequestAction::RequestCancellation) {
2835 self.key_requests_answered.remove(&(
2836 decrypted.sender.clone(),
2837 content.requesting_device_id.clone(),
2838 content.request_id.clone(),
2839 ));
2840 return Ok(());
2841 }
2842 if !matches!(content.action, RoomKeyRequestAction::Request) {
2843 return Ok(());
2844 }
2845 let Some(body) = content.body.as_ref() else { return Ok(()) };
2846 if body.algorithm != "m.megolm.v1.aes-sha2" {
2847 return Ok(());
2848 }
2849 let members = self.encrypted_room_member_ids(&body.room_id);
2850 if !members.contains(&decrypted.sender) {
2851 return Ok(());
2852 }
2853 if decrypted.sender != self.config.user_id && !self.history_forwarding_allowed(&body.room_id) {
2854 return Ok(());
2855 }
2856 if decrypted.sender == self.config.user_id && content.requesting_device_id == self.config.device_id {
2857 return Ok(());
2858 }
2859 let dedup = (decrypted.sender.clone(), content.requesting_device_id.clone(), content.request_id.clone());
2860 if self.key_requests_answered.contains(&dedup) {
2861 return Ok(());
2862 }
2863 let Some(target) = self.requester_device(decrypted, &content.requesting_device_id)? else {
2864 return Ok(());
2865 };
2866 let our_curve = self.account.identity_keys().curve25519.to_base64();
2867 let (inner_type, payload) =
2868 if let Some(forwarded) = GroupSessionManager::forwarded_room_key_content(
2869 &self.store,
2870 &body.room_id,
2871 &body.session_id,
2872 &our_curve,
2873 )? {
2874 ("m.forwarded_room_key", serde_json::to_value(&forwarded)?)
2875 } else if let Some(room_key) =
2876 GroupSessionManager::outbound_room_key_content(&self.store, &body.room_id, &body.session_id)?
2877 {
2878 ("m.room_key", serde_json::to_value(&room_key)?)
2879 } else {
2880 return Ok(());
2881 };
2882 let sent = self.enqueue_olm_to_devices(std::slice::from_ref(&target), inner_type, payload)?;
2883 if sent > 0 {
2884 self.key_requests_answered.insert(dedup);
2885 }
2886 Ok(())
2887 }
2888
2889 fn requester_device(
2893 &self,
2894 decrypted: &DecryptedToDevice,
2895 requesting_device_id: &DeviceId,
2896 ) -> Result<Option<StoredDevice>, MessengerError> {
2897 let devices = DeviceTracker::devices_for_user(&self.store, &decrypted.sender)?;
2898 if let Some(device) = devices.iter().find(|device| &device.device_id == requesting_device_id) {
2899 return Ok((!device.blocked).then(|| device.clone()));
2900 }
2901 let curve = decrypted.sender_device_curve25519.to_base64();
2902 if let Some(device) = devices.iter().find(|device| device.curve25519.to_base64() == curve) {
2903 return Ok((!device.blocked).then(|| device.clone()));
2904 }
2905 Ok(None)
2906 }
2907
2908 fn request_missing_room_key(
2913 &mut self,
2914 room_id: &RoomId,
2915 sender: &UserId,
2916 content: &MegolmEncryptedContent,
2917 ) -> Result<(), MessengerError> {
2918 let key = (room_id.clone(), content.session_id.clone());
2919 if self.key_requests_sent.contains(&key) || self.withheld_sessions.contains(&key) {
2920 return Ok(());
2921 }
2922 let sender_key = match content.sender_key.clone() {
2923 Some(sender_key) => sender_key,
2924 None => {
2925 let devices = DeviceTracker::devices_for_user(&self.store, sender)?;
2926 let found = content
2927 .device_id
2928 .as_ref()
2929 .and_then(|device_id| devices.iter().find(|device| &device.device_id == device_id))
2930 .or_else(|| devices.iter().find(|device| !device.blocked));
2931 match found {
2932 Some(device) => device.curve25519.to_base64(),
2933 None => return Ok(()),
2934 }
2935 }
2936 };
2937 let mut targets = DeviceTracker::devices_for_user(&self.store, sender)?;
2938 targets.extend(DeviceTracker::devices_for_user(&self.store, &self.config.user_id)?);
2939 if self.history_forwarding_allowed(room_id) {
2942 for admin in self.room_admins(room_id) {
2943 if &admin != sender && admin != self.config.user_id {
2944 targets.extend(DeviceTracker::devices_for_user(&self.store, &admin)?);
2945 }
2946 }
2947 }
2948 if targets.is_empty() {
2949 return Ok(());
2950 }
2951 let missing: Vec<StoredDevice> = OlmSessionManager::sessions_missing_for(&self.store, &targets)?
2955 .into_iter()
2956 .filter(|device| {
2957 !device.blocked
2958 && !(device.user_id == self.config.user_id && device.device_id == self.config.device_id)
2959 && !self.history_claim_tried.contains(&(device.user_id.clone(), device.device_id.clone()))
2960 })
2961 .cloned()
2962 .collect();
2963 if !missing.is_empty() {
2964 let claim_id = self.next_request_id()?;
2965 let refs: Vec<&StoredDevice> = missing.iter().collect();
2966 if let Some(request) = OlmSessionManager::keys_claim_request(claim_id.clone(), &refs) {
2967 for device in &missing {
2968 self.history_claim_tried.insert((device.user_id.clone(), device.device_id.clone()));
2969 }
2970 self.history_claims.insert(claim_id, missing);
2971 self.enqueue_request(request, Lane::Other)?;
2972 return Ok(());
2973 }
2974 }
2975 let request_id = self.next_txn_id()?;
2976 let body = RoomKeyRequestContent {
2977 action: RoomKeyRequestAction::Request,
2978 body: Some(RoomKeyRequestBody {
2979 algorithm: "m.megolm.v1.aes-sha2".to_string(),
2980 room_id: room_id.clone(),
2981 session_id: content.session_id.clone(),
2982 sender_key,
2983 }),
2984 request_id: request_id.as_str().to_string(),
2985 requesting_device_id: self.config.device_id.clone(),
2986 };
2987 let sent = self.enqueue_olm_to_devices(&targets, "m.room_key_request", serde_json::to_value(&body)?)?;
2988 if sent > 0 {
2989 self.key_requests_sent.insert(key);
2990 }
2991 Ok(())
2992 }
2993
2994 fn enqueue_olm_to_devices(
2999 &mut self,
3000 devices: &[StoredDevice],
3001 inner_type: &str,
3002 content: serde_json::Value,
3003 ) -> Result<usize, MessengerError> {
3004 let mut unique = Vec::new();
3005 for device in devices {
3006 if device.blocked || (device.user_id == self.config.user_id && device.device_id == self.config.device_id) {
3007 continue;
3008 }
3009 if unique.iter().any(|have: &StoredDevice| have.user_id == device.user_id && have.device_id == device.device_id)
3010 {
3011 continue;
3012 }
3013 unique.push(device.clone());
3014 }
3015 let missing = OlmSessionManager::sessions_missing_for(&self.store, &unique)?;
3016 let missing_ids: BTreeSet<(UserId, DeviceId)> =
3017 missing.into_iter().map(|device| (device.user_id.clone(), device.device_id.clone())).collect();
3018 let ready: Vec<StoredDevice> = unique
3019 .into_iter()
3020 .filter(|device| !missing_ids.contains(&(device.user_id.clone(), device.device_id.clone())))
3021 .collect();
3022 let mut sent = 0;
3023 for chunk in GroupSessionManager::chunk_recipients_for_send_to_device(&ready) {
3024 let body = GroupSessionManager::build_encrypted_to_device_body(
3025 &mut self.store,
3026 &self.account,
3027 &self.config.user_id,
3028 &self.config.device_id,
3029 chunk,
3030 inner_type,
3031 content.clone(),
3032 )?;
3033 let request_id = self.next_request_id()?;
3034 let share_txn = self.next_txn_id()?;
3035 let request = OutgoingRequest::send_to_device(request_id, ROOM_KEY_SHARE_EVENT_TYPE, &share_txn, body);
3036 self.enqueue_request(request, Lane::ToDevice)?;
3037 sent += chunk.len();
3038 }
3039 Ok(sent)
3040 }
3041
3042 fn queue_unknown_sender_retry(&mut self, event: ToDeviceEvent) {
3043 self.unknown_sender_retry.push_back(event);
3044 while self.unknown_sender_retry.len() > UNKNOWN_SENDER_RETRY_CAPACITY {
3045 self.unknown_sender_retry.pop_front();
3046 }
3047 }
3048
3049 fn retry_unknown_sender_queue(&mut self) -> Result<(), MessengerError> {
3050 let pending: Vec<ToDeviceEvent> = std::mem::take(&mut self.unknown_sender_retry).into_iter().collect();
3051 let mut newly_received_sessions = Vec::new();
3052 for event in pending {
3053 self.process_to_device_event(&event, &mut newly_received_sessions)?;
3054 }
3055 for (room_id, session_id) in &newly_received_sessions {
3056 self.retry_pending_for_session(room_id, session_id);
3057 }
3058 Ok(())
3059 }
3060
3061 fn outdated_tracked_users(&self) -> Result<BTreeSet<UserId>, MessengerError> {
3063 Ok(self.store.tracked_users()?.into_iter().filter_map(|(user_id, outdated)| outdated.then_some(user_id)).collect())
3064 }
3065
3066 fn enqueue_keys_query(&mut self) -> Result<(), MessengerError> {
3068 let request_id = self.next_request_id()?;
3069 if let Some(request) = DeviceTracker::keys_query_request(&self.store, request_id)? {
3070 self.enqueue_request(request, Lane::Other)?;
3071 }
3072 Ok(())
3073 }
3074
3075 fn retry_missing_room_keys(&mut self) {
3085 let work: Vec<(RoomId, EventId)> = self
3086 .pending_decrypt
3087 .iter()
3088 .flat_map(|(room, by_event)| by_event.keys().map(move |event| (room.clone(), event.clone())))
3089 .collect();
3090 for (room_id, event_id) in work {
3091 let _ = self.retry_decrypt_one(&room_id, &event_id);
3092 }
3093 }
3094
3095 fn room_admins(&self, room_id: &RoomId) -> BTreeSet<UserId> {
3100 let Some(state) = self.room_state(room_id) else { return BTreeSet::new() };
3101 self.encrypted_room_member_ids(room_id)
3102 .into_iter()
3103 .filter(|u| state.power_levels.users.get(u).copied().unwrap_or(state.power_levels.users_default) >= 100)
3104 .collect()
3105 }
3106
3107 fn history_forwarding_allowed(&self, room_id: &RoomId) -> bool {
3108 !matches!(
3109 self.rooms.get(room_id).and_then(|room| room.history_visibility.clone()),
3110 Some(crate::wire::events::HistoryVisibility::Joined) | Some(crate::wire::events::HistoryVisibility::Invited)
3111 )
3112 }
3113
3114 fn maybe_enqueue_cross_signing(&mut self) -> Result<(), MessengerError> {
3117 if self.xsign_inflight || self.xsign_attempts >= 4 {
3118 return Ok(());
3119 }
3120 if self.xsign.is_none() {
3121 self.xsign = Some(CrossSigningState::load_or_create(&mut self.store)?);
3122 }
3123 let Some(state) = self.xsign.clone() else { return Ok(()) };
3124 let user_id = self.config.user_id.clone();
3125 let request_id;
3126 let request = if !state.uploaded {
3127 request_id = self.next_request_id()?;
3128 OutgoingRequest::signing_keys_upload(request_id, state.upload_body(&user_id)?)
3129 } else if !state.device_signed {
3130 let device_id = self.config.device_id.clone();
3131 let device_keys = self.account.device_keys_json(&user_id, &device_id)?;
3132 request_id = self.next_request_id()?;
3133 OutgoingRequest::signatures_upload(request_id, state.device_signature_body(&user_id, &device_id, device_keys)?)
3134 } else {
3135 return Ok(());
3136 };
3137 self.xsign_attempts += 1;
3138 self.xsign_inflight = true;
3139 self.enqueue_request(request, Lane::Other)
3140 }
3141
3142 fn track_joined_members<'a>(&mut self, room_id: &RoomId, events: impl Iterator<Item = &'a RawEvent>) -> Result<(), MessengerError> {
3143 if self.rooms.get(room_id).is_none_or(|room| room.encryption.is_none()) {
3144 return Ok(());
3145 }
3146 let joined: Vec<UserId> = events
3147 .filter(|event| event.event_type == "m.room.member")
3148 .filter(|event| event.content.get("membership").and_then(serde_json::Value::as_str) == Some("join"))
3149 .filter_map(|event| event.state_key.as_deref().and_then(|state_key| UserId::parse(state_key).ok()))
3150 .filter(|user_id| *user_id != self.config.user_id)
3151 .collect();
3152 if joined.is_empty() {
3153 return Ok(());
3154 }
3155 DeviceTracker::on_device_lists(&mut self.store, &joined, &[])
3156 }
3157
3158 fn ingest_joined_room(&mut self, room_id: &RoomId, joined: &JoinedRoom) -> Result<(), MessengerError> {
3159 for event in joined.state.events.iter().chain(joined.timeline.events.iter()) {
3160 self.rooms.entry(room_id.clone()).or_default().apply_state_event(event)?;
3161 }
3162
3163 self.track_joined_members(room_id, joined.state.events.iter().chain(joined.timeline.events.iter()))?;
3164
3165 let unread_before = self.rooms.get(room_id).map(RoomState::unread_count);
3166 self.rooms.entry(room_id.clone()).or_default().apply_summary(&joined.summary, &joined.unread_notifications);
3167 let unread_after = self.rooms.get(room_id).map(RoomState::unread_count);
3168 if unread_before != unread_after {
3169 self.emit(MessengerEvent::UnreadChanged { room_id: room_id.clone() });
3170 }
3171
3172 for event in &joined.account_data.events {
3173 self.apply_synced_account_data(Some(room_id), &event.event_type, event.content.clone());
3174 }
3175
3176 if let Some(typing_event) = joined.ephemeral.events.iter().find(|event| event.event_type == "m.typing") {
3177 if let Ok(typing) = serde_json::from_value::<TypingContent>(typing_event.content.clone()) {
3178 self.typing.insert(room_id.clone(), typing.user_ids);
3179 self.emit(MessengerEvent::TypingChanged { room_id: room_id.clone() });
3180 }
3181 }
3182 if let Some(receipt_event) = joined.ephemeral.events.iter().find(|event| event.event_type == "m.receipt") {
3183 if let Ok(receipts) = serde_json::from_value::<ReceiptContent>(receipt_event.content.clone()) {
3184 self.receipts.insert(room_id.clone(), receipts);
3185 self.emit(MessengerEvent::ReceiptsChanged { room_id: room_id.clone() });
3186 }
3187 }
3188
3189 if !joined.timeline.events.is_empty() {
3190 self.timelines.entry(room_id.clone()).or_default().apply_timeline_batch(
3191 &joined.timeline.events,
3192 joined.timeline.limited,
3193 joined.timeline.prev_batch.clone(),
3194 );
3195 self.decrypt_new_timeline_events(room_id, &joined.timeline.events);
3196 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
3197 }
3198
3199 self.persist_room_state(room_id)
3200 }
3201
3202 fn ingest_invited_room(&mut self, room_id: &RoomId, invited: &InvitedRoom) -> Result<(), MessengerError> {
3203 for stripped in &invited.invite_state.events {
3204 let synthetic = stripped_state_to_raw_event(stripped);
3205 self.rooms.entry(room_id.clone()).or_default().apply_state_event(&synthetic)?;
3206 }
3207 self.persist_room_state(room_id)
3208 }
3209
3210 fn ingest_left_room(&mut self, room_id: &RoomId, left: &LeftRoom) -> Result<(), MessengerError> {
3211 for event in left.state.events.iter().chain(left.timeline.events.iter()) {
3212 self.rooms.entry(room_id.clone()).or_default().apply_state_event(event)?;
3213 }
3214 for event in &left.account_data.events {
3215 self.apply_synced_account_data(Some(room_id), &event.event_type, event.content.clone());
3216 }
3217 if !left.timeline.events.is_empty() {
3218 self.timelines.entry(room_id.clone()).or_default().apply_timeline_batch(
3219 &left.timeline.events,
3220 left.timeline.limited,
3221 left.timeline.prev_batch.clone(),
3222 );
3223 self.decrypt_new_timeline_events(room_id, &left.timeline.events);
3224 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
3225 }
3226 self.persist_room_state(room_id)
3227 }
3228
3229 fn persist_room_state(&mut self, room_id: &RoomId) -> Result<(), MessengerError> {
3230 let Some(state) = self.rooms.get(room_id) else { return Ok(()) };
3231 let bytes = serde_json::to_vec(state)
3232 .map_err(|source| MessengerError::Crypto(format!("encode room state for {room_id}: {source}")))?;
3233 self.store.save_room_state(room_id, bytes)?;
3234 Ok(())
3235 }
3236
3237 fn decrypt_new_timeline_events(&mut self, room_id: &RoomId, events: &[RawEvent]) {
3245 for event in events {
3246 if event.event_type != "m.room.encrypted" || event.state_key.is_some() || event.unsigned.redacted_because.is_some() {
3247 continue;
3248 }
3249 let Ok(RoomEncryptedContent::Megolm(content)) = serde_json::from_value::<RoomEncryptedContent>(event.content.clone())
3250 else {
3251 continue;
3252 };
3253 match GroupSessionManager::decrypt_event(
3254 &mut self.store,
3255 room_id,
3256 &event.event_id,
3257 event.origin_server_ts,
3258 &event.sender,
3259 &content,
3260 ) {
3261 Ok(plaintext) => {
3262 self.apply_decrypted_plaintext(room_id, &event.event_id, &event.sender, event.origin_server_ts, &plaintext);
3263 if let Some(by_event) = self.pending_decrypt.get_mut(room_id) {
3264 by_event.remove(&event.event_id);
3265 }
3266 }
3267 Err(err) => {
3268 if matches!(err, GroupDecryptError::MissingSession { .. }) {
3269 let _ = self.request_missing_room_key(room_id, &event.sender, &content);
3270 }
3271 let reason = format!("{:?}", err.utd_reason());
3280 if let Some(timeline) = self.timelines.get_mut(room_id) {
3281 timeline.set_decrypted(&event.event_id, ItemContent::Undecryptable { reason });
3282 }
3283 self.pending_decrypt.entry(room_id.clone()).or_default().insert(
3284 event.event_id.clone(),
3285 PendingDecryptItem {
3286 session_id: content.session_id.clone(),
3287 sender: event.sender.clone(),
3288 origin_server_ts: event.origin_server_ts,
3289 content,
3290 },
3291 );
3292 }
3293 }
3294 }
3295 }
3296
3297 fn ingest_room_messages_response(&mut self, room_id: &RoomId, body: &[u8]) -> Result<(), MessengerError> {
3298 let parsed: RoomMessagesResponseBody = serde_json::from_slice(body)?;
3299 let mut events = parsed.chunk;
3300 events.reverse();
3301 self.timelines.entry(room_id.clone()).or_default().prepend_back_page(&events, parsed.end);
3302 self.decrypt_new_timeline_events(room_id, &events);
3303 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
3304 self.persist_room_state(room_id)
3305 }
3306}
3307
3308impl MessengerCore<SealedRecordCodec> {
3309 pub fn open_sealed(
3320 records: impl IntoIterator<Item = SealedRecord>,
3321 secrets: CoreSecrets,
3322 config: CoreConfig,
3323 now_ms: i64,
3324 jitter: Box<dyn Jitter>,
3325 ) -> Result<Self, MessengerError> {
3326 let seal_key = secrets.store_seal_key.clone().ok_or_else(|| {
3327 MessengerError::Crypto(
3328 "MessengerCore::open_sealed requires CoreSecrets::store_seal_key"
3329 .to_string(),
3330 )
3331 })?;
3332 let codec = SealedRecordCodec::new(seal_key);
3333 Self::open(records, codec, config, secrets, now_ms, jitter)
3334 }
3335}
3336
3337fn stripped_placeholder_event_id() -> EventId {
3343 EventId::parse("$stripped-preview").expect("this literal is a well-formed, hardcoded $-prefixed opaque id")
3344}
3345
3346fn stripped_state_to_raw_event(stripped: &StrippedStateEvent) -> RawEvent {
3351 RawEvent {
3352 event_id: stripped_placeholder_event_id(),
3353 event_type: stripped.event_type.clone(),
3354 sender: stripped.sender.clone(),
3355 origin_server_ts: 0,
3356 state_key: Some(stripped.state_key.clone()),
3357 content: stripped.content.clone(),
3358 unsigned: Unsigned::default(),
3359 }
3360}
3361
3362fn forwardable_content(mut content: serde_json::Value, marker_key: &str, marker: serde_json::Value) -> serde_json::Value {
3369 if let Some(object) = content.as_object_mut() {
3370 for key in ["m.relates_to", "m.mentions", "m.new_content", KEY_FORWARDED, KEY_FORWARDED_FROM] {
3371 object.remove(key);
3372 }
3373 object.insert(marker_key.to_string(), marker);
3374 }
3375 content
3376}
3377
3378fn message_local_echo_content(message: &OutgoingMessage) -> ItemContent {
3385 let body = TextLikeMessageContent { body: message.body.clone(), format: None, formatted_body: None };
3386 match message.kind {
3387 MessageKind::Text => ItemContent::Text(body),
3388 MessageKind::Notice => ItemContent::Notice(body),
3389 MessageKind::Emote => ItemContent::Emote(body),
3390 }
3391}
3392
3393fn message_wire_content(message: &OutgoingMessage) -> serde_json::Value {
3402 let msgtype = message.kind.msgtype();
3403 let new_content = serde_json::json!({ "msgtype": msgtype, "body": message.body });
3404 let mut content = new_content.clone();
3405 if let Some(edit_of) = &message.edit_of {
3406 content["body"] = serde_json::Value::String(format!("* {}", message.body));
3407 content["m.new_content"] = new_content;
3408 content["m.relates_to"] = serde_json::json!({ "rel_type": "m.replace", "event_id": edit_of.as_str() });
3409 } else if let Some(reply_to) = &message.reply_to {
3410 content["m.relates_to"] = serde_json::json!({ "m.in_reply_to": { "event_id": reply_to.as_str() } });
3411 }
3412 content
3413}
3414
3415fn message_relates_to(message: &OutgoingMessage) -> Option<RelatesTo> {
3421 if let Some(edit_of) = &message.edit_of {
3422 Some(RelatesTo::Replace { event_id: edit_of.clone() })
3423 } else {
3424 message.reply_to.clone().map(|event_id| RelatesTo::InReplyTo(InReplyTo { event_id }))
3425 }
3426}
3427
3428#[cfg(test)]
3429mod tests {
3430 use super::*;
3431 use crate::room::timeline::Forwarded;
3432 use crate::store::InsecurePlainCodecForTests;
3433 use crate::wire::HttpMethod;
3434
3435 struct FixedJitter(f64);
3436
3437 impl Jitter for FixedJitter {
3438 fn next_unit(&mut self) -> f64 {
3439 self.0
3440 }
3441 }
3442
3443 fn device_id() -> DeviceId {
3444 DeviceId::parse("DEV1").expect("valid device id")
3445 }
3446
3447 fn user_id() -> UserId {
3448 UserId::parse("@alice:example.org").expect("valid user id")
3449 }
3450
3451 fn test_config() -> CoreConfig {
3452 CoreConfig { user_id: user_id(), device_id: device_id(), server_name: "example.org".to_string() }
3453 }
3454
3455 fn open_fresh_core() -> MessengerCore<InsecurePlainCodecForTests> {
3456 MessengerCore::open(Vec::new(), InsecurePlainCodecForTests, test_config(), CoreSecrets::default(), 0, Box::new(FixedJitter(0.0)))
3457 .expect("open succeeds on a brand-new device")
3458 }
3459
3460 fn flush_and_ack(core: &mut MessengerCore<InsecurePlainCodecForTests>) {
3468 while let Some(batch) = core.take_flush_batch() {
3469 core.ack_flush(batch.id);
3470 }
3471 }
3472
3473 #[test]
3474 fn counters_survive_restart_and_never_repeat_txn_ids() {
3475 let device = device_id();
3476 let mut store = Store::new(device.clone(), InsecurePlainCodecForTests);
3477 store.save_counters(Counters { next_request_id: 3, next_txn_id: 7 }).expect("save");
3478 let batch = store.take_flush_batch().expect("counters dirtied the store");
3479 store.ack_flush(batch.id);
3480
3481 let reloaded = Store::load(batch.records, InsecurePlainCodecForTests, device).expect("reload");
3482 let restored = reloaded.counters().expect("no error").expect("counters were persisted");
3483 assert_eq!(restored.next_txn_id, 7, "a restart resumes the txn-id sequence exactly where it left off");
3484 assert_eq!(restored.next_request_id, 3);
3485
3486 assert_eq!(restore_counter(3, Some(10)), 11, "resumes past the highest pending id, never repeating it");
3491 assert_eq!(restore_counter(3, None), 3, "nothing pending: the persisted value alone is authoritative");
3492 }
3493
3494 #[test]
3495 fn open_restores_the_request_counter_past_a_still_pending_requests_id() {
3496 let device = device_id();
3497 let mut store = Store::new(device.clone(), InsecurePlainCodecForTests);
3498 let mut queue = OutgoingQueue::new();
3499 let pending = OutgoingRequest::sync(RequestId::next(5), None, None);
3500 queue.enqueue(&mut store, pending, Lane::Sync).expect("enqueue");
3501 store.save_counters(Counters { next_request_id: 2, next_txn_id: 0 }).expect("save");
3504 let batch = store.take_flush_batch().expect("pending");
3505 store.ack_flush(batch.id);
3506
3507 let core = MessengerCore::open(
3508 batch.records,
3509 InsecurePlainCodecForTests,
3510 test_config(),
3511 CoreSecrets::default(),
3512 0,
3513 Box::new(FixedJitter(0.0)),
3514 )
3515 .expect("open succeeds");
3516 assert_eq!(
3517 core.counters.next_request_id, 6,
3518 "resumes past the highest id already in flight, not the stale persisted value"
3519 );
3520 }
3521
3522 #[test]
3523 fn sync_token_persisted_after_everything_it_covers() {
3524 let mut core = open_fresh_core();
3525 flush_and_ack(&mut core);
3526 core.releasable_requests(0);
3531 flush_and_ack(&mut core);
3532 let requests = core.releasable_requests(0);
3533 let sync_request = requests.iter().find(|r| r.kind == OutgoingRequestKind::Sync).expect("a sync request was enqueued");
3534
3535 assert!(core.store.sync_token().expect("no error").is_none(), "no token before the first response");
3536
3537 let body = serde_json::json!({
3538 "next_batch": "s1",
3539 "device_one_time_keys_count": { "signed_curve25519": 0 },
3540 "device_unused_fallback_key_types": []
3541 })
3542 .to_string();
3543 core.on_response(sync_request.id.clone(), HttpResponseDescriptor { status: 200, body: body.into_bytes() }, 0);
3544
3545 assert_eq!(core.store.sync_token().expect("no error"), Some("s1"));
3546 }
3547
3548 #[test]
3549 fn only_one_sync_in_flight() {
3550 let mut core = open_fresh_core();
3551 flush_and_ack(&mut core);
3552 core.releasable_requests(0);
3553 flush_and_ack(&mut core);
3554 let first = core.releasable_requests(0);
3555 assert_eq!(first.iter().filter(|r| r.kind == OutgoingRequestKind::Sync).count(), 1);
3556
3557 let second = core.releasable_requests(0);
3561 assert!(second.iter().all(|r| r.kind != OutgoingRequestKind::Sync), "no second sync request released");
3562 }
3563
3564 #[test]
3565 fn first_sync_of_a_session_never_long_polls_then_the_loop_does() {
3566 let mut core = open_fresh_core();
3567 flush_and_ack(&mut core);
3568 core.releasable_requests(0);
3569 flush_and_ack(&mut core);
3570 let first = core.releasable_requests(0);
3571 let first_sync = first.iter().find(|r| r.kind == OutgoingRequestKind::Sync).expect("a sync request was enqueued");
3572 assert!(first_sync.query.contains(&("timeout".to_string(), "0".to_string())), "the initial sync must answer at once");
3573 assert!(first_sync.query.iter().all(|(key, _)| key != "since"), "a fresh device has no since token");
3574
3575 let body = serde_json::json!({
3576 "next_batch": "s1",
3577 "device_one_time_keys_count": { "signed_curve25519": 0 },
3578 "device_unused_fallback_key_types": []
3579 })
3580 .to_string();
3581 core.on_response(first_sync.id.clone(), HttpResponseDescriptor { status: 200, body: body.into_bytes() }, 0);
3582 flush_and_ack(&mut core);
3583
3584 core.releasable_requests(0);
3587 flush_and_ack(&mut core);
3588 let second = core.releasable_requests(0);
3589 let second_sync = second.iter().find(|r| r.kind == OutgoingRequestKind::Sync).expect("the follow-up sync was enqueued");
3590 assert!(second_sync.query.contains(&("since".to_string(), "s1".to_string())));
3591 assert!(second_sync.query.contains(&("timeout".to_string(), SYNC_TIMEOUT_MS.to_string())), "once caught up the loop long-polls");
3592 }
3593
3594 #[test]
3595 fn room_key_arrival_retries_undecryptable_items() {
3596 use crate::crypto::group_sessions::GroupSessionManager;
3597 use mail4agent_vodozemac::megolm::{GroupSession, SessionConfig};
3598 use mail4agent_vodozemac::olm::Account;
3599
3600 let mut core = open_fresh_core();
3601 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3602 let sender = UserId::parse("@bob:example.org").expect("valid user id");
3603 let identity = Account::new().identity_keys();
3604
3605 let mut group_session = GroupSession::new(SessionConfig::version_1());
3606 let session_id = group_session.session_id();
3607 let session_key = group_session.session_key();
3612 let plaintext = crate::crypto::group_sessions::RoomEventPlaintext {
3613 event_type: "m.room.message".to_string(),
3614 content: serde_json::json!({ "msgtype": "m.text", "body": "hi" }),
3615 room_id: room_id.clone(),
3616 };
3617 let message = group_session.encrypt(serde_json::to_vec(&plaintext).expect("valid JSON"));
3618
3619 let event: RawEvent = serde_json::from_value(serde_json::json!({
3620 "event_id": "$evt:example.org",
3621 "type": "m.room.encrypted",
3622 "sender": sender.as_str(),
3623 "origin_server_ts": 1,
3624 "content": {
3625 "algorithm": "m.megolm.v1.aes-sha2",
3626 "ciphertext": message.to_base64(),
3627 "session_id": session_id,
3628 }
3629 }))
3630 .expect("valid event");
3631
3632 let mut timeline = Timeline::new();
3638 timeline.apply_timeline_batch(std::slice::from_ref(&event), false, None);
3639 core.timelines.insert(room_id.clone(), timeline);
3640 core.decrypt_new_timeline_events(&room_id, std::slice::from_ref(&event));
3641 {
3642 let timeline = core.timeline(&room_id).expect("timeline exists");
3643 let item = timeline.item_by_event_id(&EventId::parse("$evt:example.org").expect("valid event id")).expect("item present");
3644 assert!(matches!(item.content, ItemContent::Undecryptable { .. }));
3645 }
3646
3647 let content = RoomKeyContent {
3651 algorithm: "m.megolm.v1.aes-sha2".to_string(),
3652 room_id: room_id.clone(),
3653 session_id: session_id.clone(),
3654 session_key: session_key.to_base64(),
3655 };
3656 GroupSessionManager::receive_room_key(&mut core.store, &sender, identity.curve25519, identity.ed25519, &content)
3657 .expect("receive room key");
3658 core.retry_pending_for_session(&room_id, &session_id);
3659
3660 let timeline = core.timeline(&room_id).expect("timeline exists");
3661 let item = timeline.item_by_event_id(&EventId::parse("$evt:example.org").expect("valid event id")).expect("item present");
3662 match &item.content {
3663 ItemContent::Text(text) => assert_eq!(text.body, "hi"),
3664 other => panic!("expected the retried decrypt to succeed, got {other:?}"),
3665 }
3666 }
3667
3668 #[test]
3669 fn redelivered_to_device_after_restart_is_dropped_silently() {
3670 use crate::crypto::olm_sessions::OlmSessionManager;
3671
3672 let alice_user = UserId::parse("@alice:example.org").expect("valid user id");
3674 let alice_device = DeviceId::parse("ALICEDEV").expect("valid device id");
3675 let mut alice_store = Store::new(alice_device.clone(), InsecurePlainCodecForTests);
3676 let alice_account = OlmAccountState::load_or_create(&mut alice_store).expect("create alice's account");
3677 let alice_device_keys =
3680 alice_account.device_keys_json(&alice_user, &alice_device).expect("sign alice's device_keys");
3681
3682 let mut bob_store = Store::new(device_id(), InsecurePlainCodecForTests);
3684 let mut bob_account = OlmAccountState::load_or_create(&mut bob_store).expect("create bob's account");
3685 bob_account.on_sync_counts(&mut bob_store, 0, &["signed_curve25519".to_string()]).expect("top up bob's OTKs");
3686 let upload = bob_account
3687 .keys_upload_request(RequestId::next(0), &user_id(), &device_id())
3688 .expect("builds a request")
3689 .expect("something to upload");
3690 let one_time_keys = upload.body.expect("upload has a body");
3691 let one_time_keys = one_time_keys.get("one_time_keys").and_then(serde_json::Value::as_object).expect("OTKs present");
3692 let (key_id, key_value) = one_time_keys.iter().next().expect("at least one OTK");
3693 let claim_body = serde_json::to_vec(&serde_json::json!({
3694 "one_time_keys": { user_id().as_str(): { device_id().as_str(): { key_id: key_value } } }
3695 }))
3696 .expect("valid JSON");
3697 let bob_device_stored = crate::crypto::device_tracker::StoredDevice {
3698 user_id: user_id(),
3699 device_id: device_id(),
3700 curve25519: bob_account.identity_keys().curve25519,
3701 ed25519: bob_account.identity_keys().ed25519,
3702 algorithms: vec!["m.olm.v1.curve25519-aes-sha2".to_string(), "m.megolm.v1.aes-sha2".to_string()],
3703 display_name: None,
3704 verified: false,
3705 blocked: false,
3706 };
3707 OlmSessionManager::on_keys_claim_response(&mut alice_store, &alice_account, &[&bob_device_stored], &claim_body)
3708 .expect("alice establishes a session with bob");
3709
3710 DeviceTracker::on_keys_query_response(
3716 &mut bob_store,
3717 &serde_json::to_vec(&serde_json::json!({
3718 "device_keys": { alice_user.as_str(): { alice_device.as_str(): alice_device_keys } }
3719 }))
3720 .expect("valid JSON"),
3721 )
3722 .expect("bob learns alice's device");
3723
3724 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3725 let group_session = mail4agent_vodozemac::megolm::GroupSession::new(mail4agent_vodozemac::megolm::SessionConfig::version_1());
3726 let room_key_content = RoomKeyContent {
3727 algorithm: "m.megolm.v1.aes-sha2".to_string(),
3728 room_id,
3729 session_id: group_session.session_id(),
3730 session_key: group_session.session_key().to_base64(),
3731 };
3732 let encrypted = OlmSessionManager::encrypt_to_device(
3733 &mut alice_store,
3734 &alice_account,
3735 &alice_user,
3736 &alice_device,
3737 &bob_device_stored,
3738 "m.room_key",
3739 serde_json::to_value(&room_key_content).expect("valid JSON"),
3740 )
3741 .expect("encrypt to bob");
3742 let to_device_event = ToDeviceEvent {
3743 sender: alice_user.clone(),
3744 event_type: "m.room.encrypted".to_string(),
3745 content: serde_json::to_value(RoomEncryptedContent::Olm(encrypted)).expect("valid JSON"),
3746 };
3747
3748 let mut core =
3749 MessengerCore::open(Vec::new(), InsecurePlainCodecForTests, test_config(), CoreSecrets::default(), 0, Box::new(FixedJitter(0.0)))
3750 .expect("open succeeds");
3751 core.store = bob_store;
3752 core.account = bob_account;
3753
3754 let mut sessions = Vec::new();
3755 core.process_to_device_event(&to_device_event, &mut sessions).expect("first decrypt succeeds");
3756 assert_eq!(
3757 sessions,
3758 vec![(room_key_content.room_id.clone(), room_key_content.session_id.clone())],
3759 "the first, genuine delivery is fully validated and accepted"
3760 );
3761
3762 let batch = core.store.take_flush_batch().expect("decrypting dirtied the store");
3766 core.store.ack_flush(batch.id);
3767 let reloaded_store = Store::load(batch.records, InsecurePlainCodecForTests, device_id()).expect("reload succeeds");
3768 core.store = reloaded_store;
3769 core.account = OlmAccountState::load_or_create(&mut core.store).expect("reload reuses the persisted account");
3770
3771 let mut sessions_after_restart = Vec::new();
3775 let result = core.process_to_device_event(&to_device_event, &mut sessions_after_restart);
3776 assert!(result.is_ok(), "a redelivered to-device event is dropped, not surfaced as an ingestion error");
3777 assert!(sessions_after_restart.is_empty(), "the replay never produces a second accepted room key");
3778 }
3779
3780 #[test]
3781 fn unknown_sender_device_is_retried_after_keys_query() {
3782 use crate::crypto::olm_sessions::OlmSessionManager;
3783
3784 let alice_user = UserId::parse("@alice:example.org").expect("valid user id");
3786 let alice_device = DeviceId::parse("ALICEDEV").expect("valid device id");
3787 let mut alice_store = Store::new(alice_device.clone(), InsecurePlainCodecForTests);
3788 let alice_account = OlmAccountState::load_or_create(&mut alice_store).expect("create alice's account");
3789 let alice_device_keys =
3797 alice_account.device_keys_json(&alice_user, &alice_device).expect("sign alice's device_keys");
3798
3799 let bob_user = UserId::parse("@bob:example.org").expect("valid user id");
3807 let bob_device = DeviceId::parse("BOBDEV").expect("valid device id");
3808 let mut bob_store = Store::new(bob_device.clone(), InsecurePlainCodecForTests);
3809 let mut bob_account = OlmAccountState::load_or_create(&mut bob_store).expect("create bob's account");
3810 bob_account.on_sync_counts(&mut bob_store, 0, &["signed_curve25519".to_string()]).expect("top up bob's OTKs");
3811 let upload = bob_account
3812 .keys_upload_request(RequestId::next(0), &bob_user, &bob_device)
3813 .expect("builds a request")
3814 .expect("something to upload");
3815 let one_time_keys = upload.body.expect("upload has a body");
3816 let one_time_keys = one_time_keys.get("one_time_keys").and_then(serde_json::Value::as_object).expect("OTKs present");
3817 let (key_id, key_value) = one_time_keys.iter().next().expect("at least one OTK");
3818 let claim_body = serde_json::to_vec(&serde_json::json!({
3819 "one_time_keys": { bob_user.as_str(): { bob_device.as_str(): { key_id: key_value } } }
3820 }))
3821 .expect("valid JSON");
3822 let bob_device_stored = crate::crypto::device_tracker::StoredDevice {
3823 user_id: bob_user.clone(),
3824 device_id: bob_device.clone(),
3825 curve25519: bob_account.identity_keys().curve25519,
3826 ed25519: bob_account.identity_keys().ed25519,
3827 algorithms: vec!["m.olm.v1.curve25519-aes-sha2".to_string(), "m.megolm.v1.aes-sha2".to_string()],
3828 display_name: None,
3829 verified: false,
3830 blocked: false,
3831 };
3832 OlmSessionManager::on_keys_claim_response(&mut alice_store, &alice_account, &[&bob_device_stored], &claim_body)
3833 .expect("alice establishes a session with bob");
3834 let encrypted = OlmSessionManager::encrypt_to_device(
3835 &mut alice_store,
3836 &alice_account,
3837 &alice_user,
3838 &alice_device,
3839 &bob_device_stored,
3840 "m.dummy",
3841 serde_json::json!({}),
3842 )
3843 .expect("encrypt to bob");
3844 let to_device_event = ToDeviceEvent {
3845 sender: alice_user.clone(),
3846 event_type: "m.room.encrypted".to_string(),
3847 content: serde_json::to_value(RoomEncryptedContent::Olm(encrypted)).expect("valid JSON"),
3848 };
3849
3850 let bob_config = CoreConfig { user_id: bob_user.clone(), device_id: bob_device.clone(), server_name: "example.org".to_string() };
3851 let mut core =
3852 MessengerCore::open(Vec::new(), InsecurePlainCodecForTests, bob_config, CoreSecrets::default(), 0, Box::new(FixedJitter(0.0)))
3853 .expect("open succeeds");
3854 core.store = bob_store;
3855 core.account = bob_account;
3856
3857 let mut sessions = Vec::new();
3861 core.process_to_device_event(&to_device_event, &mut sessions).expect("queues for retry, does not error");
3862 assert_eq!(core.unknown_sender_retry.len(), 1, "queued for a retry after the next keys/query");
3863 let alice_curve = alice_account.identity_keys().curve25519.to_base64();
3864 assert!(
3865 core.store.olm_sessions_for_device(&alice_curve).expect("no error").is_empty(),
3866 "the refused attempt created no Olm session (it would have consumed the one-time key)"
3867 );
3868
3869 let query_body = serde_json::to_vec(&serde_json::json!({
3874 "device_keys": { alice_user.as_str(): { alice_device.as_str(): alice_device_keys } }
3875 }))
3876 .expect("valid JSON");
3877 DeviceTracker::on_keys_query_response(&mut core.store, &query_body).expect("bob learns alice's device");
3878 core.retry_unknown_sender_queue().expect("no error");
3879 assert!(core.unknown_sender_retry.is_empty(), "the queued entry is drained by the retry attempt");
3880 assert_eq!(
3881 core.store.olm_sessions_for_device(&alice_curve).expect("no error").len(),
3882 1,
3883 "the retry decrypted the very same pre-key message and established the inbound session"
3884 );
3885 }
3886
3887 #[test]
3888 fn dispatch_load_older_produces_an_outgoing_request() {
3889 let mut core = open_fresh_core();
3890 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3891 let mut timeline = Timeline::new();
3892 let event: RawEvent = serde_json::from_value(serde_json::json!({
3893 "event_id": "$1:example.org",
3894 "type": "m.room.message",
3895 "sender": "@alice:example.org",
3896 "origin_server_ts": 1,
3897 "content": { "msgtype": "m.text", "body": "hi" }
3898 }))
3899 .expect("valid event");
3900 timeline.apply_timeline_batch(&[event], true, Some("t1".to_string()));
3901 core.timelines.insert(room_id.clone(), timeline);
3902 flush_and_ack(&mut core);
3903
3904 core.dispatch(MessengerCommand::LoadOlder { room_id: room_id.clone() }, 0).expect("dispatch succeeds");
3905 flush_and_ack(&mut core);
3906 let requests = core.releasable_requests(0);
3907 let load_older = requests.iter().find(|r| r.kind == OutgoingRequestKind::RoomMessages);
3908 assert!(load_older.is_some(), "a RoomMessages request was enqueued using the room's own gap token");
3909 }
3910
3911 #[test]
3912 fn load_older_on_a_resumed_room_pages_back_from_the_sync_token() {
3913 let mut core = open_fresh_core();
3914 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3915 core.store.save_sync_token("s42_7".to_string()).expect("save the sync token");
3916 flush_and_ack(&mut core);
3917
3918 core.dispatch(MessengerCommand::LoadOlder { room_id: room_id.clone() }, 0).expect("dispatch succeeds");
3920 flush_and_ack(&mut core);
3921 assert!(core.releasable_requests(0).iter().all(|r| r.kind != OutgoingRequestKind::RoomMessages));
3922
3923 core.rooms.insert(room_id.clone(), RoomState::default());
3925 core.dispatch(MessengerCommand::LoadOlder { room_id: room_id.clone() }, 0).expect("dispatch succeeds");
3926 flush_and_ack(&mut core);
3927 let requests = core.releasable_requests(0);
3928 let load_older = requests.iter().find(|r| r.kind == OutgoingRequestKind::RoomMessages).expect("a RoomMessages request was enqueued");
3929 assert!(load_older.query.contains(&("from".to_string(), "s42_7".to_string())), "pages back from the sync token: {:?}", load_older.query);
3930 }
3931
3932 fn bob_joins_room_body(encrypted: bool) -> serde_json::Value {
3935 let mut state = Vec::new();
3936 if encrypted {
3937 state.push(serde_json::json!({
3938 "event_id": "$enc:example.org", "type": "m.room.encryption", "sender": "@alice:example.org",
3939 "origin_server_ts": 1, "state_key": "", "content": { "algorithm": "m.megolm.v1.aes-sha2" }
3940 }));
3941 }
3942 serde_json::json!({
3943 "next_batch": "s1",
3944 "rooms": { "join": { "!r:example.org": {
3945 "state": { "events": state },
3946 "timeline": { "events": [{
3947 "event_id": "$join:example.org", "type": "m.room.member", "sender": "@bob:example.org",
3948 "origin_server_ts": 2, "state_key": "@bob:example.org", "content": { "membership": "join" }
3949 }] }
3950 } } }
3951 })
3952 }
3953
3954 #[test]
3955 fn a_member_joining_an_encrypted_room_is_tracked_and_queried_right_away() {
3956 let mut core = open_fresh_core();
3957 let bob = UserId::parse("@bob:example.org").expect("valid user id");
3958 deliver_sync(&mut core, bob_joins_room_body(true));
3959 let tracked = core.store.tracked_users().expect("no error");
3960 assert!(tracked.iter().any(|(user_id, outdated)| *user_id == bob && *outdated), "bob is tracked with an outdated device list: {tracked:?}");
3961
3962 flush_and_ack(&mut core);
3963 let requests = core.releasable_requests(0);
3964 let query = requests.iter().find(|r| r.kind == OutgoingRequestKind::KeysQuery).expect("a /keys/query was enqueued for the new member");
3965 assert!(query.body.as_ref().is_some_and(|body| body["device_keys"].get(bob.as_str()).is_some()), "the query names bob: {:?}", query.body);
3966 }
3967
3968 #[test]
3969 fn a_member_joining_a_plaintext_room_is_not_tracked() {
3970 let mut core = open_fresh_core();
3971 deliver_sync(&mut core, bob_joins_room_body(false));
3972 assert!(core.store.tracked_users().expect("no error").is_empty());
3973 flush_and_ack(&mut core);
3974 assert!(core.releasable_requests(0).iter().all(|r| r.kind != OutgoingRequestKind::KeysQuery));
3975 }
3976
3977 #[test]
3978 fn dispatch_mark_read_and_set_typing_produce_outgoing_requests() {
3979 let mut core = open_fresh_core();
3980 flush_and_ack(&mut core);
3981 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3982 let event_id = EventId::parse("$1:example.org").expect("valid event id");
3983
3984 core.dispatch(MessengerCommand::MarkRead { room_id: room_id.clone(), event_id }, 0).expect("dispatch succeeds");
3985 core.dispatch(MessengerCommand::SetTyping { room_id: room_id.clone(), typing: true }, 0).expect("dispatch succeeds");
3986 flush_and_ack(&mut core);
3987
3988 let requests = core.releasable_requests(0);
3989 assert!(requests.iter().any(|r| r.kind == OutgoingRequestKind::ReadMarkers));
3990 assert!(requests.iter().any(|r| r.kind == OutgoingRequestKind::Typing));
3991 }
3992
3993 #[test]
3994 fn snapshot_reflects_the_latest_applied_sync() {
3995 let mut core = open_fresh_core();
3996 flush_and_ack(&mut core);
3997 core.releasable_requests(0);
3998 flush_and_ack(&mut core);
3999 let requests = core.releasable_requests(0);
4000 let sync_request = requests.iter().find(|r| r.kind == OutgoingRequestKind::Sync).expect("sync enqueued");
4001
4002 let body = serde_json::json!({
4003 "next_batch": "s1",
4004 "rooms": {
4005 "join": {
4006 "!r:example.org": {
4007 "state": { "events": [] },
4008 "timeline": {
4009 "events": [
4010 {
4011 "event_id": "$1:example.org",
4012 "type": "m.room.message",
4013 "sender": "@alice:example.org",
4014 "origin_server_ts": 1,
4015 "content": { "msgtype": "m.text", "body": "hi" }
4016 }
4017 ]
4018 },
4019 "unread_notifications": { "notification_count": 1, "highlight_count": 0 }
4020 }
4021 }
4022 }
4023 })
4024 .to_string();
4025 let events = core.on_response(sync_request.id.clone(), HttpResponseDescriptor { status: 200, body: body.into_bytes() }, 0);
4026
4027 assert!(events.contains(&MessengerEvent::RoomsChanged));
4028 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4029 assert!(events.contains(&MessengerEvent::TimelineChanged { room_id: room_id.clone() }));
4030 assert!(events.contains(&MessengerEvent::UnreadChanged { room_id: room_id.clone() }));
4031
4032 let timeline = core.timeline(&room_id).expect("timeline present");
4033 assert_eq!(timeline.items().len(), 1);
4034 assert_eq!(core.room_state(&room_id).expect("room present").unread_count(), 1);
4035 assert_eq!(core.change_counter(), events.len() as u64);
4036 assert!(core.events().is_empty());
4039 }
4040
4041 #[test]
4042 fn dispatch_set_account_data_updates_optimistically_and_sends_a_put() {
4043 let mut core = open_fresh_core();
4044 flush_and_ack(&mut core);
4045 let content = serde_json::json!({ "note": "keep" });
4046 core.dispatch(
4047 MessengerCommand::SetAccountData { event_type: "com.example.prefs".to_string(), content: content.clone() },
4048 0,
4049 )
4050 .expect("dispatch succeeds");
4051
4052 assert_eq!(
4053 core.global_account_data("com.example.prefs"),
4054 Some(&content),
4055 "SetAccountData updates the core's own cache before any round trip"
4056 );
4057
4058 flush_and_ack(&mut core);
4059 let requests = core.releasable_requests(0);
4060 let request = requests.iter().find(|r| r.kind == OutgoingRequestKind::AccountData).expect("an AccountData request was enqueued");
4061 assert_eq!(request.method, HttpMethod::Put);
4062 assert_eq!(request.path, "/_matrix/client/v3/user/%40alice%3Aexample.org/account_data/com.example.prefs");
4063 assert_eq!(request.body.as_ref(), Some(&content));
4064 }
4065
4066 #[test]
4067 fn dispatch_set_room_account_data_updates_optimistically_and_sends_a_put() {
4068 let mut core = open_fresh_core();
4069 flush_and_ack(&mut core);
4070 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4071 let content = serde_json::json!({ "flag": true });
4072 core.dispatch(
4073 MessengerCommand::SetRoomAccountData {
4074 room_id: room_id.clone(),
4075 event_type: "com.example.flag".to_string(),
4076 content: content.clone(),
4077 },
4078 0,
4079 )
4080 .expect("dispatch succeeds");
4081
4082 assert_eq!(
4083 core.room_account_data(&room_id, "com.example.flag"),
4084 Some(&content),
4085 "SetRoomAccountData updates the core's own cache before any round trip"
4086 );
4087
4088 flush_and_ack(&mut core);
4089 let requests = core.releasable_requests(0);
4090 let request =
4091 requests.iter().find(|r| r.kind == OutgoingRequestKind::RoomAccountData).expect("a RoomAccountData request was enqueued");
4092 assert_eq!(request.method, HttpMethod::Put);
4093 assert_eq!(
4094 request.path,
4095 "/_matrix/client/v3/user/%40alice%3Aexample.org/rooms/%21r%3Aexample.org/account_data/com.example.flag"
4096 );
4097 assert_eq!(request.body.as_ref(), Some(&content));
4098 }
4099
4100 #[test]
4101 fn dispatch_search_public_rooms_sends_the_expected_request() {
4102 let mut core = open_fresh_core();
4103 flush_and_ack(&mut core);
4104 core.dispatch(MessengerCommand::SearchPublicRooms { term: "trading".to_string() }, 0).expect("dispatch succeeds");
4105 flush_and_ack(&mut core);
4106 let requests = core.releasable_requests(0);
4107 let request = requests.iter().find(|r| r.kind == OutgoingRequestKind::PublicRooms).expect("a PublicRooms request was enqueued");
4108 assert_eq!(request.method, HttpMethod::Post);
4109 assert_eq!(request.path, "/_matrix/client/v3/publicRooms");
4110 assert_eq!(request.body, Some(serde_json::json!({ "filter": { "generic_search_term": "trading" }, "limit": 50 })));
4111 }
4112
4113 #[test]
4114 fn dispatch_search_users_sends_the_expected_request() {
4115 let mut core = open_fresh_core();
4116 flush_and_ack(&mut core);
4117 core.dispatch(MessengerCommand::SearchUsers { term: "bob".to_string() }, 0).expect("dispatch succeeds");
4118 flush_and_ack(&mut core);
4119 let requests = core.releasable_requests(0);
4120 let request =
4121 requests.iter().find(|r| r.kind == OutgoingRequestKind::UserDirectorySearch).expect("a UserDirectorySearch request was enqueued");
4122 assert_eq!(request.method, HttpMethod::Post);
4123 assert_eq!(request.path, "/_matrix/client/v3/user_directory/search");
4124 assert_eq!(request.body, Some(serde_json::json!({ "search_term": "bob", "limit": 20 })));
4125 }
4126
4127 #[test]
4128 fn public_rooms_response_superseded_by_a_later_search_is_dropped() {
4129 let mut core = open_fresh_core();
4130 flush_and_ack(&mut core);
4131 core.dispatch(MessengerCommand::SearchPublicRooms { term: "first".to_string() }, 0).expect("dispatch succeeds");
4132 flush_and_ack(&mut core);
4133 let first_request = core
4134 .releasable_requests(0)
4135 .into_iter()
4136 .find(|r| r.kind == OutgoingRequestKind::PublicRooms)
4137 .expect("the first search's own request");
4138
4139 core.dispatch(MessengerCommand::SearchPublicRooms { term: "second".to_string() }, 0).expect("dispatch succeeds");
4142 flush_and_ack(&mut core);
4143 let second_request = core
4144 .releasable_requests(0)
4145 .into_iter()
4146 .find(|r| r.kind == OutgoingRequestKind::PublicRooms)
4147 .expect("the second search's own request");
4148
4149 let stale_body = serde_json::json!({ "chunk": [{ "room_id": "!stale:example.org", "num_joined_members": 1 }] });
4151 core.on_response(first_request.id, HttpResponseDescriptor { status: 200, body: serde_json::to_vec(&stale_body).expect("json") }, 0);
4152 assert!(core.public_rooms_result().is_empty(), "a response to a superseded search is dropped");
4153
4154 let fresh_body = serde_json::json!({ "chunk": [{ "room_id": "!fresh:example.org", "num_joined_members": 2 }] });
4156 core.on_response(second_request.id, HttpResponseDescriptor { status: 200, body: serde_json::to_vec(&fresh_body).expect("json") }, 0);
4157 assert_eq!(core.public_rooms_result().len(), 1);
4158 assert_eq!(core.public_rooms_result()[0].room_id, RoomId::parse("!fresh:example.org").expect("valid room id"));
4159 }
4160
4161 fn is_account_data_write(request: &OutgoingRequest) -> bool {
4162 matches!(request.kind, OutgoingRequestKind::AccountData | OutgoingRequestKind::RoomAccountData)
4163 }
4164
4165 #[test]
4166 fn pin_then_archive_back_to_back_keeps_both_tags() {
4167 let mut core = open_fresh_core();
4168 flush_and_ack(&mut core);
4169 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4170 for tag in ["m.favourite", "u.example.archived"] {
4171 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: tag.to_string(), order: None }, 0)
4172 .expect("dispatch succeeds");
4173 }
4174
4175 let cached = core.room_account_data(&room_id, "m.tag").expect("m.tag cached");
4177 assert!(cached["tags"].get("m.favourite").is_some());
4178 assert!(cached["tags"].get("u.example.archived").is_some());
4179
4180 flush_and_ack(&mut core);
4181 let first_release = core.releasable_requests(0);
4182 let first_writes: Vec<&OutgoingRequest> = first_release.iter().filter(|r| is_account_data_write(r)).collect();
4183 assert_eq!(first_writes.len(), 1, "the second PUT is not released while the first is in flight");
4184 let first_body = first_writes[0].body.clone().expect("a body");
4185 assert!(first_body["tags"].get("m.favourite").is_some());
4186 assert!(first_body["tags"].get("u.example.archived").is_none(), "the first PUT carries only what existed when it was built");
4187
4188 core.on_response(first_writes[0].id.clone(), HttpResponseDescriptor { status: 200, body: b"{}".to_vec() }, 0);
4189 flush_and_ack(&mut core);
4190 let second_release = core.releasable_requests(0);
4191 let second = second_release.iter().find(|r| is_account_data_write(r)).expect("the second PUT is released after the first completed");
4192 let second_body = second.body.clone().expect("a body");
4193 assert!(second_body["tags"].get("m.favourite").is_some(), "the later PUT still carries the earlier tag");
4194 assert!(second_body["tags"].get("u.example.archived").is_some());
4195 }
4196
4197 #[test]
4198 fn account_data_lane_is_fifo_single_flight() {
4199 let mut core = open_fresh_core();
4200 flush_and_ack(&mut core);
4201 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4202 core.dispatch(MessengerCommand::SetAccountData { event_type: "org.t.one".to_string(), content: serde_json::json!({}) }, 0)
4203 .expect("dispatch succeeds");
4204 core.dispatch(
4205 MessengerCommand::SetRoomAccountData {
4206 room_id: room_id.clone(),
4207 event_type: "org.t.two".to_string(),
4208 content: serde_json::json!({}),
4209 },
4210 0,
4211 )
4212 .expect("dispatch succeeds");
4213 core.dispatch(MessengerCommand::SetAccountData { event_type: "org.t.three".to_string(), content: serde_json::json!({}) }, 0)
4214 .expect("dispatch succeeds");
4215 core.dispatch(MessengerCommand::RemoveTag { room_id: room_id.clone(), tag: "m.favourite".to_string() }, 0)
4216 .expect("dispatch succeeds");
4217 flush_and_ack(&mut core);
4218
4219 let mut released_paths = Vec::new();
4220 for _ in 0..4 {
4221 let release = core.releasable_requests(0);
4222 let writes: Vec<&OutgoingRequest> = release.iter().filter(|r| is_account_data_write(r)).collect();
4223 assert_eq!(writes.len(), 1, "exactly one account-data write in flight at a time");
4224 released_paths.push(writes[0].path.clone());
4225 core.on_response(writes[0].id.clone(), HttpResponseDescriptor { status: 200, body: b"{}".to_vec() }, 0);
4226 flush_and_ack(&mut core);
4227 }
4228 assert!(released_paths[0].ends_with("/account_data/org.t.one"));
4229 assert!(released_paths[1].ends_with("/account_data/org.t.two"));
4230 assert!(released_paths[2].ends_with("/account_data/org.t.three"));
4231 assert!(released_paths[3].ends_with("/account_data/m.tag"));
4232 assert!(core.releasable_requests(0).iter().all(|r| !is_account_data_write(r)), "the lane is drained");
4233 }
4234
4235 fn deliver_sync(core: &mut MessengerCore<InsecurePlainCodecForTests>, body: serde_json::Value) -> Vec<OutgoingRequest> {
4240 let mut others = Vec::new();
4241 for _ in 0..3 {
4242 flush_and_ack(core);
4243 let mut sync_request = None;
4244 for request in core.releasable_requests(0) {
4245 if request.kind == OutgoingRequestKind::Sync && sync_request.is_none() {
4246 sync_request = Some(request);
4247 } else {
4248 others.push(request);
4249 }
4250 }
4251 if let Some(sync_request) = sync_request {
4252 let body = serde_json::to_vec(&body).expect("valid JSON");
4253 core.on_response(sync_request.id, HttpResponseDescriptor { status: 200, body }, 0);
4254 return others;
4255 }
4256 }
4257 panic!("no sync request became releasable");
4258 }
4259
4260 fn tag_sync_body(room_id: &str, tags: serde_json::Value, next_batch: &str) -> serde_json::Value {
4261 let mut join = serde_json::Map::new();
4262 join.insert(
4263 room_id.to_string(),
4264 serde_json::json!({ "account_data": { "events": [{ "type": "m.tag", "content": { "tags": tags } }] } }),
4265 );
4266 serde_json::json!({ "next_batch": next_batch, "rooms": { "join": serde_json::Value::Object(join) } })
4267 }
4268
4269 fn ok_response() -> HttpResponseDescriptor {
4270 HttpResponseDescriptor { status: 200, body: b"{}".to_vec() }
4271 }
4272
4273 #[test]
4274 fn sync_echo_of_an_older_tag_write_does_not_clobber_a_newer_optimistic_value() {
4275 let mut core = open_fresh_core();
4276 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4277 for tag in ["m.favourite", "u.example.archived"] {
4278 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: tag.to_string(), order: None }, 0)
4279 .expect("dispatch succeeds");
4280 }
4281
4282 let released = deliver_sync(&mut core, tag_sync_body("!r:example.org", serde_json::json!({ "m.favourite": {} }), "s1"));
4284 let cached = core.room_account_data(&room_id, "m.tag").expect("m.tag cached");
4285 assert!(cached["tags"].get("u.example.archived").is_some(), "the older echo must not clobber the newer optimistic value");
4286 assert!(cached["tags"].get("m.favourite").is_some());
4287 let key = (Some(room_id.clone()), "m.tag".to_string());
4288 assert_eq!(
4289 core.account_data_guard.get(&key).expect("writes are outstanding").server_value,
4290 Some(serde_json::json!({ "tags": { "m.favourite": {} } })),
4291 "the echo is remembered as the server's value"
4292 );
4293
4294 let first = released.iter().find(|r| r.kind == OutgoingRequestKind::RoomAccountData).expect("first write released");
4297 core.on_response(first.id.clone(), ok_response(), 0);
4298 flush_and_ack(&mut core);
4299 let second = core
4300 .releasable_requests(0)
4301 .into_iter()
4302 .find(|r| r.kind == OutgoingRequestKind::RoomAccountData)
4303 .expect("second write released after the first");
4304 core.on_response(second.id, ok_response(), 0);
4305 assert!(core.account_data_guard.is_empty());
4306 let cached = core.room_account_data(&room_id, "m.tag").expect("m.tag cached");
4307 assert!(cached["tags"].get("m.favourite").is_some() && cached["tags"].get("u.example.archived").is_some());
4308
4309 deliver_sync(
4311 &mut core,
4312 tag_sync_body("!r:example.org", serde_json::json!({ "m.favourite": {}, "u.example.archived": {} }), "s2"),
4313 );
4314 let cached = core.room_account_data(&room_id, "m.tag").expect("m.tag cached");
4315 assert!(cached["tags"].get("u.example.archived").is_some());
4316 }
4317
4318 #[test]
4319 fn terminal_account_data_failure_restores_the_server_value() {
4320 let mut core = open_fresh_core();
4321 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4322 deliver_sync(&mut core, tag_sync_body("!r:example.org", serde_json::json!({ "m.favourite": {} }), "s1"));
4323
4324 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: "u.example.archived".to_string(), order: None }, 0)
4325 .expect("dispatch succeeds");
4326 assert!(core.room_account_data(&room_id, "m.tag").expect("cached")["tags"].get("u.example.archived").is_some());
4327 flush_and_ack(&mut core);
4328 let write = core
4329 .releasable_requests(0)
4330 .into_iter()
4331 .find(|r| r.kind == OutgoingRequestKind::RoomAccountData)
4332 .expect("the write is released");
4333
4334 let counter_before = core.change_counter();
4335 let forbidden =
4336 HttpResponseDescriptor { status: 403, body: br#"{"errcode":"M_FORBIDDEN","error":"nope"}"#.to_vec() };
4337 let events = core.on_response(write.id, forbidden, 0);
4338 assert!(core.account_data_guard.is_empty());
4339 let restored = core.room_account_data(&room_id, "m.tag").expect("the server's value is back");
4340 assert!(restored["tags"].get("u.example.archived").is_none(), "the failed write's optimistic value is gone");
4341 assert!(restored["tags"].get("m.favourite").is_some());
4342 assert!(events.contains(&MessengerEvent::RoomsChanged));
4343 assert!(core.change_counter() > counter_before, "the UI is told to re-render");
4344
4345 for tag in ["u.a", "u.b"] {
4349 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: tag.to_string(), order: None }, 0)
4350 .expect("dispatch succeeds");
4351 }
4352 flush_and_ack(&mut core);
4353 let first = core
4354 .releasable_requests(0)
4355 .into_iter()
4356 .find(|r| r.kind == OutgoingRequestKind::RoomAccountData)
4357 .expect("first write released");
4358 let forbidden =
4359 HttpResponseDescriptor { status: 403, body: br#"{"errcode":"M_FORBIDDEN","error":"nope"}"#.to_vec() };
4360 core.on_response(first.id, forbidden, 0);
4361 let cached = core.room_account_data(&room_id, "m.tag").expect("cached");
4362 assert!(cached["tags"].get("u.a").is_some() && cached["tags"].get("u.b").is_some(), "cache untouched while a write is pending");
4363 flush_and_ack(&mut core);
4364 let second = core
4365 .releasable_requests(0)
4366 .into_iter()
4367 .find(|r| r.kind == OutgoingRequestKind::RoomAccountData)
4368 .expect("second write released");
4369 core.on_response(second.id, ok_response(), 0);
4370 let cached = core.room_account_data(&room_id, "m.tag").expect("cached");
4371 assert!(cached["tags"].get("u.b").is_some(), "the last write succeeded: its optimistic value stands");
4372 }
4373
4374 #[test]
4375 fn pending_account_data_writes_rebuild_their_guard_after_a_restart() {
4376 let mut core = open_fresh_core();
4377 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4378 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: "m.favourite".to_string(), order: None }, 0)
4379 .expect("dispatch succeeds");
4380 core.dispatch(
4381 MessengerCommand::SetAccountData { event_type: "org.t.one".to_string(), content: serde_json::json!({ "n": 1 }) },
4382 0,
4383 )
4384 .expect("dispatch succeeds");
4385
4386 let mut records = Vec::new();
4388 while let Some(batch) = core.take_flush_batch() {
4389 records.extend(batch.records.clone());
4390 core.ack_flush(batch.id);
4391 }
4392 let reopened = MessengerCore::open(
4393 records,
4394 InsecurePlainCodecForTests,
4395 test_config(),
4396 CoreSecrets::default(),
4397 0,
4398 Box::new(FixedJitter(0.0)),
4399 )
4400 .expect("reopen succeeds");
4401
4402 assert_eq!(reopened.account_data_guard.len(), 2, "one guard per key with a pending write");
4403 assert!(reopened.account_data_guard.values().all(|guard| guard.pending == 1 && guard.server_value.is_none()));
4404 assert_eq!(reopened.global_account_data("org.t.one"), Some(&serde_json::json!({ "n": 1 })));
4405 assert!(reopened.room_account_data(&room_id, "m.tag").expect("cached")["tags"].get("m.favourite").is_some());
4406 }
4407
4408 type TestCore = MessengerCore<InsecurePlainCodecForTests>;
4413
4414 fn room(id: &str) -> RoomId {
4415 RoomId::parse(id).expect("valid room id")
4416 }
4417
4418 fn event(id: &str) -> EventId {
4419 EventId::parse(id).expect("valid event id")
4420 }
4421
4422 fn add_room(core: &mut TestCore, room_id: &RoomId, encrypted: bool, name: Option<&str>) {
4425 let mut state = RoomState::new();
4426 if encrypted {
4427 state.encryption = Some(crate::wire::events::RoomEncryptionContent {
4428 algorithm: "m.megolm.v1.aes-sha2".to_string(),
4429 rotation_period_ms: 604_800_000,
4430 rotation_period_msgs: 100,
4431 });
4432 }
4433 state.name = name.map(str::to_string);
4434 core.rooms.insert(room_id.clone(), state);
4435 core.timelines.entry(room_id.clone()).or_default();
4436 }
4437
4438 #[test]
4441 fn set_events_default_opens_a_channel_for_every_member() {
4442 let mut core = open_fresh_core();
4443 let chan = room("!chan:example.org");
4444 add_room(&mut core, &chan, false, Some("Talk"));
4445 mark_as_channel(&mut core, &chan);
4446 core.dispatch(MessengerCommand::SetEventsDefault { room_id: chan.clone(), level: 0 }, 1_000).expect("dispatch");
4447 core.releasable_requests(1_000);
4448 flush_and_ack(&mut core);
4449 let sent = core.releasable_requests(1_000);
4450 let put = sent.iter().find(|r| r.kind == OutgoingRequestKind::SetState).expect("a state PUT");
4451 assert!(put.path.ends_with("/state/m.room.power_levels/"), "{}", put.path);
4452 let body = put.body.clone().expect("body");
4453 assert_eq!(body["events_default"], 0);
4454 assert_eq!(body["users_default"], 0);
4455 }
4456
4457 #[test]
4458 fn set_events_default_refuses_an_unknown_room() {
4459 let mut core = open_fresh_core();
4460 let err = core.dispatch(MessengerCommand::SetEventsDefault { room_id: room("!nope:example.org"), level: 0 }, 1_000);
4461 assert!(err.is_err());
4462 }
4463
4464 fn mark_as_channel(core: &mut TestCore, room_id: &RoomId) {
4465 let Some(state) = core.rooms.get_mut(room_id) else { return };
4466 state
4467 .apply_state_event(&state_event_raw(
4468 "m.room.join_rules",
4469 "",
4470 "@alice:example.org",
4471 serde_json::json!({ "join_rule": "public" }),
4472 ))
4473 .expect("join_rules");
4474 state
4475 .apply_state_event(&state_event_raw(
4476 "m.room.power_levels",
4477 "",
4478 "@alice:example.org",
4479 serde_json::json!({ "events_default": 50, "users_default": 0 }),
4480 ))
4481 .expect("power_levels");
4482 }
4483
4484 fn state_event_raw(event_type: &str, state_key: &str, sender: &str, content: serde_json::Value) -> RawEvent {
4485 serde_json::from_value(serde_json::json!({
4486 "event_id": format!("${event_type}:example.org"),
4487 "type": event_type,
4488 "sender": sender,
4489 "origin_server_ts": 1,
4490 "state_key": state_key,
4491 "content": content,
4492 }))
4493 .expect("valid raw event")
4494 }
4495
4496 fn raw_event(event_id: &str, event_type: &str, sender: &str, content: serde_json::Value) -> RawEvent {
4497 serde_json::from_value(serde_json::json!({
4498 "event_id": event_id,
4499 "type": event_type,
4500 "sender": sender,
4501 "origin_server_ts": 1,
4502 "content": content,
4503 }))
4504 .expect("valid raw event")
4505 }
4506
4507 fn seed_plain(core: &mut TestCore, room_id: &RoomId, event_id: &str, sender: &str, event_type: &str, content: serde_json::Value) {
4509 let raw = raw_event(event_id, event_type, sender, content);
4510 core.timelines.entry(room_id.clone()).or_default().apply_timeline_batch(&[raw], false, None);
4511 }
4512
4513 fn seed_sealed(core: &mut TestCore, room_id: &RoomId, event_id: &str, sender: &str) {
4515 let content = serde_json::json!({ "algorithm": "m.megolm.v1.aes-sha2", "ciphertext": "AAAA", "session_id": "s1" });
4516 seed_plain(core, room_id, event_id, sender, "m.room.encrypted", content);
4517 }
4518
4519 fn seed_decrypted(
4522 core: &mut TestCore,
4523 room_id: &RoomId,
4524 event_id_str: &str,
4525 sender: &str,
4526 event_type: &str,
4527 content: serde_json::Value,
4528 ) {
4529 seed_sealed(core, room_id, event_id_str, sender);
4530 if let Some(timeline) = core.timelines.get_mut(room_id) {
4531 timeline.set_decrypted_event(&event(event_id_str), event_type, &content);
4532 }
4533 }
4534
4535 fn sent_room_event(core: &mut TestCore) -> (String, serde_json::Value) {
4539 flush_and_ack(core);
4540 let request = core
4541 .releasable_requests(0)
4542 .into_iter()
4543 .find(|request| request.kind == OutgoingRequestKind::RoomSend)
4544 .expect("a room send is releasable");
4545 let confirmed = format!("$sent{}:example.org", core.counters.next_request_id);
4546 let body = serde_json::json!({ "event_id": confirmed }).to_string().into_bytes();
4547 core.on_response(request.id.clone(), HttpResponseDescriptor { status: 200, body }, 0);
4548 (request.path, request.body.expect("a room send has a body"))
4549 }
4550
4551 fn forward(core: &mut TestCore, from: &RoomId, event_id: &str, to: &RoomId) -> Result<(), MessengerError> {
4552 core.dispatch(
4553 MessengerCommand::Forward { from_room: from.clone(), event_id: event(event_id), to_room: to.clone(), txn_id: None },
4554 0,
4555 )
4556 }
4557
4558 fn last_item(core: &TestCore, room_id: &RoomId) -> crate::room::timeline::TimelineItem {
4559 core.timeline(room_id).and_then(|timeline| timeline.items().last()).expect("the room has an item").clone()
4560 }
4561
4562 #[test]
4563 fn forward_from_encrypted_room_carries_only_the_forwarded_flag() {
4564 let mut core = open_fresh_core();
4565 let dm = room("!dm:example.org");
4566 let target = room("!target:example.org");
4567 add_room(&mut core, &dm, true, Some("Secret DM name"));
4568 add_room(&mut core, &target, false, None);
4569 seed_decrypted(
4570 &mut core,
4571 &dm,
4572 "$src:example.org",
4573 "@bob:example.org",
4574 "m.room.message",
4575 serde_json::json!({
4576 "msgtype": "m.text",
4577 "body": "quarterly numbers",
4578 "m.relates_to": { "m.in_reply_to": { "event_id": "$parent:example.org" } },
4579 "m.mentions": { "user_ids": ["@carol:example.org"] },
4580 }),
4581 );
4582
4583 forward(&mut core, &dm, "$src:example.org", &target).expect("a decrypted message is forwardable");
4584
4585 let (path, body) = sent_room_event(&mut core);
4586 assert!(path.contains("/send/m.room.message/"), "the event type is kept: {path}");
4587 assert_eq!(
4588 body,
4589 serde_json::json!({ "msgtype": "m.text", "body": "quarterly numbers", "forwarded": true }),
4590 "the text plus the bare flag; the reply pointer and the mentions are stripped"
4591 );
4592 let wire = body.to_string();
4593 for leaked in ["bob", "carol", "!dm", "Secret DM name", "$parent"] {
4594 assert!(!wire.contains(leaked), "{leaked:?} must not travel with a forward out of an encrypted room: {wire}");
4595 }
4596 let echo = last_item(&core, &target);
4597 assert_eq!(echo.forwarded, Some(Forwarded::Hidden), "the forwarder's own echo shows the same marker");
4598 }
4599
4600 #[test]
4601 fn forward_from_public_channel_carries_room_id_and_name_no_sender() {
4602 let mut core = open_fresh_core();
4603 let channel = room("!chan:example.org");
4604 let target = room("!target:example.org");
4605 add_room(&mut core, &channel, true, Some("Announcements"));
4606 mark_as_channel(&mut core, &channel);
4607 add_room(&mut core, &target, false, None);
4608 seed_plain(
4609 &mut core,
4610 &channel,
4611 "$post:example.org",
4612 "@dave:example.org",
4613 "m.room.message",
4614 serde_json::json!({
4615 "msgtype": "m.text",
4616 "body": "BTC breaks out",
4617 "m.mentions": { "room": true },
4618 "m.relates_to": { "m.in_reply_to": { "event_id": "$older:example.org" } },
4619 }),
4620 );
4621
4622 forward(&mut core, &channel, "$post:example.org", &target).expect("a channel post is forwardable");
4623
4624 let (_, body) = sent_room_event(&mut core);
4625 assert_eq!(
4626 body,
4627 serde_json::json!({
4628 "msgtype": "m.text",
4629 "body": "BTC breaks out",
4630 "forwarded_from": { "room_id": "!chan:example.org", "room_name": "Announcements" },
4631 })
4632 );
4633 assert!(!body.to_string().contains("dave"), "the original sender never travels");
4634 assert_eq!(
4635 last_item(&core, &target).forwarded,
4636 Some(Forwarded::Channel { room_id: channel.clone(), room_name: "Announcements".to_string() })
4637 );
4638 }
4639
4640 #[test]
4641 fn forward_from_an_unnamed_room_never_lists_member_names() {
4642 let mut core = open_fresh_core();
4643 let channel = room("!chan:example.org");
4644 let target = room("!target:example.org");
4645 add_room(&mut core, &channel, true, None);
4646 mark_as_channel(&mut core, &channel);
4647 if let Some(state) = core.rooms.get_mut(&channel) {
4648 state.members.insert(
4649 UserId::parse("@erin:example.org").expect("valid user id"),
4650 crate::room::state::MemberState {
4651 membership: Membership::Join,
4652 displayname: Some("Erin Private".to_string()),
4653 is_direct: false,
4654 invited_by: None,
4655 },
4656 );
4657 }
4658 add_room(&mut core, &target, false, None);
4659 seed_plain(&mut core, &channel, "$post:example.org", "@erin:example.org", "m.room.message", serde_json::json!({ "msgtype": "m.text", "body": "hi" }));
4660
4661 forward(&mut core, &channel, "$post:example.org", &target).expect("forwardable");
4662
4663 let (_, body) = sent_room_event(&mut core);
4664 let name = body["forwarded_from"]["room_name"].as_str().expect("a name is sent");
4665 assert!(!name.contains("Erin"), "an unnamed room is attributed by a generic label, not by its members: {name}");
4666 }
4667
4668 #[test]
4669 fn forward_replaces_the_sources_own_forward_marker() {
4670 let mut core = open_fresh_core();
4671 let dm = room("!dm:example.org");
4672 let target = room("!target:example.org");
4673 add_room(&mut core, &dm, true, None);
4674 add_room(&mut core, &target, false, None);
4675 seed_decrypted(
4676 &mut core,
4677 &dm,
4678 "$src:example.org",
4679 "@bob:example.org",
4680 "m.room.message",
4681 serde_json::json!({
4682 "msgtype": "m.text",
4683 "body": "second hop",
4684 "forwarded_from": { "room_id": "!older:example.org", "room_name": "Older channel" },
4685 }),
4686 );
4687
4688 forward(&mut core, &dm, "$src:example.org", &target).expect("forwardable");
4689
4690 let (_, body) = sent_room_event(&mut core);
4691 assert_eq!(body, serde_json::json!({ "msgtype": "m.text", "body": "second hop", "forwarded": true }));
4692 }
4693
4694 #[test]
4695 fn forward_of_edited_item_sends_the_latest_text() {
4696 let mut core = open_fresh_core();
4697 let channel = room("!chan:example.org");
4698 let dm = room("!dm:example.org");
4699 let target = room("!target:example.org");
4700 add_room(&mut core, &channel, false, Some("Announcements"));
4701 add_room(&mut core, &dm, true, None);
4702 add_room(&mut core, &target, false, None);
4703
4704 seed_plain(&mut core, &channel, "$orig:example.org", "@dave:example.org", "m.room.message", serde_json::json!({ "msgtype": "m.text", "body": "typo text" }));
4706 seed_plain(
4707 &mut core,
4708 &channel,
4709 "$edit:example.org",
4710 "@dave:example.org",
4711 "m.room.message",
4712 serde_json::json!({
4713 "msgtype": "m.text",
4714 "body": "* fixed text",
4715 "m.new_content": { "msgtype": "m.text", "body": "fixed text" },
4716 "m.relates_to": { "rel_type": "m.replace", "event_id": "$orig:example.org" },
4717 }),
4718 );
4719 forward(&mut core, &channel, "$orig:example.org", &target).expect("forwardable");
4720 let (_, body) = sent_room_event(&mut core);
4721 assert_eq!(body["body"], "fixed text", "the latest edit's text, not the original and not the `* ` fallback");
4722 assert!(body.get("m.new_content").is_none() && body.get("m.relates_to").is_none());
4723
4724 seed_decrypted(&mut core, &dm, "$secret:example.org", "@bob:example.org", "m.room.message", serde_json::json!({ "msgtype": "m.text", "body": "typo secret" }));
4726 seed_sealed(&mut core, &dm, "$secret-edit:example.org", "@bob:example.org");
4727 let applied = core.timelines.get_mut(&dm).expect("timeline").apply_decrypted_relation(
4728 &event("$secret-edit:example.org"),
4729 UserId::parse("@bob:example.org").expect("valid user id"),
4730 2,
4731 RelatesTo::Replace { event_id: event("$secret:example.org") },
4732 Some(serde_json::json!({ "msgtype": "m.text", "body": "fixed secret" })),
4733 );
4734 assert!(applied, "the decrypted edit folds onto its target");
4735 forward(&mut core, &dm, "$secret:example.org", &target).expect("forwardable");
4736 let (_, body) = sent_room_event(&mut core);
4737 assert_eq!(body, serde_json::json!({ "msgtype": "m.text", "body": "fixed secret", "forwarded": true }));
4738 }
4739
4740 #[test]
4741 fn forward_of_unknown_msgtype_keeps_its_fields() {
4742 let mut core = open_fresh_core();
4743 let dm = room("!dm:example.org");
4744 let target = room("!target:example.org");
4745 add_room(&mut core, &dm, true, None);
4746 add_room(&mut core, &target, false, None);
4747 let card = serde_json::json!({
4748 "msgtype": "com.example.card",
4749 "body": "opaque",
4750 "note": "kept raw",
4751 });
4752 seed_decrypted(&mut core, &dm, "$card:example.org", "@bob:example.org", "m.room.message", card.clone());
4753
4754 forward(&mut core, &dm, "$card:example.org", &target).expect("an unrecognized msgtype is still an m.room.message");
4755
4756 let (path, body) = sent_room_event(&mut core);
4757 assert!(path.contains("/send/m.room.message/"));
4758 let mut expected = card;
4759 expected["forwarded"] = serde_json::Value::Bool(true);
4760 assert_eq!(body, expected, "unrecognized msgtype fields pass through plus the marker");
4761 assert!(matches!(last_item(&core, &target).content, ItemContent::Unknown), "the echo does not invent a card type");
4762 }
4763
4764 #[test]
4765 fn forward_refuses_redacted_sealed_unknown_and_missing() {
4766 let mut core = open_fresh_core();
4767 let dm = room("!dm:example.org");
4768 let target = room("!target:example.org");
4769 add_room(&mut core, &dm, true, None);
4770 add_room(&mut core, &target, false, None);
4771
4772 seed_decrypted(&mut core, &dm, "$redacted:example.org", "@bob:example.org", "m.room.message", serde_json::json!({ "msgtype": "m.text", "body": "gone" }));
4773 core.timelines.get_mut(&dm).expect("timeline").apply_redaction(&event("$redacted:example.org"));
4774 seed_sealed(&mut core, &dm, "$sealed:example.org", "@bob:example.org");
4775 seed_sealed(&mut core, &dm, "$utd:example.org", "@bob:example.org");
4776 core.timelines
4777 .get_mut(&dm)
4778 .expect("timeline")
4779 .set_decrypted(&event("$utd:example.org"), ItemContent::Undecryptable { reason: "MissingSession".to_string() });
4780 seed_plain(&mut core, &dm, "$call:example.org", "@bob:example.org", "m.call.invite", serde_json::json!({ "call_id": "c1" }));
4781
4782 for id in ["$redacted:example.org", "$sealed:example.org", "$utd:example.org", "$call:example.org", "$missing:example.org"] {
4783 match forward(&mut core, &dm, id, &target) {
4784 Err(MessengerError::IntentRefused(reason)) => {
4785 assert!(reason.contains(id), "the refusal names the event {id}: {reason}");
4786 }
4787 other => panic!("forwarding {id} must be refused, got {other:?}"),
4788 }
4789 }
4790 match forward(&mut core, &room("!nowhere:example.org"), "$redacted:example.org", &target) {
4791 Err(MessengerError::IntentRefused(reason)) => assert!(reason.contains("$redacted:example.org"), "{reason}"),
4792 other => panic!("an unknown source room means a missing event, got {other:?}"),
4793 }
4794 match forward(&mut core, &dm, "$sealed:example.org", &room("!nowhere:example.org")) {
4795 Err(MessengerError::IntentRefused(reason)) => assert!(reason.contains("unknown"), "{reason}"),
4796 other => panic!("an unknown target room is refused, got {other:?}"),
4797 }
4798
4799 assert!(core.send_state.is_empty(), "a refused forward queues nothing");
4800 assert!(core.timeline(&target).is_some_and(|timeline| timeline.items().is_empty()), "and leaves no echo");
4801 }
4802}