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 {
318 name: String,
320 topic: Option<String>,
322 },
323}
324
325#[derive(Clone, Debug, PartialEq)]
331pub enum MessengerCommand {
332 SendMessage {
338 room_id: RoomId,
340 message: OutgoingMessage,
342 txn_id: Option<TxnId>,
345 },
346 Forward {
372 from_room: RoomId,
374 event_id: EventId,
376 to_room: RoomId,
378 txn_id: Option<TxnId>,
380 },
381 React {
385 room_id: RoomId,
387 target: EventId,
389 key: String,
391 },
392 Redact {
394 room_id: RoomId,
396 target: EventId,
398 reason: Option<String>,
400 },
401 RetrySend {
407 room_id: RoomId,
409 txn_id: TxnId,
411 },
412 CreateRoom {
414 kind: CreateRoomKind,
416 },
417 JoinRoom {
420 room_id: RoomId,
422 },
423 LeaveRoom {
425 room_id: RoomId,
427 },
428 Invite {
430 room_id: RoomId,
432 user_id: UserId,
434 },
435 Kick {
440 room_id: RoomId,
442 user_id: UserId,
444 reason: Option<String>,
446 },
447 SetPowerLevels {
453 room_id: RoomId,
455 events_default: Option<i64>,
458 users: Vec<(UserId, i64)>,
460 },
461 SetTag {
463 room_id: RoomId,
465 tag: String,
467 order: Option<f64>,
469 },
470 RemoveTag {
472 room_id: RoomId,
474 tag: String,
476 },
477 MarkRead {
480 room_id: RoomId,
482 event_id: EventId,
484 },
485 SetTyping {
488 room_id: RoomId,
490 typing: bool,
492 },
493 RetryDecryption {
498 room_id: RoomId,
500 event_id: EventId,
502 },
503 LoadOlder {
508 room_id: RoomId,
510 },
511 SetAccountData {
517 event_type: String,
519 content: serde_json::Value,
521 },
522 SetRoomAccountData {
531 room_id: RoomId,
533 event_type: String,
535 content: serde_json::Value,
537 },
538 SearchPublicRooms {
543 term: String,
545 },
546 SearchUsers {
551 term: String,
553 },
554}
555
556#[derive(Clone, Debug, PartialEq, Eq)]
561pub enum MessengerEvent {
562 RoomsChanged,
565 TimelineChanged {
567 room_id: RoomId,
569 },
570 TypingChanged {
572 room_id: RoomId,
574 },
575 ReceiptsChanged {
577 room_id: RoomId,
579 },
580 UnreadChanged {
582 room_id: RoomId,
584 },
585 DeviceKeyChanged {
589 user_id: UserId,
591 device_id: DeviceId,
593 },
594 PublicRoomsChanged,
598 UserSearchChanged,
602}
603
604#[derive(Clone)]
609struct PendingDecryptItem {
610 session_id: String,
611 sender: UserId,
612 origin_server_ts: i64,
613 content: MegolmEncryptedContent,
614}
615
616#[derive(Deserialize)]
622struct RoomMessagesResponseBody {
623 #[serde(default)]
624 chunk: Vec<RawEvent>,
625 #[serde(default)]
626 end: Option<String>,
627}
628
629#[derive(Deserialize)]
631struct SendResponseBody {
632 event_id: EventId,
633}
634
635#[derive(Deserialize)]
637struct CreateRoomResponseBody {
638 room_id: RoomId,
639}
640
641#[derive(Clone, Debug, PartialEq)]
644pub struct PublicRoomsResultEntry {
645 pub room_id: RoomId,
647 pub name: Option<String>,
649 pub topic: Option<String>,
651 pub num_joined_members: u64,
653}
654
655#[derive(Deserialize)]
657struct PublicRoomsResponseBody {
658 #[serde(default)]
659 chunk: Vec<PublicRoomsChunkEntry>,
660}
661
662#[derive(Deserialize)]
663struct PublicRoomsChunkEntry {
664 room_id: RoomId,
665 #[serde(default)]
666 name: Option<String>,
667 #[serde(default)]
668 topic: Option<String>,
669 #[serde(default)]
670 num_joined_members: u64,
671}
672
673#[derive(Clone, Debug, PartialEq)]
676pub struct UserDirectoryResultEntry {
677 pub user_id: UserId,
679 pub display_name: Option<String>,
681}
682
683#[derive(Deserialize)]
686struct UserDirectorySearchResponseBody {
687 #[serde(default)]
688 results: Vec<UserDirectorySearchResultEntry>,
689}
690
691#[derive(Deserialize)]
692struct UserDirectorySearchResultEntry {
693 user_id: UserId,
694 #[serde(default)]
695 display_name: Option<String>,
696}
697
698#[derive(Clone)]
703enum SendPayload {
704 Message(OutgoingMessage),
707 Event {
711 event_type: String,
713 content: serde_json::Value,
715 },
716 Reaction {
718 target: EventId,
720 key: String,
722 },
723 Redaction {
725 target: EventId,
727 reason: Option<String>,
729 },
730}
731
732struct RoomEventWire {
736 event_type: String,
737 content: serde_json::Value,
738 relates_to: Option<RelatesTo>,
741}
742
743impl SendPayload {
744 fn room_event(&self) -> Option<RoomEventWire> {
747 match self {
748 SendPayload::Message(message) => Some(RoomEventWire {
749 event_type: "m.room.message".to_string(),
750 content: message_wire_content(message),
751 relates_to: message_relates_to(message),
752 }),
753 SendPayload::Event { event_type, content } => {
754 Some(RoomEventWire { event_type: event_type.clone(), content: content.clone(), relates_to: None })
755 }
756 SendPayload::Reaction { .. } | SendPayload::Redaction { .. } => None,
757 }
758 }
759
760 fn is_room_event(&self) -> bool {
761 matches!(self, SendPayload::Message(_) | SendPayload::Event { .. })
762 }
763}
764
765#[derive(Clone)]
771enum WireSend {
772 Event {
774 event_type: String,
777 content: serde_json::Value,
779 },
780 Redact {
782 target: EventId,
784 reason: Option<String>,
786 },
787}
788
789#[derive(Clone)]
794enum SendPhase {
795 Queued,
799 AwaitingKeysQuery,
805 AwaitingKeysClaim {
808 devices: Vec<StoredDevice>,
812 },
813 AwaitingKeyShare {
818 pending: BTreeSet<RequestId>,
820 },
821 AwaitingSend,
823 Failed {
828 errcode: String,
830 },
831}
832
833#[derive(Clone)]
839struct PendingSend {
840 room_id: RoomId,
843 payload: SendPayload,
845 phase: SendPhase,
847 wire: Option<WireSend>,
849}
850
851type AccountDataKey = (Option<RoomId>, String);
854
855struct AccountDataGuard {
867 pending: u32,
869 server_value: Option<serde_json::Value>,
873}
874
875fn account_data_key_from_path(path: &str) -> Option<AccountDataKey> {
878 let segments: Vec<&str> = path.split('/').collect();
879 let n = segments.len();
880 if n < 4 || segments[n - 2] != "account_data" {
881 return None;
882 }
883 let event_type = percent_decode_segment(segments[n - 1])?;
884 if segments[n - 4] == "user" {
885 return Some((None, event_type));
886 }
887 if n >= 6 && segments[n - 4] == "rooms" && segments[n - 6] == "user" {
888 let room_id = RoomId::parse(percent_decode_segment(segments[n - 3])?).ok()?;
889 return Some((Some(room_id), event_type));
890 }
891 None
892}
893
894pub struct MessengerCore<C: RecordCodec> {
896 config: CoreConfig,
897 store: Store<C>,
898 account: OlmAccountState,
899 xsign: Option<CrossSigningState>,
901 xsign_inflight: bool,
902 xsign_attempts: u8,
903 outgoing: OutgoingQueue,
904 counters: Counters,
905 jitter: Box<dyn Jitter>,
906
907 rooms: BTreeMap<RoomId, RoomState>,
908 timelines: BTreeMap<RoomId, Timeline>,
909 direct_account_data: Option<DirectContent>,
910 global_account_data: BTreeMap<String, serde_json::Value>,
911 room_account_data: BTreeMap<RoomId, BTreeMap<String, serde_json::Value>>,
912 typing: BTreeMap<RoomId, Vec<UserId>>,
913 receipts: BTreeMap<RoomId, ReceiptContent>,
914
915 public_rooms_result: Vec<PublicRoomsResultEntry>,
918 latest_public_rooms_request: Option<RequestId>,
923 user_search_result: Vec<UserDirectoryResultEntry>,
926 latest_user_search_request: Option<RequestId>,
929
930 account_data_guard: BTreeMap<AccountDataKey, AccountDataGuard>,
934 account_data_write_keys: BTreeMap<RequestId, AccountDataKey>,
936
937 pending_decrypt: BTreeMap<RoomId, BTreeMap<EventId, PendingDecryptItem>>,
938 key_requests_sent: BTreeSet<(RoomId, String)>,
942 history_claims: BTreeMap<RequestId, Vec<StoredDevice>>,
945 history_claim_tried: BTreeSet<(UserId, DeviceId)>,
947 withheld_sessions: BTreeSet<(RoomId, String)>,
949 key_requests_answered: BTreeSet<(UserId, DeviceId, String)>,
951 unknown_sender_retry: VecDeque<ToDeviceEvent>,
952
953 typing_debounce: BTreeMap<RoomId, i64>,
958 pending_sends: BTreeMap<RoomId, VecDeque<TxnId>>,
964 send_state: BTreeMap<TxnId, PendingSend>,
969 send_request_owner: BTreeMap<RequestId, TxnId>,
973 pending_create_room: BTreeMap<RequestId, CreateRoomKind>,
977
978 pending_kinds: BTreeMap<RequestId, OutgoingRequestKind>,
984 pending_room_messages: BTreeMap<RequestId, RoomId>,
987 sync_request_id: Option<RequestId>,
990 sync_caught_up: bool,
995 pending_flush_batches: VecDeque<FlushBatch>,
1002
1003 events: Vec<MessengerEvent>,
1004 change_counter: u64,
1005 ingest_error: Option<String>,
1010}
1011
1012impl<C: RecordCodec> MessengerCore<C> {
1013 pub fn open(
1026 records: impl IntoIterator<Item = SealedRecord>,
1027 codec: C,
1028 config: CoreConfig,
1029 secrets: CoreSecrets,
1030 _now_ms: i64,
1031 jitter: Box<dyn Jitter>,
1032 ) -> Result<Self, MessengerError> {
1033 let _ = secrets;
1037 let mut store = Store::load(records, codec, config.device_id.clone())?;
1038 let mut outgoing = OutgoingQueue::load(&store)?;
1039 let account = OlmAccountState::load_or_create(&mut store)?;
1040
1041 let mut rooms = BTreeMap::new();
1042 let mut timelines = BTreeMap::new();
1043 for room_id in store.room_ids()? {
1044 if let Some(bytes) = store.room_state(&room_id)? {
1045 let state: RoomState = serde_json::from_slice(bytes)
1046 .map_err(|source| MessengerError::Crypto(format!("decode room state for {room_id}: {source}")))?;
1047 rooms.insert(room_id.clone(), state);
1048 timelines.insert(room_id, Timeline::new());
1049 }
1050 }
1051
1052 let pending_requests = store.pending_requests()?;
1053 let max_pending_request_seed = pending_requests.iter().filter_map(|req| req.request.id.as_seed()).max();
1054 outgoing.discard_kind(&mut store, OutgoingRequestKind::Sync)?;
1061 let persisted_counters = store.counters()?.unwrap_or_default();
1062 let mut counters = Counters {
1063 next_request_id: restore_counter(persisted_counters.next_request_id, max_pending_request_seed),
1064 next_txn_id: restore_counter(persisted_counters.next_txn_id, None),
1065 };
1066 if counters != persisted_counters {
1067 store.save_counters(counters)?;
1073 }
1074 counters.next_txn_id = counters.next_txn_id.max(fresh_txn_seed());
1080
1081 let mut core = Self {
1082 config,
1083 store,
1084 account,
1085 outgoing,
1086 counters,
1087 jitter,
1088 rooms,
1089 timelines,
1090 direct_account_data: None,
1091 global_account_data: BTreeMap::new(),
1092 room_account_data: BTreeMap::new(),
1093 typing: BTreeMap::new(),
1094 receipts: BTreeMap::new(),
1095 public_rooms_result: Vec::new(),
1096 latest_public_rooms_request: None,
1097 user_search_result: Vec::new(),
1098 latest_user_search_request: None,
1099 account_data_guard: BTreeMap::new(),
1100 account_data_write_keys: BTreeMap::new(),
1101 pending_decrypt: BTreeMap::new(),
1102 key_requests_sent: BTreeSet::new(),
1103 history_claims: BTreeMap::new(),
1104 history_claim_tried: BTreeSet::new(),
1105 withheld_sessions: BTreeSet::new(),
1106 key_requests_answered: BTreeSet::new(),
1107 unknown_sender_retry: VecDeque::new(),
1108 typing_debounce: BTreeMap::new(),
1109 pending_sends: BTreeMap::new(),
1110 send_state: BTreeMap::new(),
1111 send_request_owner: BTreeMap::new(),
1112 pending_create_room: BTreeMap::new(),
1113 pending_kinds: BTreeMap::new(),
1114 pending_room_messages: BTreeMap::new(),
1115 sync_request_id: None,
1116 sync_caught_up: false,
1117 pending_flush_batches: VecDeque::new(),
1118 events: Vec::new(),
1119 change_counter: 0,
1120 ingest_error: None,
1121 xsign: None,
1122 xsign_inflight: false,
1123 xsign_attempts: 0,
1124 };
1125 core.rebuild_account_data_guard(&pending_requests);
1126 Ok(core)
1127 }
1128
1129 fn rebuild_account_data_guard(&mut self, pending_requests: &[PendingRequest]) {
1137 let mut writes: Vec<&PendingRequest> = pending_requests
1138 .iter()
1139 .filter(|record| matches!(record.request.kind, OutgoingRequestKind::AccountData | OutgoingRequestKind::RoomAccountData))
1140 .collect();
1141 writes.sort_by(|a, b| a.request.id.cmp(&b.request.id));
1142 for record in writes {
1143 let Some(key) = account_data_key_from_path(&record.request.path) else { continue };
1144 let Some(body) = record.request.body.clone() else { continue };
1145 let guard = self.account_data_guard.entry(key.clone()).or_insert(AccountDataGuard { pending: 0, server_value: None });
1146 guard.pending += 1;
1147 self.store_account_data_cache(&key, Some(body));
1148 self.account_data_write_keys.insert(record.request.id.clone(), key);
1149 }
1150 }
1151
1152 fn next_request_id(&mut self) -> Result<RequestId, MessengerError> {
1156 let seed = self.counters.next_request_id;
1157 self.counters.next_request_id = seed.saturating_add(1);
1158 self.store.save_counters(self.counters)?;
1159 Ok(RequestId::next(seed))
1160 }
1161
1162 fn next_txn_id(&mut self) -> Result<TxnId, MessengerError> {
1167 let seed = self.counters.next_txn_id;
1168 self.counters.next_txn_id = seed.saturating_add(1);
1169 self.store.save_counters(self.counters)?;
1170 Ok(TxnId::new(seed))
1171 }
1172
1173 fn enqueue_request(&mut self, request: OutgoingRequest, lane: Lane) -> Result<(), MessengerError> {
1188 let id = request.id.clone();
1189 let kind = request.kind;
1190 if let Some(batch) = self.outgoing.enqueue(&mut self.store, request, lane)? {
1191 self.pending_flush_batches.push_back(batch);
1192 }
1193 self.pending_kinds.insert(id, kind);
1194 Ok(())
1195 }
1196
1197 fn emit(&mut self, event: MessengerEvent) {
1198 self.change_counter = self.change_counter.wrapping_add(1);
1199 self.events.push(event);
1200 }
1201
1202 fn drain_events(&mut self) -> Vec<MessengerEvent> {
1203 std::mem::take(&mut self.events)
1204 }
1205
1206 fn start_new_send(
1222 &mut self,
1223 room_id: RoomId,
1224 payload: SendPayload,
1225 caller_txn_id: Option<TxnId>,
1226 now_ms: i64,
1227 ) -> Result<(), MessengerError> {
1228 let txn_id = match caller_txn_id {
1229 Some(txn_id) => txn_id,
1230 None => self.next_txn_id()?,
1231 };
1232
1233 if let Some(wire) = payload.room_event() {
1234 if self.typing_debounce.remove(&room_id).is_some() {
1235 if let Ok(request_id) = self.next_request_id() {
1236 let request = OutgoingRequest::typing(request_id, &room_id, &self.config.user_id, false, None);
1237 let _ = self.enqueue_request(request, Lane::Other);
1238 }
1239 }
1240 let content = match &payload {
1241 SendPayload::Message(message) => message_local_echo_content(message),
1242 _ => interpret_content(&wire.event_type, None, &wire.content),
1243 };
1244 let timeline = self.timelines.entry(room_id.clone()).or_default();
1245 timeline.push_local_echo(
1246 txn_id.clone(),
1247 self.config.user_id.clone(),
1248 now_ms,
1249 &wire.event_type,
1250 content,
1251 wire.content,
1252 );
1253 timeline.mark_send_state(&txn_id, SendState::Sending);
1254 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
1255 }
1256
1257 self.send_state.insert(txn_id.clone(), PendingSend { room_id: room_id.clone(), payload, phase: SendPhase::Queued, wire: None });
1258 let queue = self.pending_sends.entry(room_id).or_default();
1259 let was_empty = queue.is_empty();
1260 queue.push_back(txn_id.clone());
1261 if was_empty {
1262 self.start_send_pipeline(&txn_id, now_ms)?;
1263 }
1264 Ok(())
1265 }
1266
1267 fn retry_send(&mut self, room_id: &RoomId, txn_id: &TxnId, now_ms: i64) -> Result<(), MessengerError> {
1272 let Some(pending) = self.send_state.get(txn_id) else { return Ok(()) };
1273 if pending.room_id != *room_id || !matches!(pending.phase, SendPhase::Failed { .. }) {
1274 return Ok(());
1275 }
1276 self.set_send_phase(txn_id, SendPhase::Queued);
1277 let queue = self.pending_sends.entry(room_id.clone()).or_default();
1278 let was_empty = queue.is_empty();
1279 queue.push_back(txn_id.clone());
1280 if was_empty {
1281 self.start_send_pipeline(txn_id, now_ms)?;
1282 }
1283 Ok(())
1284 }
1285
1286 fn set_send_phase(&mut self, txn_id: &TxnId, phase: SendPhase) {
1287 if let Some(entry) = self.send_state.get_mut(txn_id) {
1288 entry.phase = phase;
1289 }
1290 }
1291
1292 fn start_send_pipeline(&mut self, txn_id: &TxnId, now_ms: i64) -> Result<(), MessengerError> {
1301 let Some(pending) = self.send_state.get(txn_id) else { return Ok(()) };
1302 let room_id = pending.room_id.clone();
1303
1304 if pending.wire.is_some() {
1305 return self.begin_awaiting_send(txn_id, &room_id);
1306 }
1307
1308 let is_room_event = pending.payload.is_room_event();
1309 let encrypted_room = self.rooms.get(&room_id).is_some_and(|room| room.encryption.is_some());
1310
1311 if is_room_event && encrypted_room {
1312 self.begin_key_pipeline(txn_id, &room_id, now_ms)
1313 } else {
1314 self.build_plaintext_wire_body(txn_id);
1315 self.begin_awaiting_send(txn_id, &room_id)
1316 }
1317 }
1318
1319 fn encrypted_room_member_ids(&self, room_id: &RoomId) -> BTreeSet<UserId> {
1324 let mut ids: BTreeSet<UserId> = self
1325 .rooms
1326 .get(room_id)
1327 .map(|state| {
1328 state
1329 .members
1330 .iter()
1331 .filter(|(_, member)| matches!(member.membership, Membership::Join | Membership::Invite))
1332 .map(|(user_id, _)| user_id.clone())
1333 .collect()
1334 })
1335 .unwrap_or_default();
1336 ids.insert(self.config.user_id.clone());
1337 ids
1338 }
1339
1340 fn mark_outdated_or_untracked(&mut self, member_ids: &BTreeSet<UserId>) -> Result<Vec<UserId>, MessengerError> {
1346 let tracked: BTreeMap<UserId, bool> = self.store.tracked_users()?.into_iter().collect();
1347 let mut outdated = Vec::new();
1348 let mut newly_tracked = Vec::new();
1349 for user_id in member_ids {
1350 match tracked.get(user_id) {
1351 Some(true) => outdated.push(user_id.clone()),
1352 Some(false) => {}
1353 None => newly_tracked.push(user_id.clone()),
1354 }
1355 }
1356 if !newly_tracked.is_empty() {
1357 DeviceTracker::on_device_lists(&mut self.store, &newly_tracked, &[])?;
1358 outdated.extend(newly_tracked);
1359 }
1360 Ok(outdated)
1361 }
1362
1363 fn known_devices_for(&self, member_ids: &BTreeSet<UserId>) -> Result<Vec<StoredDevice>, MessengerError> {
1366 let mut all = Vec::new();
1367 for user_id in member_ids {
1368 all.extend(DeviceTracker::devices_for_user(&self.store, user_id)?);
1369 }
1370 Ok(all)
1371 }
1372
1373 fn candidate_recipient_devices(&self, member_ids: &BTreeSet<UserId>, known_devices: &[StoredDevice]) -> Vec<StoredDevice> {
1377 known_devices
1378 .iter()
1379 .filter(|device| member_ids.contains(&device.user_id) && !device.blocked)
1380 .filter(|device| !(device.user_id == self.config.user_id && device.device_id == self.config.device_id))
1381 .cloned()
1382 .collect()
1383 }
1384
1385 fn begin_key_pipeline(&mut self, txn_id: &TxnId, room_id: &RoomId, now_ms: i64) -> Result<(), MessengerError> {
1390 let members = self.encrypted_room_member_ids(room_id);
1391 let outdated = self.mark_outdated_or_untracked(&members)?;
1392 if !outdated.is_empty() {
1393 let request_id = self.next_request_id()?;
1394 if let Some(request) = DeviceTracker::keys_query_request(&self.store, request_id.clone())? {
1395 self.send_request_owner.insert(request_id.clone(), txn_id.clone());
1396 self.enqueue_request(request, Lane::Other)?;
1397 self.set_send_phase(txn_id, SendPhase::AwaitingKeysQuery);
1398 return Ok(());
1399 }
1400 }
1401 self.begin_keys_claim_or_share(txn_id, room_id, now_ms)
1402 }
1403
1404 fn begin_keys_claim_or_share(&mut self, txn_id: &TxnId, room_id: &RoomId, now_ms: i64) -> Result<(), MessengerError> {
1408 let members = self.encrypted_room_member_ids(room_id);
1409 let known_devices = self.known_devices_for(&members)?;
1410 let candidates = self.candidate_recipient_devices(&members, &known_devices);
1411 let missing: Vec<StoredDevice> =
1412 OlmSessionManager::sessions_missing_for(&self.store, &candidates)?.into_iter().cloned().collect();
1413 if !missing.is_empty() {
1414 let request_id = self.next_request_id()?;
1415 let refs: Vec<&StoredDevice> = missing.iter().collect();
1416 if let Some(request) = OlmSessionManager::keys_claim_request(request_id.clone(), &refs) {
1417 self.send_request_owner.insert(request_id.clone(), txn_id.clone());
1418 self.enqueue_request(request, Lane::Other)?;
1419 self.set_send_phase(txn_id, SendPhase::AwaitingKeysClaim { devices: missing });
1420 return Ok(());
1421 }
1422 }
1423 self.begin_share_or_encrypt(txn_id, room_id, now_ms)
1424 }
1425
1426 fn begin_share_or_encrypt(&mut self, txn_id: &TxnId, room_id: &RoomId, now_ms: i64) -> Result<(), MessengerError> {
1439 let Some(encryption) = self.rooms.get(room_id).and_then(|room| room.encryption.clone()) else {
1440 self.build_plaintext_wire_body(txn_id);
1441 return self.begin_awaiting_send(txn_id, room_id);
1442 };
1443 let members = self.encrypted_room_member_ids(room_id);
1444 let known_devices = self.known_devices_for(&members)?;
1445 let update = GroupSessionManager::ensure_outbound_session(
1446 &mut self.store,
1447 room_id,
1448 &self.config.user_id,
1449 &self.config.device_id,
1450 &encryption,
1451 &members,
1452 &known_devices,
1453 now_ms,
1454 )?;
1455 let own_keys = self.account.identity_keys();
1456 GroupSessionManager::adopt_own_outbound_session(
1457 &mut self.store,
1458 room_id,
1459 &self.config.user_id,
1460 own_keys.curve25519,
1461 own_keys.ed25519,
1462 &update,
1463 )?;
1464
1465 if let Some(room_key_content) = update.room_key_content.clone() {
1466 let missing_ids: BTreeSet<(UserId, DeviceId)> = OlmSessionManager::sessions_missing_for(&self.store, &update.new_recipients)?
1467 .into_iter()
1468 .map(|device| (device.user_id.clone(), device.device_id.clone()))
1469 .collect();
1470 let sessioned: Vec<StoredDevice> = update
1471 .new_recipients
1472 .into_iter()
1473 .filter(|device| !missing_ids.contains(&(device.user_id.clone(), device.device_id.clone())))
1474 .collect();
1475
1476 let mut pending_ids = BTreeSet::new();
1477 for chunk in GroupSessionManager::chunk_recipients_for_send_to_device(&sessioned) {
1478 let body = GroupSessionManager::build_room_key_send_to_device_body(
1479 &mut self.store,
1480 &self.account,
1481 &self.config.user_id,
1482 &self.config.device_id,
1483 chunk,
1484 &room_key_content,
1485 )?;
1486 let request_id = self.next_request_id()?;
1487 let share_txn = self.next_txn_id()?;
1488 let request = OutgoingRequest::send_to_device(request_id.clone(), ROOM_KEY_SHARE_EVENT_TYPE, &share_txn, body);
1489 self.send_request_owner.insert(request_id.clone(), txn_id.clone());
1490 self.enqueue_request(request, Lane::ToDevice)?;
1491 pending_ids.insert(request_id);
1492 }
1493 if !pending_ids.is_empty() {
1494 self.set_send_phase(txn_id, SendPhase::AwaitingKeyShare { pending: pending_ids });
1495 return Ok(());
1496 }
1497 }
1498
1499 self.finish_encrypt_and_send(txn_id, room_id)?;
1500 self.begin_awaiting_send(txn_id, room_id)
1501 }
1502
1503 fn finish_encrypt_and_send(&mut self, txn_id: &TxnId, room_id: &RoomId) -> Result<(), MessengerError> {
1510 let Some(wire) = self.send_state.get(txn_id).and_then(|pending| pending.payload.room_event()) else {
1511 return Ok(());
1512 };
1513 let sender_curve = self.account.identity_keys().curve25519.to_base64();
1514 let encrypted = GroupSessionManager::encrypt_event(
1515 &mut self.store,
1516 room_id,
1517 &sender_curve,
1518 &self.config.device_id,
1519 &wire.event_type,
1520 wire.content,
1521 wire.relates_to,
1522 )?;
1523 let wire_content = serde_json::to_value(RoomEncryptedContent::Megolm(encrypted))?;
1524 if let Some(entry) = self.send_state.get_mut(txn_id) {
1525 entry.wire = Some(WireSend::Event { event_type: "m.room.encrypted".to_string(), content: wire_content });
1526 }
1527 Ok(())
1528 }
1529
1530 fn build_plaintext_wire_body(&mut self, txn_id: &TxnId) {
1536 let Some(pending) = self.send_state.get(txn_id) else { return };
1537 let wire = match &pending.payload {
1538 SendPayload::Message(_) | SendPayload::Event { .. } => match pending.payload.room_event() {
1539 Some(room_event) => WireSend::Event { event_type: room_event.event_type, content: room_event.content },
1540 None => return,
1541 },
1542 SendPayload::Reaction { target, key } => WireSend::Event {
1543 event_type: "m.reaction".to_string(),
1544 content: serde_json::json!({
1545 "m.relates_to": { "rel_type": "m.annotation", "event_id": target.as_str(), "key": key }
1546 }),
1547 },
1548 SendPayload::Redaction { target, reason } => WireSend::Redact { target: target.clone(), reason: reason.clone() },
1549 };
1550 if let Some(entry) = self.send_state.get_mut(txn_id) {
1551 entry.wire = Some(wire);
1552 }
1553 }
1554
1555 fn begin_awaiting_send(&mut self, txn_id: &TxnId, room_id: &RoomId) -> Result<(), MessengerError> {
1560 let Some(wire) = self.send_state.get(txn_id).and_then(|pending| pending.wire.clone()) else { return Ok(()) };
1561 let request_id = self.next_request_id()?;
1562 let request = match &wire {
1563 WireSend::Event { event_type, content } => {
1564 OutgoingRequest::room_send(request_id.clone(), room_id, event_type, txn_id, content.clone())
1565 }
1566 WireSend::Redact { target, reason } => {
1567 let body = match reason {
1568 Some(reason) => serde_json::json!({ "reason": reason }),
1569 None => serde_json::json!({}),
1570 };
1571 OutgoingRequest::room_redact(request_id.clone(), room_id, target, txn_id, body)
1572 }
1573 };
1574 self.send_request_owner.insert(request_id, txn_id.clone());
1575 self.enqueue_request(request, Lane::Room(room_id.clone()))?;
1576 self.set_send_phase(txn_id, SendPhase::AwaitingSend);
1577 Ok(())
1578 }
1579
1580 fn advance_send_on_success(
1586 &mut self,
1587 txn_id: &TxnId,
1588 request_id: &RequestId,
1589 kind: Option<OutgoingRequestKind>,
1590 response: &HttpResponseDescriptor,
1591 now_ms: i64,
1592 ) {
1593 match kind {
1594 Some(OutgoingRequestKind::KeysQuery) => {
1595 let Some(room_id) = self.send_state.get(txn_id).map(|pending| pending.room_id.clone()) else { return };
1596 let _ = self.begin_keys_claim_or_share(txn_id, &room_id, now_ms);
1597 }
1598 Some(OutgoingRequestKind::KeysClaim) => {
1599 let Some(pending) = self.send_state.get(txn_id) else { return };
1600 let room_id = pending.room_id.clone();
1601 if let SendPhase::AwaitingKeysClaim { devices, .. } = &pending.phase {
1602 let devices = devices.clone();
1603 let refs: Vec<&StoredDevice> = devices.iter().collect();
1604 let _ = OlmSessionManager::on_keys_claim_response(&mut self.store, &self.account, &refs, &response.body);
1605 }
1606 let _ = self.begin_share_or_encrypt(txn_id, &room_id, now_ms);
1607 }
1608 Some(OutgoingRequestKind::SendToDevice) => {
1609 let mut remaining = None;
1610 if let Some(entry) = self.send_state.get_mut(txn_id) {
1611 if let SendPhase::AwaitingKeyShare { pending } = &mut entry.phase {
1612 pending.remove(request_id);
1613 remaining = Some(pending.len());
1614 }
1615 }
1616 if remaining == Some(0) {
1617 let Some(room_id) = self.send_state.get(txn_id).map(|pending| pending.room_id.clone()) else { return };
1618 let _ = self.finish_encrypt_and_send(txn_id, &room_id);
1619 let _ = self.begin_awaiting_send(txn_id, &room_id);
1620 }
1621 }
1622 Some(OutgoingRequestKind::RoomSend) | Some(OutgoingRequestKind::RoomRedact) => {
1623 self.complete_send_success(txn_id, response, now_ms);
1624 }
1625 _ => {}
1626 }
1627 }
1628
1629 fn complete_send_success(&mut self, txn_id: &TxnId, response: &HttpResponseDescriptor, now_ms: i64) {
1635 let Some(pending) = self.send_state.get(txn_id) else { return };
1636 let room_id = pending.room_id.clone();
1637 if pending.payload.is_room_event() {
1638 if let Ok(body) = serde_json::from_slice::<SendResponseBody>(&response.body) {
1639 if let Some(timeline) = self.timelines.get_mut(&room_id) {
1640 timeline.set_echo_event_id(txn_id, body.event_id);
1641 }
1642 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
1643 }
1644 }
1645 self.send_state.remove(txn_id);
1646 self.finish_active_send(&room_id, txn_id, now_ms);
1647 }
1648
1649 fn fail_send(&mut self, txn_id: &TxnId, errcode: String, now_ms: i64) {
1655 let Some(pending) = self.send_state.get(txn_id) else { return };
1656 let room_id = pending.room_id.clone();
1657 if pending.payload.is_room_event() {
1658 if let Some(timeline) = self.timelines.get_mut(&room_id) {
1659 timeline.mark_send_state(txn_id, SendState::Failed { reason: errcode.clone() });
1660 }
1661 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
1662 }
1663 self.set_send_phase(txn_id, SendPhase::Failed { errcode });
1664 self.finish_active_send(&room_id, txn_id, now_ms);
1665 }
1666
1667 fn finish_active_send(&mut self, room_id: &RoomId, txn_id: &TxnId, now_ms: i64) {
1673 if let Some(queue) = self.pending_sends.get_mut(room_id) {
1674 if queue.front() == Some(txn_id) {
1675 queue.pop_front();
1676 } else {
1677 queue.retain(|id| id != txn_id);
1678 }
1679 }
1680 let next = self.pending_sends.get(room_id).and_then(|queue| queue.front().cloned());
1681 if let Some(next_txn) = next {
1682 let _ = self.start_send_pipeline(&next_txn, now_ms);
1683 }
1684 }
1685
1686 pub fn releasable_requests(&mut self, now_ms: i64) -> Vec<OutgoingRequest> {
1706 self.ensure_sync_enqueued();
1707 self.outgoing.releasable(&self.store, now_ms)
1708 }
1709
1710 fn ensure_sync_enqueued(&mut self) {
1711 if self.sync_request_id.is_some() {
1712 return;
1713 }
1714 let Ok(request_id) = self.next_request_id() else { return };
1715 let since = self.store.sync_token().ok().flatten().map(str::to_string);
1716 let timeout_ms = if since.is_some() && self.sync_caught_up { SYNC_TIMEOUT_MS } else { 0 };
1726 let request = OutgoingRequest::sync(request_id.clone(), since.as_deref(), Some(timeout_ms));
1727 if self.enqueue_request(request, Lane::Sync).is_ok() {
1728 self.sync_request_id = Some(request_id);
1729 }
1730 }
1731
1732 pub fn on_response(&mut self, request_id: RequestId, resp: HttpResponseDescriptor, now_ms: i64) -> Vec<MessengerEvent> {
1738 let kind = self.pending_kinds.get(&request_id).copied();
1739 let room_messages_room = self.pending_room_messages.get(&request_id).cloned();
1740 let create_room_kind = self.pending_create_room.get(&request_id).cloned();
1741 let send_owner = self.send_request_owner.get(&request_id).cloned();
1742
1743 let outcome = self.outgoing.on_response(&mut self.store, &request_id, resp, now_ms, self.jitter.as_mut());
1744 let (is_terminal, terminal_response, failed_errcode) = match outcome {
1745 Ok(Some(ResponseOutcome::Done(response))) => (true, Some(response), None),
1746 Ok(Some(ResponseOutcome::Failed { errcode, .. })) => (true, None, Some(errcode)),
1747 _ => (false, None, None),
1748 };
1749
1750 if is_terminal {
1751 self.pending_kinds.remove(&request_id);
1752 if matches!(kind, Some(OutgoingRequestKind::Sync)) {
1753 self.sync_request_id = None;
1754 if terminal_response.is_some() {
1755 self.sync_caught_up = true;
1756 }
1757 }
1758 if room_messages_room.is_some() {
1759 self.pending_room_messages.remove(&request_id);
1760 }
1761 self.pending_create_room.remove(&request_id);
1762 self.send_request_owner.remove(&request_id);
1763 if let Some(key) = self.account_data_write_keys.remove(&request_id) {
1764 self.settle_account_data_write(&key, terminal_response.is_some());
1765 }
1766 }
1767
1768 if let Some(response) = &terminal_response {
1769 if let Err(error) = self.handle_terminal_success(&request_id, kind, room_messages_room, create_room_kind, response) {
1776 self.ingest_error = Some(match kind {
1777 Some(kind) => format!("{kind:?} response not ingested: {error}"),
1778 None => format!("response not ingested: {error}"),
1779 });
1780 }
1781 }
1782
1783 if is_terminal
1784 && terminal_response.is_none()
1785 && matches!(kind, Some(OutgoingRequestKind::SigningKeysUpload) | Some(OutgoingRequestKind::SignaturesUpload))
1786 {
1787 self.xsign_inflight = false;
1788 }
1789
1790 if let Some(txn_id) = send_owner {
1791 if let Some(response) = &terminal_response {
1792 self.advance_send_on_success(&txn_id, &request_id, kind, response, now_ms);
1793 } else if let Some(errcode) = failed_errcode.clone() {
1794 self.fail_send(&txn_id, errcode, now_ms);
1795 }
1796 }
1797
1798 if matches!(kind, Some(OutgoingRequestKind::KeysUpload)) {
1802 if let Some(errcode) = failed_errcode {
1803 self.ingest_error = Some(format!("keys/upload failed: {errcode}"));
1804 }
1805 }
1806
1807 self.drain_events()
1808 }
1809
1810 pub fn on_transport_error(&mut self, request_id: &RequestId, now_ms: i64) {
1815 let _ = self.outgoing.on_transport_error(request_id, now_ms, self.jitter.as_mut());
1816 }
1817
1818 fn handle_terminal_success(
1819 &mut self,
1820 request_id: &RequestId,
1821 kind: Option<OutgoingRequestKind>,
1822 room_messages_room: Option<RoomId>,
1823 create_room_kind: Option<CreateRoomKind>,
1824 response: &HttpResponseDescriptor,
1825 ) -> Result<(), MessengerError> {
1826 match kind {
1827 Some(OutgoingRequestKind::Sync) => {
1828 let parsed = parse_sync_response(&response.body)?;
1829 self.ingest_sync(&parsed)?;
1830 }
1831 Some(OutgoingRequestKind::KeysUpload) => {
1832 self.account.on_keys_upload_response(&mut self.store)?;
1833 }
1834 Some(OutgoingRequestKind::KeysClaim) if self.history_claims.contains_key(request_id) => {
1835 if let Some(devices) = self.history_claims.remove(request_id) {
1836 let refs: Vec<&StoredDevice> = devices.iter().collect();
1837 let _ = OlmSessionManager::on_keys_claim_response(&mut self.store, &self.account, &refs, &response.body);
1838 }
1839 self.retry_missing_room_keys();
1840 }
1841 Some(OutgoingRequestKind::SigningKeysUpload) => {
1842 self.xsign_inflight = false;
1843 if let Some(state) = self.xsign.as_mut() {
1844 state.uploaded = true;
1845 let snapshot = state.clone();
1846 snapshot.save(&mut self.store)?;
1847 }
1848 self.maybe_enqueue_cross_signing()?;
1849 }
1850 Some(OutgoingRequestKind::SignaturesUpload) => {
1851 self.xsign_inflight = false;
1852 let failures_empty = serde_json::from_slice::<serde_json::Value>(&response.body)
1853 .ok()
1854 .and_then(|v| v.get("failures").and_then(|f| f.as_object()).map(|f| f.is_empty()))
1855 .unwrap_or(true);
1856 if failures_empty {
1857 if let Some(state) = self.xsign.as_mut() {
1858 state.device_signed = true;
1859 let snapshot = state.clone();
1860 snapshot.save(&mut self.store)?;
1861 }
1862 }
1863 }
1864 Some(OutgoingRequestKind::KeysQuery) => {
1865 let outcome = DeviceTracker::on_keys_query_response(&mut self.store, &response.body)?;
1866 if !outcome.key_changes.is_empty() {
1867 let room_ids: Vec<RoomId> = self.rooms.keys().cloned().collect();
1868 for change in &outcome.key_changes {
1869 let _ = GroupSessionManager::forget_shared_device(
1870 &mut self.store,
1871 &room_ids,
1872 &change.user_id,
1873 &change.device_id,
1874 );
1875 self.emit(MessengerEvent::DeviceKeyChanged {
1876 user_id: change.user_id.clone(),
1877 device_id: change.device_id.clone(),
1878 });
1879 }
1880 }
1881 self.retry_unknown_sender_queue()?;
1882 }
1883 Some(OutgoingRequestKind::RoomMessages) => {
1884 if let Some(room_id) = room_messages_room {
1885 self.ingest_room_messages_response(&room_id, &response.body)?;
1886 }
1887 }
1888 Some(OutgoingRequestKind::CreateRoom) => {
1889 if let Some(kind) = create_room_kind {
1890 self.on_create_room_response(kind, response)?;
1891 }
1892 }
1893 Some(OutgoingRequestKind::PublicRooms) if self.latest_public_rooms_request.as_ref() == Some(request_id) => {
1898 let body: PublicRoomsResponseBody = serde_json::from_slice(&response.body)?;
1899 self.public_rooms_result = body
1900 .chunk
1901 .into_iter()
1902 .map(|entry| PublicRoomsResultEntry {
1903 room_id: entry.room_id,
1904 name: entry.name,
1905 topic: entry.topic,
1906 num_joined_members: entry.num_joined_members,
1907 })
1908 .collect();
1909 self.emit(MessengerEvent::PublicRoomsChanged);
1910 }
1911 Some(OutgoingRequestKind::UserDirectorySearch) if self.latest_user_search_request.as_ref() == Some(request_id) => {
1913 let body: UserDirectorySearchResponseBody = serde_json::from_slice(&response.body)?;
1914 self.user_search_result = body
1915 .results
1916 .into_iter()
1917 .map(|entry| UserDirectoryResultEntry { user_id: entry.user_id, display_name: entry.display_name })
1918 .collect();
1919 self.emit(MessengerEvent::UserSearchChanged);
1920 }
1921 _ => {}
1922 }
1923 Ok(())
1924 }
1925
1926 fn on_create_room_response(&mut self, kind: CreateRoomKind, response: &HttpResponseDescriptor) -> Result<(), MessengerError> {
1932 let CreateRoomKind::Dm { peer } = kind else { return Ok(()) };
1933 let body: CreateRoomResponseBody = serde_json::from_slice(&response.body)?;
1934 self.merge_direct_account_data(body.room_id, peer)
1935 }
1936
1937 fn merge_direct_account_data(&mut self, room_id: RoomId, peer: UserId) -> Result<(), MessengerError> {
1949 let mut direct = self.direct_account_data.clone().unwrap_or_default();
1950 let rooms_for_peer = direct.0.entry(peer).or_default();
1951 if !rooms_for_peer.contains(&room_id) {
1952 rooms_for_peer.push(room_id);
1953 }
1954 let value = serde_json::to_value(&direct)?;
1955 self.write_account_data(None, "m.direct".to_string(), value)
1956 }
1957
1958 fn cached_account_data(&self, key: &AccountDataKey) -> Option<&serde_json::Value> {
1959 match &key.0 {
1960 Some(room_id) => self.room_account_data(room_id, &key.1),
1961 None => self.global_account_data(&key.1),
1962 }
1963 }
1964
1965 fn store_account_data_cache(&mut self, key: &AccountDataKey, value: Option<serde_json::Value>) {
1968 if key.0.is_none() && key.1 == "m.direct" {
1969 self.direct_account_data = value.as_ref().and_then(|v| serde_json::from_value::<DirectContent>(v.clone()).ok());
1970 }
1971 match (&key.0, value) {
1972 (None, Some(v)) => {
1973 self.global_account_data.insert(key.1.clone(), v);
1974 }
1975 (None, None) => {
1976 self.global_account_data.remove(&key.1);
1977 }
1978 (Some(room_id), Some(v)) => {
1979 self.room_account_data.entry(room_id.clone()).or_default().insert(key.1.clone(), v);
1980 }
1981 (Some(room_id), None) => {
1982 if let Some(by_type) = self.room_account_data.get_mut(room_id) {
1983 by_type.remove(&key.1);
1984 }
1985 }
1986 }
1987 }
1988
1989 fn write_account_data(&mut self, room_id: Option<RoomId>, event_type: String, value: serde_json::Value) -> Result<(), MessengerError> {
1995 let request_id = self.next_request_id()?;
1996 let request = match &room_id {
1997 Some(room_id) => OutgoingRequest::room_account_data(request_id.clone(), &self.config.user_id, room_id, &event_type, value.clone()),
1998 None => OutgoingRequest::account_data(request_id.clone(), &self.config.user_id, &event_type, value.clone()),
1999 };
2000 let key: AccountDataKey = (room_id, event_type);
2001 let known = self.cached_account_data(&key).cloned();
2002 let guard = self.account_data_guard.entry(key.clone()).or_insert(AccountDataGuard { pending: 0, server_value: known });
2003 guard.pending += 1;
2004 self.store_account_data_cache(&key, Some(value));
2005 match self.enqueue_request(request, Lane::AccountData) {
2006 Ok(()) => {
2007 self.account_data_write_keys.insert(request_id, key);
2008 Ok(())
2009 }
2010 Err(error) => {
2011 self.settle_account_data_write(&key, false);
2012 Err(error)
2013 }
2014 }
2015 }
2016
2017 fn settle_account_data_write(&mut self, key: &AccountDataKey, succeeded: bool) {
2022 let Some(guard) = self.account_data_guard.get_mut(key) else { return };
2023 guard.pending = guard.pending.saturating_sub(1);
2024 if guard.pending > 0 {
2025 return;
2026 }
2027 let Some(guard) = self.account_data_guard.remove(key) else { return };
2028 if !succeeded {
2029 self.store_account_data_cache(key, guard.server_value);
2030 self.emit(MessengerEvent::RoomsChanged);
2031 }
2032 }
2033
2034 fn apply_synced_account_data(&mut self, room_id: Option<&RoomId>, event_type: &str, content: serde_json::Value) {
2038 let key: AccountDataKey = (room_id.cloned(), event_type.to_string());
2039 if let Some(guard) = self.account_data_guard.get_mut(&key) {
2040 guard.server_value = Some(content);
2041 return;
2042 }
2043 self.store_account_data_cache(&key, Some(content));
2044 }
2045
2046 pub fn take_flush_batch(&mut self) -> Option<FlushBatch> {
2049 if let Some(batch) = self.pending_flush_batches.pop_front() {
2050 return Some(batch);
2051 }
2052 self.store.take_flush_batch()
2053 }
2054
2055 pub fn ack_flush(&mut self, id: u64) {
2057 self.store.ack_flush(id);
2058 }
2059
2060 pub fn events(&mut self) -> Vec<MessengerEvent> {
2063 self.drain_events()
2064 }
2065
2066 pub fn take_ingest_error(&mut self) -> Option<String> {
2069 self.ingest_error.take()
2070 }
2071
2072 pub fn change_counter(&self) -> u64 {
2077 self.change_counter
2078 }
2079
2080 pub fn room_ids(&self) -> impl Iterator<Item = &RoomId> {
2082 self.rooms.keys()
2083 }
2084
2085 pub fn room_state(&self, room_id: &RoomId) -> Option<&RoomState> {
2087 self.rooms.get(room_id)
2088 }
2089
2090 pub fn timeline(&self, room_id: &RoomId) -> Option<&Timeline> {
2092 self.timelines.get(room_id)
2093 }
2094
2095 pub fn room_kind(&self, room_id: &RoomId) -> Option<RoomKind> {
2099 self.rooms.get(room_id).map(|state| state.derive_room_kind(room_id, self.direct_account_data.as_ref()))
2100 }
2101
2102 pub fn typing_users(&self, room_id: &RoomId) -> &[UserId] {
2106 self.typing.get(room_id).map(Vec::as_slice).unwrap_or(&[])
2107 }
2108
2109 pub fn receipts(&self, room_id: &RoomId) -> Option<&ReceiptContent> {
2111 self.receipts.get(room_id)
2112 }
2113
2114 pub fn room_account_data(&self, room_id: &RoomId, event_type: &str) -> Option<&serde_json::Value> {
2117 self.room_account_data.get(room_id).and_then(|by_type| by_type.get(event_type))
2118 }
2119
2120 pub fn global_account_data(&self, event_type: &str) -> Option<&serde_json::Value> {
2123 self.global_account_data.get(event_type)
2124 }
2125
2126 pub fn key_request_count(&self) -> usize {
2128 self.key_requests_sent.len()
2129 }
2130
2131 pub fn user_id(&self) -> &UserId {
2136 &self.config.user_id
2137 }
2138
2139 pub fn has_tracked_device(&self, user_id: &UserId) -> bool {
2145 DeviceTracker::devices_for_user(&self.store, user_id).map(|devices| !devices.is_empty()).unwrap_or(false)
2146 }
2147
2148 pub fn public_rooms_result(&self) -> &[PublicRoomsResultEntry] {
2151 &self.public_rooms_result
2152 }
2153
2154 pub fn user_search_result(&self) -> &[UserDirectoryResultEntry] {
2157 &self.user_search_result
2158 }
2159
2160 pub fn send_failure_reason(&self, txn_id: &TxnId) -> Option<&str> {
2165 match self.send_state.get(txn_id).map(|pending| &pending.phase) {
2166 Some(SendPhase::Failed { errcode }) => Some(errcode.as_str()),
2167 _ => None,
2168 }
2169 }
2170
2171 pub fn dispatch(&mut self, cmd: MessengerCommand, now_ms: i64) -> Result<(), MessengerError> {
2178 match cmd {
2179 MessengerCommand::SendMessage { room_id, message, txn_id } => {
2180 if let Some(edit_of) = &message.edit_of {
2181 let owned = self
2182 .timelines
2183 .get(&room_id)
2184 .and_then(|timeline| timeline.item_by_event_id(edit_of))
2185 .is_some_and(|item| item.sender == self.config.user_id);
2186 if !owned {
2187 return Err(MessengerError::EditNotOwned { event_id: edit_of.clone() });
2188 }
2189 }
2190 self.start_new_send(room_id, SendPayload::Message(message), txn_id, now_ms)?;
2191 }
2192 MessengerCommand::Forward { from_room, event_id, to_room, txn_id } => {
2193 let payload = self.build_forward_payload(&from_room, &event_id, &to_room)?;
2194 self.start_new_send(to_room, payload, txn_id, now_ms)?;
2195 }
2196 MessengerCommand::React { room_id, target, key } => {
2197 self.start_new_send(room_id, SendPayload::Reaction { target, key }, None, now_ms)?;
2198 }
2199 MessengerCommand::Redact { room_id, target, reason } => {
2200 self.start_new_send(room_id, SendPayload::Redaction { target, reason }, None, now_ms)?;
2201 }
2202 MessengerCommand::RetrySend { room_id, txn_id } => {
2203 self.retry_send(&room_id, &txn_id, now_ms)?;
2204 }
2205 MessengerCommand::CreateRoom { kind } => {
2206 self.dispatch_create_room(kind)?;
2207 }
2208 MessengerCommand::JoinRoom { room_id } => {
2209 let request_id = self.next_request_id()?;
2210 let request = OutgoingRequest::join_room(request_id, room_id.as_str());
2211 self.enqueue_request(request, Lane::Other)?;
2212
2213 let dm_peer = self.rooms.get(&room_id).and_then(|room| {
2220 let invited_as_direct = room.members.get(&self.config.user_id).is_some_and(|member| member.is_direct);
2221 if !invited_as_direct {
2222 return None;
2223 }
2224 room.members.keys().find(|user_id| **user_id != self.config.user_id).cloned()
2225 });
2226 if let Some(peer) = dm_peer {
2227 self.merge_direct_account_data(room_id, peer)?;
2228 }
2229 }
2230 MessengerCommand::LeaveRoom { room_id } => {
2231 let request_id = self.next_request_id()?;
2232 let request = OutgoingRequest::leave_room(request_id, &room_id);
2233 self.enqueue_request(request, Lane::Other)?;
2234 }
2235 MessengerCommand::Invite { room_id, user_id } => {
2236 let request_id = self.next_request_id()?;
2237 let request = OutgoingRequest::invite(request_id, &room_id, &user_id);
2238 self.enqueue_request(request, Lane::Other)?;
2239 }
2240 MessengerCommand::Kick { room_id, user_id, reason } => {
2241 let request_id = self.next_request_id()?;
2242 let request = OutgoingRequest::kick(request_id, &room_id, &user_id, reason.as_deref());
2243 self.enqueue_request(request, Lane::Other)?;
2244 }
2245 MessengerCommand::SetPowerLevels { room_id, events_default, users } => {
2246 let Some(state) = self.rooms.get(&room_id) else {
2247 return Err(MessengerError::IntentRefused(format!("room {room_id} is unknown, its power levels cannot be changed")));
2248 };
2249 let mut levels = state.power_levels.clone();
2250 if let Some(level) = events_default {
2251 levels.events_default = level;
2252 }
2253 for (user, level) in users {
2254 if level == levels.users_default {
2255 levels.users.remove(&user);
2256 } else {
2257 levels.users.insert(user, level);
2258 }
2259 }
2260 let body = serde_json::to_value(&levels)?;
2261 let request_id = self.next_request_id()?;
2262 let request = OutgoingRequest::room_state(request_id, &room_id, "m.room.power_levels", "", body);
2263 self.enqueue_request(request, Lane::Other)?;
2264 }
2265 MessengerCommand::SetTag { room_id, tag, order } => {
2266 let mut content = self.current_tag_content(&room_id);
2267 content.tags.insert(tag, TagInfo { order });
2268 self.enqueue_tag_update(room_id, content)?;
2269 }
2270 MessengerCommand::RemoveTag { room_id, tag } => {
2271 let mut content = self.current_tag_content(&room_id);
2272 content.tags.remove(&tag);
2273 self.enqueue_tag_update(room_id, content)?;
2274 }
2275 MessengerCommand::MarkRead { room_id, event_id } => {
2276 let request_id = self.next_request_id()?;
2277 let body = serde_json::json!({ "m.fully_read": event_id.as_str(), "m.read": event_id.as_str() });
2278 let request = OutgoingRequest::read_markers(request_id, &room_id, body);
2279 self.enqueue_request(request, Lane::Other)?;
2280 }
2281 MessengerCommand::SetTyping { room_id, typing } => {
2282 if typing {
2283 let should_send = match self.typing_debounce.get(&room_id) {
2284 Some(&last_sent_ms) => now_ms.saturating_sub(last_sent_ms) >= TYPING_DEBOUNCE_MS,
2285 None => true,
2286 };
2287 if !should_send {
2288 return Ok(());
2289 }
2290 self.typing_debounce.insert(room_id.clone(), now_ms);
2291 } else {
2292 self.typing_debounce.remove(&room_id);
2293 }
2294 let request_id = self.next_request_id()?;
2295 let timeout_ms = typing.then_some(TYPING_TIMEOUT_MS);
2296 let request = OutgoingRequest::typing(request_id, &room_id, &self.config.user_id, typing, timeout_ms);
2297 self.enqueue_request(request, Lane::Other)?;
2298 }
2299 MessengerCommand::RetryDecryption { room_id, event_id } => {
2300 if self.retry_decrypt_one(&room_id, &event_id) {
2301 self.emit(MessengerEvent::TimelineChanged { room_id });
2302 }
2303 }
2304 MessengerCommand::LoadOlder { room_id } => {
2305 let from = self
2306 .timelines
2307 .get(&room_id)
2308 .and_then(|timeline| {
2309 timeline
2310 .older_token()
2311 .map(str::to_string)
2312 .or_else(|| timeline.gap().and_then(|gap| gap.prev_batch.clone()))
2313 })
2314 .or_else(|| {
2320 if self.rooms.contains_key(&room_id) {
2321 self.store.sync_token().ok().flatten().map(str::to_string)
2322 } else {
2323 None
2324 }
2325 });
2326 let Some(from) = from else { return Ok(()) };
2327 let request_id = self.next_request_id()?;
2328 let request =
2329 OutgoingRequest::room_messages(request_id.clone(), &room_id, &from, "b", Some(LOAD_OLDER_PAGE_SIZE));
2330 self.enqueue_request(request, Lane::Room(room_id.clone()))?;
2331 self.pending_room_messages.insert(request_id, room_id);
2332 }
2333 MessengerCommand::SetAccountData { event_type, content } => {
2334 self.write_account_data(None, event_type, content)?;
2335 }
2336 MessengerCommand::SetRoomAccountData { room_id, event_type, content } => {
2337 self.write_account_data(Some(room_id), event_type, content)?;
2338 }
2339 MessengerCommand::SearchPublicRooms { term } => {
2340 let request_id = self.next_request_id()?;
2341 let body = serde_json::json!({ "filter": { "generic_search_term": term }, "limit": 50 });
2342 let request = OutgoingRequest::public_rooms(request_id.clone(), body);
2343 self.latest_public_rooms_request = Some(request_id);
2344 self.enqueue_request(request, Lane::Other)?;
2345 }
2346 MessengerCommand::SearchUsers { term } => {
2347 let request_id = self.next_request_id()?;
2348 let body = serde_json::json!({ "search_term": term, "limit": 20 });
2349 let request = OutgoingRequest::user_directory_search(request_id.clone(), body);
2350 self.latest_user_search_request = Some(request_id);
2351 self.enqueue_request(request, Lane::Other)?;
2352 }
2353 }
2354 Ok(())
2355 }
2356
2357 fn dispatch_create_room(&mut self, kind: CreateRoomKind) -> Result<(), MessengerError> {
2362 let body = match &kind {
2363 CreateRoomKind::Dm { peer } => serde_json::json!({
2364 "is_direct": true,
2365 "invite": [peer.as_str()],
2366 }),
2367 CreateRoomKind::Group { name, invite, members_can_invite } => serde_json::json!({
2368 "visibility": "private",
2369 "is_direct": false,
2370 "name": name,
2371 "invite": invite.iter().map(UserId::as_str).collect::<Vec<_>>(),
2372 "members_can_invite": members_can_invite,
2373 }),
2374 CreateRoomKind::Channel { name, topic } => {
2375 let mut value = serde_json::json!({
2376 "visibility": "public",
2377 "is_direct": false,
2378 "name": name,
2379 });
2380 if let Some(topic) = topic {
2381 value["topic"] = serde_json::Value::String(topic.clone());
2382 }
2383 value
2384 }
2385 };
2386 let request_id = self.next_request_id()?;
2387 let request = OutgoingRequest::create_room(request_id.clone(), body);
2388 self.pending_create_room.insert(request_id.clone(), kind);
2389 self.enqueue_request(request, Lane::Other)?;
2390 Ok(())
2391 }
2392
2393 fn current_tag_content(&self, room_id: &RoomId) -> TagContent {
2398 self.room_account_data(room_id, "m.tag")
2399 .and_then(|value| serde_json::from_value(value.clone()).ok())
2400 .unwrap_or_default()
2401 }
2402
2403 fn enqueue_tag_update(&mut self, room_id: RoomId, content: TagContent) -> Result<(), MessengerError> {
2409 let value = serde_json::to_value(&content)?;
2410 self.write_account_data(Some(room_id), "m.tag".to_string(), value)
2411 }
2412
2413 fn build_forward_payload(
2418 &self,
2419 from_room: &RoomId,
2420 event_id: &EventId,
2421 to_room: &RoomId,
2422 ) -> Result<SendPayload, MessengerError> {
2423 let refuse = |reason: &dyn std::fmt::Display| {
2424 MessengerError::IntentRefused(format!("cannot forward {event_id} from {from_room}: {reason}"))
2425 };
2426 if !self.rooms.contains_key(to_room) {
2427 return Err(refuse(&format!("the target room {to_room} is unknown")));
2428 }
2429 let source = match self.timelines.get(from_room) {
2430 Some(timeline) => timeline.forward_source(event_id),
2431 None => Err(ForwardRefusal::Missing),
2432 }
2433 .map_err(|reason| refuse(&reason))?;
2434 let (marker_key, marker) = self.forward_marker(from_room);
2435 Ok(SendPayload::Event {
2436 event_type: source.event_type,
2437 content: forwardable_content(source.content, marker_key, marker),
2438 })
2439 }
2440
2441 fn forward_marker(&self, from_room: &RoomId) -> (&'static str, serde_json::Value) {
2446 let public_channel = self.room_kind(from_room) == Some(RoomKind::Channel);
2447 if public_channel {
2448 let room_name = self.forward_room_name(from_room);
2449 (KEY_FORWARDED_FROM, serde_json::json!({ "room_id": from_room.as_str(), "room_name": room_name }))
2450 } else {
2451 (KEY_FORWARDED, serde_json::Value::Bool(true))
2452 }
2453 }
2454
2455 fn forward_room_name(&self, room_id: &RoomId) -> String {
2459 if let Some(name) = self.rooms.get(room_id).and_then(|room| room.name.clone()) {
2460 return name;
2461 }
2462 match self.room_kind(room_id) {
2463 Some(RoomKind::Dm) => "Direct",
2464 Some(RoomKind::Channel) => "Channel",
2465 Some(RoomKind::Group) | None => "Group",
2466 }
2467 .to_string()
2468 }
2469
2470 fn retry_decrypt_one(&mut self, room_id: &RoomId, event_id: &EventId) -> bool {
2476 let Some(item) = self.pending_decrypt.get(room_id).and_then(|by_event| by_event.get(event_id)).cloned() else {
2477 return false;
2478 };
2479 match GroupSessionManager::decrypt_event(
2480 &mut self.store,
2481 room_id,
2482 event_id,
2483 item.origin_server_ts,
2484 &item.sender,
2485 &item.content,
2486 ) {
2487 Ok(plaintext) => {
2488 self.apply_decrypted_plaintext(room_id, event_id, &item.sender, item.origin_server_ts, &plaintext);
2489 }
2490 Err(GroupDecryptError::MissingSession { .. }) => {
2491 let _ = self.request_missing_room_key(room_id, &item.sender, &item.content);
2492 return false;
2493 }
2494 Err(_) => return false,
2495 }
2496 if let Some(by_event) = self.pending_decrypt.get_mut(room_id) {
2497 by_event.remove(event_id);
2498 }
2499 true
2500 }
2501
2502 pub fn pull_latest_page(&mut self, room_id: RoomId) -> Result<(), MessengerError> {
2505 let request_id = self.next_request_id()?;
2506 let request = OutgoingRequest::room_messages_latest(request_id.clone(), &room_id, 20);
2507 self.enqueue_request(request, Lane::Room(room_id.clone()))?;
2508 self.pending_room_messages.insert(request_id, room_id);
2509 Ok(())
2510 }
2511
2512 pub fn decrypt_loaded_timeline(&mut self) {
2516 let pending: Vec<(RoomId, Vec<EventId>)> = self
2517 .pending_decrypt
2518 .iter()
2519 .map(|(room_id, by_event)| (room_id.clone(), by_event.keys().cloned().collect()))
2520 .collect();
2521 for (room_id, event_ids) in pending {
2522 for event_id in event_ids {
2523 self.retry_decrypt_one(&room_id, &event_id);
2524 }
2525 }
2526 let rooms: Vec<RoomId> = self.timelines.keys().cloned().collect();
2527 for room_id in rooms {
2528 let sealed: Vec<(EventId, i64, UserId, MegolmEncryptedContent)> = {
2529 let Some(timeline) = self.timelines.get(&room_id) else {
2530 continue;
2531 };
2532 timeline
2533 .items()
2534 .iter()
2535 .filter_map(|item| {
2536 let event_id = item.event_id.clone()?;
2537 let (session_id, ciphertext) = match &item.content {
2538 ItemContent::Encrypted { session_id, ciphertext_b64 } => {
2539 (session_id.clone(), ciphertext_b64.clone())
2540 }
2541 _ => return None,
2542 };
2543 Some((
2544 event_id,
2545 item.origin_server_ts,
2546 item.sender.clone(),
2547 MegolmEncryptedContent {
2548 ciphertext,
2549 session_id,
2550 sender_key: None,
2551 device_id: None,
2552 relates_to: None,
2553 },
2554 ))
2555 })
2556 .collect()
2557 };
2558 for (event_id, origin_server_ts, sender, content) in sealed {
2559 let plaintext = match GroupSessionManager::decrypt_event(
2560 &mut self.store,
2561 &room_id,
2562 &event_id,
2563 origin_server_ts,
2564 &sender,
2565 &content,
2566 ) {
2567 Ok(plaintext) => plaintext,
2568 Err(GroupDecryptError::MissingSession { .. }) => {
2569 let _ = self.request_missing_room_key(&room_id, &sender, &content);
2570 continue;
2571 }
2572 Err(_) => continue,
2573 };
2574 self.apply_decrypted_plaintext(
2575 &room_id,
2576 &event_id,
2577 &sender,
2578 origin_server_ts,
2579 &plaintext,
2580 );
2581 }
2582 }
2583 }
2584
2585 fn apply_decrypted_plaintext(
2598 &mut self,
2599 room_id: &RoomId,
2600 event_id: &EventId,
2601 sender: &UserId,
2602 origin_server_ts: i64,
2603 plaintext: &RoomEventPlaintext,
2604 ) {
2605 let Some(timeline) = self.timelines.get_mut(room_id) else { return };
2606 if let Some(relation) = RelatesTo::from_content(&plaintext.content) {
2607 if matches!(relation, RelatesTo::Annotation { .. } | RelatesTo::Replace { .. }) {
2608 let new_content = plaintext.content.get("m.new_content").cloned();
2609 timeline.apply_decrypted_relation(event_id, sender.clone(), origin_server_ts, relation, new_content);
2610 return;
2611 }
2612 }
2613 timeline.set_decrypted_event(event_id, &plaintext.event_type, &plaintext.content);
2614 }
2615
2616 fn retry_pending_for_session(&mut self, room_id: &RoomId, session_id: &str) {
2617 let Some(by_event) = self.pending_decrypt.get(room_id) else { return };
2618 let candidates: Vec<EventId> =
2619 by_event.iter().filter(|(_, item)| item.session_id == session_id).map(|(event_id, _)| event_id.clone()).collect();
2620 let mut changed = false;
2621 for event_id in candidates {
2622 if self.retry_decrypt_one(room_id, &event_id) {
2623 changed = true;
2624 }
2625 }
2626 if changed {
2627 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
2628 }
2629 }
2630
2631 pub fn republish_device_keys(&mut self) {
2634 self.account.forget_published_device_keys();
2635 }
2636
2637 fn ingest_sync(&mut self, response: &crate::wire::sync::SyncResponse) -> Result<(), MessengerError> {
2638 let mut newly_received_sessions: Vec<(RoomId, String)> = Vec::new();
2639
2640 for event in &response.to_device.events {
2642 self.process_to_device_event(event, &mut newly_received_sessions)?;
2643 }
2644
2645 DeviceTracker::on_device_lists(&mut self.store, &response.device_lists.changed, &response.device_lists.left)?;
2647
2648 let published_otk_count = response.device_one_time_keys_count.get("signed_curve25519").copied().unwrap_or(0);
2650 self.account.on_sync_counts(&mut self.store, published_otk_count, &response.device_unused_fallback_key_types)?;
2651 let keys_upload_request_id = self.next_request_id()?;
2652 if let Some(request) =
2653 self.account.keys_upload_request(keys_upload_request_id, &self.config.user_id, &self.config.device_id)?
2654 {
2655 self.enqueue_request(request, Lane::Other)?;
2656 }
2657 self.maybe_enqueue_cross_signing()?;
2658
2659 let outdated_before_rooms = self.outdated_tracked_users()?;
2661 if !outdated_before_rooms.is_empty() {
2662 self.enqueue_keys_query()?;
2663 }
2664
2665 let rooms_touched =
2667 !response.rooms.join.is_empty() || !response.rooms.invite.is_empty() || !response.rooms.leave.is_empty();
2668 for (room_id, joined) in &response.rooms.join {
2669 self.ingest_joined_room(room_id, joined)?;
2670 }
2671 for (room_id, invited) in &response.rooms.invite {
2672 self.ingest_invited_room(room_id, invited)?;
2673 }
2674 for (room_id, left) in &response.rooms.leave {
2675 self.ingest_left_room(room_id, left)?;
2676 }
2677 if rooms_touched {
2678 self.emit(MessengerEvent::RoomsChanged);
2679 }
2680 if !self.outdated_tracked_users()?.is_subset(&outdated_before_rooms) {
2684 self.enqueue_keys_query()?;
2685 }
2686
2687 for event in &response.account_data.events {
2689 self.apply_synced_account_data(None, &event.event_type, event.content.clone());
2690 }
2691
2692 for (room_id, session_id) in &newly_received_sessions {
2693 self.retry_pending_for_session(room_id, session_id);
2694 }
2695
2696 self.store.save_sync_token(response.next_batch.clone())?;
2699 Ok(())
2700 }
2701
2702 fn process_to_device_event(
2703 &mut self,
2704 event: &ToDeviceEvent,
2705 newly_received_sessions: &mut Vec<(RoomId, String)>,
2706 ) -> Result<(), MessengerError> {
2707 if event.event_type != "m.room.encrypted" {
2708 return Ok(());
2709 }
2710 let Ok(RoomEncryptedContent::Olm(content)) = serde_json::from_value::<RoomEncryptedContent>(event.content.clone())
2711 else {
2712 return Ok(());
2713 };
2714 match OlmSessionManager::decrypt_to_device(&mut self.store, &mut self.account, &self.config.user_id, &event.sender, &content) {
2715 Ok(decrypted) => self.route_decrypted_to_device(decrypted, newly_received_sessions),
2716 Err(OlmDecryptError::UnknownSenderDevice { .. }) => {
2717 self.queue_unknown_sender_retry(event.clone());
2718 Ok(())
2719 }
2720 Err(_) => Ok(()),
2727 }
2728 }
2729
2730 fn route_decrypted_to_device(
2731 &mut self,
2732 decrypted: DecryptedToDevice,
2733 newly_received_sessions: &mut Vec<(RoomId, String)>,
2734 ) -> Result<(), MessengerError> {
2735 match decrypted.event_type.as_str() {
2736 "m.room_key" => {
2737 if let Ok(content) = serde_json::from_value::<RoomKeyContent>(decrypted.content.clone()) {
2738 if content.algorithm == "m.megolm.v1.aes-sha2" {
2739 GroupSessionManager::receive_room_key(
2740 &mut self.store,
2741 &decrypted.sender,
2742 decrypted.sender_device_curve25519,
2743 decrypted.sender_ed25519,
2744 &content,
2745 )?;
2746 newly_received_sessions.push((content.room_id, content.session_id));
2747 }
2748 }
2749 }
2750 "m.forwarded_room_key" => {
2751 if let Ok(content) = serde_json::from_value::<ForwardedRoomKeyContent>(decrypted.content.clone()) {
2752 if let Some((user_id, curve, ed)) = self.forward_binding(&decrypted, &content)? {
2753 let stored = GroupSessionManager::receive_forwarded_room_key(
2754 &mut self.store,
2755 &user_id,
2756 curve,
2757 ed,
2758 &content,
2759 )?;
2760 if stored {
2761 newly_received_sessions.push((content.room_id, content.session_id));
2762 }
2763 }
2764 }
2765 }
2766 "m.room_key_request" => {
2767 if let Ok(content) = serde_json::from_value::<RoomKeyRequestContent>(decrypted.content.clone()) {
2768 self.answer_room_key_request(&decrypted, &content)?;
2769 }
2770 }
2771 "m.room_key.withheld" => {
2772 if let Ok(content) = serde_json::from_value::<RoomKeyWithheldContent>(decrypted.content.clone()) {
2773 if let (Some(room_id), Some(session_id)) = (content.room_id, content.session_id) {
2774 self.withheld_sessions.insert((room_id, session_id));
2775 }
2776 }
2777 }
2778 _ => {}
2779 }
2780 Ok(())
2781 }
2782
2783 fn forward_binding(
2789 &self,
2790 decrypted: &DecryptedToDevice,
2791 content: &ForwardedRoomKeyContent,
2792 ) -> Result<Option<(UserId, mail4agent_vodozemac::Curve25519PublicKey, mail4agent_vodozemac::Ed25519PublicKey)>, MessengerError>
2793 {
2794 let members = self.encrypted_room_member_ids(&content.room_id);
2795 let forwarder_is_us = decrypted.sender == self.config.user_id;
2796 if !forwarder_is_us && !members.contains(&decrypted.sender) {
2797 return Ok(None);
2798 }
2799 for user_id in &members {
2800 for device in DeviceTracker::devices_for_user(&self.store, user_id)? {
2801 if device.blocked || device.curve25519.to_base64() != content.sender_key {
2802 continue;
2803 }
2804 let same_user = decrypted.sender == device.user_id;
2805 if same_user || forwarder_is_us {
2806 return Ok(Some((device.user_id, device.curve25519, device.ed25519)));
2807 }
2808 }
2809 }
2810 if decrypted.sender_device_curve25519.to_base64() == content.sender_key {
2811 return Ok(Some((
2812 decrypted.sender.clone(),
2813 decrypted.sender_device_curve25519,
2814 decrypted.sender_ed25519,
2815 )));
2816 }
2817 Ok(None)
2818 }
2819
2820 fn answer_room_key_request(
2825 &mut self,
2826 decrypted: &DecryptedToDevice,
2827 content: &RoomKeyRequestContent,
2828 ) -> Result<(), MessengerError> {
2829 if matches!(content.action, RoomKeyRequestAction::RequestCancellation) {
2830 self.key_requests_answered.remove(&(
2831 decrypted.sender.clone(),
2832 content.requesting_device_id.clone(),
2833 content.request_id.clone(),
2834 ));
2835 return Ok(());
2836 }
2837 if !matches!(content.action, RoomKeyRequestAction::Request) {
2838 return Ok(());
2839 }
2840 let Some(body) = content.body.as_ref() else { return Ok(()) };
2841 if body.algorithm != "m.megolm.v1.aes-sha2" {
2842 return Ok(());
2843 }
2844 let members = self.encrypted_room_member_ids(&body.room_id);
2845 if !members.contains(&decrypted.sender) {
2846 return Ok(());
2847 }
2848 if decrypted.sender != self.config.user_id && !self.history_forwarding_allowed(&body.room_id) {
2849 return Ok(());
2850 }
2851 if decrypted.sender == self.config.user_id && content.requesting_device_id == self.config.device_id {
2852 return Ok(());
2853 }
2854 let dedup = (decrypted.sender.clone(), content.requesting_device_id.clone(), content.request_id.clone());
2855 if self.key_requests_answered.contains(&dedup) {
2856 return Ok(());
2857 }
2858 let Some(target) = self.requester_device(decrypted, &content.requesting_device_id)? else {
2859 return Ok(());
2860 };
2861 let our_curve = self.account.identity_keys().curve25519.to_base64();
2862 let (inner_type, payload) =
2863 if let Some(forwarded) = GroupSessionManager::forwarded_room_key_content(
2864 &self.store,
2865 &body.room_id,
2866 &body.session_id,
2867 &our_curve,
2868 )? {
2869 ("m.forwarded_room_key", serde_json::to_value(&forwarded)?)
2870 } else if let Some(room_key) =
2871 GroupSessionManager::outbound_room_key_content(&self.store, &body.room_id, &body.session_id)?
2872 {
2873 ("m.room_key", serde_json::to_value(&room_key)?)
2874 } else {
2875 return Ok(());
2876 };
2877 let sent = self.enqueue_olm_to_devices(std::slice::from_ref(&target), inner_type, payload)?;
2878 if sent > 0 {
2879 self.key_requests_answered.insert(dedup);
2880 }
2881 Ok(())
2882 }
2883
2884 fn requester_device(
2888 &self,
2889 decrypted: &DecryptedToDevice,
2890 requesting_device_id: &DeviceId,
2891 ) -> Result<Option<StoredDevice>, MessengerError> {
2892 let devices = DeviceTracker::devices_for_user(&self.store, &decrypted.sender)?;
2893 if let Some(device) = devices.iter().find(|device| &device.device_id == requesting_device_id) {
2894 return Ok((!device.blocked).then(|| device.clone()));
2895 }
2896 let curve = decrypted.sender_device_curve25519.to_base64();
2897 if let Some(device) = devices.iter().find(|device| device.curve25519.to_base64() == curve) {
2898 return Ok((!device.blocked).then(|| device.clone()));
2899 }
2900 Ok(None)
2901 }
2902
2903 fn request_missing_room_key(
2908 &mut self,
2909 room_id: &RoomId,
2910 sender: &UserId,
2911 content: &MegolmEncryptedContent,
2912 ) -> Result<(), MessengerError> {
2913 let key = (room_id.clone(), content.session_id.clone());
2914 if self.key_requests_sent.contains(&key) || self.withheld_sessions.contains(&key) {
2915 return Ok(());
2916 }
2917 let sender_key = match content.sender_key.clone() {
2918 Some(sender_key) => sender_key,
2919 None => {
2920 let devices = DeviceTracker::devices_for_user(&self.store, sender)?;
2921 let found = content
2922 .device_id
2923 .as_ref()
2924 .and_then(|device_id| devices.iter().find(|device| &device.device_id == device_id))
2925 .or_else(|| devices.iter().find(|device| !device.blocked));
2926 match found {
2927 Some(device) => device.curve25519.to_base64(),
2928 None => return Ok(()),
2929 }
2930 }
2931 };
2932 let mut targets = DeviceTracker::devices_for_user(&self.store, sender)?;
2933 targets.extend(DeviceTracker::devices_for_user(&self.store, &self.config.user_id)?);
2934 if targets.is_empty() {
2935 return Ok(());
2936 }
2937 let missing: Vec<StoredDevice> = OlmSessionManager::sessions_missing_for(&self.store, &targets)?
2941 .into_iter()
2942 .filter(|device| {
2943 !device.blocked
2944 && !(device.user_id == self.config.user_id && device.device_id == self.config.device_id)
2945 && !self.history_claim_tried.contains(&(device.user_id.clone(), device.device_id.clone()))
2946 })
2947 .cloned()
2948 .collect();
2949 if !missing.is_empty() {
2950 let claim_id = self.next_request_id()?;
2951 let refs: Vec<&StoredDevice> = missing.iter().collect();
2952 if let Some(request) = OlmSessionManager::keys_claim_request(claim_id.clone(), &refs) {
2953 for device in &missing {
2954 self.history_claim_tried.insert((device.user_id.clone(), device.device_id.clone()));
2955 }
2956 self.history_claims.insert(claim_id, missing);
2957 self.enqueue_request(request, Lane::Other)?;
2958 return Ok(());
2959 }
2960 }
2961 let request_id = self.next_txn_id()?;
2962 let body = RoomKeyRequestContent {
2963 action: RoomKeyRequestAction::Request,
2964 body: Some(RoomKeyRequestBody {
2965 algorithm: "m.megolm.v1.aes-sha2".to_string(),
2966 room_id: room_id.clone(),
2967 session_id: content.session_id.clone(),
2968 sender_key,
2969 }),
2970 request_id: request_id.as_str().to_string(),
2971 requesting_device_id: self.config.device_id.clone(),
2972 };
2973 let sent = self.enqueue_olm_to_devices(&targets, "m.room_key_request", serde_json::to_value(&body)?)?;
2974 if sent > 0 {
2975 self.key_requests_sent.insert(key);
2976 }
2977 Ok(())
2978 }
2979
2980 fn enqueue_olm_to_devices(
2985 &mut self,
2986 devices: &[StoredDevice],
2987 inner_type: &str,
2988 content: serde_json::Value,
2989 ) -> Result<usize, MessengerError> {
2990 let mut unique = Vec::new();
2991 for device in devices {
2992 if device.blocked || (device.user_id == self.config.user_id && device.device_id == self.config.device_id) {
2993 continue;
2994 }
2995 if unique.iter().any(|have: &StoredDevice| have.user_id == device.user_id && have.device_id == device.device_id)
2996 {
2997 continue;
2998 }
2999 unique.push(device.clone());
3000 }
3001 let missing = OlmSessionManager::sessions_missing_for(&self.store, &unique)?;
3002 let missing_ids: BTreeSet<(UserId, DeviceId)> =
3003 missing.into_iter().map(|device| (device.user_id.clone(), device.device_id.clone())).collect();
3004 let ready: Vec<StoredDevice> = unique
3005 .into_iter()
3006 .filter(|device| !missing_ids.contains(&(device.user_id.clone(), device.device_id.clone())))
3007 .collect();
3008 let mut sent = 0;
3009 for chunk in GroupSessionManager::chunk_recipients_for_send_to_device(&ready) {
3010 let body = GroupSessionManager::build_encrypted_to_device_body(
3011 &mut self.store,
3012 &self.account,
3013 &self.config.user_id,
3014 &self.config.device_id,
3015 chunk,
3016 inner_type,
3017 content.clone(),
3018 )?;
3019 let request_id = self.next_request_id()?;
3020 let share_txn = self.next_txn_id()?;
3021 let request = OutgoingRequest::send_to_device(request_id, ROOM_KEY_SHARE_EVENT_TYPE, &share_txn, body);
3022 self.enqueue_request(request, Lane::ToDevice)?;
3023 sent += chunk.len();
3024 }
3025 Ok(sent)
3026 }
3027
3028 fn queue_unknown_sender_retry(&mut self, event: ToDeviceEvent) {
3029 self.unknown_sender_retry.push_back(event);
3030 while self.unknown_sender_retry.len() > UNKNOWN_SENDER_RETRY_CAPACITY {
3031 self.unknown_sender_retry.pop_front();
3032 }
3033 }
3034
3035 fn retry_unknown_sender_queue(&mut self) -> Result<(), MessengerError> {
3036 let pending: Vec<ToDeviceEvent> = std::mem::take(&mut self.unknown_sender_retry).into_iter().collect();
3037 let mut newly_received_sessions = Vec::new();
3038 for event in pending {
3039 self.process_to_device_event(&event, &mut newly_received_sessions)?;
3040 }
3041 for (room_id, session_id) in &newly_received_sessions {
3042 self.retry_pending_for_session(room_id, session_id);
3043 }
3044 Ok(())
3045 }
3046
3047 fn outdated_tracked_users(&self) -> Result<BTreeSet<UserId>, MessengerError> {
3049 Ok(self.store.tracked_users()?.into_iter().filter_map(|(user_id, outdated)| outdated.then_some(user_id)).collect())
3050 }
3051
3052 fn enqueue_keys_query(&mut self) -> Result<(), MessengerError> {
3054 let request_id = self.next_request_id()?;
3055 if let Some(request) = DeviceTracker::keys_query_request(&self.store, request_id)? {
3056 self.enqueue_request(request, Lane::Other)?;
3057 }
3058 Ok(())
3059 }
3060
3061 fn retry_missing_room_keys(&mut self) {
3071 let work: Vec<(RoomId, EventId)> = self
3072 .pending_decrypt
3073 .iter()
3074 .flat_map(|(room, by_event)| by_event.keys().map(move |event| (room.clone(), event.clone())))
3075 .collect();
3076 for (room_id, event_id) in work {
3077 let _ = self.retry_decrypt_one(&room_id, &event_id);
3078 }
3079 }
3080
3081 fn history_forwarding_allowed(&self, room_id: &RoomId) -> bool {
3085 !matches!(
3086 self.rooms.get(room_id).and_then(|room| room.history_visibility.clone()),
3087 Some(crate::wire::events::HistoryVisibility::Joined) | Some(crate::wire::events::HistoryVisibility::Invited)
3088 )
3089 }
3090
3091 fn maybe_enqueue_cross_signing(&mut self) -> Result<(), MessengerError> {
3094 if self.xsign_inflight || self.xsign_attempts >= 4 {
3095 return Ok(());
3096 }
3097 if self.xsign.is_none() {
3098 self.xsign = Some(CrossSigningState::load_or_create(&mut self.store)?);
3099 }
3100 let Some(state) = self.xsign.clone() else { return Ok(()) };
3101 let user_id = self.config.user_id.clone();
3102 let request_id;
3103 let request = if !state.uploaded {
3104 request_id = self.next_request_id()?;
3105 OutgoingRequest::signing_keys_upload(request_id, state.upload_body(&user_id)?)
3106 } else if !state.device_signed {
3107 let device_id = self.config.device_id.clone();
3108 let device_keys = self.account.device_keys_json(&user_id, &device_id)?;
3109 request_id = self.next_request_id()?;
3110 OutgoingRequest::signatures_upload(request_id, state.device_signature_body(&user_id, &device_id, device_keys)?)
3111 } else {
3112 return Ok(());
3113 };
3114 self.xsign_attempts += 1;
3115 self.xsign_inflight = true;
3116 self.enqueue_request(request, Lane::Other)
3117 }
3118
3119 fn track_joined_members<'a>(&mut self, room_id: &RoomId, events: impl Iterator<Item = &'a RawEvent>) -> Result<(), MessengerError> {
3120 if self.rooms.get(room_id).is_none_or(|room| room.encryption.is_none()) {
3121 return Ok(());
3122 }
3123 let joined: Vec<UserId> = events
3124 .filter(|event| event.event_type == "m.room.member")
3125 .filter(|event| event.content.get("membership").and_then(serde_json::Value::as_str) == Some("join"))
3126 .filter_map(|event| event.state_key.as_deref().and_then(|state_key| UserId::parse(state_key).ok()))
3127 .filter(|user_id| *user_id != self.config.user_id)
3128 .collect();
3129 if joined.is_empty() {
3130 return Ok(());
3131 }
3132 DeviceTracker::on_device_lists(&mut self.store, &joined, &[])
3133 }
3134
3135 fn ingest_joined_room(&mut self, room_id: &RoomId, joined: &JoinedRoom) -> Result<(), MessengerError> {
3136 for event in joined.state.events.iter().chain(joined.timeline.events.iter()) {
3137 self.rooms.entry(room_id.clone()).or_default().apply_state_event(event)?;
3138 }
3139
3140 self.track_joined_members(room_id, joined.state.events.iter().chain(joined.timeline.events.iter()))?;
3141
3142 let unread_before = self.rooms.get(room_id).map(RoomState::unread_count);
3143 self.rooms.entry(room_id.clone()).or_default().apply_summary(&joined.summary, &joined.unread_notifications);
3144 let unread_after = self.rooms.get(room_id).map(RoomState::unread_count);
3145 if unread_before != unread_after {
3146 self.emit(MessengerEvent::UnreadChanged { room_id: room_id.clone() });
3147 }
3148
3149 for event in &joined.account_data.events {
3150 self.apply_synced_account_data(Some(room_id), &event.event_type, event.content.clone());
3151 }
3152
3153 if let Some(typing_event) = joined.ephemeral.events.iter().find(|event| event.event_type == "m.typing") {
3154 if let Ok(typing) = serde_json::from_value::<TypingContent>(typing_event.content.clone()) {
3155 self.typing.insert(room_id.clone(), typing.user_ids);
3156 self.emit(MessengerEvent::TypingChanged { room_id: room_id.clone() });
3157 }
3158 }
3159 if let Some(receipt_event) = joined.ephemeral.events.iter().find(|event| event.event_type == "m.receipt") {
3160 if let Ok(receipts) = serde_json::from_value::<ReceiptContent>(receipt_event.content.clone()) {
3161 self.receipts.insert(room_id.clone(), receipts);
3162 self.emit(MessengerEvent::ReceiptsChanged { room_id: room_id.clone() });
3163 }
3164 }
3165
3166 if !joined.timeline.events.is_empty() {
3167 self.timelines.entry(room_id.clone()).or_default().apply_timeline_batch(
3168 &joined.timeline.events,
3169 joined.timeline.limited,
3170 joined.timeline.prev_batch.clone(),
3171 );
3172 self.decrypt_new_timeline_events(room_id, &joined.timeline.events);
3173 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
3174 }
3175
3176 self.persist_room_state(room_id)
3177 }
3178
3179 fn ingest_invited_room(&mut self, room_id: &RoomId, invited: &InvitedRoom) -> Result<(), MessengerError> {
3180 for stripped in &invited.invite_state.events {
3181 let synthetic = stripped_state_to_raw_event(stripped);
3182 self.rooms.entry(room_id.clone()).or_default().apply_state_event(&synthetic)?;
3183 }
3184 self.persist_room_state(room_id)
3185 }
3186
3187 fn ingest_left_room(&mut self, room_id: &RoomId, left: &LeftRoom) -> Result<(), MessengerError> {
3188 for event in left.state.events.iter().chain(left.timeline.events.iter()) {
3189 self.rooms.entry(room_id.clone()).or_default().apply_state_event(event)?;
3190 }
3191 for event in &left.account_data.events {
3192 self.apply_synced_account_data(Some(room_id), &event.event_type, event.content.clone());
3193 }
3194 if !left.timeline.events.is_empty() {
3195 self.timelines.entry(room_id.clone()).or_default().apply_timeline_batch(
3196 &left.timeline.events,
3197 left.timeline.limited,
3198 left.timeline.prev_batch.clone(),
3199 );
3200 self.decrypt_new_timeline_events(room_id, &left.timeline.events);
3201 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
3202 }
3203 self.persist_room_state(room_id)
3204 }
3205
3206 fn persist_room_state(&mut self, room_id: &RoomId) -> Result<(), MessengerError> {
3207 let Some(state) = self.rooms.get(room_id) else { return Ok(()) };
3208 let bytes = serde_json::to_vec(state)
3209 .map_err(|source| MessengerError::Crypto(format!("encode room state for {room_id}: {source}")))?;
3210 self.store.save_room_state(room_id, bytes)?;
3211 Ok(())
3212 }
3213
3214 fn decrypt_new_timeline_events(&mut self, room_id: &RoomId, events: &[RawEvent]) {
3222 for event in events {
3223 if event.event_type != "m.room.encrypted" || event.state_key.is_some() || event.unsigned.redacted_because.is_some() {
3224 continue;
3225 }
3226 let Ok(RoomEncryptedContent::Megolm(content)) = serde_json::from_value::<RoomEncryptedContent>(event.content.clone())
3227 else {
3228 continue;
3229 };
3230 match GroupSessionManager::decrypt_event(
3231 &mut self.store,
3232 room_id,
3233 &event.event_id,
3234 event.origin_server_ts,
3235 &event.sender,
3236 &content,
3237 ) {
3238 Ok(plaintext) => {
3239 self.apply_decrypted_plaintext(room_id, &event.event_id, &event.sender, event.origin_server_ts, &plaintext);
3240 if let Some(by_event) = self.pending_decrypt.get_mut(room_id) {
3241 by_event.remove(&event.event_id);
3242 }
3243 }
3244 Err(err) => {
3245 if matches!(err, GroupDecryptError::MissingSession { .. }) {
3246 let _ = self.request_missing_room_key(room_id, &event.sender, &content);
3247 }
3248 let reason = format!("{:?}", err.utd_reason());
3257 if let Some(timeline) = self.timelines.get_mut(room_id) {
3258 timeline.set_decrypted(&event.event_id, ItemContent::Undecryptable { reason });
3259 }
3260 self.pending_decrypt.entry(room_id.clone()).or_default().insert(
3261 event.event_id.clone(),
3262 PendingDecryptItem {
3263 session_id: content.session_id.clone(),
3264 sender: event.sender.clone(),
3265 origin_server_ts: event.origin_server_ts,
3266 content,
3267 },
3268 );
3269 }
3270 }
3271 }
3272 }
3273
3274 fn ingest_room_messages_response(&mut self, room_id: &RoomId, body: &[u8]) -> Result<(), MessengerError> {
3275 let parsed: RoomMessagesResponseBody = serde_json::from_slice(body)?;
3276 let mut events = parsed.chunk;
3277 events.reverse();
3278 self.timelines.entry(room_id.clone()).or_default().prepend_back_page(&events, parsed.end);
3279 self.decrypt_new_timeline_events(room_id, &events);
3280 self.emit(MessengerEvent::TimelineChanged { room_id: room_id.clone() });
3281 self.persist_room_state(room_id)
3282 }
3283}
3284
3285impl MessengerCore<SealedRecordCodec> {
3286 pub fn open_sealed(
3297 records: impl IntoIterator<Item = SealedRecord>,
3298 secrets: CoreSecrets,
3299 config: CoreConfig,
3300 now_ms: i64,
3301 jitter: Box<dyn Jitter>,
3302 ) -> Result<Self, MessengerError> {
3303 let seal_key = secrets.store_seal_key.clone().ok_or_else(|| {
3304 MessengerError::Crypto(
3305 "MessengerCore::open_sealed requires CoreSecrets::store_seal_key"
3306 .to_string(),
3307 )
3308 })?;
3309 let codec = SealedRecordCodec::new(seal_key);
3310 Self::open(records, codec, config, secrets, now_ms, jitter)
3311 }
3312}
3313
3314fn stripped_placeholder_event_id() -> EventId {
3320 EventId::parse("$stripped-preview").expect("this literal is a well-formed, hardcoded $-prefixed opaque id")
3321}
3322
3323fn stripped_state_to_raw_event(stripped: &StrippedStateEvent) -> RawEvent {
3328 RawEvent {
3329 event_id: stripped_placeholder_event_id(),
3330 event_type: stripped.event_type.clone(),
3331 sender: stripped.sender.clone(),
3332 origin_server_ts: 0,
3333 state_key: Some(stripped.state_key.clone()),
3334 content: stripped.content.clone(),
3335 unsigned: Unsigned::default(),
3336 }
3337}
3338
3339fn forwardable_content(mut content: serde_json::Value, marker_key: &str, marker: serde_json::Value) -> serde_json::Value {
3346 if let Some(object) = content.as_object_mut() {
3347 for key in ["m.relates_to", "m.mentions", "m.new_content", KEY_FORWARDED, KEY_FORWARDED_FROM] {
3348 object.remove(key);
3349 }
3350 object.insert(marker_key.to_string(), marker);
3351 }
3352 content
3353}
3354
3355fn message_local_echo_content(message: &OutgoingMessage) -> ItemContent {
3362 let body = TextLikeMessageContent { body: message.body.clone(), format: None, formatted_body: None };
3363 match message.kind {
3364 MessageKind::Text => ItemContent::Text(body),
3365 MessageKind::Notice => ItemContent::Notice(body),
3366 MessageKind::Emote => ItemContent::Emote(body),
3367 }
3368}
3369
3370fn message_wire_content(message: &OutgoingMessage) -> serde_json::Value {
3379 let msgtype = message.kind.msgtype();
3380 let new_content = serde_json::json!({ "msgtype": msgtype, "body": message.body });
3381 let mut content = new_content.clone();
3382 if let Some(edit_of) = &message.edit_of {
3383 content["body"] = serde_json::Value::String(format!("* {}", message.body));
3384 content["m.new_content"] = new_content;
3385 content["m.relates_to"] = serde_json::json!({ "rel_type": "m.replace", "event_id": edit_of.as_str() });
3386 } else if let Some(reply_to) = &message.reply_to {
3387 content["m.relates_to"] = serde_json::json!({ "m.in_reply_to": { "event_id": reply_to.as_str() } });
3388 }
3389 content
3390}
3391
3392fn message_relates_to(message: &OutgoingMessage) -> Option<RelatesTo> {
3398 if let Some(edit_of) = &message.edit_of {
3399 Some(RelatesTo::Replace { event_id: edit_of.clone() })
3400 } else {
3401 message.reply_to.clone().map(|event_id| RelatesTo::InReplyTo(InReplyTo { event_id }))
3402 }
3403}
3404
3405#[cfg(test)]
3406mod tests {
3407 use super::*;
3408 use crate::room::timeline::Forwarded;
3409 use crate::store::InsecurePlainCodecForTests;
3410 use crate::wire::HttpMethod;
3411
3412 struct FixedJitter(f64);
3413
3414 impl Jitter for FixedJitter {
3415 fn next_unit(&mut self) -> f64 {
3416 self.0
3417 }
3418 }
3419
3420 fn device_id() -> DeviceId {
3421 DeviceId::parse("DEV1").expect("valid device id")
3422 }
3423
3424 fn user_id() -> UserId {
3425 UserId::parse("@alice:example.org").expect("valid user id")
3426 }
3427
3428 fn test_config() -> CoreConfig {
3429 CoreConfig { user_id: user_id(), device_id: device_id(), server_name: "example.org".to_string() }
3430 }
3431
3432 fn open_fresh_core() -> MessengerCore<InsecurePlainCodecForTests> {
3433 MessengerCore::open(Vec::new(), InsecurePlainCodecForTests, test_config(), CoreSecrets::default(), 0, Box::new(FixedJitter(0.0)))
3434 .expect("open succeeds on a brand-new device")
3435 }
3436
3437 fn flush_and_ack(core: &mut MessengerCore<InsecurePlainCodecForTests>) {
3445 while let Some(batch) = core.take_flush_batch() {
3446 core.ack_flush(batch.id);
3447 }
3448 }
3449
3450 #[test]
3451 fn counters_survive_restart_and_never_repeat_txn_ids() {
3452 let device = device_id();
3453 let mut store = Store::new(device.clone(), InsecurePlainCodecForTests);
3454 store.save_counters(Counters { next_request_id: 3, next_txn_id: 7 }).expect("save");
3455 let batch = store.take_flush_batch().expect("counters dirtied the store");
3456 store.ack_flush(batch.id);
3457
3458 let reloaded = Store::load(batch.records, InsecurePlainCodecForTests, device).expect("reload");
3459 let restored = reloaded.counters().expect("no error").expect("counters were persisted");
3460 assert_eq!(restored.next_txn_id, 7, "a restart resumes the txn-id sequence exactly where it left off");
3461 assert_eq!(restored.next_request_id, 3);
3462
3463 assert_eq!(restore_counter(3, Some(10)), 11, "resumes past the highest pending id, never repeating it");
3468 assert_eq!(restore_counter(3, None), 3, "nothing pending: the persisted value alone is authoritative");
3469 }
3470
3471 #[test]
3472 fn open_restores_the_request_counter_past_a_still_pending_requests_id() {
3473 let device = device_id();
3474 let mut store = Store::new(device.clone(), InsecurePlainCodecForTests);
3475 let mut queue = OutgoingQueue::new();
3476 let pending = OutgoingRequest::sync(RequestId::next(5), None, None);
3477 queue.enqueue(&mut store, pending, Lane::Sync).expect("enqueue");
3478 store.save_counters(Counters { next_request_id: 2, next_txn_id: 0 }).expect("save");
3481 let batch = store.take_flush_batch().expect("pending");
3482 store.ack_flush(batch.id);
3483
3484 let core = MessengerCore::open(
3485 batch.records,
3486 InsecurePlainCodecForTests,
3487 test_config(),
3488 CoreSecrets::default(),
3489 0,
3490 Box::new(FixedJitter(0.0)),
3491 )
3492 .expect("open succeeds");
3493 assert_eq!(
3494 core.counters.next_request_id, 6,
3495 "resumes past the highest id already in flight, not the stale persisted value"
3496 );
3497 }
3498
3499 #[test]
3500 fn sync_token_persisted_after_everything_it_covers() {
3501 let mut core = open_fresh_core();
3502 flush_and_ack(&mut core);
3503 core.releasable_requests(0);
3508 flush_and_ack(&mut core);
3509 let requests = core.releasable_requests(0);
3510 let sync_request = requests.iter().find(|r| r.kind == OutgoingRequestKind::Sync).expect("a sync request was enqueued");
3511
3512 assert!(core.store.sync_token().expect("no error").is_none(), "no token before the first response");
3513
3514 let body = serde_json::json!({
3515 "next_batch": "s1",
3516 "device_one_time_keys_count": { "signed_curve25519": 0 },
3517 "device_unused_fallback_key_types": []
3518 })
3519 .to_string();
3520 core.on_response(sync_request.id.clone(), HttpResponseDescriptor { status: 200, body: body.into_bytes() }, 0);
3521
3522 assert_eq!(core.store.sync_token().expect("no error"), Some("s1"));
3523 }
3524
3525 #[test]
3526 fn only_one_sync_in_flight() {
3527 let mut core = open_fresh_core();
3528 flush_and_ack(&mut core);
3529 core.releasable_requests(0);
3530 flush_and_ack(&mut core);
3531 let first = core.releasable_requests(0);
3532 assert_eq!(first.iter().filter(|r| r.kind == OutgoingRequestKind::Sync).count(), 1);
3533
3534 let second = core.releasable_requests(0);
3538 assert!(second.iter().all(|r| r.kind != OutgoingRequestKind::Sync), "no second sync request released");
3539 }
3540
3541 #[test]
3542 fn first_sync_of_a_session_never_long_polls_then_the_loop_does() {
3543 let mut core = open_fresh_core();
3544 flush_and_ack(&mut core);
3545 core.releasable_requests(0);
3546 flush_and_ack(&mut core);
3547 let first = core.releasable_requests(0);
3548 let first_sync = first.iter().find(|r| r.kind == OutgoingRequestKind::Sync).expect("a sync request was enqueued");
3549 assert!(first_sync.query.contains(&("timeout".to_string(), "0".to_string())), "the initial sync must answer at once");
3550 assert!(first_sync.query.iter().all(|(key, _)| key != "since"), "a fresh device has no since token");
3551
3552 let body = serde_json::json!({
3553 "next_batch": "s1",
3554 "device_one_time_keys_count": { "signed_curve25519": 0 },
3555 "device_unused_fallback_key_types": []
3556 })
3557 .to_string();
3558 core.on_response(first_sync.id.clone(), HttpResponseDescriptor { status: 200, body: body.into_bytes() }, 0);
3559 flush_and_ack(&mut core);
3560
3561 core.releasable_requests(0);
3564 flush_and_ack(&mut core);
3565 let second = core.releasable_requests(0);
3566 let second_sync = second.iter().find(|r| r.kind == OutgoingRequestKind::Sync).expect("the follow-up sync was enqueued");
3567 assert!(second_sync.query.contains(&("since".to_string(), "s1".to_string())));
3568 assert!(second_sync.query.contains(&("timeout".to_string(), SYNC_TIMEOUT_MS.to_string())), "once caught up the loop long-polls");
3569 }
3570
3571 #[test]
3572 fn room_key_arrival_retries_undecryptable_items() {
3573 use crate::crypto::group_sessions::GroupSessionManager;
3574 use mail4agent_vodozemac::megolm::{GroupSession, SessionConfig};
3575 use mail4agent_vodozemac::olm::Account;
3576
3577 let mut core = open_fresh_core();
3578 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3579 let sender = UserId::parse("@bob:example.org").expect("valid user id");
3580 let identity = Account::new().identity_keys();
3581
3582 let mut group_session = GroupSession::new(SessionConfig::version_1());
3583 let session_id = group_session.session_id();
3584 let session_key = group_session.session_key();
3589 let plaintext = crate::crypto::group_sessions::RoomEventPlaintext {
3590 event_type: "m.room.message".to_string(),
3591 content: serde_json::json!({ "msgtype": "m.text", "body": "hi" }),
3592 room_id: room_id.clone(),
3593 };
3594 let message = group_session.encrypt(serde_json::to_vec(&plaintext).expect("valid JSON"));
3595
3596 let event: RawEvent = serde_json::from_value(serde_json::json!({
3597 "event_id": "$evt:example.org",
3598 "type": "m.room.encrypted",
3599 "sender": sender.as_str(),
3600 "origin_server_ts": 1,
3601 "content": {
3602 "algorithm": "m.megolm.v1.aes-sha2",
3603 "ciphertext": message.to_base64(),
3604 "session_id": session_id,
3605 }
3606 }))
3607 .expect("valid event");
3608
3609 let mut timeline = Timeline::new();
3615 timeline.apply_timeline_batch(std::slice::from_ref(&event), false, None);
3616 core.timelines.insert(room_id.clone(), timeline);
3617 core.decrypt_new_timeline_events(&room_id, std::slice::from_ref(&event));
3618 {
3619 let timeline = core.timeline(&room_id).expect("timeline exists");
3620 let item = timeline.item_by_event_id(&EventId::parse("$evt:example.org").expect("valid event id")).expect("item present");
3621 assert!(matches!(item.content, ItemContent::Undecryptable { .. }));
3622 }
3623
3624 let content = RoomKeyContent {
3628 algorithm: "m.megolm.v1.aes-sha2".to_string(),
3629 room_id: room_id.clone(),
3630 session_id: session_id.clone(),
3631 session_key: session_key.to_base64(),
3632 };
3633 GroupSessionManager::receive_room_key(&mut core.store, &sender, identity.curve25519, identity.ed25519, &content)
3634 .expect("receive room key");
3635 core.retry_pending_for_session(&room_id, &session_id);
3636
3637 let timeline = core.timeline(&room_id).expect("timeline exists");
3638 let item = timeline.item_by_event_id(&EventId::parse("$evt:example.org").expect("valid event id")).expect("item present");
3639 match &item.content {
3640 ItemContent::Text(text) => assert_eq!(text.body, "hi"),
3641 other => panic!("expected the retried decrypt to succeed, got {other:?}"),
3642 }
3643 }
3644
3645 #[test]
3646 fn redelivered_to_device_after_restart_is_dropped_silently() {
3647 use crate::crypto::olm_sessions::OlmSessionManager;
3648
3649 let alice_user = UserId::parse("@alice:example.org").expect("valid user id");
3651 let alice_device = DeviceId::parse("ALICEDEV").expect("valid device id");
3652 let mut alice_store = Store::new(alice_device.clone(), InsecurePlainCodecForTests);
3653 let alice_account = OlmAccountState::load_or_create(&mut alice_store).expect("create alice's account");
3654 let alice_device_keys =
3657 alice_account.device_keys_json(&alice_user, &alice_device).expect("sign alice's device_keys");
3658
3659 let mut bob_store = Store::new(device_id(), InsecurePlainCodecForTests);
3661 let mut bob_account = OlmAccountState::load_or_create(&mut bob_store).expect("create bob's account");
3662 bob_account.on_sync_counts(&mut bob_store, 0, &["signed_curve25519".to_string()]).expect("top up bob's OTKs");
3663 let upload = bob_account
3664 .keys_upload_request(RequestId::next(0), &user_id(), &device_id())
3665 .expect("builds a request")
3666 .expect("something to upload");
3667 let one_time_keys = upload.body.expect("upload has a body");
3668 let one_time_keys = one_time_keys.get("one_time_keys").and_then(serde_json::Value::as_object).expect("OTKs present");
3669 let (key_id, key_value) = one_time_keys.iter().next().expect("at least one OTK");
3670 let claim_body = serde_json::to_vec(&serde_json::json!({
3671 "one_time_keys": { user_id().as_str(): { device_id().as_str(): { key_id: key_value } } }
3672 }))
3673 .expect("valid JSON");
3674 let bob_device_stored = crate::crypto::device_tracker::StoredDevice {
3675 user_id: user_id(),
3676 device_id: device_id(),
3677 curve25519: bob_account.identity_keys().curve25519,
3678 ed25519: bob_account.identity_keys().ed25519,
3679 algorithms: vec!["m.olm.v1.curve25519-aes-sha2".to_string(), "m.megolm.v1.aes-sha2".to_string()],
3680 display_name: None,
3681 verified: false,
3682 blocked: false,
3683 };
3684 OlmSessionManager::on_keys_claim_response(&mut alice_store, &alice_account, &[&bob_device_stored], &claim_body)
3685 .expect("alice establishes a session with bob");
3686
3687 DeviceTracker::on_keys_query_response(
3693 &mut bob_store,
3694 &serde_json::to_vec(&serde_json::json!({
3695 "device_keys": { alice_user.as_str(): { alice_device.as_str(): alice_device_keys } }
3696 }))
3697 .expect("valid JSON"),
3698 )
3699 .expect("bob learns alice's device");
3700
3701 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3702 let group_session = mail4agent_vodozemac::megolm::GroupSession::new(mail4agent_vodozemac::megolm::SessionConfig::version_1());
3703 let room_key_content = RoomKeyContent {
3704 algorithm: "m.megolm.v1.aes-sha2".to_string(),
3705 room_id,
3706 session_id: group_session.session_id(),
3707 session_key: group_session.session_key().to_base64(),
3708 };
3709 let encrypted = OlmSessionManager::encrypt_to_device(
3710 &mut alice_store,
3711 &alice_account,
3712 &alice_user,
3713 &alice_device,
3714 &bob_device_stored,
3715 "m.room_key",
3716 serde_json::to_value(&room_key_content).expect("valid JSON"),
3717 )
3718 .expect("encrypt to bob");
3719 let to_device_event = ToDeviceEvent {
3720 sender: alice_user.clone(),
3721 event_type: "m.room.encrypted".to_string(),
3722 content: serde_json::to_value(RoomEncryptedContent::Olm(encrypted)).expect("valid JSON"),
3723 };
3724
3725 let mut core =
3726 MessengerCore::open(Vec::new(), InsecurePlainCodecForTests, test_config(), CoreSecrets::default(), 0, Box::new(FixedJitter(0.0)))
3727 .expect("open succeeds");
3728 core.store = bob_store;
3729 core.account = bob_account;
3730
3731 let mut sessions = Vec::new();
3732 core.process_to_device_event(&to_device_event, &mut sessions).expect("first decrypt succeeds");
3733 assert_eq!(
3734 sessions,
3735 vec![(room_key_content.room_id.clone(), room_key_content.session_id.clone())],
3736 "the first, genuine delivery is fully validated and accepted"
3737 );
3738
3739 let batch = core.store.take_flush_batch().expect("decrypting dirtied the store");
3743 core.store.ack_flush(batch.id);
3744 let reloaded_store = Store::load(batch.records, InsecurePlainCodecForTests, device_id()).expect("reload succeeds");
3745 core.store = reloaded_store;
3746 core.account = OlmAccountState::load_or_create(&mut core.store).expect("reload reuses the persisted account");
3747
3748 let mut sessions_after_restart = Vec::new();
3752 let result = core.process_to_device_event(&to_device_event, &mut sessions_after_restart);
3753 assert!(result.is_ok(), "a redelivered to-device event is dropped, not surfaced as an ingestion error");
3754 assert!(sessions_after_restart.is_empty(), "the replay never produces a second accepted room key");
3755 }
3756
3757 #[test]
3758 fn unknown_sender_device_is_retried_after_keys_query() {
3759 use crate::crypto::olm_sessions::OlmSessionManager;
3760
3761 let alice_user = UserId::parse("@alice:example.org").expect("valid user id");
3763 let alice_device = DeviceId::parse("ALICEDEV").expect("valid device id");
3764 let mut alice_store = Store::new(alice_device.clone(), InsecurePlainCodecForTests);
3765 let alice_account = OlmAccountState::load_or_create(&mut alice_store).expect("create alice's account");
3766 let alice_device_keys =
3774 alice_account.device_keys_json(&alice_user, &alice_device).expect("sign alice's device_keys");
3775
3776 let bob_user = UserId::parse("@bob:example.org").expect("valid user id");
3784 let bob_device = DeviceId::parse("BOBDEV").expect("valid device id");
3785 let mut bob_store = Store::new(bob_device.clone(), InsecurePlainCodecForTests);
3786 let mut bob_account = OlmAccountState::load_or_create(&mut bob_store).expect("create bob's account");
3787 bob_account.on_sync_counts(&mut bob_store, 0, &["signed_curve25519".to_string()]).expect("top up bob's OTKs");
3788 let upload = bob_account
3789 .keys_upload_request(RequestId::next(0), &bob_user, &bob_device)
3790 .expect("builds a request")
3791 .expect("something to upload");
3792 let one_time_keys = upload.body.expect("upload has a body");
3793 let one_time_keys = one_time_keys.get("one_time_keys").and_then(serde_json::Value::as_object).expect("OTKs present");
3794 let (key_id, key_value) = one_time_keys.iter().next().expect("at least one OTK");
3795 let claim_body = serde_json::to_vec(&serde_json::json!({
3796 "one_time_keys": { bob_user.as_str(): { bob_device.as_str(): { key_id: key_value } } }
3797 }))
3798 .expect("valid JSON");
3799 let bob_device_stored = crate::crypto::device_tracker::StoredDevice {
3800 user_id: bob_user.clone(),
3801 device_id: bob_device.clone(),
3802 curve25519: bob_account.identity_keys().curve25519,
3803 ed25519: bob_account.identity_keys().ed25519,
3804 algorithms: vec!["m.olm.v1.curve25519-aes-sha2".to_string(), "m.megolm.v1.aes-sha2".to_string()],
3805 display_name: None,
3806 verified: false,
3807 blocked: false,
3808 };
3809 OlmSessionManager::on_keys_claim_response(&mut alice_store, &alice_account, &[&bob_device_stored], &claim_body)
3810 .expect("alice establishes a session with bob");
3811 let encrypted = OlmSessionManager::encrypt_to_device(
3812 &mut alice_store,
3813 &alice_account,
3814 &alice_user,
3815 &alice_device,
3816 &bob_device_stored,
3817 "m.dummy",
3818 serde_json::json!({}),
3819 )
3820 .expect("encrypt to bob");
3821 let to_device_event = ToDeviceEvent {
3822 sender: alice_user.clone(),
3823 event_type: "m.room.encrypted".to_string(),
3824 content: serde_json::to_value(RoomEncryptedContent::Olm(encrypted)).expect("valid JSON"),
3825 };
3826
3827 let bob_config = CoreConfig { user_id: bob_user.clone(), device_id: bob_device.clone(), server_name: "example.org".to_string() };
3828 let mut core =
3829 MessengerCore::open(Vec::new(), InsecurePlainCodecForTests, bob_config, CoreSecrets::default(), 0, Box::new(FixedJitter(0.0)))
3830 .expect("open succeeds");
3831 core.store = bob_store;
3832 core.account = bob_account;
3833
3834 let mut sessions = Vec::new();
3838 core.process_to_device_event(&to_device_event, &mut sessions).expect("queues for retry, does not error");
3839 assert_eq!(core.unknown_sender_retry.len(), 1, "queued for a retry after the next keys/query");
3840 let alice_curve = alice_account.identity_keys().curve25519.to_base64();
3841 assert!(
3842 core.store.olm_sessions_for_device(&alice_curve).expect("no error").is_empty(),
3843 "the refused attempt created no Olm session (it would have consumed the one-time key)"
3844 );
3845
3846 let query_body = serde_json::to_vec(&serde_json::json!({
3851 "device_keys": { alice_user.as_str(): { alice_device.as_str(): alice_device_keys } }
3852 }))
3853 .expect("valid JSON");
3854 DeviceTracker::on_keys_query_response(&mut core.store, &query_body).expect("bob learns alice's device");
3855 core.retry_unknown_sender_queue().expect("no error");
3856 assert!(core.unknown_sender_retry.is_empty(), "the queued entry is drained by the retry attempt");
3857 assert_eq!(
3858 core.store.olm_sessions_for_device(&alice_curve).expect("no error").len(),
3859 1,
3860 "the retry decrypted the very same pre-key message and established the inbound session"
3861 );
3862 }
3863
3864 #[test]
3865 fn dispatch_load_older_produces_an_outgoing_request() {
3866 let mut core = open_fresh_core();
3867 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3868 let mut timeline = Timeline::new();
3869 let event: RawEvent = serde_json::from_value(serde_json::json!({
3870 "event_id": "$1:example.org",
3871 "type": "m.room.message",
3872 "sender": "@alice:example.org",
3873 "origin_server_ts": 1,
3874 "content": { "msgtype": "m.text", "body": "hi" }
3875 }))
3876 .expect("valid event");
3877 timeline.apply_timeline_batch(&[event], true, Some("t1".to_string()));
3878 core.timelines.insert(room_id.clone(), timeline);
3879 flush_and_ack(&mut core);
3880
3881 core.dispatch(MessengerCommand::LoadOlder { room_id: room_id.clone() }, 0).expect("dispatch succeeds");
3882 flush_and_ack(&mut core);
3883 let requests = core.releasable_requests(0);
3884 let load_older = requests.iter().find(|r| r.kind == OutgoingRequestKind::RoomMessages);
3885 assert!(load_older.is_some(), "a RoomMessages request was enqueued using the room's own gap token");
3886 }
3887
3888 #[test]
3889 fn load_older_on_a_resumed_room_pages_back_from_the_sync_token() {
3890 let mut core = open_fresh_core();
3891 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3892 core.store.save_sync_token("s42_7".to_string()).expect("save the sync token");
3893 flush_and_ack(&mut core);
3894
3895 core.dispatch(MessengerCommand::LoadOlder { room_id: room_id.clone() }, 0).expect("dispatch succeeds");
3897 flush_and_ack(&mut core);
3898 assert!(core.releasable_requests(0).iter().all(|r| r.kind != OutgoingRequestKind::RoomMessages));
3899
3900 core.rooms.insert(room_id.clone(), RoomState::default());
3902 core.dispatch(MessengerCommand::LoadOlder { room_id: room_id.clone() }, 0).expect("dispatch succeeds");
3903 flush_and_ack(&mut core);
3904 let requests = core.releasable_requests(0);
3905 let load_older = requests.iter().find(|r| r.kind == OutgoingRequestKind::RoomMessages).expect("a RoomMessages request was enqueued");
3906 assert!(load_older.query.contains(&("from".to_string(), "s42_7".to_string())), "pages back from the sync token: {:?}", load_older.query);
3907 }
3908
3909 fn bob_joins_room_body(encrypted: bool) -> serde_json::Value {
3912 let mut state = Vec::new();
3913 if encrypted {
3914 state.push(serde_json::json!({
3915 "event_id": "$enc:example.org", "type": "m.room.encryption", "sender": "@alice:example.org",
3916 "origin_server_ts": 1, "state_key": "", "content": { "algorithm": "m.megolm.v1.aes-sha2" }
3917 }));
3918 }
3919 serde_json::json!({
3920 "next_batch": "s1",
3921 "rooms": { "join": { "!r:example.org": {
3922 "state": { "events": state },
3923 "timeline": { "events": [{
3924 "event_id": "$join:example.org", "type": "m.room.member", "sender": "@bob:example.org",
3925 "origin_server_ts": 2, "state_key": "@bob:example.org", "content": { "membership": "join" }
3926 }] }
3927 } } }
3928 })
3929 }
3930
3931 #[test]
3932 fn a_member_joining_an_encrypted_room_is_tracked_and_queried_right_away() {
3933 let mut core = open_fresh_core();
3934 let bob = UserId::parse("@bob:example.org").expect("valid user id");
3935 deliver_sync(&mut core, bob_joins_room_body(true));
3936 let tracked = core.store.tracked_users().expect("no error");
3937 assert!(tracked.iter().any(|(user_id, outdated)| *user_id == bob && *outdated), "bob is tracked with an outdated device list: {tracked:?}");
3938
3939 flush_and_ack(&mut core);
3940 let requests = core.releasable_requests(0);
3941 let query = requests.iter().find(|r| r.kind == OutgoingRequestKind::KeysQuery).expect("a /keys/query was enqueued for the new member");
3942 assert!(query.body.as_ref().is_some_and(|body| body["device_keys"].get(bob.as_str()).is_some()), "the query names bob: {:?}", query.body);
3943 }
3944
3945 #[test]
3946 fn a_member_joining_a_plaintext_room_is_not_tracked() {
3947 let mut core = open_fresh_core();
3948 deliver_sync(&mut core, bob_joins_room_body(false));
3949 assert!(core.store.tracked_users().expect("no error").is_empty());
3950 flush_and_ack(&mut core);
3951 assert!(core.releasable_requests(0).iter().all(|r| r.kind != OutgoingRequestKind::KeysQuery));
3952 }
3953
3954 #[test]
3955 fn dispatch_mark_read_and_set_typing_produce_outgoing_requests() {
3956 let mut core = open_fresh_core();
3957 flush_and_ack(&mut core);
3958 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
3959 let event_id = EventId::parse("$1:example.org").expect("valid event id");
3960
3961 core.dispatch(MessengerCommand::MarkRead { room_id: room_id.clone(), event_id }, 0).expect("dispatch succeeds");
3962 core.dispatch(MessengerCommand::SetTyping { room_id: room_id.clone(), typing: true }, 0).expect("dispatch succeeds");
3963 flush_and_ack(&mut core);
3964
3965 let requests = core.releasable_requests(0);
3966 assert!(requests.iter().any(|r| r.kind == OutgoingRequestKind::ReadMarkers));
3967 assert!(requests.iter().any(|r| r.kind == OutgoingRequestKind::Typing));
3968 }
3969
3970 #[test]
3971 fn snapshot_reflects_the_latest_applied_sync() {
3972 let mut core = open_fresh_core();
3973 flush_and_ack(&mut core);
3974 core.releasable_requests(0);
3975 flush_and_ack(&mut core);
3976 let requests = core.releasable_requests(0);
3977 let sync_request = requests.iter().find(|r| r.kind == OutgoingRequestKind::Sync).expect("sync enqueued");
3978
3979 let body = serde_json::json!({
3980 "next_batch": "s1",
3981 "rooms": {
3982 "join": {
3983 "!r:example.org": {
3984 "state": { "events": [] },
3985 "timeline": {
3986 "events": [
3987 {
3988 "event_id": "$1:example.org",
3989 "type": "m.room.message",
3990 "sender": "@alice:example.org",
3991 "origin_server_ts": 1,
3992 "content": { "msgtype": "m.text", "body": "hi" }
3993 }
3994 ]
3995 },
3996 "unread_notifications": { "notification_count": 1, "highlight_count": 0 }
3997 }
3998 }
3999 }
4000 })
4001 .to_string();
4002 let events = core.on_response(sync_request.id.clone(), HttpResponseDescriptor { status: 200, body: body.into_bytes() }, 0);
4003
4004 assert!(events.contains(&MessengerEvent::RoomsChanged));
4005 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4006 assert!(events.contains(&MessengerEvent::TimelineChanged { room_id: room_id.clone() }));
4007 assert!(events.contains(&MessengerEvent::UnreadChanged { room_id: room_id.clone() }));
4008
4009 let timeline = core.timeline(&room_id).expect("timeline present");
4010 assert_eq!(timeline.items().len(), 1);
4011 assert_eq!(core.room_state(&room_id).expect("room present").unread_count(), 1);
4012 assert_eq!(core.change_counter(), events.len() as u64);
4013 assert!(core.events().is_empty());
4016 }
4017
4018 #[test]
4019 fn dispatch_set_account_data_updates_optimistically_and_sends_a_put() {
4020 let mut core = open_fresh_core();
4021 flush_and_ack(&mut core);
4022 let content = serde_json::json!({ "note": "keep" });
4023 core.dispatch(
4024 MessengerCommand::SetAccountData { event_type: "com.example.prefs".to_string(), content: content.clone() },
4025 0,
4026 )
4027 .expect("dispatch succeeds");
4028
4029 assert_eq!(
4030 core.global_account_data("com.example.prefs"),
4031 Some(&content),
4032 "SetAccountData updates the core's own cache before any round trip"
4033 );
4034
4035 flush_and_ack(&mut core);
4036 let requests = core.releasable_requests(0);
4037 let request = requests.iter().find(|r| r.kind == OutgoingRequestKind::AccountData).expect("an AccountData request was enqueued");
4038 assert_eq!(request.method, HttpMethod::Put);
4039 assert_eq!(request.path, "/_matrix/client/v3/user/%40alice%3Aexample.org/account_data/com.example.prefs");
4040 assert_eq!(request.body.as_ref(), Some(&content));
4041 }
4042
4043 #[test]
4044 fn dispatch_set_room_account_data_updates_optimistically_and_sends_a_put() {
4045 let mut core = open_fresh_core();
4046 flush_and_ack(&mut core);
4047 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4048 let content = serde_json::json!({ "flag": true });
4049 core.dispatch(
4050 MessengerCommand::SetRoomAccountData {
4051 room_id: room_id.clone(),
4052 event_type: "com.example.flag".to_string(),
4053 content: content.clone(),
4054 },
4055 0,
4056 )
4057 .expect("dispatch succeeds");
4058
4059 assert_eq!(
4060 core.room_account_data(&room_id, "com.example.flag"),
4061 Some(&content),
4062 "SetRoomAccountData updates the core's own cache before any round trip"
4063 );
4064
4065 flush_and_ack(&mut core);
4066 let requests = core.releasable_requests(0);
4067 let request =
4068 requests.iter().find(|r| r.kind == OutgoingRequestKind::RoomAccountData).expect("a RoomAccountData request was enqueued");
4069 assert_eq!(request.method, HttpMethod::Put);
4070 assert_eq!(
4071 request.path,
4072 "/_matrix/client/v3/user/%40alice%3Aexample.org/rooms/%21r%3Aexample.org/account_data/com.example.flag"
4073 );
4074 assert_eq!(request.body.as_ref(), Some(&content));
4075 }
4076
4077 #[test]
4078 fn dispatch_search_public_rooms_sends_the_expected_request() {
4079 let mut core = open_fresh_core();
4080 flush_and_ack(&mut core);
4081 core.dispatch(MessengerCommand::SearchPublicRooms { term: "trading".to_string() }, 0).expect("dispatch succeeds");
4082 flush_and_ack(&mut core);
4083 let requests = core.releasable_requests(0);
4084 let request = requests.iter().find(|r| r.kind == OutgoingRequestKind::PublicRooms).expect("a PublicRooms request was enqueued");
4085 assert_eq!(request.method, HttpMethod::Post);
4086 assert_eq!(request.path, "/_matrix/client/v3/publicRooms");
4087 assert_eq!(request.body, Some(serde_json::json!({ "filter": { "generic_search_term": "trading" }, "limit": 50 })));
4088 }
4089
4090 #[test]
4091 fn dispatch_search_users_sends_the_expected_request() {
4092 let mut core = open_fresh_core();
4093 flush_and_ack(&mut core);
4094 core.dispatch(MessengerCommand::SearchUsers { term: "bob".to_string() }, 0).expect("dispatch succeeds");
4095 flush_and_ack(&mut core);
4096 let requests = core.releasable_requests(0);
4097 let request =
4098 requests.iter().find(|r| r.kind == OutgoingRequestKind::UserDirectorySearch).expect("a UserDirectorySearch request was enqueued");
4099 assert_eq!(request.method, HttpMethod::Post);
4100 assert_eq!(request.path, "/_matrix/client/v3/user_directory/search");
4101 assert_eq!(request.body, Some(serde_json::json!({ "search_term": "bob", "limit": 20 })));
4102 }
4103
4104 #[test]
4105 fn public_rooms_response_superseded_by_a_later_search_is_dropped() {
4106 let mut core = open_fresh_core();
4107 flush_and_ack(&mut core);
4108 core.dispatch(MessengerCommand::SearchPublicRooms { term: "first".to_string() }, 0).expect("dispatch succeeds");
4109 flush_and_ack(&mut core);
4110 let first_request = core
4111 .releasable_requests(0)
4112 .into_iter()
4113 .find(|r| r.kind == OutgoingRequestKind::PublicRooms)
4114 .expect("the first search's own request");
4115
4116 core.dispatch(MessengerCommand::SearchPublicRooms { term: "second".to_string() }, 0).expect("dispatch succeeds");
4119 flush_and_ack(&mut core);
4120 let second_request = core
4121 .releasable_requests(0)
4122 .into_iter()
4123 .find(|r| r.kind == OutgoingRequestKind::PublicRooms)
4124 .expect("the second search's own request");
4125
4126 let stale_body = serde_json::json!({ "chunk": [{ "room_id": "!stale:example.org", "num_joined_members": 1 }] });
4128 core.on_response(first_request.id, HttpResponseDescriptor { status: 200, body: serde_json::to_vec(&stale_body).expect("json") }, 0);
4129 assert!(core.public_rooms_result().is_empty(), "a response to a superseded search is dropped");
4130
4131 let fresh_body = serde_json::json!({ "chunk": [{ "room_id": "!fresh:example.org", "num_joined_members": 2 }] });
4133 core.on_response(second_request.id, HttpResponseDescriptor { status: 200, body: serde_json::to_vec(&fresh_body).expect("json") }, 0);
4134 assert_eq!(core.public_rooms_result().len(), 1);
4135 assert_eq!(core.public_rooms_result()[0].room_id, RoomId::parse("!fresh:example.org").expect("valid room id"));
4136 }
4137
4138 fn is_account_data_write(request: &OutgoingRequest) -> bool {
4139 matches!(request.kind, OutgoingRequestKind::AccountData | OutgoingRequestKind::RoomAccountData)
4140 }
4141
4142 #[test]
4143 fn pin_then_archive_back_to_back_keeps_both_tags() {
4144 let mut core = open_fresh_core();
4145 flush_and_ack(&mut core);
4146 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4147 for tag in ["m.favourite", "u.example.archived"] {
4148 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: tag.to_string(), order: None }, 0)
4149 .expect("dispatch succeeds");
4150 }
4151
4152 let cached = core.room_account_data(&room_id, "m.tag").expect("m.tag cached");
4154 assert!(cached["tags"].get("m.favourite").is_some());
4155 assert!(cached["tags"].get("u.example.archived").is_some());
4156
4157 flush_and_ack(&mut core);
4158 let first_release = core.releasable_requests(0);
4159 let first_writes: Vec<&OutgoingRequest> = first_release.iter().filter(|r| is_account_data_write(r)).collect();
4160 assert_eq!(first_writes.len(), 1, "the second PUT is not released while the first is in flight");
4161 let first_body = first_writes[0].body.clone().expect("a body");
4162 assert!(first_body["tags"].get("m.favourite").is_some());
4163 assert!(first_body["tags"].get("u.example.archived").is_none(), "the first PUT carries only what existed when it was built");
4164
4165 core.on_response(first_writes[0].id.clone(), HttpResponseDescriptor { status: 200, body: b"{}".to_vec() }, 0);
4166 flush_and_ack(&mut core);
4167 let second_release = core.releasable_requests(0);
4168 let second = second_release.iter().find(|r| is_account_data_write(r)).expect("the second PUT is released after the first completed");
4169 let second_body = second.body.clone().expect("a body");
4170 assert!(second_body["tags"].get("m.favourite").is_some(), "the later PUT still carries the earlier tag");
4171 assert!(second_body["tags"].get("u.example.archived").is_some());
4172 }
4173
4174 #[test]
4175 fn account_data_lane_is_fifo_single_flight() {
4176 let mut core = open_fresh_core();
4177 flush_and_ack(&mut core);
4178 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4179 core.dispatch(MessengerCommand::SetAccountData { event_type: "org.t.one".to_string(), content: serde_json::json!({}) }, 0)
4180 .expect("dispatch succeeds");
4181 core.dispatch(
4182 MessengerCommand::SetRoomAccountData {
4183 room_id: room_id.clone(),
4184 event_type: "org.t.two".to_string(),
4185 content: serde_json::json!({}),
4186 },
4187 0,
4188 )
4189 .expect("dispatch succeeds");
4190 core.dispatch(MessengerCommand::SetAccountData { event_type: "org.t.three".to_string(), content: serde_json::json!({}) }, 0)
4191 .expect("dispatch succeeds");
4192 core.dispatch(MessengerCommand::RemoveTag { room_id: room_id.clone(), tag: "m.favourite".to_string() }, 0)
4193 .expect("dispatch succeeds");
4194 flush_and_ack(&mut core);
4195
4196 let mut released_paths = Vec::new();
4197 for _ in 0..4 {
4198 let release = core.releasable_requests(0);
4199 let writes: Vec<&OutgoingRequest> = release.iter().filter(|r| is_account_data_write(r)).collect();
4200 assert_eq!(writes.len(), 1, "exactly one account-data write in flight at a time");
4201 released_paths.push(writes[0].path.clone());
4202 core.on_response(writes[0].id.clone(), HttpResponseDescriptor { status: 200, body: b"{}".to_vec() }, 0);
4203 flush_and_ack(&mut core);
4204 }
4205 assert!(released_paths[0].ends_with("/account_data/org.t.one"));
4206 assert!(released_paths[1].ends_with("/account_data/org.t.two"));
4207 assert!(released_paths[2].ends_with("/account_data/org.t.three"));
4208 assert!(released_paths[3].ends_with("/account_data/m.tag"));
4209 assert!(core.releasable_requests(0).iter().all(|r| !is_account_data_write(r)), "the lane is drained");
4210 }
4211
4212 fn deliver_sync(core: &mut MessengerCore<InsecurePlainCodecForTests>, body: serde_json::Value) -> Vec<OutgoingRequest> {
4217 let mut others = Vec::new();
4218 for _ in 0..3 {
4219 flush_and_ack(core);
4220 let mut sync_request = None;
4221 for request in core.releasable_requests(0) {
4222 if request.kind == OutgoingRequestKind::Sync && sync_request.is_none() {
4223 sync_request = Some(request);
4224 } else {
4225 others.push(request);
4226 }
4227 }
4228 if let Some(sync_request) = sync_request {
4229 let body = serde_json::to_vec(&body).expect("valid JSON");
4230 core.on_response(sync_request.id, HttpResponseDescriptor { status: 200, body }, 0);
4231 return others;
4232 }
4233 }
4234 panic!("no sync request became releasable");
4235 }
4236
4237 fn tag_sync_body(room_id: &str, tags: serde_json::Value, next_batch: &str) -> serde_json::Value {
4238 let mut join = serde_json::Map::new();
4239 join.insert(
4240 room_id.to_string(),
4241 serde_json::json!({ "account_data": { "events": [{ "type": "m.tag", "content": { "tags": tags } }] } }),
4242 );
4243 serde_json::json!({ "next_batch": next_batch, "rooms": { "join": serde_json::Value::Object(join) } })
4244 }
4245
4246 fn ok_response() -> HttpResponseDescriptor {
4247 HttpResponseDescriptor { status: 200, body: b"{}".to_vec() }
4248 }
4249
4250 #[test]
4251 fn sync_echo_of_an_older_tag_write_does_not_clobber_a_newer_optimistic_value() {
4252 let mut core = open_fresh_core();
4253 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4254 for tag in ["m.favourite", "u.example.archived"] {
4255 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: tag.to_string(), order: None }, 0)
4256 .expect("dispatch succeeds");
4257 }
4258
4259 let released = deliver_sync(&mut core, tag_sync_body("!r:example.org", serde_json::json!({ "m.favourite": {} }), "s1"));
4261 let cached = core.room_account_data(&room_id, "m.tag").expect("m.tag cached");
4262 assert!(cached["tags"].get("u.example.archived").is_some(), "the older echo must not clobber the newer optimistic value");
4263 assert!(cached["tags"].get("m.favourite").is_some());
4264 let key = (Some(room_id.clone()), "m.tag".to_string());
4265 assert_eq!(
4266 core.account_data_guard.get(&key).expect("writes are outstanding").server_value,
4267 Some(serde_json::json!({ "tags": { "m.favourite": {} } })),
4268 "the echo is remembered as the server's value"
4269 );
4270
4271 let first = released.iter().find(|r| r.kind == OutgoingRequestKind::RoomAccountData).expect("first write released");
4274 core.on_response(first.id.clone(), ok_response(), 0);
4275 flush_and_ack(&mut core);
4276 let second = core
4277 .releasable_requests(0)
4278 .into_iter()
4279 .find(|r| r.kind == OutgoingRequestKind::RoomAccountData)
4280 .expect("second write released after the first");
4281 core.on_response(second.id, ok_response(), 0);
4282 assert!(core.account_data_guard.is_empty());
4283 let cached = core.room_account_data(&room_id, "m.tag").expect("m.tag cached");
4284 assert!(cached["tags"].get("m.favourite").is_some() && cached["tags"].get("u.example.archived").is_some());
4285
4286 deliver_sync(
4288 &mut core,
4289 tag_sync_body("!r:example.org", serde_json::json!({ "m.favourite": {}, "u.example.archived": {} }), "s2"),
4290 );
4291 let cached = core.room_account_data(&room_id, "m.tag").expect("m.tag cached");
4292 assert!(cached["tags"].get("u.example.archived").is_some());
4293 }
4294
4295 #[test]
4296 fn terminal_account_data_failure_restores_the_server_value() {
4297 let mut core = open_fresh_core();
4298 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4299 deliver_sync(&mut core, tag_sync_body("!r:example.org", serde_json::json!({ "m.favourite": {} }), "s1"));
4300
4301 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: "u.example.archived".to_string(), order: None }, 0)
4302 .expect("dispatch succeeds");
4303 assert!(core.room_account_data(&room_id, "m.tag").expect("cached")["tags"].get("u.example.archived").is_some());
4304 flush_and_ack(&mut core);
4305 let write = core
4306 .releasable_requests(0)
4307 .into_iter()
4308 .find(|r| r.kind == OutgoingRequestKind::RoomAccountData)
4309 .expect("the write is released");
4310
4311 let counter_before = core.change_counter();
4312 let forbidden =
4313 HttpResponseDescriptor { status: 403, body: br#"{"errcode":"M_FORBIDDEN","error":"nope"}"#.to_vec() };
4314 let events = core.on_response(write.id, forbidden, 0);
4315 assert!(core.account_data_guard.is_empty());
4316 let restored = core.room_account_data(&room_id, "m.tag").expect("the server's value is back");
4317 assert!(restored["tags"].get("u.example.archived").is_none(), "the failed write's optimistic value is gone");
4318 assert!(restored["tags"].get("m.favourite").is_some());
4319 assert!(events.contains(&MessengerEvent::RoomsChanged));
4320 assert!(core.change_counter() > counter_before, "the UI is told to re-render");
4321
4322 for tag in ["u.a", "u.b"] {
4326 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: tag.to_string(), order: None }, 0)
4327 .expect("dispatch succeeds");
4328 }
4329 flush_and_ack(&mut core);
4330 let first = core
4331 .releasable_requests(0)
4332 .into_iter()
4333 .find(|r| r.kind == OutgoingRequestKind::RoomAccountData)
4334 .expect("first write released");
4335 let forbidden =
4336 HttpResponseDescriptor { status: 403, body: br#"{"errcode":"M_FORBIDDEN","error":"nope"}"#.to_vec() };
4337 core.on_response(first.id, forbidden, 0);
4338 let cached = core.room_account_data(&room_id, "m.tag").expect("cached");
4339 assert!(cached["tags"].get("u.a").is_some() && cached["tags"].get("u.b").is_some(), "cache untouched while a write is pending");
4340 flush_and_ack(&mut core);
4341 let second = core
4342 .releasable_requests(0)
4343 .into_iter()
4344 .find(|r| r.kind == OutgoingRequestKind::RoomAccountData)
4345 .expect("second write released");
4346 core.on_response(second.id, ok_response(), 0);
4347 let cached = core.room_account_data(&room_id, "m.tag").expect("cached");
4348 assert!(cached["tags"].get("u.b").is_some(), "the last write succeeded: its optimistic value stands");
4349 }
4350
4351 #[test]
4352 fn pending_account_data_writes_rebuild_their_guard_after_a_restart() {
4353 let mut core = open_fresh_core();
4354 let room_id = RoomId::parse("!r:example.org").expect("valid room id");
4355 core.dispatch(MessengerCommand::SetTag { room_id: room_id.clone(), tag: "m.favourite".to_string(), order: None }, 0)
4356 .expect("dispatch succeeds");
4357 core.dispatch(
4358 MessengerCommand::SetAccountData { event_type: "org.t.one".to_string(), content: serde_json::json!({ "n": 1 }) },
4359 0,
4360 )
4361 .expect("dispatch succeeds");
4362
4363 let mut records = Vec::new();
4365 while let Some(batch) = core.take_flush_batch() {
4366 records.extend(batch.records.clone());
4367 core.ack_flush(batch.id);
4368 }
4369 let reopened = MessengerCore::open(
4370 records,
4371 InsecurePlainCodecForTests,
4372 test_config(),
4373 CoreSecrets::default(),
4374 0,
4375 Box::new(FixedJitter(0.0)),
4376 )
4377 .expect("reopen succeeds");
4378
4379 assert_eq!(reopened.account_data_guard.len(), 2, "one guard per key with a pending write");
4380 assert!(reopened.account_data_guard.values().all(|guard| guard.pending == 1 && guard.server_value.is_none()));
4381 assert_eq!(reopened.global_account_data("org.t.one"), Some(&serde_json::json!({ "n": 1 })));
4382 assert!(reopened.room_account_data(&room_id, "m.tag").expect("cached")["tags"].get("m.favourite").is_some());
4383 }
4384
4385 type TestCore = MessengerCore<InsecurePlainCodecForTests>;
4390
4391 fn room(id: &str) -> RoomId {
4392 RoomId::parse(id).expect("valid room id")
4393 }
4394
4395 fn event(id: &str) -> EventId {
4396 EventId::parse(id).expect("valid event id")
4397 }
4398
4399 fn add_room(core: &mut TestCore, room_id: &RoomId, encrypted: bool, name: Option<&str>) {
4402 let mut state = RoomState::new();
4403 if encrypted {
4404 state.encryption = Some(crate::wire::events::RoomEncryptionContent {
4405 algorithm: "m.megolm.v1.aes-sha2".to_string(),
4406 rotation_period_ms: 604_800_000,
4407 rotation_period_msgs: 100,
4408 });
4409 }
4410 state.name = name.map(str::to_string);
4411 core.rooms.insert(room_id.clone(), state);
4412 core.timelines.entry(room_id.clone()).or_default();
4413 }
4414
4415 fn mark_as_channel(core: &mut TestCore, room_id: &RoomId) {
4418 let Some(state) = core.rooms.get_mut(room_id) else { return };
4419 state
4420 .apply_state_event(&state_event_raw(
4421 "m.room.join_rules",
4422 "",
4423 "@alice:example.org",
4424 serde_json::json!({ "join_rule": "public" }),
4425 ))
4426 .expect("join_rules");
4427 state
4428 .apply_state_event(&state_event_raw(
4429 "m.room.power_levels",
4430 "",
4431 "@alice:example.org",
4432 serde_json::json!({ "events_default": 50, "users_default": 0 }),
4433 ))
4434 .expect("power_levels");
4435 }
4436
4437 fn state_event_raw(event_type: &str, state_key: &str, sender: &str, content: serde_json::Value) -> RawEvent {
4438 serde_json::from_value(serde_json::json!({
4439 "event_id": format!("${event_type}:example.org"),
4440 "type": event_type,
4441 "sender": sender,
4442 "origin_server_ts": 1,
4443 "state_key": state_key,
4444 "content": content,
4445 }))
4446 .expect("valid raw event")
4447 }
4448
4449 fn raw_event(event_id: &str, event_type: &str, sender: &str, content: serde_json::Value) -> RawEvent {
4450 serde_json::from_value(serde_json::json!({
4451 "event_id": event_id,
4452 "type": event_type,
4453 "sender": sender,
4454 "origin_server_ts": 1,
4455 "content": content,
4456 }))
4457 .expect("valid raw event")
4458 }
4459
4460 fn seed_plain(core: &mut TestCore, room_id: &RoomId, event_id: &str, sender: &str, event_type: &str, content: serde_json::Value) {
4462 let raw = raw_event(event_id, event_type, sender, content);
4463 core.timelines.entry(room_id.clone()).or_default().apply_timeline_batch(&[raw], false, None);
4464 }
4465
4466 fn seed_sealed(core: &mut TestCore, room_id: &RoomId, event_id: &str, sender: &str) {
4468 let content = serde_json::json!({ "algorithm": "m.megolm.v1.aes-sha2", "ciphertext": "AAAA", "session_id": "s1" });
4469 seed_plain(core, room_id, event_id, sender, "m.room.encrypted", content);
4470 }
4471
4472 fn seed_decrypted(
4475 core: &mut TestCore,
4476 room_id: &RoomId,
4477 event_id_str: &str,
4478 sender: &str,
4479 event_type: &str,
4480 content: serde_json::Value,
4481 ) {
4482 seed_sealed(core, room_id, event_id_str, sender);
4483 if let Some(timeline) = core.timelines.get_mut(room_id) {
4484 timeline.set_decrypted_event(&event(event_id_str), event_type, &content);
4485 }
4486 }
4487
4488 fn sent_room_event(core: &mut TestCore) -> (String, serde_json::Value) {
4492 flush_and_ack(core);
4493 let request = core
4494 .releasable_requests(0)
4495 .into_iter()
4496 .find(|request| request.kind == OutgoingRequestKind::RoomSend)
4497 .expect("a room send is releasable");
4498 let confirmed = format!("$sent{}:example.org", core.counters.next_request_id);
4499 let body = serde_json::json!({ "event_id": confirmed }).to_string().into_bytes();
4500 core.on_response(request.id.clone(), HttpResponseDescriptor { status: 200, body }, 0);
4501 (request.path, request.body.expect("a room send has a body"))
4502 }
4503
4504 fn forward(core: &mut TestCore, from: &RoomId, event_id: &str, to: &RoomId) -> Result<(), MessengerError> {
4505 core.dispatch(
4506 MessengerCommand::Forward { from_room: from.clone(), event_id: event(event_id), to_room: to.clone(), txn_id: None },
4507 0,
4508 )
4509 }
4510
4511 fn last_item(core: &TestCore, room_id: &RoomId) -> crate::room::timeline::TimelineItem {
4512 core.timeline(room_id).and_then(|timeline| timeline.items().last()).expect("the room has an item").clone()
4513 }
4514
4515 #[test]
4516 fn forward_from_encrypted_room_carries_only_the_forwarded_flag() {
4517 let mut core = open_fresh_core();
4518 let dm = room("!dm:example.org");
4519 let target = room("!target:example.org");
4520 add_room(&mut core, &dm, true, Some("Secret DM name"));
4521 add_room(&mut core, &target, false, None);
4522 seed_decrypted(
4523 &mut core,
4524 &dm,
4525 "$src:example.org",
4526 "@bob:example.org",
4527 "m.room.message",
4528 serde_json::json!({
4529 "msgtype": "m.text",
4530 "body": "quarterly numbers",
4531 "m.relates_to": { "m.in_reply_to": { "event_id": "$parent:example.org" } },
4532 "m.mentions": { "user_ids": ["@carol:example.org"] },
4533 }),
4534 );
4535
4536 forward(&mut core, &dm, "$src:example.org", &target).expect("a decrypted message is forwardable");
4537
4538 let (path, body) = sent_room_event(&mut core);
4539 assert!(path.contains("/send/m.room.message/"), "the event type is kept: {path}");
4540 assert_eq!(
4541 body,
4542 serde_json::json!({ "msgtype": "m.text", "body": "quarterly numbers", "forwarded": true }),
4543 "the text plus the bare flag; the reply pointer and the mentions are stripped"
4544 );
4545 let wire = body.to_string();
4546 for leaked in ["bob", "carol", "!dm", "Secret DM name", "$parent"] {
4547 assert!(!wire.contains(leaked), "{leaked:?} must not travel with a forward out of an encrypted room: {wire}");
4548 }
4549 let echo = last_item(&core, &target);
4550 assert_eq!(echo.forwarded, Some(Forwarded::Hidden), "the forwarder's own echo shows the same marker");
4551 }
4552
4553 #[test]
4554 fn set_power_levels_puts_the_changed_copy_of_the_rooms_levels() {
4555 let mut core = open_fresh_core();
4556 let chan = room("!chan:example.org");
4557 add_room(&mut core, &chan, false, Some("Talk"));
4558 mark_as_channel(&mut core, &chan);
4559 let carol = UserId::parse("@carol:example.org").expect("valid user id");
4560 core.dispatch(MessengerCommand::SetPowerLevels {
4561 room_id: chan.clone(),
4562 events_default: Some(0),
4563 users: vec![(carol.clone(), 50)],
4564 }, 1_000)
4565 .expect("dispatch");
4566 core.releasable_requests(1_000);
4568 flush_and_ack(&mut core);
4569 let sent = core.releasable_requests(1_000);
4570 let put = sent.iter().find(|r| r.kind == OutgoingRequestKind::RoomState).expect("a state PUT");
4571 assert!(put.path.ends_with("/state/m.room.power_levels/"), "{}", put.path);
4572 let body = put.body.clone().expect("body");
4573 assert_eq!(body["events_default"], 0);
4574 assert_eq!(body["users"]["@carol:example.org"], 50);
4575 assert_eq!(body["users_default"], 0);
4576 }
4577
4578 #[test]
4579 fn set_power_levels_refuses_an_unknown_room() {
4580 let mut core = open_fresh_core();
4581 let err = core
4582 .dispatch(MessengerCommand::SetPowerLevels { room_id: room("!nope:example.org"), events_default: Some(0), users: vec![] }, 1_000)
4583 .expect_err("unknown room");
4584 assert!(err.to_string().contains("unknown"));
4585 }
4586
4587 #[test]
4588 fn forward_from_public_channel_carries_room_id_and_name_no_sender() {
4589 let mut core = open_fresh_core();
4590 let channel = room("!chan:example.org");
4591 let target = room("!target:example.org");
4592 add_room(&mut core, &channel, true, Some("Announcements"));
4593 mark_as_channel(&mut core, &channel);
4594 add_room(&mut core, &target, false, None);
4595 seed_plain(
4596 &mut core,
4597 &channel,
4598 "$post:example.org",
4599 "@dave:example.org",
4600 "m.room.message",
4601 serde_json::json!({
4602 "msgtype": "m.text",
4603 "body": "BTC breaks out",
4604 "m.mentions": { "room": true },
4605 "m.relates_to": { "m.in_reply_to": { "event_id": "$older:example.org" } },
4606 }),
4607 );
4608
4609 forward(&mut core, &channel, "$post:example.org", &target).expect("a channel post is forwardable");
4610
4611 let (_, body) = sent_room_event(&mut core);
4612 assert_eq!(
4613 body,
4614 serde_json::json!({
4615 "msgtype": "m.text",
4616 "body": "BTC breaks out",
4617 "forwarded_from": { "room_id": "!chan:example.org", "room_name": "Announcements" },
4618 })
4619 );
4620 assert!(!body.to_string().contains("dave"), "the original sender never travels");
4621 assert_eq!(
4622 last_item(&core, &target).forwarded,
4623 Some(Forwarded::Channel { room_id: channel.clone(), room_name: "Announcements".to_string() })
4624 );
4625 }
4626
4627 #[test]
4628 fn forward_from_an_unnamed_room_never_lists_member_names() {
4629 let mut core = open_fresh_core();
4630 let channel = room("!chan:example.org");
4631 let target = room("!target:example.org");
4632 add_room(&mut core, &channel, true, None);
4633 mark_as_channel(&mut core, &channel);
4634 if let Some(state) = core.rooms.get_mut(&channel) {
4635 state.members.insert(
4636 UserId::parse("@erin:example.org").expect("valid user id"),
4637 crate::room::state::MemberState {
4638 membership: Membership::Join,
4639 displayname: Some("Erin Private".to_string()),
4640 is_direct: false,
4641 },
4642 );
4643 }
4644 add_room(&mut core, &target, false, None);
4645 seed_plain(&mut core, &channel, "$post:example.org", "@erin:example.org", "m.room.message", serde_json::json!({ "msgtype": "m.text", "body": "hi" }));
4646
4647 forward(&mut core, &channel, "$post:example.org", &target).expect("forwardable");
4648
4649 let (_, body) = sent_room_event(&mut core);
4650 let name = body["forwarded_from"]["room_name"].as_str().expect("a name is sent");
4651 assert!(!name.contains("Erin"), "an unnamed room is attributed by a generic label, not by its members: {name}");
4652 }
4653
4654 #[test]
4655 fn forward_replaces_the_sources_own_forward_marker() {
4656 let mut core = open_fresh_core();
4657 let dm = room("!dm:example.org");
4658 let target = room("!target:example.org");
4659 add_room(&mut core, &dm, true, None);
4660 add_room(&mut core, &target, false, None);
4661 seed_decrypted(
4662 &mut core,
4663 &dm,
4664 "$src:example.org",
4665 "@bob:example.org",
4666 "m.room.message",
4667 serde_json::json!({
4668 "msgtype": "m.text",
4669 "body": "second hop",
4670 "forwarded_from": { "room_id": "!older:example.org", "room_name": "Older channel" },
4671 }),
4672 );
4673
4674 forward(&mut core, &dm, "$src:example.org", &target).expect("forwardable");
4675
4676 let (_, body) = sent_room_event(&mut core);
4677 assert_eq!(body, serde_json::json!({ "msgtype": "m.text", "body": "second hop", "forwarded": true }));
4678 }
4679
4680 #[test]
4681 fn forward_of_edited_item_sends_the_latest_text() {
4682 let mut core = open_fresh_core();
4683 let channel = room("!chan:example.org");
4684 let dm = room("!dm:example.org");
4685 let target = room("!target:example.org");
4686 add_room(&mut core, &channel, false, Some("Announcements"));
4687 add_room(&mut core, &dm, true, None);
4688 add_room(&mut core, &target, false, None);
4689
4690 seed_plain(&mut core, &channel, "$orig:example.org", "@dave:example.org", "m.room.message", serde_json::json!({ "msgtype": "m.text", "body": "typo text" }));
4692 seed_plain(
4693 &mut core,
4694 &channel,
4695 "$edit:example.org",
4696 "@dave:example.org",
4697 "m.room.message",
4698 serde_json::json!({
4699 "msgtype": "m.text",
4700 "body": "* fixed text",
4701 "m.new_content": { "msgtype": "m.text", "body": "fixed text" },
4702 "m.relates_to": { "rel_type": "m.replace", "event_id": "$orig:example.org" },
4703 }),
4704 );
4705 forward(&mut core, &channel, "$orig:example.org", &target).expect("forwardable");
4706 let (_, body) = sent_room_event(&mut core);
4707 assert_eq!(body["body"], "fixed text", "the latest edit's text, not the original and not the `* ` fallback");
4708 assert!(body.get("m.new_content").is_none() && body.get("m.relates_to").is_none());
4709
4710 seed_decrypted(&mut core, &dm, "$secret:example.org", "@bob:example.org", "m.room.message", serde_json::json!({ "msgtype": "m.text", "body": "typo secret" }));
4712 seed_sealed(&mut core, &dm, "$secret-edit:example.org", "@bob:example.org");
4713 let applied = core.timelines.get_mut(&dm).expect("timeline").apply_decrypted_relation(
4714 &event("$secret-edit:example.org"),
4715 UserId::parse("@bob:example.org").expect("valid user id"),
4716 2,
4717 RelatesTo::Replace { event_id: event("$secret:example.org") },
4718 Some(serde_json::json!({ "msgtype": "m.text", "body": "fixed secret" })),
4719 );
4720 assert!(applied, "the decrypted edit folds onto its target");
4721 forward(&mut core, &dm, "$secret:example.org", &target).expect("forwardable");
4722 let (_, body) = sent_room_event(&mut core);
4723 assert_eq!(body, serde_json::json!({ "msgtype": "m.text", "body": "fixed secret", "forwarded": true }));
4724 }
4725
4726 #[test]
4727 fn forward_of_unknown_msgtype_keeps_its_fields() {
4728 let mut core = open_fresh_core();
4729 let dm = room("!dm:example.org");
4730 let target = room("!target:example.org");
4731 add_room(&mut core, &dm, true, None);
4732 add_room(&mut core, &target, false, None);
4733 let card = serde_json::json!({
4734 "msgtype": "com.example.card",
4735 "body": "opaque",
4736 "note": "kept raw",
4737 });
4738 seed_decrypted(&mut core, &dm, "$card:example.org", "@bob:example.org", "m.room.message", card.clone());
4739
4740 forward(&mut core, &dm, "$card:example.org", &target).expect("an unrecognized msgtype is still an m.room.message");
4741
4742 let (path, body) = sent_room_event(&mut core);
4743 assert!(path.contains("/send/m.room.message/"));
4744 let mut expected = card;
4745 expected["forwarded"] = serde_json::Value::Bool(true);
4746 assert_eq!(body, expected, "unrecognized msgtype fields pass through plus the marker");
4747 assert!(matches!(last_item(&core, &target).content, ItemContent::Unknown), "the echo does not invent a card type");
4748 }
4749
4750 #[test]
4751 fn forward_refuses_redacted_sealed_unknown_and_missing() {
4752 let mut core = open_fresh_core();
4753 let dm = room("!dm:example.org");
4754 let target = room("!target:example.org");
4755 add_room(&mut core, &dm, true, None);
4756 add_room(&mut core, &target, false, None);
4757
4758 seed_decrypted(&mut core, &dm, "$redacted:example.org", "@bob:example.org", "m.room.message", serde_json::json!({ "msgtype": "m.text", "body": "gone" }));
4759 core.timelines.get_mut(&dm).expect("timeline").apply_redaction(&event("$redacted:example.org"));
4760 seed_sealed(&mut core, &dm, "$sealed:example.org", "@bob:example.org");
4761 seed_sealed(&mut core, &dm, "$utd:example.org", "@bob:example.org");
4762 core.timelines
4763 .get_mut(&dm)
4764 .expect("timeline")
4765 .set_decrypted(&event("$utd:example.org"), ItemContent::Undecryptable { reason: "MissingSession".to_string() });
4766 seed_plain(&mut core, &dm, "$call:example.org", "@bob:example.org", "m.call.invite", serde_json::json!({ "call_id": "c1" }));
4767
4768 for id in ["$redacted:example.org", "$sealed:example.org", "$utd:example.org", "$call:example.org", "$missing:example.org"] {
4769 match forward(&mut core, &dm, id, &target) {
4770 Err(MessengerError::IntentRefused(reason)) => {
4771 assert!(reason.contains(id), "the refusal names the event {id}: {reason}");
4772 }
4773 other => panic!("forwarding {id} must be refused, got {other:?}"),
4774 }
4775 }
4776 match forward(&mut core, &room("!nowhere:example.org"), "$redacted:example.org", &target) {
4777 Err(MessengerError::IntentRefused(reason)) => assert!(reason.contains("$redacted:example.org"), "{reason}"),
4778 other => panic!("an unknown source room means a missing event, got {other:?}"),
4779 }
4780 match forward(&mut core, &dm, "$sealed:example.org", &room("!nowhere:example.org")) {
4781 Err(MessengerError::IntentRefused(reason)) => assert!(reason.contains("unknown"), "{reason}"),
4782 other => panic!("an unknown target room is refused, got {other:?}"),
4783 }
4784
4785 assert!(core.send_state.is_empty(), "a refused forward queues nothing");
4786 assert!(core.timeline(&target).is_some_and(|timeline| timeline.items().is_empty()), "and leaves no echo");
4787 }
4788}