// Copyright 2018 The Chromium Authors // 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 #include #include #include 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 // NOTE: new values should be added to the end of the of enum and existing // values should not be changed. 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 = 2, kCborIncompleteMessage = 3, kCborInvalidResponseId = 4, kCborInvalidMessage = 5, // Presentation start errors. kNoAvailableReceivers = 6, kRequestCancelled = 7, kNoPresentationFound = 8, kPreviousStartInProgress = 9, kUnknownStartError = 10, kUnknownRequestId = 11, kAddressInUse = 12, kDomainNameTooLong = 13, kDomainNameLabelTooLong = 14, kIOFailure = 15, kInitializationFailure = 16, kInvalidIPV4Address = 17, kInvalidIPV6Address = 18, kConnectionFailed = 19, kSocketOptionSettingFailure = 20, kSocketAcceptFailure = 21, kSocketBindFailure = 22, kSocketClosedFailure = 23, kSocketConnectFailure = 24, kSocketInvalidState = 25, kSocketListenFailure = 26, kSocketReadFailure = 27, kSocketSendFailure = 28, // MDNS errors. kMdnsRegisterFailure = 29, kMdnsReadFailure = 30, kMdnsNonConformingFailure = 31, kParseError = 32, kUnknownMessageType = 33, kNoActiveConnection = 34, kAlreadyClosed = 35, kInvalidConnectionState = 36, kNoStartedPresentation = 37, kPresentationAlreadyStarted = 38, kJsonParseError = 39, kJsonWriteError = 40, // OpenSSL errors. // Was unable to generate an RSA key. kRSAKeyGenerationFailure = 41, kRSAKeyParseError = 42, // Was unable to initialize an EVP_PKEY type. kEVPInitializationError = 43, // Was unable to generate a certificate. kCertificateCreationError = 44, // Certificate failed validation. kCertificateValidationError = 45, // Failed to produce a hashing digest. kSha256HashFailure = 46, // A non-recoverable SSL library error has occurred. kFatalSSLError = 47, kFileLoadFailure = 48, // Cast certificate errors. // Certificates were not provided for verification. kErrCertsMissing = 49, // The certificates provided could not be parsed. kErrCertsParse = 50, // Key usage is missing or is not set to Digital Signature. // This error could also be thrown if the CN is missing. kErrCertsRestrictions = 51, // The current date is before the notBefore date or after the notAfter date. kErrCertsDateInvalid = 52, // The certificate failed to chain to a trusted root. kErrCertsVerifyGeneric = 53, // The certificate was not found in the trust store. kErrCertsVerifyUntrustedCert = 54, // The CRL is missing or failed to verify. kErrCrlInvalid = 55, // One of the certificates in the chain is revoked. kErrCertsRevoked = 56, // The pathlen constraint of the root certificate was exceeded. kErrCertsPathlen = 57, // The certificate provided could not be serialized. kErrCertSerialize = 58, // Cast authentication errors. kCastV2PeerCertEmpty = 59, kCastV2WrongPayloadType = 60, kCastV2NoPayload = 61, kCastV2PayloadParsingFailed = 62, kCastV2MessageError = 63, kCastV2NoResponse = 64, kCastV2FingerprintNotFound = 65, kCastV2CertNotSignedByTrustedCa = 66, kCastV2CannotExtractPublicKey = 67, kCastV2SignedBlobsMismatch = 68, kCastV2TlsCertValidityPeriodTooLong = 69, kCastV2TlsCertValidStartDateInFuture = 70, kCastV2TlsCertExpired = 71, kCastV2SenderNonceMismatch = 72, kCastV2DigestUnsupported = 73, kCastV2SignatureEmpty = 74, // Cast channel errors. kCastV2ChannelNotOpen = 75, kCastV2AuthenticationError = 76, kCastV2ConnectError = 77, kCastV2CastSocketError = 78, kCastV2TransportError = 79, kCastV2InvalidMessage = 80, kCastV2InvalidChannelId = 81, kCastV2ConnectTimeout = 82, kCastV2PingTimeout = 83, kCastV2ChannelPolicyMismatch = 84, kCreateSignatureFailed = 85, // Discovery errors. kUpdateReceivedRecordFailure = 86, kRecordPublicationError = 87, kProcessReceivedRecordFailure = 88, // Generic errors. kUnknownError = 89, kNotImplemented = 90, kInsufficientBuffer = 91, kParameterInvalid = 92, kParameterOutOfRange = 93, kParameterNullPointer = 94, kIndexOutOfBounds = 95, kItemAlreadyExists = 96, kItemNotFound = 97, kOperationInvalid = 98, kOperationInProgress = 99, kOperationCancelled = 100, kInterrupted = 101, // Cast streaming errors. kUnknownCodec = 102, kInvalidCodecParameter = 103, kSocketFailure = 104, kUnencryptedOffer = 105, kRemotingNotSupported = 106, kNoStreamSelected = 107, // An Answer timeout means that the receiver failed to reply to our Offer // within a reasonable amount of time. kAnswerTimeout = 108, // Received an ANSWER, but it was invalid. kInvalidAnswer = 109, // A generic message timeout occured. kMessageTimeout = 110, }; 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::string ToString(openscreen::Error::Code code); 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 Foo::DoSomething() { // if (success) { // return Bar(); // } else { // return Error(kBadThingHappened, "No can do"); // } // } // // TODO(mfoltz): Add support for type conversions. template class ErrorOr { public: static ErrorOr None() { static ErrorOr 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(); new (this) ErrorOr(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 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(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 bool operator<(const ErrorOr& lhs, const ErrorOr& 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(lhs.error().code()) < static_cast(rhs.error().code()); } // Handle the case where both are values. return lhs.value() < rhs.value(); } template bool operator>(const ErrorOr& lhs, const ErrorOr& rhs) { return rhs < lhs; } template bool operator<=(const ErrorOr& lhs, const ErrorOr& rhs) { return !(lhs > rhs); } template bool operator>=(const ErrorOr& lhs, const ErrorOr& rhs) { return !(rhs < lhs); } template bool operator==(const ErrorOr& lhs, const ErrorOr& 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 bool operator!=(const ErrorOr& lhs, const ErrorOr& rhs) { return !(lhs == rhs); } } // namespace openscreen #endif // PLATFORM_BASE_ERROR_H_