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    )
50    .await
51}
52
53/// Process the to-device events and other related e2ee data based on a response
54/// from a [`/v3/sync` request][`v3`].
55///
56/// This returns a list of all the to-device events that were passed in but
57/// encrypted ones were replaced with their decrypted version.
58pub async fn from_sync_v2(
59    response: &v3::Response,
60    olm_machine: Option<&OlmMachine>,
61    decryption_settings: &DecryptionSettings,
62) -> Result<Output> {
63    process(
64        olm_machine,
65        response.to_device.events.clone(),
66        &response.device_lists,
67        &response.device_one_time_keys_count,
68        response.device_unused_fallback_key_types.as_deref(),
69        Some(response.next_batch.clone()),
70        decryption_settings,
71    )
72    .await
73}
74
75/// Process the to-device events and other related e2ee data.
76///
77/// This returns a list of all the to-device events that were passed in but
78/// encrypted ones were replaced with their decrypted version.
79async fn process(
80    olm_machine: Option<&OlmMachine>,
81    to_device_events: Vec<Raw<AnyToDeviceEvent>>,
82    device_lists: &DeviceLists,
83    one_time_keys_counts: &BTreeMap<OneTimeKeyAlgorithm, UInt>,
84    unused_fallback_keys: Option<&[OneTimeKeyAlgorithm]>,
85    next_batch_token: Option<String>,
86    decryption_settings: &DecryptionSettings,
87) -> Result<Output> {
88    let encryption_sync_changes = EncryptionSyncChanges {
89        to_device_events,
90        changed_devices: device_lists,
91        one_time_keys_counts,
92        unused_fallback_keys,
93        next_batch_token,
94    };
95
96    Ok(if let Some(olm_machine) = olm_machine {
97        // Let the crypto machine handle the sync response, this
98        // decrypts to-device events, but leaves room events alone.
99        // This makes sure that we have the decryption keys for the room
100        // events at hand.
101        let (events, _room_key_updates) =
102            olm_machine.receive_sync_changes(encryption_sync_changes, decryption_settings).await?;
103
104        Output { processed_to_device_events: events }
105    } else {
106        // If we have no `OlmMachine`, just return the clear events that were passed in.
107        // The encrypted ones are dropped as they are un-usable.
108        // This should not happen unless we forget to set things up by calling
109        // `Self::activate()`.
110        Output {
111            processed_to_device_events: encryption_sync_changes
112                .to_device_events
113                .into_iter()
114                .map(|raw| {
115                    if let Ok(Some(event_type)) = raw.get_field::<String>("type") {
116                        if event_type == "m.room.encrypted" {
117                            ProcessedToDeviceEvent::UnableToDecrypt {
118                                encrypted_event: raw,
119                                utd_info: ToDeviceUnableToDecryptInfo {
120                                    reason: ToDeviceUnableToDecryptReason::NoOlmMachine,
121                                },
122                            }
123                        } else {
124                            ProcessedToDeviceEvent::PlainText(raw)
125                        }
126                    } else {
127                        // Exclude events with no type
128                        ProcessedToDeviceEvent::Invalid(raw)
129                    }
130                })
131                .collect(),
132        }
133    })
134}
135
136pub struct Output {
137    pub processed_to_device_events: Vec<ProcessedToDeviceEvent>,
138}