Skip to content

Commit 2e746c0

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

File tree

7 files changed

+501
-13
lines changed

7 files changed

+501
-13
lines changed

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

+45-2
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

+1-1
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

+82-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
// See the License for the specific language governing permissions and
1515
// limitations under the License.
1616

17+
#[cfg(feature = "e2e-encryption")]
18+
use std::ops::Deref;
1719
use std::{
1820
collections::{btree_map, BTreeMap},
1921
fmt::{self, Debug},
@@ -37,8 +39,6 @@ use matrix_sdk_base::{
3739
StateStoreDataKey, StateStoreDataValue, SyncOutsideWasm,
3840
};
3941
use matrix_sdk_common::ttl_cache::TtlCache;
40-
#[cfg(feature = "e2e-encryption")]
41-
use ruma::events::{room::encryption::RoomEncryptionEventContent, InitialStateEvent};
4242
use ruma::{
4343
api::{
4444
client::{
@@ -69,6 +69,15 @@ use ruma::{
6969
DeviceId, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName,
7070
RoomAliasId, RoomId, RoomOrAliasId, ServerName, UInt, UserId,
7171
};
72+
#[cfg(feature = "e2e-encryption")]
73+
use ruma::{
74+
events::{
75+
room::encryption::RoomEncryptionEventContent, AnyToDeviceEventContent, InitialStateEvent,
76+
},
77+
serde::Raw,
78+
to_device::DeviceIdOrAllDevices,
79+
OwnedUserId,
80+
};
7281
use serde::de::DeserializeOwned;
7382
use tokio::sync::{broadcast, Mutex, OnceCell, RwLock, RwLockReadGuard};
7483
use tracing::{debug, error, instrument, trace, warn, Instrument, Span};
@@ -99,7 +108,9 @@ use crate::{
99108
};
100109
#[cfg(feature = "e2e-encryption")]
101110
use crate::{
102-
encryption::{Encryption, EncryptionData, EncryptionSettings, VerificationState},
111+
encryption::{
112+
identities::Device, Encryption, EncryptionData, EncryptionSettings, VerificationState,
113+
},
103114
store_locks::CrossProcessStoreLock,
104115
};
105116

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

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

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

+15
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

+1
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)