blob: 0c08359c19c35a18b4378bc00fa24fb41bbc4ac4 [file] [log] [blame]
// Copyright 2020 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/client_base.h>
#include <lib/fidl/txn_header.h>
#include <lib/fit/function.h>
namespace fidl {
namespace internal {
// TODO(madhaviyengar): Move this constant to zircon/fidl.h
constexpr uint32_t kUserspaceTxidMask = 0x7FFFFFFF;
std::shared_ptr<ClientBase> ClientBase::Create(
AnyTransport&& transport, async_dispatcher_t* dispatcher,
AnyIncomingEventDispatcher&& event_dispatcher, AsyncEventHandler* error_handler,
fidl::AnyTeardownObserver&& teardown_observer, ThreadingPolicy threading_policy,
std::weak_ptr<ClientControlBlock> client_object_lifetime) {
std::shared_ptr client_base = std::make_shared<ClientBase>();
client_base->Bind(std::move(transport), dispatcher, std::move(event_dispatcher), error_handler,
std::move(teardown_observer), threading_policy,
std::move(client_object_lifetime));
return client_base;
}
void ClientBase::Bind(AnyTransport&& transport, async_dispatcher_t* dispatcher,
AnyIncomingEventDispatcher&& event_dispatcher,
AsyncEventHandler* error_handler, AnyTeardownObserver&& teardown_observer,
ThreadingPolicy threading_policy,
std::weak_ptr<ClientControlBlock> client_object_lifetime) {
ZX_DEBUG_ASSERT(!binding_.lock());
auto binding = AsyncClientBinding::Create(
dispatcher, std::make_shared<fidl::internal::AnyTransport>(std::move(transport)),
shared_from_this(), error_handler, std::move(teardown_observer), threading_policy);
binding_ = WeakBindingRef<AsyncClientBinding>{binding, binding->shared_unbind_info()};
client_object_lifetime_ = std::move(client_object_lifetime);
dispatcher_ = dispatcher;
event_dispatcher_ = std::move(event_dispatcher);
binding->BeginFirstWait();
}
ClientBase::AsyncTeardownResult ClientBase::AsyncTeardown() {
auto matchers = MatchVariant{
[this](std::shared_ptr<AsyncClientBinding>&& binding) -> AsyncTeardownResult {
fit::result teardown_result = binding->StartTeardown(std::move(binding));
if (teardown_result.is_error()) {
// Already torn down or raced with in-progress teardown.
return fit::error(fidl::Status::Unbound());
}
// If there are pending two-way calls, do not allow the user to extract
// the client endpoint.
std::scoped_lock lock(lock_);
if (!contexts_.is_empty()) {
return fit::error(fidl::Status::PendingTwoWayCallPreventsUnbind());
}
return teardown_result.take_value();
},
[](fidl::UnbindInfo info) -> AsyncTeardownResult { return fit::error(info.ToError()); },
[](WeakClientBindingRef::Invalid) -> AsyncTeardownResult {
// Should not reach here if we check for a valid client before
// user initiated teardown.
__builtin_abort();
},
};
return std::visit(matchers, binding_.lock_or_error());
}
void ClientBase::PrepareAsyncTxn(ResponseContext* context) {
std::scoped_lock lock(lock_);
// Generate the next txid. Verify that it doesn't overlap with any outstanding txids.
do {
do {
context->txid_ = ++txid_base_ & kUserspaceTxidMask; // txid must be within mask.
} while (unlikely(!context->txid_)); // txid must be non-zero.
} while (unlikely(!contexts_.insert_or_find(context)));
list_add_tail(&delete_list_, context);
}
void ClientBase::ForgetAsyncTxn(ResponseContext* context) {
std::scoped_lock lock(lock_);
ZX_ASSERT(context->InContainer());
contexts_.erase(*context);
list_delete(static_cast<list_node_t*>(context));
}
void ClientBase::ReleaseResponseContexts(fidl::UnbindInfo info) {
// Release ownership on any outstanding |ResponseContext|s outside of locks.
list_node_t delete_list;
{
std::scoped_lock lock(lock_);
contexts_.clear();
list_move(&delete_list_, &delete_list);
}
list_node_t* node = nullptr;
list_node_t* temp_node = nullptr;
list_for_every_safe(&delete_list, node, temp_node) {
list_delete(node);
auto* context = static_cast<ResponseContext*>(node);
// |kClose| is never used on the client side.
ZX_DEBUG_ASSERT(info.reason() != fidl::Reason::kClose);
context->OnError(ErrorFromUnbindInfo(info));
}
}
void ClientBase::SendTwoWay(fidl::OutgoingMessage& message, ResponseContext* context,
fidl::WriteOptions write_options) {
auto matchers = MatchVariant{
[&](std::shared_ptr<AnyTransport>&& transport) {
{
std::shared_ptr scoped_transport = std::move(transport);
PrepareAsyncTxn(context);
message.set_txid(context->Txid());
message.Write(*scoped_transport, std::move(write_options));
}
// Handle error without holding onto the transport.
if (!message.ok()) {
ForgetAsyncTxn(context);
TryAsyncDeliverError(message.error(), context);
HandleSendError(message.error());
}
},
[&](fidl::UnbindInfo info) { TryAsyncDeliverError(ErrorFromUnbindInfo(info), context); },
};
std::visit(matchers, GetTransportOrError());
}
fidl::OneWayStatus ClientBase::SendOneWay(fidl::OutgoingMessage& message,
fidl::WriteOptions write_options) {
auto matchers = MatchVariant{
[&](std::shared_ptr<AnyTransport>&& transport) {
{
std::shared_ptr scoped_transport = std::move(transport);
message.set_txid(0);
message.Write(*scoped_transport, std::move(write_options));
}
// Handle error without holding onto the transport.
if (!message.ok()) {
HandleSendError(message.error());
return fidl::OneWayStatus{message.error()};
}
return fidl::OneWayStatus{fidl::Status::Ok()};
},
[](fidl::UnbindInfo info) { return OneWayErrorFromUnbindInfo(info); },
};
return std::visit(matchers, GetTransportOrError());
}
void ClientBase::HandleSendError(fidl::Status error) {
if (auto binding = binding_.lock()) {
binding->HandleError(std::move(binding), {UnbindInfo{error}, ErrorOrigin::kSend});
}
}
void ClientBase::TryAsyncDeliverError(::fidl::Status error, ResponseContext* context) {
zx_status_t status = context->TryAsyncDeliverError(error, dispatcher_);
if (status != ZX_OK) {
context->OnError(error);
}
}
ClientBase::TransportOrError ClientBase::GetTransportOrError() {
auto matchers = MatchVariant{
[](const std::shared_ptr<AsyncClientBinding>& binding) -> TransportOrError {
if (auto transport = binding->GetTransport(); transport) {
return transport;
}
// If the transport is gone, that means the user explicitly requested unbinding.
return fidl::UnbindInfo::Unbind();
},
[](fidl::UnbindInfo info) -> TransportOrError { return info; },
[](WeakClientBindingRef::Invalid) -> TransportOrError {
// Should not reach here if we check for a valid client before
// making calls.
__builtin_abort();
},
};
return std::visit(matchers, binding_.lock_or_error());
}
std::optional<UnbindInfo> ClientBase::Dispatch(fidl::IncomingHeaderAndMessage& msg,
internal::MessageStorageViewBase* storage_view) {
if (fidl_epitaph_t* epitaph = msg.maybe_epitaph(); unlikely(epitaph)) {
return UnbindInfo::PeerClosed(epitaph->error);
}
auto* hdr = msg.header();
if (hdr->txid == 0) {
// Dispatch events (received messages with no txid).
// Dispatch will always consume the message even if it is not recognized
// (unknown interaction), so it is important to not reference the message or
// header again after calling this.
fidl::Status status = event_dispatcher_->DispatchEvent(msg, storage_view);
if (status.ok()) {
return std::nullopt;
}
return fidl::UnbindInfo{status};
}
// If this is a response, look up the corresponding ResponseContext based on the txid.
ResponseContext* context = nullptr;
{
std::scoped_lock lock(lock_);
context = contexts_.erase(hdr->txid);
if (likely(context != nullptr)) {
list_delete(static_cast<list_node_t*>(context));
} else {
// Received unknown txid.
return UnbindInfo{
Status::UnexpectedMessage(ZX_ERR_NOT_FOUND, fidl::internal::kErrorUnknownTxId)};
}
}
return context->OnRawResult(std::move(msg), storage_view);
}
void ClientController::Bind(AnyTransport client_end, async_dispatcher_t* dispatcher,
AnyIncomingEventDispatcher&& event_dispatcher,
AsyncEventHandler* error_handler,
AnyTeardownObserver&& teardown_observer,
ThreadingPolicy threading_policy) {
ZX_ASSERT(!client_impl_);
// This three step dance is to setup a circular reference where |ClientBase|
// weakly references |ClientControlBlock| and |ClientControlBlock| strongly
// references |ClientBase|.
control_ = std::make_shared<ClientControlBlock>(nullptr);
client_impl_ = ClientBase::Create(std::move(client_end), dispatcher, std::move(event_dispatcher),
error_handler, std::move(teardown_observer), threading_policy,
control_->weak_from_this());
// Actually fill in the |client_impl_|.
*control_ = ClientControlBlock{client_impl_};
}
void ClientController::Unbind() { (void)UnbindMaybeGetEndpoint(); }
fit::result<fidl::Error, fidl::internal::AnyTransport> ClientController::UnbindMaybeGetEndpoint() {
ZX_ASSERT(client_impl_);
// Destroy |control| after |AsyncTeardown|.
std::shared_ptr control = std::move(control_);
return client_impl_->ClientBase::AsyncTeardown();
}
} // namespace internal
} // namespace fidl