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 pub(crate) fn store_for_migration<Storage: crate::storage::StorageProvider>(
29 &self,
30 storage: &Storage,
31 ) -> Result<(), Storage::Error> {
32 self.public_group.store_for_migration(storage)?;
33 storage.write_group_epoch_secrets(self.group_id(), &self.group_epoch_secrets)?;
34 storage.write_own_leaf_index(self.group_id(), &self.own_leaf_index)?;
35 storage.write_message_secrets(self.group_id(), &self.message_secrets_store)?;
36 storage.write_resumption_psk_store(self.group_id(), &self.resumption_psk_store)?;
37 storage.write_mls_join_config(self.group_id(), &self.mls_group_config)?;
38
39 // clear `own_leaf_nodes`, and rewrite one-by-one
40 storage.delete_own_leaf_nodes(self.group_id())?;
41 for leaf_node in self.own_leaf_nodes.iter() {
42 storage.append_own_leaf_node(self.group_id(), leaf_node)?;
43 }
44
45 storage.write_group_state(self.group_id(), &self.group_state)?;
46 #[cfg(feature = "extensions-draft")]
47 match &self.application_export_tree {
48 Some(application_export_tree) => {
49 storage.write_application_export_tree(self.group_id(), application_export_tree)?;
50 }
51 // No tree to write (migrating in from a version/config without
52 // `extensions-draft`, or a migration whose source has none). Delete
53 // any stale entry so the group's persisted state is fully replaced
54 // rather than left pointing at a previously stored tree.
55 None => {
56 storage
57 .delete_application_export_tree::<_, ApplicationExportTree>(self.group_id())?;
58 }
59 }
60
61 Ok(())
62 }
63}
64
65impl GroupMigrationBundle {
66 /// Store the migrated group and all of its group-associated data in the
67 /// current storage format.
68 ///
69 /// **NOTE**: This replaces any existing state for the group's [`GroupId`], under the same [`GroupId`] encoding.
70 ///
71 /// Single-value tables are overwritten and the accumulate-style data
72 /// (own leaf nodes, proposal queue) are cleared before writing.
73 ///
74 /// Old-format entries under a different key encoding are not changed.
75 pub fn store<Storage: crate::storage::StorageProvider>(
76 &self,
77 storage: &Storage,
78 ) -> Result<(), Storage::Error> {
79 // Group state (config, tree, context, proposals, secrets, ...).
80 self.group.store_for_migration(storage)?;
81
82 // The group's own encryption key pairs for the current epoch.
83 storage.write_encryption_epoch_key_pairs(
84 self.group.group_id(),
85 &self.group.epoch(),
86 self.group.own_leaf_index().u32(),
87 &self.epoch_encryption_key_pairs,
88 )?;
89
90 // Encryption key pairs for pending (uncommitted) update leaf nodes.
91 for key_pair in &self.update_encryption_key_pairs {
92 key_pair.write(storage)?;
93 }
94
95 Ok(())
96 }
97}