matrix_sdk_crypto/types/events/
room_key_withheld.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
15//! Types for the `m.room_key.withheld` events.
16
17use std::collections::BTreeMap;
18
19use matrix_sdk_common::deserialized_responses::WithheldCode;
20use ruma::{
21    OwnedDeviceId, OwnedRoomId, RoomId, events::AnyToDeviceEventContent, serde::JsonCastable,
22};
23use serde::{Deserialize, Serialize};
24use serde_json::Value;
25use vodozemac::Curve25519PublicKey;
26
27use super::{EventType, ToDeviceEvent};
28use crate::types::{EventEncryptionAlgorithm, deserialize_curve_key, serialize_curve_key};
29
30/// The `m.room_key_request` to-device event.
31pub type RoomKeyWithheldEvent = ToDeviceEvent<RoomKeyWithheldContent>;
32
33impl Clone for RoomKeyWithheldEvent {
34    fn clone(&self) -> Self {
35        Self {
36            sender: self.sender.clone(),
37            content: self.content.clone(),
38            other: self.other.clone(),
39        }
40    }
41}
42
43/// The `m.room_key.withheld` event content.
44///
45/// This is an enum over the different room key algorithms we support.
46///
47/// Devices that purposely do not send megolm keys to a device may instead send
48/// an m.room_key.withheld event as a to-device message to the device to
49/// indicate that it should not expect to receive keys for the message.
50#[derive(Clone, Debug, Deserialize)]
51#[serde(try_from = "WithheldHelper")]
52pub enum RoomKeyWithheldContent {
53    /// The `m.megolm.v1.aes-sha2` variant of the `m.room_key.withheld` content.
54    MegolmV1AesSha2(MegolmV1AesSha2WithheldContent),
55    /// The `m.megolm.v2.aes-sha2` variant of the `m.room_key.withheld` content.
56    #[cfg(feature = "experimental-algorithms")]
57    MegolmV2AesSha2(MegolmV2AesSha2WithheldContent),
58    /// An unknown and unsupported variant of the `m.room_key.withheld` content.
59    Unknown(UnknownRoomKeyWithHeld),
60}
61
62macro_rules! construct_withheld_content {
63    ($algorithm:ident, $code:ident, $room_id:ident, $session_id:ident, $sender_key:ident, $from_device:ident) => {
64        match $code {
65            WithheldCode::Blacklisted
66            | WithheldCode::Unverified
67            | WithheldCode::Unauthorised
68            | WithheldCode::Unavailable
69            | WithheldCode::HistoryNotShared => {
70                let content = CommonWithheldCodeContent {
71                    $room_id,
72                    $session_id,
73                    $sender_key,
74                    $from_device,
75                    other: Default::default(),
76                };
77
78                RoomKeyWithheldContent::$algorithm(
79                    MegolmV1AesSha2WithheldContent::from_code_and_content($code, content),
80                )
81            }
82            WithheldCode::NoOlm => {
83                RoomKeyWithheldContent::$algorithm(MegolmV1AesSha2WithheldContent::NoOlm(
84                    NoOlmWithheldContent { $sender_key, $from_device, other: Default::default() }
85                        .into(),
86                ))
87            }
88            WithheldCode::_Custom(_) => {
89                unreachable!("Can't create an unknown withheld code content")
90            }
91        }
92    };
93}
94
95impl RoomKeyWithheldContent {
96    /// Creates a withheld content from the given info
97    ///
98    /// # Panics
99    ///
100    /// The method will panic if a unsupported algorithm is given. The only
101    /// supported algorithm as of now is `m.megolm.v1.aes-sha2`.
102    pub fn new(
103        algorithm: EventEncryptionAlgorithm,
104        code: WithheldCode,
105        room_id: OwnedRoomId,
106        session_id: String,
107        sender_key: Curve25519PublicKey,
108        from_device: OwnedDeviceId,
109    ) -> Self {
110        let from_device = Some(from_device);
111
112        match algorithm {
113            EventEncryptionAlgorithm::MegolmV1AesSha2 => {
114                construct_withheld_content!(
115                    MegolmV1AesSha2,
116                    code,
117                    room_id,
118                    session_id,
119                    sender_key,
120                    from_device
121                )
122            }
123            #[cfg(feature = "experimental-algorithms")]
124            EventEncryptionAlgorithm::MegolmV2AesSha2 => {
125                construct_withheld_content!(
126                    MegolmV2AesSha2,
127                    code,
128                    room_id,
129                    session_id,
130                    sender_key,
131                    from_device
132                )
133            }
134            _ => unreachable!("Unsupported algorithm {algorithm}"),
135        }
136    }
137
138    /// Get the withheld code of this event.
139    pub fn withheld_code(&self) -> WithheldCode {
140        match self {
141            RoomKeyWithheldContent::MegolmV1AesSha2(c) => c.withheld_code(),
142            #[cfg(feature = "experimental-algorithms")]
143            RoomKeyWithheldContent::MegolmV2AesSha2(c) => c.withheld_code(),
144            RoomKeyWithheldContent::Unknown(c) => c.code.to_owned(),
145        }
146    }
147
148    /// Get the algorithm of the room key withheld.
149    pub fn algorithm(&self) -> EventEncryptionAlgorithm {
150        match &self {
151            RoomKeyWithheldContent::MegolmV1AesSha2(_) => EventEncryptionAlgorithm::MegolmV1AesSha2,
152            #[cfg(feature = "experimental-algorithms")]
153            RoomKeyWithheldContent::MegolmV2AesSha2(_) => EventEncryptionAlgorithm::MegolmV2AesSha2,
154            RoomKeyWithheldContent::Unknown(c) => c.algorithm.to_owned(),
155        }
156    }
157
158    /// Get the room ID of the withheld session, if known
159    pub fn room_id(&self) -> Option<&RoomId> {
160        match &self {
161            RoomKeyWithheldContent::MegolmV1AesSha2(c) => c.room_id(),
162            #[cfg(feature = "experimental-algorithms")]
163            RoomKeyWithheldContent::MegolmV2AesSha2(c) => c.room_id(),
164            RoomKeyWithheldContent::Unknown(_) => None,
165        }
166    }
167
168    /// Get the megolm session ID of the withheld session, if it is in fact a
169    /// megolm session.
170    pub fn megolm_session_id(&self) -> Option<&str> {
171        match &self {
172            RoomKeyWithheldContent::MegolmV1AesSha2(c) => c.session_id(),
173            #[cfg(feature = "experimental-algorithms")]
174            RoomKeyWithheldContent::MegolmV2AesSha2(c) => c.session_id(),
175            RoomKeyWithheldContent::Unknown(_) => None,
176        }
177    }
178}
179
180impl EventType for RoomKeyWithheldContent {
181    const EVENT_TYPE: &'static str = "m.room_key.withheld";
182}
183
184impl JsonCastable<AnyToDeviceEventContent> for RoomKeyWithheldContent {}
185
186#[derive(Debug, Deserialize, Serialize)]
187struct WithheldHelper {
188    pub algorithm: EventEncryptionAlgorithm,
189    pub reason: Option<String>,
190    pub code: WithheldCode,
191    #[serde(flatten)]
192    other: Value,
193}
194
195/// Content for the `m.room_key.withheld` event for the `m.megolm.v1.aes-sha2`
196/// algorithm.
197#[derive(Clone, Debug)]
198pub enum MegolmV1AesSha2WithheldContent {
199    /// The `m.blacklisted` variant of the withheld code content.
200    BlackListed(Box<CommonWithheldCodeContent>),
201    /// The `m.unverified` variant of the withheld code content.
202    Unverified(Box<CommonWithheldCodeContent>),
203    /// The `m.unauthorised` variant of the withheld code content.
204    Unauthorised(Box<CommonWithheldCodeContent>),
205    /// The `m.unavailable` variant of the withheld code content.
206    Unavailable(Box<CommonWithheldCodeContent>),
207    /// The `m.history_not_shared` variant of the withheld code content (cf
208    /// [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268)).
209    HistoryNotShared(Box<CommonWithheldCodeContent>),
210    /// The `m.no_olm` variant of the withheld code content.
211    NoOlm(Box<NoOlmWithheldContent>),
212}
213
214/// Struct for most of the withheld code content variants.
215#[derive(Clone, PartialEq, Eq, Deserialize, Serialize)]
216pub struct CommonWithheldCodeContent {
217    /// The room where the key is used.
218    pub room_id: OwnedRoomId,
219
220    /// The ID of the session.
221    pub session_id: String,
222
223    /// The device curve25519 key of the session creator.
224    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
225    pub sender_key: Curve25519PublicKey,
226
227    /// The device ID of the device sending the m.room_key.withheld message
228    /// MSC3735.
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub from_device: Option<OwnedDeviceId>,
231
232    #[serde(flatten)]
233    other: BTreeMap<String, Value>,
234}
235
236impl CommonWithheldCodeContent {
237    /// Create a new common withheld code content.
238    pub fn new(
239        room_id: OwnedRoomId,
240        session_id: String,
241        sender_key: Curve25519PublicKey,
242        device_id: OwnedDeviceId,
243    ) -> Self {
244        Self {
245            room_id,
246            session_id,
247            sender_key,
248            from_device: Some(device_id),
249            other: Default::default(),
250        }
251    }
252}
253
254impl MegolmV1AesSha2WithheldContent {
255    /// Get the session ID for this content, if available.
256    pub fn session_id(&self) -> Option<&str> {
257        match self {
258            MegolmV1AesSha2WithheldContent::BlackListed(content)
259            | MegolmV1AesSha2WithheldContent::Unverified(content)
260            | MegolmV1AesSha2WithheldContent::Unauthorised(content)
261            | MegolmV1AesSha2WithheldContent::Unavailable(content)
262            | MegolmV1AesSha2WithheldContent::HistoryNotShared(content) => {
263                Some(&content.session_id)
264            }
265            MegolmV1AesSha2WithheldContent::NoOlm(_) => None,
266        }
267    }
268
269    /// Get the room ID for this content, if available.
270    pub fn room_id(&self) -> Option<&RoomId> {
271        match self {
272            MegolmV1AesSha2WithheldContent::BlackListed(content)
273            | MegolmV1AesSha2WithheldContent::Unverified(content)
274            | MegolmV1AesSha2WithheldContent::Unauthorised(content)
275            | MegolmV1AesSha2WithheldContent::Unavailable(content)
276            | MegolmV1AesSha2WithheldContent::HistoryNotShared(content) => Some(&content.room_id),
277            MegolmV1AesSha2WithheldContent::NoOlm(_) => None,
278        }
279    }
280
281    /// Get the withheld code for this content
282    pub fn withheld_code(&self) -> WithheldCode {
283        match self {
284            MegolmV1AesSha2WithheldContent::BlackListed(_) => WithheldCode::Blacklisted,
285            MegolmV1AesSha2WithheldContent::Unverified(_) => WithheldCode::Unverified,
286            MegolmV1AesSha2WithheldContent::Unauthorised(_) => WithheldCode::Unauthorised,
287            MegolmV1AesSha2WithheldContent::Unavailable(_) => WithheldCode::Unavailable,
288            MegolmV1AesSha2WithheldContent::HistoryNotShared(_) => WithheldCode::HistoryNotShared,
289            MegolmV1AesSha2WithheldContent::NoOlm(_) => WithheldCode::NoOlm,
290        }
291    }
292
293    fn from_code_and_content(code: WithheldCode, content: CommonWithheldCodeContent) -> Self {
294        let content = content.into();
295
296        match code {
297            WithheldCode::Blacklisted => Self::BlackListed(content),
298            WithheldCode::Unverified => Self::Unverified(content),
299            WithheldCode::Unauthorised => Self::Unauthorised(content),
300            WithheldCode::Unavailable => Self::Unavailable(content),
301            WithheldCode::HistoryNotShared => Self::HistoryNotShared(content),
302            WithheldCode::NoOlm | WithheldCode::_Custom(_) => {
303                unreachable!("This constructor requires one of the common withheld codes")
304            }
305        }
306    }
307}
308
309/// No olm content (no room_id nor session_id)
310#[derive(Clone, Deserialize, Serialize)]
311pub struct NoOlmWithheldContent {
312    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
313    /// The device curve25519 key of the session creator.
314    pub sender_key: Curve25519PublicKey,
315
316    /// The device ID of the device sending the m.room_key.withheld message
317    /// MSC3735.
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub from_device: Option<OwnedDeviceId>,
320
321    #[serde(flatten)]
322    other: BTreeMap<String, Value>,
323}
324
325#[cfg(not(tarpaulin_include))]
326impl std::fmt::Debug for CommonWithheldCodeContent {
327    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328        f.debug_struct("CommonWithheldCodeContent")
329            .field("room_id", &self.room_id)
330            .field("session_id", &self.session_id)
331            .field("sender_key", &self.sender_key)
332            .field("from_device", &self.from_device)
333            .finish_non_exhaustive()
334    }
335}
336
337#[cfg(not(tarpaulin_include))]
338impl std::fmt::Debug for NoOlmWithheldContent {
339    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340        f.debug_struct("NoOlmWithheldContent")
341            .field("sender_key", &self.sender_key)
342            .field("from_device", &self.from_device)
343            .finish_non_exhaustive()
344    }
345}
346
347/// Megolm V2 variant of withheld content
348pub type MegolmV2AesSha2WithheldContent = MegolmV1AesSha2WithheldContent;
349
350/// Withheld info for unknown/unsupported encryption alhg
351#[derive(Clone, Debug, Serialize, Deserialize)]
352pub struct UnknownRoomKeyWithHeld {
353    /// The algorithm of the unknown room key.
354    pub algorithm: EventEncryptionAlgorithm,
355    /// The withheld code
356    pub code: WithheldCode,
357    /// A human-readable reason for why the key was not sent.
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub reason: Option<String>,
360    /// The other data of the unknown room key.
361    #[serde(flatten)]
362    other: BTreeMap<String, Value>,
363}
364
365impl TryFrom<WithheldHelper> for RoomKeyWithheldContent {
366    type Error = serde_json::Error;
367
368    fn try_from(value: WithheldHelper) -> Result<Self, Self::Error> {
369        let unknown = |value: WithheldHelper| -> Result<RoomKeyWithheldContent, _> {
370            Ok(Self::Unknown(UnknownRoomKeyWithHeld {
371                algorithm: value.algorithm,
372                code: value.code,
373                reason: value.reason,
374                other: serde_json::from_value(value.other)?,
375            }))
376        };
377
378        Ok(match value.algorithm {
379            EventEncryptionAlgorithm::MegolmV1AesSha2 => match value.code {
380                WithheldCode::NoOlm => {
381                    let content: NoOlmWithheldContent = serde_json::from_value(value.other)?;
382                    Self::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::NoOlm(content.into()))
383                }
384                WithheldCode::Blacklisted
385                | WithheldCode::Unverified
386                | WithheldCode::Unauthorised
387                | WithheldCode::Unavailable
388                | WithheldCode::HistoryNotShared => {
389                    let content: CommonWithheldCodeContent = serde_json::from_value(value.other)?;
390
391                    Self::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::from_code_and_content(
392                        value.code, content,
393                    ))
394                }
395                WithheldCode::_Custom(_) => unknown(value)?,
396            },
397            #[cfg(feature = "experimental-algorithms")]
398            EventEncryptionAlgorithm::MegolmV2AesSha2 => match value.code {
399                WithheldCode::NoOlm => {
400                    let content: NoOlmWithheldContent = serde_json::from_value(value.other)?;
401                    Self::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::NoOlm(content.into()))
402                }
403                WithheldCode::Blacklisted
404                | WithheldCode::Unverified
405                | WithheldCode::Unauthorised
406                | WithheldCode::Unavailable
407                | WithheldCode::HistoryNotShared => {
408                    let content: CommonWithheldCodeContent = serde_json::from_value(value.other)?;
409
410                    Self::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::from_code_and_content(
411                        value.code, content,
412                    ))
413                }
414                WithheldCode::_Custom(_) => unknown(value)?,
415            },
416            _ => unknown(value)?,
417        })
418    }
419}
420
421impl Serialize for RoomKeyWithheldContent {
422    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
423    where
424        S: serde::Serializer,
425    {
426        let algorithm = self.algorithm();
427
428        let helper = match self {
429            Self::MegolmV1AesSha2(r) => {
430                let code = r.withheld_code();
431                let reason = Some(code.to_string());
432
433                match r {
434                    MegolmV1AesSha2WithheldContent::BlackListed(content)
435                    | MegolmV1AesSha2WithheldContent::Unverified(content)
436                    | MegolmV1AesSha2WithheldContent::Unauthorised(content)
437                    | MegolmV1AesSha2WithheldContent::Unavailable(content)
438                    | MegolmV1AesSha2WithheldContent::HistoryNotShared(content) => WithheldHelper {
439                        algorithm,
440                        code,
441                        reason,
442                        other: serde_json::to_value(content).map_err(serde::ser::Error::custom)?,
443                    },
444                    MegolmV1AesSha2WithheldContent::NoOlm(content) => WithheldHelper {
445                        algorithm,
446                        code,
447                        reason,
448                        other: serde_json::to_value(content).map_err(serde::ser::Error::custom)?,
449                    },
450                }
451            }
452            #[cfg(feature = "experimental-algorithms")]
453            Self::MegolmV2AesSha2(r) => {
454                let code = r.withheld_code();
455                let reason = Some(code.to_string());
456
457                match r {
458                    MegolmV1AesSha2WithheldContent::BlackListed(content)
459                    | MegolmV1AesSha2WithheldContent::Unverified(content)
460                    | MegolmV1AesSha2WithheldContent::Unauthorised(content)
461                    | MegolmV1AesSha2WithheldContent::Unavailable(content)
462                    | MegolmV1AesSha2WithheldContent::HistoryNotShared(content) => WithheldHelper {
463                        algorithm,
464                        code,
465                        reason,
466                        other: serde_json::to_value(content).map_err(serde::ser::Error::custom)?,
467                    },
468                    MegolmV1AesSha2WithheldContent::NoOlm(content) => WithheldHelper {
469                        algorithm,
470                        code,
471                        reason,
472                        other: serde_json::to_value(content).map_err(serde::ser::Error::custom)?,
473                    },
474                }
475            }
476            Self::Unknown(r) => WithheldHelper {
477                algorithm: r.algorithm.to_owned(),
478                code: r.code.to_owned(),
479                reason: r.reason.to_owned(),
480                other: serde_json::to_value(r.other.clone()).map_err(serde::ser::Error::custom)?,
481            },
482        };
483
484        helper.serialize(serializer)
485    }
486}
487
488#[cfg(test)]
489pub(super) mod tests {
490    use std::collections::BTreeMap;
491
492    use assert_matches::assert_matches;
493    use assert_matches2::assert_let;
494    use matrix_sdk_common::deserialized_responses::WithheldCode;
495    use ruma::{device_id, room_id, serde::Raw, to_device::DeviceIdOrAllDevices, user_id};
496    use serde_json::{Value, json};
497    use vodozemac::Curve25519PublicKey;
498
499    use super::RoomKeyWithheldEvent;
500    use crate::types::{
501        EventEncryptionAlgorithm,
502        events::room_key_withheld::{MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent},
503    };
504
505    pub fn json(code: &WithheldCode) -> Value {
506        json!({
507            "sender": "@alice:example.org",
508            "content": {
509                "room_id": "!DwLygpkclUAfQNnfva:localhost:8481",
510                "session_id": "0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs",
511                "algorithm": "m.megolm.v1.aes-sha2",
512                "sender_key": "9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA",
513                "code": code.to_owned(),
514                "reason": code.to_string(),
515                "org.matrix.msgid": "8836f2f0-635d-4f0e-9228-446c63ba3ea3"
516            },
517            "type": "m.room_key.withheld",
518            "m.custom.top": "something custom in the top",
519        })
520    }
521
522    pub fn no_olm_json() -> Value {
523        json!({
524            "sender": "@alice:example.org",
525            "content": {
526                "algorithm": "m.megolm.v1.aes-sha2",
527                "sender_key": "9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA",
528                "code": "m.no_olm",
529                "reason": "Unable to establish a secure channel.",
530                "org.matrix.msgid": "8836f2f0-635d-4f0e-9228-446c63ba3ea3"
531            },
532            "type": "m.room_key.withheld",
533            "m.custom.top": "something custom in the top",
534        })
535    }
536
537    pub fn unknown_alg_json() -> Value {
538        json!({
539            "sender": "@alice:example.org",
540            "content": {
541                "algorithm": "caesar.cipher",
542                "sender_key": "9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA",
543                "code": "m.brutus",
544                "reason": "Tu quoque fili",
545                "org.matrix.msgid": "8836f2f0-635d-4f0e-9228-446c63ba3ea3"
546            },
547            "type": "m.room_key.withheld",
548            "m.custom.top": "something custom in the top",
549        })
550    }
551
552    pub fn unknown_code_json() -> Value {
553        json!({
554            "sender": "@alice:example.org",
555            "content": {
556                "room_id": "!DwLygpkclUAfQNnfva:localhost:8481",
557                "session_id": "0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs",
558                "algorithm": "m.megolm.v1.aes-sha2",
559                "sender_key": "9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA",
560                "code": "org.mscXXX.new_code",
561                "reason": "Unable to establish a secure channel.",
562                "org.matrix.msgid": "8836f2f0-635d-4f0e-9228-446c63ba3ea3"
563            },
564            "type": "m.room_key.withheld",
565            "m.custom.top": "something custom in the top",
566        })
567    }
568
569    #[test]
570    fn deserialization() -> Result<(), serde_json::Error> {
571        let codes = [
572            WithheldCode::Unverified,
573            WithheldCode::Blacklisted,
574            WithheldCode::Unauthorised,
575            WithheldCode::Unavailable,
576            WithheldCode::HistoryNotShared,
577        ];
578        for code in codes {
579            let json = json(&code);
580            let event: RoomKeyWithheldEvent = serde_json::from_value(json.clone())?;
581
582            assert_let!(RoomKeyWithheldContent::MegolmV1AesSha2(content) = &event.content);
583            assert_eq!(code, content.withheld_code());
584
585            assert_eq!(event.content.algorithm(), EventEncryptionAlgorithm::MegolmV1AesSha2);
586            let serialized = serde_json::to_value(event)?;
587            assert_eq!(json, serialized);
588        }
589        Ok(())
590    }
591
592    #[test]
593    fn deserialization_no_olm() -> Result<(), serde_json::Error> {
594        let json = no_olm_json();
595        let event: RoomKeyWithheldEvent = serde_json::from_value(json.clone())?;
596        assert_matches!(
597            event.content,
598            RoomKeyWithheldContent::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::NoOlm(_))
599        );
600        let serialized = serde_json::to_value(event)?;
601        assert_eq!(json, serialized);
602
603        Ok(())
604    }
605
606    #[test]
607    fn deserialization_unknown_code() -> Result<(), serde_json::Error> {
608        let json = unknown_code_json();
609        let event: RoomKeyWithheldEvent = serde_json::from_value(json.clone())?;
610        assert_matches!(event.content, RoomKeyWithheldContent::Unknown(_));
611
612        assert_let!(RoomKeyWithheldContent::Unknown(content) = &event.content);
613        assert_eq!(content.code.as_str(), "org.mscXXX.new_code");
614
615        let serialized = serde_json::to_value(event)?;
616        assert_eq!(json, serialized);
617
618        Ok(())
619    }
620
621    #[test]
622    fn deserialization_unknown_alg() -> Result<(), serde_json::Error> {
623        let json = unknown_alg_json();
624        let event: RoomKeyWithheldEvent = serde_json::from_value(json.clone())?;
625        assert_matches!(event.content, RoomKeyWithheldContent::Unknown(_));
626
627        assert_let!(RoomKeyWithheldContent::Unknown(content) = &event.content);
628        assert_matches!(&content.code, WithheldCode::_Custom(_));
629        let serialized = serde_json::to_value(event)?;
630        assert_eq!(json, serialized);
631
632        Ok(())
633    }
634
635    #[test]
636    fn serialization_to_device() {
637        let mut messages = BTreeMap::new();
638
639        let room_id = room_id!("!DwLygpkclUAfQNnfva:localhost:8481");
640        let user_id = user_id!("@alice:example.org");
641        let device_id = device_id!("DEV001");
642        let sender_key =
643            Curve25519PublicKey::from_base64("9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA")
644                .unwrap();
645
646        let content = RoomKeyWithheldContent::new(
647            EventEncryptionAlgorithm::MegolmV1AesSha2,
648            WithheldCode::Unverified,
649            room_id.to_owned(),
650            "0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs".to_owned(),
651            sender_key,
652            device_id.to_owned(),
653        );
654        let content = Raw::new(&content).expect("We can always serialize a withheld content info");
655
656        messages
657            .entry(user_id.to_owned())
658            .or_insert_with(BTreeMap::new)
659            .insert(DeviceIdOrAllDevices::DeviceId(device_id.to_owned()), content);
660
661        let serialized = serde_json::to_value(messages).unwrap();
662
663        let expected: Value = json!({
664            "@alice:example.org":{
665                "DEV001":{
666                    "algorithm":"m.megolm.v1.aes-sha2",
667                    "code":"m.unverified",
668                    "from_device":"DEV001",
669                    "reason":"The sender has disabled encrypting to unverified devices.",
670                    "room_id":"!DwLygpkclUAfQNnfva:localhost:8481",
671                    "sender_key":"9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA",
672                    "session_id":"0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs"
673                }
674            }
675        });
676        assert_eq!(serialized, expected);
677    }
678
679    #[test]
680    fn no_olm_should_not_have_room_and_session() {
681        let room_id = room_id!("!DwLygpkclUAfQNnfva:localhost:8481");
682        let device_id = device_id!("DEV001");
683        let sender_key =
684            Curve25519PublicKey::from_base64("9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA")
685                .unwrap();
686
687        let content = RoomKeyWithheldContent::new(
688            EventEncryptionAlgorithm::MegolmV1AesSha2,
689            WithheldCode::NoOlm,
690            room_id.to_owned(),
691            "0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs".to_owned(),
692            sender_key,
693            device_id.to_owned(),
694        );
695
696        assert_let!(
697            RoomKeyWithheldContent::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::NoOlm(
698                content
699            )) = content
700        );
701        assert_eq!(content.sender_key, sender_key);
702
703        let content = RoomKeyWithheldContent::new(
704            EventEncryptionAlgorithm::MegolmV1AesSha2,
705            WithheldCode::Unverified,
706            room_id.to_owned(),
707            "0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs".to_owned(),
708            sender_key,
709            device_id.to_owned(),
710        );
711
712        assert_let!(
713            RoomKeyWithheldContent::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::Unverified(
714                content
715            )) = content
716        );
717        assert_eq!(content.session_id, "0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs");
718    }
719}