//! Declare an error type for the tor-dirmgr crate. use std::sync::Arc; use crate::DocSource; use futures::task::SpawnError; use thiserror::Error; use tor_error::{ErrorKind, HasKind}; /// An error originated by the directory manager code #[derive(Error, Debug, Clone)] #[non_exhaustive] pub enum Error { /// We received a document we didn't want at all. #[error("unwanted object: {0}")] Unwanted(&'static str), /// This DirMgr doesn't support downloads. #[error("tried to download information on a DirMgr with no download support")] NoDownloadSupport, /// We couldn't read something from disk that we should have been /// able to read. #[error("corrupt cache: {0}")] CacheCorruption(&'static str), /// rusqlite gave us an error. #[error("sqlite error: {0}")] SqliteError(#[source] Arc), /// A schema version that says we can't read it. #[error("unrecognized data storage schema")] UnrecognizedSchema, /// We couldn't configure the network. #[error("bad network configuration")] BadNetworkConfig(&'static str), /// User requested an operation that required a usable /// bootstrapped directory, but we didn't have one. #[error("directory not present or not up-to-date")] DirectoryNotPresent, /// A consensus document is signed by an unrecognized authority set. #[error("authorities on consensus do not match what we expect.")] UnrecognizedAuthorities, /// A directory manager has been dropped; background tasks can exit too. #[error("dirmgr has been dropped; background tasks exiting")] ManagerDropped, /// We made a bunch of attempts, but weren't unable to advance the /// state of a download. #[error("unable to finish bootstrapping a directory")] CantAdvanceState, /// Blob storage error #[error("storage error: {0}")] StorageError(String), /// An error given by the consensus diff crate. #[error("consdiff error: {0}")] ConsensusDiffError(#[from] tor_consdiff::Error), /// Invalid UTF8 in directory response. #[error("invalid utf-8 from directory server")] BadUtf8FromDirectory(#[source] std::string::FromUtf8Error), /// Invalid UTF8 from our cache. #[error("Invalid utf-8 in directory cache")] BadUtf8InCache(#[source] std::str::Utf8Error), /// Invalid hexadecimal value in the cache. #[error("Invalid hexadecimal id in directory cache")] BadHexInCache(#[source] hex::FromHexError), /// An error given by the network document crate. #[error("netdoc error from {source}: {cause}")] NetDocError { /// Where the document came from. source: DocSource, /// What error we got. #[source] cause: tor_netdoc::Error, }, /// An error given by dirclient #[error("dirclient error: {0}")] DirClientError(#[from] tor_dirclient::Error), /// An error given by the checkable crate. #[error("checkable error: {0}")] SignatureError(#[source] Arc), /// An IO error occurred while manipulating storage on disk. #[error("IO error: {0}")] IOError(#[source] Arc), /// An attempt was made to bootstrap a `DirMgr` created in offline mode. #[error("cannot bootstrap offline DirMgr")] OfflineMode, /// Unable to spawn 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, }, /// A programming problem, either in our code or the code calling it. #[error("programming problem: {0}")] Bug(#[from] tor_error::Bug), } impl From for Error { fn from(err: std::io::Error) -> Self { Self::IOError(Arc::new(err)) } } impl From for Error { fn from(err: signature::Error) -> Self { Self::SignatureError(Arc::new(err)) } } impl Error { /// Construct a new `Error` from a `SpawnError`. pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> Error { Error::Spawn { spawning, cause: Arc::new(err), } } /// Construct a new `Error` from `tor_netdoc::Error`. /// /// Also takes a source so that we can keep track of where the document came from. pub(crate) fn from_netdoc(source: DocSource, cause: tor_netdoc::Error) -> Error { Error::NetDocError { source, cause } } } impl From for Error { fn from(err: rusqlite::Error) -> Self { use ErrorKind as EK; let kind = sqlite_error_kind(&err); match kind { EK::Internal | EK::BadApiUsage => { // TODO: should this be a .is_bug() on EK ? tor_error::Bug::from_error(kind, err, "sqlite detected bug").into() } _ => Self::SqliteError(Arc::new(err)), } } } impl HasKind for Error { fn kind(&self) -> ErrorKind { use Error as E; use ErrorKind as EK; match self { E::Unwanted(_) => EK::TorProtocolViolation, E::NoDownloadSupport => EK::NotImplemented, E::CacheCorruption(_) => EK::CacheCorrupted, E::SqliteError(e) => sqlite_error_kind(e), E::UnrecognizedSchema => EK::CacheCorrupted, E::BadNetworkConfig(_) => EK::InvalidConfig, E::DirectoryNotPresent => EK::DirectoryExpired, E::BadUtf8FromDirectory(_) => EK::TorProtocolViolation, E::BadUtf8InCache(_) => EK::CacheCorrupted, E::BadHexInCache(_) => EK::CacheCorrupted, E::UnrecognizedAuthorities => EK::TorProtocolViolation, E::ManagerDropped => EK::ArtiShuttingDown, E::CantAdvanceState => EK::TorAccessFailed, E::StorageError(_) => EK::CacheAccessFailed, E::ConsensusDiffError(_) => EK::TorProtocolViolation, E::NetDocError { source, .. } => match source { DocSource::LocalCache => EK::CacheCorrupted, DocSource::DirServer { .. } => EK::TorProtocolViolation, }, E::DirClientError(e) => e.kind(), E::SignatureError(_) => EK::TorProtocolViolation, E::IOError(_) => EK::CacheAccessFailed, E::OfflineMode => EK::BadApiUsage, E::Spawn { cause, .. } => cause.kind(), E::Bug(e) => e.kind(), } } } /// Convert a sqlite error code into a real ErrorKind. fn sqlite_error_kind(e: &rusqlite::Error) -> ErrorKind { use rusqlite::ErrorCode as RE; use ErrorKind as EK; match e { rusqlite::Error::SqliteFailure(code, _) => match code.code { RE::DatabaseCorrupt => EK::CacheCorrupted, RE::SchemaChanged | RE::TooBig | RE::ConstraintViolation | RE::TypeMismatch | RE::ApiMisuse | RE::NoLargeFileSupport | RE::ParameterOutOfRange | RE::OperationInterrupted | RE::ReadOnly | RE::OperationAborted | RE::DatabaseBusy | RE::DatabaseLocked | RE::OutOfMemory | RE::InternalMalfunction => EK::Internal, RE::FileLockingProtocolFailed | RE::AuthorizationForStatementDenied | RE::NotFound | RE::DiskFull | RE::CannotOpen | RE::SystemIoFailure | RE::PermissionDenied => EK::CacheAccessFailed, RE::NotADatabase => EK::InvalidConfig, _ => EK::Internal, }, // TODO: Some of the other sqlite error types can sometimes represent // possible database corruption (like UTF8Error.) But I haven't // found a way to distinguish when. _ => EK::Internal, } }