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