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