| // 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 |