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}