Skip to main content

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_common::deserialized_responses::{
18    ProcessedToDeviceEvent, ToDeviceUnableToDecryptInfo, ToDeviceUnableToDecryptReason,
19};
20use matrix_sdk_crypto::{DecryptionSettings, EncryptionSyncChanges, OlmMachine};
21use ruma::{
22    OneTimeKeyAlgorithm, UInt,
23    api::client::sync::sync_events::{DeviceLists, v3, v5},
24    events::AnyToDeviceEvent,
25    serde::Raw,
26};
27
28use crate::Result;
29
30/// Process the to-device events and other related e2ee data based on a response
31/// from a [MSC4186 request][`v5`].
32///
33/// This returns a list of all the to-device events that were passed in but
34/// encrypted ones were replaced with their decrypted version.
35pub async fn from_msc4186(
36    to_device: Option<&v5::response::ToDevice>,
37    e2ee: &v5::response::E2EE,
38    olm_machine: Option<&OlmMachine>,
39    decryption_settings: &DecryptionSettings,
40) -> Result<Output> {
41    process(
42        olm_machine,
43        to_device.as_ref().map(|to_device| to_device.events.clone()).unwrap_or_default(),
44        &e2ee.device_lists,
45        &e2ee.device_one_time_keys_count,
46        e2ee.device_unused_fallback_key_types.as_deref(),
47        to_device.as_ref().map(|to_device| to_device.next_batch.clone()),
48        decryption_settings,
49        true,
50    )
51    .await
52}
53
54/// Process the to-device events and other related e2ee data based on a response
55/// from a [`/v3/sync` request][`v3`].
56///
57/// This returns a list of all the to-device events that were passed in but
58/// encrypted ones were replaced with their decrypted version.
59pub async fn from_sync_v2(
60    response: &v3::Response,
61    olm_machine: Option<&OlmMachine>,
62    decryption_settings: &DecryptionSettings,
63) -> Result<Output> {
64    process(
65        olm_machine,
66        response.to_device.events.clone(),
67        &response.device_lists,
68        &response.device_one_time_keys_count,
69        response.device_unused_fallback_key_types.as_deref(),
70        Some(response.next_batch.clone()),
71        decryption_settings,
72        false,
73    )
74    .await
75}
76
77/// Process the to-device events and other related e2ee data.
78///
79/// This returns a list of all the to-device events that were passed in but
80/// encrypted ones were replaced with their decrypted version.
81#[allow(clippy::too_many_arguments)]
82async fn process(
83    olm_machine: Option<&OlmMachine>,
84    to_device_events: Vec<Raw<AnyToDeviceEvent>>,
85    device_lists: &DeviceLists,
86    one_time_keys_counts: &BTreeMap<OneTimeKeyAlgorithm, UInt>,
87    unused_fallback_keys: Option<&[OneTimeKeyAlgorithm]>,
88    next_batch_token: Option<String>,
89    decryption_settings: &DecryptionSettings,
90    msc_4186: bool,
91) -> Result<Output> {
92    let encryption_sync_changes = EncryptionSyncChanges {
93        to_device_events,
94        changed_devices: device_lists,
95        one_time_keys_counts,
96        unused_fallback_keys,
97        next_batch_token,
98    };
99
100    Ok(if let Some(olm_machine) = olm_machine {
101        // Let the crypto machine handle the sync response, this
102        // decrypts to-device events, but leaves room events alone.
103        // This makes sure that we have the decryption keys for the room
104        // events at hand.
105        let (events, _room_key_updates) = if msc_4186 {
106            olm_machine
107                .receive_sync_changes_msc4186(encryption_sync_changes, decryption_settings)
108                .await?
109        } else {
110            olm_machine.receive_sync_changes(encryption_sync_changes, decryption_settings).await?
111        };
112
113        Output { processed_to_device_events: events }
114    } else {
115        // If we have no `OlmMachine`, just return the clear events that were passed in.
116        // The encrypted ones are dropped as they are un-usable.
117        // This should not happen unless we forget to set things up by calling
118        // `Self::activate()`.
119        Output {
120            processed_to_device_events: encryption_sync_changes
121                .to_device_events
122                .into_iter()
123                .map(|raw| {
124                    if let Ok(Some(event_type)) = raw.get_field::<String>("type") {
125                        if event_type == "m.room.encrypted" {
126                            ProcessedToDeviceEvent::UnableToDecrypt {
127                                encrypted_event: raw,
128                                utd_info: ToDeviceUnableToDecryptInfo {
129                                    reason: ToDeviceUnableToDecryptReason::NoOlmMachine,
130                                },
131                            }
132                        } else {
133                            ProcessedToDeviceEvent::PlainText(raw)
134                        }
135                    } else {
136                        // Exclude events with no type
137                        ProcessedToDeviceEvent::Invalid(raw)
138                    }
139                })
140                .collect(),
141        }
142    })
143}
144
145pub struct Output {
146    pub processed_to_device_events: Vec<ProcessedToDeviceEvent>,
147}