mls_rs_core/group/group_state.rs
1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// Copyright by contributors to this project.
3// SPDX-License-Identifier: (Apache-2.0 OR MIT)
4
5use core::fmt::{self, Debug};
6
7use crate::error::IntoAnyError;
8#[cfg(mls_build_async)]
9use alloc::boxed::Box;
10use alloc::vec::Vec;
11use zeroize::Zeroizing;
12
13/// Generic representation of a group's state.
14#[derive(Clone, PartialEq, Eq)]
15pub struct GroupState {
16 /// A unique group identifier.
17 pub id: Vec<u8>,
18 pub data: Zeroizing<Vec<u8>>,
19}
20
21impl Debug for GroupState {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 f.debug_struct("GroupState")
24 .field("id", &crate::debug::pretty_bytes(&self.id))
25 .finish()
26 }
27}
28
29/// Generic representation of a prior epoch.
30#[derive(Clone, PartialEq, Eq)]
31pub struct EpochRecord {
32 /// A unique epoch identifier within a particular group.
33 pub id: u64,
34 pub data: Zeroizing<Vec<u8>>,
35}
36
37impl Debug for EpochRecord {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 f.debug_struct("EpochRecord").field("id", &self.id).finish()
40 }
41}
42
43impl EpochRecord {
44 pub fn new(id: u64, data: Zeroizing<Vec<u8>>) -> Self {
45 Self { id, data }
46 }
47}
48
49/// Storage that can persist and reload a group state.
50///
51/// A group state is recorded as a combination of the current state
52/// (represented by the [`GroupState`] trait) and some number of prior
53/// group states (represented by the [`EpochRecord`] trait).
54/// This trait implements reading and writing group data as requested by the protocol
55/// implementation.
56///
57/// # Cleaning up records
58///
59/// Group state will not be purged when the local member is removed from the
60/// group. It is up to the implementer of this trait to provide a mechanism
61/// to delete records that can be used by an application.
62///
63
64#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
65#[cfg_attr(mls_build_async, maybe_async::must_be_async)]
66pub trait GroupStateStorage: Send + Sync {
67 type Error: IntoAnyError;
68
69 /// Fetch a group state from storage.
70 async fn state(&self, group_id: &[u8]) -> Result<Option<Zeroizing<Vec<u8>>>, Self::Error>;
71
72 /// Lazy load cached epoch data from a particular group.
73 async fn epoch(
74 &self,
75 group_id: &[u8],
76 epoch_id: u64,
77 ) -> Result<Option<Zeroizing<Vec<u8>>>, Self::Error>;
78
79 /// Write pending state updates.
80 ///
81 /// The group id that this update belongs to can be retrieved with
82 /// [`GroupState::id`]. Prior epoch id values can be retrieved with
83 /// [`EpochRecord::id`].
84 ///
85 /// The protocol implementation handles managing the max size of a prior epoch
86 /// cache and the deleting of prior states based on group activity.
87 /// The maximum number of prior epochs that will be stored is controlled by the
88 /// `Preferences::max_epoch_retention` function in `mls_rs`.
89 /// value. Requested deletes are communicated by the `delete_epoch_under`
90 /// parameter being set to `Some`.
91 ///
92 /// # Warning
93 ///
94 /// It is important to consider error recovery when creating an implementation
95 /// of this trait. Calls to [`write`](GroupStateStorage::write) should
96 /// optimally be a single atomic transaction in order to avoid partial writes
97 /// that may corrupt the group state.
98 async fn write(
99 &mut self,
100 state: GroupState,
101 epoch_inserts: Vec<EpochRecord>,
102 epoch_updates: Vec<EpochRecord>,
103 ) -> Result<(), Self::Error>;
104
105 /// The [`EpochRecord::id`] value that is associated with a stored
106 /// prior epoch for a particular group.
107 async fn max_epoch_id(&self, group_id: &[u8]) -> Result<Option<u64>, Self::Error>;
108}