1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
//! Errors relating to being a hidden service client
use std::sync::Arc;

use derive_more::{From, Into};
use futures::task::SpawnError;

use thiserror::Error;
use tracing::error;

use retry_error::RetryError;
use safelog::{Redacted, Sensitive};
use tor_cell::relaycell::hs::IntroduceAckStatus;
use tor_error::define_asref_dyn_std_error;
use tor_error::{internal, Bug, ErrorKind, ErrorReport as _, HasKind, HasRetryTime, RetryTime};
use tor_linkspec::RelayIds;
use tor_llcrypto::pk::ed25519::Ed25519Identity;
use tor_netdir::Relay;

/// Identity of a rendezvous point, for use in error reports
pub(crate) type RendPtIdentityForError = Redacted<RelayIds>;

/// Given a `Relay` for a rendezvous pt, provides its identify for use in error reports
pub(crate) fn rend_pt_identity_for_error(relay: &Relay<'_>) -> RendPtIdentityForError {
    RelayIds::from_relay_ids(relay).into()
}

/// Index of an introduction point in the descriptor
///
/// Principally used in error reporting.
///
/// Formats as `#<n+1>`.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, From, Into)]
#[allow(clippy::exhaustive_structs)]
#[derive(derive_more::Display)]
#[display(fmt = "#{}", self + 1)]
pub struct IntroPtIndex(pub usize);

/// Error that occurred attempting to reach a hidden service
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
pub enum ConnError {
    /// Invalid hidden service identity (`.onion` address)
    #[error("Invalid hidden service identity (`.onion` address)")]
    InvalidHsId,

    /// Unable to download hidden service descriptor
    #[error("Unable to download hidden service descriptor")]
    DescriptorDownload(RetryError<tor_error::Report<DescriptorError>>),

    /// Obtained descriptor but unable to connect to hidden service due to problem with IPT or RPT
    // TODO HS is this the right name for this variant?
    #[error("Unable to connect to hidden service using any Rendezvous Point / Introduction Point")]
    Failed(#[source] RetryError<FailedAttemptError>),

    /// The consensus network contains no suitable hidden service directories!
    #[error("consensus contains no suitable hidden service directories")]
    NoHsDirs,

    /// The descriptor contained only unusable introduction points!
    ///
    /// This is the fault of the service, or shows incompatibility between us and them.
    #[error("hidden service has no introduction points usable by us")]
    NoUsableIntroPoints,

    /// Unable to spawn
    #[error("Unable to spawn {spawning}")]
    Spawn {
        /// What we were trying to spawn
        spawning: &'static str,
        /// What happened when we tried to spawn it
        #[source]
        cause: Arc<SpawnError>,
    },

    /// Internal error
    #[error("{0}")]
    Bug(#[from] Bug),
}

/// Error that occurred attempting to download a descriptor
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
#[error("tried hsdir {hsdir}: {error}")]
pub struct DescriptorError {
    /// Which hsdir we were trying
    // TODO #813 this should be Redacted<RelayDescription> or something
    // It seems likely that the set of redacted hsdir ids could identify the service,
    // so use Sensitive rather than Redacted.
    pub hsdir: Sensitive<Ed25519Identity>,

    /// What happened
    #[source]
    pub error: DescriptorErrorDetail,
}
define_asref_dyn_std_error!(DescriptorError);

/// Error that occurred attempting to download a descriptor
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
//
// NOTE! These are in an order!  "Most interesting" errors come last.
// Specifically, after various attempts, the ErrorKind of the overall error
// will be that of the error which is latest in this enum.
//
#[derive(strum::EnumDiscriminants)]
#[strum_discriminants(derive(PartialOrd, Ord))]
pub enum DescriptorErrorDetail {
    /// Timed out
    #[error("timed out")]
    Timeout,

    /// Failed to establish circuit to hidden service directory
    #[error("circuit failed")]
    Circuit(#[from] tor_circmgr::Error),

    /// Failed to establish stream to hidden service directory
    #[error("stream failed")]
    Stream(#[source] tor_proto::Error),

    /// Failed to make directory request
    #[error("directory error")]
    Directory(#[from] tor_dirclient::RequestError),

    /// Failed to parse or validate descriptor
    #[error("problem with descriptor")]
    Descriptor(#[from] tor_netdoc::doc::hsdesc::HsDescError),

    /// Internal error
    #[error("{0}")]
    Bug(#[from] Bug),
}

/// Error that occurred making one attempt to connect to a hidden service using an IP and RP
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
//
// NOTE! These are in an order!  "Most interesting" errors come last.
// Specifically, after various attempts, the ErrorKind of the overall error
// will be that of the error which is latest in this enum.
//
#[derive(strum::EnumDiscriminants)]
#[strum_discriminants(derive(PartialOrd, Ord))]
// TODO HS is this the right name for this type?  It's a very mixed bag, so maybe it is.
pub enum FailedAttemptError {
    /// Introduction point unusable because it couldn't be used as a circuit target
    #[error("Unusable introduction point #{intro_index}")]
    UnusableIntro {
        /// Why it's not use able
        #[source]
        error: crate::relay_info::InvalidTarget,

        /// The index of the IPT in the list of IPTs in the descriptor
        intro_index: IntroPtIndex,
    },

    /// Failed to obtain any circuit to use as a rendezvous circuit
    #[error("Failed to obtain any circuit to use as a rendezvous circuit")]
    RendezvousCircuitObtain {
        /// Why it's not use able
        #[source]
        error: tor_circmgr::Error,
    },

    /// Creating a rendezvous circuit and rendezvous point took too long
    #[error("Creating a rendezvous circuit and rendezvous point took too long")]
    RendezvousEstablishTimeout {
        /// Which relay did we choose for rendezvous point
        // TODO #813 this should be Redacted<RelayDescription> or something
        rend_pt: RendPtIdentityForError,
    },

    /// Failed to establish rendezvous point
    #[error("Failed to establish rendezvous point at {rend_pt}")]
    RendezvousEstablish {
        /// What happened
        #[source]
        error: tor_proto::Error,

        /// Which relay did we choose for rendezvous point
        // TODO #813 this should be Redacted<RelayDescription> or something
        rend_pt: RendPtIdentityForError,
    },

    /// Failed to obtain circuit to introduction point
    #[error("Failed to obtain circuit to introduction point {intro_index}")]
    IntroductionCircuitObtain {
        /// What happened
        #[source]
        error: tor_circmgr::Error,

        /// The index of the IPT in the list of IPTs in the descriptor
        intro_index: IntroPtIndex,
    },

    /// Introduction exchange (with the introduction point) failed
    #[error("Introduction exchange (with the introduction point) failed")]
    IntroductionExchange {
        /// What happened
        #[source]
        error: tor_proto::Error,

        /// The index of the IPT in the list of IPTs in the descriptor
        intro_index: IntroPtIndex,
    },

    /// Introduction point reported error in its INTRODUCE_ACK
    #[error("Introduction point reported error in its INTRODUCE_ACK: {status}")]
    IntroductionFailed {
        /// The status code provided by the introduction point
        status: IntroduceAckStatus,

        /// The index of the IPT in the list of IPTs in the descriptor
        intro_index: IntroPtIndex,
    },

    /// Communication with introduction point {intro_index} took too long
    ///
    /// This might mean it took too long to establish a circuit to the IPT,
    /// or that the INTRODUCE exchange took too long.
    #[error("Communication with introduction point {intro_index} took too long")]
    IntroductionTimeout {
        /// The index of the IPT in the list of IPTs in the descriptor
        intro_index: IntroPtIndex,
    },

    /// It took too long for the rendezvous to be completed
    ///
    /// This might be the fault of almost anyone.  All we know is that we got
    /// a successful `INTRODUCE_ACK` but the `RENDEZVOUS2` never arrived.
    #[error("Rendezvous at {rend_pt} using introduction point {intro_index} took too long")]
    RendezvousCompletionTimeout {
        /// The index of the IPT in the list of IPTs in the descriptor
        intro_index: IntroPtIndex,

        /// Which relay did we choose for rendezvous point
        // TODO #813 this should be Redacted<RelayDescription> or something
        rend_pt: RendPtIdentityForError,
    },

    /// Error on rendezvous circuit when expecting rendezvous completion (`RENDEZVOUS2`)
    #[error(
        "Error on rendezvous circuit when expecting rendezvous completion (RENDEZVOUS2 message)"
    )]
    RendezvousCompletionCircuitError {
        /// What happened
        #[source]
        error: tor_proto::Error,

        /// The index of the IPT in the list of IPTs in the descriptor
        intro_index: IntroPtIndex,

        /// Which relay did we choose for rendezvous point
        // TODO #813 this should be Redacted<RelayDescription> or something
        rend_pt: RendPtIdentityForError,
    },

    /// Error processing rendezvous completion (`RENDEZVOUS2`)
    ///
    /// This is might be the fault of the hidden service or the rendezvous point.
    #[error("Rendezvous completion end-to-end crypto handshake failed (bad RENDEZVOUS2 message)")]
    RendezvousCompletionHandshake {
        /// What happened
        #[source]
        error: tor_proto::Error,

        /// The index of the IPT in the list of IPTs in the descriptor
        intro_index: IntroPtIndex,

        /// Which relay did we choose for rendezvous point
        // TODO #813 this should be Redacted<RelayDescription> or something
        rend_pt: RendPtIdentityForError,
    },

    /// Internal error
    #[error("{0}")]
    Bug(#[from] Bug),
}
define_asref_dyn_std_error!(FailedAttemptError);

impl FailedAttemptError {
    /// Which introduction point did this error involve (or implicate), if any?
    ///
    /// This is an index into the table in the HS descriptor,
    /// so it can be less-than-useful outside the context where this error was generated.
    // TODO derive this, too much human error possibility
    pub(crate) fn intro_index(&self) -> Option<IntroPtIndex> {
        use FailedAttemptError as FAE;
        match self {
            FAE::UnusableIntro { intro_index, .. }
            | FAE::RendezvousCompletionCircuitError { intro_index, .. }
            | FAE::RendezvousCompletionHandshake { intro_index, .. }
            | FAE::RendezvousCompletionTimeout { intro_index, .. }
            | FAE::IntroductionCircuitObtain { intro_index, .. }
            | FAE::IntroductionExchange { intro_index, .. }
            | FAE::IntroductionFailed { intro_index, .. }
            | FAE::IntroductionTimeout { intro_index, .. } => Some(*intro_index),
            FAE::RendezvousCircuitObtain { .. }
            | FAE::RendezvousEstablish { .. }
            | FAE::RendezvousEstablishTimeout { .. }
            | FAE::Bug(_) => None,
        }
    }
}

/// When *an attempt like this* should be retried.
///
/// For error variants with an introduction point index
/// (`FailedAttemptError::intro_index` returns `Some`)
/// that's when we might retry *with that introduction point*.
///
/// For error variants with a rendezvous point,
/// that's when we might retry *with that rendezvous point*.
///
/// For variants with both, we don't know
/// which of the introduction point or rendezvous point is implicated.
/// Retrying earlier with *one* different relay out of the two relays would be reasonable,
/// as would delaying retrying with *either* of the same relays.
//
// Our current code doesn't keep history about rendezvous points.
// We use this to choose what order to try the service's introduction points.
// See `IptSortKey` in connect.rs.
impl HasRetryTime for FailedAttemptError {
    fn retry_time(&self) -> RetryTime {
        use FailedAttemptError as FAE;
        use RetryTime as RT;
        match self {
            // Delegate to the cause
            FAE::UnusableIntro { error, .. } => error.retry_time(),
            FAE::RendezvousCircuitObtain { error } => error.retry_time(),
            FAE::IntroductionCircuitObtain { error, .. } => error.retry_time(),
            FAE::IntroductionFailed { status, .. } => status.retry_time(),
            // tor_proto::Error doesn't impl HasRetryTime, so we guess
            FAE::RendezvousCompletionCircuitError { error: _e, .. }
            | FAE::IntroductionExchange { error: _e, .. }
            | FAE::RendezvousEstablish { error: _e, .. } => RT::AfterWaiting,
            // Timeouts
            FAE::RendezvousEstablishTimeout { .. }
            | FAE::RendezvousCompletionTimeout { .. }
            | FAE::IntroductionTimeout { .. } => RT::AfterWaiting,
            // Other cases where we define the ErrorKind ourselves
            // If service didn't cause this, it was the RPT, so prefer to try another RPT
            FAE::RendezvousCompletionHandshake { error: _e, .. } => RT::Never,
            FAE::Bug(_) => RT::Never,
        }
    }
}

impl HasKind for ConnError {
    fn kind(&self) -> ErrorKind {
        use ConnError as CE;
        use ErrorKind as EK;
        match self {
            CE::InvalidHsId => EK::InvalidStreamTarget,
            CE::NoHsDirs => EK::TorDirectoryUnusable,
            CE::NoUsableIntroPoints => EK::OnionServiceProtocolViolation,
            CE::Spawn { cause, .. } => cause.kind(),
            CE::Bug(e) => e.kind(),

            CE::DescriptorDownload(attempts) => attempts
                .sources()
                .max_by_key(|attempt| DescriptorErrorDetailDiscriminants::from(&attempt.0.error))
                .map(|attempt| attempt.0.kind())
                .unwrap_or_else(|| {
                    let bug = internal!("internal error, empty CE::DescriptorDownload");
                    error!("bug: {}", bug.report());
                    bug.kind()
                }),

            CE::Failed(attempts) => attempts
                .sources()
                .max_by_key(|attempt| FailedAttemptErrorDiscriminants::from(*attempt))
                .map(|attempt| attempt.kind())
                .unwrap_or_else(|| {
                    let bug = internal!("internal error, empty CE::DescriptorDownload");
                    error!("bug: {}", bug.report());
                    bug.kind()
                }),
        }
    }
}

impl HasKind for DescriptorError {
    fn kind(&self) -> ErrorKind {
        self.error.kind()
    }
}

impl HasKind for DescriptorErrorDetail {
    fn kind(&self) -> ErrorKind {
        use tor_dirclient::RequestError as RE;
        use DescriptorErrorDetail as DED;
        use ErrorKind as EK;
        match self {
            DED::Timeout => EK::TorNetworkTimeout,
            DED::Circuit(e) => e.kind(),
            DED::Stream(e) => e.kind(),
            DED::Directory(RE::HttpStatus(st, _)) if *st == 404 => EK::OnionServiceNotFound,
            DED::Directory(RE::ResponseTooLong(_)) => EK::OnionServiceProtocolViolation,
            DED::Directory(RE::Utf8Encoding(_)) => EK::OnionServiceProtocolViolation,
            DED::Directory(other_re) => other_re.kind(),
            DED::Descriptor(e) => e.kind(),
            DED::Bug(e) => e.kind(),
        }
    }
}

impl HasKind for FailedAttemptError {
    fn kind(&self) -> ErrorKind {
        /*use tor_dirclient::RequestError as RE;
        use tor_netdoc::NetdocErrorKind as NEK;
        use DescriptorErrorDetail as DED;*/
        use ErrorKind as EK;
        use FailedAttemptError as FAE;
        match self {
            FAE::UnusableIntro { .. } => EK::OnionServiceProtocolViolation,
            FAE::RendezvousCircuitObtain { error, .. } => error.kind(),
            FAE::RendezvousEstablish { error, .. } => error.kind(),
            FAE::RendezvousCompletionCircuitError { error, .. } => error.kind(),
            FAE::RendezvousCompletionHandshake { error, .. } => error.kind(),
            FAE::RendezvousEstablishTimeout { .. } => EK::TorNetworkTimeout,
            FAE::IntroductionCircuitObtain { error, .. } => error.kind(),
            FAE::IntroductionExchange { error, .. } => error.kind(),
            FAE::IntroductionFailed { .. } => EK::OnionServiceConnectionFailed,
            FAE::IntroductionTimeout { .. } => EK::TorNetworkTimeout,
            FAE::RendezvousCompletionTimeout { .. } => EK::RemoteNetworkTimeout,
            FAE::Bug(e) => e.kind(),
        }
    }
}

/// Error that occurred attempting to start up a hidden service client connector
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
pub enum StartupError {
    /// Unable to spawn
    #[error("Unable to spawn {spawning}")]
    Spawn {
        /// What we were trying to spawn
        spawning: &'static str,
        /// What happened when we tried to spawn it
        #[source]
        cause: Arc<SpawnError>,
    },

    /// Internal error
    #[error("{0}")]
    Bug(#[from] Bug),
}

impl HasKind for StartupError {
    fn kind(&self) -> ErrorKind {
        use StartupError as SE;
        match self {
            SE::Spawn { cause, .. } => cause.kind(),
            SE::Bug(e) => e.kind(),
        }
    }
}