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