Skip to content

Commit 21de891

Browse files
authored
feat(sdk): Add the encrypt_and_send_raw_to_device method
This method allows users to encrypt and send custom to-device events to a set of devices of their choosing.
1 parent 154f29e commit 21de891

File tree

10 files changed

+502
-8
lines changed

10 files changed

+502
-8
lines changed

crates/matrix-sdk-base/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ e2e-encryption = ["dep:matrix-sdk-crypto"]
2121
js = ["matrix-sdk-common/js", "matrix-sdk-crypto?/js", "ruma/js", "matrix-sdk-store-encryption/js"]
2222
qrcode = ["matrix-sdk-crypto?/qrcode"]
2323
automatic-room-key-forwarding = ["matrix-sdk-crypto?/automatic-room-key-forwarding"]
24+
experimental-send-custom-to-device = ["matrix-sdk-crypto?/experimental-send-custom-to-device"]
2425
uniffi = ["dep:uniffi", "matrix-sdk-crypto?/uniffi", "matrix-sdk-common/uniffi"]
2526

2627
# Private feature, see

crates/matrix-sdk-crypto/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
1717
[features]
1818
default = []
1919
automatic-room-key-forwarding = []
20+
experimental-send-custom-to-device = []
2021
js = ["ruma/js", "vodozemac/js", "matrix-sdk-common/js"]
2122
qrcode = ["dep:matrix-sdk-qrcode"]
2223
experimental-algorithms = []

crates/matrix-sdk-crypto/src/machine/mod.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use std::{
1919
};
2020

2121
use itertools::Itertools;
22+
#[cfg(feature = "experimental-send-custom-to-device")]
23+
use matrix_sdk_common::deserialized_responses::WithheldCode;
2224
use matrix_sdk_common::{
2325
deserialized_responses::{
2426
AlgorithmInfo, DecryptedRoomEvent, DeviceLinkProblem, EncryptionInfo, UnableToDecryptInfo,
@@ -50,6 +52,8 @@ use ruma::{
5052
};
5153
use serde_json::{value::to_raw_value, Value};
5254
use tokio::sync::Mutex;
55+
#[cfg(feature = "experimental-send-custom-to-device")]
56+
use tracing::trace;
5357
use tracing::{
5458
debug, error,
5559
field::{debug, display},
@@ -1119,6 +1123,50 @@ impl OlmMachine {
11191123
self.inner.group_session_manager.share_room_key(room_id, users, encryption_settings).await
11201124
}
11211125

1126+
/// Encrypts the given content using Olm for each of the given devices.
1127+
///
1128+
/// The 1-to-1 session must be established prior to this
1129+
/// call by using the [`OlmMachine::get_missing_sessions`] method or the
1130+
/// encryption will fail.
1131+
///
1132+
/// The caller is responsible for sending the encrypted
1133+
/// event to the target device, and should do it ASAP to avoid out-of-order
1134+
/// messages.
1135+
///
1136+
/// # Returns
1137+
/// A list of `ToDeviceRequest` to send out the event, and the list of
1138+
/// devices where encryption did not succeed (device excluded or no olm)
1139+
#[cfg(feature = "experimental-send-custom-to-device")]
1140+
pub async fn encrypt_content_for_devices(
1141+
&self,
1142+
devices: Vec<DeviceData>,
1143+
event_type: &str,
1144+
content: &Value,
1145+
) -> OlmResult<(Vec<ToDeviceRequest>, Vec<(DeviceData, WithheldCode)>)> {
1146+
// TODO: Use a `CollectStrategy` arguments to filter our devices depending on
1147+
// safety settings (like not sending to insecure devices).
1148+
let mut changes = Changes::default();
1149+
1150+
let result = self
1151+
.inner
1152+
.group_session_manager
1153+
.encrypt_content_for_devices(devices, event_type, content.clone(), &mut changes)
1154+
.await;
1155+
1156+
// Persist any changes we might have collected.
1157+
if !changes.is_empty() {
1158+
let session_count = changes.sessions.len();
1159+
1160+
self.inner.store.save_changes(changes).await?;
1161+
1162+
trace!(
1163+
session_count = session_count,
1164+
"Stored the changed sessions after encrypting a custom to-device event"
1165+
);
1166+
}
1167+
1168+
result
1169+
}
11221170
/// Collect the devices belonging to the given user, and send the details of
11231171
/// a room key bundle to those devices.
11241172
///

crates/matrix-sdk-crypto/src/session_manager/group_sessions/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ impl GroupSessionManager {
827827
/// Returns a tuple containing (1) the list of to-device requests, and (2)
828828
/// the list of devices that we could not find an olm session for (so
829829
/// need a withheld message).
830-
async fn encrypt_content_for_devices(
830+
pub(crate) async fn encrypt_content_for_devices(
831831
&self,
832832
recipient_devices: Vec<DeviceData>,
833833
event_type: &str,

crates/matrix-sdk/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ All notable changes to this project will be documented in this file.
2525
- `Room::set_unread_flag()` now sets the stable `m.marked_unread` room account data, which was
2626
stabilized in Matrix 1.12. `Room::is_marked_unread()` also ignores the unstable
2727
`com.famedly.marked_unread` room account data if the stable variant is present.
28+
- `Encryption::encrypt_and_send_raw_to_device`: Introduced as an experimental method for
29+
sending custom encrypted to-device events. This feature is gated behind the
30+
`experimental-send-custom-to-device` flag, as it remains under active development and may undergo changes.
31+
([4998](https://github.com/matrix-org/matrix-rust-sdk/pull/4998))
32+
2833

2934
### Bug fixes
3035

crates/matrix-sdk/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ indexeddb = ["matrix-sdk-indexeddb/state-store"]
3636

3737
qrcode = ["e2e-encryption", "matrix-sdk-base/qrcode"]
3838
automatic-room-key-forwarding = ["e2e-encryption", "matrix-sdk-base/automatic-room-key-forwarding"]
39+
experimental-send-custom-to-device = ["e2e-encryption", "matrix-sdk-base/experimental-send-custom-to-device"]
3940
markdown = ["ruma/markdown"]
4041
native-tls = ["reqwest/native-tls"]
4142
rustls-tls = ["reqwest/rustls-tls"]

crates/matrix-sdk/src/encryption/mod.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#![doc = include_str!("../docs/encryption.md")]
1717
#![cfg_attr(target_arch = "wasm32", allow(unused_imports))]
1818

19+
#[cfg(feature = "experimental-send-custom-to-device")]
20+
use std::ops::Deref;
1921
use std::{
2022
collections::{BTreeMap, HashSet},
2123
io::{Cursor, Read, Write},
@@ -57,6 +59,8 @@ use ruma::{
5759
},
5860
DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, TransactionId, UserId,
5961
};
62+
#[cfg(feature = "experimental-send-custom-to-device")]
63+
use ruma::{events::AnyToDeviceEventContent, serde::Raw, to_device::DeviceIdOrAllDevices};
6064
use serde::Deserialize;
6165
use tokio::sync::{Mutex, RwLockReadGuard};
6266
use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
@@ -99,6 +103,8 @@ pub use matrix_sdk_base::crypto::{
99103
SessionCreationError, SignatureError, VERSION,
100104
};
101105

106+
#[cfg(feature = "experimental-send-custom-to-device")]
107+
use crate::config::RequestConfig;
102108
pub use crate::error::RoomKeyImportError;
103109

104110
/// All the data related to the encryption state.
@@ -1735,6 +1741,82 @@ impl Encryption {
17351741
}
17361742
}
17371743
}
1744+
1745+
/// Encrypts then send the given content via the `/sendToDevice` end-point
1746+
/// using Olm encryption.
1747+
///
1748+
/// If there are a lot of recipient devices multiple `/sendToDevice`
1749+
/// requests might be sent out.
1750+
///
1751+
/// # Returns
1752+
/// A list of failures. The list of devices that couldn't get the messages.
1753+
#[cfg(feature = "experimental-send-custom-to-device")]
1754+
pub async fn encrypt_and_send_raw_to_device(
1755+
&self,
1756+
recipient_devices: Vec<&Device>,
1757+
event_type: &str,
1758+
content: Raw<AnyToDeviceEventContent>,
1759+
) -> Result<Vec<(OwnedUserId, OwnedDeviceId)>> {
1760+
let users = recipient_devices.iter().map(|device| device.user_id());
1761+
1762+
// Will claim one-time-key for users that needs it
1763+
// TODO: For later optimisation: This will establish missing olm sessions with
1764+
// all this users devices, but we just want for some devices.
1765+
self.client.claim_one_time_keys(users).await?;
1766+
1767+
let olm = self.client.olm_machine().await;
1768+
let olm = olm.as_ref().expect("Olm machine wasn't started");
1769+
1770+
let (requests, withhelds) = olm
1771+
.encrypt_content_for_devices(
1772+
recipient_devices.into_iter().map(|d| d.deref().clone()).collect(),
1773+
event_type,
1774+
&content
1775+
.deserialize_as::<serde_json::Value>()
1776+
.expect("Deserialize as Value will always work"),
1777+
)
1778+
.await?;
1779+
1780+
let mut failures: Vec<(OwnedUserId, OwnedDeviceId)> = Default::default();
1781+
1782+
// Push the withhelds in the failures
1783+
withhelds.iter().for_each(|(d, _)| {
1784+
failures.push((d.user_id().to_owned(), d.device_id().to_owned()));
1785+
});
1786+
1787+
// TODO: parallelize that? it's already grouping 250 devices per chunk.
1788+
for request in requests {
1789+
let request = RumaToDeviceRequest::new_raw(
1790+
request.event_type.clone(),
1791+
request.txn_id.clone(),
1792+
request.messages.clone(),
1793+
);
1794+
1795+
let send_result = self
1796+
.client
1797+
.send_inner(request, Some(RequestConfig::short_retry()), Default::default())
1798+
.await;
1799+
1800+
// If the sending failed we need to collect the failures to report them
1801+
if send_result.is_err() {
1802+
// Mark the sending as failed
1803+
for (user_id, device_map) in request.messages {
1804+
for device_id in device_map.keys() {
1805+
match device_id {
1806+
DeviceIdOrAllDevices::DeviceId(device_id) => {
1807+
failures.push((user_id.clone(), device_id.to_owned()));
1808+
}
1809+
DeviceIdOrAllDevices::AllDevices => {
1810+
// Cannot happen in this case
1811+
}
1812+
}
1813+
}
1814+
}
1815+
}
1816+
}
1817+
1818+
Ok(failures)
1819+
}
17381820
}
17391821

17401822
#[cfg(all(test, not(target_arch = "wasm32")))]

crates/matrix-sdk/tests/integration/encryption.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod backups;
22
mod cross_signing;
33
mod recovery;
44
mod secret_storage;
5+
mod to_device;
56
mod verification;
67

78
/// The backup key, which is also returned (encrypted) as part of the secret

0 commit comments

Comments
 (0)