matrix_sdk_base/response_processors/e2ee/to_device.rs
1// Copyright 2025 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::BTreeMap;
16
17use matrix_sdk_crypto::{store::RoomKeyInfo, EncryptionSyncChanges, OlmMachine};
18use ruma::{
19 api::client::sync::sync_events::{v3, v5, DeviceLists},
20 events::AnyToDeviceEvent,
21 serde::Raw,
22 OneTimeKeyAlgorithm, UInt,
23};
24
25use crate::Result;
26
27/// Process the to-device events and other related e2ee data based on a response
28/// from a [MSC4186 request][`v5`].
29///
30/// This returns a list of all the to-device events that were passed in but
31/// encrypted ones were replaced with their decrypted version.
32pub async fn from_msc4186(
33 to_device: Option<&v5::response::ToDevice>,
34 e2ee: &v5::response::E2EE,
35 olm_machine: Option<&OlmMachine>,
36) -> Result<Output> {
37 process(
38 olm_machine,
39 to_device.as_ref().map(|to_device| to_device.events.clone()).unwrap_or_default(),
40 &e2ee.device_lists,
41 &e2ee.device_one_time_keys_count,
42 e2ee.device_unused_fallback_key_types.as_deref(),
43 to_device.as_ref().map(|to_device| to_device.next_batch.clone()),
44 )
45 .await
46}
47
48/// Process the to-device events and other related e2ee data based on a response
49/// from a [`/v3/sync` request][`v3`].
50///
51/// This returns a list of all the to-device events that were passed in but
52/// encrypted ones were replaced with their decrypted version.
53pub async fn from_sync_v2(
54 response: &v3::Response,
55 olm_machine: Option<&OlmMachine>,
56) -> Result<Output> {
57 process(
58 olm_machine,
59 response.to_device.events.clone(),
60 &response.device_lists,
61 &response.device_one_time_keys_count,
62 response.device_unused_fallback_key_types.as_deref(),
63 Some(response.next_batch.clone()),
64 )
65 .await
66}
67
68/// Process the to-device events and other related e2ee data.
69///
70/// This returns a list of all the to-device events that were passed in but
71/// encrypted ones were replaced with their decrypted version.
72async fn process(
73 olm_machine: Option<&OlmMachine>,
74 to_device_events: Vec<Raw<AnyToDeviceEvent>>,
75 device_lists: &DeviceLists,
76 one_time_keys_counts: &BTreeMap<OneTimeKeyAlgorithm, UInt>,
77 unused_fallback_keys: Option<&[OneTimeKeyAlgorithm]>,
78 next_batch_token: Option<String>,
79) -> Result<Output> {
80 let encryption_sync_changes = EncryptionSyncChanges {
81 to_device_events,
82 changed_devices: device_lists,
83 one_time_keys_counts,
84 unused_fallback_keys,
85 next_batch_token,
86 };
87
88 Ok(if let Some(olm_machine) = olm_machine {
89 // Let the crypto machine handle the sync response, this
90 // decrypts to-device events, but leaves room events alone.
91 // This makes sure that we have the decryption keys for the room
92 // events at hand.
93 let (events, room_key_updates) =
94 olm_machine.receive_sync_changes(encryption_sync_changes).await?;
95
96 let events = events
97 .iter()
98 // TODO: There is loss of information here, after calling `to_raw` it is not
99 // possible to make the difference between a successfully decrypted event and a plain
100 // text event. This information needs to be propagated to top layer at some point if
101 // clients relies on custom encrypted to device events.
102 .map(|p| p.to_raw())
103 .collect();
104
105 Output { decrypted_to_device_events: events, room_key_updates: Some(room_key_updates) }
106 } else {
107 // If we have no `OlmMachine`, just return the events that were passed in.
108 // This should not happen unless we forget to set things up by calling
109 // `Self::activate()`.
110 Output {
111 decrypted_to_device_events: encryption_sync_changes.to_device_events,
112 room_key_updates: None,
113 }
114 })
115}
116
117pub struct Output {
118 pub decrypted_to_device_events: Vec<Raw<AnyToDeviceEvent>>,
119 pub room_key_updates: Option<Vec<RoomKeyInfo>>,
120}