|  | // Copyright 2019 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/async/time.h> | 
|  | #include <lib/fidl/epitaph.h> | 
|  | #include <lib/fidl/llcpp/async_binding.h> | 
|  | #include <lib/fidl/llcpp/async_transaction.h> | 
|  | #include <lib/fidl/llcpp/client_base.h> | 
|  | #include <lib/fidl/trace.h> | 
|  | #include <zircon/assert.h> | 
|  | #include <zircon/syscalls.h> | 
|  |  | 
|  | #include <type_traits> | 
|  |  | 
|  | namespace fidl { | 
|  | namespace internal { | 
|  |  | 
|  | AsyncBinding::AsyncBinding(async_dispatcher_t* dispatcher, zx::unowned_channel channel) | 
|  | : async_wait_t({{ASYNC_STATE_INIT}, | 
|  | &AsyncBinding::OnMessage, | 
|  | channel->get(), | 
|  | ZX_CHANNEL_PEER_CLOSED | ZX_CHANNEL_READABLE, | 
|  | 0}), | 
|  | dispatcher_(dispatcher) { | 
|  | ZX_ASSERT(dispatcher_); | 
|  | ZX_ASSERT(handle() != ZX_HANDLE_INVALID); | 
|  | } | 
|  |  | 
|  | AsyncBinding::~AsyncBinding() { | 
|  | if (on_delete_) | 
|  | sync_completion_signal(on_delete_); | 
|  | } | 
|  |  | 
|  | void AsyncBinding::OnUnbind(std::shared_ptr<AsyncBinding>&& calling_ref, UnbindInfo info, | 
|  | bool is_unbind_task) { | 
|  | ZX_DEBUG_ASSERT(calling_ref.get() == this); | 
|  | auto binding = std::move(calling_ref);  // Move calling_ref into this scope. | 
|  |  | 
|  | { | 
|  | std::scoped_lock lock(lock_); | 
|  |  | 
|  | // Only one thread should wait for unbind. | 
|  | if (sync_unbind_) | 
|  | return; | 
|  | if (is_unbind_task) { | 
|  | // If the async_cancel_wait() in UnbindInternal() failed, another dispatcher thread has | 
|  | // access to keep_alive_ and may already be waiting on other references to be released. | 
|  | if (!canceled_) | 
|  | return; | 
|  | // No other thread will touch the internal reference. | 
|  | keep_alive_ = nullptr; | 
|  | } | 
|  | unbind_ = sync_unbind_ = true; | 
|  |  | 
|  | // If the peer was not closed, and the user invoked Close() or there was a dispatch error, | 
|  | // overwrite the unbound reason and recover the epitaph or error status. Note that | 
|  | // UnbindInfo::kUnbind is simply the default value for unbind_info_.reason. | 
|  | if (info.reason != UnbindInfo::kPeerClosed && unbind_info_.reason != UnbindInfo::kUnbind) { | 
|  | info = unbind_info_; | 
|  | } | 
|  | } | 
|  |  | 
|  | FinishUnbind(std::move(binding), info); | 
|  | } | 
|  |  | 
|  | void AsyncBinding::MessageHandler(zx_status_t status, const zx_packet_signal_t* signal) { | 
|  | ZX_ASSERT(keep_alive_); | 
|  |  | 
|  | if (status != ZX_OK) | 
|  | return OnUnbind(std::move(keep_alive_), {UnbindInfo::kDispatcherError, status}); | 
|  |  | 
|  | if (signal->observed & ZX_CHANNEL_READABLE) { | 
|  | char bytes[ZX_CHANNEL_MAX_MSG_BYTES]; | 
|  | zx_handle_info_t handles[ZX_CHANNEL_MAX_MSG_HANDLES]; | 
|  | for (uint64_t i = 0; i < signal->count; i++) { | 
|  | fidl_incoming_msg_t msg = { | 
|  | .bytes = bytes, | 
|  | .handles = handles, | 
|  | .num_bytes = 0u, | 
|  | .num_handles = 0u, | 
|  | }; | 
|  | fidl_trace(WillLLCPPAsyncChannelRead); | 
|  | status = zx_channel_read_etc(handle(), 0, bytes, handles, ZX_CHANNEL_MAX_MSG_BYTES, | 
|  | ZX_CHANNEL_MAX_MSG_HANDLES, &msg.num_bytes, &msg.num_handles); | 
|  | if (status != ZX_OK) | 
|  | return OnUnbind(std::move(keep_alive_), {UnbindInfo::kChannelError, status}); | 
|  |  | 
|  | // Do basic validation on the message. | 
|  | status = msg.num_bytes < sizeof(fidl_message_header_t) | 
|  | ? ZX_ERR_INVALID_ARGS | 
|  | : fidl_validate_txn_header(reinterpret_cast<fidl_message_header_t*>(msg.bytes)); | 
|  | if (status != ZX_OK) { | 
|  | FidlHandleInfoCloseMany(msg.handles, msg.num_handles); | 
|  | return OnUnbind(std::move(keep_alive_), {UnbindInfo::kUnexpectedMessage, status}); | 
|  | } | 
|  | fidl_trace(DidLLCPPAsyncChannelRead, nullptr /* type */, bytes, msg.num_bytes, | 
|  | msg.num_handles); | 
|  |  | 
|  | // Flag indicating whether this thread still has access to the binding. | 
|  | bool binding_released = false; | 
|  | // Dispatch the message. | 
|  | auto maybe_unbind = Dispatch(&msg, &binding_released); | 
|  |  | 
|  | // If binding_released is not set, keep_alive_ is still valid and this thread will continue to | 
|  | // read messages on this binding. | 
|  | if (binding_released) | 
|  | return; | 
|  | ZX_ASSERT(keep_alive_); | 
|  |  | 
|  | // If there was any error enabling dispatch or an unexpected message, destroy the binding. | 
|  | if (maybe_unbind) { | 
|  | if (maybe_unbind->status == ZX_ERR_PEER_CLOSED) | 
|  | maybe_unbind->reason = UnbindInfo::kPeerClosed; | 
|  | return OnUnbind(std::move(keep_alive_), *maybe_unbind); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Add the wait back to the dispatcher. | 
|  | // NOTE: If EnableNextDispatch() fails due to a dispatcher error, unbind_info_ will override the | 
|  | // arguments passed to OnUnbind(). | 
|  | if (EnableNextDispatch() != ZX_OK) | 
|  | OnUnbind(std::move(keep_alive_), {UnbindInfo::kUnbind, ZX_OK}); | 
|  | } else { | 
|  | ZX_ASSERT(signal->observed & ZX_CHANNEL_PEER_CLOSED); | 
|  | OnUnbind(std::move(keep_alive_), {UnbindInfo::kPeerClosed, ZX_ERR_PEER_CLOSED}); | 
|  | } | 
|  | } | 
|  |  | 
|  | zx_status_t AsyncBinding::BeginWait() { | 
|  | std::scoped_lock lock(lock_); | 
|  | ZX_ASSERT(!begun_); | 
|  | auto status = async_begin_wait(dispatcher_, this); | 
|  | // On error, release the internal reference so it can be destroyed. | 
|  | if (status != ZX_OK) { | 
|  | keep_alive_ = nullptr; | 
|  | return status; | 
|  | } | 
|  | begun_ = true; | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | zx_status_t AsyncBinding::EnableNextDispatch() { | 
|  | std::scoped_lock lock(lock_); | 
|  | if (unbind_) | 
|  | return ZX_ERR_CANCELED; | 
|  | auto status = async_begin_wait(dispatcher_, this); | 
|  | if (status != ZX_OK && unbind_info_.status == ZX_OK) | 
|  | unbind_info_ = {UnbindInfo::kDispatcherError, status}; | 
|  | return status; | 
|  | } | 
|  |  | 
|  | void AsyncBinding::UnbindInternal(std::shared_ptr<AsyncBinding>&& calling_ref, UnbindInfo info) { | 
|  | ZX_ASSERT(calling_ref); | 
|  | // Move the calling reference into this scope. | 
|  | auto binding = std::move(calling_ref); | 
|  |  | 
|  | std::scoped_lock lock(lock_); | 
|  | // Another thread has entered this critical section already via Unbind(), Close(), or | 
|  | // OnUnbind(). Release our reference and return to unblock that caller. | 
|  | if (unbind_) | 
|  | return; | 
|  | unbind_ = true;       // Indicate that waits should no longer be added to the dispatcher. | 
|  | unbind_info_ = info;  // Store the reason for unbinding. | 
|  |  | 
|  | // Attempt to add a task to unbind the channel. On failure, the dispatcher was shutdown, | 
|  | // and another thread will do the unbinding. | 
|  | auto* unbind_task = new UnbindTask{ | 
|  | .task = {{ASYNC_STATE_INIT}, &AsyncBinding::OnUnbindTask, async_now(dispatcher_)}, | 
|  | .binding = binding, | 
|  | }; | 
|  | if (async_post_task(dispatcher_, &unbind_task->task) != ZX_OK) { | 
|  | delete unbind_task; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Attempt to cancel the current wait. On failure, a dispatcher thread (possibly this thread) | 
|  | // will invoke OnUnbind() before returning to the dispatcher. | 
|  | canceled_ = async_cancel_wait(dispatcher_, this) == ZX_OK; | 
|  | } | 
|  |  | 
|  | std::shared_ptr<AsyncServerBinding> AsyncServerBinding::Create( | 
|  | async_dispatcher_t* dispatcher, zx::channel channel, void* impl, | 
|  | TypeErasedServerDispatchFn dispatch_fn, TypeErasedOnUnboundFn on_unbound_fn) { | 
|  | auto ret = std::shared_ptr<AsyncServerBinding>(new AsyncServerBinding( | 
|  | dispatcher, std::move(channel), impl, dispatch_fn, std::move(on_unbound_fn))); | 
|  | ret->keep_alive_ = ret;  // We keep the binding alive until somebody decides to close the channel. | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | AsyncServerBinding::AsyncServerBinding(async_dispatcher_t* dispatcher, zx::channel channel, | 
|  | void* impl, TypeErasedServerDispatchFn dispatch_fn, | 
|  | TypeErasedOnUnboundFn on_unbound_fn) | 
|  | : AsyncBinding(dispatcher, channel.borrow()), | 
|  | channel_(std::move(channel)), | 
|  | interface_(impl), | 
|  | dispatch_fn_(dispatch_fn), | 
|  | on_unbound_fn_(std::move(on_unbound_fn)) {} | 
|  |  | 
|  | std::optional<UnbindInfo> AsyncServerBinding::Dispatch(fidl_incoming_msg_t* msg, | 
|  | bool* binding_released) { | 
|  | auto* hdr = reinterpret_cast<fidl_message_header_t*>(msg->bytes); | 
|  | AsyncTransaction txn(hdr->txid, dispatch_fn_, binding_released); | 
|  | return txn.Dispatch(std::move(keep_alive_), msg); | 
|  | } | 
|  |  | 
|  | void AsyncServerBinding::FinishUnbind(std::shared_ptr<AsyncBinding>&& calling_ref, | 
|  | UnbindInfo info) { | 
|  | auto binding = std::move(calling_ref);  // Move binding into scope. | 
|  |  | 
|  | // Stash state required after deleting the binding. | 
|  | auto on_unbound_fn = std::move(on_unbound_fn_); | 
|  | auto* intf = interface_; | 
|  | auto channel = std::move(channel_); | 
|  |  | 
|  | sync_completion_t on_delete; | 
|  | on_delete_ = &on_delete; | 
|  | // Delete the calling reference. Wait for any transient references to be released. | 
|  | binding = nullptr; | 
|  | ZX_ASSERT(sync_completion_wait(&on_delete, ZX_TIME_INFINITE) == ZX_OK); | 
|  | // `this` is no longer valid. | 
|  |  | 
|  | // If required, send the epitaph. | 
|  | if (info.reason == UnbindInfo::kClose) | 
|  | info.status = fidl_epitaph_write(channel.get(), info.status); | 
|  |  | 
|  | // Execute the unbound hook if specified. | 
|  | if (on_unbound_fn) | 
|  | on_unbound_fn(intf, info, std::move(channel)); | 
|  | } | 
|  |  | 
|  | std::shared_ptr<AsyncClientBinding> AsyncClientBinding::Create(async_dispatcher_t* dispatcher, | 
|  | std::shared_ptr<ChannelRef> channel, | 
|  | std::shared_ptr<ClientBase> client, | 
|  | OnClientUnboundFn on_unbound_fn) { | 
|  | auto ret = std::shared_ptr<AsyncClientBinding>(new AsyncClientBinding( | 
|  | dispatcher, std::move(channel), std::move(client), std::move(on_unbound_fn))); | 
|  | ret->keep_alive_ = ret;  // Keep the binding alive until an unbind operation or channel error. | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | AsyncClientBinding::AsyncClientBinding(async_dispatcher_t* dispatcher, | 
|  | std::shared_ptr<ChannelRef> channel, | 
|  | std::shared_ptr<ClientBase> client, | 
|  | OnClientUnboundFn on_unbound_fn) | 
|  | : AsyncBinding(dispatcher, zx::unowned_channel(channel->handle())), | 
|  | channel_(std::move(channel)), | 
|  | client_(std::move(client)), | 
|  | on_unbound_fn_(std::move(on_unbound_fn)) {} | 
|  |  | 
|  | std::optional<UnbindInfo> AsyncClientBinding::Dispatch(fidl_incoming_msg_t* msg, bool*) { | 
|  | return client_->Dispatch(msg); | 
|  | } | 
|  |  | 
|  | void AsyncClientBinding::FinishUnbind(std::shared_ptr<AsyncBinding>&& calling_ref, | 
|  | UnbindInfo info) { | 
|  | auto binding = std::move(calling_ref);  // Move binding into scope. | 
|  |  | 
|  | // Stash state required after deleting the binding. | 
|  | auto on_unbound_fn = std::move(on_unbound_fn_); | 
|  | auto client = std::move(client_); | 
|  |  | 
|  | // Delete the calling reference. Transient references don't access the channel, so don't wait. | 
|  | binding = nullptr; | 
|  | // `this` is no longer valid. | 
|  |  | 
|  | // Outstanding async responses will no longer be received, so release the contexts. | 
|  | client->ReleaseResponseContextsWithError(); | 
|  | client = nullptr; | 
|  |  | 
|  | // Execute the unbound hook if specified. | 
|  | if (on_unbound_fn) | 
|  | on_unbound_fn(info); | 
|  | } | 
|  |  | 
|  | }  // namespace internal | 
|  | }  // namespace fidl |