1
//! Error module for `tor-dirserver`.
2

            
3
use thiserror::Error;
4
use tor_netdoc::parse2::VerifyFailed;
5

            
6
/// Indicates that an error variant is fatal.
7
///
8
/// A fatal error means that the application should abort execution and may
9
/// not retry again in the future.
10
///
11
/// This trait should only be implemented for error variants where some error
12
/// variants are fatal and others are not.  In other words: An error where all
13
/// variants are either fatal or non-fatal does not qualify for this trait.
14
// TODO DIRMIRROR: Move this to tor_error.
15
pub(crate) trait IsFatal: std::error::Error {
16
    /// Checks whether the current error is considered to be fatal.
17
    fn is_fatal(&self) -> bool;
18
}
19

            
20
/// An error while performing a request at a directory authority.
21
#[derive(Debug, Error)]
22
#[non_exhaustive]
23
pub(crate) enum AuthorityRequestError {
24
    /// TCP connection to the endpoint failed.
25
    #[error("tcp connection error: {0}")]
26
    TcpConnect(std::io::Error),
27

            
28
    /// [`tor_dirclient`] failed at performing the request.
29
    ///
30
    /// This usually indicates some failures in HTTP/1.0, such as the response
31
    /// not being valid HTTP/1.0; although Tor generally has some specialities
32
    /// here and there with regard to this, hence why we have dirclient in the
33
    /// first place.
34
    #[error("dirclient error: {0}")]
35
    Request(Box<tor_dirclient::RequestFailedError>),
36

            
37
    /// A response does not make semantic sense.
38
    ///
39
    /// This for example may include cases where we got more netdocs than we
40
    /// requested for.
41
    #[error("response error: {0}")]
42
    Response(&'static str),
43

            
44
    /// A request led to no progress being made.
45
    ///
46
    /// This is the case if the response only consists of netdocs we did not
47
    /// request or no netdocs at all, leading to no progress.
48
    ///
49
    /// The correct error handling strategy for this case is to pick a different
50
    /// endpoint, as the current one is either not useful for us anymore or
51
    /// is maliciously tricking us into something that should not happen.
52
    #[error("request resulted in no progress")]
53
    NoRequestedDocumentsReturned,
54

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

            
60
impl From<tor_dirclient::RequestFailedError> for AuthorityRequestError {
61
    fn from(value: tor_dirclient::RequestFailedError) -> Self {
62
        Self::Request(Box::new(value))
63
    }
64
}
65

            
66
impl IsFatal for AuthorityRequestError {
67
    /// The [`AuthorityRequestError`] is considered to be fatal.
68
    ///
69
    /// Right now, the following variants are considered to be fatal:
70
    /// * [`AuthorityRequestError::Bug`]
71
    fn is_fatal(&self) -> bool {
72
        matches!(&self, Self::Bug(_))
73
    }
74
}
75

            
76
/// An error while interacting with a database.
77
///
78
/// This error should be returned by all functions that interact with the
79
/// database in one way or another.
80
#[derive(Debug, Error)]
81
#[non_exhaustive]
82
pub(crate) enum DatabaseError {
83
    /// A low-level SQLite error has occurred, which can have a basically
84
    /// infinite amount of reasons, all of them outlined in the actual SQLite
85
    /// and [`rusqlite`] documentation.
86
    #[error("low-level rusqlite error: {0}")]
87
    LowLevel(#[from] rusqlite::Error),
88

            
89
    /// This is an application level error meaning that the database can be
90
    /// successfully accessed but its content implies it is of a schema version
91
    /// we do not support.
92
    ///
93
    /// Keep in mind that an unrecognized schema is not equal to no schema.
94
    /// In the latter case we actually initialize the database, whereas in the
95
    /// previous one, we fail early in order to not corrupt an existing database.
96
    /// Future versions of this crate should continue with this promise in order
97
    /// to ensure forward compatibility.
98
    #[error("incompatible schema version: {version}")]
99
    IncompatibleSchema {
100
        /// The incompatible schema version found in the database.
101
        version: String,
102
    },
103

            
104
    /// Interaction with our database pool, [`r2d2`], has failed.
105
    ///
106
    /// Unlike other database pools, this error is fairly straightforward and
107
    /// may only be obtained in the cases in which we try to obtain a connection
108
    /// handle from the pool.  Notably, it does not fail if, for example,
109
    /// the low-level [`rusqlite`] has a failure.
110
    #[error("pool error: {0}")]
111
    Pool(#[from] r2d2::Error),
112

            
113
    /// An internal error.
114
    #[error("Internal error")]
115
    Bug(#[from] tor_error::Bug),
116
}
117

            
118
/// An error related to an operation in the dirmirror FSM.
119
//
120
// TODO: Rename this to MirrorOperationError.
121
#[derive(Debug, Error)]
122
#[non_exhaustive]
123
pub(crate) enum OperationError {
124
    /// Request to a directory authority failed.
125
    #[error("authority request error: {0}")]
126
    AuthorityRequest(#[from] AuthorityRequestError),
127

            
128
    /// Access to the database failed for good.
129
    #[error("database error: {0}")]
130
    Database(#[from] DatabaseError),
131

            
132
    /// Verificaton of a network document has failed.
133
    ///
134
    /// This also includes checks for timeliness.
135
    ///
136
    /// We do not consider this fatal but simply discard the unverified network
137
    /// document.
138
    #[error("verification error: {0}")]
139
    VerifyFailed(#[from] VerifyFailed),
140

            
141
    /// An internal error.
142
    #[error("Internal error")]
143
    Bug(#[from] tor_error::Bug),
144
}
145

            
146
impl IsFatal for OperationError {
147
    /// The [`OperationError`] is considered to be fatal.
148
    ///
149
    /// Right now, the following variants are considered to be fatal:
150
    /// * [`OperationError::Database`]
151
    /// * [`OperationError::Bug`]
152
    /// * [`OperationError::AuthorityRequest`] if [`IsFatal`]
153
    fn is_fatal(&self) -> bool {
154
        match self {
155
            Self::Database(_) | Self::Bug(_) => true,
156
            Self::AuthorityRequest(e) => e.is_fatal(),
157
            _ => false,
158
        }
159
    }
160
}