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