Skip to main content

openmls/group/mls_group/
config.rs

1//! Configuration module for [`MlsGroup`] configurations.
2//!
3//! ## Building an MlsGroupCreateConfig
4//! The [`MlsGroupCreateConfigBuilder`] makes it easy to build configurations for the
5//! [`MlsGroup`].
6//!
7//! ```
8//! use openmls::prelude::*;
9//!
10//! let group_config = MlsGroupCreateConfig::builder()
11//!     .use_ratchet_tree_extension(true)
12//!     .build();
13//! ```
14//!
15//! See [`MlsGroupCreateConfigBuilder`](MlsGroupCreateConfigBuilder#implementations) for
16//! all options that can be configured.
17//!
18//! ### Wire format policies
19//! Only some combination of possible wire formats are valid within OpenMLS.
20//! The [`WIRE_FORMAT_POLICIES`] lists all valid options that can be set.
21//!
22//! ```
23//! use openmls::prelude::*;
24//!
25//! let group_config = MlsGroupCreateConfig::builder()
26//!     .wire_format_policy(MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY)
27//!     .build();
28//! ```
29
30#[cfg(not(target_arch = "wasm32"))]
31use std::time::SystemTime;
32#[cfg(target_arch = "wasm32")]
33use web_time::SystemTime;
34
35use super::*;
36use crate::{
37    extensions::Extensions,
38    key_packages::Lifetime,
39    tree::sender_ratchet::SenderRatchetConfiguration,
40    treesync::{errors::LeafNodeValidationError, node::leaf_node::Capabilities},
41};
42use serde::{Deserialize, Serialize};
43
44/// Configures the automatic deletion of past epoch secrets.
45///
46/// **WARNING**
47///
48/// Policies other than `MaxEpochs(0)` enable the storage of message secrets from past epochs.
49/// It is a trade-off between functionality and forward secrecy and should only be enabled
50/// if the Delivery Service cannot guarantee that application messages will be sent in
51/// the same epoch in which they were generated. The number for `max_epochs` should be
52/// as low as possible.
53///
54/// If the MaxEpochs policy is set to MaxEpochs(usize::MAX), if will be returned
55/// as KeepAll after deserialization due to backwards compatibility constraints.
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub enum PastEpochDeletionPolicy {
58    /// Keep at most `n` past epoch secrets.
59    MaxEpochs(usize),
60    /// Keep all past epoch secrets.
61    ///
62    /// NOTE: The application is responsible for deleting past epoch secrets when
63    /// `KeepAll` is set. Past epoch secrets can be deleted manually using:
64    /// - [`MlsGroup::delete_past_epoch_secrets()`]
65    KeepAll,
66}
67
68impl Default for PastEpochDeletionPolicy {
69    fn default() -> Self {
70        Self::MaxEpochs(0)
71    }
72}
73
74impl Serialize for PastEpochDeletionPolicy {
75    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76    where
77        S: serde::Serializer,
78    {
79        let usize = match self {
80            Self::MaxEpochs(epochs) => *epochs,
81            Self::KeepAll => usize::MAX,
82        };
83        serializer.serialize_u64(usize as u64)
84    }
85}
86
87impl<'de> Deserialize<'de> for PastEpochDeletionPolicy {
88    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
89        // Previously supported format (changed to plain integer in 8bdba6f)
90        #[derive(Deserialize)]
91        enum Tagged {
92            MaxEpochs(usize),
93            KeepAll,
94        }
95
96        #[derive(Deserialize)]
97        #[serde(untagged)]
98        enum Format {
99            Int(u64),
100            Tagged(Tagged),
101        }
102
103        Ok(match Format::deserialize(deserializer)? {
104            Format::Int(u64::MAX) => Self::KeepAll,
105            Format::Int(n) => {
106                Self::MaxEpochs(usize::try_from(n).map_err(serde::de::Error::custom)?)
107            }
108            Format::Tagged(Tagged::MaxEpochs(n)) => Self::MaxEpochs(n),
109            Format::Tagged(Tagged::KeepAll) => Self::KeepAll,
110        })
111    }
112}
113
114/// The input to [`MlsGroup::delete_past_epoch_secrets()`].
115///
116/// This struct can be used for manual deletion of past epoch secrets by the application.
117///
118/// An [`MlsGroup`] also applies automatic deletion of past epoch secrets by default.
119///
120/// For more information, see [`PastEpochDeletionPolicy`] and [`MlsGroup::set_past_epoch_deletion_policy()`].
121///
122/// These methods can be used by the application to set up time-based deletion schedules:
123/// - [`PastEpochDeletion::before_timestamp()`]
124/// - [`PastEpochDeletion::older_than_duration()`]
125///
126/// **NOTE**: Epoch secrets that were created using `openmls=0.8.1` or earlier will not yet include a timestamp.
127/// After migration, these may not always be deleted by applying a time-based [`PastEpochDeletion`]. Only if a new secret that does include a timestamp is added later, and it matches the time-based condition in the [`PastEpochDeletion`], all earlier past epoch secrets without timestamps will be deleted, as well. However, otherwise, past epoch secrets without timestamps will not be affected by applying time-based [`PastEpochDeletion`]s.
128///
129/// To manually delete all past epoch secrets without timestamps, see:
130/// [`PastEpochDeletion::delete_all_without_timestamps()`]
131pub struct PastEpochDeletion {
132    pub(crate) config: Option<PastEpochDeletionTimeConfig>,
133    pub(crate) max_past_epochs: Option<usize>,
134}
135
136/// A duration or timestamp before which to delete past epoch secrets.
137pub(crate) enum PastEpochDeletionTimeConfig {
138    OlderThanDuration(std::time::Duration),
139    BeforeTimestamp(SystemTime),
140    DeleteAllWithoutTimestamp,
141}
142
143impl PastEpochDeletion {
144    /// Delete all past epoch secrets older than a provided duration.
145    pub fn older_than_duration(duration: std::time::Duration) -> Self {
146        Self {
147            config: Some(PastEpochDeletionTimeConfig::OlderThanDuration(duration)),
148            max_past_epochs: None,
149        }
150    }
151
152    /// Delete all past epoch secrets before a provided timestamp.
153    pub fn before_timestamp(timestamp: SystemTime) -> Self {
154        Self {
155            config: Some(PastEpochDeletionTimeConfig::BeforeTimestamp(timestamp)),
156            max_past_epochs: None,
157        }
158    }
159
160    /// Delete all past epoch secrets without timestamps.
161    ///
162    /// NOTE: This will delete all past epoch secrets having the legacy
163    /// format that does not include a timestamp.
164    pub fn delete_all_without_timestamps() -> Self {
165        Self {
166            config: Some(PastEpochDeletionTimeConfig::DeleteAllWithoutTimestamp),
167            max_past_epochs: None,
168        }
169    }
170
171    /// Delete all past epoch secrets.
172    pub fn delete_all() -> Self {
173        Self {
174            config: None,
175            max_past_epochs: None,
176        }
177    }
178
179    /// Set the number of `max_past_epochs` that should be kept, at most.
180    pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
181        self.max_past_epochs = Some(max_past_epochs);
182        self
183    }
184}
185
186impl PastEpochDeletionPolicy {
187    pub(crate) fn max_epochs(&self) -> Option<usize> {
188        match self {
189            Self::MaxEpochs(epochs) => Some(*epochs),
190            Self::KeepAll => None,
191        }
192    }
193}
194
195/// The [`MlsGroupJoinConfig`] contains all configuration parameters that are
196/// relevant to group operation at runtime. It is used to configure the group's
197/// behaviour when joining an existing group. To configure a newly created
198/// group, use [`MlsGroupCreateConfig`].
199#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
200pub struct MlsGroupJoinConfig {
201    /// Defines the wire format policy for outgoing and incoming handshake messages.
202    /// Application are always encrypted regardless.
203    pub(crate) wire_format_policy: WireFormatPolicy,
204    /// Size of padding in bytes
205    pub(crate) padding_size: usize,
206    /// Maximum number of past epochs for which application messages
207    /// can be decrypted. The default is 0.
208    #[serde(alias = "max_past_epochs")]
209    // alias for backwards compatibility after renaming field
210    pub(crate) past_epoch_deletion_policy: PastEpochDeletionPolicy,
211    /// Number of resumption secrets to keep
212    pub(crate) number_of_resumption_psks: usize,
213    /// Flag to indicate the Ratchet Tree Extension should be used
214    pub(crate) use_ratchet_tree_extension: bool,
215    /// Sender ratchet configuration
216    pub(crate) sender_ratchet_configuration: SenderRatchetConfiguration,
217}
218
219impl MlsGroupJoinConfig {
220    /// Returns a builder for [`MlsGroupJoinConfig`].
221    pub fn builder() -> MlsGroupJoinConfigBuilder {
222        MlsGroupJoinConfigBuilder::new()
223    }
224
225    /// Returns the wire format policy set in this  [`MlsGroupJoinConfig`].
226    pub fn wire_format_policy(&self) -> WireFormatPolicy {
227        self.wire_format_policy
228    }
229
230    /// Returns the padding size set in this  [`MlsGroupJoinConfig`].
231    pub fn padding_size(&self) -> usize {
232        self.padding_size
233    }
234
235    /// Returns the [`SenderRatchetConfiguration`] set in this  [`MlsGroupJoinConfig`].
236    pub fn sender_ratchet_configuration(&self) -> &SenderRatchetConfiguration {
237        &self.sender_ratchet_configuration
238    }
239
240    /// Returns the max past epochs configured in this [`MlsGroupJoinConfig`]
241    pub(crate) fn max_past_epochs(&self) -> Option<usize> {
242        self.past_epoch_deletion_policy.max_epochs()
243    }
244
245    pub(crate) fn past_epoch_deletion_policy(&self) -> &PastEpochDeletionPolicy {
246        &self.past_epoch_deletion_policy
247    }
248}
249
250/// Specifies configuration for the creation of an [`MlsGroup`]. Refer to the
251/// [User Manual](https://book.openmls.tech/user_manual/group_config.html) for
252/// more information about the different configuration values.
253#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
254pub struct MlsGroupCreateConfig {
255    /// Capabilities advertised in the creator's leaf node
256    pub(crate) capabilities: Capabilities,
257    /// Lifetime of the own leaf node
258    pub(crate) lifetime: Lifetime,
259    /// Ciphersuite and protocol version
260    pub(crate) ciphersuite: Ciphersuite,
261    /// Configuration parameters relevant to group operation at runtime
262    pub(crate) join_config: MlsGroupJoinConfig,
263    /// List of initial group context extensions
264    pub(crate) group_context_extensions: Extensions<GroupContext>,
265    /// List of initial leaf node extensions
266    pub(crate) leaf_node_extensions: Extensions<LeafNode>,
267    /// Flag marking the created group as an emulation group of a virtual
268    /// client. Only consulted at group creation, the group keeps the flag
269    /// itself afterwards.
270    #[cfg(feature = "virtual-clients-draft")]
271    pub(crate) emulation_group: bool,
272}
273
274impl Default for MlsGroupCreateConfig {
275    fn default() -> Self {
276        Self {
277            capabilities: Capabilities::default(),
278            lifetime: Lifetime::default(),
279            ciphersuite: Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
280            join_config: MlsGroupJoinConfig::default(),
281            group_context_extensions: Extensions::default(),
282            leaf_node_extensions: Extensions::default(),
283            #[cfg(feature = "virtual-clients-draft")]
284            emulation_group: false,
285        }
286    }
287}
288
289/// Builder struct for an [`MlsGroupJoinConfig`].
290#[derive(Default)]
291pub struct MlsGroupJoinConfigBuilder {
292    join_config: MlsGroupJoinConfig,
293}
294
295impl MlsGroupJoinConfigBuilder {
296    /// Creates a new builder with default values.
297    fn new() -> Self {
298        Self {
299            join_config: MlsGroupJoinConfig::default(),
300        }
301    }
302
303    /// Sets the `wire_format` property of the [`MlsGroupJoinConfig`].
304    pub fn wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
305        self.join_config.wire_format_policy = wire_format_policy;
306        self
307    }
308
309    /// Sets the `padding_size` property of the [`MlsGroupJoinConfig`].
310    pub fn padding_size(mut self, padding_size: usize) -> Self {
311        self.join_config.padding_size = padding_size;
312        self
313    }
314
315    /// Sets the `max_past_epochs` property of the [`MlsGroupJoinConfig`].
316    ///
317    /// This method overrides the policy set by [`Self::set_past_epoch_deletion_policy()`],
318    /// and is equivalent to setting the past epoch deletion policy to
319    /// `PastEpochDeletionPolicy::MaxEpochs(max_past_epochs)`.
320    ///
321    /// **WARNING**
322    ///
323    /// This feature enables the storage of message secrets from past epochs.
324    /// It is a trade-off between functionality and forward secrecy and should only be enabled
325    /// if the Delivery Service cannot guarantee that application messages will be sent in
326    /// the same epoch in which they were generated. The number for `max_epochs` should be
327    /// as low as possible.
328    pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
329        self.join_config.past_epoch_deletion_policy =
330            PastEpochDeletionPolicy::MaxEpochs(max_past_epochs);
331        self
332    }
333
334    /// Set the policy for deleting past epoch secrets.
335    ///
336    /// By default, storage of past epoch secrets is disabled.
337    ///
338    /// This method overrides the configuration set by [`Self::max_past_epochs()`].
339    ///
340    /// **WARNING**
341    ///
342    /// This feature enables the storage of message secrets from past epochs.
343    /// It is a trade-off between functionality and forward secrecy and should only be enabled
344    /// if the Delivery Service cannot guarantee that application messages will be sent in
345    /// the same epoch in which they were generated. The number for `max_epochs` should be
346    /// as low as possible.
347    pub fn set_past_epoch_deletion_policy(mut self, policy: PastEpochDeletionPolicy) -> Self {
348        self.join_config.past_epoch_deletion_policy = policy;
349        self
350    }
351
352    /// Sets the `number_of_resumption_psks` property of the [`MlsGroupJoinConfig`].
353    pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
354        self.join_config.number_of_resumption_psks = number_of_resumption_psks;
355        self
356    }
357
358    /// Sets the `use_ratchet_tree_extension` property of the [`MlsGroupJoinConfig`].
359    pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
360        self.join_config.use_ratchet_tree_extension = use_ratchet_tree_extension;
361        self
362    }
363
364    /// Sets the `sender_ratchet_configuration` property of the [`MlsGroupJoinConfig`].
365    pub fn sender_ratchet_configuration(
366        mut self,
367        sender_ratchet_configuration: SenderRatchetConfiguration,
368    ) -> Self {
369        self.join_config.sender_ratchet_configuration = sender_ratchet_configuration;
370        self
371    }
372
373    /// Finalizes the builder and returns an [`MlsGroupJoinConfig`].
374    pub fn build(self) -> MlsGroupJoinConfig {
375        self.join_config
376    }
377}
378
379impl MlsGroupCreateConfig {
380    /// Returns a builder for [`MlsGroupCreateConfig`]
381    pub fn builder() -> MlsGroupCreateConfigBuilder {
382        MlsGroupCreateConfigBuilder::new()
383    }
384
385    /// Returns the [`MlsGroupCreateConfig`] wire format policy.
386    pub fn wire_format_policy(&self) -> WireFormatPolicy {
387        self.join_config.wire_format_policy
388    }
389
390    /// Returns the [`MlsGroupCreateConfig`] padding size.
391    pub fn padding_size(&self) -> usize {
392        self.join_config.padding_size
393    }
394
395    /// Returns the [`MlsGroupCreateConfig`] max past epochs.
396    pub fn max_past_epochs(&self) -> Option<usize> {
397        self.join_config.max_past_epochs()
398    }
399
400    /// Returns the [`MlsGroupCreateConfig`] number of resumption psks.
401    pub fn number_of_resumption_psks(&self) -> usize {
402        self.join_config.number_of_resumption_psks
403    }
404
405    /// Returns the [`MlsGroupCreateConfig`] boolean flag that indicates whether ratchet_tree_extension should be used.
406    pub fn use_ratchet_tree_extension(&self) -> bool {
407        self.join_config.use_ratchet_tree_extension
408    }
409
410    /// Returns the [`MlsGroupCreateConfig`] sender ratchet configuration.
411    pub fn sender_ratchet_configuration(&self) -> &SenderRatchetConfiguration {
412        &self.join_config.sender_ratchet_configuration
413    }
414
415    /// Returns the [`Extensions`] set as the initial group context.
416    /// This does not contain the initial group context extensions
417    /// added from builder calls to `external_senders` or `required_capabilities`.
418    pub fn group_context_extensions(&self) -> &Extensions<GroupContext> {
419        &self.group_context_extensions
420    }
421
422    /// Returns the [`MlsGroupCreateConfig`] lifetime configuration.
423    pub fn lifetime(&self) -> &Lifetime {
424        &self.lifetime
425    }
426
427    /// Returns the [`Ciphersuite`].
428    pub fn ciphersuite(&self) -> Ciphersuite {
429        self.ciphersuite
430    }
431
432    /// Returns whether groups created with this config are emulation groups of
433    /// a virtual client. See [`MlsGroupCreateConfigBuilder::emulation_group`].
434    #[cfg(feature = "virtual-clients-draft")]
435    pub fn emulation_group(&self) -> bool {
436        self.emulation_group
437    }
438
439    #[cfg(any(feature = "test-utils", test))]
440    pub fn test_default(ciphersuite: Ciphersuite) -> Self {
441        Self::builder()
442            .wire_format_policy(WireFormatPolicy::new(
443                OutgoingWireFormatPolicy::AlwaysPlaintext,
444                IncomingWireFormatPolicy::Mixed,
445            ))
446            .ciphersuite(ciphersuite)
447            .build()
448    }
449
450    /// Returns the [`MlsGroupJoinConfig`] of groups created with this create config.
451    pub fn join_config(&self) -> &MlsGroupJoinConfig {
452        &self.join_config
453    }
454}
455
456/// Builder for an [`MlsGroupCreateConfig`].
457#[derive(Default, Debug)]
458pub struct MlsGroupCreateConfigBuilder {
459    config: MlsGroupCreateConfig,
460}
461
462impl MlsGroupCreateConfigBuilder {
463    /// Creates a new builder with default values.
464    fn new() -> Self {
465        MlsGroupCreateConfigBuilder {
466            config: MlsGroupCreateConfig::default(),
467        }
468    }
469
470    /// Sets the `wire_format` property of the MlsGroupCreateConfig.
471    pub fn wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
472        self.config.join_config.wire_format_policy = wire_format_policy;
473        self
474    }
475
476    /// Sets the `padding_size` property of the MlsGroupCreateConfig.
477    pub fn padding_size(mut self, padding_size: usize) -> Self {
478        self.config.join_config.padding_size = padding_size;
479        self
480    }
481
482    /// Sets the `max_past_epochs` property of the MlsGroupCreateConfig.
483    /// This allows application messages from previous epochs to be decrypted.
484    ///
485    /// This method overrides the policy set by [`Self::set_past_epoch_deletion_policy()`],
486    /// and is equivalent to setting the past epoch deletion policy to
487    /// `PastEpochDeletionPolicy::MaxEpochs(max_past_epochs)`.
488    ///
489    /// **WARNING**
490    ///
491    /// This feature enables the storage of message secrets from past epochs.
492    /// It is a trade-off between functionality and forward secrecy and should only be enabled
493    /// if the Delivery Service cannot guarantee that application messages will be sent in
494    /// the same epoch in which they were generated. The number for `max_epochs` should be
495    /// as low as possible.
496    pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
497        self.config.join_config.past_epoch_deletion_policy =
498            PastEpochDeletionPolicy::MaxEpochs(max_past_epochs);
499        self
500    }
501
502    /// Set the policy for deleting past epoch secrets.
503    ///
504    /// By default, storage of past epoch secrets is disabled.
505    ///
506    /// This method overrides the configuration set by [`Self::max_past_epochs()`].
507    ///
508    /// **WARNING**
509    ///
510    /// This feature enables the storage of message secrets from past epochs.
511    /// It is a trade-off between functionality and forward secrecy and should only be enabled
512    /// if the Delivery Service cannot guarantee that application messages will be sent in
513    /// the same epoch in which they were generated. The number for `max_epochs` should be
514    /// as low as possible.
515    pub fn set_past_epoch_deletion_policy(mut self, policy: PastEpochDeletionPolicy) -> Self {
516        self.config.join_config.past_epoch_deletion_policy = policy;
517        self
518    }
519
520    /// Sets the `number_of_resumption_psks` property of the MlsGroupCreateConfig.
521    pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
522        self.config.join_config.number_of_resumption_psks = number_of_resumption_psks;
523        self
524    }
525
526    /// Sets the `use_ratchet_tree_extension` property of the MlsGroupCreateConfig.
527    pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
528        self.config.join_config.use_ratchet_tree_extension = use_ratchet_tree_extension;
529        self
530    }
531
532    /// Sets the `capabilities` of the group creator's leaf node.
533    pub fn capabilities(mut self, capabilities: Capabilities) -> Self {
534        self.config.capabilities = capabilities;
535        self
536    }
537
538    /// Marks the group as an emulation group of a virtual client.
539    ///
540    /// This is the application's declaration that the group's members are the
541    /// emulator clients of one virtual client. It is local state, nothing about
542    /// it travels on the wire, and every member of an emulation group has to
543    /// set it. Members that join by Welcome set it on the
544    /// [`StagedWelcome`](crate::group::StagedWelcome) instead, and members that
545    /// join by external commit on the
546    /// [`ExternalCommitBuilder`](crate::group::ExternalCommitBuilder).
547    ///
548    /// An emulation group derives the virtual client's secrets from its
549    /// derivation epochs. The initial epoch is a derivation epoch, and so is
550    /// the output epoch of every commit that changes membership or that carries
551    /// a `new_derivation_epoch` action in its virtual-clients Safe AAD item
552    /// (see [`CommitBuilder::derivation_epoch`]). OpenMLS derives and
553    /// persists the derivation-epoch state itself at group creation, at a
554    /// Welcome join, and when such a commit is merged. Applications should wrap
555    /// every merge in a storage transaction. A merge performs several storage
556    /// writes even without virtual clients, and derivation-epoch registration
557    /// adds more.
558    ///
559    /// Use [`MlsGroup::newest_vc_derivation_epoch`] to look up the derivation
560    /// epoch that virtual-client operations resolve to. It may be older than
561    /// the group's current epoch.
562    ///
563    /// Groups without this flag never write virtual-clients state.
564    ///
565    /// [`CommitBuilder::derivation_epoch`]: crate::group::CommitBuilder::derivation_epoch
566    /// [`MlsGroup::newest_vc_derivation_epoch`]: crate::group::MlsGroup::newest_vc_derivation_epoch
567    #[cfg(feature = "virtual-clients-draft")]
568    pub fn emulation_group(mut self, emulation_group: bool) -> Self {
569        self.config.emulation_group = emulation_group;
570        self
571    }
572
573    /// Sets the `sender_ratchet_configuration` property of the MlsGroupCreateConfig.
574    /// See [`SenderRatchetConfiguration`] for more information.
575    pub fn sender_ratchet_configuration(
576        mut self,
577        sender_ratchet_configuration: SenderRatchetConfiguration,
578    ) -> Self {
579        self.config.join_config.sender_ratchet_configuration = sender_ratchet_configuration;
580        self
581    }
582
583    /// Sets the `lifetime` property of the MlsGroupCreateConfig.
584    pub fn lifetime(mut self, lifetime: Lifetime) -> Self {
585        self.config.lifetime = lifetime;
586        self
587    }
588
589    /// Sets the `ciphersuite` property of the MlsGroupCreateConfig.
590    pub fn ciphersuite(mut self, ciphersuite: Ciphersuite) -> Self {
591        self.config.ciphersuite = ciphersuite;
592        self
593    }
594
595    /// Sets initial group context extensions.
596    pub fn with_group_context_extensions(mut self, extensions: Extensions<GroupContext>) -> Self {
597        self.config.group_context_extensions = extensions;
598        self
599    }
600
601    /// Sets extensions of the group creator's [`LeafNode`].
602    ///
603    /// Returns an error if the extension types are not valid in a leaf node.
604    pub fn with_leaf_node_extensions(
605        mut self,
606        extensions: Extensions<LeafNode>,
607    ) -> Result<Self, LeafNodeValidationError> {
608        // Make sure that the extension type is supported in this context.
609        // This means that the leaf node needs to have support listed in the
610        // the capabilities (https://validation.openmls.tech/#valn0107).
611        if !self.config.capabilities.contains_extensions(&extensions) {
612            return Err(LeafNodeValidationError::ExtensionsNotInCapabilities);
613        }
614
615        // Note that the extensions have already been checked to be allowed here.
616        self.config.leaf_node_extensions = extensions;
617        Ok(self)
618    }
619
620    /// Finalizes the builder and returns an [`MlsGroupCreateConfig`].
621    pub fn build(self) -> MlsGroupCreateConfig {
622        self.config
623    }
624}
625
626/// Defines what wire format is acceptable for incoming handshake messages.
627/// Note that application messages must always be encrypted.
628#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
629pub enum IncomingWireFormatPolicy {
630    /// Handshake messages must always be PrivateMessage
631    AlwaysCiphertext,
632    /// Handshake messages must always be PublicMessage
633    AlwaysPlaintext,
634    /// Handshake messages can either be PrivateMessage or PublicMessage
635    Mixed,
636}
637
638impl IncomingWireFormatPolicy {
639    pub(crate) fn is_compatible_with(&self, wire_format: WireFormat) -> bool {
640        match self {
641            IncomingWireFormatPolicy::AlwaysCiphertext => wire_format == WireFormat::PrivateMessage,
642            IncomingWireFormatPolicy::AlwaysPlaintext => wire_format == WireFormat::PublicMessage,
643            IncomingWireFormatPolicy::Mixed => {
644                wire_format == WireFormat::PrivateMessage
645                    || wire_format == WireFormat::PublicMessage
646            }
647        }
648    }
649}
650
651/// Defines what wire format should be used for outgoing handshake messages.
652/// Note that application messages must always be encrypted.
653#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
654pub enum OutgoingWireFormatPolicy {
655    /// Handshake messages must always be PrivateMessage
656    AlwaysCiphertext,
657    /// Handshake messages must always be PublicMessage
658    AlwaysPlaintext,
659}
660
661/// Defines what wire format is desired for outgoing handshake messages.
662/// Note that application messages must always be encrypted.
663#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
664pub struct WireFormatPolicy {
665    outgoing: OutgoingWireFormatPolicy,
666    incoming: IncomingWireFormatPolicy,
667}
668
669impl WireFormatPolicy {
670    /// Creates a new wire format policy from an [`OutgoingWireFormatPolicy`]
671    /// and an [`IncomingWireFormatPolicy`].
672    #[cfg(any(feature = "test-utils", test))]
673    pub(crate) fn new(
674        outgoing: OutgoingWireFormatPolicy,
675        incoming: IncomingWireFormatPolicy,
676    ) -> Self {
677        Self { outgoing, incoming }
678    }
679
680    /// Returns a reference to the wire format policy's outgoing wire format policy.
681    pub fn outgoing(&self) -> OutgoingWireFormatPolicy {
682        self.outgoing
683    }
684
685    /// Returns a reference to the wire format policy's incoming wire format policy.
686    pub fn incoming(&self) -> IncomingWireFormatPolicy {
687        self.incoming
688    }
689}
690
691impl Default for WireFormatPolicy {
692    fn default() -> Self {
693        PURE_CIPHERTEXT_WIRE_FORMAT_POLICY
694    }
695}
696
697impl From<OutgoingWireFormatPolicy> for WireFormat {
698    fn from(outgoing: OutgoingWireFormatPolicy) -> Self {
699        match outgoing {
700            OutgoingWireFormatPolicy::AlwaysCiphertext => WireFormat::PrivateMessage,
701            OutgoingWireFormatPolicy::AlwaysPlaintext => WireFormat::PublicMessage,
702        }
703    }
704}
705
706/// All valid wire format policy combinations.
707/// - [`PURE_PLAINTEXT_WIRE_FORMAT_POLICY`]
708/// - [`PURE_CIPHERTEXT_WIRE_FORMAT_POLICY`]
709/// - [`MIXED_PLAINTEXT_WIRE_FORMAT_POLICY`]
710/// - [`MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY`]
711pub const WIRE_FORMAT_POLICIES: [WireFormatPolicy; 4] = [
712    PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
713    PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
714    MIXED_PLAINTEXT_WIRE_FORMAT_POLICY,
715    MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY,
716];
717
718/// Incoming and outgoing wire formats are always plaintext.
719pub const PURE_PLAINTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
720    outgoing: OutgoingWireFormatPolicy::AlwaysPlaintext,
721    incoming: IncomingWireFormatPolicy::AlwaysPlaintext,
722};
723
724/// Incoming and outgoing wire formats are always ciphertext.
725pub const PURE_CIPHERTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
726    outgoing: OutgoingWireFormatPolicy::AlwaysCiphertext,
727    incoming: IncomingWireFormatPolicy::AlwaysCiphertext,
728};
729
730/// Incoming wire formats can be mixed while outgoing wire formats are always
731/// plaintext.
732pub const MIXED_PLAINTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
733    outgoing: OutgoingWireFormatPolicy::AlwaysPlaintext,
734    incoming: IncomingWireFormatPolicy::Mixed,
735};
736
737/// Incoming wire formats can be mixed while outgoing wire formats are always
738/// ciphertext.
739pub const MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
740    outgoing: OutgoingWireFormatPolicy::AlwaysCiphertext,
741    incoming: IncomingWireFormatPolicy::Mixed,
742};
743
744#[cfg(test)]
745mod tests {
746    use super::PastEpochDeletionPolicy;
747
748    #[test]
749    fn past_epoch_deletion_policy_roundtrip() {
750        for policy in [
751            PastEpochDeletionPolicy::MaxEpochs(0),
752            PastEpochDeletionPolicy::MaxEpochs(42),
753            PastEpochDeletionPolicy::KeepAll,
754        ] {
755            let json = serde_json::to_string(&policy).unwrap();
756            let deserialized: PastEpochDeletionPolicy = serde_json::from_str(&json).unwrap();
757            assert_eq!(deserialized, policy);
758        }
759
760        // MaxEpochs(usize::MAX) is indistinguishable from KeepAll after serialization.
761        let json = serde_json::to_string(&PastEpochDeletionPolicy::MaxEpochs(usize::MAX)).unwrap();
762        let deserialized: PastEpochDeletionPolicy = serde_json::from_str(&json).unwrap();
763        assert_eq!(deserialized, PastEpochDeletionPolicy::KeepAll);
764    }
765
766    #[test]
767    fn past_epoch_deletion_policy_deserializes_plain_integer() {
768        let deserialized: PastEpochDeletionPolicy = serde_json::from_str("42").unwrap();
769        assert_eq!(deserialized, PastEpochDeletionPolicy::MaxEpochs(42));
770
771        let deserialized: PastEpochDeletionPolicy =
772            serde_json::from_str(&u64::MAX.to_string()).unwrap();
773        assert_eq!(deserialized, PastEpochDeletionPolicy::KeepAll);
774    }
775
776    #[test]
777    fn past_epoch_deletion_policy_deserializes_legacy_tagged_format() {
778        // Externally tagged enum format used before 8bdba6f.
779        let deserialized: PastEpochDeletionPolicy =
780            serde_json::from_str(r#"{"MaxEpochs":42}"#).unwrap();
781        assert_eq!(deserialized, PastEpochDeletionPolicy::MaxEpochs(42));
782
783        let deserialized: PastEpochDeletionPolicy = serde_json::from_str(r#""KeepAll""#).unwrap();
784        assert_eq!(deserialized, PastEpochDeletionPolicy::KeepAll);
785    }
786}