Skip to main content

de_mls/mls_crypto/
error.rs

1//! Error type for MLS operations.
2//!
3//! Variants that wrap an OpenMLS error type generic over the storage
4//! backend ([`ProcessMessageError`], [`WelcomeError`], …) erase that
5//! parameter via `Box<dyn std::error::Error + Send + Sync>` so a single
6//! [`MlsError`] can flow regardless of which storage backend the
7//! [`OpenMlsService`](crate::mls_crypto::OpenMlsService) is configured
8//! with. The blanket `From` impls below box automatically, so `?` keeps
9//! working at call sites.
10
11use std::error::Error as StdError;
12
13use openmls::prelude::tls_codec::Error as TlsCodecError;
14use openmls::{
15    error::LibraryError,
16    group::{NewGroupError, ProposeRemoveMemberError, WelcomeError},
17    prelude::{
18        CommitToPendingProposalsError, CreateMessageError, KeyPackageNewError, MergeCommitError,
19        MergePendingCommitError, ProcessMessageError, ProposeAddMemberError,
20    },
21};
22use openmls_traits::types::CryptoError;
23
24/// Boxed `std::error::Error` used as the storage-error payload in any
25/// `MlsError` variant whose source type is generic over the storage
26/// backend.
27pub type BoxedError = Box<dyn StdError + Send + Sync>;
28
29#[derive(Debug, thiserror::Error)]
30pub enum MlsError {
31    // ── Crypto + key-package serialization ──
32    #[error(transparent)]
33    UnableToCreateKeyPackage(#[from] KeyPackageNewError),
34
35    #[error("Invalid hash reference: {0}")]
36    InvalidHashRef(#[from] LibraryError),
37
38    #[error("Failed to create signer: {0}")]
39    UnableToCreateSigner(#[from] CryptoError),
40
41    #[error("Key package TLS codec error: {0}")]
42    KeyPackageTls(TlsCodecError),
43
44    // ── MLS wire format (not parameterised over storage) ──
45    #[error(transparent)]
46    MlsMessageDeserialize(#[from] openmls::prelude::Error),
47
48    #[error(transparent)]
49    ProtocolMessage(#[from] openmls::framing::errors::ProtocolMessageError),
50
51    #[error(transparent)]
52    MlsMessageSerialize(#[from] openmls::framing::errors::MlsMessageError),
53
54    // ── MLS group operations (storage-error payloads erased) ──
55    #[error(transparent)]
56    ProcessMessage(BoxedError),
57
58    #[error(transparent)]
59    CreateMessage(#[from] CreateMessageError),
60
61    #[error(transparent)]
62    MergeCommit(BoxedError),
63
64    #[error(transparent)]
65    MergePendingCommit(BoxedError),
66
67    #[error(transparent)]
68    CommitToPendingProposals(BoxedError),
69
70    #[error(transparent)]
71    ProposeAddMember(BoxedError),
72
73    #[error(transparent)]
74    ProposeRemoveMember(BoxedError),
75
76    #[error(transparent)]
77    NewGroup(BoxedError),
78
79    #[error(transparent)]
80    Welcome(BoxedError),
81
82    // ── Storage backend (erased) ──
83    #[error(transparent)]
84    MlsStorage(BoxedError),
85
86    #[error("Lock poisoned: {0}")]
87    Lock(String),
88
89    // ── Semantic ──
90    #[error("Unexpected MLS message type")]
91    UnexpectedMessageType,
92
93    #[error("Conversation not found: {0}")]
94    ConversationNotFound(String),
95
96    #[error("No pending staged commit for group: {0}")]
97    NoPendingStagedCommit(String),
98
99    #[error("Remove proposal references leaf index {0} with no active credential")]
100    UnknownLeafIndex(u32),
101
102    #[error("Unexpected Proposal Action type")]
103    UnknownProposalAction,
104}
105
106impl MlsError {
107    /// Box any storage-backend error into [`MlsError::MlsStorage`]. Used in
108    /// `.map_err(MlsError::storage)?` at call sites whose error type is the
109    /// storage's concrete `Error` (which can't be auto-converted via
110    /// blanket `From<E>` without colliding with existing impls).
111    pub fn storage<E: StdError + Send + Sync + 'static>(e: E) -> Self {
112        MlsError::MlsStorage(Box::new(e))
113    }
114}
115
116// ── Boxing From impls for storage-parameterised OpenMLS error types ──
117//
118// Each impl is generic over `E: StdError + Send + Sync + 'static` so any
119// storage backend's error type works.
120
121impl<E: StdError + Send + Sync + 'static> From<ProcessMessageError<E>> for MlsError {
122    fn from(e: ProcessMessageError<E>) -> Self {
123        MlsError::ProcessMessage(Box::new(e))
124    }
125}
126
127impl<E: StdError + Send + Sync + 'static> From<MergeCommitError<E>> for MlsError {
128    fn from(e: MergeCommitError<E>) -> Self {
129        MlsError::MergeCommit(Box::new(e))
130    }
131}
132
133impl<E: StdError + Send + Sync + 'static> From<MergePendingCommitError<E>> for MlsError {
134    fn from(e: MergePendingCommitError<E>) -> Self {
135        MlsError::MergePendingCommit(Box::new(e))
136    }
137}
138
139impl<E: StdError + Send + Sync + 'static> From<CommitToPendingProposalsError<E>> for MlsError {
140    fn from(e: CommitToPendingProposalsError<E>) -> Self {
141        MlsError::CommitToPendingProposals(Box::new(e))
142    }
143}
144
145impl<E: StdError + Send + Sync + 'static> From<ProposeAddMemberError<E>> for MlsError {
146    fn from(e: ProposeAddMemberError<E>) -> Self {
147        MlsError::ProposeAddMember(Box::new(e))
148    }
149}
150
151impl<E: StdError + Send + Sync + 'static> From<ProposeRemoveMemberError<E>> for MlsError {
152    fn from(e: ProposeRemoveMemberError<E>) -> Self {
153        MlsError::ProposeRemoveMember(Box::new(e))
154    }
155}
156
157impl<E: StdError + Send + Sync + 'static> From<NewGroupError<E>> for MlsError {
158    fn from(e: NewGroupError<E>) -> Self {
159        MlsError::NewGroup(Box::new(e))
160    }
161}
162
163impl<E: StdError + Send + Sync + 'static> From<WelcomeError<E>> for MlsError {
164    fn from(e: WelcomeError<E>) -> Self {
165        MlsError::Welcome(Box::new(e))
166    }
167}
168
169/// Recover a poisoned lock as [`MlsError::Lock`].
170impl<T> From<std::sync::PoisonError<T>> for MlsError {
171    fn from(e: std::sync::PoisonError<T>) -> Self {
172        MlsError::Lock(e.to_string())
173    }
174}