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
//! Declare error types for the `tor-guardmgr` crate.

use futures::task::SpawnError;
use std::sync::Arc;
use std::time::Instant;
use tor_basic_utils::iter::FilterCount;
use tor_error::{Bug, ErrorKind, HasKind};

/// A error caused by a failure to pick a guard.
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum PickGuardError {
    /// All members of the current sample were down or unusable.
    #[error(
        "No usable guards. Rejected {} as down, then {} as pending, then \
         {} as unsuitable to purpose, then {} with filter.",
        running.display_frac_rejected(),
        pending.display_frac_rejected(),
        suitable.display_frac_rejected(),
        filtered.display_frac_rejected(),
    )]
    AllGuardsDown {
        /// The next time at which any guard will be retriable.
        retry_at: Option<Instant>,
        /// How many guards we rejected because they had failed too recently.
        running: FilterCount,
        /// How many guards we rejected because we are already probing them.
        pending: FilterCount,
        /// How many guards we rejected as unsuitable for the intended application.
        suitable: FilterCount,
        /// How many guards we rejected because of the provided filter.
        filtered: FilterCount,
    },

    /// We have no usable fallback directories.
    #[error(
        "No usable fallbacks. Rejected {} as not running, then {} as filtered.", 
         running.display_frac_rejected(),
        filtered.display_frac_rejected()
    )]
    AllFallbacksDown {
        /// The next time at which any fallback directory will back available.
        retry_at: Option<Instant>,
        /// The number of fallbacks that were believed to be running or down, after applying
        /// the filter.
        running: FilterCount,
        /// The number of fallbacks that satisfied our filter, or did not.
        filtered: FilterCount,
    },

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

    /// An internal programming error occurred.
    #[error("Internal error")]
    Internal(#[from] Bug),
}

impl tor_error::HasKind for PickGuardError {
    fn kind(&self) -> tor_error::ErrorKind {
        use tor_error::ErrorKind as EK;
        use PickGuardError as E;
        match self {
            E::AllFallbacksDown { .. } | E::AllGuardsDown { .. } => EK::TorAccessFailed,
            E::NoCandidatesAvailable => EK::NoPath,
            E::Internal(_) => EK::Internal,
        }
    }
}

impl tor_error::HasRetryTime for PickGuardError {
    fn retry_time(&self) -> tor_error::RetryTime {
        use tor_error::RetryTime as RT;
        use PickGuardError as E;
        match self {
            // Some errors contain times that we can just use.
            E::AllGuardsDown {
                retry_at: Some(when),
                ..
            } => RT::At(*when),
            E::AllFallbacksDown {
                retry_at: Some(when),
                ..
            } => RT::At(*when),

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

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

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

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

    /// An error that occurred while trying to spawn a daemon task.
    #[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>,
    },
}

impl HasKind for GuardMgrError {
    #[rustfmt::skip] // to preserve table in match
    fn kind(&self) -> ErrorKind {
        use GuardMgrError as G;
        match self {
            G::State(e)               => e.kind(),
            G::InvalidConfig(e)       => e.kind(),
            G::Spawn{ cause, .. }     => cause.kind(),
        }
    }
}

impl GuardMgrError {
    /// Construct a new `GuardMgrError` from a `SpawnError`.
    pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> GuardMgrError {
        GuardMgrError::Spawn {
            spawning,
            cause: Arc::new(err),
        }
    }
}

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

impl From<GuardMgrConfigError> for tor_config::ReconfigureError {
    fn from(g: GuardMgrConfigError) -> tor_config::ReconfigureError {
        use tor_config::ReconfigureError as R;
        use GuardMgrConfigError as G;
        match g {
            e @ G::NoLock(_) => R::UnsupportedSituation(e.to_string()),
        }
    }
}

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