ruma-events: Create separate to-device content structs

While it's possible to share the content between different event types
in the usual case some event types have slightly different contents if
they are sent out as a to-device event vs a room message event.

The canonical example for this are key verification events where the
to-device version has a transaction id field but the room message
version uses event relationships for the same purpose.

This patch makes it possible for to-device events to have different
content structs. Type aliases are used where a common struct can be
used.
This commit is contained in:
Damir Jelić 2020-12-03 10:46:11 +01:00 committed by Jonas Platte
parent 5828f7b3b5
commit 7ec2b0b555
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
12 changed files with 112 additions and 105 deletions

View File

@ -372,7 +372,7 @@ fn expand_content_enum(
let event_type_str = events;
let content =
events.iter().map(|ev| to_event_content_path(ev, ruma_events)).collect::<Vec<_>>();
events.iter().map(|ev| to_event_content_path(kind, ev, ruma_events)).collect::<Vec<_>>();
let variant_decls = variants.iter().map(|v| v.decl());
@ -809,8 +809,7 @@ fn to_event_path(name: &LitStr, struct_name: &Ident, ruma_events: &TokenStream)
};
quote! { #ruma_events::room::redaction::#redaction }
}
"ToDeviceEvent"
| "SyncStateEvent"
"SyncStateEvent"
| "StrippedStateEvent"
| "InitialStateEvent"
| "SyncMessageEvent"
@ -818,6 +817,10 @@ fn to_event_path(name: &LitStr, struct_name: &Ident, ruma_events: &TokenStream)
let content = format_ident!("{}EventContent", event);
quote! { #ruma_events::#struct_name<#ruma_events::#( #path )::*::#content> }
}
"ToDeviceEvent" => {
let content = format_ident!("{}ToDeviceEventContent", event);
quote! { #ruma_events::#struct_name<#ruma_events::#( #path )::*::#content> }
}
struct_str if struct_str.contains("Redacted") => {
let content = format_ident!("Redacted{}EventContent", event);
quote! { #ruma_events::#struct_name<#ruma_events::#( #path )::*::#content> }
@ -829,7 +832,11 @@ fn to_event_path(name: &LitStr, struct_name: &Ident, ruma_events: &TokenStream)
}
}
fn to_event_content_path(name: &LitStr, ruma_events: &TokenStream) -> TokenStream {
fn to_event_content_path(
kind: &EventKind,
name: &LitStr,
ruma_events: &TokenStream,
) -> TokenStream {
let span = name.span();
let name = name.value();
@ -844,8 +851,13 @@ fn to_event_content_path(name: &LitStr, ruma_events: &TokenStream) -> TokenStrea
.map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
.collect::<String>();
let content_str = format_ident!("{}EventContent", event);
let content_str = match kind {
EventKind::ToDevice => format_ident!("{}ToDeviceEventContent", event),
_ => format_ident!("{}EventContent", event),
};
let path = path.iter().map(|s| Ident::new(s, span));
quote! {
#ruma_events::#( #path )::*::#content_str
}

View File

@ -24,6 +24,9 @@ pub type DummyEvent = BasicEvent<DummyEventContent>;
#[ruma_event(type = "m.dummy")]
pub struct DummyEventContent(pub Empty);
/// The to-device version of the payload for the `DummyEvent`.
pub type DummyToDeviceEventContent = DummyEventContent;
impl Deref for DummyEventContent {
type Target = Empty;

View File

@ -4,17 +4,10 @@ use ruma_events_macros::BasicEventContent;
use ruma_identifiers::{EventEncryptionAlgorithm, RoomId};
use serde::{Deserialize, Serialize};
use crate::BasicEvent;
/// This event type is used to forward keys for end-to-end encryption.
///
/// Typically it is encrypted as an *m.room.encrypted* event, then sent as a to-device event.
pub type ForwardedRoomKeyEvent = BasicEvent<ForwardedRoomKeyEventContent>;
/// The payload for `ForwardedRoomKeyEvent`.
#[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)]
#[ruma_event(type = "m.forwarded_room_key")]
pub struct ForwardedRoomKeyEventContent {
pub struct ForwardedRoomKeyToDeviceEventContent {
/// The encryption algorithm the key in this event is to be used with.
pub algorithm: EventEncryptionAlgorithm,

View File

@ -9,17 +9,11 @@ use serde_json::Value as JsonValue;
use super::{
HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, ShortAuthenticationString,
};
use crate::BasicEvent;
/// Accepts a previously sent *m.key.verification.start* message.
///
/// Typically sent as a to-device event.
pub type AcceptEvent = BasicEvent<AcceptEventContent>;
/// The payload for `AcceptEvent`.
#[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)]
#[ruma_event(type = "m.key.verification.accept")]
pub struct AcceptEventContent {
pub struct AcceptToDeviceEventContent {
/// An opaque identifier for the verification process.
///
/// Must be the same as the one used for the *m.key.verification.start*
@ -130,19 +124,21 @@ mod tests {
use std::collections::BTreeMap;
use matches::assert_matches;
use ruma_identifiers::user_id;
use ruma_serde::Raw;
use serde_json::{
from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue,
};
use super::{
AcceptEvent, AcceptEventContent, AcceptMethod, CustomContent, HashAlgorithm,
AcceptMethod, AcceptToDeviceEventContent, CustomContent, HashAlgorithm,
KeyAgreementProtocol, MSasV1Content, MessageAuthenticationCode, ShortAuthenticationString,
};
use ruma_serde::Raw;
use crate::ToDeviceEvent;
#[test]
fn serialization() {
let key_verification_accept_content = AcceptEventContent {
let key_verification_accept_content = AcceptToDeviceEventContent {
transaction_id: "456".into(),
method: AcceptMethod::MSasV1(MSasV1Content {
hash: HashAlgorithm::Sha256,
@ -153,7 +149,7 @@ mod tests {
}),
};
let key_verification_accept = AcceptEvent { content: key_verification_accept_content };
let sender = user_id!("@example:localhost");
let json_data = json!({
"content": {
@ -165,21 +161,28 @@ mod tests {
"message_authentication_code": "hkdf-hmac-sha256",
"short_authentication_string": ["decimal"]
},
"sender": sender,
"type": "m.key.verification.accept"
});
let key_verification_accept =
ToDeviceEvent { sender, content: key_verification_accept_content };
assert_eq!(to_json_value(&key_verification_accept).unwrap(), json_data);
let sender = user_id!("@example:localhost");
let json_data = json!({
"content": {
"transaction_id": "456",
"method": "m.sas.custom",
"test": "field",
},
"sender": sender,
"type": "m.key.verification.accept"
});
let key_verification_accept_content = AcceptEventContent {
let key_verification_accept_content = AcceptToDeviceEventContent {
transaction_id: "456".into(),
method: AcceptMethod::Custom(CustomContent {
method: "m.sas.custom".to_owned(),
@ -189,7 +192,8 @@ mod tests {
}),
};
let key_verification_accept = AcceptEvent { content: key_verification_accept_content };
let key_verification_accept =
ToDeviceEvent { sender, content: key_verification_accept_content };
assert_eq!(to_json_value(&key_verification_accept).unwrap(), json_data);
}
@ -208,11 +212,11 @@ mod tests {
// Deserialize the content struct separately to verify `TryFromRaw` is implemented for it.
assert_matches!(
from_json_value::<Raw<AcceptEventContent>>(json)
from_json_value::<Raw<AcceptToDeviceEventContent>>(json)
.unwrap()
.deserialize()
.unwrap(),
AcceptEventContent {
AcceptToDeviceEventContent {
transaction_id,
method: AcceptMethod::MSasV1(MSasV1Content {
commitment,
@ -229,6 +233,8 @@ mod tests {
&& short_authentication_string == vec![ShortAuthenticationString::Decimal]
);
let sender = user_id!("@example:localhost");
let json = json!({
"content": {
"commitment": "test_commitment",
@ -239,16 +245,18 @@ mod tests {
"message_authentication_code": "hkdf-hmac-sha256",
"short_authentication_string": ["decimal"]
},
"type": "m.key.verification.accept"
"type": "m.key.verification.accept",
"sender": sender,
});
assert_matches!(
from_json_value::<Raw<AcceptEvent>>(json)
from_json_value::<Raw<ToDeviceEvent<AcceptToDeviceEventContent>>>(json)
.unwrap()
.deserialize()
.unwrap(),
AcceptEvent {
content: AcceptEventContent {
ToDeviceEvent {
sender,
content: AcceptToDeviceEventContent {
transaction_id,
method: AcceptMethod::MSasV1(MSasV1Content {
commitment,
@ -259,6 +267,7 @@ mod tests {
})
}
} if commitment == "test_commitment"
&& sender == user_id!("@example:localhost")
&& transaction_id == "456"
&& hash == HashAlgorithm::Sha256
&& key_agreement_protocol == KeyAgreementProtocol::Curve25519
@ -266,6 +275,8 @@ mod tests {
&& short_authentication_string == vec![ShortAuthenticationString::Decimal]
);
let sender = user_id!("@example:localhost");
let json = json!({
"content": {
"from_device": "123",
@ -273,16 +284,18 @@ mod tests {
"method": "m.sas.custom",
"test": "field",
},
"type": "m.key.verification.accept"
"type": "m.key.verification.accept",
"sender": sender
});
assert_matches!(
from_json_value::<Raw<AcceptEvent>>(json)
from_json_value::<Raw<ToDeviceEvent<AcceptToDeviceEventContent>>>(json)
.unwrap()
.deserialize()
.unwrap(),
AcceptEvent {
content: AcceptEventContent {
ToDeviceEvent {
sender,
content: AcceptToDeviceEventContent {
transaction_id,
method: AcceptMethod::Custom(CustomContent {
method,
@ -290,6 +303,7 @@ mod tests {
})
}
} if transaction_id == "456"
&& sender == user_id!("@example:localhost")
&& method == "m.sas.custom"
&& fields.get("test").unwrap() == &JsonValue::from("field")
);

View File

@ -4,17 +4,10 @@ use ruma_events_macros::BasicEventContent;
use ruma_serde::StringEnum;
use serde::{Deserialize, Serialize};
use crate::BasicEvent;
/// Cancels a key verification process/request.
///
/// Typically sent as a to-device event.
pub type CancelEvent = BasicEvent<CancelEventContent>;
/// The payload for `CancelEvent`.
#[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)]
#[ruma_event(type = "m.key.verification.cancel")]
pub struct CancelEventContent {
pub struct CancelToDeviceEventContent {
/// The opaque identifier for the verification process/request.
pub transaction_id: String,

View File

@ -3,17 +3,10 @@
use ruma_events_macros::BasicEventContent;
use serde::{Deserialize, Serialize};
use crate::BasicEvent;
/// Sends the ephemeral public key for a device to the partner device.
///
/// Typically sent as a to-device event.
pub type KeyEvent = BasicEvent<KeyEventContent>;
/// The payload for `KeyEvent`.
#[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)]
#[ruma_event(type = "m.key.verification.key")]
pub struct KeyEventContent {
pub struct KeyToDeviceEventContent {
/// An opaque identifier for the verification process.
///
/// Must be the same as the one used for the *m.key.verification.start* message.

View File

@ -5,17 +5,10 @@ use std::collections::BTreeMap;
use ruma_events_macros::BasicEventContent;
use serde::{Deserialize, Serialize};
use crate::BasicEvent;
/// Sends the MAC of a device's key to the partner device.
///
/// Typically sent as a to-device event.
pub type MacEvent = BasicEvent<MacEventContent>;
/// The payload for `MacEvent`.
#[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)]
#[ruma_event(type = "m.key.verification.mac")]
pub struct MacEventContent {
pub struct MacToDeviceEventContent {
/// An opaque identifier for the verification process.
///
/// Must be the same as the one used for the *m.key.verification.start* message.

View File

@ -7,17 +7,11 @@ use ruma_identifiers::DeviceIdBox;
use serde::{Deserialize, Serialize};
use super::VerificationMethod;
use crate::BasicEvent;
/// Requests a key verification with another user's devices.
///
/// Typically sent as a to-device event.
pub type RequestEvent = BasicEvent<RequestEventContent>;
/// The payload for `RequestEvent`.
#[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)]
#[ruma_event(type = "m.key.verification.request")]
pub struct RequestEventContent {
pub struct RequestToDeviceEventContent {
/// The device ID which is initiating the request.
pub from_device: DeviceIdBox,

View File

@ -10,17 +10,12 @@ use serde_json::Value as JsonValue;
use super::{
HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, ShortAuthenticationString,
};
use crate::{BasicEvent, InvalidInput};
/// Begins an SAS key verification process.
///
/// Typically sent as a to-device event.
pub type StartEvent = BasicEvent<StartEventContent>;
use crate::InvalidInput;
/// The payload of an *m.key.verification.start* event.
#[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)]
#[ruma_event(type = "m.key.verification.start")]
pub struct StartEventContent {
pub struct StartToDeviceEventContent {
/// The device ID which is initiating the process.
pub from_device: DeviceIdBox,
@ -179,16 +174,18 @@ mod tests {
use std::collections::BTreeMap;
use matches::assert_matches;
use ruma_identifiers::user_id;
use ruma_serde::Raw;
use serde_json::{
from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue,
};
use super::{
CustomContent, HashAlgorithm, KeyAgreementProtocol, MSasV1Content, MSasV1ContentInit,
MessageAuthenticationCode, ShortAuthenticationString, StartEvent, StartEventContent,
StartMethod,
MessageAuthenticationCode, ShortAuthenticationString, StartMethod,
StartToDeviceEventContent,
};
use ruma_serde::Raw;
use crate::ToDeviceEvent;
#[test]
fn invalid_m_sas_v1_content_missing_required_key_agreement_protocols() {
@ -248,7 +245,7 @@ mod tests {
#[test]
fn serialization() {
let key_verification_start_content = StartEventContent {
let key_verification_start_content = StartToDeviceEventContent {
from_device: "123".into(),
transaction_id: "456".into(),
method: StartMethod::MSasV1(
@ -262,7 +259,7 @@ mod tests {
),
};
let key_verification_start = StartEvent { content: key_verification_start_content };
let sender = user_id!("@example:localhost");
let json_data = json!({
"content": {
@ -274,11 +271,17 @@ mod tests {
"message_authentication_codes": ["hkdf-hmac-sha256"],
"short_authentication_string": ["decimal"]
},
"type": "m.key.verification.start"
"type": "m.key.verification.start",
"sender": sender
});
let key_verification_start =
ToDeviceEvent { sender, content: key_verification_start_content };
assert_eq!(to_json_value(&key_verification_start).unwrap(), json_data);
let sender = user_id!("@example:localhost");
let json_data = json!({
"content": {
"from_device": "123",
@ -286,10 +289,11 @@ mod tests {
"method": "m.sas.custom",
"test": "field",
},
"type": "m.key.verification.start"
"type": "m.key.verification.start",
"sender": sender
});
let key_verification_start_content = StartEventContent {
let key_verification_start_content = StartToDeviceEventContent {
from_device: "123".into(),
transaction_id: "456".into(),
method: StartMethod::Custom(CustomContent {
@ -300,7 +304,8 @@ mod tests {
}),
};
let key_verification_start = StartEvent { content: key_verification_start_content };
let key_verification_start =
ToDeviceEvent { sender, content: key_verification_start_content };
assert_eq!(to_json_value(&key_verification_start).unwrap(), json_data);
}
@ -319,11 +324,11 @@ mod tests {
// Deserialize the content struct separately to verify `TryFromRaw` is implemented for it.
assert_matches!(
from_json_value::<Raw<StartEventContent>>(json)
from_json_value::<Raw<StartToDeviceEventContent>>(json)
.unwrap()
.deserialize()
.unwrap(),
StartEventContent {
StartToDeviceEventContent {
from_device,
transaction_id,
method: StartMethod::MSasV1(MSasV1Content {
@ -340,6 +345,8 @@ mod tests {
&& short_authentication_string == vec![ShortAuthenticationString::Decimal]
);
let sender = user_id!("@example:localhost");
let json = json!({
"content": {
"from_device": "123",
@ -350,16 +357,18 @@ mod tests {
"message_authentication_codes": ["hkdf-hmac-sha256"],
"short_authentication_string": ["decimal"]
},
"type": "m.key.verification.start"
"type": "m.key.verification.start",
"sender": sender
});
assert_matches!(
from_json_value::<Raw<StartEvent>>(json)
from_json_value::<Raw<ToDeviceEvent<StartToDeviceEventContent>>>(json)
.unwrap()
.deserialize()
.unwrap(),
StartEvent {
content: StartEventContent {
ToDeviceEvent {
sender,
content: StartToDeviceEventContent {
from_device,
transaction_id,
method: StartMethod::MSasV1(MSasV1Content {
@ -370,6 +379,7 @@ mod tests {
})
}
} if from_device == "123"
&& sender == user_id!("@example:localhost")
&& transaction_id == "456"
&& hashes == vec![HashAlgorithm::Sha256]
&& key_agreement_protocols == vec![KeyAgreementProtocol::Curve25519]
@ -377,6 +387,8 @@ mod tests {
&& short_authentication_string == vec![ShortAuthenticationString::Decimal]
);
let sender = user_id!("@example:localhost");
let json = json!({
"content": {
"from_device": "123",
@ -384,16 +396,18 @@ mod tests {
"method": "m.sas.custom",
"test": "field",
},
"type": "m.key.verification.start"
"type": "m.key.verification.start",
"sender": sender,
});
assert_matches!(
from_json_value::<Raw<StartEvent>>(json)
from_json_value::<Raw<ToDeviceEvent<StartToDeviceEventContent>>>(json)
.unwrap()
.deserialize()
.unwrap(),
StartEvent {
content: StartEventContent {
ToDeviceEvent {
sender,
content: StartToDeviceEventContent {
from_device,
transaction_id,
method: StartMethod::Custom(CustomContent {
@ -402,6 +416,7 @@ mod tests {
})
}
} if from_device == "123"
&& sender == user_id!("@example:localhost")
&& transaction_id == "456"
&& method == "m.sas.custom"
&& fields.get("test").unwrap() == &JsonValue::from("field")
@ -411,7 +426,7 @@ mod tests {
#[test]
fn deserialization_failure() {
// Ensure that invalid JSON creates a `serde_json::Error` and not `InvalidEvent`
assert!(serde_json::from_str::<Raw<StartEventContent>>("{").is_err());
assert!(serde_json::from_str::<Raw<StartToDeviceEventContent>>("{").is_err());
}
// TODO this fails because the error is a Validation error not deserialization?

View File

@ -27,6 +27,9 @@ pub enum EncryptedEventContent {
MegolmV1AesSha2(MegolmV1AesSha2Content),
}
/// The to-device version of the payload for the `EncryptedEvent`.
pub type EncryptedToDeviceEventContent = EncryptedEventContent;
/// The payload for `EncryptedEvent` using the *m.olm.v1.curve25519-aes-sha2* algorithm.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]

View File

@ -6,9 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::BasicEvent;
/// This event type is used to exchange keys for end-to-end encryption.
///
/// Typically it is encrypted as an *m.room.encrypted* event, then sent as a to-device event.
/// Typically encrypted as an *m.room.encrypted* event, then sent as a to-device event.
pub type RoomKeyEvent = BasicEvent<RoomKeyEventContent>;
/// The payload for `RoomKeyEvent`.
@ -30,6 +28,9 @@ pub struct RoomKeyEventContent {
pub session_key: String,
}
/// The to-device version of the payload for the `RoomKeyEvent`.
pub type RoomKeyToDeviceEventContent = RoomKeyEventContent;
#[cfg(test)]
mod tests {
use ruma_identifiers::{room_id, EventEncryptionAlgorithm};

View File

@ -5,17 +5,10 @@ use ruma_identifiers::{DeviceIdBox, EventEncryptionAlgorithm, RoomId};
use ruma_serde::StringEnum;
use serde::{Deserialize, Serialize};
use crate::BasicEvent;
/// This event type is used to request keys for end-to-end encryption.
///
/// It is sent as an unencrypted to-device event.
pub type RoomKeyRequestEvent = BasicEvent<RoomKeyRequestEventContent>;
/// The payload for `RoomKeyRequestEvent`.
#[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)]
#[ruma_event(type = "m.room_key_request")]
pub struct RoomKeyRequestEventContent {
pub struct RoomKeyRequestToDeviceEventContent {
/// Whether this is a new key request or a cancellation of a previous request.
pub action: Action,