chromium/base/fuchsia/scoped_service_binding.h

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_FUCHSIA_SCOPED_SERVICE_BINDING_H_
#define BASE_FUCHSIA_SCOPED_SERVICE_BINDING_H_

// TODO(crbug.com/42050587): Remove this include once the explicit
// async_get_default_dispatcher() is no longer needed.
#include <lib/async/default.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/fidl/cpp/interface_request.h>
#include <lib/fidl/cpp/wire/connect_service.h>
#include <lib/zx/channel.h>

#include <optional>
#include <string_view>
#include <utility>

#include "base/base_export.h"
#include "base/fuchsia/scoped_service_publisher.h"
#include "base/functional/callback.h"

namespace sys {
class OutgoingDirectory;
}  // namespace sys

namespace vfs {
class PseudoDir;
}  // namespace vfs

namespace base {

template <typename Interface>
class BASE_EXPORT ScopedServiceBinding {
 public:
  // Publishes a public service in the specified |outgoing_directory|.
  // |outgoing_directory| and |impl| must outlive the binding. The service is
  // unpublished on destruction.
  ScopedServiceBinding(sys::OutgoingDirectory* outgoing_directory,
                       Interface* impl,
                       std::string_view name = Interface::Name_)
      : publisher_(outgoing_directory, bindings_.GetHandler(impl), name) {}

  // Publishes a service in the specified |pseudo_dir|. |pseudo_dir| and |impl|
  // must outlive the binding. The service is unpublished on destruction.
  ScopedServiceBinding(vfs::PseudoDir* pseudo_dir,
                       Interface* impl,
                       std::string_view name = Interface::Name_)
      : publisher_(pseudo_dir, bindings_.GetHandler(impl), name) {}

  ScopedServiceBinding(const ScopedServiceBinding&) = delete;
  ScopedServiceBinding& operator=(const ScopedServiceBinding&) = delete;

  ~ScopedServiceBinding() = default;

  // |on_last_client_callback| will be called every time the number of connected
  // clients drops to 0.
  void SetOnLastClientCallback(base::RepeatingClosure on_last_client_callback) {
    bindings_.set_empty_set_handler(
        [callback = std::move(on_last_client_callback)] { callback.Run(); });
  }

  bool has_clients() const { return bindings_.size() != 0; }

 private:
  fidl::BindingSet<Interface> bindings_;
  ScopedServicePublisher<Interface> publisher_;
};

template <typename Protocol>
class BASE_EXPORT ScopedNaturalServiceBinding {
 public:
  // Publishes a public service in the specified |outgoing_directory|.
  // |outgoing_directory| and |impl| must outlive the binding. The service is
  // unpublished on destruction.
  ScopedNaturalServiceBinding(
      sys::OutgoingDirectory* outgoing_directory,
      fidl::Server<Protocol>* impl,
      std::string_view name = fidl::DiscoverableProtocolName<Protocol>)
      : publisher_(
            outgoing_directory,
            bindings_.CreateHandler(
                impl,
                // TODO(crbug.com/42050587): Remove this param once there's an
                // overload of `CreateHandler` that doesn't require it.
                async_get_default_dispatcher(),
                [](fidl::UnbindInfo info) {}),
            name) {}

  // Publishes a service in the specified |pseudo_dir|. |pseudo_dir| and |impl|
  // must outlive the binding. The service is unpublished on destruction.
  ScopedNaturalServiceBinding(
      vfs::PseudoDir* pseudo_dir,
      fidl::Server<Protocol>* impl,
      std::string_view name = fidl::DiscoverableProtocolName<Protocol>)
      : publisher_(
            pseudo_dir,
            bindings_.CreateHandler(
                impl,
                // TODO(crbug.com/42050587): Remove this param once there's an
                // overload of `CreateHandler` that doesn't require it.
                async_get_default_dispatcher(),
                [](fidl::UnbindInfo info) {}),
            name) {}

  ScopedNaturalServiceBinding(const ScopedNaturalServiceBinding&) = delete;
  ScopedNaturalServiceBinding& operator=(const ScopedNaturalServiceBinding&) =
      delete;

  ~ScopedNaturalServiceBinding() = default;

  // Registers `on_last_client_callback` to be called every time the number of
  // connected clients drops to 0.
  void SetOnLastClientCallback(base::RepeatingClosure on_last_client_callback) {
    bindings_.set_empty_set_handler(
        [callback = std::move(on_last_client_callback)] { callback.Run(); });
  }

  bool has_clients() const { return bindings_.size() != 0; }

 private:
  fidl::ServerBindingGroup<Protocol> bindings_;
  ScopedNaturalServicePublisher<Protocol> publisher_;
};

// Scoped service binding which allows only a single client to be connected
// at any time. By default a new connection will disconnect an existing client.
enum class ScopedServiceBindingPolicy {
  kPreferNew,
  kPreferExisting,
  kConnectOnce
};

template <typename Interface,
          ScopedServiceBindingPolicy Policy =
              ScopedServiceBindingPolicy::kPreferNew>
class BASE_EXPORT ScopedSingleClientServiceBinding {
 public:
  // |outgoing_directory| and |impl| must outlive the binding.
  ScopedSingleClientServiceBinding(sys::OutgoingDirectory* outgoing_directory,
                                   Interface* impl,
                                   std::string_view name = Interface::Name_)
      : binding_(impl) {
    publisher_.emplace(
        outgoing_directory,
        fit::bind_member(this, &ScopedSingleClientServiceBinding::BindClient),
        name);
    binding_.set_error_handler(fit::bind_member(
        this, &ScopedSingleClientServiceBinding::OnBindingEmpty));
  }

  ScopedSingleClientServiceBinding(vfs::PseudoDir* publish_to,
                                   Interface* impl,
                                   std::string_view name = Interface::Name_)
      : binding_(impl) {
    publisher_.emplace(
        publish_to,
        fit::bind_member(this, &ScopedSingleClientServiceBinding::BindClient),
        name);
    binding_.set_error_handler(fit::bind_member(
        this, &ScopedSingleClientServiceBinding::OnBindingEmpty));
  }

  ScopedSingleClientServiceBinding(const ScopedSingleClientServiceBinding&) =
      delete;
  ScopedSingleClientServiceBinding& operator=(
      const ScopedSingleClientServiceBinding&) = delete;

  ~ScopedSingleClientServiceBinding() = default;

  typename Interface::EventSender_& events() { return binding_.events(); }

  // |on_last_client_callback| will be called the first time a client
  // disconnects. It is still  possible for a client to connect after that point
  // if Policy is kPreferNew of kPreferExisting.
  void SetOnLastClientCallback(base::OnceClosure on_last_client_callback) {
    on_last_client_callback_ = std::move(on_last_client_callback);
  }

  bool has_clients() const { return binding_.is_bound(); }

 private:
  void BindClient(fidl::InterfaceRequest<Interface> request) {
    if (Policy == ScopedServiceBindingPolicy::kPreferExisting &&
        binding_.is_bound()) {
      return;
    }
    binding_.Bind(std::move(request));
    if (Policy == ScopedServiceBindingPolicy::kConnectOnce) {
      publisher_.reset();
    }
  }

  void OnBindingEmpty(zx_status_t status) {
    if (on_last_client_callback_) {
      std::move(on_last_client_callback_).Run();
    }
  }

  fidl::Binding<Interface> binding_;
  std::optional<ScopedServicePublisher<Interface>> publisher_;
  base::OnceClosure on_last_client_callback_;
};

}  // namespace base

#endif  // BASE_FUCHSIA_SCOPED_SERVICE_BINDING_H_