// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PLATFORM_BASE_ERROR_H_ #define PLATFORM_BASE_ERROR_H_ #include <cassert> #include <ostream> #include <string> #include <utility> #include "platform/base/macros.h" namespace openscreen { // Represents an error returned by an OSP library operation. An error has a // code and an optional message. class Error { public: // TODO(crbug.com/openscreen/65): Group/rename OSP-specific errors enum class Code : int8_t { // No error occurred. kNone = 0, // A transient condition prevented the operation from proceeding (e.g., // cannot send on a non-blocking socket without blocking). This indicates // the caller should try again later. kAgain = -1, // CBOR errors. kCborParsing = 1, kCborEncoding, kCborIncompleteMessage, kCborInvalidResponseId, kCborInvalidMessage, // Presentation start errors. kNoAvailableReceivers, kRequestCancelled, kNoPresentationFound, kPreviousStartInProgress, kUnknownStartError, kUnknownRequestId, kAddressInUse, kDomainNameTooLong, kDomainNameLabelTooLong, kIOFailure, kInitializationFailure, kInvalidIPV4Address, kInvalidIPV6Address, kConnectionFailed, kSocketOptionSettingFailure, kSocketAcceptFailure, kSocketBindFailure, kSocketClosedFailure, kSocketConnectFailure, kSocketInvalidState, kSocketListenFailure, kSocketReadFailure, kSocketSendFailure, // MDNS errors. kMdnsRegisterFailure, kMdnsReadFailure, kMdnsNonConformingFailure, kParseError, kUnknownMessageType, kNoActiveConnection, kAlreadyClosed, kInvalidConnectionState, kNoStartedPresentation, kPresentationAlreadyStarted, kJsonParseError, kJsonWriteError, // OpenSSL errors. // Was unable to generate an RSA key. kRSAKeyGenerationFailure, kRSAKeyParseError, // Was unable to initialize an EVP_PKEY type. kEVPInitializationError, // Was unable to generate a certificate. kCertificateCreationError, // Certificate failed validation. kCertificateValidationError, // Failed to produce a hashing digest. kSha256HashFailure, // A non-recoverable SSL library error has occurred. kFatalSSLError, kFileLoadFailure, // Cast certificate errors. // Certificates were not provided for verification. kErrCertsMissing, // The certificates provided could not be parsed. kErrCertsParse, // Key usage is missing or is not set to Digital Signature. // This error could also be thrown if the CN is missing. kErrCertsRestrictions, // The current date is before the notBefore date or after the notAfter date. kErrCertsDateInvalid, // The certificate failed to chain to a trusted root. kErrCertsVerifyGeneric, // The certificate was not found in the trust store. kErrCertsVerifyUntrustedCert, // The CRL is missing or failed to verify. kErrCrlInvalid, // One of the certificates in the chain is revoked. kErrCertsRevoked, // The pathlen constraint of the root certificate was exceeded. kErrCertsPathlen, // Cast authentication errors. kCastV2PeerCertEmpty, kCastV2WrongPayloadType, kCastV2NoPayload, kCastV2PayloadParsingFailed, kCastV2MessageError, kCastV2NoResponse, kCastV2FingerprintNotFound, kCastV2CertNotSignedByTrustedCa, kCastV2CannotExtractPublicKey, kCastV2SignedBlobsMismatch, kCastV2TlsCertValidityPeriodTooLong, kCastV2TlsCertValidStartDateInFuture, kCastV2TlsCertExpired, kCastV2SenderNonceMismatch, kCastV2DigestUnsupported, kCastV2SignatureEmpty, // Cast channel errors. kCastV2ChannelNotOpen, kCastV2AuthenticationError, kCastV2ConnectError, kCastV2CastSocketError, kCastV2TransportError, kCastV2InvalidMessage, kCastV2InvalidChannelId, kCastV2ConnectTimeout, kCastV2PingTimeout, kCastV2ChannelPolicyMismatch, kCreateSignatureFailed, // Discovery errors. kUpdateReceivedRecordFailure, kRecordPublicationError, kProcessReceivedRecordFailure, // Generic errors. kUnknownError, kNotImplemented, kInsufficientBuffer, kParameterInvalid, kParameterOutOfRange, kParameterNullPointer, kIndexOutOfBounds, kItemAlreadyExists, kItemNotFound, kOperationInvalid, kOperationInProgress, kOperationCancelled, // Cast streaming errors kTypeError, kUnknownCodec, kSocketFailure, kUnencryptedOffer }; Error(); Error(const Error& error); Error(Error&& error) noexcept; Error(Code code); // NOLINT Error(Code code, const std::string& message); Error(Code code, std::string&& message); ~Error(); Error& operator=(const Error& other); Error& operator=(Error&& other); bool operator==(const Error& other) const; bool operator!=(const Error& other) const; // Special case comparison with codes. Without this case, comparisons will // not work as expected, e.g. // const Error foo(Error::Code::kItemNotFound, "Didn't find an item"); // foo == Error::Code::kItemNotFound is actually false. bool operator==(Code code) const; bool operator!=(Code code) const; bool ok() const { return code_ == Code::kNone; } Code code() const { return code_; } const std::string& message() const { return message_; } std::string& message() { return message_; } static const Error& None(); std::string ToString() const; private: Code code_ = Code::kNone; std::string message_; }; std::ostream& operator<<(std::ostream& os, const Error::Code& code); std::ostream& operator<<(std::ostream& out, const Error& error); // A convenience function to return a single value from a function that can // return a value or an error. For normal results, construct with a ValueType* // (ErrorOr takes ownership) and the Error will be kNone with an empty message. // For Error results, construct with an error code and value. // // Example: // // ErrorOr<Bar> Foo::DoSomething() { // if (success) { // return Bar(); // } else { // return Error(kBadThingHappened, "No can do"); // } // } // // TODO(mfoltz): Add support for type conversions. template <typename ValueType> class ErrorOr { public: static ErrorOr<ValueType> None() { static ErrorOr<ValueType> error(Error::Code::kNone); return error; } ErrorOr(const ValueType& value) : value_(value), is_value_(true) {} // NOLINT ErrorOr(ValueType&& value) noexcept // NOLINT : value_(std::move(value)), is_value_(true) {} ErrorOr(const Error& error) : error_(error), is_value_(false) { // NOLINT assert(error_.code() != Error::Code::kNone); } ErrorOr(Error&& error) noexcept // NOLINT : error_(std::move(error)), is_value_(false) { assert(error_.code() != Error::Code::kNone); } ErrorOr(Error::Code code) : error_(code), is_value_(false) { // NOLINT assert(error_.code() != Error::Code::kNone); } ErrorOr(Error::Code code, std::string message) : error_(code, std::move(message)), is_value_(false) { assert(error_.code() != Error::Code::kNone); } ErrorOr(const ErrorOr& other) = delete; ErrorOr(ErrorOr&& other) noexcept : is_value_(other.is_value_) { // NB: Both |value_| and |error_| are uninitialized memory at this point! // Unlike the other constructors, the compiler will not auto-generate // constructor calls for either union member because neither appeared in // this constructor's initializer list. if (other.is_value_) { new (&value_) ValueType(std::move(other.value_)); } else { new (&error_) Error(std::move(other.error_)); } } ErrorOr& operator=(const ErrorOr& other) = delete; ErrorOr& operator=(ErrorOr&& other) noexcept { this->~ErrorOr<ValueType>(); new (this) ErrorOr<ValueType>(std::move(other)); return *this; } ~ErrorOr() { // NB: |value_| or |error_| must be explicitly destroyed since the compiler // will not auto-generate the destructor calls for union members. if (is_value_) { value_.~ValueType(); } else { error_.~Error(); } } bool is_error() const { return !is_value_; } bool is_value() const { return is_value_; } // Unlike Error, we CAN provide an operator bool here, since it is // more obvious to callers that ErrorOr<Foo> will be true if it's Foo. operator bool() const { return is_value_; } const Error& error() const { assert(!is_value_); return error_; } Error& error() { assert(!is_value_); return error_; } const ValueType& value() const { assert(is_value_); return value_; } ValueType& value() { assert(is_value_); return value_; } // Move only value or fallback ValueType&& value(ValueType&& fallback) { if (is_value()) { return std::move(value()); } return std::forward<ValueType>(fallback); } // Copy only value or fallback ValueType value(ValueType fallback) const { if (is_value()) { return value(); } return std::move(fallback); } private: // Only one of these is an active member, determined by |is_value_|. Since // they are union'ed, they must be explicitly constructed and destroyed. union { ValueType value_; Error error_; }; // If true, |value_| is initialized and active. Otherwise, |error_| is // initialized and active. const bool is_value_; }; // Define comparison operators using SFINAE. template <typename ValueType> bool operator<(const ErrorOr<ValueType>& lhs, const ErrorOr<ValueType>& rhs) { // Handle the cases where one side is an error. if (lhs.is_error() != rhs.is_error()) { return lhs.is_error(); } // Handle the case where both sides are errors. if (lhs.is_error()) { return static_cast<int8_t>(lhs.error().code()) < static_cast<int8_t>(rhs.error().code()); } // Handle the case where both are values. return lhs.value() < rhs.value(); } template <typename ValueType> bool operator>(const ErrorOr<ValueType>& lhs, const ErrorOr<ValueType>& rhs) { return rhs < lhs; } template <typename ValueType> bool operator<=(const ErrorOr<ValueType>& lhs, const ErrorOr<ValueType>& rhs) { return !(lhs > rhs); } template <typename ValueType> bool operator>=(const ErrorOr<ValueType>& lhs, const ErrorOr<ValueType>& rhs) { return !(rhs < lhs); } template <typename ValueType> bool operator==(const ErrorOr<ValueType>& lhs, const ErrorOr<ValueType>& rhs) { // Handle the cases where one side is an error. if (lhs.is_error() != rhs.is_error()) { return false; } // Handle the case where both sides are errors. if (lhs.is_error()) { return lhs.error() == rhs.error(); } // Handle the case where both are values. return lhs.value() == rhs.value(); } template <typename ValueType> bool operator!=(const ErrorOr<ValueType>& lhs, const ErrorOr<ValueType>& rhs) { return !(lhs == rhs); } } // namespace openscreen #endif // PLATFORM_BASE_ERROR_H_