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
    )]
22
    AllGuardsDown {
23
        /// The next time at which any guard will be retriable.
24
        retry_at: Option<Instant>,
25
        /// How many guards we rejected because they had failed too recently.
26
        running: FilterCount,
27
        /// How many guards we rejected because we are already probing them.
28
        pending: FilterCount,
29
        /// How many guards we rejected as unsuitable for the intended application.
30
        suitable: FilterCount,
31
        /// How many guards we rejected because of the provided filter.
32
        filtered: FilterCount,
33
    },
34

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

            
51
    /// Tried to select guards or fallbacks from an empty list.
52
    #[error("Tried to pick from an empty list")]
53
    NoCandidatesAvailable,
54

            
55
    /// An internal programming error occurred.
56
    #[error("Internal error")]
57
    Internal(#[from] Bug),
58
}
59

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

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

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

            
91
            // We were asked to choose some kind of guard that doesn't exist in
92
            // our current universe; that's not going to be come viable down the
93
            // line.
94
            E::NoCandidatesAvailable => RT::Never,
95

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

            
109
    /// Configuration is not valid or available
110
    #[error("Invalid configuration")]
111
    InvalidConfig(#[from] GuardMgrConfigError),
112

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

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

            
136
impl GuardMgrError {
137
    /// Construct a new `GuardMgrError` from a `SpawnError`.
138
    pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> GuardMgrError {
139
        GuardMgrError::Spawn {
140
            spawning,
141
            cause: Arc::new(err),
142
        }
143
    }
144
}
145

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

            
161
impl From<GuardMgrConfigError> for tor_config::ReconfigureError {
162
    fn from(g: GuardMgrConfigError) -> tor_config::ReconfigureError {
163
        use tor_config::ReconfigureError as R;
164
        use GuardMgrConfigError as G;
165
        match g {
166
            e @ G::NoLock(_) => R::UnsupportedSituation(e.to_string()),
167
        }
168
    }
169
}
170

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