openmls/error.rs
1//! # OpenMLS Errors
2//!
3//! Each module has their own errors it is returning. This module will defines
4//! helper macros and functions to define OpenMLS errors.
5//!
6//! ## Error handling
7//!
8//! Most function calls in the library return a `Result` and can therefore surface errors to the library consumer.
9//! Errors can have different sources, depending on their nature. The following list explains the different error sources and how to handle them:
10//!
11//! ### Errors in dependencies
12//!
13//! The OpenMLS library relies on external dependencies for cryptographic primitives and storage of cryptographic key material. See the traits in the [User Manual] for more details on the dependencies.
14//! When an unexpected error occurs in one of those dependencies, it is usually surfaced as a `LibraryError` to the consumer.
15//!
16//! ### Errors induced by wrong API use
17//!
18//! Whenever the caller calls an OpenMLS function with invalid input, an error is returned. Examples of wrong input can be: Adding a member twice to a group, interacting with an inactive group, removing inexistent
19//! members from a group, etc. The precise error message depends on the function called, and the error will typically be an `enum` with explicit variants that state the reason for the error.
20//! Consumers can branch on the variants of the `enum` and take action accordingly.
21//!
22//! ### Errors induced by processing invalid payload
23//!
24//! The library processes external payload in the form of messages sent over a network, or state loaded from disk. In both cases, multi-layered checks need to be done to make sure the payload
25//! is syntactically and semantically correct. The syntax checks typically all happen at the serialization level and get detected early on. Semantic validation is more complex because data needs to be evaluated
26//! in context. You can find more details about validation in the validation chapter of the [User Manual].
27//! These errors are surfaced to the consumer at various stages of the processing, and the processing is aborted for the payload in question. Much like errors induced by wrong API usage, these errors are `enums` that
28//! contain explicit variants for every error type. Consumers can branch on these variants to take action according to the specific error.
29//!
30//! ### Correctness errors in the library itself
31//!
32//! While the library has good test coverage in the form of unit & integration tests, theoretical correctness errors cannot be completely excluded. Should such an error occur, consumers will get
33//! a `LibraryError` as a return value that contains backtraces indicating where in the code the error occurred and a short string for context. These details are important for debugging the library in such a case.
34//! Consumers should save this information.
35//!
36//! All errors derive [`thiserror::Error`](https://docs.rs/thiserror/latest/thiserror/) as well as
37//! [`Debug`](`std::fmt::Debug`), [`PartialEq`](`std::cmp::PartialEq`), and [`Clone`](`std::clone::Clone`).
38
39use openmls_traits::types::CryptoError;
40use std::fmt::Display;
41use thiserror::Error;
42use tls_codec::Error as TlsCodecError;
43
44/// Generic error type that indicates unrecoverable errors in the library.
45///
46/// This error has 3 subtypes:
47///
48/// **MissingBoundsCheck**
49///
50/// This error is returned when the library tries to serialize data that is too big for the
51/// MLS structs. In particular, when element lists contain more elements than the theoretical maximum
52/// defined in the spec, the serialization will fail. This should not happen when all input values are checked.
53///
54/// **CryptoError**
55///
56/// This error is returned if the underlying crypto provider encountered an unexpected error. Possible reasons
57/// for this could be: the implementation of the crypto provider is not correct, the key material is not correct,
58/// the crypto provider does not support all functions required. Another reason could be that the OpenMLS library
59/// does not use the crypto provider API correctly.
60///
61/// **Custom**
62///
63/// This error is returned in situations where the implementation would otherwise use an `unwrap()`.
64/// If applications receive this error, it clearly indicates an implementation mistake in OpenMLS. The error
65/// includes a string that can give some more context about where the error originated and helps debugging.
66///
67/// In all cases, when a `LibraryError` is returned, applications should try to recover gracefully from it.
68/// It is recommended to log the error for potential debugging.
69#[derive(Error, Debug, PartialEq, Eq, Clone)]
70pub struct LibraryError {
71 internal: InternalLibraryError,
72}
73
74impl LibraryError {
75 /// A custom error (typically to avoid an unwrap())
76 pub(crate) fn custom(s: &'static str) -> Self {
77 #[cfg(feature = "backtrace")]
78 let display_string = format!(
79 "Error description: {s}\n Backtrace:\n{:?}",
80 backtrace::Backtrace::new()
81 );
82 #[cfg(not(feature = "backtrace"))]
83 let display_string = format!("Error description: {s}");
84
85 Self {
86 internal: InternalLibraryError::Custom(display_string),
87 }
88 }
89
90 /// Used when encoding doesn't work because of missing bound checks
91 pub(crate) fn missing_bound_check(e: TlsCodecError) -> Self {
92 Self {
93 internal: InternalLibraryError::MissingBoundsCheck(e),
94 }
95 }
96
97 /// Used when the crypto provider returns an unexpected error
98 pub(crate) fn unexpected_crypto_error(e: CryptoError) -> Self {
99 Self {
100 internal: InternalLibraryError::CryptoError(e),
101 }
102 }
103}
104
105impl Display for LibraryError {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 write!(f, "{:?}", self.internal)
108 }
109}
110
111/// Internal enum to differentiate between the different types of library errors
112#[derive(Error, PartialEq, Eq, Clone)]
113enum InternalLibraryError {
114 /// See [`TlsCodecError`] for more details.
115 #[error(transparent)]
116 MissingBoundsCheck(#[from] TlsCodecError),
117 /// See [`CryptoError`] for more details.
118 #[error(transparent)]
119 CryptoError(#[from] CryptoError),
120 #[error("Custom library error: {0}")]
121 Custom(String),
122}
123
124impl std::fmt::Debug for InternalLibraryError {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 match self {
127 InternalLibraryError::MissingBoundsCheck(e) => f
128 .debug_struct("InternalLibraryError")
129 .field("MissingBoundsCheck", e)
130 .finish(),
131 InternalLibraryError::CryptoError(e) => f
132 .debug_struct("InternalLibraryError")
133 .field("CryptoError", e)
134 .finish(),
135 InternalLibraryError::Custom(s) => writeln!(f, "InternalLibraryError: {s}"),
136 }
137 }
138}
139
140/// A wrapper struct for an error string. This can be used when no complex error
141/// variant is needed.
142#[derive(Debug, Clone, PartialEq, Eq)]
143pub struct ErrorString(String);
144
145impl From<String> for ErrorString {
146 fn from(s: String) -> Self {
147 Self(s)
148 }
149}
150impl From<&str> for ErrorString {
151 fn from(s: &str) -> Self {
152 Self(s.to_string())
153 }
154}
155
156impl std::error::Error for ErrorString {}
157
158impl std::fmt::Display for ErrorString {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 f.write_fmt(format_args!("{}", self.0))
161 }
162}
163
164impl ErrorString {
165 pub(crate) fn _description(&self) -> String {
166 self.0.clone()
167 }
168}
169
170/*
171
172Note to maintainers
173
174The thiserror crate does not add any documentation to enum variants and this needs to be done manually.
175The best way to do this if you don't want to duplicate the string manually is to use the following regex:
176
177Add comments for naked variants:
178
179 ([,|{])\n(\s+)#\[error\("([a-z0-9 .,-_'^:]+)"\)\]
180
181 $1
182 $2/// $3
183 $2#[error("$3")]
184
185 Add comments for nested variants:
186
187 ([,|{])\n([\s]+)#\[error\(transparent\)\]\n[\s]+(([A-Z][a-z0-9]+)+)\(#\[from\] (([A-Z][a-z0-9]+)+)\)
188
189 $1
190 $2/// See [`$5`] for more details.
191 $2#[error(transparent)]
192 $2$3(#[from] $5)
193
194 The above was tested in VSCode, but should be easily adaptable to other tools.
195
196*/