1
//! Declare an error type for tor-circmgr
2

            
3
use std::sync::Arc;
4

            
5
use futures::task::SpawnError;
6
use retry_error::RetryError;
7
use thiserror::Error;
8

            
9
use tor_error::{Bug, ErrorKind, HasKind};
10
use tor_linkspec::OwnedChanTarget;
11

            
12
/// An error returned while looking up or building a circuit
13
74
#[derive(Error, Debug, Clone)]
14
#[non_exhaustive]
15
pub enum Error {
16
    /// We started building a circuit on a guard, but later decided not
17
    /// to use that guard.
18
    //
19
    // TODO: We shouldn't count this as an error for the purposes of the number
20
    // of allowable failures of a circuit request.
21
    #[error("Discarded circuit because of speculative guard selection")]
22
    GuardNotUsable,
23

            
24
    /// We were waiting on a pending circuit, but it failed to report
25
    #[error("Pending circuit(s) failed without reporting status")]
26
    PendingCanceled,
27

            
28
    /// A circuit succeeded, but was cancelled before it could be used.
29
    ///
30
    /// Circuits can be cancelled either by a call to
31
    /// `retire_all_circuits()`, or by a configuration change that
32
    /// makes old paths unusable.
33
    //
34
    // TODO: We shouldn't count this as an error for the purposes of the number
35
    // of allowable failures of a circuit request.
36
    #[error("Circuit canceled")]
37
    CircCanceled,
38

            
39
    /// A circuit build took too long to finish.
40
    #[error("Circuit took too long to build")]
41
    CircTimeout,
42

            
43
    /// A request spent too long waiting for a circuit
44
    #[error("Spent too long waiting for a circuit to build")]
45
    RequestTimeout,
46

            
47
    /// No suitable relays for a request
48
    #[error("Can't build path for circuit: {0}")]
49
    NoPath(String),
50

            
51
    /// No suitable exit relay for a request.
52
    #[error("Can't find exit for circuit: {0}")]
53
    NoExit(String),
54

            
55
    /// Problem creating or updating a guard manager.
56
    #[error("Problem creating or updating guards list: {0}")]
57
    GuardMgr(#[source] tor_guardmgr::GuardMgrError),
58

            
59
    /// Problem selecting a guard relay.
60
    #[error("Unable to select a guard relay: {0}")]
61
    Guard(#[from] tor_guardmgr::PickGuardError),
62

            
63
    /// Unable to get or build a circuit, despite retrying.
64
    #[error("{0}")]
65
    RequestFailed(RetryError<Box<Error>>),
66

            
67
    /// Problem with channel
68
    #[error("Problem with channel to {peer}")]
69
    Channel {
70
        /// Which relay we were trying to connect to
71
        peer: OwnedChanTarget,
72

            
73
        /// What went wrong
74
        #[source]
75
        cause: tor_chanmgr::Error,
76
    },
77

            
78
    /// Protocol issue while building a circuit.
79
    #[error("Problem building a circuit: {0}")]
80
    Protocol(#[from] tor_proto::Error),
81

            
82
    /// We have an expired consensus
83
    #[error("Consensus is expired")]
84
    ExpiredConsensus,
85

            
86
    /// Unable to spawn task
87
    #[error("unable to spawn {spawning}")]
88
    Spawn {
89
        /// What we were trying to spawn
90
        spawning: &'static str,
91
        /// What happened when we tried to spawn it.
92
        #[source]
93
        cause: Arc<SpawnError>,
94
    },
95

            
96
    /// Problem loading or storing persistent state.
97
    #[error("Problem loading or storing state: {0}")]
98
    State(#[from] tor_persist::Error),
99

            
100
    /// An error caused by a programming issue . or a failure in another
101
    /// library that we can't work around.
102
    #[error("Programming issue: {0}")]
103
    Bug(#[from] Bug),
104
}
105

            
106
impl From<futures::channel::oneshot::Canceled> for Error {
107
    fn from(_: futures::channel::oneshot::Canceled) -> Error {
108
        Error::PendingCanceled
109
    }
110
}
111

            
112
impl From<tor_rtcompat::TimeoutError> for Error {
113
16
    fn from(_: tor_rtcompat::TimeoutError) -> Error {
114
16
        Error::CircTimeout
115
16
    }
116
}
117

            
118
impl From<tor_guardmgr::GuardMgrError> for Error {
119
    fn from(err: tor_guardmgr::GuardMgrError) -> Error {
120
        match err {
121
            tor_guardmgr::GuardMgrError::State(e) => Error::State(e),
122
            _ => Error::GuardMgr(err),
123
        }
124
    }
125
}
126

            
127
impl HasKind for Error {
128
    fn kind(&self) -> ErrorKind {
129
        use Error as E;
130
        use ErrorKind as EK;
131
        match self {
132
            E::Channel { cause, .. } => cause.kind(),
133
            E::Bug(e) => e.kind(),
134
            E::NoPath(_) => EK::NoPath,
135
            E::NoExit(_) => EK::NoExit,
136
            E::PendingCanceled => EK::ReactorShuttingDown,
137
            E::CircTimeout => EK::TorNetworkTimeout,
138
            E::GuardNotUsable => EK::TransientFailure,
139
            E::RequestTimeout => EK::TorNetworkTimeout,
140
            E::RequestFailed(e) => e
141
                .sources()
142
                .max_by_key(|e| e.severity())
143
                .map(|e| e.kind())
144
                .unwrap_or(EK::Internal),
145
            E::CircCanceled => EK::TransientFailure,
146
            E::Protocol(e) => e.kind(),
147
            E::State(e) => e.kind(),
148
            E::GuardMgr(e) => e.kind(),
149
            E::Guard(_) => EK::NoPath,
150
            E::ExpiredConsensus => EK::DirectoryExpired,
151
            E::Spawn { cause, .. } => cause.kind(),
152
        }
153
    }
154
}
155

            
156
impl Error {
157
    /// Construct a new `Error` from a `SpawnError`.
158
    pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> Error {
159
        Error::Spawn {
160
            spawning,
161
            cause: Arc::new(err),
162
        }
163
    }
164

            
165
    /// Return an integer representing the relative severity of this error.
166
    ///
167
    /// Used to determine which error to use when determining the kind of a retry error.
168
    fn severity(&self) -> usize {
169
        use Error as E;
170
        match self {
171
            E::GuardNotUsable => 10,
172
            E::PendingCanceled => 20,
173
            E::CircCanceled => 20,
174
            E::CircTimeout => 30,
175
            E::RequestTimeout => 30,
176
            E::NoPath(_) => 40,
177
            E::NoExit(_) => 40,
178
            E::GuardMgr(_) => 40,
179
            E::Guard(_) => 40,
180
            E::RequestFailed(_) => 40,
181
            E::Channel { .. } => 40,
182
            E::Protocol(_) => 45,
183
            E::ExpiredConsensus => 50,
184
            E::Spawn { .. } => 90,
185
            E::State(_) => 90,
186
            E::Bug(_) => 100,
187
        }
188
    }
189
}