iroh_blobs/get/
error.rs

1//! Error returned from get operations
2use std::io;
3
4use iroh::endpoint::{self, ClosedStream};
5use n0_snafu::SpanTrace;
6use nested_enum_utils::common_fields;
7use quinn::{ConnectionError, ReadError, WriteError};
8use snafu::{Backtrace, IntoError, Snafu};
9
10use crate::{
11    api::ExportBaoError,
12    get::fsm::{AtBlobHeaderNextError, ConnectedNextError, DecodeError},
13};
14
15#[derive(Debug, Snafu)]
16pub enum NotFoundCases {
17    #[snafu(transparent)]
18    AtBlobHeaderNext { source: AtBlobHeaderNextError },
19    #[snafu(transparent)]
20    Decode { source: DecodeError },
21}
22
23#[derive(Debug, Snafu)]
24pub enum NoncompliantNodeCases {
25    #[snafu(transparent)]
26    Connection { source: ConnectionError },
27    #[snafu(transparent)]
28    Decode { source: DecodeError },
29}
30
31#[derive(Debug, Snafu)]
32pub enum RemoteResetCases {
33    #[snafu(transparent)]
34    Read { source: ReadError },
35    #[snafu(transparent)]
36    Write { source: WriteError },
37    #[snafu(transparent)]
38    Connection { source: ConnectionError },
39}
40
41#[derive(Debug, Snafu)]
42pub enum BadRequestCases {
43    #[snafu(transparent)]
44    Anyhow { source: anyhow::Error },
45    #[snafu(transparent)]
46    Postcard { source: postcard::Error },
47    #[snafu(transparent)]
48    ConnectedNext { source: ConnectedNextError },
49}
50
51#[derive(Debug, Snafu)]
52pub enum LocalFailureCases {
53    #[snafu(transparent)]
54    Io {
55        source: io::Error,
56    },
57    #[snafu(transparent)]
58    Anyhow {
59        source: anyhow::Error,
60    },
61    #[snafu(transparent)]
62    IrpcSend {
63        source: irpc::channel::SendError,
64    },
65    #[snafu(transparent)]
66    Irpc {
67        source: irpc::Error,
68    },
69    #[snafu(transparent)]
70    ExportBao {
71        source: ExportBaoError,
72    },
73    TokioSend {},
74}
75
76impl<T> From<tokio::sync::mpsc::error::SendError<T>> for LocalFailureCases {
77    fn from(_: tokio::sync::mpsc::error::SendError<T>) -> Self {
78        LocalFailureCases::TokioSend {}
79    }
80}
81
82#[derive(Debug, Snafu)]
83pub enum IoCases {
84    #[snafu(transparent)]
85    Io { source: io::Error },
86    #[snafu(transparent)]
87    ConnectionError { source: endpoint::ConnectionError },
88    #[snafu(transparent)]
89    ReadError { source: endpoint::ReadError },
90    #[snafu(transparent)]
91    WriteError { source: endpoint::WriteError },
92    #[snafu(transparent)]
93    ClosedStream { source: endpoint::ClosedStream },
94    #[snafu(transparent)]
95    ConnectedNextError { source: ConnectedNextError },
96    #[snafu(transparent)]
97    AtBlobHeaderNextError { source: AtBlobHeaderNextError },
98}
99
100/// Failures for a get operation
101#[common_fields({
102    backtrace: Option<Backtrace>,
103    #[snafu(implicit)]
104    span_trace: SpanTrace,
105})]
106#[derive(Debug, Snafu)]
107#[snafu(visibility(pub(crate)))]
108pub enum GetError {
109    /// Hash not found, or a requested chunk for the hash not found.
110    #[snafu(display("Data for hash not found"))]
111    NotFound {
112        #[snafu(source(from(NotFoundCases, Box::new)))]
113        source: Box<NotFoundCases>,
114    },
115    /// Remote has reset the connection.
116    #[snafu(display("Remote has reset the connection"))]
117    RemoteReset {
118        #[snafu(source(from(RemoteResetCases, Box::new)))]
119        source: Box<RemoteResetCases>,
120    },
121    /// Remote behaved in a non-compliant way.
122    #[snafu(display("Remote behaved in a non-compliant way"))]
123    NoncompliantNode {
124        #[snafu(source(from(NoncompliantNodeCases, Box::new)))]
125        source: Box<NoncompliantNodeCases>,
126    },
127
128    /// Network or IO operation failed.
129    #[snafu(display("A network or IO operation failed"))]
130    Io {
131        #[snafu(source(from(IoCases, Box::new)))]
132        source: Box<IoCases>,
133    },
134    /// Our download request is invalid.
135    #[snafu(display("Our download request is invalid"))]
136    BadRequest {
137        #[snafu(source(from(BadRequestCases, Box::new)))]
138        source: Box<BadRequestCases>,
139    },
140    /// Operation failed on the local node.
141    #[snafu(display("Operation failed on the local node"))]
142    LocalFailure {
143        #[snafu(source(from(LocalFailureCases, Box::new)))]
144        source: Box<LocalFailureCases>,
145    },
146}
147
148pub type GetResult<T> = std::result::Result<T, GetError>;
149
150impl From<irpc::channel::SendError> for GetError {
151    fn from(value: irpc::channel::SendError) -> Self {
152        LocalFailureSnafu.into_error(value.into())
153    }
154}
155
156impl<T: Send + Sync + 'static> From<tokio::sync::mpsc::error::SendError<T>> for GetError {
157    fn from(value: tokio::sync::mpsc::error::SendError<T>) -> Self {
158        LocalFailureSnafu.into_error(value.into())
159    }
160}
161
162impl From<endpoint::ConnectionError> for GetError {
163    fn from(value: endpoint::ConnectionError) -> Self {
164        // explicit match just to be sure we are taking everything into account
165        use endpoint::ConnectionError;
166        match value {
167            e @ ConnectionError::VersionMismatch => {
168                // > The peer doesn't implement any supported version
169                // unsupported version is likely a long time error, so this peer is not usable
170                NoncompliantNodeSnafu.into_error(e.into())
171            }
172            e @ ConnectionError::TransportError(_) => {
173                // > The peer violated the QUIC specification as understood by this implementation
174                // bad peer we don't want to keep around
175                NoncompliantNodeSnafu.into_error(e.into())
176            }
177            e @ ConnectionError::ConnectionClosed(_) => {
178                // > The peer's QUIC stack aborted the connection automatically
179                // peer might be disconnecting or otherwise unavailable, drop it
180                IoSnafu.into_error(e.into())
181            }
182            e @ ConnectionError::ApplicationClosed(_) => {
183                // > The peer closed the connection
184                // peer might be disconnecting or otherwise unavailable, drop it
185                IoSnafu.into_error(e.into())
186            }
187            e @ ConnectionError::Reset => {
188                // > The peer is unable to continue processing this connection, usually due to having restarted
189                RemoteResetSnafu.into_error(e.into())
190            }
191            e @ ConnectionError::TimedOut => {
192                // > Communication with the peer has lapsed for longer than the negotiated idle timeout
193                IoSnafu.into_error(e.into())
194            }
195            e @ ConnectionError::LocallyClosed => {
196                // > The local application closed the connection
197                // TODO(@divma): don't see how this is reachable but let's just not use the peer
198                IoSnafu.into_error(e.into())
199            }
200            e @ ConnectionError::CidsExhausted => {
201                // > The connection could not be created because not enough of the CID space
202                // > is available
203                IoSnafu.into_error(e.into())
204            }
205        }
206    }
207}
208
209impl From<endpoint::ReadError> for GetError {
210    fn from(value: endpoint::ReadError) -> Self {
211        use endpoint::ReadError;
212        match value {
213            e @ ReadError::Reset(_) => RemoteResetSnafu.into_error(e.into()),
214            ReadError::ConnectionLost(conn_error) => conn_error.into(),
215            ReadError::ClosedStream
216            | ReadError::IllegalOrderedRead
217            | ReadError::ZeroRttRejected => {
218                // all these errors indicate the peer is not usable at this moment
219                IoSnafu.into_error(value.into())
220            }
221        }
222    }
223}
224impl From<ClosedStream> for GetError {
225    fn from(value: ClosedStream) -> Self {
226        IoSnafu.into_error(value.into())
227    }
228}
229
230impl From<quinn::WriteError> for GetError {
231    fn from(value: quinn::WriteError) -> Self {
232        use quinn::WriteError;
233        match value {
234            e @ WriteError::Stopped(_) => RemoteResetSnafu.into_error(e.into()),
235            WriteError::ConnectionLost(conn_error) => conn_error.into(),
236            WriteError::ClosedStream | WriteError::ZeroRttRejected => {
237                // all these errors indicate the peer is not usable at this moment
238                IoSnafu.into_error(value.into())
239            }
240        }
241    }
242}
243
244impl From<crate::get::fsm::ConnectedNextError> for GetError {
245    fn from(value: crate::get::fsm::ConnectedNextError) -> Self {
246        use crate::get::fsm::ConnectedNextError::*;
247        match value {
248            e @ PostcardSer { .. } => {
249                // serialization errors indicate something wrong with the request itself
250                BadRequestSnafu.into_error(e.into())
251            }
252            e @ RequestTooBig { .. } => {
253                // request will never be sent, drop it
254                BadRequestSnafu.into_error(e.into())
255            }
256            Write { source, .. } => source.into(),
257            Closed { source, .. } => source.into(),
258            e @ Io { .. } => {
259                // io errors are likely recoverable
260                IoSnafu.into_error(e.into())
261            }
262        }
263    }
264}
265
266impl From<crate::get::fsm::AtBlobHeaderNextError> for GetError {
267    fn from(value: crate::get::fsm::AtBlobHeaderNextError) -> Self {
268        use crate::get::fsm::AtBlobHeaderNextError::*;
269        match value {
270            e @ NotFound { .. } => {
271                // > This indicates that the provider does not have the requested data.
272                // peer might have the data later, simply retry it
273                NotFoundSnafu.into_error(e.into())
274            }
275            EndpointRead { source, .. } => source.into(),
276            e @ Io { .. } => {
277                // io errors are likely recoverable
278                IoSnafu.into_error(e.into())
279            }
280        }
281    }
282}
283
284impl From<crate::get::fsm::DecodeError> for GetError {
285    fn from(value: crate::get::fsm::DecodeError) -> Self {
286        use crate::get::fsm::DecodeError::*;
287
288        match value {
289            e @ ChunkNotFound { .. } => NotFoundSnafu.into_error(e.into()),
290            e @ ParentNotFound { .. } => NotFoundSnafu.into_error(e.into()),
291            e @ LeafNotFound { .. } => NotFoundSnafu.into_error(e.into()),
292            e @ ParentHashMismatch { .. } => {
293                // TODO(@divma): did the peer sent wrong data? is it corrupted? did we sent a wrong
294                // request?
295                NoncompliantNodeSnafu.into_error(e.into())
296            }
297            e @ LeafHashMismatch { .. } => {
298                // TODO(@divma): did the peer sent wrong data? is it corrupted? did we sent a wrong
299                // request?
300                NoncompliantNodeSnafu.into_error(e.into())
301            }
302            Read { source, .. } => source.into(),
303            DecodeIo { source, .. } => source.into(),
304        }
305    }
306}
307
308impl From<std::io::Error> for GetError {
309    fn from(value: std::io::Error) -> Self {
310        // generally consider io errors recoverable
311        // we might want to revisit this at some point
312        IoSnafu.into_error(value.into())
313    }
314}