Skip to content

Commit d4c0e90

Browse files
committed
feat(crypto): Add new encrypt_and_send_custom_to_device to the client
1 parent 284db61 commit d4c0e90

File tree

7 files changed

+494
-12
lines changed

7 files changed

+494
-12
lines changed

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

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use matrix_sdk_common::{
2323
deserialized_responses::{
2424
AlgorithmInfo, DecryptedRoomEvent, DeviceLinkProblem, EncryptionInfo, UnableToDecryptInfo,
2525
UnableToDecryptReason, UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel,
26-
VerificationState,
26+
VerificationState, WithheldCode,
2727
},
2828
locks::RwLock as StdRwLock,
2929
BoxFuture,
@@ -53,7 +53,7 @@ use tokio::sync::Mutex;
5353
use tracing::{
5454
debug, error,
5555
field::{debug, display},
56-
info, instrument, warn, Span,
56+
info, instrument, trace, warn, Span,
5757
};
5858
use vodozemac::{
5959
megolm::{DecryptionError, SessionOrdering},
@@ -1111,6 +1111,49 @@ impl OlmMachine {
11111111
self.inner.group_session_manager.share_room_key(room_id, users, encryption_settings).await
11121112
}
11131113

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

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/src/client/mod.rs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::{
1818
collections::{btree_map, BTreeMap},
1919
fmt::{self, Debug},
2020
future::{ready, Future},
21+
ops::Deref,
2122
pin::Pin,
2223
sync::{Arc, Mutex as StdMutex, RwLock as StdRwLock, Weak},
2324
};
@@ -64,12 +65,16 @@ use ruma::{
6465
MatrixVersion, OutgoingRequest,
6566
},
6667
assign,
68+
events::AnyToDeviceEventContent,
6769
push::Ruleset,
70+
serde::Raw,
6871
time::Instant,
72+
to_device::DeviceIdOrAllDevices,
6973
DeviceId, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName,
70-
RoomAliasId, RoomId, RoomOrAliasId, ServerName, UInt, UserId,
74+
OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId, ServerName, UInt, UserId,
7175
};
7276
use serde::de::DeserializeOwned;
77+
use serde_json::Value;
7378
use tokio::sync::{broadcast, Mutex, OnceCell, RwLock, RwLockReadGuard};
7479
use tracing::{debug, error, instrument, trace, warn, Instrument, Span};
7580
use url::Url;
@@ -99,7 +104,9 @@ use crate::{
99104
};
100105
#[cfg(feature = "e2e-encryption")]
101106
use crate::{
102-
encryption::{Encryption, EncryptionData, EncryptionSettings, VerificationState},
107+
encryption::{
108+
identities::Device, Encryption, EncryptionData, EncryptionSettings, VerificationState,
109+
},
103110
store_locks::CrossProcessStoreLock,
104111
};
105112

@@ -2513,6 +2520,72 @@ impl Client {
25132520
let base_room = self.inner.base_client.room_knocked(&response.room_id).await?;
25142521
Ok(Room::new(self.clone(), base_room))
25152522
}
2523+
2524+
/// Encrypts then send the given content via the `sendToDevice` end-point
2525+
/// using olm encryption.
2526+
///
2527+
/// If there are a lot of targets this will be break down by chunks.
2528+
///
2529+
/// # Returns
2530+
/// A list of `ToDeviceRequest` to send out the event, and the list of
2531+
/// devices where encryption did not succeed (device excluded or no olm)
2532+
#[cfg(feature = "e2e-encryption")]
2533+
pub async fn encrypt_and_send_custom_to_device(
2534+
&self,
2535+
targets: Vec<&Device>,
2536+
event_type: &str,
2537+
content: Raw<AnyToDeviceEventContent>,
2538+
) -> Result<Vec<(OwnedUserId, OwnedDeviceId)>> {
2539+
let users = targets.iter().map(|device| device.user_id());
2540+
2541+
// Will claim one-time-key for users that needs it
2542+
// TODO: For later optimisation: This will establish missing olm sessions with
2543+
// all this users devices, but we just want for some devices.
2544+
self.claim_one_time_keys(users).await?;
2545+
2546+
let olm = self.olm_machine().await;
2547+
let olm = olm.as_ref().expect("Olm machine wasn't started");
2548+
2549+
let (requests, withhelds) = olm
2550+
.encrypt_content_for_devices(
2551+
targets.into_iter().map(|d| d.deref().clone()).collect(),
2552+
event_type,
2553+
&content.deserialize_as::<Value>().expect("Deserialize as Value will always work"),
2554+
)
2555+
.await?;
2556+
2557+
let mut failures: Vec<(OwnedUserId, OwnedDeviceId)> = Default::default();
2558+
2559+
// Push the withhelds in the failures
2560+
withhelds.iter().for_each(|(d, _)| {
2561+
failures.push((d.user_id().to_owned(), d.device_id().to_owned()));
2562+
});
2563+
2564+
// TODO: parallelize that? it's already grouping 250 devices per chunk.
2565+
for request in requests {
2566+
let send_result =
2567+
self.send_to_device_with_config(&request, RequestConfig::short_retry()).await;
2568+
2569+
// If the sending failed we need to collect the failures to report them
2570+
if send_result.is_err() {
2571+
// Mark the sending as failed
2572+
for (user_id, device_map) in request.messages {
2573+
for device_id in device_map.keys() {
2574+
match device_id {
2575+
DeviceIdOrAllDevices::DeviceId(device_id) => {
2576+
failures.push((user_id.clone(), device_id.to_owned()));
2577+
}
2578+
DeviceIdOrAllDevices::AllDevices => {
2579+
// Cannot happen in this case
2580+
}
2581+
}
2582+
}
2583+
}
2584+
}
2585+
}
2586+
2587+
Ok(failures)
2588+
}
25162589
}
25172590

25182591
/// A weak reference to the inner client, useful when trying to get a handle

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ pub use matrix_sdk_base::crypto::{
9999
SessionCreationError, SignatureError, VERSION,
100100
};
101101

102+
use crate::config::RequestConfig;
102103
pub use crate::error::RoomKeyImportError;
103104

104105
/// All the data related to the encryption state.
@@ -572,6 +573,20 @@ impl Client {
572573
self.send(request).await
573574
}
574575

576+
pub(crate) async fn send_to_device_with_config(
577+
&self,
578+
request: &ToDeviceRequest,
579+
config: RequestConfig,
580+
) -> HttpResult<ToDeviceResponse> {
581+
let request = RumaToDeviceRequest::new_raw(
582+
request.event_type.clone(),
583+
request.txn_id.clone(),
584+
request.messages.clone(),
585+
);
586+
587+
self.send_inner(request, Some(config), Default::default()).await
588+
}
589+
575590
pub(crate) async fn send_verification_request(
576591
&self,
577592
request: OutgoingVerificationRequest,

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)