matrix_sdk_common/
deserialized_responses.rs

1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{collections::BTreeMap, fmt, sync::Arc};
16
17#[cfg(doc)]
18use ruma::events::AnyTimelineEvent;
19use ruma::{
20    events::{AnyMessageLikeEvent, AnySyncTimelineEvent, AnyToDeviceEvent, MessageLikeEventType},
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.";
45const MISMATCHED_SENDER: &str = "\
46    The sender of the event does not match the owner of the device \
47    that created the Megolm session.";
48pub const SENT_IN_CLEAR: &str = "Not encrypted.";
49
50/// Represents the state of verification for a decrypted message sent by a
51/// device.
52#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
53#[serde(from = "OldVerificationStateHelper")]
54pub enum VerificationState {
55    /// This message is guaranteed to be authentic as it is coming from a device
56    /// belonging to a user that we have verified.
57    ///
58    /// This is the only state where authenticity can be guaranteed.
59    Verified,
60
61    /// The message could not be linked to a verified device.
62    ///
63    /// For more detailed information on why the message is considered
64    /// unverified, refer to the VerificationLevel sub-enum.
65    Unverified(VerificationLevel),
66}
67
68// TODO: Remove this once we're confident that everybody that serialized these
69// states uses the new enum.
70#[derive(Clone, Debug, Deserialize)]
71enum OldVerificationStateHelper {
72    Untrusted,
73    UnknownDevice,
74    #[serde(alias = "Trusted")]
75    Verified,
76    Unverified(VerificationLevel),
77}
78
79impl From<OldVerificationStateHelper> for VerificationState {
80    fn from(value: OldVerificationStateHelper) -> Self {
81        match value {
82            // This mapping isn't strictly correct but we don't know which part in the old
83            // `VerificationState` enum was unverified.
84            OldVerificationStateHelper::Untrusted => {
85                VerificationState::Unverified(VerificationLevel::UnsignedDevice)
86            }
87            OldVerificationStateHelper::UnknownDevice => {
88                Self::Unverified(VerificationLevel::None(DeviceLinkProblem::MissingDevice))
89            }
90            OldVerificationStateHelper::Verified => Self::Verified,
91            OldVerificationStateHelper::Unverified(l) => Self::Unverified(l),
92        }
93    }
94}
95
96impl VerificationState {
97    /// Convert the `VerificationState` into a `ShieldState` which can be
98    /// directly used to decorate messages in the recommended way.
99    ///
100    /// This method decorates messages using a strict ruleset, for a more lax
101    /// variant of this method take a look at
102    /// [`VerificationState::to_shield_state_lax()`].
103    pub fn to_shield_state_strict(&self) -> ShieldState {
104        match self {
105            VerificationState::Verified => ShieldState::None,
106            VerificationState::Unverified(level) => match level {
107                VerificationLevel::UnverifiedIdentity
108                | VerificationLevel::VerificationViolation
109                | VerificationLevel::UnsignedDevice => ShieldState::Red {
110                    code: ShieldStateCode::UnverifiedIdentity,
111                    message: UNVERIFIED_IDENTITY,
112                },
113                VerificationLevel::None(link) => match link {
114                    DeviceLinkProblem::MissingDevice => ShieldState::Red {
115                        code: ShieldStateCode::UnknownDevice,
116                        message: UNKNOWN_DEVICE,
117                    },
118                    DeviceLinkProblem::InsecureSource => ShieldState::Red {
119                        code: ShieldStateCode::AuthenticityNotGuaranteed,
120                        message: AUTHENTICITY_NOT_GUARANTEED,
121                    },
122                },
123                VerificationLevel::MismatchedSender => ShieldState::Red {
124                    code: ShieldStateCode::MismatchedSender,
125                    message: MISMATCHED_SENDER,
126                },
127            },
128        }
129    }
130
131    /// Convert the `VerificationState` into a `ShieldState` which can be used
132    /// to decorate messages in the recommended way.
133    ///
134    /// This implements a legacy, lax decoration mode.
135    ///
136    /// For a more strict variant of this method take a look at
137    /// [`VerificationState::to_shield_state_strict()`].
138    pub fn to_shield_state_lax(&self) -> ShieldState {
139        match self {
140            VerificationState::Verified => ShieldState::None,
141            VerificationState::Unverified(level) => match level {
142                VerificationLevel::UnverifiedIdentity => {
143                    // If you didn't show interest in verifying that user we don't
144                    // nag you with an error message.
145                    ShieldState::None
146                }
147                VerificationLevel::VerificationViolation => {
148                    // This is a high warning. The sender was previously
149                    // verified, but changed their identity.
150                    ShieldState::Red {
151                        code: ShieldStateCode::VerificationViolation,
152                        message: VERIFICATION_VIOLATION,
153                    }
154                }
155                VerificationLevel::UnsignedDevice => {
156                    // This is a high warning. The sender hasn't verified his own device.
157                    ShieldState::Red {
158                        code: ShieldStateCode::UnsignedDevice,
159                        message: UNSIGNED_DEVICE,
160                    }
161                }
162                VerificationLevel::None(link) => match link {
163                    DeviceLinkProblem::MissingDevice => {
164                        // Have to warn as it could have been a temporary injected device.
165                        // Notice that the device might just not be known at this time, so callers
166                        // should retry when there is a device change for that user.
167                        ShieldState::Red {
168                            code: ShieldStateCode::UnknownDevice,
169                            message: UNKNOWN_DEVICE,
170                        }
171                    }
172                    DeviceLinkProblem::InsecureSource => {
173                        // In legacy mode, we tone down this warning as it is quite common and
174                        // mostly noise (due to legacy backup and lack of trusted forwards).
175                        ShieldState::Grey {
176                            code: ShieldStateCode::AuthenticityNotGuaranteed,
177                            message: AUTHENTICITY_NOT_GUARANTEED,
178                        }
179                    }
180                },
181                VerificationLevel::MismatchedSender => ShieldState::Red {
182                    code: ShieldStateCode::MismatchedSender,
183                    message: MISMATCHED_SENDER,
184                },
185            },
186        }
187    }
188}
189
190/// The sub-enum containing detailed information on why a message is considered
191/// to be unverified.
192#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
193pub enum VerificationLevel {
194    /// The message was sent by a user identity we have not verified.
195    UnverifiedIdentity,
196
197    /// The message was sent by a user identity we have not verified, but the
198    /// user was previously verified.
199    #[serde(alias = "PreviouslyVerified")]
200    VerificationViolation,
201
202    /// The message was sent by a device not linked to (signed by) any user
203    /// identity.
204    UnsignedDevice,
205
206    /// We weren't able to link the message back to any device. This might be
207    /// because the message claims to have been sent by a device which we have
208    /// not been able to obtain (for example, because the device was since
209    /// deleted) or because the key to decrypt the message was obtained from
210    /// an insecure source.
211    None(DeviceLinkProblem),
212
213    /// The `sender` field on the event does not match the owner of the device
214    /// that established the Megolm session.
215    MismatchedSender,
216}
217
218impl fmt::Display for VerificationLevel {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
220        let display = match self {
221            VerificationLevel::UnverifiedIdentity => "The sender's identity was not verified",
222            VerificationLevel::VerificationViolation => {
223                "The sender's identity was previously verified but has changed"
224            }
225            VerificationLevel::UnsignedDevice => {
226                "The sending device was not signed by the user's identity"
227            }
228            VerificationLevel::None(..) => "The sending device is not known",
229            VerificationLevel::MismatchedSender => MISMATCHED_SENDER,
230        };
231        write!(f, "{display}")
232    }
233}
234
235/// The sub-enum containing detailed information on why we were not able to link
236/// a message back to a device.
237#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
238pub enum DeviceLinkProblem {
239    /// The device is missing, either because it was deleted, or you haven't
240    /// yet downoaled it or the server is erroneously omitting it (federation
241    /// lag).
242    MissingDevice,
243    /// The key was obtained from an insecure source: imported from a file,
244    /// obtained from a legacy (asymmetric) backup, unsafe key forward, etc.
245    InsecureSource,
246}
247
248/// Recommended decorations for decrypted messages, representing the message's
249/// authenticity properties.
250#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
251pub enum ShieldState {
252    /// A red shield with a tooltip containing the associated message should be
253    /// presented.
254    Red {
255        /// A machine-readable representation.
256        code: ShieldStateCode,
257        /// A human readable description.
258        message: &'static str,
259    },
260    /// A grey shield with a tooltip containing the associated message should be
261    /// presented.
262    Grey {
263        /// A machine-readable representation.
264        code: ShieldStateCode,
265        /// A human readable description.
266        message: &'static str,
267    },
268    /// No shield should be presented.
269    None,
270}
271
272/// A machine-readable representation of the authenticity for a `ShieldState`.
273#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)]
274#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
275#[cfg_attr(target_family = "wasm", wasm_bindgen)]
276pub enum ShieldStateCode {
277    /// Not enough information available to check the authenticity.
278    AuthenticityNotGuaranteed,
279    /// The sending device isn't yet known by the Client.
280    UnknownDevice,
281    /// The sending device hasn't been verified by the sender.
282    UnsignedDevice,
283    /// The sender hasn't been verified by the Client's user.
284    UnverifiedIdentity,
285    /// An unencrypted event in an encrypted room.
286    SentInClear,
287    /// The sender was previously verified but changed their identity.
288    #[serde(alias = "PreviouslyVerified")]
289    VerificationViolation,
290    /// The `sender` field on the event does not match the owner of the device
291    /// that established the Megolm session.
292    MismatchedSender,
293}
294
295/// The algorithm specific information of a decrypted event.
296#[derive(Clone, Debug, Deserialize, Serialize)]
297pub enum AlgorithmInfo {
298    /// The info if the event was encrypted using m.megolm.v1.aes-sha2
299    MegolmV1AesSha2 {
300        /// The curve25519 key of the device that created the megolm decryption
301        /// key originally.
302        curve25519_key: String,
303        /// The signing keys that have created the megolm key that was used to
304        /// decrypt this session. This map will usually contain a single ed25519
305        /// key.
306        sender_claimed_keys: BTreeMap<DeviceKeyAlgorithm, String>,
307
308        /// The Megolm session ID that was used to encrypt this event, or None
309        /// if this info was stored before we collected this data.
310        #[serde(default, skip_serializing_if = "Option::is_none")]
311        session_id: Option<String>,
312    },
313
314    /// The info if the event was encrypted using m.olm.v1.curve25519-aes-sha2
315    OlmV1Curve25519AesSha2 {
316        // The sender device key, base64 encoded
317        curve25519_public_key_base64: String,
318    },
319}
320
321/// Struct containing information on how an event was decrypted.
322#[derive(Clone, Debug, Serialize)]
323pub struct EncryptionInfo {
324    /// The user ID of the event sender, note this is untrusted data unless the
325    /// `verification_state` is `Verified` as well.
326    pub sender: OwnedUserId,
327    /// The device ID of the device that sent us the event, note this is
328    /// untrusted data unless `verification_state` is `Verified` as well.
329    pub sender_device: Option<OwnedDeviceId>,
330    /// Information about the algorithm that was used to encrypt the event.
331    pub algorithm_info: AlgorithmInfo,
332    /// The verification state of the device that sent us the event, note this
333    /// is the state of the device at the time of decryption. It may change in
334    /// the future if a device gets verified or deleted.
335    ///
336    /// Callers that persist this should mark the state as dirty when a device
337    /// change is received down the sync.
338    pub verification_state: VerificationState,
339}
340
341impl EncryptionInfo {
342    /// Helper to get the megolm session id used to encrypt.
343    pub fn session_id(&self) -> Option<&str> {
344        if let AlgorithmInfo::MegolmV1AesSha2 { session_id, .. } = &self.algorithm_info {
345            session_id.as_deref()
346        } else {
347            None
348        }
349    }
350}
351
352impl<'de> Deserialize<'de> for EncryptionInfo {
353    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
354    where
355        D: serde::Deserializer<'de>,
356    {
357        // Backwards compatibility: Capture session_id at root if exists. In legacy
358        // EncryptionInfo the session_id was not in AlgorithmInfo
359        #[derive(Deserialize)]
360        struct Helper {
361            pub sender: OwnedUserId,
362            pub sender_device: Option<OwnedDeviceId>,
363            pub algorithm_info: AlgorithmInfo,
364            pub verification_state: VerificationState,
365            #[serde(rename = "session_id")]
366            pub old_session_id: Option<String>,
367        }
368
369        let Helper { sender, sender_device, algorithm_info, verification_state, old_session_id } =
370            Helper::deserialize(deserializer)?;
371
372        let algorithm_info = match algorithm_info {
373            AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, sender_claimed_keys, session_id } => {
374                AlgorithmInfo::MegolmV1AesSha2 {
375                    // Migration, merge the old_session_id in algorithm_info
376                    session_id: session_id.or(old_session_id),
377                    curve25519_key,
378                    sender_claimed_keys,
379                }
380            }
381            other => other,
382        };
383
384        Ok(EncryptionInfo { sender, sender_device, algorithm_info, verification_state })
385    }
386}
387
388/// A simplified thread summary.
389///
390/// A thread summary contains useful information pertaining to a thread, and
391/// that would be usually attached in clients to a thread root event (i.e. the
392/// first event from which the thread originated), along with links into the
393/// thread's view. This summary may include, for instance:
394///
395/// - the number of replies to the thread,
396/// - the full event of the latest reply to the thread,
397/// - whether the user participated or not to this thread.
398#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
399pub struct ThreadSummary {
400    /// The event id for the latest reply to the thread.
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub latest_reply: Option<OwnedEventId>,
403
404    /// The number of replies to the thread.
405    ///
406    /// This doesn't include the thread root event itself. It can be zero if no
407    /// events in the thread are considered to be meaningful (or they've all
408    /// been redacted).
409    pub num_replies: u32,
410}
411
412/// The status of a thread summary.
413#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
414pub enum ThreadSummaryStatus {
415    /// We don't know if the event has a thread summary.
416    #[default]
417    Unknown,
418    /// The event has no thread summary.
419    None,
420    /// The event has a thread summary, which is bundled in the event itself.
421    Some(ThreadSummary),
422}
423
424impl ThreadSummaryStatus {
425    /// Is the thread status of this event unknown?
426    fn is_unknown(&self) -> bool {
427        matches!(self, ThreadSummaryStatus::Unknown)
428    }
429
430    /// Transforms the [`ThreadSummaryStatus`] into an optional thread summary,
431    /// for cases where we don't care about distinguishing unknown and none.
432    pub fn summary(&self) -> Option<&ThreadSummary> {
433        match self {
434            ThreadSummaryStatus::Unknown | ThreadSummaryStatus::None => None,
435            ThreadSummaryStatus::Some(thread_summary) => Some(thread_summary),
436        }
437    }
438}
439
440/// Represents a Matrix room event that has been returned from `/sync`,
441/// after initial processing.
442///
443/// Previously, this differed from [`TimelineEvent`] by wrapping an
444/// [`AnySyncTimelineEvent`] instead of an [`AnyTimelineEvent`], but nowadays
445/// they are essentially identical, and one of them should probably be removed.
446//
447// 🚨 Note about this type, please read! 🚨
448//
449// `TimelineEvent` is heavily used across the SDK crates. In some cases, we
450// are reaching a [`recursion_limit`] when the compiler is trying to figure out
451// if `TimelineEvent` implements `Sync` when it's embedded in other types.
452//
453// We want to help the compiler so that one doesn't need to increase the
454// `recursion_limit`. We stop the recursive check by (un)safely implement `Sync`
455// and `Send` on `TimelineEvent` directly.
456//
457// See
458// https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823
459// which has addressed this issue first
460//
461// [`recursion_limit`]: https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute
462#[derive(Clone, Debug, Serialize)]
463pub struct TimelineEvent {
464    /// The event itself, together with any information on decryption.
465    pub kind: TimelineEventKind,
466
467    /// The push actions associated with this event.
468    ///
469    /// If it's set to `None`, then it means we couldn't compute those actions,
470    /// or that they could be computed but there were none.
471    #[serde(skip_serializing_if = "skip_serialize_push_actions")]
472    push_actions: Option<Vec<Action>>,
473
474    /// If the event is part of a thread, a thread summary.
475    #[serde(default, skip_serializing_if = "ThreadSummaryStatus::is_unknown")]
476    pub thread_summary: ThreadSummaryStatus,
477
478    /// The bundled latest thread event, if it was provided in the unsigned
479    /// relations of this event.
480    ///
481    /// Not serialized.
482    #[serde(skip)]
483    pub bundled_latest_thread_event: Option<Box<TimelineEvent>>,
484}
485
486// Don't serialize push actions if they're `None` or an empty vec.
487fn skip_serialize_push_actions(push_actions: &Option<Vec<Action>>) -> bool {
488    push_actions.as_ref().is_none_or(|v| v.is_empty())
489}
490
491// See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.
492#[cfg(not(feature = "test-send-sync"))]
493unsafe impl Send for TimelineEvent {}
494
495// See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.
496#[cfg(not(feature = "test-send-sync"))]
497unsafe impl Sync for TimelineEvent {}
498
499#[cfg(feature = "test-send-sync")]
500#[test]
501// See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.
502fn test_send_sync_for_sync_timeline_event() {
503    fn assert_send_sync<T: crate::SendOutsideWasm + crate::SyncOutsideWasm>() {}
504
505    assert_send_sync::<TimelineEvent>();
506}
507
508impl TimelineEvent {
509    /// Create a new [`TimelineEvent`] from the given raw event.
510    ///
511    /// This is a convenience constructor for a plaintext event when you don't
512    /// need to set `push_action`, for example inside a test.
513    pub fn from_plaintext(event: Raw<AnySyncTimelineEvent>) -> Self {
514        Self::new(TimelineEventKind::PlainText { event }, None)
515    }
516
517    /// Create a new [`TimelineEvent`] from a decrypted event.
518    pub fn from_decrypted(
519        decrypted: DecryptedRoomEvent,
520        push_actions: Option<Vec<Action>>,
521    ) -> Self {
522        Self::new(TimelineEventKind::Decrypted(decrypted), push_actions)
523    }
524
525    /// Create a new [`TimelineEvent`] to represent the given decryption
526    /// failure.
527    pub fn from_utd(event: Raw<AnySyncTimelineEvent>, utd_info: UnableToDecryptInfo) -> Self {
528        Self::new(TimelineEventKind::UnableToDecrypt { event, utd_info }, None)
529    }
530
531    /// Internal only: helps extracting a thread summary and latest thread event
532    /// when creating a new [`TimelineEvent`].
533    fn new(kind: TimelineEventKind, push_actions: Option<Vec<Action>>) -> Self {
534        let (thread_summary, latest_thread_event) = extract_bundled_thread_summary(kind.raw());
535        let bundled_latest_thread_event =
536            Self::from_bundled_latest_event(&kind, latest_thread_event);
537        Self { kind, push_actions, thread_summary, bundled_latest_thread_event }
538    }
539
540    /// Try to create a new [`TimelineEvent`] for the bundled latest thread
541    /// event, if available, and if we have enough information about the
542    /// encryption status for it.
543    fn from_bundled_latest_event(
544        this: &TimelineEventKind,
545        latest_event: Option<Raw<AnyMessageLikeEvent>>,
546    ) -> Option<Box<Self>> {
547        let latest_event = latest_event?;
548
549        match this {
550            TimelineEventKind::Decrypted(decrypted) => {
551                if let Some(unsigned_decryption_result) =
552                    decrypted.unsigned_encryption_info.as_ref().and_then(|unsigned_map| {
553                        unsigned_map.get(&UnsignedEventLocation::RelationsThreadLatestEvent)
554                    })
555                {
556                    match unsigned_decryption_result {
557                        UnsignedDecryptionResult::Decrypted(encryption_info) => {
558                            // The bundled event was encrypted, and we could decrypt it: pass that
559                            // information around.
560                            return Some(Box::new(TimelineEvent::from_decrypted(
561                                DecryptedRoomEvent {
562                                    event: latest_event,
563                                    encryption_info: encryption_info.clone(),
564                                    // A bundled latest event is never a thread root. It could have
565                                    // a replacement event, but we don't carry this information
566                                    // around.
567                                    unsigned_encryption_info: None,
568                                },
569                                None,
570                            )));
571                        }
572
573                        UnsignedDecryptionResult::UnableToDecrypt(utd_info) => {
574                            // The bundled event was a UTD; store that information.
575                            return Some(Box::new(TimelineEvent::from_utd(
576                                latest_event.cast(),
577                                utd_info.clone(),
578                            )));
579                        }
580                    }
581                }
582            }
583
584            TimelineEventKind::UnableToDecrypt { .. } | TimelineEventKind::PlainText { .. } => {
585                // Figure based on the event type below.
586            }
587        }
588
589        match latest_event.get_field::<MessageLikeEventType>("type") {
590            Ok(None) => {
591                let event_id = latest_event.get_field::<OwnedEventId>("event_id").ok().flatten();
592                warn!(
593                    ?event_id,
594                    "couldn't deserialize bundled latest thread event: missing `type` field \
595                     in bundled latest thread event"
596                );
597                None
598            }
599
600            Ok(Some(MessageLikeEventType::RoomEncrypted)) => {
601                // The bundled latest thread event is encrypted, but we didn't have any
602                // information about it in the unsigned map. Provide some dummy
603                // UTD info, since we can't really do much better.
604                Some(Box::new(TimelineEvent::from_utd(
605                    latest_event.cast(),
606                    UnableToDecryptInfo {
607                        session_id: None,
608                        reason: UnableToDecryptReason::Unknown,
609                    },
610                )))
611            }
612
613            Ok(_) => Some(Box::new(TimelineEvent::from_plaintext(latest_event.cast()))),
614
615            Err(err) => {
616                let event_id = latest_event.get_field::<OwnedEventId>("event_id").ok().flatten();
617                warn!(?event_id, "couldn't deserialize bundled latest thread event's type: {err}");
618                None
619            }
620        }
621    }
622
623    /// Read the current push actions.
624    ///
625    /// Returns `None` if they were never computed, or if they could not be
626    /// computed.
627    pub fn push_actions(&self) -> Option<&[Action]> {
628        self.push_actions.as_deref()
629    }
630
631    /// Set the push actions for this event.
632    pub fn set_push_actions(&mut self, push_actions: Vec<Action>) {
633        self.push_actions = Some(push_actions);
634    }
635
636    /// Get the event id of this [`TimelineEvent`] if the event has any valid
637    /// id.
638    pub fn event_id(&self) -> Option<OwnedEventId> {
639        self.kind.event_id()
640    }
641
642    /// Returns a reference to the (potentially decrypted) Matrix event inside
643    /// this [`TimelineEvent`].
644    pub fn raw(&self) -> &Raw<AnySyncTimelineEvent> {
645        self.kind.raw()
646    }
647
648    /// Replace the raw event included in this item by another one.
649    pub fn replace_raw(&mut self, replacement: Raw<AnyMessageLikeEvent>) {
650        match &mut self.kind {
651            TimelineEventKind::Decrypted(decrypted) => decrypted.event = replacement,
652            TimelineEventKind::UnableToDecrypt { event, .. }
653            | TimelineEventKind::PlainText { event } => {
654                // It's safe to cast `AnyMessageLikeEvent` into `AnySyncMessageLikeEvent`,
655                // because the former contains a superset of the fields included in the latter.
656                *event = replacement.cast();
657            }
658        }
659    }
660
661    /// If the event was a decrypted event that was successfully decrypted, get
662    /// its encryption info. Otherwise, `None`.
663    pub fn encryption_info(&self) -> Option<&Arc<EncryptionInfo>> {
664        self.kind.encryption_info()
665    }
666
667    /// Takes ownership of this [`TimelineEvent`], returning the (potentially
668    /// decrypted) Matrix event within.
669    pub fn into_raw(self) -> Raw<AnySyncTimelineEvent> {
670        self.kind.into_raw()
671    }
672}
673
674impl<'de> Deserialize<'de> for TimelineEvent {
675    /// Custom deserializer for [`TimelineEvent`], to support older formats.
676    ///
677    /// Ideally we might use an untagged enum and then convert from that;
678    /// however, that doesn't work due to a [serde bug](https://github.com/serde-rs/json/issues/497).
679    ///
680    /// Instead, we first deserialize into an unstructured JSON map, and then
681    /// inspect the json to figure out which format we have.
682    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
683    where
684        D: serde::Deserializer<'de>,
685    {
686        use serde_json::{Map, Value};
687
688        // First, deserialize to an unstructured JSON map
689        let value = Map::<String, Value>::deserialize(deserializer)?;
690
691        // If we have a top-level `event`, it's V0
692        if value.contains_key("event") {
693            let v0: SyncTimelineEventDeserializationHelperV0 =
694                serde_json::from_value(Value::Object(value)).map_err(|e| {
695                    serde::de::Error::custom(format!(
696                        "Unable to deserialize V0-format TimelineEvent: {e}",
697                    ))
698                })?;
699            Ok(v0.into())
700        }
701        // Otherwise, it's V1
702        else {
703            let v1: SyncTimelineEventDeserializationHelperV1 =
704                serde_json::from_value(Value::Object(value)).map_err(|e| {
705                    serde::de::Error::custom(format!(
706                        "Unable to deserialize V1-format TimelineEvent: {e}",
707                    ))
708                })?;
709            Ok(v1.into())
710        }
711    }
712}
713
714/// The event within a [`TimelineEvent`], together with encryption data.
715#[derive(Clone, Serialize, Deserialize)]
716pub enum TimelineEventKind {
717    /// A successfully-decrypted encrypted event.
718    Decrypted(DecryptedRoomEvent),
719
720    /// An encrypted event which could not be decrypted.
721    UnableToDecrypt {
722        /// The `m.room.encrypted` event. Depending on the source of the event,
723        /// it could actually be an [`AnyTimelineEvent`] (i.e., it may
724        /// have a `room_id` property).
725        event: Raw<AnySyncTimelineEvent>,
726
727        /// Information on the reason we failed to decrypt
728        utd_info: UnableToDecryptInfo,
729    },
730
731    /// An unencrypted event.
732    PlainText {
733        /// The actual event. Depending on the source of the event, it could
734        /// actually be a [`AnyTimelineEvent`] (which differs from
735        /// [`AnySyncTimelineEvent`] by the addition of a `room_id` property).
736        event: Raw<AnySyncTimelineEvent>,
737    },
738}
739
740impl TimelineEventKind {
741    /// Returns a reference to the (potentially decrypted) Matrix event inside
742    /// this `TimelineEvent`.
743    pub fn raw(&self) -> &Raw<AnySyncTimelineEvent> {
744        match self {
745            // It is safe to cast from an `AnyMessageLikeEvent` (i.e. JSON which does
746            // *not* contain a `state_key` and *does* contain a `room_id`) into an
747            // `AnySyncTimelineEvent` (i.e. JSON which *may* contain a `state_key` and is *not*
748            // expected to contain a `room_id`). It just means that the `room_id` will be ignored
749            // in a future deserialization.
750            TimelineEventKind::Decrypted(d) => d.event.cast_ref(),
751            TimelineEventKind::UnableToDecrypt { event, .. } => event.cast_ref(),
752            TimelineEventKind::PlainText { event } => event,
753        }
754    }
755
756    /// Get the event id of this `TimelineEventKind` if the event has any valid
757    /// id.
758    pub fn event_id(&self) -> Option<OwnedEventId> {
759        self.raw().get_field::<OwnedEventId>("event_id").ok().flatten()
760    }
761
762    /// If the event was a decrypted event that was successfully decrypted, get
763    /// its encryption info. Otherwise, `None`.
764    pub fn encryption_info(&self) -> Option<&Arc<EncryptionInfo>> {
765        match self {
766            TimelineEventKind::Decrypted(d) => Some(&d.encryption_info),
767            TimelineEventKind::UnableToDecrypt { .. } | TimelineEventKind::PlainText { .. } => None,
768        }
769    }
770
771    /// If the event was a decrypted event that was successfully decrypted, get
772    /// the map of decryption metadata related to the bundled events.
773    pub fn unsigned_encryption_map(
774        &self,
775    ) -> Option<&BTreeMap<UnsignedEventLocation, UnsignedDecryptionResult>> {
776        match self {
777            TimelineEventKind::Decrypted(d) => d.unsigned_encryption_info.as_ref(),
778            TimelineEventKind::UnableToDecrypt { .. } | TimelineEventKind::PlainText { .. } => None,
779        }
780    }
781
782    /// Takes ownership of this `TimelineEvent`, returning the (potentially
783    /// decrypted) Matrix event within.
784    pub fn into_raw(self) -> Raw<AnySyncTimelineEvent> {
785        match self {
786            // It is safe to cast from an `AnyMessageLikeEvent` (i.e. JSON which does
787            // *not* contain a `state_key` and *does* contain a `room_id`) into an
788            // `AnySyncTimelineEvent` (i.e. JSON which *may* contain a `state_key` and is *not*
789            // expected to contain a `room_id`). It just means that the `room_id` will be ignored
790            // in a future deserialization.
791            TimelineEventKind::Decrypted(d) => d.event.cast(),
792            TimelineEventKind::UnableToDecrypt { event, .. } => event.cast(),
793            TimelineEventKind::PlainText { event } => event,
794        }
795    }
796
797    /// The Megolm session ID that was used to send this event, if it was
798    /// encrypted.
799    pub fn session_id(&self) -> Option<&str> {
800        match self {
801            TimelineEventKind::Decrypted(decrypted_room_event) => {
802                decrypted_room_event.encryption_info.session_id()
803            }
804            TimelineEventKind::UnableToDecrypt { utd_info, .. } => utd_info.session_id.as_deref(),
805            TimelineEventKind::PlainText { .. } => None,
806        }
807    }
808}
809
810#[cfg(not(tarpaulin_include))]
811impl fmt::Debug for TimelineEventKind {
812    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
813        match &self {
814            Self::PlainText { event } => f
815                .debug_struct("TimelineEventDecryptionResult::PlainText")
816                .field("event", &DebugRawEvent(event))
817                .finish(),
818
819            Self::UnableToDecrypt { event, utd_info } => f
820                .debug_struct("TimelineEventDecryptionResult::UnableToDecrypt")
821                .field("event", &DebugRawEvent(event))
822                .field("utd_info", &utd_info)
823                .finish(),
824
825            Self::Decrypted(decrypted) => {
826                f.debug_tuple("TimelineEventDecryptionResult::Decrypted").field(decrypted).finish()
827            }
828        }
829    }
830}
831
832#[derive(Clone, Serialize, Deserialize)]
833/// A successfully-decrypted encrypted event.
834pub struct DecryptedRoomEvent {
835    /// The decrypted event.
836    ///
837    /// Note: it's not an error that this contains an `AnyMessageLikeEvent`: an
838    /// encrypted payload *always contains* a room id, by the [spec].
839    ///
840    /// [spec]: https://spec.matrix.org/v1.12/client-server-api/#mmegolmv1aes-sha2
841    pub event: Raw<AnyMessageLikeEvent>,
842
843    /// The encryption info about the event.
844    pub encryption_info: Arc<EncryptionInfo>,
845
846    /// The encryption info about the events bundled in the `unsigned`
847    /// object.
848    ///
849    /// Will be `None` if no bundled event was encrypted.
850    #[serde(skip_serializing_if = "Option::is_none")]
851    pub unsigned_encryption_info: Option<BTreeMap<UnsignedEventLocation, UnsignedDecryptionResult>>,
852}
853
854#[cfg(not(tarpaulin_include))]
855impl fmt::Debug for DecryptedRoomEvent {
856    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
857        let DecryptedRoomEvent { event, encryption_info, unsigned_encryption_info } = self;
858
859        f.debug_struct("DecryptedRoomEvent")
860            .field("event", &DebugRawEvent(event))
861            .field("encryption_info", encryption_info)
862            .maybe_field("unsigned_encryption_info", unsigned_encryption_info)
863            .finish()
864    }
865}
866
867/// The location of an event bundled in an `unsigned` object.
868#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
869pub enum UnsignedEventLocation {
870    /// An event at the `m.replace` key of the `m.relations` object, that is a
871    /// bundled replacement.
872    RelationsReplace,
873    /// An event at the `latest_event` key of the `m.thread` object of the
874    /// `m.relations` object, that is the latest event of a thread.
875    RelationsThreadLatestEvent,
876}
877
878impl UnsignedEventLocation {
879    /// Find the mutable JSON value at this location in the given unsigned
880    /// object.
881    ///
882    /// # Arguments
883    ///
884    /// * `unsigned` - The `unsigned` property of an event as a JSON object.
885    pub fn find_mut<'a>(&self, unsigned: &'a mut JsonObject) -> Option<&'a mut serde_json::Value> {
886        let relations = unsigned.get_mut("m.relations")?.as_object_mut()?;
887
888        match self {
889            Self::RelationsReplace => relations.get_mut("m.replace"),
890            Self::RelationsThreadLatestEvent => {
891                relations.get_mut("m.thread")?.as_object_mut()?.get_mut("latest_event")
892            }
893        }
894    }
895}
896
897/// The result of the decryption of an event bundled in an `unsigned` object.
898#[derive(Debug, Clone, Serialize, Deserialize)]
899pub enum UnsignedDecryptionResult {
900    /// The event was successfully decrypted.
901    Decrypted(Arc<EncryptionInfo>),
902    /// The event failed to be decrypted.
903    UnableToDecrypt(UnableToDecryptInfo),
904}
905
906impl UnsignedDecryptionResult {
907    /// Returns the encryption info for this bundled event if it was
908    /// successfully decrypted.
909    pub fn encryption_info(&self) -> Option<&Arc<EncryptionInfo>> {
910        match self {
911            Self::Decrypted(info) => Some(info),
912            Self::UnableToDecrypt(_) => None,
913        }
914    }
915}
916
917/// Metadata about an event that could not be decrypted.
918#[derive(Debug, Clone, Serialize, Deserialize)]
919pub struct UnableToDecryptInfo {
920    /// The ID of the session used to encrypt the message, if it used the
921    /// `m.megolm.v1.aes-sha2` algorithm.
922    #[serde(skip_serializing_if = "Option::is_none")]
923    pub session_id: Option<String>,
924
925    /// Reason code for the decryption failure
926    #[serde(default = "unknown_utd_reason", deserialize_with = "deserialize_utd_reason")]
927    pub reason: UnableToDecryptReason,
928}
929
930fn unknown_utd_reason() -> UnableToDecryptReason {
931    UnableToDecryptReason::Unknown
932}
933
934/// Provides basic backward compatibility for deserializing older serialized
935/// `UnableToDecryptReason` values.
936pub fn deserialize_utd_reason<'de, D>(d: D) -> Result<UnableToDecryptReason, D::Error>
937where
938    D: serde::Deserializer<'de>,
939{
940    // Start by deserializing as to an untyped JSON value.
941    let v: serde_json::Value = Deserialize::deserialize(d)?;
942    // Backwards compatibility: `MissingMegolmSession` used to be stored without the
943    // withheld code.
944    if v.as_str().is_some_and(|s| s == "MissingMegolmSession") {
945        return Ok(UnableToDecryptReason::MissingMegolmSession { withheld_code: None });
946    }
947    // Otherwise, use the derived deserialize impl to turn the JSON into a
948    // UnableToDecryptReason
949    serde_json::from_value::<UnableToDecryptReason>(v).map_err(serde::de::Error::custom)
950}
951
952/// Reason code for a decryption failure
953#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
954pub enum UnableToDecryptReason {
955    /// The reason for the decryption failure is unknown. This is only intended
956    /// for use when deserializing old UnableToDecryptInfo instances.
957    #[doc(hidden)]
958    Unknown,
959
960    /// The `m.room.encrypted` event that should have been decrypted is
961    /// malformed in some way (e.g. unsupported algorithm, missing fields,
962    /// unknown megolm message type).
963    MalformedEncryptedEvent,
964
965    /// Decryption failed because we're missing the megolm session that was used
966    /// to encrypt the event.
967    MissingMegolmSession {
968        /// If the key was withheld on purpose, the associated code. `None`
969        /// means no withheld code was received.
970        withheld_code: Option<WithheldCode>,
971    },
972
973    /// Decryption failed because, while we have the megolm session that was
974    /// used to encrypt the message, it is ratcheted too far forward.
975    UnknownMegolmMessageIndex,
976
977    /// We found the Megolm session, but were unable to decrypt the event using
978    /// that session for some reason (e.g. incorrect MAC).
979    ///
980    /// This represents all `vodozemac::megolm::DecryptionError`s, except
981    /// `UnknownMessageIndex`, which is represented as
982    /// `UnknownMegolmMessageIndex`.
983    MegolmDecryptionFailure,
984
985    /// The event could not be deserialized after decryption.
986    PayloadDeserializationFailure,
987
988    /// Decryption failed because of a mismatch between the identity keys of the
989    /// device we received the room key from and the identity keys recorded in
990    /// the plaintext of the room key to-device message.
991    MismatchedIdentityKeys,
992
993    /// An encrypted message wasn't decrypted, because the sender's
994    /// cross-signing identity did not satisfy the requested
995    /// `TrustRequirement`.
996    SenderIdentityNotTrusted(VerificationLevel),
997}
998
999impl UnableToDecryptReason {
1000    /// Returns true if this UTD is due to a missing room key (and hence might
1001    /// resolve itself if we wait a bit.)
1002    pub fn is_missing_room_key(&self) -> bool {
1003        // In case of MissingMegolmSession with a withheld code we return false here
1004        // given that this API is used to decide if waiting a bit will help.
1005        matches!(
1006            self,
1007            Self::MissingMegolmSession { withheld_code: None } | Self::UnknownMegolmMessageIndex
1008        )
1009    }
1010}
1011
1012/// A machine-readable code for why a Megolm key was not sent.
1013///
1014/// Normally sent as the payload of an [`m.room_key.withheld`](https://spec.matrix.org/v1.12/client-server-api/#mroom_keywithheld) to-device message.
1015#[derive(
1016    Clone,
1017    PartialEq,
1018    Eq,
1019    Hash,
1020    AsStrAsRefStr,
1021    AsRefStr,
1022    FromString,
1023    DebugAsRefStr,
1024    SerializeAsRefStr,
1025    DeserializeFromCowStr,
1026)]
1027pub enum WithheldCode {
1028    /// the user/device was blacklisted.
1029    #[ruma_enum(rename = "m.blacklisted")]
1030    Blacklisted,
1031
1032    /// the user/devices is unverified.
1033    #[ruma_enum(rename = "m.unverified")]
1034    Unverified,
1035
1036    /// The user/device is not allowed have the key. For example, this would
1037    /// usually be sent in response to a key request if the user was not in
1038    /// the room when the message was sent.
1039    #[ruma_enum(rename = "m.unauthorised")]
1040    Unauthorised,
1041
1042    /// Sent in reply to a key request if the device that the key is requested
1043    /// from does not have the requested key.
1044    #[ruma_enum(rename = "m.unavailable")]
1045    Unavailable,
1046
1047    /// An olm session could not be established.
1048    /// This may happen, for example, if the sender was unable to obtain a
1049    /// one-time key from the recipient.
1050    #[ruma_enum(rename = "m.no_olm")]
1051    NoOlm,
1052
1053    #[doc(hidden)]
1054    _Custom(PrivOwnedStr),
1055}
1056
1057impl fmt::Display for WithheldCode {
1058    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1059        let string = match self {
1060            WithheldCode::Blacklisted => "The sender has blocked you.",
1061            WithheldCode::Unverified => "The sender has disabled encrypting to unverified devices.",
1062            WithheldCode::Unauthorised => "You are not authorised to read the message.",
1063            WithheldCode::Unavailable => "The requested key was not found.",
1064            WithheldCode::NoOlm => "Unable to establish a secure channel.",
1065            _ => self.as_str(),
1066        };
1067
1068        f.write_str(string)
1069    }
1070}
1071
1072// The Ruma macro expects the type to have this name.
1073// The payload is counter intuitively made public in order to avoid having
1074// multiple copies of this struct.
1075#[doc(hidden)]
1076#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1077pub struct PrivOwnedStr(pub Box<str>);
1078
1079#[cfg(not(tarpaulin_include))]
1080impl fmt::Debug for PrivOwnedStr {
1081    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1082        self.0.fmt(f)
1083    }
1084}
1085
1086/// Deserialization helper for [`TimelineEvent`], for the modern format.
1087///
1088/// This has the exact same fields as [`TimelineEvent`] itself, but has a
1089/// regular `Deserialize` implementation.
1090#[derive(Debug, Deserialize)]
1091struct SyncTimelineEventDeserializationHelperV1 {
1092    /// The event itself, together with any information on decryption.
1093    kind: TimelineEventKind,
1094
1095    /// The push actions associated with this event.
1096    #[serde(default)]
1097    push_actions: Vec<Action>,
1098
1099    /// If the event is part of a thread, a thread summary.
1100    #[serde(default)]
1101    thread_summary: ThreadSummaryStatus,
1102}
1103
1104impl From<SyncTimelineEventDeserializationHelperV1> for TimelineEvent {
1105    fn from(value: SyncTimelineEventDeserializationHelperV1) -> Self {
1106        let SyncTimelineEventDeserializationHelperV1 { kind, push_actions, thread_summary } = value;
1107        TimelineEvent {
1108            kind,
1109            push_actions: Some(push_actions),
1110            thread_summary,
1111            // Bundled latest thread event is not persisted.
1112            bundled_latest_thread_event: None,
1113        }
1114    }
1115}
1116
1117/// Deserialization helper for [`TimelineEvent`], for an older format.
1118#[derive(Deserialize)]
1119struct SyncTimelineEventDeserializationHelperV0 {
1120    /// The actual event.
1121    event: Raw<AnySyncTimelineEvent>,
1122
1123    /// The encryption info about the event.
1124    ///
1125    /// Will be `None` if the event was not encrypted.
1126    encryption_info: Option<Arc<EncryptionInfo>>,
1127
1128    /// The push actions associated with this event.
1129    #[serde(default)]
1130    push_actions: Vec<Action>,
1131
1132    /// The encryption info about the events bundled in the `unsigned`
1133    /// object.
1134    ///
1135    /// Will be `None` if no bundled event was encrypted.
1136    unsigned_encryption_info: Option<BTreeMap<UnsignedEventLocation, UnsignedDecryptionResult>>,
1137}
1138
1139impl From<SyncTimelineEventDeserializationHelperV0> for TimelineEvent {
1140    fn from(value: SyncTimelineEventDeserializationHelperV0) -> Self {
1141        let SyncTimelineEventDeserializationHelperV0 {
1142            event,
1143            encryption_info,
1144            push_actions,
1145            unsigned_encryption_info,
1146        } = value;
1147
1148        let kind = match encryption_info {
1149            Some(encryption_info) => {
1150                TimelineEventKind::Decrypted(DecryptedRoomEvent {
1151                    // We cast from `Raw<AnySyncTimelineEvent>` to
1152                    // `Raw<AnyMessageLikeEvent>`, which means
1153                    // we are asserting that it contains a room_id.
1154                    // That *should* be ok, because if this is genuinely a decrypted
1155                    // room event (as the encryption_info indicates), then it will have
1156                    // a room_id.
1157                    event: event.cast(),
1158                    encryption_info,
1159                    unsigned_encryption_info,
1160                })
1161            }
1162
1163            None => TimelineEventKind::PlainText { event },
1164        };
1165
1166        TimelineEvent {
1167            kind,
1168            push_actions: Some(push_actions),
1169            // No serialized events had a thread summary at this version of the struct.
1170            thread_summary: ThreadSummaryStatus::Unknown,
1171            // Bundled latest thread event is not persisted.
1172            bundled_latest_thread_event: None,
1173        }
1174    }
1175}
1176
1177/// Represents a to-device event after it has been processed by the Olm machine.
1178#[derive(Clone, Debug)]
1179pub enum ProcessedToDeviceEvent {
1180    /// A successfully-decrypted encrypted event.
1181    /// Contains the raw decrypted event and encryption info
1182    Decrypted {
1183        /// The raw decrypted event
1184        raw: Raw<AnyToDeviceEvent>,
1185        /// The Olm encryption info
1186        encryption_info: EncryptionInfo,
1187    },
1188
1189    /// An encrypted event which could not be decrypted.
1190    UnableToDecrypt(Raw<AnyToDeviceEvent>),
1191
1192    /// An unencrypted event.
1193    PlainText(Raw<AnyToDeviceEvent>),
1194
1195    /// An invalid to device event that was ignored because it is missing some
1196    /// required information to be processed (like no event `type` for
1197    /// example)
1198    Invalid(Raw<AnyToDeviceEvent>),
1199}
1200
1201impl ProcessedToDeviceEvent {
1202    /// Converts a ProcessedToDeviceEvent to the `Raw<AnyToDeviceEvent>` it
1203    /// encapsulates
1204    pub fn to_raw(&self) -> Raw<AnyToDeviceEvent> {
1205        match self {
1206            ProcessedToDeviceEvent::Decrypted { raw, .. } => raw.clone(),
1207            ProcessedToDeviceEvent::UnableToDecrypt(event) => event.clone(),
1208            ProcessedToDeviceEvent::PlainText(event) => event.clone(),
1209            ProcessedToDeviceEvent::Invalid(event) => event.clone(),
1210        }
1211    }
1212
1213    /// Gets the raw to-device event.
1214    pub fn as_raw(&self) -> &Raw<AnyToDeviceEvent> {
1215        match self {
1216            ProcessedToDeviceEvent::Decrypted { raw, .. } => raw,
1217            ProcessedToDeviceEvent::UnableToDecrypt(event) => event,
1218            ProcessedToDeviceEvent::PlainText(event) => event,
1219            ProcessedToDeviceEvent::Invalid(event) => event,
1220        }
1221    }
1222}
1223
1224#[cfg(test)]
1225mod tests {
1226    use std::{collections::BTreeMap, sync::Arc};
1227
1228    use assert_matches::assert_matches;
1229    use assert_matches2::assert_let;
1230    use insta::{assert_json_snapshot, with_settings};
1231    use ruma::{
1232        device_id, event_id, events::room::message::RoomMessageEventContent, serde::Raw, user_id,
1233        DeviceKeyAlgorithm,
1234    };
1235    use serde::Deserialize;
1236    use serde_json::json;
1237
1238    use super::{
1239        AlgorithmInfo, DecryptedRoomEvent, DeviceLinkProblem, EncryptionInfo, ShieldState,
1240        ShieldStateCode, TimelineEvent, TimelineEventKind, UnableToDecryptInfo,
1241        UnableToDecryptReason, UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel,
1242        VerificationState, WithheldCode,
1243    };
1244    use crate::deserialized_responses::{ThreadSummary, ThreadSummaryStatus};
1245
1246    fn example_event() -> serde_json::Value {
1247        json!({
1248            "content": RoomMessageEventContent::text_plain("secret"),
1249            "type": "m.room.message",
1250            "event_id": "$xxxxx:example.org",
1251            "room_id": "!someroom:example.com",
1252            "origin_server_ts": 2189,
1253            "sender": "@carl:example.com",
1254        })
1255    }
1256
1257    #[test]
1258    fn sync_timeline_debug_content() {
1259        let room_event = TimelineEvent::from_plaintext(Raw::new(&example_event()).unwrap().cast());
1260        let debug_s = format!("{room_event:?}");
1261        assert!(
1262            !debug_s.contains("secret"),
1263            "Debug representation contains event content!\n{debug_s}"
1264        );
1265    }
1266
1267    #[test]
1268    fn old_verification_state_to_new_migration() {
1269        #[derive(Deserialize)]
1270        struct State {
1271            state: VerificationState,
1272        }
1273
1274        let state = json!({
1275            "state": "Trusted",
1276        });
1277        let deserialized: State =
1278            serde_json::from_value(state).expect("We can deserialize the old trusted value");
1279        assert_eq!(deserialized.state, VerificationState::Verified);
1280
1281        let state = json!({
1282            "state": "UnknownDevice",
1283        });
1284
1285        let deserialized: State =
1286            serde_json::from_value(state).expect("We can deserialize the old unknown device value");
1287
1288        assert_eq!(
1289            deserialized.state,
1290            VerificationState::Unverified(VerificationLevel::None(
1291                DeviceLinkProblem::MissingDevice
1292            ))
1293        );
1294
1295        let state = json!({
1296            "state": "Untrusted",
1297        });
1298        let deserialized: State =
1299            serde_json::from_value(state).expect("We can deserialize the old trusted value");
1300
1301        assert_eq!(
1302            deserialized.state,
1303            VerificationState::Unverified(VerificationLevel::UnsignedDevice)
1304        );
1305    }
1306
1307    #[test]
1308    fn test_verification_level_deserializes() {
1309        // Given a JSON VerificationLevel
1310        #[derive(Deserialize)]
1311        struct Container {
1312            verification_level: VerificationLevel,
1313        }
1314        let container = json!({ "verification_level": "VerificationViolation" });
1315
1316        // When we deserialize it
1317        let deserialized: Container = serde_json::from_value(container)
1318            .expect("We can deserialize the old PreviouslyVerified value");
1319
1320        // Then it is populated correctly
1321        assert_eq!(deserialized.verification_level, VerificationLevel::VerificationViolation);
1322    }
1323
1324    #[test]
1325    fn test_verification_level_deserializes_from_old_previously_verified_value() {
1326        // Given a JSON VerificationLevel with the old value PreviouslyVerified
1327        #[derive(Deserialize)]
1328        struct Container {
1329            verification_level: VerificationLevel,
1330        }
1331        let container = json!({ "verification_level": "PreviouslyVerified" });
1332
1333        // When we deserialize it
1334        let deserialized: Container = serde_json::from_value(container)
1335            .expect("We can deserialize the old PreviouslyVerified value");
1336
1337        // Then it is migrated to the new value
1338        assert_eq!(deserialized.verification_level, VerificationLevel::VerificationViolation);
1339    }
1340
1341    #[test]
1342    fn test_shield_state_code_deserializes() {
1343        // Given a JSON ShieldStateCode with value VerificationViolation
1344        #[derive(Deserialize)]
1345        struct Container {
1346            shield_state_code: ShieldStateCode,
1347        }
1348        let container = json!({ "shield_state_code": "VerificationViolation" });
1349
1350        // When we deserialize it
1351        let deserialized: Container = serde_json::from_value(container)
1352            .expect("We can deserialize the old PreviouslyVerified value");
1353
1354        // Then it is populated correctly
1355        assert_eq!(deserialized.shield_state_code, ShieldStateCode::VerificationViolation);
1356    }
1357
1358    #[test]
1359    fn test_shield_state_code_deserializes_from_old_previously_verified_value() {
1360        // Given a JSON ShieldStateCode with the old value PreviouslyVerified
1361        #[derive(Deserialize)]
1362        struct Container {
1363            shield_state_code: ShieldStateCode,
1364        }
1365        let container = json!({ "shield_state_code": "PreviouslyVerified" });
1366
1367        // When we deserialize it
1368        let deserialized: Container = serde_json::from_value(container)
1369            .expect("We can deserialize the old PreviouslyVerified value");
1370
1371        // Then it is migrated to the new value
1372        assert_eq!(deserialized.shield_state_code, ShieldStateCode::VerificationViolation);
1373    }
1374
1375    #[test]
1376    fn sync_timeline_event_serialisation() {
1377        let room_event = TimelineEvent {
1378            kind: TimelineEventKind::Decrypted(DecryptedRoomEvent {
1379                event: Raw::new(&example_event()).unwrap().cast(),
1380                encryption_info: Arc::new(EncryptionInfo {
1381                    sender: user_id!("@sender:example.com").to_owned(),
1382                    sender_device: None,
1383                    algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
1384                        curve25519_key: "xxx".to_owned(),
1385                        sender_claimed_keys: Default::default(),
1386                        session_id: Some("xyz".to_owned()),
1387                    },
1388                    verification_state: VerificationState::Verified,
1389                }),
1390                unsigned_encryption_info: Some(BTreeMap::from([(
1391                    UnsignedEventLocation::RelationsReplace,
1392                    UnsignedDecryptionResult::UnableToDecrypt(UnableToDecryptInfo {
1393                        session_id: Some("xyz".to_owned()),
1394                        reason: UnableToDecryptReason::MalformedEncryptedEvent,
1395                    }),
1396                )])),
1397            }),
1398            push_actions: Default::default(),
1399            thread_summary: ThreadSummaryStatus::Unknown,
1400            bundled_latest_thread_event: None,
1401        };
1402
1403        let serialized = serde_json::to_value(&room_event).unwrap();
1404
1405        // Test that the serialization is as expected
1406        assert_eq!(
1407            serialized,
1408            json!({
1409                "kind": {
1410                    "Decrypted": {
1411                        "event": {
1412                            "content": {"body": "secret", "msgtype": "m.text"},
1413                            "event_id": "$xxxxx:example.org",
1414                            "origin_server_ts": 2189,
1415                            "room_id": "!someroom:example.com",
1416                            "sender": "@carl:example.com",
1417                            "type": "m.room.message",
1418                        },
1419                        "encryption_info": {
1420                            "sender": "@sender:example.com",
1421                            "sender_device": null,
1422                            "algorithm_info": {
1423                                "MegolmV1AesSha2": {
1424                                    "curve25519_key": "xxx",
1425                                    "sender_claimed_keys": {},
1426                                    "session_id": "xyz",
1427                                }
1428                            },
1429                            "verification_state": "Verified",
1430                        },
1431                        "unsigned_encryption_info": {
1432                            "RelationsReplace": {"UnableToDecrypt": {
1433                                "session_id": "xyz",
1434                                "reason": "MalformedEncryptedEvent",
1435                            }}
1436                        }
1437                    }
1438                }
1439            })
1440        );
1441
1442        // And it can be properly deserialized from the new format.
1443        let event: TimelineEvent = serde_json::from_value(serialized).unwrap();
1444        assert_eq!(event.event_id(), Some(event_id!("$xxxxx:example.org").to_owned()));
1445        assert_matches!(
1446            event.encryption_info().unwrap().algorithm_info,
1447            AlgorithmInfo::MegolmV1AesSha2 { .. }
1448        );
1449
1450        // Test that the previous format can also be deserialized.
1451        let serialized = json!({
1452            "event": {
1453                "content": {"body": "secret", "msgtype": "m.text"},
1454                "event_id": "$xxxxx:example.org",
1455                "origin_server_ts": 2189,
1456                "room_id": "!someroom:example.com",
1457                "sender": "@carl:example.com",
1458                "type": "m.room.message",
1459            },
1460            "encryption_info": {
1461                "sender": "@sender:example.com",
1462                "sender_device": null,
1463                "algorithm_info": {
1464                    "MegolmV1AesSha2": {
1465                        "curve25519_key": "xxx",
1466                        "sender_claimed_keys": {}
1467                    }
1468                },
1469                "verification_state": "Verified",
1470            },
1471        });
1472        let event: TimelineEvent = serde_json::from_value(serialized).unwrap();
1473        assert_eq!(event.event_id(), Some(event_id!("$xxxxx:example.org").to_owned()));
1474        assert_matches!(
1475            event.encryption_info().unwrap().algorithm_info,
1476            AlgorithmInfo::MegolmV1AesSha2 { session_id: None, .. }
1477        );
1478
1479        // Test that the previous format, with an undecryptable unsigned event, can also
1480        // be deserialized.
1481        let serialized = json!({
1482            "event": {
1483                "content": {"body": "secret", "msgtype": "m.text"},
1484                "event_id": "$xxxxx:example.org",
1485                "origin_server_ts": 2189,
1486                "room_id": "!someroom:example.com",
1487                "sender": "@carl:example.com",
1488                "type": "m.room.message",
1489            },
1490            "encryption_info": {
1491                "sender": "@sender:example.com",
1492                "sender_device": null,
1493                "algorithm_info": {
1494                    "MegolmV1AesSha2": {
1495                        "curve25519_key": "xxx",
1496                        "sender_claimed_keys": {}
1497                    }
1498                },
1499                "verification_state": "Verified",
1500            },
1501            "unsigned_encryption_info": {
1502                "RelationsReplace": {"UnableToDecrypt": {"session_id": "xyz"}}
1503            }
1504        });
1505        let event: TimelineEvent = serde_json::from_value(serialized).unwrap();
1506        assert_eq!(event.event_id(), Some(event_id!("$xxxxx:example.org").to_owned()));
1507        assert_matches!(
1508            event.encryption_info().unwrap().algorithm_info,
1509            AlgorithmInfo::MegolmV1AesSha2 { .. }
1510        );
1511        assert_matches!(event.kind, TimelineEventKind::Decrypted(decrypted) => {
1512            assert_matches!(decrypted.unsigned_encryption_info, Some(map) => {
1513                assert_eq!(map.len(), 1);
1514                let (location, result) = map.into_iter().next().unwrap();
1515                assert_eq!(location, UnsignedEventLocation::RelationsReplace);
1516                assert_matches!(result, UnsignedDecryptionResult::UnableToDecrypt(utd_info) => {
1517                    assert_eq!(utd_info.session_id, Some("xyz".to_owned()));
1518                    assert_eq!(utd_info.reason, UnableToDecryptReason::Unknown);
1519                })
1520            });
1521        });
1522    }
1523
1524    #[test]
1525    fn test_creating_or_deserializing_an_event_extracts_summary() {
1526        let event = json!({
1527            "event_id": "$eid:example.com",
1528            "type": "m.room.message",
1529            "sender": "@alice:example.com",
1530            "origin_server_ts": 42,
1531            "content": {
1532                "body": "Hello, world!",
1533            },
1534            "unsigned": {
1535                "m.relations": {
1536                    "m.thread": {
1537                        "latest_event": {
1538                            "event_id": "$latest_event:example.com",
1539                            "type": "m.room.message",
1540                            "sender": "@bob:example.com",
1541                            "origin_server_ts": 42,
1542                            "content": {
1543                                "body": "Hello to you too!",
1544                                "msgtype": "m.text",
1545                            }
1546                        },
1547                        "count": 2,
1548                        "current_user_participated": true,
1549                    }
1550                }
1551            }
1552        });
1553
1554        let raw = Raw::new(&event).unwrap().cast();
1555
1556        // When creating a timeline event from a raw event, the thread summary is always
1557        // extracted, if available.
1558        let timeline_event = TimelineEvent::from_plaintext(raw);
1559        assert_matches!(timeline_event.thread_summary, ThreadSummaryStatus::Some(ThreadSummary { num_replies, latest_reply }) => {
1560            assert_eq!(num_replies, 2);
1561            assert_eq!(latest_reply.as_deref(), Some(event_id!("$latest_event:example.com")));
1562        });
1563
1564        assert!(timeline_event.bundled_latest_thread_event.is_some());
1565
1566        // When deserializing an old serialized timeline event, the thread summary is
1567        // also extracted, if it wasn't serialized.
1568        let serialized_timeline_item = json!({
1569            "kind": {
1570                "PlainText": {
1571                    "event": event
1572                }
1573            }
1574        });
1575
1576        let timeline_event: TimelineEvent =
1577            serde_json::from_value(serialized_timeline_item).unwrap();
1578        assert_matches!(timeline_event.thread_summary, ThreadSummaryStatus::Unknown);
1579
1580        // The bundled latest thread event is not persisted, so it should be `None` when
1581        // deserialized from a previously serialized `TimelineEvent`.
1582        assert!(timeline_event.bundled_latest_thread_event.is_none());
1583    }
1584
1585    #[test]
1586    fn sync_timeline_event_deserialisation_migration_for_withheld() {
1587        // Old serialized version was
1588        //    "utd_info": {
1589        //         "reason": "MissingMegolmSession",
1590        //         "session_id": "session000"
1591        //       }
1592
1593        // The new version would be
1594        //      "utd_info": {
1595        //         "reason": {
1596        //           "MissingMegolmSession": {
1597        //              "withheld_code": null
1598        //           }
1599        //         },
1600        //         "session_id": "session000"
1601        //       }
1602
1603        let serialized = json!({
1604             "kind": {
1605                "UnableToDecrypt": {
1606                  "event": {
1607                    "content": {
1608                      "algorithm": "m.megolm.v1.aes-sha2",
1609                      "ciphertext": "AwgAEoABzL1JYhqhjW9jXrlT3M6H8mJ4qffYtOQOnPuAPNxsuG20oiD/Fnpv6jnQGhU6YbV9pNM+1mRnTvxW3CbWOPjLKqCWTJTc7Q0vDEVtYePg38ncXNcwMmfhgnNAoW9S7vNs8C003x3yUl6NeZ8bH+ci870BZL+kWM/lMl10tn6U7snNmSjnE3ckvRdO+11/R4//5VzFQpZdf4j036lNSls/WIiI67Fk9iFpinz9xdRVWJFVdrAiPFwb8L5xRZ8aX+e2JDMlc1eW8gk",
1610                      "device_id": "SKCGPNUWAU",
1611                      "sender_key": "Gim/c7uQdSXyrrUbmUOrBT6sMC0gO7QSLmOK6B7NOm0",
1612                      "session_id": "hgLyeSqXfb8vc5AjQLsg6TSHVu0HJ7HZ4B6jgMvxkrs"
1613                    },
1614                    "event_id": "$xxxxx:example.org",
1615                    "origin_server_ts": 2189,
1616                    "room_id": "!someroom:example.com",
1617                    "sender": "@carl:example.com",
1618                    "type": "m.room.message"
1619                  },
1620                  "utd_info": {
1621                    "reason": "MissingMegolmSession",
1622                    "session_id": "session000"
1623                  }
1624                }
1625              }
1626        });
1627
1628        let result = serde_json::from_value(serialized);
1629        assert!(result.is_ok());
1630
1631        // should have migrated to the new format
1632        let event: TimelineEvent = result.unwrap();
1633        assert_matches!(
1634            event.kind,
1635            TimelineEventKind::UnableToDecrypt { utd_info, .. }=> {
1636                assert_matches!(
1637                    utd_info.reason,
1638                    UnableToDecryptReason::MissingMegolmSession { withheld_code: None }
1639                );
1640            }
1641        )
1642    }
1643
1644    #[test]
1645    fn unable_to_decrypt_info_migration_for_withheld() {
1646        let old_format = json!({
1647            "reason": "MissingMegolmSession",
1648            "session_id": "session000"
1649        });
1650
1651        let deserialized = serde_json::from_value::<UnableToDecryptInfo>(old_format).unwrap();
1652        let session_id = Some("session000".to_owned());
1653
1654        assert_eq!(deserialized.session_id, session_id);
1655        assert_eq!(
1656            deserialized.reason,
1657            UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
1658        );
1659
1660        let new_format = json!({
1661             "session_id": "session000",
1662              "reason": {
1663                "MissingMegolmSession": {
1664                  "withheld_code": null
1665                }
1666              }
1667        });
1668
1669        let deserialized = serde_json::from_value::<UnableToDecryptInfo>(new_format).unwrap();
1670
1671        assert_eq!(
1672            deserialized.reason,
1673            UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
1674        );
1675        assert_eq!(deserialized.session_id, session_id);
1676    }
1677
1678    #[test]
1679    fn unable_to_decrypt_reason_is_missing_room_key() {
1680        let reason = UnableToDecryptReason::MissingMegolmSession { withheld_code: None };
1681        assert!(reason.is_missing_room_key());
1682
1683        let reason = UnableToDecryptReason::MissingMegolmSession {
1684            withheld_code: Some(WithheldCode::Blacklisted),
1685        };
1686        assert!(!reason.is_missing_room_key());
1687
1688        let reason = UnableToDecryptReason::UnknownMegolmMessageIndex;
1689        assert!(reason.is_missing_room_key());
1690    }
1691
1692    #[test]
1693    fn snapshot_test_verification_level() {
1694        with_settings!({ prepend_module_to_snapshot => false }, {
1695            assert_json_snapshot!(VerificationLevel::VerificationViolation);
1696            assert_json_snapshot!(VerificationLevel::UnsignedDevice);
1697            assert_json_snapshot!(VerificationLevel::None(DeviceLinkProblem::InsecureSource));
1698            assert_json_snapshot!(VerificationLevel::None(DeviceLinkProblem::MissingDevice));
1699            assert_json_snapshot!(VerificationLevel::UnverifiedIdentity);
1700        });
1701    }
1702
1703    #[test]
1704    fn snapshot_test_verification_states() {
1705        with_settings!({ prepend_module_to_snapshot => false }, {
1706            assert_json_snapshot!(VerificationState::Unverified(VerificationLevel::UnsignedDevice));
1707            assert_json_snapshot!(VerificationState::Unverified(
1708                VerificationLevel::VerificationViolation
1709            ));
1710            assert_json_snapshot!(VerificationState::Unverified(VerificationLevel::None(
1711                DeviceLinkProblem::InsecureSource,
1712            )));
1713            assert_json_snapshot!(VerificationState::Unverified(VerificationLevel::None(
1714                DeviceLinkProblem::MissingDevice,
1715            )));
1716            assert_json_snapshot!(VerificationState::Verified);
1717        });
1718    }
1719
1720    #[test]
1721    fn snapshot_test_shield_states() {
1722        with_settings!({ prepend_module_to_snapshot => false }, {
1723            assert_json_snapshot!(ShieldState::None);
1724            assert_json_snapshot!(ShieldState::Red {
1725                code: ShieldStateCode::UnverifiedIdentity,
1726                message: "a message"
1727            });
1728            assert_json_snapshot!(ShieldState::Grey {
1729                code: ShieldStateCode::AuthenticityNotGuaranteed,
1730                message: "authenticity of this message cannot be guaranteed",
1731            });
1732        });
1733    }
1734
1735    #[test]
1736    fn snapshot_test_shield_codes() {
1737        with_settings!({ prepend_module_to_snapshot => false }, {
1738            assert_json_snapshot!(ShieldStateCode::AuthenticityNotGuaranteed);
1739            assert_json_snapshot!(ShieldStateCode::UnknownDevice);
1740            assert_json_snapshot!(ShieldStateCode::UnsignedDevice);
1741            assert_json_snapshot!(ShieldStateCode::UnverifiedIdentity);
1742            assert_json_snapshot!(ShieldStateCode::SentInClear);
1743            assert_json_snapshot!(ShieldStateCode::VerificationViolation);
1744        });
1745    }
1746
1747    #[test]
1748    fn snapshot_test_algorithm_info() {
1749        let mut map = BTreeMap::new();
1750        map.insert(DeviceKeyAlgorithm::Curve25519, "claimedclaimedcurve25519".to_owned());
1751        map.insert(DeviceKeyAlgorithm::Ed25519, "claimedclaimeded25519".to_owned());
1752        let info = AlgorithmInfo::MegolmV1AesSha2 {
1753            curve25519_key: "curvecurvecurve".into(),
1754            sender_claimed_keys: BTreeMap::from([
1755                (DeviceKeyAlgorithm::Curve25519, "claimedclaimedcurve25519".to_owned()),
1756                (DeviceKeyAlgorithm::Ed25519, "claimedclaimeded25519".to_owned()),
1757            ]),
1758            session_id: None,
1759        };
1760
1761        with_settings!({ prepend_module_to_snapshot => false }, {
1762            assert_json_snapshot!(info)
1763        });
1764    }
1765
1766    #[test]
1767    fn test_encryption_info_migration() {
1768        // In the old format the session_id was in the EncryptionInfo, now
1769        // it is moved to the `algorithm_info` struct.
1770        let old_format = json!({
1771          "sender": "@alice:localhost",
1772          "sender_device": "ABCDEFGH",
1773          "algorithm_info": {
1774            "MegolmV1AesSha2": {
1775              "curve25519_key": "curvecurvecurve",
1776              "sender_claimed_keys": {}
1777            }
1778          },
1779          "verification_state": "Verified",
1780          "session_id": "mysessionid76"
1781        });
1782
1783        let deserialized = serde_json::from_value::<EncryptionInfo>(old_format).unwrap();
1784        let expected_session_id = Some("mysessionid76".to_owned());
1785
1786        assert_let!(
1787            AlgorithmInfo::MegolmV1AesSha2 { session_id, .. } = deserialized.algorithm_info.clone()
1788        );
1789        assert_eq!(session_id, expected_session_id);
1790
1791        assert_json_snapshot!(deserialized);
1792    }
1793
1794    #[test]
1795    fn snapshot_test_encryption_info() {
1796        let info = EncryptionInfo {
1797            sender: user_id!("@alice:localhost").to_owned(),
1798            sender_device: Some(device_id!("ABCDEFGH").to_owned()),
1799            algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
1800                curve25519_key: "curvecurvecurve".into(),
1801                sender_claimed_keys: Default::default(),
1802                session_id: Some("mysessionid76".to_owned()),
1803            },
1804            verification_state: VerificationState::Verified,
1805        };
1806
1807        with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
1808            assert_json_snapshot!(info)
1809        })
1810    }
1811
1812    #[test]
1813    fn snapshot_test_sync_timeline_event() {
1814        let room_event = TimelineEvent {
1815            kind: TimelineEventKind::Decrypted(DecryptedRoomEvent {
1816                event: Raw::new(&example_event()).unwrap().cast(),
1817                encryption_info: Arc::new(EncryptionInfo {
1818                    sender: user_id!("@sender:example.com").to_owned(),
1819                    sender_device: Some(device_id!("ABCDEFGHIJ").to_owned()),
1820                    algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
1821                        curve25519_key: "xxx".to_owned(),
1822                        sender_claimed_keys: BTreeMap::from([
1823                            (
1824                                DeviceKeyAlgorithm::Ed25519,
1825                                "I3YsPwqMZQXHkSQbjFNEs7b529uac2xBpI83eN3LUXo".to_owned(),
1826                            ),
1827                            (
1828                                DeviceKeyAlgorithm::Curve25519,
1829                                "qzdW3F5IMPFl0HQgz5w/L5Oi/npKUFn8Um84acIHfPY".to_owned(),
1830                            ),
1831                        ]),
1832                        session_id: Some("mysessionid112".to_owned()),
1833                    },
1834                    verification_state: VerificationState::Verified,
1835                }),
1836                unsigned_encryption_info: Some(BTreeMap::from([(
1837                    UnsignedEventLocation::RelationsThreadLatestEvent,
1838                    UnsignedDecryptionResult::UnableToDecrypt(UnableToDecryptInfo {
1839                        session_id: Some("xyz".to_owned()),
1840                        reason: UnableToDecryptReason::MissingMegolmSession {
1841                            withheld_code: Some(WithheldCode::Unverified),
1842                        },
1843                    }),
1844                )])),
1845            }),
1846            push_actions: Default::default(),
1847            thread_summary: ThreadSummaryStatus::Some(ThreadSummary {
1848                num_replies: 2,
1849                latest_reply: None,
1850            }),
1851            bundled_latest_thread_event: None,
1852        };
1853
1854        with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
1855            // We use directly the serde_json formatter here, because of a bug in insta
1856            // not serializing custom BTreeMap key enum https://github.com/mitsuhiko/insta/issues/689
1857            assert_json_snapshot! {
1858                serde_json::to_value(&room_event).unwrap(),
1859            }
1860        });
1861    }
1862}