| // 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. |
| |
| #ifndef SRC_DEVELOPER_FORENSICS_UTILS_FIDL_ONESHOT_PTR_H_ |
| #define SRC_DEVELOPER_FORENSICS_UTILS_FIDL_ONESHOT_PTR_H_ |
| |
| #include <lib/async/dispatcher.h> |
| #include <lib/fidl/cpp/interface_ptr.h> |
| #include <lib/fit/function.h> |
| #include <lib/fpromise/promise.h> |
| #include <lib/sys/cpp/service_directory.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include "src/developer/forensics/utils/errors.h" |
| #include "src/developer/forensics/utils/fit/bridge.h" |
| #include "src/developer/forensics/utils/fit/timeout.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace forensics { |
| namespace fidl { |
| |
| // Wrapper around InterfacePtr<Interface> that provides 'one shot' to make a call to the interface. |
| // |
| // For example, if we wished to fetch a device's update channel from |
| // fuchsia::update::channel::Provider then we would use OneShotPtr as follows: |
| // |
| // ::fpromise::promise<std::string> GetChannel() { |
| // OneShotPtr<fuchsia::update::channel::Provider, std::string> channel_ptr(dispatcher, |
| // services); |
| // |
| // channel_ptr->GetCurrent([&](std::string channel) { |
| // if (channel_ptr.IsAlreadyDone()) { |
| // return; |
| // } |
| // |
| // channel_ptr.CompleteOk(channel); |
| // }); |
| // |
| // return channel_ptr.WaitForDone() |
| // } |
| // |
| // Any additional attempts to dereference |channel_ptr| in GetChannel() would cause the program to |
| // check-fail. |
| template <typename Interface, typename V = void> |
| class OneShotPtr { |
| public: |
| OneShotPtr(async_dispatcher_t* dispatcher, std::shared_ptr<sys::ServiceDirectory> services, |
| std::string name = Interface::Name_) |
| : services_(services), |
| bridge_(dispatcher, fxl::StringPrintf("call on %s", name.c_str())), |
| name_(std::move(name)) {} |
| |
| bool IsAlreadyDone() const { return bridge_.IsAlreadyDone(); } |
| |
| template <typename VV = V, typename = std::enable_if_t<std::is_void_v<VV>>> |
| void CompleteOk() { |
| bridge_.CompleteOk(); |
| } |
| template <typename VV = V, typename = std::enable_if_t<!std::is_void_v<VV>>> |
| void CompleteOk(VV value) { |
| bridge_.CompleteOk(std::move(value)); |
| } |
| |
| void CompleteError(Error error) { bridge_.CompleteError(error); } |
| |
| ::fpromise::promise<V, Error> WaitForDone() { return bridge_.WaitForDone(); } |
| |
| ::fpromise::promise<V, Error> WaitForDone(fit::Timeout timeout) { |
| return bridge_.WaitForDone(std::move(timeout)); |
| } |
| |
| Interface* operator->() { |
| // We use FXL_ and not FX_ so the messages goes to stderr and can be intercepted by |
| // ASSERT_DEATH. |
| FX_CHECK(!oneshot_used_) << fxl::StringPrintf( |
| "You've only got one shot to use -> on a OneShotPtr (%s)", name_.c_str()); |
| oneshot_used_ = true; |
| |
| connection_ = services_->Connect<Interface>(name_); |
| |
| connection_.set_error_handler([this](zx_status_t status) { |
| if (bridge_.IsAlreadyDone()) { |
| return; |
| } |
| |
| FX_PLOGS(WARNING, status) << "Lost connection to " << name_; |
| |
| bridge_.CompleteError(Error::kConnectionError); |
| }); |
| |
| return connection_.get(); |
| } |
| |
| protected: |
| ::fidl::InterfacePtr<Interface> connection_; |
| |
| private: |
| const std::shared_ptr<sys::ServiceDirectory> services_; |
| fit::Bridge<V> bridge_; |
| std::string name_; |
| |
| bool oneshot_used_ = false; |
| }; |
| |
| } // namespace fidl |
| } // namespace forensics |
| |
| #endif // SRC_DEVELOPER_FORENSICS_UTILS_FIDL_ONESHOT_PTR_H_ |