Skip to main content

bitwarden_ipc/
error.rs

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