Skip to main content

whatsapp_rust/socket/
error.rs

1use thiserror::Error;
2use wacore::handshake::NoiseError;
3use wacore_binary::error::BinaryError;
4
5#[derive(Debug, Error)]
6#[non_exhaustive]
7pub enum SocketError {
8    #[error("socket is closed")]
9    SocketClosed,
10    #[error("I/O error")]
11    Io(#[from] std::io::Error),
12    #[error("noise cipher operation failed")]
13    Cipher(#[from] NoiseError),
14    #[error("binary protocol marshalling failed")]
15    Marshal(#[source] BinaryError),
16}
17
18pub type Result<T> = std::result::Result<T, SocketError>;
19
20/// Outcome of one frame's trip through the noise sender.
21pub type EncryptSendResult = std::result::Result<(), EncryptSendError>;
22
23#[derive(Debug, thiserror::Error)]
24#[non_exhaustive]
25pub enum EncryptSendErrorKind {
26    #[error("cryptography error")]
27    Crypto,
28    #[error("framing error")]
29    Framing,
30    #[error("transport error")]
31    Transport,
32    #[error("task join error")]
33    Join,
34    #[error("sender channel closed")]
35    ChannelClosed,
36    /// A previous frame failed at the transport, so this connection's write
37    /// keystream can no longer be extended safely. See
38    /// [`EncryptSendError::poisoned`].
39    #[error("sender poisoned by an earlier transport failure")]
40    Poisoned,
41}
42
43#[derive(Debug, thiserror::Error)]
44#[error("{kind}")]
45#[non_exhaustive]
46pub struct EncryptSendError {
47    pub kind: EncryptSendErrorKind,
48    #[source]
49    pub source: anyhow::Error,
50}
51
52impl EncryptSendError {
53    pub fn crypto(source: impl Into<anyhow::Error>) -> Self {
54        Self {
55            kind: EncryptSendErrorKind::Crypto,
56            source: source.into(),
57        }
58    }
59
60    pub fn framing(source: impl Into<anyhow::Error>) -> Self {
61        Self {
62            kind: EncryptSendErrorKind::Framing,
63            source: source.into(),
64        }
65    }
66
67    pub fn transport(source: impl Into<anyhow::Error>) -> Self {
68        Self {
69            kind: EncryptSendErrorKind::Transport,
70            source: source.into(),
71        }
72    }
73
74    pub fn join(source: impl Into<anyhow::Error>) -> Self {
75        Self {
76            kind: EncryptSendErrorKind::Join,
77            source: source.into(),
78        }
79    }
80
81    pub fn channel_closed() -> Self {
82        Self {
83            kind: EncryptSendErrorKind::ChannelClosed,
84            source: anyhow::anyhow!("sender task channel closed unexpectedly"),
85        }
86    }
87
88    /// A transport write failed earlier on this connection, so the peer's view
89    /// of the frame stream is unknown: it may have consumed the frame, seen a
90    /// truncated prefix, or nothing at all. Encrypting anything else under the
91    /// same write key would have to guess a counter, and guessing wrong reuses
92    /// an AES-GCM nonce (two ciphertexts under one key/nonce leak both
93    /// plaintexts). The only safe recovery is a new connection with fresh
94    /// handshake keys, so the sender refuses every later frame instead.
95    pub fn poisoned() -> Self {
96        Self {
97            kind: EncryptSendErrorKind::Poisoned,
98            source: anyhow::anyhow!(
99                "noise sender disabled after a transport failure; reconnect to rekey"
100            ),
101        }
102    }
103
104    /// The transport is gone (broken pipe, closed connection, channel dropped)
105    /// or was declared unusable by [`Self::poisoned`]. Callers treat all three
106    /// the same way: stop retrying on this connection and reconnect.
107    pub fn is_transport_unavailable(&self) -> bool {
108        matches!(
109            self.kind,
110            EncryptSendErrorKind::Transport
111                | EncryptSendErrorKind::ChannelClosed
112                | EncryptSendErrorKind::Poisoned
113        )
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use wacore::libsignal::crypto::CryptoProviderError;
121
122    #[test]
123    fn cipher_preserves_noise_source_through_socket_error() {
124        let noise = NoiseError::Decrypt(CryptoProviderError::AuthFailed);
125        let se: SocketError = noise.into();
126        // First hop: SocketError → NoiseError
127        let src = std::error::Error::source(&se).expect("source preserved");
128        let ne = src
129            .downcast_ref::<NoiseError>()
130            .expect("downcasts to NoiseError");
131        assert!(matches!(ne, NoiseError::Decrypt(_)));
132        // Second hop: NoiseError → CryptoProviderError
133        let inner = std::error::Error::source(ne).expect("inner source preserved");
134        let cpe = inner
135            .downcast_ref::<CryptoProviderError>()
136            .expect("downcasts to CryptoProviderError");
137        assert!(matches!(cpe, CryptoProviderError::AuthFailed));
138    }
139
140    #[test]
141    fn crypto_preserves_the_noise_error_type() {
142        let err = EncryptSendError::crypto(NoiseError::Encrypt(CryptoProviderError::BackendFailed));
143        assert!(matches!(err.kind, EncryptSendErrorKind::Crypto));
144        let src = std::error::Error::source(&err).expect("source preserved");
145        let ne = src
146            .downcast_ref::<NoiseError>()
147            .expect("downcasts to NoiseError");
148        assert!(matches!(ne, NoiseError::Encrypt(_)));
149    }
150
151    #[test]
152    fn crypto_from_an_untyped_source_still_carries_its_message() {
153        let err = EncryptSendError::crypto(anyhow::anyhow!("some opaque failure"));
154        let src = std::error::Error::source(&err).expect("source preserved");
155        assert!(src.downcast_ref::<NoiseError>().is_none());
156        assert_eq!(src.to_string(), "some opaque failure");
157    }
158
159    #[test]
160    fn marshal_preserves_binary_error_source() {
161        let be = BinaryError::InvalidNode;
162        let se = SocketError::Marshal(be);
163        let src = std::error::Error::source(&se).expect("source preserved");
164        let inner = src
165            .downcast_ref::<BinaryError>()
166            .expect("downcasts to BinaryError");
167        assert!(matches!(inner, BinaryError::InvalidNode));
168    }
169}