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