iroh_blobs/get/
error.rs

1//! Error returned from get operations
2
3use iroh::endpoint::{self, ClosedStream};
4
5use crate::util::progress::ProgressSendError;
6
7/// Failures for a get operation
8#[derive(Debug, thiserror::Error)]
9pub enum GetError {
10    /// Hash not found.
11    #[error("Hash not found")]
12    NotFound(#[source] anyhow::Error),
13    /// Remote has reset the connection.
14    #[error("Remote has reset the connection")]
15    RemoteReset(#[source] anyhow::Error),
16    /// Remote behaved in a non-compliant way.
17    #[error("Remote behaved in a non-compliant way")]
18    NoncompliantNode(#[source] anyhow::Error),
19
20    /// Network or IO operation failed.
21    #[error("A network or IO operation failed")]
22    Io(#[source] anyhow::Error),
23
24    /// Our download request is invalid.
25    #[error("Our download request is invalid")]
26    BadRequest(#[source] anyhow::Error),
27    /// Operation failed on the local node.
28    #[error("Operation failed on the local node")]
29    LocalFailure(#[source] anyhow::Error),
30}
31
32impl From<ProgressSendError> for GetError {
33    fn from(value: ProgressSendError) -> Self {
34        Self::LocalFailure(value.into())
35    }
36}
37
38impl From<endpoint::ConnectionError> for GetError {
39    fn from(value: endpoint::ConnectionError) -> Self {
40        // explicit match just to be sure we are taking everything into account
41        use endpoint::ConnectionError;
42        match value {
43            e @ ConnectionError::VersionMismatch => {
44                // > The peer doesn't implement any supported version
45                // unsupported version is likely a long time error, so this peer is not usable
46                GetError::NoncompliantNode(e.into())
47            }
48            e @ ConnectionError::TransportError(_) => {
49                // > The peer violated the QUIC specification as understood by this implementation
50                // bad peer we don't want to keep around
51                GetError::NoncompliantNode(e.into())
52            }
53            e @ ConnectionError::ConnectionClosed(_) => {
54                // > The peer's QUIC stack aborted the connection automatically
55                // peer might be disconnecting or otherwise unavailable, drop it
56                GetError::Io(e.into())
57            }
58            e @ ConnectionError::ApplicationClosed(_) => {
59                // > The peer closed the connection
60                // peer might be disconnecting or otherwise unavailable, drop it
61                GetError::Io(e.into())
62            }
63            e @ ConnectionError::Reset => {
64                // > The peer is unable to continue processing this connection, usually due to having restarted
65                GetError::RemoteReset(e.into())
66            }
67            e @ ConnectionError::TimedOut => {
68                // > Communication with the peer has lapsed for longer than the negotiated idle timeout
69                GetError::Io(e.into())
70            }
71            e @ ConnectionError::LocallyClosed => {
72                // > The local application closed the connection
73                // TODO(@divma): don't see how this is reachable but let's just not use the peer
74                GetError::Io(e.into())
75            }
76            e @ ConnectionError::CidsExhausted => {
77                // > The connection could not be created because not enough of the CID space
78                // > is available
79                GetError::Io(e.into())
80            }
81        }
82    }
83}
84
85impl From<endpoint::ReadError> for GetError {
86    fn from(value: endpoint::ReadError) -> Self {
87        use endpoint::ReadError;
88        match value {
89            e @ ReadError::Reset(_) => GetError::RemoteReset(e.into()),
90            ReadError::ConnectionLost(conn_error) => conn_error.into(),
91            ReadError::ClosedStream
92            | ReadError::IllegalOrderedRead
93            | ReadError::ZeroRttRejected => {
94                // all these errors indicate the peer is not usable at this moment
95                GetError::Io(value.into())
96            }
97        }
98    }
99}
100impl From<ClosedStream> for GetError {
101    fn from(value: ClosedStream) -> Self {
102        GetError::Io(value.into())
103    }
104}
105
106impl From<endpoint::WriteError> for GetError {
107    fn from(value: endpoint::WriteError) -> Self {
108        use endpoint::WriteError;
109        match value {
110            e @ WriteError::Stopped(_) => GetError::RemoteReset(e.into()),
111            WriteError::ConnectionLost(conn_error) => conn_error.into(),
112            WriteError::ClosedStream | WriteError::ZeroRttRejected => {
113                // all these errors indicate the peer is not usable at this moment
114                GetError::Io(value.into())
115            }
116        }
117    }
118}
119
120impl From<crate::get::fsm::ConnectedNextError> for GetError {
121    fn from(value: crate::get::fsm::ConnectedNextError) -> Self {
122        use crate::get::fsm::ConnectedNextError::*;
123        match value {
124            e @ PostcardSer(_) => {
125                // serialization errors indicate something wrong with the request itself
126                GetError::BadRequest(e.into())
127            }
128            e @ RequestTooBig => {
129                // request will never be sent, drop it
130                GetError::BadRequest(e.into())
131            }
132            Write(e) => e.into(),
133            Closed(e) => e.into(),
134            e @ Io(_) => {
135                // io errors are likely recoverable
136                GetError::Io(e.into())
137            }
138        }
139    }
140}
141
142impl From<crate::get::fsm::AtBlobHeaderNextError> for GetError {
143    fn from(value: crate::get::fsm::AtBlobHeaderNextError) -> Self {
144        use crate::get::fsm::AtBlobHeaderNextError::*;
145        match value {
146            e @ NotFound => {
147                // > This indicates that the provider does not have the requested data.
148                // peer might have the data later, simply retry it
149                GetError::NotFound(e.into())
150            }
151            Read(e) => e.into(),
152            e @ Io(_) => {
153                // io errors are likely recoverable
154                GetError::Io(e.into())
155            }
156        }
157    }
158}
159
160impl From<crate::get::fsm::DecodeError> for GetError {
161    fn from(value: crate::get::fsm::DecodeError) -> Self {
162        use crate::get::fsm::DecodeError::*;
163
164        match value {
165            e @ NotFound => GetError::NotFound(e.into()),
166            e @ ParentNotFound(_) => GetError::NotFound(e.into()),
167            e @ LeafNotFound(_) => GetError::NotFound(e.into()),
168            e @ ParentHashMismatch(_) => {
169                // TODO(@divma): did the peer sent wrong data? is it corrupted? did we sent a wrong
170                // request?
171                GetError::NoncompliantNode(e.into())
172            }
173            e @ LeafHashMismatch(_) => {
174                // TODO(@divma): did the peer sent wrong data? is it corrupted? did we sent a wrong
175                // request?
176                GetError::NoncompliantNode(e.into())
177            }
178            Read(e) => e.into(),
179            Io(e) => e.into(),
180        }
181    }
182}
183
184impl From<std::io::Error> for GetError {
185    fn from(value: std::io::Error) -> Self {
186        // generally consider io errors recoverable
187        // we might want to revisit this at some point
188        GetError::Io(value.into())
189    }
190}