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