blob: 61a7293638a6da1481347b7b8f14a57cd07e1ee4 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/fidl/cpp/wire/status.h>
#include <lib/fit/nullable.h>
#include <zircon/compiler.h>
#include <array>
#include <iostream>
#include <string>
#include <string_view>
namespace fidl {
namespace internal {
const char* const kErrorInvalidHeader = "invalid header";
const char* const kErrorUnknownTxId = "unknown txid";
const char* const kErrorUnknownOrdinal = "unknown ordinal";
const char* const kErrorTransport = "underlying transport I/O error";
const char* const kErrorChannelUnbound = "unbound endpoint";
const char* const kErrorWaitOneFailed = "failed waiting to read from endpoint";
const char* const kErrorSyncEventBufferTooSmall =
"received a larger message than allowed by the events";
const char* const kErrorSyncEventUnhandledTransitionalEvent = "unhandled transitional event";
const char* const kCallerAllocatedBufferTooSmall =
"buffer provided to caller-allocating flavor is too small";
const char* const kUnknownMethod = "server did not recognize this method";
const char* const kUnsupportedFrameworkError =
"server sent a framework_err value that is not supported";
bool IsFatalErrorUniversal(fidl::Reason error) {
switch (error) {
case Reason::__DoNotUse:
case Reason::kCanceledDueToOtherError:
case Reason::kUnknownMethod:
case Reason::kPendingTwoWayCallPreventsUnbind:
// These are not concrete errors.
__builtin_abort();
case Reason::kUnbind:
case Reason::kClose:
case Reason::kPeerClosedWhileReading:
case Reason::kDispatcherError:
case Reason::kTransportError:
return true;
case Reason::kUnexpectedMessage:
case Reason::kEncodeError:
case Reason::kDecodeError:
case Reason::kAbandonedAsyncReply:
return false;
}
}
fidl::Status ErrorFromUnbindInfo(fidl::UnbindInfo info) {
// Depending on what kind of error caused teardown, we may want to propagate
// the error to all other outstanding contexts.
if (IsFatalErrorUniversal(info.reason())) {
return info.ToError();
}
// These errors are specific to one call, whose corresponding context
// would have been notified during |Dispatch| or making the call.
return fidl::Status::Canceled(info);
}
fidl::OneWayStatus OneWayErrorFromUnbindInfo(fidl::UnbindInfo info) {
fidl::Status error = ErrorFromUnbindInfo(info);
if (error.is_peer_closed()) {
return fidl::OneWayStatus{fidl::Status::Ok()};
}
return fidl::OneWayStatus{error};
}
} // namespace internal
namespace {
constexpr const char* DescribeReason(Reason reason) {
// The error descriptions are quite terse to save binary size.
switch (reason) {
case internal::kUninitializedReason:
return nullptr;
case Reason::kUnbind:
return "user initiated unbind";
case Reason::kClose:
return "(server) user initiated close with epitaph";
case Reason::kPendingTwoWayCallPreventsUnbind:
return "pending two-way calls renders unbinding unsafe";
case Reason::kCanceledDueToOtherError:
return "being canceled by failure from another operation: ";
case Reason::kPeerClosedWhileReading:
return "peer closed";
case Reason::kDispatcherError:
return "dispatcher error";
case Reason::kTransportError:
return internal::kErrorTransport;
case Reason::kEncodeError:
return "encode error";
case Reason::kDecodeError:
return "decode error";
case Reason::kUnexpectedMessage:
return "unexpected message";
case Reason::kUnknownMethod:
return "unknown interaction";
case Reason::kAbandonedAsyncReply:
return "(server) async completer is discarded without a reply";
}
}
// A buffer of 256 bytes is sufficient for all tested results.
// If the description exceeds this length at runtime,
// the output will be truncated.
// We can increase the size if necessary.
using StatusFormattingBuffer = std::array<char, 256>;
} // namespace
Status Status::Canceled(UnbindInfo cause) {
Status status = cause.ToError();
// Universal errors should be directly passed instead of tucked under a nested error.
ZX_DEBUG_ASSERT(!internal::IsFatalErrorUniversal(status.reason_));
return Status(ZX_ERR_CANCELED, Reason::kCanceledDueToOtherError, status.reason_, status.error_);
}
[[nodiscard]] std::string Status::FormatDescription() const {
StatusFormattingBuffer buf;
size_t length = FormatImpl(&*buf.begin(), sizeof(buf), /* from_unbind_info */ false);
return std::string(&*buf.begin(), length);
}
[[nodiscard]] const char* Status::lossy_description() const {
// If an error string was explicitly specified, use that.
if (error_) {
return error_;
}
// Otherwise, derive an error from |reason_|.
return reason_description();
}
[[nodiscard]] const char* Status::reason_description() const { return DescribeReason(reason_); }
[[nodiscard]] const char* Status::underlying_reason_description() const {
ZX_DEBUG_ASSERT(underlying_reason_ != Reason::kCanceledDueToOtherError);
return DescribeReason(underlying_reason_);
}
size_t Status::FormatImpl(char* destination, size_t length, bool from_unbind_info) const {
ZX_ASSERT(length > 0);
int num_would_write = 0;
// We use |snprintf| since it minimizes allocation and is faster
// than output streams.
if (!from_unbind_info && status_ == ZX_OK && reason_ == internal::kUninitializedReason) {
num_would_write = snprintf(destination, length, "FIDL success");
} else {
const char* prelude = from_unbind_info ? "FIDL endpoint was unbound" : "FIDL operation failed";
const char* status_meaning = [&] {
switch (reason_) {
// This reason may only appear in an |UnbindInfo|.
case Reason::kClose:
ZX_DEBUG_ASSERT(from_unbind_info);
return "status of sending epitaph";
case Reason::kPeerClosedWhileReading:
if (status_ != ZX_ERR_PEER_CLOSED) {
return "epitaph";
}
break;
default:
break;
}
return "status";
}();
const char* detail_prefix = error_ ? ", detail: " : "";
const char* detail = error_ ? error_ : "";
const char* underlying_description = underlying_reason_description();
if (!underlying_description) {
underlying_description = "";
}
#ifdef __Fuchsia__
num_would_write = snprintf(destination, length, "%s due to %s%s, %s: %s (%d)%s%s", prelude,
reason_description(), underlying_description, status_meaning,
zx_status_get_string(status_), status_, detail_prefix, detail);
#else
num_would_write =
snprintf(destination, length, "%s due to %s%s, %s: %d%s%s", prelude, reason_description(),
underlying_description, status_meaning, status_, detail_prefix, detail);
#endif // __Fuchsia__
}
ZX_ASSERT(num_would_write > 0);
return static_cast<size_t>(num_would_write) >= length ? length - 1 : num_would_write;
}
std::ostream& operator<<(std::ostream& ostream, const Status& result) {
StatusFormattingBuffer buf;
size_t length = result.FormatImpl(&*buf.begin(), sizeof(buf), /* from_unbind_info */ false);
ostream << std::string_view(&*buf.begin(), length);
return ostream;
}
[[nodiscard]] std::string UnbindInfo::FormatDescription() const {
StatusFormattingBuffer buf;
size_t length = FormatImpl(&*buf.begin(), sizeof(buf), /* from_unbind_info */ true);
return std::string(&*buf.begin(), length);
}
std::ostream& operator<<(std::ostream& ostream, const UnbindInfo& info) {
StatusFormattingBuffer buf;
size_t length = info.Status::FormatImpl(&*buf.begin(), sizeof(buf), /* from_unbind_info */ true);
ostream << std::string_view(&*buf.begin(), length);
return ostream;
}
size_t fidl::internal::DisplayError<fidl::Status>::Format(const fidl::Status& value,
char* destination, size_t capacity) {
return value.FormatImpl(destination, capacity, false);
}
} // namespace fidl