Skip to content

Commit 6d5ad4e

Browse files
toger5BillCarsonFr
andauthored
feat(widget-driver): Add to-device support
The widget Driver should be able to send and receive to-device events. This is useful for element call encryption keys. This PR focusses on the widget driver and machine logic. To send/communicate the events from the widget to the driver. It skips any encryption logic. Some of the encryption logic will be part of crypto crate and the code in the widget driver crate should be kept minimal once the crypto crate is ready. --------- Co-authored-by: Valere <bill.carson@valrsoft.com>
1 parent ec638e0 commit 6d5ad4e

File tree

18 files changed

+692
-87
lines changed

18 files changed

+692
-87
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindings/matrix-sdk-ffi/src/widget.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use async_compat::get_runtime_handle;
44
use language_tags::LanguageTag;
55
use matrix_sdk::{
66
async_trait,
7-
widget::{MessageLikeEventFilter, StateEventFilter},
7+
widget::{MessageLikeEventFilter, StateEventFilter, ToDeviceEventFilter},
88
};
99
use ruma::events::MessageLikeEventType;
1010
use tracing::error;
@@ -327,7 +327,9 @@ pub fn get_element_call_required_permissions(
327327
event_type: "org.matrix.rageshake_request".to_owned(),
328328
},
329329
// To read and send encryption keys
330+
WidgetEventFilter::ToDevice { event_type: "io.element.call.encryption_keys".to_owned() },
330331
// TODO change this to the appropriate to-device version once ready
332+
// remove this once all matrixRTC call apps supports to-device encryption.
331333
WidgetEventFilter::MessageLikeWithType {
332334
event_type: "io.element.call.encryption_keys".to_owned(),
333335
},
@@ -494,6 +496,8 @@ pub enum WidgetEventFilter {
494496
StateWithType { event_type: String },
495497
/// Matches state events with the given `type` and `state_key`.
496498
StateWithTypeAndStateKey { event_type: String, state_key: String },
499+
/// Matches to-device events with the given `event_type`.
500+
ToDevice { event_type: String },
497501
}
498502

499503
impl From<WidgetEventFilter> for matrix_sdk::widget::Filter {
@@ -511,6 +515,9 @@ impl From<WidgetEventFilter> for matrix_sdk::widget::Filter {
511515
WidgetEventFilter::StateWithTypeAndStateKey { event_type, state_key } => {
512516
Self::State(StateEventFilter::WithTypeAndStateKey(event_type.into(), state_key))
513517
}
518+
WidgetEventFilter::ToDevice { event_type } => {
519+
Self::ToDevice(ToDeviceEventFilter { event_type: event_type.into() })
520+
}
514521
}
515522
}
516523
}
@@ -532,6 +539,9 @@ impl From<matrix_sdk::widget::Filter> for WidgetEventFilter {
532539
F::State(StateEventFilter::WithTypeAndStateKey(event_type, state_key)) => {
533540
Self::StateWithTypeAndStateKey { event_type: event_type.to_string(), state_key }
534541
}
542+
F::ToDevice(ToDeviceEventFilter { event_type }) => {
543+
Self::ToDevice { event_type: event_type.to_string() }
544+
}
535545
}
536546
}
537547
}

crates/matrix-sdk/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ assert_matches = { workspace = true }
139139
assert_matches2 = { workspace = true }
140140
dirs = "6.0.0"
141141
futures-executor = { workspace = true }
142+
insta = { workspace = true }
142143
matrix-sdk-base = { workspace = true, features = ["testing"] }
143144
matrix-sdk-test = { workspace = true }
144145
serde_urlencoded = "0.7.1"

crates/matrix-sdk/src/test_utils/mocks/mod.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,56 @@ impl MatrixMockServer {
778778
self.mock_endpoint(mock, DeleteRoomKeysVersionEndpoint).expect_default_access_token()
779779
}
780780

781+
/// Creates a prebuilt mock for the `/sendToDevice` endpoint.
782+
///
783+
/// This mock can be used to simulate sending to-device messages in tests.
784+
/// # Examples
785+
///
786+
/// ```
787+
/// # #[cfg(feature = "e2e-encryption")]
788+
/// # {
789+
/// # tokio_test::block_on(async {
790+
/// use std::collections::BTreeMap;
791+
/// use matrix_sdk::{
792+
/// ruma::{
793+
/// serde::Raw,
794+
/// api::client::to_device::send_event_to_device::v3::Request as ToDeviceRequest,
795+
/// to_device::DeviceIdOrAllDevices,
796+
/// user_id,owned_device_id
797+
/// },
798+
/// test_utils::mocks::MatrixMockServer,
799+
/// };
800+
/// use serde_json::json;
801+
///
802+
/// let mock_server = MatrixMockServer::new().await;
803+
/// let client = mock_server.client_builder().build().await;
804+
///
805+
/// mock_server.mock_send_to_device().ok().mock_once().mount().await;
806+
///
807+
/// let request = ToDeviceRequest::new_raw(
808+
/// "m.custom.event".into(),
809+
/// "txn_id".into(),
810+
/// BTreeMap::from([
811+
/// (user_id!("@alice:localhost").to_owned(), BTreeMap::from([(
812+
/// DeviceIdOrAllDevices::AllDevices,
813+
/// Raw::new(&ruma::events::AnyToDeviceEventContent::Dummy(ruma::events::dummy::ToDeviceDummyEventContent {})).unwrap(),
814+
/// )])),
815+
/// ])
816+
/// );
817+
///
818+
/// client
819+
/// .send(request)
820+
/// .await
821+
/// .expect("We should be able to send a to-device message");
822+
/// # anyhow::Ok(()) });
823+
/// # }
824+
/// ```
825+
pub fn mock_send_to_device(&self) -> MockEndpoint<'_, SendToDeviceEndpoint> {
826+
let mock =
827+
Mock::given(method("PUT")).and(path_regex(r"^/_matrix/client/v3/sendToDevice/.*/.*"));
828+
self.mock_endpoint(mock, SendToDeviceEndpoint).expect_default_access_token()
829+
}
830+
781831
/// Create a prebuilt mock for getting the room members in a room.
782832
///
783833
/// # Examples
@@ -2385,6 +2435,17 @@ impl<'a> MockEndpoint<'a, DeleteRoomKeysVersionEndpoint> {
23852435
}
23862436
}
23872437

2438+
/// A prebuilt mock for the `/sendToDevice` endpoint.
2439+
///
2440+
/// This mock can be used to simulate sending to-device messages in tests.
2441+
pub struct SendToDeviceEndpoint;
2442+
impl<'a> MockEndpoint<'a, SendToDeviceEndpoint> {
2443+
/// Returns a successful response with default data.
2444+
pub fn ok(self) -> MatrixMock<'a> {
2445+
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
2446+
}
2447+
}
2448+
23882449
/// A prebuilt mock for `GET /members` request.
23892450
pub struct GetRoomMembersEndpoint;
23902451

crates/matrix-sdk/src/widget/capabilities.rs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer}
2222
use tracing::{debug, warn};
2323

2424
use super::{
25-
filter::{Filter, FilterInput},
25+
filter::{Filter, FilterInput, ToDeviceEventFilter},
2626
MessageLikeEventFilter, StateEventFilter,
2727
};
2828

@@ -60,7 +60,7 @@ pub struct Capabilities {
6060
impl Capabilities {
6161
/// Checks if a given event is allowed to be forwarded to the widget.
6262
///
63-
/// - `event_filter_input` is a minimized event respresntation that contains
63+
/// - `event_filter_input` is a minimized event representation that contains
6464
/// only the information needed to check if the widget is allowed to
6565
/// receive the event. (See [`FilterInput`])
6666
pub(super) fn allow_reading<'a>(
@@ -78,7 +78,7 @@ impl Capabilities {
7878

7979
/// Checks if a given event is allowed to be sent by the widget.
8080
///
81-
/// - `event_filter_input` is a minimized event respresntation that contains
81+
/// - `event_filter_input` is a minimized event representation that contains
8282
/// only the information needed to check if the widget is allowed to send
8383
/// the event to a matrix room. (See [`FilterInput`])
8484
pub(super) fn allow_sending<'a>(
@@ -102,11 +102,13 @@ impl Capabilities {
102102
}
103103
}
104104

105-
const SEND_EVENT: &str = "org.matrix.msc2762.send.event";
106-
const READ_EVENT: &str = "org.matrix.msc2762.receive.event";
107-
const SEND_STATE: &str = "org.matrix.msc2762.send.state_event";
108-
const READ_STATE: &str = "org.matrix.msc2762.receive.state_event";
109-
const REQUIRES_CLIENT: &str = "io.element.requires_client";
105+
pub(super) const SEND_EVENT: &str = "org.matrix.msc2762.send.event";
106+
pub(super) const READ_EVENT: &str = "org.matrix.msc2762.receive.event";
107+
pub(super) const SEND_STATE: &str = "org.matrix.msc2762.send.state_event";
108+
pub(super) const READ_STATE: &str = "org.matrix.msc2762.receive.state_event";
109+
pub(super) const SEND_TODEVICE: &str = "org.matrix.msc3819.send.to_device";
110+
pub(super) const READ_TODEVICE: &str = "org.matrix.msc3819.receive.to_device";
111+
pub(super) const REQUIRES_CLIENT: &str = "io.element.requires_client";
110112
pub(super) const SEND_DELAYED_EVENT: &str = "org.matrix.msc4157.send.delayed_event";
111113
pub(super) const UPDATE_DELAYED_EVENT: &str = "org.matrix.msc4157.update_delayed_event";
112114

@@ -121,6 +123,12 @@ impl Serialize for Capabilities {
121123
match self.0 {
122124
Filter::MessageLike(filter) => PrintMessageLikeEventFilter(filter).fmt(f),
123125
Filter::State(filter) => PrintStateEventFilter(filter).fmt(f),
126+
Filter::ToDevice(filter) => {
127+
// As per MSC 3819 https://github.com/matrix-org/matrix-spec-proposals/pull/3819
128+
// ToDevice capabilities is in the form of `m.send.to_device:<event type>`
129+
// or `m.receive.to_device:<event type>`
130+
write!(f, "{}", filter.event_type)
131+
}
124132
}
125133
}
126134
}
@@ -168,13 +176,15 @@ impl Serialize for Capabilities {
168176
let name = match filter {
169177
Filter::MessageLike(_) => READ_EVENT,
170178
Filter::State(_) => READ_STATE,
179+
Filter::ToDevice(_) => READ_TODEVICE,
171180
};
172181
seq.serialize_element(&format!("{name}:{}", PrintEventFilter(filter)))?;
173182
}
174183
for filter in &self.send {
175184
let name = match filter {
176185
Filter::MessageLike(_) => SEND_EVENT,
177186
Filter::State(_) => SEND_STATE,
187+
Filter::ToDevice(_) => SEND_TODEVICE,
178188
};
179189
seq.serialize_element(&format!("{name}:{}", PrintEventFilter(filter)))?;
180190
}
@@ -226,6 +236,12 @@ impl<'de> Deserialize<'de> for Capabilities {
226236
Some((SEND_STATE, filter_s)) => {
227237
Ok(Permission::Send(Filter::State(parse_state_event_filter(filter_s))))
228238
}
239+
Some((READ_TODEVICE, filter_s)) => Ok(Permission::Read(Filter::ToDevice(
240+
parse_to_device_event_filter(filter_s),
241+
))),
242+
Some((SEND_TODEVICE, filter_s)) => Ok(Permission::Send(Filter::ToDevice(
243+
parse_to_device_event_filter(filter_s),
244+
))),
229245
_ => {
230246
debug!("Unknown capability `{s}`");
231247
Ok(Self::Unknown)
@@ -252,6 +268,10 @@ impl<'de> Deserialize<'de> for Capabilities {
252268
}
253269
}
254270

271+
fn parse_to_device_event_filter(s: &str) -> ToDeviceEventFilter {
272+
ToDeviceEventFilter::new(s.into())
273+
}
274+
255275
let mut capabilities = Capabilities::default();
256276
for capability in Vec::<Permission>::deserialize(deserializer)? {
257277
match capability {
@@ -274,6 +294,7 @@ mod tests {
274294
use ruma::events::StateEventType;
275295

276296
use super::*;
297+
use crate::widget::filter::ToDeviceEventFilter;
277298

278299
#[test]
279300
fn deserialization_of_no_capabilities() {
@@ -293,8 +314,10 @@ mod tests {
293314
"org.matrix.msc2762.receive.event:org.matrix.rageshake_request",
294315
"org.matrix.msc2762.receive.state_event:m.room.member",
295316
"org.matrix.msc2762.receive.state_event:org.matrix.msc3401.call.member",
317+
"org.matrix.msc3819.receive.to_device:io.element.call.encryption_keys",
296318
"org.matrix.msc2762.send.event:org.matrix.rageshake_request",
297319
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@user:matrix.server",
320+
"org.matrix.msc3819.send.to_device:io.element.call.encryption_keys",
298321
"org.matrix.msc4157.send.delayed_event",
299322
"org.matrix.msc4157.update_delayed_event"
300323
]"#;
@@ -307,6 +330,9 @@ mod tests {
307330
)),
308331
Filter::State(StateEventFilter::WithType(StateEventType::RoomMember)),
309332
Filter::State(StateEventFilter::WithType("org.matrix.msc3401.call.member".into())),
333+
Filter::ToDevice(ToDeviceEventFilter::new(
334+
"io.element.call.encryption_keys".into(),
335+
)),
310336
],
311337
send: vec![
312338
Filter::MessageLike(MessageLikeEventFilter::WithType(
@@ -316,6 +342,9 @@ mod tests {
316342
"org.matrix.msc3401.call.member".into(),
317343
"@user:matrix.server".into(),
318344
)),
345+
Filter::ToDevice(ToDeviceEventFilter::new(
346+
"io.element.call.encryption_keys".into(),
347+
)),
319348
],
320349
requires_client: true,
321350
update_delayed_event: true,
@@ -335,13 +364,17 @@ mod tests {
335364
"org.matrix.msc3401.call.member".into(),
336365
"@user:matrix.server".into(),
337366
)),
367+
Filter::ToDevice(ToDeviceEventFilter::new(
368+
"io.element.call.encryption_keys".into(),
369+
)),
338370
],
339371
send: vec![
340372
Filter::MessageLike(MessageLikeEventFilter::WithType("io.element.custom".into())),
341373
Filter::State(StateEventFilter::WithTypeAndStateKey(
342374
"org.matrix.msc3401.call.member".into(),
343375
"@user:matrix.server".into(),
344376
)),
377+
Filter::ToDevice(ToDeviceEventFilter::new("my.org.other.to_device_event".into())),
345378
],
346379
requires_client: true,
347380
update_delayed_event: false,

0 commit comments

Comments
 (0)