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}
286
287/// Struct containing information on how an event was decrypted.
288#[derive(Clone, Debug, Deserialize, Serialize)]
289pub struct EncryptionInfo {
290    /// The user ID of the event sender, note this is untrusted data unless the
291    /// `verification_state` is `Verified` as well.
292    pub sender: OwnedUserId,
293    /// The device ID of the device that sent us the event, note this is
294    /// untrusted data unless `verification_state` is `Verified` as well.
295    pub sender_device: Option<OwnedDeviceId>,
296    /// Information about the algorithm that was used to encrypt the event.
297    pub algorithm_info: AlgorithmInfo,
298    /// The verification state of the device that sent us the event, note this
299    /// is the state of the device at the time of decryption. It may change in
300    /// the future if a device gets verified or deleted.
301    ///
302    /// Callers that persist this should mark the state as dirty when a device
303    /// change is received down the sync.
304    pub verification_state: VerificationState,
305    /// The Megolm session ID that was used to encrypt this event, or None if
306    /// this info was stored before we collected this data.
307    pub session_id: Option<String>,
308}
309
310/// Represents a matrix room event that has been returned from `/sync`,
311/// after initial processing.
312///
313/// Previously, this differed from [`TimelineEvent`] by wrapping an
314/// [`AnySyncTimelineEvent`] instead of an [`AnyTimelineEvent`], but nowadays
315/// they are essentially identical, and one of them should probably be removed.
316//
317// 🚨 Note about this type, please read! 🚨
318//
319// `TimelineEvent` is heavily used across the SDK crates. In some cases, we
320// are reaching a [`recursion_limit`] when the compiler is trying to figure out
321// if `TimelineEvent` implements `Sync` when it's embedded in other types.
322//
323// We want to help the compiler so that one doesn't need to increase the
324// `recursion_limit`. We stop the recursive check by (un)safely implement `Sync`
325// and `Send` on `TimelineEvent` directly.
326//
327// See
328// https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823
329// which has addressed this issue first
330//
331// [`recursion_limit`]: https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute
332#[derive(Clone, Debug, Serialize)]
333pub struct TimelineEvent {
334    /// The event itself, together with any information on decryption.
335    pub kind: TimelineEventKind,
336
337    /// The push actions associated with this event.
338    ///
339    /// If it's set to `None`, then it means we couldn't compute those actions.
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub push_actions: Option<Vec<Action>>,
342}
343
344// See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.
345#[cfg(not(feature = "test-send-sync"))]
346unsafe impl Send for TimelineEvent {}
347
348// See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.
349#[cfg(not(feature = "test-send-sync"))]
350unsafe impl Sync for TimelineEvent {}
351
352#[cfg(feature = "test-send-sync")]
353#[test]
354// See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.
355fn test_send_sync_for_sync_timeline_event() {
356    fn assert_send_sync<T: Send + Sync>() {}
357
358    assert_send_sync::<TimelineEvent>();
359}
360
361impl TimelineEvent {
362    /// Create a new [`TimelineEvent`] from the given raw event.
363    ///
364    /// This is a convenience constructor for a plaintext event when you don't
365    /// need to set `push_action`, for example inside a test.
366    pub fn new(event: Raw<AnySyncTimelineEvent>) -> Self {
367        Self { kind: TimelineEventKind::PlainText { event }, push_actions: None }
368    }
369
370    /// Create a new [`TimelineEvent`] from the given raw event and push
371    /// actions.
372    ///
373    /// This is a convenience constructor for a plaintext event, for example
374    /// inside a test.
375    pub fn new_with_push_actions(
376        event: Raw<AnySyncTimelineEvent>,
377        push_actions: Vec<Action>,
378    ) -> Self {
379        Self { kind: TimelineEventKind::PlainText { event }, push_actions: Some(push_actions) }
380    }
381
382    /// Create a new [`TimelineEvent`] to represent the given decryption
383    /// failure.
384    pub fn new_utd_event(event: Raw<AnySyncTimelineEvent>, utd_info: UnableToDecryptInfo) -> Self {
385        Self { kind: TimelineEventKind::UnableToDecrypt { event, utd_info }, push_actions: None }
386    }
387
388    /// Get the event id of this [`TimelineEvent`] if the event has any valid
389    /// id.
390    pub fn event_id(&self) -> Option<OwnedEventId> {
391        self.kind.event_id()
392    }
393
394    /// Returns a reference to the (potentially decrypted) Matrix event inside
395    /// this [`TimelineEvent`].
396    pub fn raw(&self) -> &Raw<AnySyncTimelineEvent> {
397        self.kind.raw()
398    }
399
400    /// Replace the raw event included in this item by another one.
401    pub fn replace_raw(&mut self, replacement: Raw<AnyMessageLikeEvent>) {
402        match &mut self.kind {
403            TimelineEventKind::Decrypted(decrypted) => decrypted.event = replacement,
404            TimelineEventKind::UnableToDecrypt { event, .. }
405            | TimelineEventKind::PlainText { event } => {
406                // It's safe to cast `AnyMessageLikeEvent` into `AnySyncMessageLikeEvent`,
407                // because the former contains a superset of the fields included in the latter.
408                *event = replacement.cast();
409            }
410        }
411    }
412
413    /// If the event was a decrypted event that was successfully decrypted, get
414    /// its encryption info. Otherwise, `None`.
415    pub fn encryption_info(&self) -> Option<&EncryptionInfo> {
416        self.kind.encryption_info()
417    }
418
419    /// Takes ownership of this `TimelineEvent`, returning the (potentially
420    /// decrypted) Matrix event within.
421    pub fn into_raw(self) -> Raw<AnySyncTimelineEvent> {
422        self.kind.into_raw()
423    }
424}
425
426impl From<DecryptedRoomEvent> for TimelineEvent {
427    fn from(decrypted: DecryptedRoomEvent) -> Self {
428        Self { kind: TimelineEventKind::Decrypted(decrypted), push_actions: None }
429    }
430}
431
432impl<'de> Deserialize<'de> for TimelineEvent {
433    /// Custom deserializer for [`TimelineEvent`], to support older formats.
434    ///
435    /// Ideally we might use an untagged enum and then convert from that;
436    /// however, that doesn't work due to a [serde bug](https://github.com/serde-rs/json/issues/497).
437    ///
438    /// Instead, we first deserialize into an unstructured JSON map, and then
439    /// inspect the json to figure out which format we have.
440    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
441    where
442        D: serde::Deserializer<'de>,
443    {
444        use serde_json::{Map, Value};
445
446        // First, deserialize to an unstructured JSON map
447        let value = Map::<String, Value>::deserialize(deserializer)?;
448
449        // If we have a top-level `event`, it's V0
450        if value.contains_key("event") {
451            let v0: SyncTimelineEventDeserializationHelperV0 =
452                serde_json::from_value(Value::Object(value)).map_err(|e| {
453                    serde::de::Error::custom(format!(
454                        "Unable to deserialize V0-format TimelineEvent: {}",
455                        e
456                    ))
457                })?;
458            Ok(v0.into())
459        }
460        // Otherwise, it's V1
461        else {
462            let v1: SyncTimelineEventDeserializationHelperV1 =
463                serde_json::from_value(Value::Object(value)).map_err(|e| {
464                    serde::de::Error::custom(format!(
465                        "Unable to deserialize V1-format TimelineEvent: {}",
466                        e
467                    ))
468                })?;
469            Ok(v1.into())
470        }
471    }
472}
473
474/// The event within a [`TimelineEvent`], together with encryption data.
475#[derive(Clone, Serialize, Deserialize)]
476pub enum TimelineEventKind {
477    /// A successfully-decrypted encrypted event.
478    Decrypted(DecryptedRoomEvent),
479
480    /// An encrypted event which could not be decrypted.
481    UnableToDecrypt {
482        /// The `m.room.encrypted` event. Depending on the source of the event,
483        /// it could actually be an [`AnyTimelineEvent`] (i.e., it may
484        /// have a `room_id` property).
485        event: Raw<AnySyncTimelineEvent>,
486
487        /// Information on the reason we failed to decrypt
488        utd_info: UnableToDecryptInfo,
489    },
490
491    /// An unencrypted event.
492    PlainText {
493        /// The actual event. Depending on the source of the event, it could
494        /// actually be a [`AnyTimelineEvent`] (which differs from
495        /// [`AnySyncTimelineEvent`] by the addition of a `room_id` property).
496        event: Raw<AnySyncTimelineEvent>,
497    },
498}
499
500impl TimelineEventKind {
501    /// Returns a reference to the (potentially decrypted) Matrix event inside
502    /// this `TimelineEvent`.
503    pub fn raw(&self) -> &Raw<AnySyncTimelineEvent> {
504        match self {
505            // It is safe to cast from an `AnyMessageLikeEvent` (i.e. JSON which does
506            // *not* contain a `state_key` and *does* contain a `room_id`) into an
507            // `AnySyncTimelineEvent` (i.e. JSON which *may* contain a `state_key` and is *not*
508            // expected to contain a `room_id`). It just means that the `room_id` will be ignored
509            // in a future deserialization.
510            TimelineEventKind::Decrypted(d) => d.event.cast_ref(),
511            TimelineEventKind::UnableToDecrypt { event, .. } => event.cast_ref(),
512            TimelineEventKind::PlainText { event } => event,
513        }
514    }
515
516    /// Get the event id of this `TimelineEventKind` if the event has any valid
517    /// id.
518    pub fn event_id(&self) -> Option<OwnedEventId> {
519        self.raw().get_field::<OwnedEventId>("event_id").ok().flatten()
520    }
521
522    /// If the event was a decrypted event that was successfully decrypted, get
523    /// its encryption info. Otherwise, `None`.
524    pub fn encryption_info(&self) -> Option<&EncryptionInfo> {
525        match self {
526            TimelineEventKind::Decrypted(d) => Some(&d.encryption_info),
527            TimelineEventKind::UnableToDecrypt { .. } => None,
528            TimelineEventKind::PlainText { .. } => None,
529        }
530    }
531
532    /// Takes ownership of this `TimelineEvent`, returning the (potentially
533    /// decrypted) Matrix event within.
534    pub fn into_raw(self) -> Raw<AnySyncTimelineEvent> {
535        match self {
536            // It is safe to cast from an `AnyMessageLikeEvent` (i.e. JSON which does
537            // *not* contain a `state_key` and *does* contain a `room_id`) into an
538            // `AnySyncTimelineEvent` (i.e. JSON which *may* contain a `state_key` and is *not*
539            // expected to contain a `room_id`). It just means that the `room_id` will be ignored
540            // in a future deserialization.
541            TimelineEventKind::Decrypted(d) => d.event.cast(),
542            TimelineEventKind::UnableToDecrypt { event, .. } => event.cast(),
543            TimelineEventKind::PlainText { event } => event,
544        }
545    }
546
547    /// The Megolm session ID that was used to send this event, if it was
548    /// encrypted.
549    pub fn session_id(&self) -> Option<&str> {
550        match self {
551            TimelineEventKind::Decrypted(decrypted_room_event) => {
552                decrypted_room_event.encryption_info.session_id.as_ref()
553            }
554            TimelineEventKind::UnableToDecrypt { utd_info, .. } => utd_info.session_id.as_ref(),
555            TimelineEventKind::PlainText { .. } => None,
556        }
557        .map(String::as_str)
558    }
559}
560
561#[cfg(not(tarpaulin_include))]
562impl fmt::Debug for TimelineEventKind {
563    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
564        match &self {
565            Self::PlainText { event } => f
566                .debug_struct("TimelineEventDecryptionResult::PlainText")
567                .field("event", &DebugRawEvent(event))
568                .finish(),
569
570            Self::UnableToDecrypt { event, utd_info } => f
571                .debug_struct("TimelineEventDecryptionResult::UnableToDecrypt")
572                .field("event", &DebugRawEvent(event))
573                .field("utd_info", &utd_info)
574                .finish(),
575
576            Self::Decrypted(decrypted) => {
577                f.debug_tuple("TimelineEventDecryptionResult::Decrypted").field(decrypted).finish()
578            }
579        }
580    }
581}
582
583#[derive(Clone, Serialize, Deserialize)]
584/// A successfully-decrypted encrypted event.
585pub struct DecryptedRoomEvent {
586    /// The decrypted event.
587    ///
588    /// Note: it's not an error that this contains an `AnyMessageLikeEvent`: an
589    /// encrypted payload *always contains* a room id, by the [spec].
590    ///
591    /// [spec]: https://spec.matrix.org/v1.12/client-server-api/#mmegolmv1aes-sha2
592    pub event: Raw<AnyMessageLikeEvent>,
593
594    /// The encryption info about the event.
595    pub encryption_info: EncryptionInfo,
596
597    /// The encryption info about the events bundled in the `unsigned`
598    /// object.
599    ///
600    /// Will be `None` if no bundled event was encrypted.
601    #[serde(skip_serializing_if = "Option::is_none")]
602    pub unsigned_encryption_info: Option<BTreeMap<UnsignedEventLocation, UnsignedDecryptionResult>>,
603}
604
605#[cfg(not(tarpaulin_include))]
606impl fmt::Debug for DecryptedRoomEvent {
607    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608        let DecryptedRoomEvent { event, encryption_info, unsigned_encryption_info } = self;
609
610        f.debug_struct("DecryptedRoomEvent")
611            .field("event", &DebugRawEvent(event))
612            .field("encryption_info", encryption_info)
613            .maybe_field("unsigned_encryption_info", unsigned_encryption_info)
614            .finish()
615    }
616}
617
618/// The location of an event bundled in an `unsigned` object.
619#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
620pub enum UnsignedEventLocation {
621    /// An event at the `m.replace` key of the `m.relations` object, that is a
622    /// bundled replacement.
623    RelationsReplace,
624    /// An event at the `latest_event` key of the `m.thread` object of the
625    /// `m.relations` object, that is the latest event of a thread.
626    RelationsThreadLatestEvent,
627}
628
629impl UnsignedEventLocation {
630    /// Find the mutable JSON value at this location in the given unsigned
631    /// object.
632    ///
633    /// # Arguments
634    ///
635    /// * `unsigned` - The `unsigned` property of an event as a JSON object.
636    pub fn find_mut<'a>(&self, unsigned: &'a mut JsonObject) -> Option<&'a mut serde_json::Value> {
637        let relations = unsigned.get_mut("m.relations")?.as_object_mut()?;
638
639        match self {
640            Self::RelationsReplace => relations.get_mut("m.replace"),
641            Self::RelationsThreadLatestEvent => {
642                relations.get_mut("m.thread")?.as_object_mut()?.get_mut("latest_event")
643            }
644        }
645    }
646}
647
648/// The result of the decryption of an event bundled in an `unsigned` object.
649#[derive(Debug, Clone, Serialize, Deserialize)]
650pub enum UnsignedDecryptionResult {
651    /// The event was successfully decrypted.
652    Decrypted(EncryptionInfo),
653    /// The event failed to be decrypted.
654    UnableToDecrypt(UnableToDecryptInfo),
655}
656
657/// Metadata about an event that could not be decrypted.
658#[derive(Debug, Clone, Serialize, Deserialize)]
659pub struct UnableToDecryptInfo {
660    /// The ID of the session used to encrypt the message, if it used the
661    /// `m.megolm.v1.aes-sha2` algorithm.
662    #[serde(skip_serializing_if = "Option::is_none")]
663    pub session_id: Option<String>,
664
665    /// Reason code for the decryption failure
666    #[serde(default = "unknown_utd_reason", deserialize_with = "deserialize_utd_reason")]
667    pub reason: UnableToDecryptReason,
668}
669
670fn unknown_utd_reason() -> UnableToDecryptReason {
671    UnableToDecryptReason::Unknown
672}
673
674/// Provides basic backward compatibility for deserializing older serialized
675/// `UnableToDecryptReason` values.
676pub fn deserialize_utd_reason<'de, D>(d: D) -> Result<UnableToDecryptReason, D::Error>
677where
678    D: serde::Deserializer<'de>,
679{
680    // Start by deserializing as to an untyped JSON value.
681    let v: serde_json::Value = Deserialize::deserialize(d)?;
682    // Backwards compatibility: `MissingMegolmSession` used to be stored without the
683    // withheld code.
684    if v.as_str().is_some_and(|s| s == "MissingMegolmSession") {
685        return Ok(UnableToDecryptReason::MissingMegolmSession { withheld_code: None });
686    }
687    // Otherwise, use the derived deserialize impl to turn the JSON into a
688    // UnableToDecryptReason
689    serde_json::from_value::<UnableToDecryptReason>(v).map_err(serde::de::Error::custom)
690}
691
692/// Reason code for a decryption failure
693#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
694pub enum UnableToDecryptReason {
695    /// The reason for the decryption failure is unknown. This is only intended
696    /// for use when deserializing old UnableToDecryptInfo instances.
697    #[doc(hidden)]
698    Unknown,
699
700    /// The `m.room.encrypted` event that should have been decrypted is
701    /// malformed in some way (e.g. unsupported algorithm, missing fields,
702    /// unknown megolm message type).
703    MalformedEncryptedEvent,
704
705    /// Decryption failed because we're missing the megolm session that was used
706    /// to encrypt the event.
707    MissingMegolmSession {
708        /// If the key was withheld on purpose, the associated code. `None`
709        /// means no withheld code was received.
710        withheld_code: Option<WithheldCode>,
711    },
712
713    /// Decryption failed because, while we have the megolm session that was
714    /// used to encrypt the message, it is ratcheted too far forward.
715    UnknownMegolmMessageIndex,
716
717    /// We found the Megolm session, but were unable to decrypt the event using
718    /// that session for some reason (e.g. incorrect MAC).
719    ///
720    /// This represents all `vodozemac::megolm::DecryptionError`s, except
721    /// `UnknownMessageIndex`, which is represented as
722    /// `UnknownMegolmMessageIndex`.
723    MegolmDecryptionFailure,
724
725    /// The event could not be deserialized after decryption.
726    PayloadDeserializationFailure,
727
728    /// Decryption failed because of a mismatch between the identity keys of the
729    /// device we received the room key from and the identity keys recorded in
730    /// the plaintext of the room key to-device message.
731    MismatchedIdentityKeys,
732
733    /// An encrypted message wasn't decrypted, because the sender's
734    /// cross-signing identity did not satisfy the requested
735    /// `TrustRequirement`.
736    SenderIdentityNotTrusted(VerificationLevel),
737}
738
739impl UnableToDecryptReason {
740    /// Returns true if this UTD is due to a missing room key (and hence might
741    /// resolve itself if we wait a bit.)
742    pub fn is_missing_room_key(&self) -> bool {
743        // In case of MissingMegolmSession with a withheld code we return false here
744        // given that this API is used to decide if waiting a bit will help.
745        matches!(
746            self,
747            Self::MissingMegolmSession { withheld_code: None } | Self::UnknownMegolmMessageIndex
748        )
749    }
750}
751
752/// A machine-readable code for why a Megolm key was not sent.
753///
754/// 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.
755#[derive(
756    Clone,
757    PartialEq,
758    Eq,
759    Hash,
760    AsStrAsRefStr,
761    AsRefStr,
762    FromString,
763    DebugAsRefStr,
764    SerializeAsRefStr,
765    DeserializeFromCowStr,
766)]
767pub enum WithheldCode {
768    /// the user/device was blacklisted.
769    #[ruma_enum(rename = "m.blacklisted")]
770    Blacklisted,
771
772    /// the user/devices is unverified.
773    #[ruma_enum(rename = "m.unverified")]
774    Unverified,
775
776    /// The user/device is not allowed have the key. For example, this would
777    /// usually be sent in response to a key request if the user was not in
778    /// the room when the message was sent.
779    #[ruma_enum(rename = "m.unauthorised")]
780    Unauthorised,
781
782    /// Sent in reply to a key request if the device that the key is requested
783    /// from does not have the requested key.
784    #[ruma_enum(rename = "m.unavailable")]
785    Unavailable,
786
787    /// An olm session could not be established.
788    /// This may happen, for example, if the sender was unable to obtain a
789    /// one-time key from the recipient.
790    #[ruma_enum(rename = "m.no_olm")]
791    NoOlm,
792
793    #[doc(hidden)]
794    _Custom(PrivOwnedStr),
795}
796
797impl fmt::Display for WithheldCode {
798    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
799        let string = match self {
800            WithheldCode::Blacklisted => "The sender has blocked you.",
801            WithheldCode::Unverified => "The sender has disabled encrypting to unverified devices.",
802            WithheldCode::Unauthorised => "You are not authorised to read the message.",
803            WithheldCode::Unavailable => "The requested key was not found.",
804            WithheldCode::NoOlm => "Unable to establish a secure channel.",
805            _ => self.as_str(),
806        };
807
808        f.write_str(string)
809    }
810}
811
812// The Ruma macro expects the type to have this name.
813// The payload is counter intuitively made public in order to avoid having
814// multiple copies of this struct.
815#[doc(hidden)]
816#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
817pub struct PrivOwnedStr(pub Box<str>);
818
819#[cfg(not(tarpaulin_include))]
820impl fmt::Debug for PrivOwnedStr {
821    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
822        self.0.fmt(f)
823    }
824}
825
826/// Deserialization helper for [`TimelineEvent`], for the modern format.
827///
828/// This has the exact same fields as [`TimelineEvent`] itself, but has a
829/// regular `Deserialize` implementation.
830#[derive(Debug, Deserialize)]
831struct SyncTimelineEventDeserializationHelperV1 {
832    /// The event itself, together with any information on decryption.
833    kind: TimelineEventKind,
834
835    /// The push actions associated with this event.
836    #[serde(default)]
837    push_actions: Vec<Action>,
838}
839
840impl From<SyncTimelineEventDeserializationHelperV1> for TimelineEvent {
841    fn from(value: SyncTimelineEventDeserializationHelperV1) -> Self {
842        let SyncTimelineEventDeserializationHelperV1 { kind, push_actions } = value;
843        TimelineEvent { kind, push_actions: Some(push_actions) }
844    }
845}
846
847/// Deserialization helper for [`TimelineEvent`], for an older format.
848#[derive(Deserialize)]
849struct SyncTimelineEventDeserializationHelperV0 {
850    /// The actual event.
851    event: Raw<AnySyncTimelineEvent>,
852
853    /// The encryption info about the event. Will be `None` if the event
854    /// was not encrypted.
855    encryption_info: Option<EncryptionInfo>,
856
857    /// The push actions associated with this event.
858    #[serde(default)]
859    push_actions: Vec<Action>,
860
861    /// The encryption info about the events bundled in the `unsigned`
862    /// object.
863    ///
864    /// Will be `None` if no bundled event was encrypted.
865    unsigned_encryption_info: Option<BTreeMap<UnsignedEventLocation, UnsignedDecryptionResult>>,
866}
867
868impl From<SyncTimelineEventDeserializationHelperV0> for TimelineEvent {
869    fn from(value: SyncTimelineEventDeserializationHelperV0) -> Self {
870        let SyncTimelineEventDeserializationHelperV0 {
871            event,
872            encryption_info,
873            push_actions,
874            unsigned_encryption_info,
875        } = value;
876
877        let kind = match encryption_info {
878            Some(encryption_info) => {
879                TimelineEventKind::Decrypted(DecryptedRoomEvent {
880                    // We cast from `Raw<AnySyncTimelineEvent>` to
881                    // `Raw<AnyMessageLikeEvent>`, which means
882                    // we are asserting that it contains a room_id.
883                    // That *should* be ok, because if this is genuinely a decrypted
884                    // room event (as the encryption_info indicates), then it will have
885                    // a room_id.
886                    event: event.cast(),
887                    encryption_info,
888                    unsigned_encryption_info,
889                })
890            }
891
892            None => TimelineEventKind::PlainText { event },
893        };
894
895        TimelineEvent { kind, push_actions: Some(push_actions) }
896    }
897}
898
899#[cfg(test)]
900mod tests {
901    use std::collections::BTreeMap;
902
903    use assert_matches::assert_matches;
904    use insta::{assert_json_snapshot, with_settings};
905    use ruma::{
906        device_id, event_id, events::room::message::RoomMessageEventContent, serde::Raw, user_id,
907        DeviceKeyAlgorithm,
908    };
909    use serde::Deserialize;
910    use serde_json::json;
911
912    use super::{
913        AlgorithmInfo, DecryptedRoomEvent, DeviceLinkProblem, EncryptionInfo, ShieldState,
914        ShieldStateCode, TimelineEvent, TimelineEventKind, UnableToDecryptInfo,
915        UnableToDecryptReason, UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel,
916        VerificationState, WithheldCode,
917    };
918
919    fn example_event() -> serde_json::Value {
920        json!({
921            "content": RoomMessageEventContent::text_plain("secret"),
922            "type": "m.room.message",
923            "event_id": "$xxxxx:example.org",
924            "room_id": "!someroom:example.com",
925            "origin_server_ts": 2189,
926            "sender": "@carl:example.com",
927        })
928    }
929
930    #[test]
931    fn sync_timeline_debug_content() {
932        let room_event = TimelineEvent::new(Raw::new(&example_event()).unwrap().cast());
933        let debug_s = format!("{room_event:?}");
934        assert!(
935            !debug_s.contains("secret"),
936            "Debug representation contains event content!\n{debug_s}"
937        );
938    }
939
940    #[test]
941    fn old_verification_state_to_new_migration() {
942        #[derive(Deserialize)]
943        struct State {
944            state: VerificationState,
945        }
946
947        let state = json!({
948            "state": "Trusted",
949        });
950        let deserialized: State =
951            serde_json::from_value(state).expect("We can deserialize the old trusted value");
952        assert_eq!(deserialized.state, VerificationState::Verified);
953
954        let state = json!({
955            "state": "UnknownDevice",
956        });
957
958        let deserialized: State =
959            serde_json::from_value(state).expect("We can deserialize the old unknown device value");
960
961        assert_eq!(
962            deserialized.state,
963            VerificationState::Unverified(VerificationLevel::None(
964                DeviceLinkProblem::MissingDevice
965            ))
966        );
967
968        let state = json!({
969            "state": "Untrusted",
970        });
971        let deserialized: State =
972            serde_json::from_value(state).expect("We can deserialize the old trusted value");
973
974        assert_eq!(
975            deserialized.state,
976            VerificationState::Unverified(VerificationLevel::UnsignedDevice)
977        );
978    }
979
980    #[test]
981    fn test_verification_level_deserializes() {
982        // Given a JSON VerificationLevel
983        #[derive(Deserialize)]
984        struct Container {
985            verification_level: VerificationLevel,
986        }
987        let container = json!({ "verification_level": "VerificationViolation" });
988
989        // When we deserialize it
990        let deserialized: Container = serde_json::from_value(container)
991            .expect("We can deserialize the old PreviouslyVerified value");
992
993        // Then it is populated correctly
994        assert_eq!(deserialized.verification_level, VerificationLevel::VerificationViolation);
995    }
996
997    #[test]
998    fn test_verification_level_deserializes_from_old_previously_verified_value() {
999        // Given a JSON VerificationLevel with the old value PreviouslyVerified
1000        #[derive(Deserialize)]
1001        struct Container {
1002            verification_level: VerificationLevel,
1003        }
1004        let container = json!({ "verification_level": "PreviouslyVerified" });
1005
1006        // When we deserialize it
1007        let deserialized: Container = serde_json::from_value(container)
1008            .expect("We can deserialize the old PreviouslyVerified value");
1009
1010        // Then it is migrated to the new value
1011        assert_eq!(deserialized.verification_level, VerificationLevel::VerificationViolation);
1012    }
1013
1014    #[test]
1015    fn test_shield_state_code_deserializes() {
1016        // Given a JSON ShieldStateCode with value VerificationViolation
1017        #[derive(Deserialize)]
1018        struct Container {
1019            shield_state_code: ShieldStateCode,
1020        }
1021        let container = json!({ "shield_state_code": "VerificationViolation" });
1022
1023        // When we deserialize it
1024        let deserialized: Container = serde_json::from_value(container)
1025            .expect("We can deserialize the old PreviouslyVerified value");
1026
1027        // Then it is populated correctly
1028        assert_eq!(deserialized.shield_state_code, ShieldStateCode::VerificationViolation);
1029    }
1030
1031    #[test]
1032    fn test_shield_state_code_deserializes_from_old_previously_verified_value() {
1033        // Given a JSON ShieldStateCode with the old value PreviouslyVerified
1034        #[derive(Deserialize)]
1035        struct Container {
1036            shield_state_code: ShieldStateCode,
1037        }
1038        let container = json!({ "shield_state_code": "PreviouslyVerified" });
1039
1040        // When we deserialize it
1041        let deserialized: Container = serde_json::from_value(container)
1042            .expect("We can deserialize the old PreviouslyVerified value");
1043
1044        // Then it is migrated to the new value
1045        assert_eq!(deserialized.shield_state_code, ShieldStateCode::VerificationViolation);
1046    }
1047
1048    #[test]
1049    fn sync_timeline_event_serialisation() {
1050        let room_event = TimelineEvent {
1051            kind: TimelineEventKind::Decrypted(DecryptedRoomEvent {
1052                event: Raw::new(&example_event()).unwrap().cast(),
1053                encryption_info: EncryptionInfo {
1054                    sender: user_id!("@sender:example.com").to_owned(),
1055                    sender_device: None,
1056                    algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
1057                        curve25519_key: "xxx".to_owned(),
1058                        sender_claimed_keys: Default::default(),
1059                    },
1060                    verification_state: VerificationState::Verified,
1061                    session_id: Some("xyz".to_owned()),
1062                },
1063                unsigned_encryption_info: Some(BTreeMap::from([(
1064                    UnsignedEventLocation::RelationsReplace,
1065                    UnsignedDecryptionResult::UnableToDecrypt(UnableToDecryptInfo {
1066                        session_id: Some("xyz".to_owned()),
1067                        reason: UnableToDecryptReason::MalformedEncryptedEvent,
1068                    }),
1069                )])),
1070            }),
1071            push_actions: Default::default(),
1072        };
1073
1074        let serialized = serde_json::to_value(&room_event).unwrap();
1075
1076        // Test that the serialization is as expected
1077        assert_eq!(
1078            serialized,
1079            json!({
1080                "kind": {
1081                    "Decrypted": {
1082                        "event": {
1083                            "content": {"body": "secret", "msgtype": "m.text"},
1084                            "event_id": "$xxxxx:example.org",
1085                            "origin_server_ts": 2189,
1086                            "room_id": "!someroom:example.com",
1087                            "sender": "@carl:example.com",
1088                            "type": "m.room.message",
1089                        },
1090                        "encryption_info": {
1091                            "sender": "@sender:example.com",
1092                            "sender_device": null,
1093                            "algorithm_info": {
1094                                "MegolmV1AesSha2": {
1095                                    "curve25519_key": "xxx",
1096                                    "sender_claimed_keys": {}
1097                                }
1098                            },
1099                            "verification_state": "Verified",
1100                            "session_id": "xyz",
1101                        },
1102                        "unsigned_encryption_info": {
1103                            "RelationsReplace": {"UnableToDecrypt": {
1104                                "session_id": "xyz",
1105                                "reason": "MalformedEncryptedEvent",
1106                            }}
1107                        }
1108                    }
1109                }
1110            })
1111        );
1112
1113        // And it can be properly deserialized from the new format.
1114        let event: TimelineEvent = serde_json::from_value(serialized).unwrap();
1115        assert_eq!(event.event_id(), Some(event_id!("$xxxxx:example.org").to_owned()));
1116        assert_matches!(
1117            event.encryption_info().unwrap().algorithm_info,
1118            AlgorithmInfo::MegolmV1AesSha2 { .. }
1119        );
1120
1121        // Test that the previous format can also be deserialized.
1122        let serialized = json!({
1123            "event": {
1124                "content": {"body": "secret", "msgtype": "m.text"},
1125                "event_id": "$xxxxx:example.org",
1126                "origin_server_ts": 2189,
1127                "room_id": "!someroom:example.com",
1128                "sender": "@carl:example.com",
1129                "type": "m.room.message",
1130            },
1131            "encryption_info": {
1132                "sender": "@sender:example.com",
1133                "sender_device": null,
1134                "algorithm_info": {
1135                    "MegolmV1AesSha2": {
1136                        "curve25519_key": "xxx",
1137                        "sender_claimed_keys": {}
1138                    }
1139                },
1140                "verification_state": "Verified",
1141            },
1142        });
1143        let event: TimelineEvent = serde_json::from_value(serialized).unwrap();
1144        assert_eq!(event.event_id(), Some(event_id!("$xxxxx:example.org").to_owned()));
1145        assert_matches!(
1146            event.encryption_info().unwrap().algorithm_info,
1147            AlgorithmInfo::MegolmV1AesSha2 { .. }
1148        );
1149        assert_eq!(event.encryption_info().unwrap().session_id, None);
1150
1151        // Test that the previous format, with an undecryptable unsigned event, can also
1152        // be deserialized.
1153        let serialized = json!({
1154            "event": {
1155                "content": {"body": "secret", "msgtype": "m.text"},
1156                "event_id": "$xxxxx:example.org",
1157                "origin_server_ts": 2189,
1158                "room_id": "!someroom:example.com",
1159                "sender": "@carl:example.com",
1160                "type": "m.room.message",
1161            },
1162            "encryption_info": {
1163                "sender": "@sender:example.com",
1164                "sender_device": null,
1165                "algorithm_info": {
1166                    "MegolmV1AesSha2": {
1167                        "curve25519_key": "xxx",
1168                        "sender_claimed_keys": {}
1169                    }
1170                },
1171                "verification_state": "Verified",
1172            },
1173            "unsigned_encryption_info": {
1174                "RelationsReplace": {"UnableToDecrypt": {"session_id": "xyz"}}
1175            }
1176        });
1177        let event: TimelineEvent = serde_json::from_value(serialized).unwrap();
1178        assert_eq!(event.event_id(), Some(event_id!("$xxxxx:example.org").to_owned()));
1179        assert_matches!(
1180            event.encryption_info().unwrap().algorithm_info,
1181            AlgorithmInfo::MegolmV1AesSha2 { .. }
1182        );
1183        assert_matches!(event.kind, TimelineEventKind::Decrypted(decrypted) => {
1184            assert_matches!(decrypted.unsigned_encryption_info, Some(map) => {
1185                assert_eq!(map.len(), 1);
1186                let (location, result) = map.into_iter().next().unwrap();
1187                assert_eq!(location, UnsignedEventLocation::RelationsReplace);
1188                assert_matches!(result, UnsignedDecryptionResult::UnableToDecrypt(utd_info) => {
1189                    assert_eq!(utd_info.session_id, Some("xyz".to_owned()));
1190                    assert_eq!(utd_info.reason, UnableToDecryptReason::Unknown);
1191                })
1192            });
1193        });
1194    }
1195
1196    #[test]
1197    fn sync_timeline_event_deserialisation_migration_for_withheld() {
1198        // Old serialized version was
1199        //    "utd_info": {
1200        //         "reason": "MissingMegolmSession",
1201        //         "session_id": "session000"
1202        //       }
1203
1204        // The new version would be
1205        //      "utd_info": {
1206        //         "reason": {
1207        //           "MissingMegolmSession": {
1208        //              "withheld_code": null
1209        //           }
1210        //         },
1211        //         "session_id": "session000"
1212        //       }
1213
1214        let serialized = json!({
1215             "kind": {
1216                "UnableToDecrypt": {
1217                  "event": {
1218                    "content": {
1219                      "algorithm": "m.megolm.v1.aes-sha2",
1220                      "ciphertext": "AwgAEoABzL1JYhqhjW9jXrlT3M6H8mJ4qffYtOQOnPuAPNxsuG20oiD/Fnpv6jnQGhU6YbV9pNM+1mRnTvxW3CbWOPjLKqCWTJTc7Q0vDEVtYePg38ncXNcwMmfhgnNAoW9S7vNs8C003x3yUl6NeZ8bH+ci870BZL+kWM/lMl10tn6U7snNmSjnE3ckvRdO+11/R4//5VzFQpZdf4j036lNSls/WIiI67Fk9iFpinz9xdRVWJFVdrAiPFwb8L5xRZ8aX+e2JDMlc1eW8gk",
1221                      "device_id": "SKCGPNUWAU",
1222                      "sender_key": "Gim/c7uQdSXyrrUbmUOrBT6sMC0gO7QSLmOK6B7NOm0",
1223                      "session_id": "hgLyeSqXfb8vc5AjQLsg6TSHVu0HJ7HZ4B6jgMvxkrs"
1224                    },
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                  "utd_info": {
1232                    "reason": "MissingMegolmSession",
1233                    "session_id": "session000"
1234                  }
1235                }
1236              }
1237        });
1238
1239        let result = serde_json::from_value(serialized);
1240        assert!(result.is_ok());
1241
1242        // should have migrated to the new format
1243        let event: TimelineEvent = result.unwrap();
1244        assert_matches!(
1245            event.kind,
1246            TimelineEventKind::UnableToDecrypt { utd_info, .. }=> {
1247                assert_matches!(
1248                    utd_info.reason,
1249                    UnableToDecryptReason::MissingMegolmSession { withheld_code: None }
1250                );
1251            }
1252        )
1253    }
1254
1255    #[test]
1256    fn unable_to_decrypt_info_migration_for_withheld() {
1257        let old_format = json!({
1258            "reason": "MissingMegolmSession",
1259            "session_id": "session000"
1260        });
1261
1262        let deserialized = serde_json::from_value::<UnableToDecryptInfo>(old_format).unwrap();
1263        let session_id = Some("session000".to_owned());
1264
1265        assert_eq!(deserialized.session_id, session_id);
1266        assert_eq!(
1267            deserialized.reason,
1268            UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
1269        );
1270
1271        let new_format = json!({
1272             "session_id": "session000",
1273              "reason": {
1274                "MissingMegolmSession": {
1275                  "withheld_code": null
1276                }
1277              }
1278        });
1279
1280        let deserialized = serde_json::from_value::<UnableToDecryptInfo>(new_format).unwrap();
1281
1282        assert_eq!(
1283            deserialized.reason,
1284            UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
1285        );
1286        assert_eq!(deserialized.session_id, session_id);
1287    }
1288
1289    #[test]
1290    fn unable_to_decrypt_reason_is_missing_room_key() {
1291        let reason = UnableToDecryptReason::MissingMegolmSession { withheld_code: None };
1292        assert!(reason.is_missing_room_key());
1293
1294        let reason = UnableToDecryptReason::MissingMegolmSession {
1295            withheld_code: Some(WithheldCode::Blacklisted),
1296        };
1297        assert!(!reason.is_missing_room_key());
1298
1299        let reason = UnableToDecryptReason::UnknownMegolmMessageIndex;
1300        assert!(reason.is_missing_room_key());
1301    }
1302
1303    #[test]
1304    fn snapshot_test_verification_level() {
1305        with_settings!({ prepend_module_to_snapshot => false }, {
1306            assert_json_snapshot!(VerificationLevel::VerificationViolation);
1307            assert_json_snapshot!(VerificationLevel::UnsignedDevice);
1308            assert_json_snapshot!(VerificationLevel::None(DeviceLinkProblem::InsecureSource));
1309            assert_json_snapshot!(VerificationLevel::None(DeviceLinkProblem::MissingDevice));
1310            assert_json_snapshot!(VerificationLevel::UnverifiedIdentity);
1311        });
1312    }
1313
1314    #[test]
1315    fn snapshot_test_verification_states() {
1316        with_settings!({ prepend_module_to_snapshot => false }, {
1317            assert_json_snapshot!(VerificationState::Unverified(VerificationLevel::UnsignedDevice));
1318            assert_json_snapshot!(VerificationState::Unverified(
1319                VerificationLevel::VerificationViolation
1320            ));
1321            assert_json_snapshot!(VerificationState::Unverified(VerificationLevel::None(
1322                DeviceLinkProblem::InsecureSource,
1323            )));
1324            assert_json_snapshot!(VerificationState::Unverified(VerificationLevel::None(
1325                DeviceLinkProblem::MissingDevice,
1326            )));
1327            assert_json_snapshot!(VerificationState::Verified);
1328        });
1329    }
1330
1331    #[test]
1332    fn snapshot_test_shield_states() {
1333        with_settings!({ prepend_module_to_snapshot => false }, {
1334            assert_json_snapshot!(ShieldState::None);
1335            assert_json_snapshot!(ShieldState::Red {
1336                code: ShieldStateCode::UnverifiedIdentity,
1337                message: "a message"
1338            });
1339            assert_json_snapshot!(ShieldState::Grey {
1340                code: ShieldStateCode::AuthenticityNotGuaranteed,
1341                message: "authenticity of this message cannot be guaranteed",
1342            });
1343        });
1344    }
1345
1346    #[test]
1347    fn snapshot_test_shield_codes() {
1348        with_settings!({ prepend_module_to_snapshot => false }, {
1349            assert_json_snapshot!(ShieldStateCode::AuthenticityNotGuaranteed);
1350            assert_json_snapshot!(ShieldStateCode::UnknownDevice);
1351            assert_json_snapshot!(ShieldStateCode::UnsignedDevice);
1352            assert_json_snapshot!(ShieldStateCode::UnverifiedIdentity);
1353            assert_json_snapshot!(ShieldStateCode::SentInClear);
1354            assert_json_snapshot!(ShieldStateCode::VerificationViolation);
1355        });
1356    }
1357
1358    #[test]
1359    fn snapshot_test_algorithm_info() {
1360        let mut map = BTreeMap::new();
1361        map.insert(DeviceKeyAlgorithm::Curve25519, "claimedclaimedcurve25519".to_owned());
1362        map.insert(DeviceKeyAlgorithm::Ed25519, "claimedclaimeded25519".to_owned());
1363        let info = AlgorithmInfo::MegolmV1AesSha2 {
1364            curve25519_key: "curvecurvecurve".into(),
1365            sender_claimed_keys: BTreeMap::from([
1366                (DeviceKeyAlgorithm::Curve25519, "claimedclaimedcurve25519".to_owned()),
1367                (DeviceKeyAlgorithm::Ed25519, "claimedclaimeded25519".to_owned()),
1368            ]),
1369        };
1370
1371        with_settings!({ prepend_module_to_snapshot => false }, {
1372            assert_json_snapshot!(info)
1373        });
1374    }
1375
1376    #[test]
1377    fn snapshot_test_encryption_info() {
1378        let info = EncryptionInfo {
1379            sender: user_id!("@alice:localhost").to_owned(),
1380            sender_device: Some(device_id!("ABCDEFGH").to_owned()),
1381            algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
1382                curve25519_key: "curvecurvecurve".into(),
1383                sender_claimed_keys: Default::default(),
1384            },
1385            verification_state: VerificationState::Verified,
1386            session_id: Some("mysessionid76".to_owned()),
1387        };
1388
1389        with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
1390            assert_json_snapshot!(info)
1391        })
1392    }
1393
1394    #[test]
1395    fn snapshot_test_sync_timeline_event() {
1396        let room_event = TimelineEvent {
1397            kind: TimelineEventKind::Decrypted(DecryptedRoomEvent {
1398                event: Raw::new(&example_event()).unwrap().cast(),
1399                encryption_info: EncryptionInfo {
1400                    sender: user_id!("@sender:example.com").to_owned(),
1401                    sender_device: Some(device_id!("ABCDEFGHIJ").to_owned()),
1402                    algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
1403                        curve25519_key: "xxx".to_owned(),
1404                        sender_claimed_keys: BTreeMap::from([
1405                            (
1406                                DeviceKeyAlgorithm::Ed25519,
1407                                "I3YsPwqMZQXHkSQbjFNEs7b529uac2xBpI83eN3LUXo".to_owned(),
1408                            ),
1409                            (
1410                                DeviceKeyAlgorithm::Curve25519,
1411                                "qzdW3F5IMPFl0HQgz5w/L5Oi/npKUFn8Um84acIHfPY".to_owned(),
1412                            ),
1413                        ]),
1414                    },
1415                    verification_state: VerificationState::Verified,
1416                    session_id: Some("mysessionid112".to_owned()),
1417                },
1418                unsigned_encryption_info: Some(BTreeMap::from([(
1419                    UnsignedEventLocation::RelationsThreadLatestEvent,
1420                    UnsignedDecryptionResult::UnableToDecrypt(UnableToDecryptInfo {
1421                        session_id: Some("xyz".to_owned()),
1422                        reason: UnableToDecryptReason::MissingMegolmSession {
1423                            withheld_code: Some(WithheldCode::Unverified),
1424                        },
1425                    }),
1426                )])),
1427            }),
1428            push_actions: Default::default(),
1429        };
1430
1431        with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
1432            // We use directly the serde_json formatter here, because of a bug in insta
1433            // not serializing custom BTreeMap key enum https://github.com/mitsuhiko/insta/issues/689
1434            assert_json_snapshot! {
1435                serde_json::to_value(&room_event).unwrap(),
1436            }
1437        });
1438    }
1439}