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