1use 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
30pub 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#[derive(Clone, Debug, Deserialize)]
51#[serde(try_from = "WithheldHelper")]
52pub enum RoomKeyWithheldContent {
53 MegolmV1AesSha2(MegolmV1AesSha2WithheldContent),
55 #[cfg(feature = "experimental-algorithms")]
57 MegolmV2AesSha2(MegolmV2AesSha2WithheldContent),
58 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 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 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 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 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 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#[derive(Clone, Debug)]
198pub enum MegolmV1AesSha2WithheldContent {
199 BlackListed(Box<CommonWithheldCodeContent>),
201 Unverified(Box<CommonWithheldCodeContent>),
203 Unauthorised(Box<CommonWithheldCodeContent>),
205 Unavailable(Box<CommonWithheldCodeContent>),
207 HistoryNotShared(Box<CommonWithheldCodeContent>),
210 NoOlm(Box<NoOlmWithheldContent>),
212}
213
214#[derive(Clone, PartialEq, Eq, Deserialize, Serialize)]
216pub struct CommonWithheldCodeContent {
217 pub room_id: OwnedRoomId,
219
220 pub session_id: String,
222
223 #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
225 pub sender_key: Curve25519PublicKey,
226
227 #[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 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 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 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 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#[derive(Clone, Deserialize, Serialize)]
311pub struct NoOlmWithheldContent {
312 #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
313 pub sender_key: Curve25519PublicKey,
315
316 #[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
347pub type MegolmV2AesSha2WithheldContent = MegolmV1AesSha2WithheldContent;
349
350#[derive(Clone, Debug, Serialize, Deserialize)]
352pub struct UnknownRoomKeyWithHeld {
353 pub algorithm: EventEncryptionAlgorithm,
355 pub code: WithheldCode,
357 #[serde(skip_serializing_if = "Option::is_none")]
359 pub reason: Option<String>,
360 #[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}