text: // 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.
  
  #ifndef LIB_SERVICE_LLCPP_SERVICE_H_
  #define LIB_SERVICE_LLCPP_SERVICE_H_
  
  #include <fuchsia/io/llcpp/fidl.h>
  #include <lib/fidl/llcpp/connect_service.h>
  #include <lib/fidl/llcpp/string_view.h>
  #include <lib/fit/result.h>
  #include <lib/service/llcpp/constants.h>
  #include <lib/stdcompat/string_view.h>
  #include <lib/zx/channel.h>
  #include <lib/zx/status.h>
  
  #include <utility>
  
  namespace service {
  
  // Opens the directory containing incoming services in the application's default
  // incoming namespace. By default the path is "/svc". Users may specify a custom path.
  ::zx::status<::fidl::ClientEnd<fuchsia_io::Directory>> OpenServiceRoot(
      const char* path = llcpp::sys::kServiceDirectory);
  
  namespace internal {
  
  // Implementation of |service::Connect| that is independent from the actual |Protocol|.
  ::zx::status<zx::channel> ConnectRaw(const char* path);
  
  // Implementation of |service::ConnectAt| that is independent from the actual |Protocol|.
  ::zx::status<zx::channel> ConnectAtRaw(::fidl::UnownedClientEnd<fuchsia_io::Directory> svc_dir,
                                         const char* protocol_name);
  
  // Implementation of |service::Clone| that is independent from the actual |Protocol|.
  ::zx::status<zx::channel> CloneRaw(::zx::unowned_channel&& node);
  
  template <size_t... I>
  using seq = std::integer_sequence<size_t, I...>;
  template <size_t N>
  using make_seq = std::make_integer_sequence<size_t, N>;
  
  template <const char*, typename, const char*, typename>
  struct concat;
  template <const char* s1, size_t... i1, const char* s2, size_t... i2>
  struct concat<s1, seq<i1...>, s2, seq<i2...>> {
    // |s1| followed by |s2| followed by trailing NUL.
    static constexpr const char value[]{s1[i1]..., s2[i2]..., 0};
  };
  
  template <size_t N>
  constexpr size_t string_length(const char (&literal)[N], size_t size = N - 1) {
    return size;
  }
  
  // Returns the default path for a protocol in the `/svc/{name}` format,
  // where `{name}` is the fully qualified name of the FIDL protocol.
  // The string concatentation happens at compile time.
  template <typename Protocol>
  constexpr const char* DefaultPath() {
    constexpr auto svc_length = string_length(llcpp::sys::kServiceDirectoryTrailingSlash);
    constexpr auto protocol_length = string_length(Protocol::Name);
    return concat<llcpp::sys::kServiceDirectoryTrailingSlash, make_seq<svc_length>, Protocol::Name,
                  make_seq<protocol_length>>::value;
  }
  
  // Determines if |Protocol| contains a method named |Clone|.
  // TODO(fxbug.dev/65964): This template is coupled to LLCPP codegen details,
  // and as such would need to be adapted when e.g. we change the LLCPP generated
  // namespace and hierarchies.
  template <typename Protocol, typename = void>
  struct has_fidl_method_named_clone : public ::std::false_type {};
  template <typename Protocol>
  struct has_fidl_method_named_clone<
      Protocol, std::void_t<decltype(fidl::WireRequest<typename Protocol::Clone>(
                    std::declval<zx_txid_t>() /* txid */, std::declval<uint32_t>() /* flags */,
                    std::declval<::fidl::ServerEnd<fuchsia_io::Node>&>() /* object */))>>
      : public std::true_type {};
  template <typename Protocol>
  constexpr inline auto has_fidl_method_named_clone_v = has_fidl_method_named_clone<Protocol>::value;
  
  // Determines if |T| is fully defined i.e. |sizeof(T)| can be evaluated.
  template <typename T, typename = void>
  struct is_complete : public ::std::false_type {};
  template <typename T>
  struct is_complete<T, std::void_t<std::integral_constant<std::size_t, sizeof(T)>>>
      : public std::true_type {};
  template <typename T>
  constexpr inline auto is_complete_v = is_complete<T>::value;
  
  enum class AssumeProtocolComposesNodeTag { kAssumeProtocolComposesNode };
  
  template <typename Protocol>
  void CheckProtocolForClone(::fidl::UnownedClientEnd<Protocol> node, std::nullptr_t tag) {
    static_assert(internal::is_complete_v<Protocol>,
                  "|Protocol| must be fully defined in order to use |service::Clone|");
    static_assert(internal::has_fidl_method_named_clone_v<Protocol>,
                  "|Protocol| should be or compose the |fuchsia.io/Node| protocol");
  }
  
  template <typename Protocol>
  void CheckProtocolForClone(::fidl::UnownedClientEnd<Protocol> node,
                             AssumeProtocolComposesNodeTag tag) {
    static_assert(!internal::has_fidl_method_named_clone_v<Protocol>,
                  "|Protocol| already appears to compose the |fuchsia.io/Node| protocol. "
                  "There is no need to specify |AssumeProtocolComposesNode|.");
  }
  
  }  // namespace internal
  
  // Typed channel wrapper around |fdio_service_connect|.
  //
  // Connects to the |Protocol| protocol in the default namespace for the current
  // process. |path| defaults to `/svc/{name}`, where `{name}` is the fully
  // qualified name of the FIDL protocol. The path may be overridden to
  // a custom value.
  //
  // See documentation on |fdio_service_connect| for details.
  template <typename Protocol>
  ::zx::status<::fidl::ClientEnd<Protocol>> Connect(
      const char* path = internal::DefaultPath<Protocol>()) {
    auto channel = internal::ConnectRaw(path);
    if (channel.is_error()) {
      return channel.take_error();
    }
    return ::zx::ok(::fidl::ClientEnd<Protocol>(std::move(channel.value())));
  }
  
  // Typed channel wrapper around |fdio_service_connect_at|.
  //
  // Connects to the |Protocol| protocol relative to the |svc_dir| directory.
  // |protocol_name| defaults to the fully qualified name of the FIDL protocol,
  // but may be overridden to a custom value.
  //
  // See documentation on |fdio_service_connect_at| for details.
  template <typename Protocol>
  ::zx::status<::fidl::ClientEnd<Protocol>> ConnectAt(
      ::fidl::UnownedClientEnd<fuchsia_io::Directory> svc_dir,
      const char* protocol_name = Protocol::Name) {
    auto channel = internal::ConnectAtRaw(svc_dir, protocol_name);
    if (channel.is_error()) {
      return channel.take_error();
    }
    return ::zx::ok(::fidl::ClientEnd<Protocol>(std::move(channel.value())));
  }
  
  // Passing this value to |service::Clone| implies opting out of any
  // compile-time checks the the FIDL protocol supports |fuchsia.io/Node.Clone|.
  // This option should be used with care. See documentation on |service::Clone|.
  constexpr inline auto AssumeProtocolComposesNode =
      internal::AssumeProtocolComposesNodeTag::kAssumeProtocolComposesNode;
  
  // Typed channel wrapper around |fdio_service_clone| and |fdio_service_clone_to|.
  //
  // Given an unowned client end |node|, returns an owned clone as a new
  // connection using protocol request pipelining.
  //
  // |node| must be a channel that implements the |fuchsia.io/Node| protocol,
  // or one that composes such a protocol.
  //
  // This function looks a little involved due to the template programming; here
  // is an example how it could be used:
  //
  //     // |node| could be |fidl::ClientEnd| or |fidl::UnownedClientEnd|.
  //     auto clone = service::Clone(node);
  //
  // By default, this function will verify that the protocol type supports cloning
  // (i.e. it has a FIDL method named "Clone"), which is generally satisfied by
  // composing |fuchsia.io/Node|. Under special circumstances, it is possible to
  // explicitly state that the protocol actually composes |fuchsia.io/Node| at
  // run-time, even though it may not be defined this way in the FIDL schema. This
  // could happen as a result of implicit or unsupported multiplexing of FIDL
  // protocols. There will not be any compile-time validation that the cloning is
  // supported, if the extra |service::AssumeProtocolComposeseNode| argument is
  // provided. Note that if the channel does not implement |fuchsia.io/Node.Clone|,
  // the remote endpoint of the cloned node will be asynchronously closed.
  //
  // As such, this override should be used sparingly, and with caution:
  //
  //     // Assume that |node| supports the |fuchsia.io/Node.Clone| method call.
  //     // If that is not the case, there will be runtime failures at a later
  //     // stage when |clone| is actually used.
  //     auto clone = service::Clone(node, service::AssumeProtocolComposesNode);
  //
  // See documentation on |fdio_service_clone| for details.
  template <typename Protocol, typename Tag = std::nullptr_t,
            typename = std::enable_if_t<
                std::disjunction_v<std::is_same<Tag, std::nullptr_t>,
                                   std::is_same<Tag, internal::AssumeProtocolComposesNodeTag>>>>
  ::zx::status<::fidl::ClientEnd<Protocol>> Clone(::fidl::UnownedClientEnd<Protocol> node,
                                                  Tag tag = nullptr) {
    internal::CheckProtocolForClone(node, tag);
    auto result = internal::CloneRaw(node.channel());
    if (!result.is_ok()) {
      return result.take_error();
    }
    return ::zx::ok(::fidl::ClientEnd<Protocol>(std::move(*result)));
  }
  
  // Overload of |service::Clone| to emulate implicit conversion from a
  // |const fidl::ClientEnd&| into |fidl::UnownedClientEnd|. C++ cannot consider
  // actual implicit conversions when performing template argument deduction.
  template <typename Protocol, typename Tag = std::nullptr_t>
  ::zx::status<::fidl::ClientEnd<Protocol>> Clone(const ::fidl::ClientEnd<Protocol>& node,
                                                  Tag tag = nullptr) {
    return Clone(node.borrow(), tag);
  }
  
  // Typed channel wrapper around |fdio_service_clone| and |fdio_service_clone_to|.
  //
  // Different from |Clone|, this version swallows any synchronous error and will
  // return an invalid client-end in those cases. As such, |service::Clone| should
  // be preferred over this function.
  template <typename Protocol, typename Tag = std::nullptr_t,
            typename = std::enable_if_t<
                std::disjunction_v<std::is_same<Tag, std::nullptr_t>,
                                   std::is_same<Tag, internal::AssumeProtocolComposesNodeTag>>>>
  ::fidl::ClientEnd<Protocol> MaybeClone(::fidl::UnownedClientEnd<Protocol> node, Tag tag = nullptr) {
    auto result = Clone(node, tag);
    if (!result.is_ok()) {
      return {};
    }
    return std::move(*result);
  }
  
  // Overload of |service::MaybeClone| to emulate implicit conversion from a
  // |const fidl::ClientEnd&| into |fidl::UnownedClientEnd|. C++ cannot consider
  // actual implicit conversions when performing template argument deduction.
  template <typename Protocol, typename Tag = std::nullptr_t>
  ::fidl::ClientEnd<Protocol> MaybeClone(const ::fidl::ClientEnd<Protocol>& node, Tag tag = nullptr) {
    return MaybeClone(node.borrow(), tag);
  }
  
  }  // namespace service
  
  namespace llcpp::sys {
  
  // Opens a connection to the default instance of a FIDL service of type `FidlService`, rooted at
  // `dir`. The default instance is called 'default'. See
  // `OpenServiceAt(zx::unowned_channel,fidl::StringView)` for details.
  template <typename FidlService>
  ::zx::status<typename FidlService::ServiceClient> OpenServiceAt(
      ::fidl::UnownedClientEnd<fuchsia_io::Directory> dir);
  
  // Opens a connection to the given instance of a FIDL service of type `FidlService`, rooted at
  // `dir`. The result, if successful, is a `FidlService::ServiceClient` that exposes methods that
  // connect to the various members of the FIDL service.
  //
  // If the service or instance does not exist, the resulting `FidlService::ServiceClient` will fail
  // to connect to a member.
  //
  // Returns a zx::status of status Ok on success. In the event of failure, an error status variant
  // is returned, set to an error value.
  //
  // Returns a zx::status of state Error set to ZX_ERR_INVALID_ARGS if `instance` is more than 255
  // characters long.
  //
  // ## Example
  //
  // ```C++
  // using Echo = fuchsia_echo::Echo;
  // using EchoService = fuchsia_echo::EchoService;
  //
  // zx::status<EchoService::ServiceClient> open_result =
  //     sys::OpenServiceAt<EchoService>(std::move(svc_));
  // ASSERT_TRUE(open_result.is_ok());
  //
  // EchoService::ServiceClient service = open_result.take_value();
  //
  // zx::status<fidl::ClientEnd<Echo>> connect_result = service.ConnectFoo();
  // ASSERT_TRUE(connect_result.is_ok());
  //
  // fidl::WireSyncClient<Echo> client = fidl::BindSyncClient(connect_result.take_value());
  // ```
  template <typename FidlService>
  ::zx::status<typename FidlService::ServiceClient> OpenServiceAt(
      ::fidl::UnownedClientEnd<fuchsia_io::Directory> dir, cpp17::string_view instance);
  
  // Opens a connection to the given instance of a FIDL service with the name `service_name`, rooted
  // at `dir`. The `remote` channel is passed to the remote service, and its local twin can be used to
  // issue FIDL protocol messages. Most callers will want to use `OpenServiceAt(...)`.
  //
  // If the service or instance does not exist, the `remote` channel will be closed.
  //
  // Returns ZX_OK on success. In the event of failure, an error value is returned.
  //
  // Returns ZX_ERR_INVALID_ARGS if `service_path` or `instance` are more than 255 characters long.
  ::zx::status<> OpenNamedServiceAt(::fidl::UnownedClientEnd<fuchsia_io::Directory> dir,
                                    cpp17::string_view service_path, cpp17::string_view instance,
                                    ::zx::channel remote);
  
  namespace internal {
  
  // The internal |DirectoryOpenFunc| needs to take raw Zircon channels,
  // because the FIDL runtime that interfaces with it cannot depend on the
  // |fuchsia.io| FIDL library. See <lib/fidl/llcpp/connect_service.h>.
  ::zx::status<> DirectoryOpenFunc(::zx::unowned_channel dir, ::fidl::StringView path,
                                   ::zx::channel remote);
  
  }  // namespace internal
  
  template <typename FidlService>
  ::zx::status<typename FidlService::ServiceClient> OpenServiceAt(
      ::fidl::UnownedClientEnd<fuchsia_io::Directory> dir, cpp17::string_view instance) {
    ::zx::channel local, remote;
    if (zx_status_t status = ::zx::channel::create(0, &local, &remote); status != ZX_OK) {
      return ::zx::error(status);
    }
  
    ::zx::status<> result = OpenNamedServiceAt(dir, FidlService::Name, instance, std::move(remote));
    if (result.is_error()) {
      return result.take_error();
    }
    return ::zx::ok(
        typename FidlService::ServiceClient(std::move(local), internal::DirectoryOpenFunc));
  }
  
  template <typename FidlService>
  ::zx::status<typename FidlService::ServiceClient> OpenServiceAt(
      ::fidl::UnownedClientEnd<fuchsia_io::Directory> dir) {
    return OpenServiceAt<FidlService>(dir, kDefaultInstance);
  }
  
  }  // namespace llcpp::sys
  
  #endif  // LIB_SERVICE_LLCPP_SERVICE_H_
  