Skip to main content

openmls/group/mls_group/
migration_import.rs

1use super::*;
2
3/// A group and all of the group-associated data it owns, deserialized from a
4/// previous OpenMLS version for migration. The mirror of the older version's
5/// `MlsGroup::export_for_migration`.
6#[cfg_attr(
7    all(feature = "test-utils", feature = "migration-import"),
8    derive(serde::Serialize)
9)]
10#[derive(serde::Deserialize)]
11pub struct GroupMigrationBundle {
12    group: MlsGroup,
13    epoch_encryption_key_pairs: Vec<EncryptionKeyPair>,
14    update_encryption_key_pairs: Vec<EncryptionKeyPair>,
15}
16
17impl MlsGroup {
18    /// Store a group as part of a storage migration.
19    ///
20    /// This replaces the group's persisted state for its [`GroupId`]: the
21    /// single-value tables are overwritten, and the accumulate-style tables (the
22    /// own leaf nodes and the proposal queue) are cleared before being rewritten.
23    ///
24    /// **NOTE**: This helper only addresses entries under the current key encoding. When the key
25    /// encoding differs between versions (e.g. postcard → JSON), old-format entries
26    /// live under different keys and are not changed; remove those with the
27    /// older version's `MlsGroup::delete`.
28    #[openmls_traits::maybe_async]
29    pub(crate) async fn store_for_migration<Storage: crate::storage::StorageProvider>(
30        &self,
31        storage: &Storage,
32    ) -> Result<(), Storage::Error> {
33        self.public_group.store_for_migration(storage).await?;
34        storage
35            .write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)
36            .await?;
37        storage
38            .write_own_leaf_index(self.group_id(), &self.own_leaf_index)
39            .await?;
40        storage
41            .write_message_secrets(self.group_id(), &self.message_secrets_store)
42            .await?;
43        storage
44            .write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)
45            .await?;
46        storage
47            .write_mls_join_config(self.group_id(), &self.mls_group_config)
48            .await?;
49
50        // clear `own_leaf_nodes`, and rewrite one-by-one
51        storage.delete_own_leaf_nodes(self.group_id()).await?;
52        for leaf_node in self.own_leaf_nodes.iter() {
53            storage
54                .append_own_leaf_node(self.group_id(), leaf_node)
55                .await?;
56        }
57
58        storage
59            .write_group_state(self.group_id(), &self.group_state)
60            .await?;
61        #[cfg(feature = "extensions-draft")]
62        match &self.application_export_tree {
63            Some(application_export_tree) => {
64                storage
65                    .write_application_export_tree(self.group_id(), application_export_tree)
66                    .await?;
67            }
68            // No tree to write (migrating in from a version/config without
69            // `extensions-draft`, or a migration whose source has none). Delete
70            // any stale entry so the group's persisted state is fully replaced
71            // rather than left pointing at a previously stored tree.
72            None => {
73                storage
74                    .delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())
75                    .await?;
76            }
77        }
78
79        Ok(())
80    }
81}
82
83impl GroupMigrationBundle {
84    /// Store the migrated group and all of its group-associated data in the
85    /// current storage format.
86    ///
87    /// **NOTE**: This replaces any existing state for the group's [`GroupId`], under the same [`GroupId`] encoding.
88    ///
89    /// Single-value tables are overwritten and the accumulate-style data
90    /// (own leaf nodes, proposal queue) are cleared before writing.
91    ///
92    /// Old-format entries under a different key encoding are not changed.
93    #[openmls_traits::maybe_async]
94    pub async fn store<Storage: crate::storage::StorageProvider>(
95        &self,
96        storage: &Storage,
97    ) -> Result<(), Storage::Error> {
98        // Group state (config, tree, context, proposals, secrets, ...).
99        self.group.store_for_migration(storage).await?;
100
101        // The group's own encryption key pairs for the current epoch.
102        storage
103            .write_encryption_epoch_key_pairs(
104                self.group.group_id(),
105                &self.group.epoch(),
106                self.group.own_leaf_index().u32(),
107                &self.epoch_encryption_key_pairs,
108            )
109            .await?;
110
111        // Encryption key pairs for pending (uncommitted) update leaf nodes.
112        for key_pair in &self.update_encryption_key_pairs {
113            key_pair.write(storage).await?;
114        }
115
116        Ok(())
117    }
118}