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