1
//! Error types from [`Uploader`](super::Uploader)s
2
//! and [`Publisher`](super::Publisher)s.
3
use std::{sync::Arc, time::Duration};
4
use tor_error::{Bug, HasKind, internal};
5

            
6
/// Trait to represent the kinds of errors that we generally return in Arti.
7
//
8
// TODO: Move this to tor-error?
9
// NOTE: Adding clone makes this no longer dyn-safe.
10
pub trait TorError:
11
    HasKind + std::fmt::Debug + std::fmt::Display + std::error::Error + Send + Sync + 'static
12
{
13
}
14
impl<T> TorError for T where
15
    T: HasKind + std::fmt::Debug + std::fmt::Display + std::error::Error + Send + Sync + 'static
16
{
17
}
18

            
19
/// A failure to upload a document to a single target.
20
#[derive(Clone, Debug, thiserror::Error)]
21
#[non_exhaustive]
22
pub enum UploadError {
23
    /// The target refused the document, and we should not retry.
24
    #[error("Directory rejected document, saying {0}")]
25
    Rejected(Rejection),
26

            
27
    /// We were unable to make a direct connection to a directory.
28
    #[error("Unable to connect to directory")]
29
    Connect(#[source] Arc<std::io::Error>),
30

            
31
    /// The response we received appears to violate some part of the protocol.
32
    #[error("Invalid response: {0}")]
33
    InvalidResponse(#[source] Arc<dyn TorError>),
34

            
35
    /// Our attempt to upload the document took too long.
36
    #[error("Upload attempt took too long")]
37
    Timeout,
38

            
39
    /// The target told us to come back later.
40
    ///
41
    /// If a duration is provided, we should not come back
42
    /// before the provided duration.
43
    #[error("Directory told us to come back later.")]
44
    Deferred {
45
        /// The status code we received
46
        status_code: u16,
47

            
48
        /// The HTTP message we received
49
        message: String,
50

            
51
        /// The amount of time we were told to wait (or decided to wait)
52
        how_long: Option<Duration>,
53
    },
54

            
55
    /// An error from tor_dirclient that did not fall into any other category.
56
    ///
57
    /// (Don't construct this directly; instead, use From.)
58
    #[error("Unable to upload document")]
59
    SendRequest(#[source] Box<tor_dirclient::RequestError>),
60

            
61
    /// The upload failed for some other non-retriable reason.
62
    #[error("Document upload failed; cannot retry at this target")]
63
    DocumentFailedPermanently(#[source] Arc<dyn TorError>),
64

            
65
    /// The upload failed for some other retriable reason.
66
    ///
67
    /// (This variant is here so we can construct [`UploadError`]s from anywhere in Arti.)
68
    #[error("Directory upload failed")]
69
    Other(#[source] Arc<dyn TorError>),
70

            
71
    /// A bug occurred.
72
    #[error("Bug occurred while uploading")]
73
    Bug(#[source] Bug),
74
}
75

            
76
impl UploadError {
77
    /// If this error has a suggested delay before retrying, return that delay.
78
24
    pub(crate) fn suggested_delay(&self) -> Option<Duration> {
79
24
        match self {
80
            UploadError::Deferred { how_long, .. } => *how_long,
81
24
            _ => None,
82
        }
83
24
    }
84
}
85

            
86
/// A record of a rejection from an upload target.
87
#[derive(Clone, Debug)]
88
pub struct Rejection {
89
    /// The provided message when the document was rejected.
90
    message: String,
91
}
92

            
93
impl Rejection {
94
    /// Create a new rejection from a message returned by an upload target.
95
    pub fn from_message(message: String) -> Self {
96
        Rejection { message }
97
    }
98

            
99
    /// Return the message returned by the upload target.
100
    pub fn message(&self) -> &str {
101
        self.message.as_str()
102
    }
103
}
104

            
105
impl std::fmt::Display for Rejection {
106
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107
        write!(f, "{:?}", &self.message)
108
    }
109
}
110

            
111
impl From<tor_dirclient::RequestError> for UploadError {
112
    fn from(err: tor_dirclient::RequestError) -> Self {
113
        use UploadError as E;
114
        use tor_dirclient::RequestError as RE;
115
        match err {
116
            RE::DirTimeout => E::Timeout,
117
            RE::ResponseTooLong(_)
118
            | RE::HeadersTooLong(_)
119
            | RE::Utf8Encoding(_)
120
            | RE::HttpError(_)
121
            | RE::HttparseError(_) => E::InvalidResponse(Arc::new(err)),
122
            RE::HttpStatus(status_code, message) => {
123
                match Self::from_http_response(status_code, Some(message)) {
124
                    Some(e) => e,
125
                    None => E::Bug(internal!(
126
                        "Unexpected successful status code {status_code:?}"
127
                    )),
128
                }
129
            }
130
            RE::IoError(_)
131
            | RE::TruncatedHeaders
132
            | RE::Proto(_)
133
            | RE::Tunnel(_)
134
            | RE::TooMuchClockSkew
135
            | RE::EmptyRequest
136
            | RE::EmptyResponse
137
            | RE::ContentEncoding(_) => E::SendRequest(Box::new(err)),
138
            // We need this catch-all since the tor_dirclient::RequestError is non-exhaustive.
139
            other => E::SendRequest(Box::new(other)),
140
        }
141
    }
142
}
143

            
144
impl From<tor_dirclient::Error> for UploadError {
145
    fn from(err: tor_dirclient::Error) -> Self {
146
        use tor_dirclient::Error as DE;
147
        match err {
148
            DE::CircMgr(error) => Self::Other(Arc::new(error) as _),
149
            DE::RequestFailed(request_failed_error) => Self::from(request_failed_error.error),
150
            DE::Bug(bug) => Self::Bug(bug),
151
            other => Self::Other(Arc::new(other) as _),
152
        }
153
    }
154
}
155

            
156
/// Error type for an invalid http status code.
157
#[derive(Clone, Debug, thiserror::Error)]
158
#[error("Invalid http response code {0:?}")]
159
struct BadStatusCode(u16);
160

            
161
impl tor_error::HasKind for BadStatusCode {
162
    fn kind(&self) -> tor_error::ErrorKind {
163
        tor_error::ErrorKind::TorProtocolViolation
164
    }
165
}
166

            
167
impl UploadError {
168
    /// Construct a `Result<(), UploadError>` from a [`tor_dirclient::DirResponse`].
169
    pub(crate) fn from_directory_response(
170
        response: Result<tor_dirclient::DirResponse, tor_dirclient::Error>,
171
    ) -> Result<(), UploadError> {
172
        let response = response.map_err(Self::from)?;
173
        if let Some(e) = response.error() {
174
            return Err(Self::from(e.clone()));
175
        }
176

            
177
        match Self::from_http_response(
178
            response.status_code(),
179
            response.status_message().map(String::from),
180
        ) {
181
            Some(error) => Err(error),
182
            None => Ok(()),
183
        }
184
    }
185

            
186
    /// If the status code was 503, and no delay was provided,
187
    /// set the delay to `duration`
188
    pub(crate) fn set_503_delay(mut self, duration: Duration) -> Self {
189
        match &mut self {
190
            Self::Deferred {
191
                status_code: 503,
192
                how_long,
193
                ..
194
            } if how_long.is_none() => {
195
                *how_long = Some(duration);
196
            }
197
            _ => {}
198
        }
199
        self
200
    }
201

            
202
    /// Return true if we can retry after this error.
203
24
    pub(crate) fn is_retriable(&self) -> bool {
204
24
        match self {
205
            UploadError::Rejected(_) => false,
206
            UploadError::DocumentFailedPermanently(_) => false,
207

            
208
            UploadError::Connect(_) => true,
209
            UploadError::InvalidResponse(_) => true,
210
24
            UploadError::Timeout => true,
211
            UploadError::Deferred { .. } => true,
212
            UploadError::SendRequest(_) => true,
213
            UploadError::Other(_) => true,
214
            UploadError::Bug(_) => true,
215
        }
216
24
    }
217

            
218
    /// Construct an [`UploadError`] from a status code and message.
219
    ///
220
    /// Return None if the status code indicates success.
221
    fn from_http_response(status_code: u16, message: Option<String>) -> Option<Self> {
222
        use UploadError as E;
223
        Some(match status_code {
224
            200 => return None,
225
            400..=499 => E::Rejected(Rejection::from_message(
226
                message.unwrap_or_else(|| "Refused".into()),
227
            )),
228
            500..=599 => E::Deferred {
229
                status_code,
230
                message: message.unwrap_or_else(|| "Server Error".into()),
231
                how_long: None,
232
            },
233
            // TODO: We don't historically handle 3xx or 1xx or any 2xx except 200 while uploading,
234
            // but probably we should.
235
            100..=399 => return None,
236
            // Anything else is invalid HTTP.
237
            _ => E::InvalidResponse(Arc::new(BadStatusCode(status_code)) as _),
238
        })
239
    }
240
}
241

            
242
impl tor_error::HasKind for UploadError {
243
    fn kind(&self) -> tor_error::ErrorKind {
244
        use UploadError as E;
245
        use tor_error::ErrorKind as EK;
246
        match self {
247
            E::Rejected(_) => EK::TorDocumentRejected,
248
            E::Connect(_) => EK::LocalNetworkError,
249
            E::InvalidResponse(_) => EK::TorProtocolViolation,
250
            E::Timeout => EK::TorNetworkTimeout,
251
            E::Deferred { .. } => EK::RelayTooBusy,
252
            E::SendRequest(e) => e.kind(),
253
            E::DocumentFailedPermanently(e) => e.kind(),
254
            E::Other(e) => e.kind(),
255
            E::Bug(e) => e.kind(),
256
        }
257
    }
258
}