blob: 5e8c20f3d7ed84733f8f21b7c81600af38ab6041 [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/llcpp/client_base.h>
#include <lib/fidl/trace.h>
#include <lib/fidl/txn_header.h>
#include <lib/fit/function.h>
#include <stdio.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_ = binding;
client_object_lifetime_ = std::move(client_object_lifetime);
dispatcher_ = dispatcher;
event_dispatcher_ = std::move(event_dispatcher);
binding->BeginFirstWait();
}
void ClientBase::AsyncTeardown() {
if (auto binding = binding_.lock())
binding->StartTeardown(std::move(binding));
}
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);
// Depending on what kind of error caused teardown, we may want to propgate
// the error to all other outstanding contexts.
switch (info.reason()) {
case fidl::Reason::kClose:
// |kClose| is never used on the client side.
__builtin_abort();
break;
case fidl::Reason::kUnbind:
// The user explicitly initiated teardown.
case fidl::Reason::kEncodeError:
case fidl::Reason::kDecodeError:
// These errors are specific to one call, whose corresponding context
// would have been notified during |Dispatch| or making the call.
context->OnError(fidl::Status::Unbound());
break;
case fidl::Reason::kPeerClosed:
case fidl::Reason::kDispatcherError:
case fidl::Reason::kTransportError:
case fidl::Reason::kUnexpectedMessage:
// These errors apply to all calls.
context->OnError(info.ToError());
break;
default:
// Should not reach here, but there is no compile-time approach to
// guarantee it.
ZX_PANIC("Unknown reason %d", static_cast<int>(info.reason()));
}
}
}
void ClientBase::SendTwoWay(fidl::OutgoingMessage& message, ResponseContext* context,
fidl::WriteOptions write_options) {
if (auto transport = GetTransport()) {
PrepareAsyncTxn(context);
message.set_txid(context->Txid());
message.Write(*transport, std::move(write_options));
if (!message.ok()) {
ForgetAsyncTxn(context);
TryAsyncDeliverError(message.error(), context);
HandleSendError(message.error());
}
return;
}
TryAsyncDeliverError(fidl::Status::Unbound(), context);
}
fidl::Status ClientBase::SendOneWay(::fidl::OutgoingMessage& message,
fidl::WriteOptions write_options) {
if (auto transport = GetTransport()) {
message.set_txid(0);
message.Write(*transport, std::move(write_options));
if (!message.ok()) {
HandleSendError(message.error());
return message.error();
}
return fidl::Status::Ok();
}
return fidl::Status::Unbound();
}
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);
}
}
std::optional<UnbindInfo> ClientBase::Dispatch(fidl::IncomingMessage& msg,
internal::MessageStorageViewBase* storage_view) {
if (fit::nullable 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).
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() {
ZX_ASSERT(client_impl_);
control_.reset();
client_impl_->ClientBase::AsyncTeardown();
}
} // namespace internal
} // namespace fidl