1//! Declare dirclient-specific errors.
23use std::sync::Arc;
45use thiserror::Error;
6use tor_error::{Bug, ErrorKind, HasKind};
7use tor_linkspec::OwnedChanTarget;
8use tor_rtcompat::TimeoutError;
910use crate::SourceInfo;
1112/// An error originating from the tor-dirclient crate.
13#[derive(Error, Debug, Clone)]
14#[non_exhaustive]
15#[allow(clippy::large_enum_variant)] // TODO(nickm) worth fixing as we do #587
16pub enum Error {
17/// Error while getting a circuit
18#[error("Error while getting a circuit")]
19CircMgr(#[from] tor_circmgr::Error),
2021/// An error that has occurred after we have contacted a directory cache and made a circuit to it.
22#[error("Error fetching directory information")]
23RequestFailed(#[from] RequestFailedError),
2425/// We ran into a problem that is probably due to a programming issue.
26#[error("Internal error")]
27Bug(#[from] Bug),
28}
2930/// An error that has occurred after we have contacted a directory cache and made a circuit to it.
31#[derive(Error, Debug, Clone)]
32#[allow(clippy::exhaustive_structs)] // TODO should not be exhaustive
33#[error("Request failed{}", FromSource(.source))]
34pub struct RequestFailedError {
35/// The source that gave us this error.
36pub source: Option<SourceInfo>,
3738/// The underlying error that occurred.
39#[source]
40pub error: RequestError,
41}
4243/// Helper type to display an optional source of directory information.
44struct FromSource<'a>(&'a Option<SourceInfo>);
4546impl std::fmt::Display for FromSource<'_> {
47fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48if let Some(si) = self.0 {
49write!(f, " from {}", si)
50 } else {
51Ok(())
52 }
53 }
54}
5556/// An error originating from the tor-dirclient crate.
57#[derive(Error, Debug, Clone)]
58#[non_exhaustive]
59pub enum RequestError {
60/// The directory cache took too long to reply to us.
61#[error("directory timed out")]
62DirTimeout,
6364/// We got an EOF before we were done with the headers.
65#[error("truncated HTTP headers")]
66TruncatedHeaders,
6768/// Received a response that was longer than we expected.
69#[error("response too long; gave up after {0} bytes")]
70ResponseTooLong(usize),
7172/// Received too many bytes in our headers.
73#[error("headers too long; gave up after {0} bytes")]
74HeadersTooLong(usize),
7576/// Data received was not UTF-8 encoded.
77#[error("Couldn't decode data as UTF-8.")]
78Utf8Encoding(#[from] std::string::FromUtf8Error),
7980/// Io error while reading on connection
81#[error("IO error")]
82IoError(#[source] Arc<std::io::Error>),
8384/// A protocol error while launching a stream
85#[error("Protocol error while launching a stream")]
86Proto(#[from] tor_proto::Error),
8788/// Error when parsing http
89#[error("Couldn't parse HTTP headers")]
90HttparseError(#[from] httparse::Error),
9192/// Error while creating http request
93//
94 // TODO this should be abolished, in favour of a `Bug` variant,
95 // so that we get a stack trace, as per the notes for EK::Internal.
96 // We could convert via into_internal!, or a custom `From` impl.
97#[error("Couldn't create HTTP request")]
98HttpError(#[source] Arc<http::Error>),
99100/// Unrecognized content-encoding
101#[error("Unrecognized content encoding: {0:?}")]
102ContentEncoding(String),
103104/// Too much clock skew between us and the directory.
105 ///
106 /// (We've giving up on this request early, since any directory that it
107 /// believes in, we would reject as untimely.)
108#[error("Too much clock skew with directory cache")]
109TooMuchClockSkew,
110111/// We tried to launch a request without any requested objects.
112 ///
113 /// This can happen if (for example) we request an empty list of
114 /// microdescriptors or certificates.
115#[error("We didn't have any objects to request")]
116EmptyRequest,
117118/// HTTP status code indicates a not completely successful request
119#[error("HTTP status code {0}: {1:?}")]
120HttpStatus(u16, String),
121}
122123impl From<TimeoutError> for RequestError {
124fn from(_: TimeoutError) -> Self {
125 RequestError::DirTimeout
126 }
127}
128129impl From<std::io::Error> for RequestError {
130fn from(err: std::io::Error) -> Self {
131Self::IoError(Arc::new(err))
132 }
133}
134135impl From<http::Error> for RequestError {
136fn from(err: http::Error) -> Self {
137Self::HttpError(Arc::new(err))
138 }
139}
140141impl Error {
142/// Return true if this error means that the circuit shouldn't be used
143 /// for any more directory requests.
144pub fn should_retire_circ(&self) -> bool {
145// TODO: probably this is too aggressive, and we should
146 // actually _not_ dump the circuit under all circumstances.
147match self {
148 Error::CircMgr(_) => true, // should be unreachable.
149Error::RequestFailed(RequestFailedError { error, .. }) => error.should_retire_circ(),
150 Error::Bug(_) => true,
151 }
152 }
153154/// Return the peer or peers that are to be blamed for the error.
155 ///
156 /// (This can return multiple peers if the request failed because multiple
157 /// circuit attempts all failed.)
158pub fn cache_ids(&self) -> Vec<&OwnedChanTarget> {
159match &self {
160 Error::CircMgr(e) => e.peers(),
161 Error::RequestFailed(RequestFailedError {
162 source: Some(source),
163 ..
164 }) => vec![source.cache_id()],
165_ => Vec::new(),
166 }
167 }
168}
169170impl RequestError {
171/// Return true if this error means that the circuit shouldn't be used
172 /// for any more directory requests.
173pub fn should_retire_circ(&self) -> bool {
174// TODO: probably this is too aggressive, and we should
175 // actually _not_ dump the circuit under all circumstances.
176true
177}
178}
179180impl HasKind for RequestError {
181fn kind(&self) -> ErrorKind {
182use ErrorKind as EK;
183use RequestError as E;
184match self {
185 E::DirTimeout => EK::TorNetworkTimeout,
186 E::TruncatedHeaders => EK::TorProtocolViolation,
187 E::ResponseTooLong(_) => EK::TorProtocolViolation,
188 E::HeadersTooLong(_) => EK::TorProtocolViolation,
189 E::Utf8Encoding(_) => EK::TorProtocolViolation,
190// TODO: it would be good to get more information out of the IoError
191 // in this case, but that would require a bunch of gnarly
192 // downcasting.
193E::IoError(_) => EK::TorDirectoryError,
194 E::Proto(e) => e.kind(),
195 E::HttparseError(_) => EK::TorProtocolViolation,
196 E::HttpError(_) => EK::Internal,
197 E::ContentEncoding(_) => EK::TorProtocolViolation,
198 E::TooMuchClockSkew => EK::TorDirectoryError,
199 E::EmptyRequest => EK::Internal,
200 E::HttpStatus(_, _) => EK::TorDirectoryError,
201 }
202 }
203}
204205impl HasKind for RequestFailedError {
206fn kind(&self) -> ErrorKind {
207self.error.kind()
208 }
209}
210211impl HasKind for Error {
212fn kind(&self) -> ErrorKind {
213use Error as E;
214match self {
215 E::CircMgr(e) => e.kind(),
216 E::RequestFailed(e) => e.kind(),
217 E::Bug(e) => e.kind(),
218 }
219 }
220}
221222#[cfg(any(feature = "hs-client", feature = "hs-service"))]
223impl Error {
224/// Return true if this error is one that we should report as a suspicious event,
225 /// along with the dirserver and description of the relevant document,
226 /// if the request was made anonymously.
227pub fn should_report_as_suspicious_if_anon(&self) -> bool {
228use Error as E;
229match self {
230 E::CircMgr(_) => false,
231 E::RequestFailed(e) => e.error.should_report_as_suspicious_if_anon(),
232 E::Bug(_) => false,
233 }
234 }
235}
236#[cfg(any(feature = "hs-client", feature = "hs-service"))]
237impl RequestError {
238/// Return true if this error is one that we should report as a suspicious event,
239 /// along with the dirserver and description of the relevant document,
240 /// if the request was made anonymously.
241pub fn should_report_as_suspicious_if_anon(&self) -> bool {
242use tor_proto::Error as PE;
243match self {
244 RequestError::ResponseTooLong(_) => true,
245 RequestError::HeadersTooLong(_) => true,
246 RequestError::Proto(PE::ExcessInboundCells) => true,
247 RequestError::Proto(_) => false,
248 RequestError::DirTimeout => false,
249 RequestError::TruncatedHeaders => false,
250 RequestError::Utf8Encoding(_) => false,
251 RequestError::IoError(_) => false,
252 RequestError::HttparseError(_) => false,
253 RequestError::HttpError(_) => false,
254 RequestError::ContentEncoding(_) => false,
255 RequestError::TooMuchClockSkew => false,
256 RequestError::EmptyRequest => false,
257 RequestError::HttpStatus(_, _) => false,
258 }
259 }
260}