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}
268
269impl Default for MlsGroupCreateConfig {
270    fn default() -> Self {
271        Self {
272            capabilities: Capabilities::default(),
273            lifetime: Lifetime::default(),
274            ciphersuite: Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
275            join_config: MlsGroupJoinConfig::default(),
276            group_context_extensions: Extensions::default(),
277            leaf_node_extensions: Extensions::default(),
278        }
279    }
280}
281
282/// Builder struct for an [`MlsGroupJoinConfig`].
283#[derive(Default)]
284pub struct MlsGroupJoinConfigBuilder {
285    join_config: MlsGroupJoinConfig,
286}
287
288impl MlsGroupJoinConfigBuilder {
289    /// Creates a new builder with default values.
290    fn new() -> Self {
291        Self {
292            join_config: MlsGroupJoinConfig::default(),
293        }
294    }
295
296    /// Sets the `wire_format` property of the [`MlsGroupJoinConfig`].
297    pub fn wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
298        self.join_config.wire_format_policy = wire_format_policy;
299        self
300    }
301
302    /// Sets the `padding_size` property of the [`MlsGroupJoinConfig`].
303    pub fn padding_size(mut self, padding_size: usize) -> Self {
304        self.join_config.padding_size = padding_size;
305        self
306    }
307
308    /// Sets the `max_past_epochs` property of the [`MlsGroupJoinConfig`].
309    ///
310    /// This method overrides the policy set by [`Self::set_past_epoch_deletion_policy()`],
311    /// and is equivalent to setting the past epoch deletion policy to
312    /// `PastEpochDeletionPolicy::MaxEpochs(max_past_epochs)`.
313    ///
314    /// **WARNING**
315    ///
316    /// This feature enables the storage of message secrets from past epochs.
317    /// It is a trade-off between functionality and forward secrecy and should only be enabled
318    /// if the Delivery Service cannot guarantee that application messages will be sent in
319    /// the same epoch in which they were generated. The number for `max_epochs` should be
320    /// as low as possible.
321    pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
322        self.join_config.past_epoch_deletion_policy =
323            PastEpochDeletionPolicy::MaxEpochs(max_past_epochs);
324        self
325    }
326
327    /// Set the policy for deleting past epoch secrets.
328    ///
329    /// By default, storage of past epoch secrets is disabled.
330    ///
331    /// This method overrides the configuration set by [`Self::max_past_epochs()`].
332    ///
333    /// **WARNING**
334    ///
335    /// This feature enables the storage of message secrets from past epochs.
336    /// It is a trade-off between functionality and forward secrecy and should only be enabled
337    /// if the Delivery Service cannot guarantee that application messages will be sent in
338    /// the same epoch in which they were generated. The number for `max_epochs` should be
339    /// as low as possible.
340    pub fn set_past_epoch_deletion_policy(mut self, policy: PastEpochDeletionPolicy) -> Self {
341        self.join_config.past_epoch_deletion_policy = policy;
342        self
343    }
344
345    /// Sets the `number_of_resumption_psks` property of the [`MlsGroupJoinConfig`].
346    pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
347        self.join_config.number_of_resumption_psks = number_of_resumption_psks;
348        self
349    }
350
351    /// Sets the `use_ratchet_tree_extension` property of the [`MlsGroupJoinConfig`].
352    pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
353        self.join_config.use_ratchet_tree_extension = use_ratchet_tree_extension;
354        self
355    }
356
357    /// Sets the `sender_ratchet_configuration` property of the [`MlsGroupJoinConfig`].
358    pub fn sender_ratchet_configuration(
359        mut self,
360        sender_ratchet_configuration: SenderRatchetConfiguration,
361    ) -> Self {
362        self.join_config.sender_ratchet_configuration = sender_ratchet_configuration;
363        self
364    }
365
366    /// Finalizes the builder and returns an [`MlsGroupJoinConfig`].
367    pub fn build(self) -> MlsGroupJoinConfig {
368        self.join_config
369    }
370}
371
372impl MlsGroupCreateConfig {
373    /// Returns a builder for [`MlsGroupCreateConfig`]
374    pub fn builder() -> MlsGroupCreateConfigBuilder {
375        MlsGroupCreateConfigBuilder::new()
376    }
377
378    /// Returns the [`MlsGroupCreateConfig`] wire format policy.
379    pub fn wire_format_policy(&self) -> WireFormatPolicy {
380        self.join_config.wire_format_policy
381    }
382
383    /// Returns the [`MlsGroupCreateConfig`] padding size.
384    pub fn padding_size(&self) -> usize {
385        self.join_config.padding_size
386    }
387
388    /// Returns the [`MlsGroupCreateConfig`] max past epochs.
389    pub fn max_past_epochs(&self) -> Option<usize> {
390        self.join_config.max_past_epochs()
391    }
392
393    /// Returns the [`MlsGroupCreateConfig`] number of resumption psks.
394    pub fn number_of_resumption_psks(&self) -> usize {
395        self.join_config.number_of_resumption_psks
396    }
397
398    /// Returns the [`MlsGroupCreateConfig`] boolean flag that indicates whether ratchet_tree_extension should be used.
399    pub fn use_ratchet_tree_extension(&self) -> bool {
400        self.join_config.use_ratchet_tree_extension
401    }
402
403    /// Returns the [`MlsGroupCreateConfig`] sender ratchet configuration.
404    pub fn sender_ratchet_configuration(&self) -> &SenderRatchetConfiguration {
405        &self.join_config.sender_ratchet_configuration
406    }
407
408    /// Returns the [`Extensions`] set as the initial group context.
409    /// This does not contain the initial group context extensions
410    /// added from builder calls to `external_senders` or `required_capabilities`.
411    pub fn group_context_extensions(&self) -> &Extensions<GroupContext> {
412        &self.group_context_extensions
413    }
414
415    /// Returns the [`MlsGroupCreateConfig`] lifetime configuration.
416    pub fn lifetime(&self) -> &Lifetime {
417        &self.lifetime
418    }
419
420    /// Returns the [`Ciphersuite`].
421    pub fn ciphersuite(&self) -> Ciphersuite {
422        self.ciphersuite
423    }
424
425    #[cfg(any(feature = "test-utils", test))]
426    pub fn test_default(ciphersuite: Ciphersuite) -> Self {
427        Self::builder()
428            .wire_format_policy(WireFormatPolicy::new(
429                OutgoingWireFormatPolicy::AlwaysPlaintext,
430                IncomingWireFormatPolicy::Mixed,
431            ))
432            .ciphersuite(ciphersuite)
433            .build()
434    }
435
436    /// Returns the [`MlsGroupJoinConfig`] of groups created with this create config.
437    pub fn join_config(&self) -> &MlsGroupJoinConfig {
438        &self.join_config
439    }
440}
441
442/// Builder for an [`MlsGroupCreateConfig`].
443#[derive(Default, Debug)]
444pub struct MlsGroupCreateConfigBuilder {
445    config: MlsGroupCreateConfig,
446}
447
448impl MlsGroupCreateConfigBuilder {
449    /// Creates a new builder with default values.
450    fn new() -> Self {
451        MlsGroupCreateConfigBuilder {
452            config: MlsGroupCreateConfig::default(),
453        }
454    }
455
456    /// Sets the `wire_format` property of the MlsGroupCreateConfig.
457    pub fn wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
458        self.config.join_config.wire_format_policy = wire_format_policy;
459        self
460    }
461
462    /// Sets the `padding_size` property of the MlsGroupCreateConfig.
463    pub fn padding_size(mut self, padding_size: usize) -> Self {
464        self.config.join_config.padding_size = padding_size;
465        self
466    }
467
468    /// Sets the `max_past_epochs` property of the MlsGroupCreateConfig.
469    /// This allows application messages from previous epochs to be decrypted.
470    ///
471    /// This method overrides the policy set by [`Self::set_past_epoch_deletion_policy()`],
472    /// and is equivalent to setting the past epoch deletion policy to
473    /// `PastEpochDeletionPolicy::MaxEpochs(max_past_epochs)`.
474    ///
475    /// **WARNING**
476    ///
477    /// This feature enables the storage of message secrets from past epochs.
478    /// It is a trade-off between functionality and forward secrecy and should only be enabled
479    /// if the Delivery Service cannot guarantee that application messages will be sent in
480    /// the same epoch in which they were generated. The number for `max_epochs` should be
481    /// as low as possible.
482    pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
483        self.config.join_config.past_epoch_deletion_policy =
484            PastEpochDeletionPolicy::MaxEpochs(max_past_epochs);
485        self
486    }
487
488    /// Set the policy for deleting past epoch secrets.
489    ///
490    /// By default, storage of past epoch secrets is disabled.
491    ///
492    /// This method overrides the configuration set by [`Self::max_past_epochs()`].
493    ///
494    /// **WARNING**
495    ///
496    /// This feature enables the storage of message secrets from past epochs.
497    /// It is a trade-off between functionality and forward secrecy and should only be enabled
498    /// if the Delivery Service cannot guarantee that application messages will be sent in
499    /// the same epoch in which they were generated. The number for `max_epochs` should be
500    /// as low as possible.
501    pub fn set_past_epoch_deletion_policy(mut self, policy: PastEpochDeletionPolicy) -> Self {
502        self.config.join_config.past_epoch_deletion_policy = policy;
503        self
504    }
505
506    /// Sets the `number_of_resumption_psks` property of the MlsGroupCreateConfig.
507    pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
508        self.config.join_config.number_of_resumption_psks = number_of_resumption_psks;
509        self
510    }
511
512    /// Sets the `use_ratchet_tree_extension` property of the MlsGroupCreateConfig.
513    pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
514        self.config.join_config.use_ratchet_tree_extension = use_ratchet_tree_extension;
515        self
516    }
517
518    /// Sets the `capabilities` of the group creator's leaf node.
519    pub fn capabilities(mut self, capabilities: Capabilities) -> Self {
520        self.config.capabilities = capabilities;
521        self
522    }
523
524    /// Sets the `sender_ratchet_configuration` property of the MlsGroupCreateConfig.
525    /// See [`SenderRatchetConfiguration`] for more information.
526    pub fn sender_ratchet_configuration(
527        mut self,
528        sender_ratchet_configuration: SenderRatchetConfiguration,
529    ) -> Self {
530        self.config.join_config.sender_ratchet_configuration = sender_ratchet_configuration;
531        self
532    }
533
534    /// Sets the `lifetime` property of the MlsGroupCreateConfig.
535    pub fn lifetime(mut self, lifetime: Lifetime) -> Self {
536        self.config.lifetime = lifetime;
537        self
538    }
539
540    /// Sets the `ciphersuite` property of the MlsGroupCreateConfig.
541    pub fn ciphersuite(mut self, ciphersuite: Ciphersuite) -> Self {
542        self.config.ciphersuite = ciphersuite;
543        self
544    }
545
546    /// Sets initial group context extensions.
547    pub fn with_group_context_extensions(mut self, extensions: Extensions<GroupContext>) -> Self {
548        self.config.group_context_extensions = extensions;
549        self
550    }
551
552    /// Sets extensions of the group creator's [`LeafNode`].
553    ///
554    /// Returns an error if the extension types are not valid in a leaf node.
555    pub fn with_leaf_node_extensions(
556        mut self,
557        extensions: Extensions<LeafNode>,
558    ) -> Result<Self, LeafNodeValidationError> {
559        // Make sure that the extension type is supported in this context.
560        // This means that the leaf node needs to have support listed in the
561        // the capabilities (https://validation.openmls.tech/#valn0107).
562        if !self.config.capabilities.contains_extensions(&extensions) {
563            return Err(LeafNodeValidationError::ExtensionsNotInCapabilities);
564        }
565
566        // Note that the extensions have already been checked to be allowed here.
567        self.config.leaf_node_extensions = extensions;
568        Ok(self)
569    }
570
571    /// Finalizes the builder and returns an [`MlsGroupCreateConfig`].
572    pub fn build(self) -> MlsGroupCreateConfig {
573        self.config
574    }
575}
576
577/// Defines what wire format is acceptable for incoming handshake messages.
578/// Note that application messages must always be encrypted.
579#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
580pub enum IncomingWireFormatPolicy {
581    /// Handshake messages must always be PrivateMessage
582    AlwaysCiphertext,
583    /// Handshake messages must always be PublicMessage
584    AlwaysPlaintext,
585    /// Handshake messages can either be PrivateMessage or PublicMessage
586    Mixed,
587}
588
589impl IncomingWireFormatPolicy {
590    pub(crate) fn is_compatible_with(&self, wire_format: WireFormat) -> bool {
591        match self {
592            IncomingWireFormatPolicy::AlwaysCiphertext => wire_format == WireFormat::PrivateMessage,
593            IncomingWireFormatPolicy::AlwaysPlaintext => wire_format == WireFormat::PublicMessage,
594            IncomingWireFormatPolicy::Mixed => {
595                wire_format == WireFormat::PrivateMessage
596                    || wire_format == WireFormat::PublicMessage
597            }
598        }
599    }
600}
601
602/// Defines what wire format should be used for outgoing handshake messages.
603/// Note that application messages must always be encrypted.
604#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
605pub enum OutgoingWireFormatPolicy {
606    /// Handshake messages must always be PrivateMessage
607    AlwaysCiphertext,
608    /// Handshake messages must always be PublicMessage
609    AlwaysPlaintext,
610}
611
612/// Defines what wire format is desired for outgoing handshake messages.
613/// Note that application messages must always be encrypted.
614#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
615pub struct WireFormatPolicy {
616    outgoing: OutgoingWireFormatPolicy,
617    incoming: IncomingWireFormatPolicy,
618}
619
620impl WireFormatPolicy {
621    /// Creates a new wire format policy from an [`OutgoingWireFormatPolicy`]
622    /// and an [`IncomingWireFormatPolicy`].
623    #[cfg(any(feature = "test-utils", test))]
624    pub(crate) fn new(
625        outgoing: OutgoingWireFormatPolicy,
626        incoming: IncomingWireFormatPolicy,
627    ) -> Self {
628        Self { outgoing, incoming }
629    }
630
631    /// Returns a reference to the wire format policy's outgoing wire format policy.
632    pub fn outgoing(&self) -> OutgoingWireFormatPolicy {
633        self.outgoing
634    }
635
636    /// Returns a reference to the wire format policy's incoming wire format policy.
637    pub fn incoming(&self) -> IncomingWireFormatPolicy {
638        self.incoming
639    }
640}
641
642impl Default for WireFormatPolicy {
643    fn default() -> Self {
644        PURE_CIPHERTEXT_WIRE_FORMAT_POLICY
645    }
646}
647
648impl From<OutgoingWireFormatPolicy> for WireFormat {
649    fn from(outgoing: OutgoingWireFormatPolicy) -> Self {
650        match outgoing {
651            OutgoingWireFormatPolicy::AlwaysCiphertext => WireFormat::PrivateMessage,
652            OutgoingWireFormatPolicy::AlwaysPlaintext => WireFormat::PublicMessage,
653        }
654    }
655}
656
657/// All valid wire format policy combinations.
658/// - [`PURE_PLAINTEXT_WIRE_FORMAT_POLICY`]
659/// - [`PURE_CIPHERTEXT_WIRE_FORMAT_POLICY`]
660/// - [`MIXED_PLAINTEXT_WIRE_FORMAT_POLICY`]
661/// - [`MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY`]
662pub const WIRE_FORMAT_POLICIES: [WireFormatPolicy; 4] = [
663    PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
664    PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
665    MIXED_PLAINTEXT_WIRE_FORMAT_POLICY,
666    MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY,
667];
668
669/// Incoming and outgoing wire formats are always plaintext.
670pub const PURE_PLAINTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
671    outgoing: OutgoingWireFormatPolicy::AlwaysPlaintext,
672    incoming: IncomingWireFormatPolicy::AlwaysPlaintext,
673};
674
675/// Incoming and outgoing wire formats are always ciphertext.
676pub const PURE_CIPHERTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
677    outgoing: OutgoingWireFormatPolicy::AlwaysCiphertext,
678    incoming: IncomingWireFormatPolicy::AlwaysCiphertext,
679};
680
681/// Incoming wire formats can be mixed while outgoing wire formats are always
682/// plaintext.
683pub const MIXED_PLAINTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
684    outgoing: OutgoingWireFormatPolicy::AlwaysPlaintext,
685    incoming: IncomingWireFormatPolicy::Mixed,
686};
687
688/// Incoming wire formats can be mixed while outgoing wire formats are always
689/// ciphertext.
690pub const MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
691    outgoing: OutgoingWireFormatPolicy::AlwaysCiphertext,
692    incoming: IncomingWireFormatPolicy::Mixed,
693};
694
695#[cfg(test)]
696mod tests {
697    use super::PastEpochDeletionPolicy;
698
699    #[test]
700    fn past_epoch_deletion_policy_roundtrip() {
701        for policy in [
702            PastEpochDeletionPolicy::MaxEpochs(0),
703            PastEpochDeletionPolicy::MaxEpochs(42),
704            PastEpochDeletionPolicy::KeepAll,
705        ] {
706            let json = serde_json::to_string(&policy).unwrap();
707            let deserialized: PastEpochDeletionPolicy = serde_json::from_str(&json).unwrap();
708            assert_eq!(deserialized, policy);
709        }
710
711        // MaxEpochs(usize::MAX) is indistinguishable from KeepAll after serialization.
712        let json = serde_json::to_string(&PastEpochDeletionPolicy::MaxEpochs(usize::MAX)).unwrap();
713        let deserialized: PastEpochDeletionPolicy = serde_json::from_str(&json).unwrap();
714        assert_eq!(deserialized, PastEpochDeletionPolicy::KeepAll);
715    }
716
717    #[test]
718    fn past_epoch_deletion_policy_deserializes_plain_integer() {
719        let deserialized: PastEpochDeletionPolicy = serde_json::from_str("42").unwrap();
720        assert_eq!(deserialized, PastEpochDeletionPolicy::MaxEpochs(42));
721
722        let deserialized: PastEpochDeletionPolicy =
723            serde_json::from_str(&u64::MAX.to_string()).unwrap();
724        assert_eq!(deserialized, PastEpochDeletionPolicy::KeepAll);
725    }
726
727    #[test]
728    fn past_epoch_deletion_policy_deserializes_legacy_tagged_format() {
729        // Externally tagged enum format used before 8bdba6f.
730        let deserialized: PastEpochDeletionPolicy =
731            serde_json::from_str(r#"{"MaxEpochs":42}"#).unwrap();
732        assert_eq!(deserialized, PastEpochDeletionPolicy::MaxEpochs(42));
733
734        let deserialized: PastEpochDeletionPolicy = serde_json::from_str(r#""KeepAll""#).unwrap();
735        assert_eq!(deserialized, PastEpochDeletionPolicy::KeepAll);
736    }
737}