use std::{collections::BTreeMap, fmt::Debug};
use ruma::{
events::{
key::verification::{
accept::ToDeviceKeyVerificationAcceptEvent, cancel::ToDeviceKeyVerificationCancelEvent,
done::ToDeviceKeyVerificationDoneEvent, key::ToDeviceKeyVerificationKeyEvent,
mac::ToDeviceKeyVerificationMacEvent, ready::ToDeviceKeyVerificationReadyEvent,
request::ToDeviceKeyVerificationRequestEvent, start::ToDeviceKeyVerificationStartEvent,
},
secret::request::{SecretName, ToDeviceSecretRequestEvent},
EventContent, ToDeviceEventType,
},
serde::Raw,
OwnedUserId, UserId,
};
use serde::{Deserialize, Serialize};
use serde_json::{
value::{to_raw_value, RawValue},
Value,
};
use zeroize::Zeroize;
use super::{
dummy::DummyEvent,
forwarded_room_key::{ForwardedRoomKeyContent, ForwardedRoomKeyEvent},
room::encrypted::EncryptedToDeviceEvent,
room_key::RoomKeyEvent,
room_key_request::RoomKeyRequestEvent,
room_key_withheld::RoomKeyWithheldEvent,
secret_send::SecretSendEvent,
EventType,
};
use crate::types::events::from_str;
#[derive(Debug)]
pub enum ToDeviceEvents {
Custom(ToDeviceCustomEvent),
Dummy(DummyEvent),
KeyVerificationAccept(ToDeviceKeyVerificationAcceptEvent),
KeyVerificationCancel(ToDeviceKeyVerificationCancelEvent),
KeyVerificationKey(ToDeviceKeyVerificationKeyEvent),
KeyVerificationMac(ToDeviceKeyVerificationMacEvent),
KeyVerificationDone(ToDeviceKeyVerificationDoneEvent),
KeyVerificationStart(ToDeviceKeyVerificationStartEvent),
KeyVerificationReady(ToDeviceKeyVerificationReadyEvent),
KeyVerificationRequest(ToDeviceKeyVerificationRequestEvent),
RoomEncrypted(EncryptedToDeviceEvent),
RoomKey(RoomKeyEvent),
RoomKeyRequest(RoomKeyRequestEvent),
ForwardedRoomKey(Box<ForwardedRoomKeyEvent>),
SecretSend(SecretSendEvent),
SecretRequest(ToDeviceSecretRequestEvent),
RoomKeyWithheld(RoomKeyWithheldEvent),
}
impl ToDeviceEvents {
pub fn sender(&self) -> &UserId {
match self {
ToDeviceEvents::Custom(e) => &e.sender,
ToDeviceEvents::Dummy(e) => &e.sender,
ToDeviceEvents::KeyVerificationAccept(e) => &e.sender,
ToDeviceEvents::KeyVerificationCancel(e) => &e.sender,
ToDeviceEvents::KeyVerificationKey(e) => &e.sender,
ToDeviceEvents::KeyVerificationMac(e) => &e.sender,
ToDeviceEvents::KeyVerificationDone(e) => &e.sender,
ToDeviceEvents::KeyVerificationStart(e) => &e.sender,
ToDeviceEvents::KeyVerificationReady(e) => &e.sender,
ToDeviceEvents::KeyVerificationRequest(e) => &e.sender,
ToDeviceEvents::RoomEncrypted(e) => &e.sender,
ToDeviceEvents::RoomKey(e) => &e.sender,
ToDeviceEvents::RoomKeyRequest(e) => &e.sender,
ToDeviceEvents::ForwardedRoomKey(e) => &e.sender,
ToDeviceEvents::SecretSend(e) => &e.sender,
ToDeviceEvents::SecretRequest(e) => &e.sender,
ToDeviceEvents::RoomKeyWithheld(e) => &e.sender,
}
}
pub fn event_type(&self) -> ToDeviceEventType {
match self {
ToDeviceEvents::Custom(e) => ToDeviceEventType::from(e.event_type.to_owned()),
ToDeviceEvents::Dummy(_) => ToDeviceEventType::Dummy,
ToDeviceEvents::KeyVerificationAccept(e) => e.content.event_type(),
ToDeviceEvents::KeyVerificationCancel(e) => e.content.event_type(),
ToDeviceEvents::KeyVerificationKey(e) => e.content.event_type(),
ToDeviceEvents::KeyVerificationMac(e) => e.content.event_type(),
ToDeviceEvents::KeyVerificationDone(e) => e.content.event_type(),
ToDeviceEvents::KeyVerificationStart(e) => e.content.event_type(),
ToDeviceEvents::KeyVerificationReady(e) => e.content.event_type(),
ToDeviceEvents::KeyVerificationRequest(e) => e.content.event_type(),
ToDeviceEvents::RoomEncrypted(_) => ToDeviceEventType::RoomEncrypted,
ToDeviceEvents::RoomKey(_) => ToDeviceEventType::RoomKey,
ToDeviceEvents::RoomKeyRequest(_) => ToDeviceEventType::RoomKeyRequest,
ToDeviceEvents::ForwardedRoomKey(_) => ToDeviceEventType::ForwardedRoomKey,
ToDeviceEvents::SecretSend(_) => ToDeviceEventType::SecretSend,
ToDeviceEvents::SecretRequest(e) => e.content.event_type(),
ToDeviceEvents::RoomKeyWithheld(e) => {
ToDeviceEventType::from(e.content.event_type().to_owned())
}
}
}
pub(crate) fn serialize_zeroized(self) -> Result<Raw<ToDeviceEvents>, serde_json::Error> {
let serialized = match self {
ToDeviceEvents::Custom(_)
| ToDeviceEvents::Dummy(_)
| ToDeviceEvents::KeyVerificationAccept(_)
| ToDeviceEvents::KeyVerificationCancel(_)
| ToDeviceEvents::KeyVerificationKey(_)
| ToDeviceEvents::KeyVerificationMac(_)
| ToDeviceEvents::KeyVerificationDone(_)
| ToDeviceEvents::KeyVerificationStart(_)
| ToDeviceEvents::KeyVerificationReady(_)
| ToDeviceEvents::KeyVerificationRequest(_)
| ToDeviceEvents::RoomEncrypted(_)
| ToDeviceEvents::RoomKeyRequest(_)
| ToDeviceEvents::RoomKeyWithheld(_)
| ToDeviceEvents::SecretRequest(_) => Raw::from_json(to_raw_value(&self)?),
ToDeviceEvents::RoomKey(e) => {
let event_type = e.content.event_type();
let content = e.content.serialize_zeroized()?;
#[derive(Serialize)]
struct Helper<'a, C> {
sender: &'a UserId,
content: &'a Raw<C>,
#[serde(rename = "type")]
event_type: &'a str,
}
let helper = Helper { sender: &e.sender, content: &content, event_type };
let raw_value = to_raw_value(&helper)?;
Raw::from_json(raw_value)
}
ToDeviceEvents::ForwardedRoomKey(mut e) => {
match &mut e.content {
ForwardedRoomKeyContent::MegolmV1AesSha2(c) => c.session_key.zeroize(),
#[cfg(feature = "experimental-algorithms")]
ForwardedRoomKeyContent::MegolmV2AesSha2(c) => c.session_key.zeroize(),
ForwardedRoomKeyContent::Unknown(_) => (),
}
Raw::from_json(to_raw_value(&e)?)
}
ToDeviceEvents::SecretSend(mut e) => {
if let Some(SecretName::RecoveryKey) = e.content.secret_name {
} else {
e.content.secret.zeroize();
}
Raw::from_json(to_raw_value(&e)?)
}
};
Ok(serialized)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ToDeviceCustomEvent {
pub sender: OwnedUserId,
pub content: BTreeMap<String, Value>,
#[serde(rename = "type")]
pub event_type: String,
#[serde(flatten)]
other: BTreeMap<String, Value>,
}
#[derive(Debug)]
pub struct ToDeviceEvent<C>
where
C: EventType + Debug + Sized + Serialize,
{
pub sender: OwnedUserId,
pub content: C,
pub(crate) other: BTreeMap<String, Value>,
}
impl<C: EventType + Debug + Sized + Serialize> ToDeviceEvent<C> {
pub fn new(sender: OwnedUserId, content: C) -> ToDeviceEvent<C> {
ToDeviceEvent { sender, content, other: Default::default() }
}
}
impl<C> Serialize for ToDeviceEvent<C>
where
C: EventType + Debug + Sized + Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(Serialize)]
struct Helper<'a, C> {
sender: &'a UserId,
content: &'a C,
#[serde(rename = "type")]
event_type: &'a str,
#[serde(flatten)]
other: &'a BTreeMap<String, Value>,
}
let event_type = self.content.event_type();
let helper =
Helper { sender: &self.sender, content: &self.content, event_type, other: &self.other };
helper.serialize(serializer)
}
}
impl<'de, C> Deserialize<'de> for ToDeviceEvent<C>
where
C: EventType + Debug + Sized + Deserialize<'de> + Serialize,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper<C> {
sender: OwnedUserId,
content: C,
#[allow(dead_code)]
#[serde(rename = "type")]
event_type: String,
#[serde(flatten)]
other: BTreeMap<String, Value>,
}
let helper: Helper<C> = Helper::deserialize(deserializer)?;
if helper.event_type == helper.content.event_type() {
Ok(Self { sender: helper.sender, content: helper.content, other: helper.other })
} else {
Err(serde::de::Error::custom(format!(
"Mismatched event type, got {}, expected {}",
helper.event_type,
helper.content.event_type()
)))
}
}
}
impl<'de> Deserialize<'de> for ToDeviceEvents {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Debug, Deserialize)]
struct Helper<'a> {
#[serde(rename = "type")]
event_type: &'a str,
}
let json = Box::<RawValue>::deserialize(deserializer)?;
let helper: Helper<'_> =
serde_json::from_str(json.get()).map_err(serde::de::Error::custom)?;
let json = json.get();
Ok(match helper.event_type {
"m.dummy" => ToDeviceEvents::Dummy(from_str(json)?),
"m.key.verification.accept" => ToDeviceEvents::KeyVerificationAccept(from_str(json)?),
"m.key.verification.cancel" => ToDeviceEvents::KeyVerificationCancel(from_str(json)?),
"m.key.verification.done" => ToDeviceEvents::KeyVerificationDone(from_str(json)?),
"m.key.verification.key" => ToDeviceEvents::KeyVerificationKey(from_str(json)?),
"m.key.verification.mac" => ToDeviceEvents::KeyVerificationMac(from_str(json)?),
"m.key.verification.start" => ToDeviceEvents::KeyVerificationStart(from_str(json)?),
"m.key.verification.ready" => ToDeviceEvents::KeyVerificationReady(from_str(json)?),
"m.key.verification.request" => ToDeviceEvents::KeyVerificationRequest(from_str(json)?),
"m.room.encrypted" => ToDeviceEvents::RoomEncrypted(from_str(json)?),
"m.room_key" => ToDeviceEvents::RoomKey(from_str(json)?),
"m.forwarded_room_key" => ToDeviceEvents::ForwardedRoomKey(from_str(json)?),
"m.room_key_request" => ToDeviceEvents::RoomKeyRequest(from_str(json)?),
"m.room_key.withheld" => ToDeviceEvents::RoomKeyWithheld(from_str(json)?),
"m.secret.send" => ToDeviceEvents::SecretSend(from_str(json)?),
"m.secret.request" => ToDeviceEvents::SecretRequest(from_str(json)?),
_ => ToDeviceEvents::Custom(from_str(json)?),
})
}
}
impl Serialize for ToDeviceEvents {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
ToDeviceEvents::Custom(e) => e.serialize(serializer),
ToDeviceEvents::Dummy(e) => e.serialize(serializer),
ToDeviceEvents::KeyVerificationAccept(e) => e.serialize(serializer),
ToDeviceEvents::KeyVerificationCancel(e) => e.serialize(serializer),
ToDeviceEvents::KeyVerificationKey(e) => e.serialize(serializer),
ToDeviceEvents::KeyVerificationMac(e) => e.serialize(serializer),
ToDeviceEvents::KeyVerificationDone(e) => e.serialize(serializer),
ToDeviceEvents::KeyVerificationStart(e) => e.serialize(serializer),
ToDeviceEvents::KeyVerificationReady(e) => e.serialize(serializer),
ToDeviceEvents::KeyVerificationRequest(e) => e.serialize(serializer),
ToDeviceEvents::RoomEncrypted(e) => e.serialize(serializer),
ToDeviceEvents::RoomKey(e) => e.serialize(serializer),
ToDeviceEvents::RoomKeyRequest(e) => e.serialize(serializer),
ToDeviceEvents::ForwardedRoomKey(e) => e.serialize(serializer),
ToDeviceEvents::SecretSend(e) => e.serialize(serializer),
ToDeviceEvents::SecretRequest(e) => e.serialize(serializer),
ToDeviceEvents::RoomKeyWithheld(e) => e.serialize(serializer),
}
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use serde_json::{json, Value};
use super::ToDeviceEvents;
fn custom_event() -> Value {
json!({
"sender": "@alice:example.org",
"content": {
"custom_key": "custom_value",
},
"m.custom.top": "something custom in the top",
"type": "m.custom.event",
})
}
fn key_verification_event() -> Value {
json!({
"sender": "@alice:example.org",
"content": {
"from_device": "AliceDevice2",
"methods": [
"m.sas.v1"
],
"timestamp": 1559598944869u64,
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.request"
})
}
fn dummy_event() -> Value {
json!({
"sender": "@alice:example.org",
"content": {},
"type": "m.dummy"
})
}
fn secret_request_event() -> Value {
json!({
"sender": "@alice:example.org",
"content": {
"name": "org.example.some.secret",
"action": "request",
"requesting_device_id": "ABCDEFG",
"request_id": "randomly_generated_id_9573"
},
"type": "m.secret.request"
})
}
fn forwarded_room_key_event() -> Value {
json!({
"sender": "@alice:example.org",
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"forwarding_curve25519_key_chain": [
"hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw"
],
"room_id": "!Cuyf34gef24t:localhost",
"sender_claimed_ed25519_key": "aj40p+aw64yPIdsxoog8jhPu9i7l7NcFRecuOQblE3Y",
"sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
"session_key": "AQAAAAq2JpkMceK5f6JrZPJWwzQTn59zliuIv0F7apVLXDcZCCT\
3LqBjD21sULYEO5YTKdpMVhi9i6ZSZhdvZvp//tzRpDT7wpWVWI\
00Y3EPEjmpm/HfZ4MMAKpk+tzJVuuvfAcHBZgpnxBGzYOc/DAqa\
pK7Tk3t3QJ1UMSD94HfAqlb1JF5QBPwoh0fOvD8pJdanB8zxz05\
tKFdR73/vo2Q/zE3"
},
"type": "m.forwarded_room_key"
})
}
fn room_key_request_event() -> Value {
json!({
"sender": "@alice:example.org",
"content": {
"action": "request",
"body": {
"algorithm": "m.megolm.v1.aes-sha2",
"room_id": "!Cuyf34gef24t:localhost",
"sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ"
},
"request_id": "1495474790150.19",
"requesting_device_id": "RJYKSTBOIE"
},
"type": "m.room_key_request"
})
}
#[test]
fn deserialization() -> Result<(), serde_json::Error> {
macro_rules! assert_serialization_roundtrip {
( $( $json:path => $to_device_events:ident ),* $(,)? ) => {
$(
let json = $json();
let event: ToDeviceEvents = serde_json::from_value(json.clone())?;
assert_matches!(event, ToDeviceEvents::$to_device_events(_));
let serialized = serde_json::to_value(event)?;
assert_eq!(json, serialized);
)*
}
}
assert_serialization_roundtrip!(
crate::types::events::room_key::tests::json => RoomKey,
forwarded_room_key_event => ForwardedRoomKey,
room_key_request_event => RoomKeyRequest,
crate::types::events::secret_send::tests::json => SecretSend,
secret_request_event => SecretRequest,
custom_event => Custom,
key_verification_event => KeyVerificationRequest,
dummy_event => Dummy,
crate::types::events::room::encrypted::tests::to_device_json => RoomEncrypted,
);
Ok(())
}
#[test]
fn zeroized_deserialization() -> Result<(), serde_json::Error> {
use ruma::events::AnyToDeviceEvent;
macro_rules! assert_serialization_roundtrip {
( $( $json:path => $to_device_events:ident ),* $(,)? ) => {
$(
let json = $json();
let event: ToDeviceEvents = serde_json::from_value(json.clone())?;
let zeroized = event.serialize_zeroized()?;
let ruma_event: AnyToDeviceEvent = zeroized.deserialize_as()?;
assert_matches!(ruma_event, AnyToDeviceEvent::$to_device_events(_));
)*
}
}
assert_serialization_roundtrip!(
crate::types::events::room_key::tests::json => RoomKey,
forwarded_room_key_event => ForwardedRoomKey,
room_key_request_event => RoomKeyRequest,
crate::types::events::secret_send::tests::json => SecretSend,
secret_request_event => SecretRequest,
key_verification_event => KeyVerificationRequest,
dummy_event => Dummy,
crate::types::events::room::encrypted::tests::to_device_json => RoomEncrypted,
);
Ok(())
}
}