use std::sync::Arc;
use derive_more::{From, Into};
use futures::task::SpawnError;
use thiserror::Error;
use tracing::error;
use retry_error::RetryError;
use safelog::{Redacted, Sensitive};
use tor_cell::relaycell::hs::IntroduceAckStatus;
use tor_error::define_asref_dyn_std_error;
use tor_error::{internal, Bug, ErrorKind, ErrorReport as _, HasKind, HasRetryTime, RetryTime};
use tor_linkspec::RelayIds;
use tor_llcrypto::pk::ed25519::Ed25519Identity;
use tor_netdir::Relay;
pub(crate) type RendPtIdentityForError = Redacted<RelayIds>;
pub(crate) fn rend_pt_identity_for_error(relay: &Relay<'_>) -> RendPtIdentityForError {
RelayIds::from_relay_ids(relay).into()
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, From, Into)]
#[allow(clippy::exhaustive_structs)]
#[derive(derive_more::Display)]
#[display(fmt = "#{}", self + 1)]
pub struct IntroPtIndex(pub usize);
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
pub enum ConnError {
#[error("Invalid hidden service identity (`.onion` address)")]
InvalidHsId,
#[error("Unable to download hidden service descriptor")]
DescriptorDownload(RetryError<tor_error::Report<DescriptorError>>),
#[error("Unable to connect to hidden service using any Rendezvous Point / Introduction Point")]
Failed(#[source] RetryError<tor_error::Report<FailedAttemptError>>),
#[error("consensus contains no suitable hidden service directories")]
NoHsDirs,
#[error("hidden service has no introduction points usable by us")]
NoUsableIntroPoints,
#[error("Unable to spawn {spawning}")]
Spawn {
spawning: &'static str,
#[source]
cause: Arc<SpawnError>,
},
#[error("{0}")]
Bug(#[from] Bug),
}
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
#[error("tried hsdir {hsdir}: {error}")]
pub struct DescriptorError {
pub hsdir: Sensitive<Ed25519Identity>,
#[source]
pub error: DescriptorErrorDetail,
}
define_asref_dyn_std_error!(DescriptorError);
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
#[derive(strum::EnumDiscriminants)]
#[strum_discriminants(derive(PartialOrd, Ord))]
pub enum DescriptorErrorDetail {
#[error("timed out")]
Timeout,
#[error("circuit failed")]
Circuit(#[from] tor_circmgr::Error),
#[error("stream failed")]
Stream(#[source] tor_proto::Error),
#[error("directory error")]
Directory(#[from] tor_dirclient::RequestError),
#[error("problem with descriptor")]
Descriptor(#[from] tor_netdoc::doc::hsdesc::HsDescError),
#[error("{0}")]
Bug(#[from] Bug),
}
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
#[derive(strum::EnumDiscriminants)]
#[strum_discriminants(derive(PartialOrd, Ord))]
pub enum FailedAttemptError {
#[error("Unusable introduction point #{intro_index}")]
UnusableIntro {
#[source]
error: crate::relay_info::InvalidTarget,
intro_index: IntroPtIndex,
},
#[error("Failed to obtain any circuit to use as a rendezvous circuit")]
RendezvousCircuitObtain {
#[source]
error: tor_circmgr::Error,
},
#[error("Creating a rendezvous circuit and rendezvous point took too long")]
RendezvousEstablishTimeout {
rend_pt: RendPtIdentityForError,
},
#[error("Failed to establish rendezvous point at {rend_pt}")]
RendezvousEstablish {
#[source]
error: tor_proto::Error,
rend_pt: RendPtIdentityForError,
},
#[error("Failed to obtain circuit to introduction point {intro_index}")]
IntroductionCircuitObtain {
#[source]
error: tor_circmgr::Error,
intro_index: IntroPtIndex,
},
#[error("Introduction exchange (with the introduction point) failed")]
IntroductionExchange {
#[source]
error: tor_proto::Error,
intro_index: IntroPtIndex,
},
#[error("Introduction point reported error in its INTRODUCE_ACK: {status}")]
IntroductionFailed {
status: IntroduceAckStatus,
intro_index: IntroPtIndex,
},
#[error("Communication with introduction point {intro_index} took too long")]
IntroductionTimeout {
intro_index: IntroPtIndex,
},
#[error("Rendezvous at {rend_pt} using introduction point {intro_index} took too long")]
RendezvousCompletionTimeout {
intro_index: IntroPtIndex,
rend_pt: RendPtIdentityForError,
},
#[error(
"Error on rendezvous circuit when expecting rendezvous completion (RENDEZVOUS2 message)"
)]
RendezvousCompletionCircuitError {
#[source]
error: tor_proto::Error,
intro_index: IntroPtIndex,
rend_pt: RendPtIdentityForError,
},
#[error("Rendezvous completion end-to-end crypto handshake failed (bad RENDEZVOUS2 message)")]
RendezvousCompletionHandshake {
#[source]
error: tor_proto::Error,
intro_index: IntroPtIndex,
rend_pt: RendPtIdentityForError,
},
#[error("{0}")]
Bug(#[from] Bug),
}
define_asref_dyn_std_error!(FailedAttemptError);
impl FailedAttemptError {
pub(crate) fn intro_index(&self) -> Option<IntroPtIndex> {
use FailedAttemptError as FAE;
match self {
FAE::UnusableIntro { intro_index, .. }
| FAE::RendezvousCompletionCircuitError { intro_index, .. }
| FAE::RendezvousCompletionHandshake { intro_index, .. }
| FAE::RendezvousCompletionTimeout { intro_index, .. }
| FAE::IntroductionCircuitObtain { intro_index, .. }
| FAE::IntroductionExchange { intro_index, .. }
| FAE::IntroductionFailed { intro_index, .. }
| FAE::IntroductionTimeout { intro_index, .. } => Some(*intro_index),
FAE::RendezvousCircuitObtain { .. }
| FAE::RendezvousEstablish { .. }
| FAE::RendezvousEstablishTimeout { .. }
| FAE::Bug(_) => None,
}
}
}
impl HasRetryTime for FailedAttemptError {
fn retry_time(&self) -> RetryTime {
use FailedAttemptError as FAE;
use RetryTime as RT;
match self {
FAE::UnusableIntro { error, .. } => error.retry_time(),
FAE::RendezvousCircuitObtain { error } => error.retry_time(),
FAE::IntroductionCircuitObtain { error, .. } => error.retry_time(),
FAE::IntroductionFailed { status, .. } => status.retry_time(),
FAE::RendezvousCompletionCircuitError { error: _e, .. }
| FAE::IntroductionExchange { error: _e, .. }
| FAE::RendezvousEstablish { error: _e, .. } => RT::AfterWaiting,
FAE::RendezvousEstablishTimeout { .. }
| FAE::RendezvousCompletionTimeout { .. }
| FAE::IntroductionTimeout { .. } => RT::AfterWaiting,
FAE::RendezvousCompletionHandshake { error: _e, .. } => RT::Never,
FAE::Bug(_) => RT::Never,
}
}
}
impl HasKind for ConnError {
fn kind(&self) -> ErrorKind {
use ConnError as CE;
use ErrorKind as EK;
match self {
CE::InvalidHsId => EK::InvalidStreamTarget,
CE::NoHsDirs => EK::TorDirectoryUnusable,
CE::NoUsableIntroPoints => EK::OnionServiceProtocolViolation,
CE::Spawn { cause, .. } => cause.kind(),
CE::Bug(e) => e.kind(),
CE::DescriptorDownload(attempts) => attempts
.sources()
.max_by_key(|attempt| DescriptorErrorDetailDiscriminants::from(&attempt.0.error))
.map(|attempt| attempt.0.kind())
.unwrap_or_else(|| {
let bug = internal!("internal error, empty CE::DescriptorDownload");
error!("bug: {}", bug.report());
bug.kind()
}),
CE::Failed(attempts) => attempts
.sources()
.max_by_key(|attempt| FailedAttemptErrorDiscriminants::from(&attempt.0))
.map(|attempt| attempt.0.kind())
.unwrap_or_else(|| {
let bug = internal!("internal error, empty CE::DescriptorDownload");
error!("bug: {}", bug.report());
bug.kind()
}),
}
}
}
impl HasKind for DescriptorError {
fn kind(&self) -> ErrorKind {
self.error.kind()
}
}
impl HasKind for DescriptorErrorDetail {
fn kind(&self) -> ErrorKind {
use tor_dirclient::RequestError as RE;
use DescriptorErrorDetail as DED;
use ErrorKind as EK;
match self {
DED::Timeout => EK::TorNetworkTimeout,
DED::Circuit(e) => e.kind(),
DED::Stream(e) => e.kind(),
DED::Directory(RE::HttpStatus(st)) if *st == 404 => EK::OnionServiceNotFound,
DED::Directory(RE::ResponseTooLong(_)) => EK::OnionServiceProtocolViolation,
DED::Directory(RE::Utf8Encoding(_)) => EK::OnionServiceProtocolViolation,
DED::Directory(other_re) => other_re.kind(),
DED::Descriptor(e) => e.kind(),
DED::Bug(e) => e.kind(),
}
}
}
impl HasKind for FailedAttemptError {
fn kind(&self) -> ErrorKind {
use ErrorKind as EK;
use FailedAttemptError as FAE;
match self {
FAE::UnusableIntro { .. } => EK::OnionServiceProtocolViolation,
FAE::RendezvousCircuitObtain { error, .. } => error.kind(),
FAE::RendezvousEstablish { error, .. } => error.kind(),
FAE::RendezvousCompletionCircuitError { error, .. } => error.kind(),
FAE::RendezvousCompletionHandshake { error, .. } => error.kind(),
FAE::RendezvousEstablishTimeout { .. } => EK::TorNetworkTimeout,
FAE::IntroductionCircuitObtain { error, .. } => error.kind(),
FAE::IntroductionExchange { error, .. } => error.kind(),
FAE::IntroductionFailed { .. } => EK::OnionServiceConnectionFailed,
FAE::IntroductionTimeout { .. } => EK::TorNetworkTimeout,
FAE::RendezvousCompletionTimeout { .. } => EK::RemoteNetworkTimeout,
FAE::Bug(e) => e.kind(),
}
}
}
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
pub enum StartupError {
#[error("Unable to spawn {spawning}")]
Spawn {
spawning: &'static str,
#[source]
cause: Arc<SpawnError>,
},
#[error("{0}")]
Bug(#[from] Bug),
}
impl HasKind for StartupError {
fn kind(&self) -> ErrorKind {
use StartupError as SE;
match self {
SE::Spawn { cause, .. } => cause.kind(),
SE::Bug(e) => e.kind(),
}
}
}