litep2p/
error.rs

1// Copyright 2019 Parity Technologies (UK) Ltd.
2// Copyright 2023 litep2p developers
3//
4// Permission is hereby granted, free of charge, to any person obtaining a
5// copy of this software and associated documentation files (the "Software"),
6// to deal in the Software without restriction, including without limitation
7// the rights to use, copy, modify, merge, publish, distribute, sublicense,
8// and/or sell copies of the Software, and to permit persons to whom the
9// Software is furnished to do so, subject to the following conditions:
10//
11// The above copyright notice and this permission notice shall be included in
12// all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20// DEALINGS IN THE SOFTWARE.
21
22#![allow(clippy::enum_variant_names)]
23
24//! [`Litep2p`](`crate::Litep2p`) error types.
25
26use crate::{
27    protocol::Direction,
28    transport::manager::limits::ConnectionLimitsError,
29    types::{protocol::ProtocolName, ConnectionId, SubstreamId},
30    PeerId,
31};
32
33type Multihash = multihash::Multihash<64>;
34
35use multiaddr::Multiaddr;
36use std::io::{self, ErrorKind};
37
38// TODO: https://github.com/paritytech/litep2p/issues/204 clean up the overarching error.
39// Please note that this error is not propagated directly to the user.
40#[allow(clippy::large_enum_variant)]
41#[derive(Debug, thiserror::Error)]
42pub enum Error {
43    #[error("Peer `{0}` does not exist")]
44    PeerDoesntExist(PeerId),
45    #[error("Peer `{0}` already exists")]
46    PeerAlreadyExists(PeerId),
47    #[error("Protocol `{0}` not supported")]
48    ProtocolNotSupported(String),
49    #[error("Address error: `{0}`")]
50    AddressError(#[from] AddressError),
51    #[error("Parse error: `{0}`")]
52    ParseError(ParseError),
53    #[error("I/O error: `{0}`")]
54    IoError(ErrorKind),
55    #[error("Negotiation error: `{0}`")]
56    NegotiationError(#[from] NegotiationError),
57    #[error("Substream error: `{0}`")]
58    SubstreamError(#[from] SubstreamError),
59    #[error("Substream error: `{0}`")]
60    NotificationError(NotificationError),
61    #[error("Essential task closed")]
62    EssentialTaskClosed,
63    #[error("Unknown error occurred")]
64    Unknown,
65    #[error("Cannot dial self: `{0}`")]
66    CannotDialSelf(Multiaddr),
67    #[error("Transport not supported")]
68    TransportNotSupported(Multiaddr),
69    #[error("Yamux error for substream `{0:?}`: `{1}`")]
70    YamuxError(Direction, crate::yamux::ConnectionError),
71    #[error("Operation not supported: `{0}`")]
72    NotSupported(String),
73    #[error("Other error occurred: `{0}`")]
74    Other(String),
75    #[error("Protocol already exists: `{0:?}`")]
76    ProtocolAlreadyExists(ProtocolName),
77    #[error("Operation timed out")]
78    Timeout,
79    #[error("Invalid state transition")]
80    InvalidState,
81    #[error("DNS address resolution failed")]
82    DnsAddressResolutionFailed,
83    #[error("Transport error: `{0}`")]
84    TransportError(String),
85    #[cfg(feature = "quic")]
86    #[error("Failed to generate certificate: `{0}`")]
87    CertificateGeneration(#[from] crate::crypto::tls::certificate::GenError),
88    #[error("Invalid data")]
89    InvalidData,
90    #[error("Input rejected")]
91    InputRejected,
92    #[cfg(feature = "websocket")]
93    #[error("WebSocket error: `{0}`")]
94    WebSocket(#[from] tokio_tungstenite::tungstenite::error::Error),
95    #[error("Insufficient peers")]
96    InsufficientPeers,
97    #[error("Substream doens't exist")]
98    SubstreamDoesntExist,
99    #[cfg(feature = "webrtc")]
100    #[error("`str0m` error: `{0}`")]
101    WebRtc(#[from] str0m::RtcError),
102    #[error("Remote peer disconnected")]
103    Disconnected,
104    #[error("Channel does not exist")]
105    ChannelDoesntExist,
106    #[error("Tried to dial self")]
107    TriedToDialSelf,
108    #[error("Litep2p is already connected to the peer")]
109    AlreadyConnected,
110    #[error("No addres available for `{0}`")]
111    NoAddressAvailable(PeerId),
112    #[error("Connection closed")]
113    ConnectionClosed,
114    #[cfg(feature = "quic")]
115    #[error("Quinn error: `{0}`")]
116    Quinn(quinn::ConnectionError),
117    #[error("Invalid certificate")]
118    InvalidCertificate,
119    #[error("Peer ID mismatch: expected `{0}`, got `{1}`")]
120    PeerIdMismatch(PeerId, PeerId),
121    #[error("Channel is clogged")]
122    ChannelClogged,
123    #[error("Connection doesn't exist: `{0:?}`")]
124    ConnectionDoesntExist(ConnectionId),
125    #[error("Exceeded connection limits `{0:?}`")]
126    ConnectionLimit(ConnectionLimitsError),
127    #[error("Failed to dial peer immediately")]
128    ImmediateDialError(#[from] ImmediateDialError),
129    #[error("Cannot read system DNS config: `{0}`")]
130    CannotReadSystemDnsConfig(hickory_resolver::ResolveError),
131}
132
133/// Error type for address parsing.
134#[derive(Debug, thiserror::Error)]
135pub enum AddressError {
136    /// The provided address does not correspond to the transport protocol.
137    ///
138    /// For example, this can happen when the address used the UDP protocol but
139    /// the handling transport only allows TCP connections.
140    #[error("Invalid address for protocol")]
141    InvalidProtocol,
142    /// The provided address is not a valid URL.
143    #[error("Invalid URL")]
144    InvalidUrl,
145    /// The provided address does not include a peer ID.
146    #[error("`PeerId` missing from the address")]
147    PeerIdMissing,
148    /// No address is available for the provided peer ID.
149    #[error("Address not available")]
150    AddressNotAvailable,
151    /// The provided address contains an invalid multihash.
152    #[error("Multihash does not contain a valid peer ID : `{0:?}`")]
153    InvalidPeerId(Multihash),
154}
155
156#[derive(Debug, thiserror::Error, PartialEq)]
157pub enum ParseError {
158    /// The provided probuf message cannot be decoded.
159    #[error("Failed to decode protobuf message: `{0:?}`")]
160    ProstDecodeError(#[from] prost::DecodeError),
161    /// The provided protobuf message cannot be encoded.
162    #[error("Failed to encode protobuf message: `{0:?}`")]
163    ProstEncodeError(#[from] prost::EncodeError),
164    /// The protobuf message contains an unexpected key type.
165    ///
166    /// This error can happen when:
167    ///  - The provided key type is not recognized.
168    ///  - The provided key type is recognized but not supported.
169    #[error("Unknown key type from protobuf message: `{0}`")]
170    UnknownKeyType(i32),
171    /// The public key bytes are invalid and cannot be parsed.
172    ///
173    /// This error can happen when:
174    ///  - The received number of bytes is not equal to the expected number of bytes (32 bytes).
175    ///  - The bytes are not a valid Ed25519 public key.
176    ///  - Length of the public key is not represented by 2 bytes (WebRTC specific).
177    #[error("Invalid public key")]
178    InvalidPublicKey,
179    /// The provided date has an invalid format.
180    ///
181    /// This error is protocol specific.
182    #[error("Invalid data")]
183    InvalidData,
184    /// The provided reply length is not valid
185    #[error("Invalid reply length")]
186    InvalidReplyLength,
187}
188
189#[derive(Debug, thiserror::Error)]
190pub enum SubstreamError {
191    // Note: this can mean as well `SubstreamClosed`.
192    #[error("Connection closed")]
193    ConnectionClosed,
194    #[error("Connection channel clogged")]
195    ChannelClogged,
196    #[error("Connection to peer does not exist: `{0}`")]
197    PeerDoesNotExist(PeerId),
198    #[error("I/O error: `{0}`")]
199    IoError(ErrorKind),
200    #[error("yamux error: `{0}`")]
201    YamuxError(crate::yamux::ConnectionError, Direction),
202    #[error("Failed to read from substream, substream id `{0:?}`")]
203    ReadFailure(Option<SubstreamId>),
204    #[error("Failed to write to substream, substream id `{0:?}`")]
205    WriteFailure(Option<SubstreamId>),
206    #[error("Negotiation error: `{0:?}`")]
207    NegotiationError(#[from] NegotiationError),
208}
209
210// Libp2p yamux does not implement PartialEq for ConnectionError.
211impl PartialEq for SubstreamError {
212    fn eq(&self, other: &Self) -> bool {
213        match (self, other) {
214            (Self::ConnectionClosed, Self::ConnectionClosed) => true,
215            (Self::ChannelClogged, Self::ChannelClogged) => true,
216            (Self::PeerDoesNotExist(lhs), Self::PeerDoesNotExist(rhs)) => lhs == rhs,
217            (Self::IoError(lhs), Self::IoError(rhs)) => lhs == rhs,
218            (Self::YamuxError(lhs, lhs_1), Self::YamuxError(rhs, rhs_1)) => {
219                if lhs_1 != rhs_1 {
220                    return false;
221                }
222
223                match (lhs, rhs) {
224                    (
225                        crate::yamux::ConnectionError::Io(lhs),
226                        crate::yamux::ConnectionError::Io(rhs),
227                    ) => lhs.kind() == rhs.kind(),
228                    (
229                        crate::yamux::ConnectionError::Decode(lhs),
230                        crate::yamux::ConnectionError::Decode(rhs),
231                    ) => match (lhs, rhs) {
232                        (
233                            crate::yamux::FrameDecodeError::Io(lhs),
234                            crate::yamux::FrameDecodeError::Io(rhs),
235                        ) => lhs.kind() == rhs.kind(),
236                        (
237                            crate::yamux::FrameDecodeError::FrameTooLarge(lhs),
238                            crate::yamux::FrameDecodeError::FrameTooLarge(rhs),
239                        ) => lhs == rhs,
240                        (
241                            crate::yamux::FrameDecodeError::Header(lhs),
242                            crate::yamux::FrameDecodeError::Header(rhs),
243                        ) => match (lhs, rhs) {
244                            (
245                                crate::yamux::HeaderDecodeError::Version(lhs),
246                                crate::yamux::HeaderDecodeError::Version(rhs),
247                            ) => lhs == rhs,
248                            (
249                                crate::yamux::HeaderDecodeError::Type(lhs),
250                                crate::yamux::HeaderDecodeError::Type(rhs),
251                            ) => lhs == rhs,
252                            _ => false,
253                        },
254                        _ => false,
255                    },
256                    (
257                        crate::yamux::ConnectionError::NoMoreStreamIds,
258                        crate::yamux::ConnectionError::NoMoreStreamIds,
259                    ) => true,
260                    (
261                        crate::yamux::ConnectionError::Closed,
262                        crate::yamux::ConnectionError::Closed,
263                    ) => true,
264                    (
265                        crate::yamux::ConnectionError::TooManyStreams,
266                        crate::yamux::ConnectionError::TooManyStreams,
267                    ) => true,
268                    _ => false,
269                }
270            }
271
272            (Self::ReadFailure(lhs), Self::ReadFailure(rhs)) => lhs == rhs,
273            (Self::WriteFailure(lhs), Self::WriteFailure(rhs)) => lhs == rhs,
274            (Self::NegotiationError(lhs), Self::NegotiationError(rhs)) => lhs == rhs,
275            _ => false,
276        }
277    }
278}
279
280/// Error during the negotiation phase.
281#[derive(Debug, thiserror::Error)]
282pub enum NegotiationError {
283    /// Error occurred during the multistream-select phase of the negotiation.
284    #[error("multistream-select error: `{0:?}`")]
285    MultistreamSelectError(#[from] crate::multistream_select::NegotiationError),
286    /// Error occurred during the Noise handshake negotiation.
287    #[error("multistream-select error: `{0:?}`")]
288    SnowError(#[from] snow::Error),
289    /// The peer ID was not provided by the noise handshake.
290    #[error("`PeerId` missing from Noise handshake")]
291    PeerIdMissing,
292    /// The remote peer ID is not the same as the one expected.
293    #[error("The signature of the remote identity's public key does not verify")]
294    BadSignature,
295    /// The negotiation operation timed out.
296    #[error("Operation timed out")]
297    Timeout,
298    /// The message provided over the wire has an invalid format or is unsupported.
299    #[error("Parse error: `{0}`")]
300    ParseError(#[from] ParseError),
301    /// An I/O error occurred during the negotiation process.
302    #[error("I/O error: `{0}`")]
303    IoError(ErrorKind),
304    /// Expected a different state during the negotiation process.
305    #[error("Expected a different state")]
306    StateMismatch,
307    /// The noise handshake provided a different peer ID than the one expected in the dialing
308    /// address.
309    #[error("Peer ID mismatch: expected `{0}`, got `{1}`")]
310    PeerIdMismatch(PeerId, PeerId),
311    /// Error specific to the QUIC transport.
312    #[cfg(feature = "quic")]
313    #[error("QUIC error: `{0}`")]
314    Quic(#[from] QuicError),
315    /// Error specific to the WebSocket transport.
316    #[cfg(feature = "websocket")]
317    #[error("WebSocket error: `{0}`")]
318    WebSocket(#[from] tokio_tungstenite::tungstenite::error::Error),
319}
320
321impl PartialEq for NegotiationError {
322    fn eq(&self, other: &Self) -> bool {
323        match (self, other) {
324            (Self::MultistreamSelectError(lhs), Self::MultistreamSelectError(rhs)) => lhs == rhs,
325            (Self::SnowError(lhs), Self::SnowError(rhs)) => lhs == rhs,
326            (Self::ParseError(lhs), Self::ParseError(rhs)) => lhs == rhs,
327            (Self::IoError(lhs), Self::IoError(rhs)) => lhs == rhs,
328            (Self::PeerIdMismatch(lhs, lhs_1), Self::PeerIdMismatch(rhs, rhs_1)) =>
329                lhs == rhs && lhs_1 == rhs_1,
330            #[cfg(feature = "quic")]
331            (Self::Quic(lhs), Self::Quic(rhs)) => lhs == rhs,
332            #[cfg(feature = "websocket")]
333            (Self::WebSocket(lhs), Self::WebSocket(rhs)) =>
334                core::mem::discriminant(lhs) == core::mem::discriminant(rhs),
335            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
336        }
337    }
338}
339
340#[derive(Debug, thiserror::Error)]
341pub enum NotificationError {
342    #[error("Peer already exists")]
343    PeerAlreadyExists,
344    #[error("Peer is in invalid state")]
345    InvalidState,
346    #[error("Notifications clogged")]
347    NotificationsClogged,
348    #[error("Notification stream closed")]
349    NotificationStreamClosed(PeerId),
350}
351
352/// The error type for dialing a peer.
353///
354/// This error is reported via the litep2p events after performing
355/// a network dialing operation.
356#[derive(Debug, thiserror::Error)]
357pub enum DialError {
358    /// The dialing operation timed out.
359    ///
360    /// This error indicates that the `connection_open_timeout` from the protocol configuration
361    /// was exceeded.
362    #[error("Dial timed out")]
363    Timeout,
364    /// The provided address for dialing is invalid.
365    #[error("Address error: `{0}`")]
366    AddressError(#[from] AddressError),
367    /// An error occurred during DNS lookup operation.
368    ///
369    /// The address provided may be valid, however it failed to resolve to a concrete IP address.
370    /// This error may be recoverable.
371    #[error("DNS lookup error for `{0}`")]
372    DnsError(#[from] DnsError),
373    /// An error occurred during the negotiation process.
374    #[error("Negotiation error: `{0}`")]
375    NegotiationError(#[from] NegotiationError),
376}
377
378/// Dialing resulted in an immediate error before performing any network operations.
379#[derive(Debug, thiserror::Error, Copy, Clone, Eq, PartialEq)]
380pub enum ImmediateDialError {
381    /// The provided address does not include a peer ID.
382    #[error("`PeerId` missing from the address")]
383    PeerIdMissing,
384    /// The peer ID provided in the address is the same as the local peer ID.
385    #[error("Tried to dial self")]
386    TriedToDialSelf,
387    /// Cannot dial an already connected peer.
388    #[error("Already connected to peer")]
389    AlreadyConnected,
390    /// Cannot dial a peer that does not have any address available.
391    #[error("No address available for peer")]
392    NoAddressAvailable,
393    /// The essential task was closed.
394    #[error("TaskClosed")]
395    TaskClosed,
396    /// The channel is clogged.
397    #[error("Connection channel clogged")]
398    ChannelClogged,
399}
400
401/// Error during the QUIC transport negotiation.
402#[cfg(feature = "quic")]
403#[derive(Debug, thiserror::Error, PartialEq)]
404pub enum QuicError {
405    /// The provided certificate is invalid.
406    #[error("Invalid certificate")]
407    InvalidCertificate,
408    /// The connection was lost.
409    #[error("Failed to negotiate QUIC: `{0}`")]
410    ConnectionError(#[from] quinn::ConnectionError),
411    /// The connection could not be established.
412    #[error("Failed to connect to peer: `{0}`")]
413    ConnectError(#[from] quinn::ConnectError),
414}
415
416/// Error during DNS resolution.
417#[derive(Debug, thiserror::Error, PartialEq)]
418pub enum DnsError {
419    /// The DNS resolution failed to resolve the provided URL.
420    #[error("DNS failed to resolve url `{0}`")]
421    ResolveError(String),
422    /// The DNS expected a different IP address version.
423    ///
424    /// For example, DNSv4 was expected but DNSv6 was provided.
425    #[error("DNS type is different from the provided IP address")]
426    IpVersionMismatch,
427}
428
429impl From<Multihash> for Error {
430    fn from(hash: Multihash) -> Self {
431        Error::AddressError(AddressError::InvalidPeerId(hash))
432    }
433}
434
435impl From<io::Error> for Error {
436    fn from(error: io::Error) -> Error {
437        Error::IoError(error.kind())
438    }
439}
440
441impl From<io::Error> for SubstreamError {
442    fn from(error: io::Error) -> SubstreamError {
443        SubstreamError::IoError(error.kind())
444    }
445}
446
447impl From<io::Error> for DialError {
448    fn from(error: io::Error) -> Self {
449        DialError::NegotiationError(NegotiationError::IoError(error.kind()))
450    }
451}
452
453impl From<crate::multistream_select::NegotiationError> for Error {
454    fn from(error: crate::multistream_select::NegotiationError) -> Error {
455        Error::NegotiationError(NegotiationError::MultistreamSelectError(error))
456    }
457}
458
459impl From<snow::Error> for Error {
460    fn from(error: snow::Error) -> Self {
461        Error::NegotiationError(NegotiationError::SnowError(error))
462    }
463}
464
465impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error {
466    fn from(_: tokio::sync::mpsc::error::SendError<T>) -> Self {
467        Error::EssentialTaskClosed
468    }
469}
470
471impl From<tokio::sync::oneshot::error::RecvError> for Error {
472    fn from(_: tokio::sync::oneshot::error::RecvError) -> Self {
473        Error::EssentialTaskClosed
474    }
475}
476
477impl From<prost::DecodeError> for Error {
478    fn from(error: prost::DecodeError) -> Self {
479        Error::ParseError(ParseError::ProstDecodeError(error))
480    }
481}
482
483impl From<prost::EncodeError> for Error {
484    fn from(error: prost::EncodeError) -> Self {
485        Error::ParseError(ParseError::ProstEncodeError(error))
486    }
487}
488
489impl From<io::Error> for NegotiationError {
490    fn from(error: io::Error) -> Self {
491        NegotiationError::IoError(error.kind())
492    }
493}
494
495impl From<ParseError> for Error {
496    fn from(error: ParseError) -> Self {
497        Error::ParseError(error)
498    }
499}
500
501impl From<Multihash> for AddressError {
502    fn from(hash: Multihash) -> Self {
503        AddressError::InvalidPeerId(hash)
504    }
505}
506
507#[cfg(feature = "quic")]
508impl From<quinn::ConnectionError> for Error {
509    fn from(error: quinn::ConnectionError) -> Self {
510        match error {
511            quinn::ConnectionError::TimedOut => Error::Timeout,
512            error => Error::Quinn(error),
513        }
514    }
515}
516
517#[cfg(feature = "quic")]
518impl From<quinn::ConnectionError> for DialError {
519    fn from(error: quinn::ConnectionError) -> Self {
520        match error {
521            quinn::ConnectionError::TimedOut => DialError::Timeout,
522            error => DialError::NegotiationError(NegotiationError::Quic(error.into())),
523        }
524    }
525}
526
527#[cfg(feature = "quic")]
528impl From<quinn::ConnectError> for DialError {
529    fn from(error: quinn::ConnectError) -> Self {
530        DialError::NegotiationError(NegotiationError::Quic(error.into()))
531    }
532}
533
534impl From<ConnectionLimitsError> for Error {
535    fn from(error: ConnectionLimitsError) -> Self {
536        Error::ConnectionLimit(error)
537    }
538}
539
540#[cfg(test)]
541mod tests {
542    use super::*;
543    use tokio::sync::mpsc::{channel, Sender};
544
545    #[tokio::test]
546    async fn try_from_errors() {
547        let (tx, rx) = channel(1);
548        drop(rx);
549
550        async fn test(tx: Sender<()>) -> crate::Result<()> {
551            tx.send(()).await.map_err(From::from)
552        }
553
554        match test(tx).await.unwrap_err() {
555            Error::EssentialTaskClosed => {}
556            _ => panic!("invalid error"),
557        }
558    }
559}