Skip to main content

bitwarden_ipc/
error.rs

1use bitwarden_error::bitwarden_error;
2use serde::{Deserialize, Serialize};
3use thiserror::Error;
4
5use crate::{endpoint::Endpoint, rpc::error::RpcError};
6
7/// Classification of an IPC error, returned by [`IpcErrorKind::kind`].
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ErrorKind {
10    /// The client can no longer make progress, so the shared processing loop should stop.
11    Fatal,
12    /// The destination could not be reached (e.g. the peer transport is not connected);
13    /// The client should continue to process messages. A peer may become reachable later.
14    Unreachable,
15    /// Any other, recoverable failure: only the current operation failed, so the client should stay
16    /// running and continue processing other messages.
17    Other,
18}
19
20/// Classifies an IPC error into an [`ErrorKind`].
21///
22/// The IPC client runs a single long-lived processing loop that is shared across every peer and
23/// every message. Historically *any* transport or crypto error tore that loop down, which meant a
24/// single transient failure (a handshake timeout, a peer disconnecting mid-send, a malformed
25/// frame) permanently disabled the shared client and it never recovered.
26///
27/// This trait lets each layer classify its own errors so the client can distinguish the cases:
28/// - [`ErrorKind::Fatal`]: the client can no longer make progress, so the processing loop should
29///   stop.
30/// - [`ErrorKind::Unreachable`] and [`ErrorKind::Other`]: only the current operation failed, so the
31///   client should stay running and continue processing other messages.
32///
33/// Implementations should classify errors at construction, where the most context is available,
34/// and default ambiguous cases to [`ErrorKind::Other`]. Failing open keeps the shared client alive,
35/// which is almost always the safer choice.
36pub trait IpcErrorKind {
37    /// Classifies the error so the IPC client can decide whether to stop the processing loop or
38    /// keep running.
39    fn kind(&self) -> ErrorKind;
40}
41
42impl IpcErrorKind for std::convert::Infallible {
43    fn kind(&self) -> ErrorKind {
44        // `Infallible` can never be constructed, so this is unreachable.
45        match *self {}
46    }
47}
48
49#[cfg(any(test, feature = "test-support"))]
50impl IpcErrorKind for () {
51    fn kind(&self) -> ErrorKind {
52        ErrorKind::Other
53    }
54}
55
56/// Error returned by [`IpcClient::start`](crate::IpcClient::start). Indicates that the IPC client
57/// is already running.
58#[derive(Debug, Error, Clone, PartialEq, Eq)]
59#[error("IPC client is already running")]
60#[bitwarden_error(basic)]
61pub struct AlreadyRunningError;
62
63/// Error returned by [`IpcClient::send`](crate::IpcClient::send).
64#[derive(Debug, Error, Clone, PartialEq, Eq, Serialize, Deserialize)]
65#[bitwarden_ffi::wasm_record]
66pub enum SendError {
67    /// The destination could not be reached (e.g. the peer transport is not connected).
68    #[error("Destination unreachable")]
69    Unreachable,
70    /// Any other send failure, carrying the underlying error's debug representation.
71    #[error("{0}")]
72    Other(String),
73}
74
75/// Error returned by [`IpcClient::broadcast`](crate::IpcClient::broadcast) when the group could
76/// not be turned into a set of endpoints. Nothing has been sent when this is returned; failures to
77/// deliver to an individual endpoint are reported per endpoint instead.
78#[derive(Debug, Error, Clone, PartialEq, Eq)]
79#[bitwarden_error(flat)]
80pub enum BroadcastError {
81    /// The platform cannot resolve this group.
82    #[error("The communication backend does not support broadcasting to this group")]
83    Unsupported,
84    /// The platform asked to delegate delivery to another endpoint, which is not supported.
85    #[error("Delegating delivery to another endpoint is not supported")]
86    DelegationUnsupported,
87    /// The platform resolved the group to an endpoint outside of it.
88    #[error("The communication backend resolved the group to an endpoint outside of it: {0:?}")]
89    OutOfScope(Endpoint),
90    /// The payload could not be serialized.
91    #[error("Failed to serialize payload: {0}")]
92    Serialization(String),
93}
94
95#[derive(Debug, Error, Clone, PartialEq, Eq)]
96#[bitwarden_error(flat)]
97#[allow(missing_docs)]
98pub enum SubscribeError {
99    #[error("The IPC processing thread is not running")]
100    NotStarted,
101}
102
103#[derive(Debug, Error, PartialEq, Eq)]
104#[bitwarden_error(flat)]
105#[allow(missing_docs)]
106pub enum ReceiveError {
107    #[error("Failed to subscribe to the IPC channel: {0}")]
108    Channel(#[from] tokio::sync::broadcast::error::RecvError),
109
110    #[error("Timed out while waiting for a message: {0}")]
111    Timeout(#[from] bitwarden_threading::time::ElapsedError),
112
113    #[error("Cancelled while waiting for a message")]
114    Cancelled,
115}
116
117#[derive(Debug, Error, PartialEq, Eq)]
118#[bitwarden_error(flat)]
119#[allow(missing_docs)]
120pub enum TypedReceiveError {
121    #[error("Failed to subscribe to the IPC channel: {0}")]
122    Channel(#[from] tokio::sync::broadcast::error::RecvError),
123
124    #[error("Timed out while waiting for a message: {0}")]
125    Timeout(#[from] bitwarden_threading::time::ElapsedError),
126
127    #[error("Cancelled while waiting for a message")]
128    Cancelled,
129
130    #[error("Typing error: {0}")]
131    Typing(String),
132}
133
134impl From<ReceiveError> for TypedReceiveError {
135    fn from(value: ReceiveError) -> Self {
136        match value {
137            ReceiveError::Channel(e) => TypedReceiveError::Channel(e),
138            ReceiveError::Timeout(e) => TypedReceiveError::Timeout(e),
139            ReceiveError::Cancelled => TypedReceiveError::Cancelled,
140        }
141    }
142}
143
144#[derive(Debug, Error, PartialEq, Eq)]
145#[bitwarden_error(flat)]
146#[allow(missing_docs)]
147pub enum RequestError {
148    #[error(transparent)]
149    Subscribe(#[from] SubscribeError),
150
151    #[error(transparent)]
152    Receive(#[from] TypedReceiveError),
153
154    #[error("Timed out while waiting for a message: {0}")]
155    Timeout(#[from] bitwarden_threading::time::ElapsedError),
156
157    #[error("Failed to send message: {0}")]
158    Send(String),
159
160    #[error("Destination unreachable")]
161    Unreachable,
162
163    #[error("Error occurred on the remote target: {0}")]
164    Rpc(#[from] RpcError),
165}
166
167impl From<SendError> for RequestError {
168    fn from(error: SendError) -> Self {
169        match error {
170            SendError::Unreachable => RequestError::Unreachable,
171            SendError::Other(message) => RequestError::Send(message),
172        }
173    }
174}