matrix_sdk_indexeddb/crypto_store/migrations/
v8_to_v10.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Migration code that moves from inbound_group_sessions2 to
//! inbound_group_sessions3, shrinking the values stored in each record.

use indexed_db_futures::IdbQuerySource;
use matrix_sdk_crypto::olm::InboundGroupSession;
use tracing::{debug, info};
use web_sys::{DomException, IdbTransactionMode};

use crate::{
    crypto_store::{
        indexeddb_serializer::IndexeddbSerializer,
        keys,
        migrations::{
            add_nonunique_index, do_schema_upgrade, old_keys,
            v7::InboundGroupSessionIndexedDbObject2, MigrationDb,
        },
        InboundGroupSessionIndexedDbObject, Result,
    },
    IndexeddbCryptoStoreError,
};

/// Perform the schema upgrade v8 to v9, creating `inbound_group_sessions3`.
pub(crate) async fn schema_add(name: &str) -> Result<(), DomException> {
    do_schema_upgrade(name, 9, |db, _, _| {
        let object_store = db.create_object_store(keys::INBOUND_GROUP_SESSIONS_V3)?;

        add_nonunique_index(
            &object_store,
            keys::INBOUND_GROUP_SESSIONS_BACKUP_INDEX,
            "needs_backup",
        )?;

        // See https://github.com/element-hq/element-web/issues/26892#issuecomment-1906336076
        // for the plan concerning this property and index. At time of writing, it is
        // unused, and needs_backup is still used.
        add_nonunique_index(
            &object_store,
            keys::INBOUND_GROUP_SESSIONS_BACKED_UP_TO_INDEX,
            "backed_up_to",
        )?;

        Ok(())
    })
    .await
}

/// Migrate data from `inbound_group_sessions2` into `inbound_group_sessions3`.
pub(crate) async fn data_migrate(name: &str, serializer: &IndexeddbSerializer) -> Result<()> {
    let db = MigrationDb::new(name, 10).await?;

    let txn = db.transaction_on_multi_with_mode(
        &[old_keys::INBOUND_GROUP_SESSIONS_V2, keys::INBOUND_GROUP_SESSIONS_V3],
        IdbTransactionMode::Readwrite,
    )?;

    let inbound_group_sessions2 = txn.object_store(old_keys::INBOUND_GROUP_SESSIONS_V2)?;
    let inbound_group_sessions3 = txn.object_store(keys::INBOUND_GROUP_SESSIONS_V3)?;

    let row_count = inbound_group_sessions2.count()?.await?;
    info!(row_count, "Shrinking inbound_group_session records");

    // Iterate through all rows
    if let Some(cursor) = inbound_group_sessions2.open_cursor()?.await? {
        let mut idx = 0;
        loop {
            idx += 1;

            if idx % 100 == 0 {
                debug!("Migrating session {idx} of {row_count}");
            }

            // Deserialize the session from the old store
            let old_value: InboundGroupSessionIndexedDbObject2 =
                serde_wasm_bindgen::from_value(cursor.value())?;

            let session = InboundGroupSession::from_pickle(
                serializer.deserialize_value_from_bytes(&old_value.pickled_session)?,
            )
            .map_err(|e| IndexeddbCryptoStoreError::CryptoStoreError(e.into()))?;

            // Calculate its key in the new table
            let new_key = serializer.encode_key(
                keys::INBOUND_GROUP_SESSIONS_V3,
                (&session.room_id, session.session_id()),
            );

            // Serialize the session in the new format
            let new_value =
                InboundGroupSessionIndexedDbObject::from_session(&session, serializer).await?;

            // Write it to the new store
            inbound_group_sessions3
                .add_key_val(&new_key, &serde_wasm_bindgen::to_value(&new_value)?)?;

            // We are done with the original data, so delete it now.
            cursor.delete()?;

            // Continue to the next record, or stop if we're done
            if !cursor.continue_cursor()?.await? {
                debug!("Migrated {idx} sessions.");
                break;
            }
        }
    }

    // We have finished with the old store. Clear it, since it is faster to
    // clear+delete than just delete. See https://www.artificialworlds.net/blog/2024/02/02/deleting-an-indexed-db-store-can-be-incredibly-slow-on-firefox/
    // for more details.
    inbound_group_sessions2.clear()?.await?;

    txn.await.into_result()?;
    Ok(())
}

/// Perform the schema upgrade v8 to v10, deleting `inbound_group_sessions2`.
pub(crate) async fn schema_delete(name: &str) -> Result<(), DomException> {
    do_schema_upgrade(name, 10, |db, _, _| {
        db.delete_object_store(old_keys::INBOUND_GROUP_SESSIONS_V2)?;
        Ok(())
    })
    .await
}