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