1
//! Declare error types for the `tor-guardmgr` crate.
2

            
3
use futures::task::SpawnError;
4
use std::sync::Arc;
5
use std::time::Instant;
6
use tor_basic_utils::iter::FilterCount;
7
use tor_error::{Bug, ErrorKind, HasKind};
8

            
9
/// A error caused by a failure to pick a guard.
10
#[derive(Clone, Debug, thiserror::Error)]
11
#[non_exhaustive]
12
pub enum PickGuardError {
13
    /// All members of the current sample were down or unusable.
14
    #[error(
15
        "No usable guards. Rejected {} as down, then {} as pending, then \
16
         {} as unsuitable to purpose, then {} with filter.{}",
17
        running.display_frac_rejected(),
18
        pending.display_frac_rejected(),
19
        suitable.display_frac_rejected(),
20
        filtered.display_frac_rejected(),
21
        if let Some(retry_at) = retry_at {
22
            format!(" Retrying in {:?}.", humantime::format_duration(*retry_at - Instant::now()))
23
        } else {
24
            "".to_string()
25
        },
26
    )]
27
    AllGuardsDown {
28
        /// The next time at which any guard will be retriable.
29
        retry_at: Option<Instant>,
30
        /// How many guards we rejected because they had failed too recently.
31
        running: FilterCount,
32
        /// How many guards we rejected because we are already probing them.
33
        pending: FilterCount,
34
        /// How many guards we rejected as unsuitable for the intended application.
35
        suitable: FilterCount,
36
        /// How many guards we rejected because of the provided filter.
37
        filtered: FilterCount,
38
    },
39

            
40
    /// We have no usable fallback directories.
41
    #[error(
42
        "No usable fallbacks. Rejected {} as not running, then {} as filtered.", 
43
         running.display_frac_rejected(),
44
        filtered.display_frac_rejected()
45
    )]
46
    AllFallbacksDown {
47
        /// The next time at which any fallback directory will back available.
48
        retry_at: Option<Instant>,
49
        /// The number of fallbacks that were believed to be running or down, after applying
50
        /// the filter.
51
        running: FilterCount,
52
        /// The number of fallbacks that satisfied our filter, or did not.
53
        filtered: FilterCount,
54
    },
55

            
56
    /// Tried to select guards or fallbacks from an empty list.
57
    #[error("Tried to pick from an empty list")]
58
    NoCandidatesAvailable,
59

            
60
    /// An internal programming error occurred.
61
    #[error("Internal error")]
62
    Internal(#[from] Bug),
63
}
64

            
65
impl tor_error::HasKind for PickGuardError {
66
    fn kind(&self) -> tor_error::ErrorKind {
67
        use PickGuardError as E;
68
        use tor_error::ErrorKind as EK;
69
        match self {
70
            E::AllFallbacksDown { .. } | E::AllGuardsDown { .. } => EK::TorAccessFailed,
71
            E::NoCandidatesAvailable => EK::NoPath,
72
            E::Internal(_) => EK::Internal,
73
        }
74
    }
75
}
76

            
77
impl tor_error::HasRetryTime for PickGuardError {
78
    fn retry_time(&self) -> tor_error::RetryTime {
79
        use PickGuardError as E;
80
        use tor_error::RetryTime as RT;
81
        match self {
82
            // Some errors contain times that we can just use.
83
            E::AllGuardsDown {
84
                retry_at: Some(when),
85
                ..
86
            } => RT::At(*when),
87
            E::AllFallbacksDown {
88
                retry_at: Some(when),
89
                ..
90
            } => RT::At(*when),
91

            
92
            // If we don't know when the guards/fallbacks will be back up,
93
            // though, then we should suggest a random delay.
94
            E::AllGuardsDown { .. } | E::AllFallbacksDown { .. } => RT::AfterWaiting,
95

            
96
            // We were asked to choose some kind of guard that doesn't exist in
97
            // our current universe; that's not going to be come viable down the
98
            // line.
99
            E::NoCandidatesAvailable => RT::Never,
100

            
101
            // Don't try to recover from internal errors.
102
            E::Internal(_) => RT::Never,
103
        }
104
    }
105
}
106
/// An error caused while creating or updating a guard manager.
107
#[derive(Clone, Debug, thiserror::Error)]
108
#[non_exhaustive]
109
pub enum GuardMgrError {
110
    /// An error manipulating persistent state
111
    #[error("Problem accessing persistent guard state")]
112
    State(#[from] tor_persist::Error),
113

            
114
    /// Configuration is not valid or available
115
    #[error("Invalid configuration")]
116
    InvalidConfig(#[from] GuardMgrConfigError),
117

            
118
    /// An error that occurred while trying to spawn a daemon task.
119
    #[error("Unable to spawn {spawning}")]
120
    Spawn {
121
        /// What we were trying to spawn.
122
        spawning: &'static str,
123
        /// What happened when we tried to spawn it.
124
        #[source]
125
        cause: Arc<SpawnError>,
126
    },
127
}
128

            
129
impl HasKind for GuardMgrError {
130
    #[rustfmt::skip] // to preserve table in match
131
    fn kind(&self) -> ErrorKind {
132
        use GuardMgrError as G;
133
        match self {
134
            G::State(e)               => e.kind(),
135
            G::InvalidConfig(e)       => e.kind(),
136
            G::Spawn{ cause, .. }     => cause.kind(),
137
        }
138
    }
139
}
140

            
141
impl GuardMgrError {
142
    /// Construct a new `GuardMgrError` from a `SpawnError`.
143
    pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> GuardMgrError {
144
        GuardMgrError::Spawn {
145
            spawning,
146
            cause: Arc::new(err),
147
        }
148
    }
149
}
150

            
151
/// An error encountered while configuring or reconfiguring a guard manager
152
///
153
/// When this occurs during initial configuration, it will be returned wrapped
154
/// up in `GuardMgrError`.
155
///
156
/// When it occurs during reconfiguration, it is not exposed to caller:
157
/// instead, it is converted into a `tor_config::ReconfigureError`.
158
#[derive(Clone, Debug, thiserror::Error)]
159
#[non_exhaustive]
160
pub enum GuardMgrConfigError {
161
    /// Specified configuration requires exclusive access to stored state, which we don't have
162
    #[error(
163
        "Configuration requires exclusive access to shared state, but another instance of Arti has the lock: {0}"
164
    )]
165
    NoLock(String),
166
}
167

            
168
impl From<GuardMgrConfigError> for tor_config::ReconfigureError {
169
    fn from(g: GuardMgrConfigError) -> tor_config::ReconfigureError {
170
        use GuardMgrConfigError as G;
171
        use tor_config::ReconfigureError as R;
172
        match g {
173
            e @ G::NoLock(_) => R::UnsupportedSituation(e.to_string()),
174
        }
175
    }
176
}
177

            
178
impl HasKind for GuardMgrConfigError {
179
    fn kind(&self) -> ErrorKind {
180
        use ErrorKind as EK;
181
        use GuardMgrConfigError as G;
182
        match self {
183
            // `InvalidConfig` and `FeatureDisabled` aren't right, because
184
            // those should be detected at config build time and reported as `ConfigBuildError`.
185
            // `InvalidConfigTransition` isn't right, because restarting arti won't help.
186
            // Possibly at some point we will allow concurrent artis to work this way.
187
            G::NoLock(_) => EK::NotImplemented,
188
        }
189
    }
190
}