1use std::{collections::BTreeMap, fmt, sync::Arc};
16
17#[cfg(doc)]
18use ruma::events::AnyTimelineEvent;
19use ruma::{
20 events::{AnyMessageLikeEvent, AnySyncTimelineEvent},
21 push::Action,
22 serde::{
23 AsRefStr, AsStrAsRefStr, DebugAsRefStr, DeserializeFromCowStr, FromString, JsonObject, Raw,
24 SerializeAsRefStr,
25 },
26 DeviceKeyAlgorithm, OwnedDeviceId, OwnedEventId, OwnedUserId,
27};
28use serde::{Deserialize, Serialize};
29use tracing::warn;
30#[cfg(target_family = "wasm")]
31use wasm_bindgen::prelude::*;
32
33use crate::{
34 debug::{DebugRawEvent, DebugStructExt},
35 serde_helpers::extract_bundled_thread_summary,
36};
37
38const AUTHENTICITY_NOT_GUARANTEED: &str =
39 "The authenticity of this encrypted message can't be guaranteed on this device.";
40const UNVERIFIED_IDENTITY: &str = "Encrypted by an unverified user.";
41const VERIFICATION_VIOLATION: &str =
42 "Encrypted by a previously-verified user who is no longer verified.";
43const UNSIGNED_DEVICE: &str = "Encrypted by a device not verified by its owner.";
44const UNKNOWN_DEVICE: &str = "Encrypted by an unknown or deleted device.";
45pub const SENT_IN_CLEAR: &str = "Not encrypted.";
46
47#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
50#[serde(from = "OldVerificationStateHelper")]
51pub enum VerificationState {
52 Verified,
57
58 Unverified(VerificationLevel),
63}
64
65#[derive(Clone, Debug, Deserialize)]
68enum OldVerificationStateHelper {
69 Untrusted,
70 UnknownDevice,
71 #[serde(alias = "Trusted")]
72 Verified,
73 Unverified(VerificationLevel),
74}
75
76impl From<OldVerificationStateHelper> for VerificationState {
77 fn from(value: OldVerificationStateHelper) -> Self {
78 match value {
79 OldVerificationStateHelper::Untrusted => {
82 VerificationState::Unverified(VerificationLevel::UnsignedDevice)
83 }
84 OldVerificationStateHelper::UnknownDevice => {
85 Self::Unverified(VerificationLevel::None(DeviceLinkProblem::MissingDevice))
86 }
87 OldVerificationStateHelper::Verified => Self::Verified,
88 OldVerificationStateHelper::Unverified(l) => Self::Unverified(l),
89 }
90 }
91}
92
93impl VerificationState {
94 pub fn to_shield_state_strict(&self) -> ShieldState {
101 match self {
102 VerificationState::Verified => ShieldState::None,
103 VerificationState::Unverified(level) => match level {
104 VerificationLevel::UnverifiedIdentity
105 | VerificationLevel::VerificationViolation
106 | VerificationLevel::UnsignedDevice => ShieldState::Red {
107 code: ShieldStateCode::UnverifiedIdentity,
108 message: UNVERIFIED_IDENTITY,
109 },
110 VerificationLevel::None(link) => match link {
111 DeviceLinkProblem::MissingDevice => ShieldState::Red {
112 code: ShieldStateCode::UnknownDevice,
113 message: UNKNOWN_DEVICE,
114 },
115 DeviceLinkProblem::InsecureSource => ShieldState::Red {
116 code: ShieldStateCode::AuthenticityNotGuaranteed,
117 message: AUTHENTICITY_NOT_GUARANTEED,
118 },
119 },
120 },
121 }
122 }
123
124 pub fn to_shield_state_lax(&self) -> ShieldState {
132 match self {
133 VerificationState::Verified => ShieldState::None,
134 VerificationState::Unverified(level) => match level {
135 VerificationLevel::UnverifiedIdentity => {
136 ShieldState::None
139 }
140 VerificationLevel::VerificationViolation => {
141 ShieldState::Red {
144 code: ShieldStateCode::VerificationViolation,
145 message: VERIFICATION_VIOLATION,
146 }
147 }
148 VerificationLevel::UnsignedDevice => {
149 ShieldState::Red {
151 code: ShieldStateCode::UnsignedDevice,
152 message: UNSIGNED_DEVICE,
153 }
154 }
155 VerificationLevel::None(link) => match link {
156 DeviceLinkProblem::MissingDevice => {
157 ShieldState::Red {
161 code: ShieldStateCode::UnknownDevice,
162 message: UNKNOWN_DEVICE,
163 }
164 }
165 DeviceLinkProblem::InsecureSource => {
166 ShieldState::Grey {
169 code: ShieldStateCode::AuthenticityNotGuaranteed,
170 message: AUTHENTICITY_NOT_GUARANTEED,
171 }
172 }
173 },
174 },
175 }
176 }
177}
178
179#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
182pub enum VerificationLevel {
183 UnverifiedIdentity,
185
186 #[serde(alias = "PreviouslyVerified")]
189 VerificationViolation,
190
191 UnsignedDevice,
194
195 None(DeviceLinkProblem),
201}
202
203impl fmt::Display for VerificationLevel {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
205 let display = match self {
206 VerificationLevel::UnverifiedIdentity => "The sender's identity was not verified",
207 VerificationLevel::VerificationViolation => {
208 "The sender's identity was previously verified but has changed"
209 }
210 VerificationLevel::UnsignedDevice => {
211 "The sending device was not signed by the user's identity"
212 }
213 VerificationLevel::None(..) => "The sending device is not known",
214 };
215 write!(f, "{display}")
216 }
217}
218
219#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
222pub enum DeviceLinkProblem {
223 MissingDevice,
227 InsecureSource,
230}
231
232#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
235pub enum ShieldState {
236 Red {
239 code: ShieldStateCode,
241 message: &'static str,
243 },
244 Grey {
247 code: ShieldStateCode,
249 message: &'static str,
251 },
252 None,
254}
255
256#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)]
258#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
259#[cfg_attr(target_family = "wasm", wasm_bindgen)]
260pub enum ShieldStateCode {
261 AuthenticityNotGuaranteed,
263 UnknownDevice,
265 UnsignedDevice,
267 UnverifiedIdentity,
269 SentInClear,
271 #[serde(alias = "PreviouslyVerified")]
273 VerificationViolation,
274}
275
276#[derive(Clone, Debug, Deserialize, Serialize)]
278pub enum AlgorithmInfo {
279 MegolmV1AesSha2 {
281 curve25519_key: String,
284 sender_claimed_keys: BTreeMap<DeviceKeyAlgorithm, String>,
288
289 #[serde(default, skip_serializing_if = "Option::is_none")]
292 session_id: Option<String>,
293 },
294
295 OlmV1Curve25519AesSha2 {
297 curve25519_public_key_base64: String,
299 },
300}
301
302#[derive(Clone, Debug, Serialize)]
304pub struct EncryptionInfo {
305 pub sender: OwnedUserId,
308 pub sender_device: Option<OwnedDeviceId>,
311 pub algorithm_info: AlgorithmInfo,
313 pub verification_state: VerificationState,
320}
321
322impl EncryptionInfo {
323 pub fn session_id(&self) -> Option<&str> {
325 if let AlgorithmInfo::MegolmV1AesSha2 { session_id, .. } = &self.algorithm_info {
326 session_id.as_deref()
327 } else {
328 None
329 }
330 }
331}
332
333impl<'de> Deserialize<'de> for EncryptionInfo {
334 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
335 where
336 D: serde::Deserializer<'de>,
337 {
338 #[derive(Deserialize)]
341 struct Helper {
342 pub sender: OwnedUserId,
343 pub sender_device: Option<OwnedDeviceId>,
344 pub algorithm_info: AlgorithmInfo,
345 pub verification_state: VerificationState,
346 #[serde(rename = "session_id")]
347 pub old_session_id: Option<String>,
348 }
349
350 let Helper { sender, sender_device, algorithm_info, verification_state, old_session_id } =
351 Helper::deserialize(deserializer)?;
352
353 let algorithm_info = match algorithm_info {
354 AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, sender_claimed_keys, session_id } => {
355 AlgorithmInfo::MegolmV1AesSha2 {
356 session_id: session_id.or(old_session_id),
358 curve25519_key,
359 sender_claimed_keys,
360 }
361 }
362 other => other,
363 };
364
365 Ok(EncryptionInfo { sender, sender_device, algorithm_info, verification_state })
366 }
367}
368
369#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
380pub struct ThreadSummary {
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub latest_reply: Option<OwnedEventId>,
384
385 pub num_replies: usize,
391}
392
393#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
395pub enum ThreadSummaryStatus {
396 #[default]
398 Unknown,
399 None,
401 Some(ThreadSummary),
403}
404
405impl ThreadSummaryStatus {
406 fn is_unknown(&self) -> bool {
408 matches!(self, ThreadSummaryStatus::Unknown)
409 }
410
411 pub fn summary(&self) -> Option<&ThreadSummary> {
414 match self {
415 ThreadSummaryStatus::Unknown | ThreadSummaryStatus::None => None,
416 ThreadSummaryStatus::Some(thread_summary) => Some(thread_summary),
417 }
418 }
419}
420
421#[derive(Clone, Debug, Serialize)]
444pub struct TimelineEvent {
445 pub kind: TimelineEventKind,
447
448 #[serde(skip_serializing_if = "skip_serialize_push_actions")]
453 push_actions: Option<Vec<Action>>,
454
455 #[serde(default, skip_serializing_if = "ThreadSummaryStatus::is_unknown")]
457 pub thread_summary: ThreadSummaryStatus,
458
459 #[serde(skip)]
464 pub bundled_latest_thread_event: Option<Box<TimelineEvent>>,
465}
466
467fn skip_serialize_push_actions(push_actions: &Option<Vec<Action>>) -> bool {
469 push_actions.as_ref().is_none_or(|v| v.is_empty())
470}
471
472#[cfg(not(feature = "test-send-sync"))]
474unsafe impl Send for TimelineEvent {}
475
476#[cfg(not(feature = "test-send-sync"))]
478unsafe impl Sync for TimelineEvent {}
479
480#[cfg(feature = "test-send-sync")]
481#[test]
482fn test_send_sync_for_sync_timeline_event() {
484 fn assert_send_sync<T: crate::SendOutsideWasm + crate::SyncOutsideWasm>() {}
485
486 assert_send_sync::<TimelineEvent>();
487}
488
489impl TimelineEvent {
490 pub fn from_plaintext(event: Raw<AnySyncTimelineEvent>) -> Self {
495 Self::new(TimelineEventKind::PlainText { event }, None)
496 }
497
498 pub fn from_decrypted(
500 decrypted: DecryptedRoomEvent,
501 push_actions: Option<Vec<Action>>,
502 ) -> Self {
503 Self::new(TimelineEventKind::Decrypted(decrypted), push_actions)
504 }
505
506 pub fn from_utd(event: Raw<AnySyncTimelineEvent>, utd_info: UnableToDecryptInfo) -> Self {
509 Self::new(TimelineEventKind::UnableToDecrypt { event, utd_info }, None)
510 }
511
512 fn new(kind: TimelineEventKind, push_actions: Option<Vec<Action>>) -> Self {
515 let (thread_summary, latest_thread_event) = extract_bundled_thread_summary(kind.raw());
516 let bundled_latest_thread_event =
517 Self::from_bundled_latest_event(&kind, latest_thread_event);
518 Self { kind, push_actions, thread_summary, bundled_latest_thread_event }
519 }
520
521 fn from_bundled_latest_event(
525 this: &TimelineEventKind,
526 latest_event: Option<Raw<AnyMessageLikeEvent>>,
527 ) -> Option<Box<Self>> {
528 let latest_event = latest_event?;
529
530 match this {
531 TimelineEventKind::Decrypted(decrypted) => {
532 if let Some(unsigned_decryption_result) =
533 decrypted.unsigned_encryption_info.as_ref().and_then(|unsigned_map| {
534 unsigned_map.get(&UnsignedEventLocation::RelationsThreadLatestEvent)
535 })
536 {
537 match unsigned_decryption_result {
538 UnsignedDecryptionResult::Decrypted(encryption_info) => {
539 return Some(Box::new(TimelineEvent::from_decrypted(
542 DecryptedRoomEvent {
543 event: latest_event,
544 encryption_info: encryption_info.clone(),
545 unsigned_encryption_info: None,
549 },
550 None,
551 )));
552 }
553
554 UnsignedDecryptionResult::UnableToDecrypt(utd_info) => {
555 return Some(Box::new(TimelineEvent::from_utd(
557 latest_event.cast(),
558 utd_info.clone(),
559 )));
560 }
561 }
562 }
563 }
564
565 TimelineEventKind::UnableToDecrypt { .. } | TimelineEventKind::PlainText { .. } => {
566 }
568 }
569
570 let deserialized = match latest_event.deserialize() {
571 Ok(ev) => ev,
572 Err(err) => {
573 warn!("couldn't deserialize bundled latest thread event: {err}");
574 return None;
575 }
576 };
577
578 match deserialized {
579 AnyMessageLikeEvent::RoomEncrypted(_) => {
580 Some(Box::new(TimelineEvent::from_utd(
584 latest_event.cast(),
585 UnableToDecryptInfo {
586 session_id: None,
587 reason: UnableToDecryptReason::Unknown,
588 },
589 )))
590 }
591
592 _ => Some(Box::new(TimelineEvent::from_plaintext(latest_event.cast()))),
593 }
594 }
595
596 pub fn push_actions(&self) -> Option<&[Action]> {
601 self.push_actions.as_deref()
602 }
603
604 pub fn set_push_actions(&mut self, push_actions: Vec<Action>) {
606 self.push_actions = Some(push_actions);
607 }
608
609 pub fn event_id(&self) -> Option<OwnedEventId> {
612 self.kind.event_id()
613 }
614
615 pub fn raw(&self) -> &Raw<AnySyncTimelineEvent> {
618 self.kind.raw()
619 }
620
621 pub fn replace_raw(&mut self, replacement: Raw<AnyMessageLikeEvent>) {
623 match &mut self.kind {
624 TimelineEventKind::Decrypted(decrypted) => decrypted.event = replacement,
625 TimelineEventKind::UnableToDecrypt { event, .. }
626 | TimelineEventKind::PlainText { event } => {
627 *event = replacement.cast();
630 }
631 }
632 }
633
634 pub fn encryption_info(&self) -> Option<&Arc<EncryptionInfo>> {
637 self.kind.encryption_info()
638 }
639
640 pub fn into_raw(self) -> Raw<AnySyncTimelineEvent> {
643 self.kind.into_raw()
644 }
645}
646
647impl<'de> Deserialize<'de> for TimelineEvent {
648 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
656 where
657 D: serde::Deserializer<'de>,
658 {
659 use serde_json::{Map, Value};
660
661 let value = Map::<String, Value>::deserialize(deserializer)?;
663
664 if value.contains_key("event") {
666 let v0: SyncTimelineEventDeserializationHelperV0 =
667 serde_json::from_value(Value::Object(value)).map_err(|e| {
668 serde::de::Error::custom(format!(
669 "Unable to deserialize V0-format TimelineEvent: {e}",
670 ))
671 })?;
672 Ok(v0.into())
673 }
674 else {
676 let v1: SyncTimelineEventDeserializationHelperV1 =
677 serde_json::from_value(Value::Object(value)).map_err(|e| {
678 serde::de::Error::custom(format!(
679 "Unable to deserialize V1-format TimelineEvent: {e}",
680 ))
681 })?;
682 Ok(v1.into())
683 }
684 }
685}
686
687#[derive(Clone, Serialize, Deserialize)]
689pub enum TimelineEventKind {
690 Decrypted(DecryptedRoomEvent),
692
693 UnableToDecrypt {
695 event: Raw<AnySyncTimelineEvent>,
699
700 utd_info: UnableToDecryptInfo,
702 },
703
704 PlainText {
706 event: Raw<AnySyncTimelineEvent>,
710 },
711}
712
713impl TimelineEventKind {
714 pub fn raw(&self) -> &Raw<AnySyncTimelineEvent> {
717 match self {
718 TimelineEventKind::Decrypted(d) => d.event.cast_ref(),
724 TimelineEventKind::UnableToDecrypt { event, .. } => event.cast_ref(),
725 TimelineEventKind::PlainText { event } => event,
726 }
727 }
728
729 pub fn event_id(&self) -> Option<OwnedEventId> {
732 self.raw().get_field::<OwnedEventId>("event_id").ok().flatten()
733 }
734
735 pub fn encryption_info(&self) -> Option<&Arc<EncryptionInfo>> {
738 match self {
739 TimelineEventKind::Decrypted(d) => Some(&d.encryption_info),
740 TimelineEventKind::UnableToDecrypt { .. } | TimelineEventKind::PlainText { .. } => None,
741 }
742 }
743
744 pub fn unsigned_encryption_map(
747 &self,
748 ) -> Option<&BTreeMap<UnsignedEventLocation, UnsignedDecryptionResult>> {
749 match self {
750 TimelineEventKind::Decrypted(d) => d.unsigned_encryption_info.as_ref(),
751 TimelineEventKind::UnableToDecrypt { .. } | TimelineEventKind::PlainText { .. } => None,
752 }
753 }
754
755 pub fn into_raw(self) -> Raw<AnySyncTimelineEvent> {
758 match self {
759 TimelineEventKind::Decrypted(d) => d.event.cast(),
765 TimelineEventKind::UnableToDecrypt { event, .. } => event.cast(),
766 TimelineEventKind::PlainText { event } => event,
767 }
768 }
769
770 pub fn session_id(&self) -> Option<&str> {
773 match self {
774 TimelineEventKind::Decrypted(decrypted_room_event) => {
775 decrypted_room_event.encryption_info.session_id()
776 }
777 TimelineEventKind::UnableToDecrypt { utd_info, .. } => utd_info.session_id.as_deref(),
778 TimelineEventKind::PlainText { .. } => None,
779 }
780 }
781}
782
783#[cfg(not(tarpaulin_include))]
784impl fmt::Debug for TimelineEventKind {
785 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
786 match &self {
787 Self::PlainText { event } => f
788 .debug_struct("TimelineEventDecryptionResult::PlainText")
789 .field("event", &DebugRawEvent(event))
790 .finish(),
791
792 Self::UnableToDecrypt { event, utd_info } => f
793 .debug_struct("TimelineEventDecryptionResult::UnableToDecrypt")
794 .field("event", &DebugRawEvent(event))
795 .field("utd_info", &utd_info)
796 .finish(),
797
798 Self::Decrypted(decrypted) => {
799 f.debug_tuple("TimelineEventDecryptionResult::Decrypted").field(decrypted).finish()
800 }
801 }
802 }
803}
804
805#[derive(Clone, Serialize, Deserialize)]
806pub struct DecryptedRoomEvent {
808 pub event: Raw<AnyMessageLikeEvent>,
815
816 pub encryption_info: Arc<EncryptionInfo>,
818
819 #[serde(skip_serializing_if = "Option::is_none")]
824 pub unsigned_encryption_info: Option<BTreeMap<UnsignedEventLocation, UnsignedDecryptionResult>>,
825}
826
827#[cfg(not(tarpaulin_include))]
828impl fmt::Debug for DecryptedRoomEvent {
829 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
830 let DecryptedRoomEvent { event, encryption_info, unsigned_encryption_info } = self;
831
832 f.debug_struct("DecryptedRoomEvent")
833 .field("event", &DebugRawEvent(event))
834 .field("encryption_info", encryption_info)
835 .maybe_field("unsigned_encryption_info", unsigned_encryption_info)
836 .finish()
837 }
838}
839
840#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
842pub enum UnsignedEventLocation {
843 RelationsReplace,
846 RelationsThreadLatestEvent,
849}
850
851impl UnsignedEventLocation {
852 pub fn find_mut<'a>(&self, unsigned: &'a mut JsonObject) -> Option<&'a mut serde_json::Value> {
859 let relations = unsigned.get_mut("m.relations")?.as_object_mut()?;
860
861 match self {
862 Self::RelationsReplace => relations.get_mut("m.replace"),
863 Self::RelationsThreadLatestEvent => {
864 relations.get_mut("m.thread")?.as_object_mut()?.get_mut("latest_event")
865 }
866 }
867 }
868}
869
870#[derive(Debug, Clone, Serialize, Deserialize)]
872pub enum UnsignedDecryptionResult {
873 Decrypted(Arc<EncryptionInfo>),
875 UnableToDecrypt(UnableToDecryptInfo),
877}
878
879impl UnsignedDecryptionResult {
880 pub fn encryption_info(&self) -> Option<&Arc<EncryptionInfo>> {
883 match self {
884 Self::Decrypted(info) => Some(info),
885 Self::UnableToDecrypt(_) => None,
886 }
887 }
888}
889
890#[derive(Debug, Clone, Serialize, Deserialize)]
892pub struct UnableToDecryptInfo {
893 #[serde(skip_serializing_if = "Option::is_none")]
896 pub session_id: Option<String>,
897
898 #[serde(default = "unknown_utd_reason", deserialize_with = "deserialize_utd_reason")]
900 pub reason: UnableToDecryptReason,
901}
902
903fn unknown_utd_reason() -> UnableToDecryptReason {
904 UnableToDecryptReason::Unknown
905}
906
907pub fn deserialize_utd_reason<'de, D>(d: D) -> Result<UnableToDecryptReason, D::Error>
910where
911 D: serde::Deserializer<'de>,
912{
913 let v: serde_json::Value = Deserialize::deserialize(d)?;
915 if v.as_str().is_some_and(|s| s == "MissingMegolmSession") {
918 return Ok(UnableToDecryptReason::MissingMegolmSession { withheld_code: None });
919 }
920 serde_json::from_value::<UnableToDecryptReason>(v).map_err(serde::de::Error::custom)
923}
924
925#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
927pub enum UnableToDecryptReason {
928 #[doc(hidden)]
931 Unknown,
932
933 MalformedEncryptedEvent,
937
938 MissingMegolmSession {
941 withheld_code: Option<WithheldCode>,
944 },
945
946 UnknownMegolmMessageIndex,
949
950 MegolmDecryptionFailure,
957
958 PayloadDeserializationFailure,
960
961 MismatchedIdentityKeys,
965
966 SenderIdentityNotTrusted(VerificationLevel),
970}
971
972impl UnableToDecryptReason {
973 pub fn is_missing_room_key(&self) -> bool {
976 matches!(
979 self,
980 Self::MissingMegolmSession { withheld_code: None } | Self::UnknownMegolmMessageIndex
981 )
982 }
983}
984
985#[derive(
989 Clone,
990 PartialEq,
991 Eq,
992 Hash,
993 AsStrAsRefStr,
994 AsRefStr,
995 FromString,
996 DebugAsRefStr,
997 SerializeAsRefStr,
998 DeserializeFromCowStr,
999)]
1000pub enum WithheldCode {
1001 #[ruma_enum(rename = "m.blacklisted")]
1003 Blacklisted,
1004
1005 #[ruma_enum(rename = "m.unverified")]
1007 Unverified,
1008
1009 #[ruma_enum(rename = "m.unauthorised")]
1013 Unauthorised,
1014
1015 #[ruma_enum(rename = "m.unavailable")]
1018 Unavailable,
1019
1020 #[ruma_enum(rename = "m.no_olm")]
1024 NoOlm,
1025
1026 #[doc(hidden)]
1027 _Custom(PrivOwnedStr),
1028}
1029
1030impl fmt::Display for WithheldCode {
1031 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1032 let string = match self {
1033 WithheldCode::Blacklisted => "The sender has blocked you.",
1034 WithheldCode::Unverified => "The sender has disabled encrypting to unverified devices.",
1035 WithheldCode::Unauthorised => "You are not authorised to read the message.",
1036 WithheldCode::Unavailable => "The requested key was not found.",
1037 WithheldCode::NoOlm => "Unable to establish a secure channel.",
1038 _ => self.as_str(),
1039 };
1040
1041 f.write_str(string)
1042 }
1043}
1044
1045#[doc(hidden)]
1049#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1050pub struct PrivOwnedStr(pub Box<str>);
1051
1052#[cfg(not(tarpaulin_include))]
1053impl fmt::Debug for PrivOwnedStr {
1054 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1055 self.0.fmt(f)
1056 }
1057}
1058
1059#[derive(Debug, Deserialize)]
1064struct SyncTimelineEventDeserializationHelperV1 {
1065 kind: TimelineEventKind,
1067
1068 #[serde(default)]
1070 push_actions: Vec<Action>,
1071
1072 #[serde(default)]
1074 thread_summary: ThreadSummaryStatus,
1075}
1076
1077impl From<SyncTimelineEventDeserializationHelperV1> for TimelineEvent {
1078 fn from(value: SyncTimelineEventDeserializationHelperV1) -> Self {
1079 let SyncTimelineEventDeserializationHelperV1 { kind, push_actions, thread_summary } = value;
1080 TimelineEvent {
1081 kind,
1082 push_actions: Some(push_actions),
1083 thread_summary,
1084 bundled_latest_thread_event: None,
1086 }
1087 }
1088}
1089
1090#[derive(Deserialize)]
1092struct SyncTimelineEventDeserializationHelperV0 {
1093 event: Raw<AnySyncTimelineEvent>,
1095
1096 encryption_info: Option<Arc<EncryptionInfo>>,
1100
1101 #[serde(default)]
1103 push_actions: Vec<Action>,
1104
1105 unsigned_encryption_info: Option<BTreeMap<UnsignedEventLocation, UnsignedDecryptionResult>>,
1110}
1111
1112impl From<SyncTimelineEventDeserializationHelperV0> for TimelineEvent {
1113 fn from(value: SyncTimelineEventDeserializationHelperV0) -> Self {
1114 let SyncTimelineEventDeserializationHelperV0 {
1115 event,
1116 encryption_info,
1117 push_actions,
1118 unsigned_encryption_info,
1119 } = value;
1120
1121 let kind = match encryption_info {
1122 Some(encryption_info) => {
1123 TimelineEventKind::Decrypted(DecryptedRoomEvent {
1124 event: event.cast(),
1131 encryption_info,
1132 unsigned_encryption_info,
1133 })
1134 }
1135
1136 None => TimelineEventKind::PlainText { event },
1137 };
1138
1139 TimelineEvent {
1140 kind,
1141 push_actions: Some(push_actions),
1142 thread_summary: ThreadSummaryStatus::Unknown,
1144 bundled_latest_thread_event: None,
1146 }
1147 }
1148}
1149
1150#[cfg(test)]
1151mod tests {
1152 use std::{collections::BTreeMap, sync::Arc};
1153
1154 use assert_matches::assert_matches;
1155 use assert_matches2::assert_let;
1156 use insta::{assert_json_snapshot, with_settings};
1157 use ruma::{
1158 device_id, event_id, events::room::message::RoomMessageEventContent, serde::Raw, user_id,
1159 DeviceKeyAlgorithm,
1160 };
1161 use serde::Deserialize;
1162 use serde_json::json;
1163
1164 use super::{
1165 AlgorithmInfo, DecryptedRoomEvent, DeviceLinkProblem, EncryptionInfo, ShieldState,
1166 ShieldStateCode, TimelineEvent, TimelineEventKind, UnableToDecryptInfo,
1167 UnableToDecryptReason, UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel,
1168 VerificationState, WithheldCode,
1169 };
1170 use crate::deserialized_responses::{ThreadSummary, ThreadSummaryStatus};
1171
1172 fn example_event() -> serde_json::Value {
1173 json!({
1174 "content": RoomMessageEventContent::text_plain("secret"),
1175 "type": "m.room.message",
1176 "event_id": "$xxxxx:example.org",
1177 "room_id": "!someroom:example.com",
1178 "origin_server_ts": 2189,
1179 "sender": "@carl:example.com",
1180 })
1181 }
1182
1183 #[test]
1184 fn sync_timeline_debug_content() {
1185 let room_event = TimelineEvent::from_plaintext(Raw::new(&example_event()).unwrap().cast());
1186 let debug_s = format!("{room_event:?}");
1187 assert!(
1188 !debug_s.contains("secret"),
1189 "Debug representation contains event content!\n{debug_s}"
1190 );
1191 }
1192
1193 #[test]
1194 fn old_verification_state_to_new_migration() {
1195 #[derive(Deserialize)]
1196 struct State {
1197 state: VerificationState,
1198 }
1199
1200 let state = json!({
1201 "state": "Trusted",
1202 });
1203 let deserialized: State =
1204 serde_json::from_value(state).expect("We can deserialize the old trusted value");
1205 assert_eq!(deserialized.state, VerificationState::Verified);
1206
1207 let state = json!({
1208 "state": "UnknownDevice",
1209 });
1210
1211 let deserialized: State =
1212 serde_json::from_value(state).expect("We can deserialize the old unknown device value");
1213
1214 assert_eq!(
1215 deserialized.state,
1216 VerificationState::Unverified(VerificationLevel::None(
1217 DeviceLinkProblem::MissingDevice
1218 ))
1219 );
1220
1221 let state = json!({
1222 "state": "Untrusted",
1223 });
1224 let deserialized: State =
1225 serde_json::from_value(state).expect("We can deserialize the old trusted value");
1226
1227 assert_eq!(
1228 deserialized.state,
1229 VerificationState::Unverified(VerificationLevel::UnsignedDevice)
1230 );
1231 }
1232
1233 #[test]
1234 fn test_verification_level_deserializes() {
1235 #[derive(Deserialize)]
1237 struct Container {
1238 verification_level: VerificationLevel,
1239 }
1240 let container = json!({ "verification_level": "VerificationViolation" });
1241
1242 let deserialized: Container = serde_json::from_value(container)
1244 .expect("We can deserialize the old PreviouslyVerified value");
1245
1246 assert_eq!(deserialized.verification_level, VerificationLevel::VerificationViolation);
1248 }
1249
1250 #[test]
1251 fn test_verification_level_deserializes_from_old_previously_verified_value() {
1252 #[derive(Deserialize)]
1254 struct Container {
1255 verification_level: VerificationLevel,
1256 }
1257 let container = json!({ "verification_level": "PreviouslyVerified" });
1258
1259 let deserialized: Container = serde_json::from_value(container)
1261 .expect("We can deserialize the old PreviouslyVerified value");
1262
1263 assert_eq!(deserialized.verification_level, VerificationLevel::VerificationViolation);
1265 }
1266
1267 #[test]
1268 fn test_shield_state_code_deserializes() {
1269 #[derive(Deserialize)]
1271 struct Container {
1272 shield_state_code: ShieldStateCode,
1273 }
1274 let container = json!({ "shield_state_code": "VerificationViolation" });
1275
1276 let deserialized: Container = serde_json::from_value(container)
1278 .expect("We can deserialize the old PreviouslyVerified value");
1279
1280 assert_eq!(deserialized.shield_state_code, ShieldStateCode::VerificationViolation);
1282 }
1283
1284 #[test]
1285 fn test_shield_state_code_deserializes_from_old_previously_verified_value() {
1286 #[derive(Deserialize)]
1288 struct Container {
1289 shield_state_code: ShieldStateCode,
1290 }
1291 let container = json!({ "shield_state_code": "PreviouslyVerified" });
1292
1293 let deserialized: Container = serde_json::from_value(container)
1295 .expect("We can deserialize the old PreviouslyVerified value");
1296
1297 assert_eq!(deserialized.shield_state_code, ShieldStateCode::VerificationViolation);
1299 }
1300
1301 #[test]
1302 fn sync_timeline_event_serialisation() {
1303 let room_event = TimelineEvent {
1304 kind: TimelineEventKind::Decrypted(DecryptedRoomEvent {
1305 event: Raw::new(&example_event()).unwrap().cast(),
1306 encryption_info: Arc::new(EncryptionInfo {
1307 sender: user_id!("@sender:example.com").to_owned(),
1308 sender_device: None,
1309 algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
1310 curve25519_key: "xxx".to_owned(),
1311 sender_claimed_keys: Default::default(),
1312 session_id: Some("xyz".to_owned()),
1313 },
1314 verification_state: VerificationState::Verified,
1315 }),
1316 unsigned_encryption_info: Some(BTreeMap::from([(
1317 UnsignedEventLocation::RelationsReplace,
1318 UnsignedDecryptionResult::UnableToDecrypt(UnableToDecryptInfo {
1319 session_id: Some("xyz".to_owned()),
1320 reason: UnableToDecryptReason::MalformedEncryptedEvent,
1321 }),
1322 )])),
1323 }),
1324 push_actions: Default::default(),
1325 thread_summary: ThreadSummaryStatus::Unknown,
1326 bundled_latest_thread_event: None,
1327 };
1328
1329 let serialized = serde_json::to_value(&room_event).unwrap();
1330
1331 assert_eq!(
1333 serialized,
1334 json!({
1335 "kind": {
1336 "Decrypted": {
1337 "event": {
1338 "content": {"body": "secret", "msgtype": "m.text"},
1339 "event_id": "$xxxxx:example.org",
1340 "origin_server_ts": 2189,
1341 "room_id": "!someroom:example.com",
1342 "sender": "@carl:example.com",
1343 "type": "m.room.message",
1344 },
1345 "encryption_info": {
1346 "sender": "@sender:example.com",
1347 "sender_device": null,
1348 "algorithm_info": {
1349 "MegolmV1AesSha2": {
1350 "curve25519_key": "xxx",
1351 "sender_claimed_keys": {},
1352 "session_id": "xyz",
1353 }
1354 },
1355 "verification_state": "Verified",
1356 },
1357 "unsigned_encryption_info": {
1358 "RelationsReplace": {"UnableToDecrypt": {
1359 "session_id": "xyz",
1360 "reason": "MalformedEncryptedEvent",
1361 }}
1362 }
1363 }
1364 }
1365 })
1366 );
1367
1368 let event: TimelineEvent = serde_json::from_value(serialized).unwrap();
1370 assert_eq!(event.event_id(), Some(event_id!("$xxxxx:example.org").to_owned()));
1371 assert_matches!(
1372 event.encryption_info().unwrap().algorithm_info,
1373 AlgorithmInfo::MegolmV1AesSha2 { .. }
1374 );
1375
1376 let serialized = json!({
1378 "event": {
1379 "content": {"body": "secret", "msgtype": "m.text"},
1380 "event_id": "$xxxxx:example.org",
1381 "origin_server_ts": 2189,
1382 "room_id": "!someroom:example.com",
1383 "sender": "@carl:example.com",
1384 "type": "m.room.message",
1385 },
1386 "encryption_info": {
1387 "sender": "@sender:example.com",
1388 "sender_device": null,
1389 "algorithm_info": {
1390 "MegolmV1AesSha2": {
1391 "curve25519_key": "xxx",
1392 "sender_claimed_keys": {}
1393 }
1394 },
1395 "verification_state": "Verified",
1396 },
1397 });
1398 let event: TimelineEvent = serde_json::from_value(serialized).unwrap();
1399 assert_eq!(event.event_id(), Some(event_id!("$xxxxx:example.org").to_owned()));
1400 assert_matches!(
1401 event.encryption_info().unwrap().algorithm_info,
1402 AlgorithmInfo::MegolmV1AesSha2 { session_id: None, .. }
1403 );
1404
1405 let serialized = json!({
1408 "event": {
1409 "content": {"body": "secret", "msgtype": "m.text"},
1410 "event_id": "$xxxxx:example.org",
1411 "origin_server_ts": 2189,
1412 "room_id": "!someroom:example.com",
1413 "sender": "@carl:example.com",
1414 "type": "m.room.message",
1415 },
1416 "encryption_info": {
1417 "sender": "@sender:example.com",
1418 "sender_device": null,
1419 "algorithm_info": {
1420 "MegolmV1AesSha2": {
1421 "curve25519_key": "xxx",
1422 "sender_claimed_keys": {}
1423 }
1424 },
1425 "verification_state": "Verified",
1426 },
1427 "unsigned_encryption_info": {
1428 "RelationsReplace": {"UnableToDecrypt": {"session_id": "xyz"}}
1429 }
1430 });
1431 let event: TimelineEvent = serde_json::from_value(serialized).unwrap();
1432 assert_eq!(event.event_id(), Some(event_id!("$xxxxx:example.org").to_owned()));
1433 assert_matches!(
1434 event.encryption_info().unwrap().algorithm_info,
1435 AlgorithmInfo::MegolmV1AesSha2 { .. }
1436 );
1437 assert_matches!(event.kind, TimelineEventKind::Decrypted(decrypted) => {
1438 assert_matches!(decrypted.unsigned_encryption_info, Some(map) => {
1439 assert_eq!(map.len(), 1);
1440 let (location, result) = map.into_iter().next().unwrap();
1441 assert_eq!(location, UnsignedEventLocation::RelationsReplace);
1442 assert_matches!(result, UnsignedDecryptionResult::UnableToDecrypt(utd_info) => {
1443 assert_eq!(utd_info.session_id, Some("xyz".to_owned()));
1444 assert_eq!(utd_info.reason, UnableToDecryptReason::Unknown);
1445 })
1446 });
1447 });
1448 }
1449
1450 #[test]
1451 fn test_creating_or_deserializing_an_event_extracts_summary() {
1452 let event = json!({
1453 "event_id": "$eid:example.com",
1454 "type": "m.room.message",
1455 "sender": "@alice:example.com",
1456 "origin_server_ts": 42,
1457 "content": {
1458 "body": "Hello, world!",
1459 },
1460 "unsigned": {
1461 "m.relations": {
1462 "m.thread": {
1463 "latest_event": {
1464 "event_id": "$latest_event:example.com",
1465 "type": "m.room.message",
1466 "sender": "@bob:example.com",
1467 "origin_server_ts": 42,
1468 "content": {
1469 "body": "Hello to you too!",
1470 }
1471 },
1472 "count": 2,
1473 "current_user_participated": true,
1474 }
1475 }
1476 }
1477 });
1478
1479 let raw = Raw::new(&event).unwrap().cast();
1480
1481 let timeline_event = TimelineEvent::from_plaintext(raw);
1484 assert_matches!(timeline_event.thread_summary, ThreadSummaryStatus::Some(ThreadSummary { num_replies, latest_reply }) => {
1485 assert_eq!(num_replies, 2);
1486 assert_eq!(latest_reply.as_deref(), Some(event_id!("$latest_event:example.com")));
1487 });
1488
1489 let serialized_timeline_item = json!({
1492 "kind": {
1493 "PlainText": {
1494 "event": event
1495 }
1496 }
1497 });
1498
1499 let timeline_event: TimelineEvent =
1500 serde_json::from_value(serialized_timeline_item).unwrap();
1501 assert_matches!(timeline_event.thread_summary, ThreadSummaryStatus::Unknown);
1502 }
1503
1504 #[test]
1505 fn sync_timeline_event_deserialisation_migration_for_withheld() {
1506 let serialized = json!({
1523 "kind": {
1524 "UnableToDecrypt": {
1525 "event": {
1526 "content": {
1527 "algorithm": "m.megolm.v1.aes-sha2",
1528 "ciphertext": "AwgAEoABzL1JYhqhjW9jXrlT3M6H8mJ4qffYtOQOnPuAPNxsuG20oiD/Fnpv6jnQGhU6YbV9pNM+1mRnTvxW3CbWOPjLKqCWTJTc7Q0vDEVtYePg38ncXNcwMmfhgnNAoW9S7vNs8C003x3yUl6NeZ8bH+ci870BZL+kWM/lMl10tn6U7snNmSjnE3ckvRdO+11/R4//5VzFQpZdf4j036lNSls/WIiI67Fk9iFpinz9xdRVWJFVdrAiPFwb8L5xRZ8aX+e2JDMlc1eW8gk",
1529 "device_id": "SKCGPNUWAU",
1530 "sender_key": "Gim/c7uQdSXyrrUbmUOrBT6sMC0gO7QSLmOK6B7NOm0",
1531 "session_id": "hgLyeSqXfb8vc5AjQLsg6TSHVu0HJ7HZ4B6jgMvxkrs"
1532 },
1533 "event_id": "$xxxxx:example.org",
1534 "origin_server_ts": 2189,
1535 "room_id": "!someroom:example.com",
1536 "sender": "@carl:example.com",
1537 "type": "m.room.message"
1538 },
1539 "utd_info": {
1540 "reason": "MissingMegolmSession",
1541 "session_id": "session000"
1542 }
1543 }
1544 }
1545 });
1546
1547 let result = serde_json::from_value(serialized);
1548 assert!(result.is_ok());
1549
1550 let event: TimelineEvent = result.unwrap();
1552 assert_matches!(
1553 event.kind,
1554 TimelineEventKind::UnableToDecrypt { utd_info, .. }=> {
1555 assert_matches!(
1556 utd_info.reason,
1557 UnableToDecryptReason::MissingMegolmSession { withheld_code: None }
1558 );
1559 }
1560 )
1561 }
1562
1563 #[test]
1564 fn unable_to_decrypt_info_migration_for_withheld() {
1565 let old_format = json!({
1566 "reason": "MissingMegolmSession",
1567 "session_id": "session000"
1568 });
1569
1570 let deserialized = serde_json::from_value::<UnableToDecryptInfo>(old_format).unwrap();
1571 let session_id = Some("session000".to_owned());
1572
1573 assert_eq!(deserialized.session_id, session_id);
1574 assert_eq!(
1575 deserialized.reason,
1576 UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
1577 );
1578
1579 let new_format = json!({
1580 "session_id": "session000",
1581 "reason": {
1582 "MissingMegolmSession": {
1583 "withheld_code": null
1584 }
1585 }
1586 });
1587
1588 let deserialized = serde_json::from_value::<UnableToDecryptInfo>(new_format).unwrap();
1589
1590 assert_eq!(
1591 deserialized.reason,
1592 UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
1593 );
1594 assert_eq!(deserialized.session_id, session_id);
1595 }
1596
1597 #[test]
1598 fn unable_to_decrypt_reason_is_missing_room_key() {
1599 let reason = UnableToDecryptReason::MissingMegolmSession { withheld_code: None };
1600 assert!(reason.is_missing_room_key());
1601
1602 let reason = UnableToDecryptReason::MissingMegolmSession {
1603 withheld_code: Some(WithheldCode::Blacklisted),
1604 };
1605 assert!(!reason.is_missing_room_key());
1606
1607 let reason = UnableToDecryptReason::UnknownMegolmMessageIndex;
1608 assert!(reason.is_missing_room_key());
1609 }
1610
1611 #[test]
1612 fn snapshot_test_verification_level() {
1613 with_settings!({ prepend_module_to_snapshot => false }, {
1614 assert_json_snapshot!(VerificationLevel::VerificationViolation);
1615 assert_json_snapshot!(VerificationLevel::UnsignedDevice);
1616 assert_json_snapshot!(VerificationLevel::None(DeviceLinkProblem::InsecureSource));
1617 assert_json_snapshot!(VerificationLevel::None(DeviceLinkProblem::MissingDevice));
1618 assert_json_snapshot!(VerificationLevel::UnverifiedIdentity);
1619 });
1620 }
1621
1622 #[test]
1623 fn snapshot_test_verification_states() {
1624 with_settings!({ prepend_module_to_snapshot => false }, {
1625 assert_json_snapshot!(VerificationState::Unverified(VerificationLevel::UnsignedDevice));
1626 assert_json_snapshot!(VerificationState::Unverified(
1627 VerificationLevel::VerificationViolation
1628 ));
1629 assert_json_snapshot!(VerificationState::Unverified(VerificationLevel::None(
1630 DeviceLinkProblem::InsecureSource,
1631 )));
1632 assert_json_snapshot!(VerificationState::Unverified(VerificationLevel::None(
1633 DeviceLinkProblem::MissingDevice,
1634 )));
1635 assert_json_snapshot!(VerificationState::Verified);
1636 });
1637 }
1638
1639 #[test]
1640 fn snapshot_test_shield_states() {
1641 with_settings!({ prepend_module_to_snapshot => false }, {
1642 assert_json_snapshot!(ShieldState::None);
1643 assert_json_snapshot!(ShieldState::Red {
1644 code: ShieldStateCode::UnverifiedIdentity,
1645 message: "a message"
1646 });
1647 assert_json_snapshot!(ShieldState::Grey {
1648 code: ShieldStateCode::AuthenticityNotGuaranteed,
1649 message: "authenticity of this message cannot be guaranteed",
1650 });
1651 });
1652 }
1653
1654 #[test]
1655 fn snapshot_test_shield_codes() {
1656 with_settings!({ prepend_module_to_snapshot => false }, {
1657 assert_json_snapshot!(ShieldStateCode::AuthenticityNotGuaranteed);
1658 assert_json_snapshot!(ShieldStateCode::UnknownDevice);
1659 assert_json_snapshot!(ShieldStateCode::UnsignedDevice);
1660 assert_json_snapshot!(ShieldStateCode::UnverifiedIdentity);
1661 assert_json_snapshot!(ShieldStateCode::SentInClear);
1662 assert_json_snapshot!(ShieldStateCode::VerificationViolation);
1663 });
1664 }
1665
1666 #[test]
1667 fn snapshot_test_algorithm_info() {
1668 let mut map = BTreeMap::new();
1669 map.insert(DeviceKeyAlgorithm::Curve25519, "claimedclaimedcurve25519".to_owned());
1670 map.insert(DeviceKeyAlgorithm::Ed25519, "claimedclaimeded25519".to_owned());
1671 let info = AlgorithmInfo::MegolmV1AesSha2 {
1672 curve25519_key: "curvecurvecurve".into(),
1673 sender_claimed_keys: BTreeMap::from([
1674 (DeviceKeyAlgorithm::Curve25519, "claimedclaimedcurve25519".to_owned()),
1675 (DeviceKeyAlgorithm::Ed25519, "claimedclaimeded25519".to_owned()),
1676 ]),
1677 session_id: None,
1678 };
1679
1680 with_settings!({ prepend_module_to_snapshot => false }, {
1681 assert_json_snapshot!(info)
1682 });
1683 }
1684
1685 #[test]
1686 fn test_encryption_info_migration() {
1687 let old_format = json!({
1690 "sender": "@alice:localhost",
1691 "sender_device": "ABCDEFGH",
1692 "algorithm_info": {
1693 "MegolmV1AesSha2": {
1694 "curve25519_key": "curvecurvecurve",
1695 "sender_claimed_keys": {}
1696 }
1697 },
1698 "verification_state": "Verified",
1699 "session_id": "mysessionid76"
1700 });
1701
1702 let deserialized = serde_json::from_value::<EncryptionInfo>(old_format).unwrap();
1703 let expected_session_id = Some("mysessionid76".to_owned());
1704
1705 assert_let!(
1706 AlgorithmInfo::MegolmV1AesSha2 { session_id, .. } = deserialized.algorithm_info.clone()
1707 );
1708 assert_eq!(session_id, expected_session_id);
1709
1710 assert_json_snapshot!(deserialized);
1711 }
1712
1713 #[test]
1714 fn snapshot_test_encryption_info() {
1715 let info = EncryptionInfo {
1716 sender: user_id!("@alice:localhost").to_owned(),
1717 sender_device: Some(device_id!("ABCDEFGH").to_owned()),
1718 algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
1719 curve25519_key: "curvecurvecurve".into(),
1720 sender_claimed_keys: Default::default(),
1721 session_id: Some("mysessionid76".to_owned()),
1722 },
1723 verification_state: VerificationState::Verified,
1724 };
1725
1726 with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
1727 assert_json_snapshot!(info)
1728 })
1729 }
1730
1731 #[test]
1732 fn snapshot_test_sync_timeline_event() {
1733 let room_event = TimelineEvent {
1734 kind: TimelineEventKind::Decrypted(DecryptedRoomEvent {
1735 event: Raw::new(&example_event()).unwrap().cast(),
1736 encryption_info: Arc::new(EncryptionInfo {
1737 sender: user_id!("@sender:example.com").to_owned(),
1738 sender_device: Some(device_id!("ABCDEFGHIJ").to_owned()),
1739 algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
1740 curve25519_key: "xxx".to_owned(),
1741 sender_claimed_keys: BTreeMap::from([
1742 (
1743 DeviceKeyAlgorithm::Ed25519,
1744 "I3YsPwqMZQXHkSQbjFNEs7b529uac2xBpI83eN3LUXo".to_owned(),
1745 ),
1746 (
1747 DeviceKeyAlgorithm::Curve25519,
1748 "qzdW3F5IMPFl0HQgz5w/L5Oi/npKUFn8Um84acIHfPY".to_owned(),
1749 ),
1750 ]),
1751 session_id: Some("mysessionid112".to_owned()),
1752 },
1753 verification_state: VerificationState::Verified,
1754 }),
1755 unsigned_encryption_info: Some(BTreeMap::from([(
1756 UnsignedEventLocation::RelationsThreadLatestEvent,
1757 UnsignedDecryptionResult::UnableToDecrypt(UnableToDecryptInfo {
1758 session_id: Some("xyz".to_owned()),
1759 reason: UnableToDecryptReason::MissingMegolmSession {
1760 withheld_code: Some(WithheldCode::Unverified),
1761 },
1762 }),
1763 )])),
1764 }),
1765 push_actions: Default::default(),
1766 thread_summary: ThreadSummaryStatus::Some(ThreadSummary {
1767 num_replies: 2,
1768 latest_reply: None,
1769 }),
1770 bundled_latest_thread_event: None,
1771 };
1772
1773 with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
1774 assert_json_snapshot! {
1777 serde_json::to_value(&room_event).unwrap(),
1778 }
1779 });
1780 }
1781}