| // Copyright 2022 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_DRIVER_OUTGOING_CPP_OUTGOING_DIRECTORY_H_ |
| #define LIB_DRIVER_OUTGOING_CPP_OUTGOING_DIRECTORY_H_ |
| |
| #include <lib/component/outgoing/cpp/handlers.h> |
| #include <lib/component/outgoing/cpp/outgoing_directory.h> |
| #include <lib/driver/outgoing/cpp/handlers.h> |
| #include <lib/fdf/cpp/channel.h> |
| #include <lib/fdf/cpp/protocol.h> |
| #include <lib/fdf/dispatcher.h> |
| #include <lib/fidl/cpp/wire/service_handler.h> |
| #include <lib/fidl_driver/cpp/transport.h> |
| |
| namespace fdf { |
| |
| // The name of the default FIDL Service instance. |
| constexpr const std::string_view kDefaultInstance = |
| component::OutgoingDirectory::kDefaultServiceInstance; |
| |
| // Driver specific implementation for an outgoing directory that wraps around |
| // |component::OutgoingDirectory|. |
| // |
| // # Thread safety |
| // |
| // This class is thread-unsafe. Instances must be managed and used from tasks |
| // running on the |fdf_dispatcher_t|, and the dispatcher must be synchronized. |
| // See |
| // https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/thread-safe-async#mutual-exclusion-guarantee |
| class OutgoingDirectory final { |
| public: |
| static OutgoingDirectory Create(fdf_dispatcher_t* dispatcher) { |
| ZX_ASSERT_MSG(dispatcher != nullptr, |
| "OutgoingDirectory::Create received nullptr |dispatcher|."); |
| auto component_outgoing_dir = |
| component::OutgoingDirectory(fdf_dispatcher_get_async_dispatcher(dispatcher)); |
| return OutgoingDirectory(std::move(component_outgoing_dir), dispatcher); |
| } |
| |
| OutgoingDirectory(component::OutgoingDirectory component_outgoing_dir, |
| fdf_dispatcher_t* dispatcher) |
| : component_outgoing_dir_(std::move(component_outgoing_dir)), dispatcher_(dispatcher) {} |
| |
| // OutgoingDirectory can be moved. Once moved, invoking a method on an |
| // instance will yield undefined behavior. |
| OutgoingDirectory(OutgoingDirectory&&) noexcept; |
| OutgoingDirectory& operator=(OutgoingDirectory&&) noexcept; |
| |
| // OutgoingDirectory cannot be copied. |
| OutgoingDirectory(const OutgoingDirectory&) = delete; |
| OutgoingDirectory& operator=(const OutgoingDirectory&) = delete; |
| |
| // Adds an instance of a FIDL Service. |
| // |
| // A |handler| is added to provide an |instance| of a service. |
| // |
| // The template type |Service| must be the generated type representing a FIDL Service. |
| // The template type must be a specialization of |fidl::ServiceInstanceHandler<Transport>| |
| // where Transport must be either |fidl::internal::ChannelTransport| for |zx::channel| |
| // transports, or |fidl::internal::DriverTransport| for |fdf::Channel| transport. Users |
| // should not use this method directly and instead should use the specialization |
| // for |component::ServiceInstanceHandler| and |fdf::ServiceInstanceHandler| |
| // instead. |
| // |
| // # Errors |
| // |
| // ZX_ERR_ALREADY_EXISTS: The instance already exists. |
| // |
| // ZX_ERR_INVALID_ARGS: |instance| is an empty string or |handler| is empty. |
| template <typename TransportHandler, typename Service> |
| zx::result<> AddService(TransportHandler handler, |
| cpp17::string_view instance = kDefaultInstance) { |
| auto handlers = handler.TakeMemberHandlers(); |
| if (handlers.empty()) { |
| return zx::make_result(ZX_ERR_INVALID_ARGS); |
| } |
| |
| std::string basepath = |
| std::string("svc") + "/" + std::string(Service::Name) + "/" + std::string(instance); |
| |
| for (auto& [member_name, member_handler] : handlers) { |
| component::AnyHandler handler = nullptr; |
| if constexpr (std::is_same_v<TransportHandler, component::ServiceInstanceHandler>) { |
| handler = [member_handler = std::move(member_handler)](zx::channel server_end) mutable { |
| ZX_ASSERT(server_end.is_valid()); |
| member_handler(std::move(server_end)); |
| return; |
| }; |
| } else if constexpr (std::is_same_v<TransportHandler, fdf::ServiceInstanceHandler>) { |
| handler = [this, |
| member_handler = std::move(member_handler)](zx::channel server_end) mutable { |
| ZX_ASSERT(server_end.is_valid()); |
| |
| // The received |server_end| is the token channel handle, which needs to be registered |
| // with the runtime. |
| RegisterRuntimeToken(std::move(server_end), member_handler.share()); |
| }; |
| } else { |
| static_assert( |
| always_false<TransportHandler>, |
| "TransportHandler must be either fidl::internal::ChannelTransport or fidl::internal::DriverTransport"); |
| } |
| |
| zx::result<> status = |
| component_outgoing_dir_.AddUnmanagedProtocolAt(std::move(handler), basepath, member_name); |
| if (status.is_error()) { |
| return status; |
| } |
| } |
| return zx::ok(); |
| } |
| |
| // Same as above but strictly for services using the driver channel transport. |
| template <typename Service> |
| zx::result<> AddService(fdf::ServiceInstanceHandler handler, |
| cpp17::string_view instance = kDefaultInstance) { |
| return AddService<fdf::ServiceInstanceHandler, Service>(std::move(handler), instance); |
| } |
| |
| // Same as above but strictly for services using the Zircon channel transport. |
| template <typename Service> |
| zx::result<> AddService(component::ServiceInstanceHandler handler, |
| cpp17::string_view instance = kDefaultInstance) { |
| return AddService<component::ServiceInstanceHandler, Service>(std::move(handler), instance); |
| } |
| |
| // |
| // Wrappers around |component::OutgoingDirectory|. |
| // |
| // Protocols are not supported. Drivers should use |AddService| instead. |
| // |
| |
| // Starts serving the outgoing directory on the given channel. This should |
| // be invoked after the outgoing directory has been populated, e.g. via |
| // |AddService|. |
| // |
| // This object will implement the |fuchsia.io.Directory| interface using this |
| // channel. Note that this method returns immediately and that the |dispatcher| |
| // provided to the constructor will be responsible for processing messages |
| // sent to the server endpoint. |
| // |
| // # Errors |
| // |
| // ZX_ERR_BAD_HANDLE: |directory_server_end| is not a valid handle. |
| // |
| // ZX_ERR_ACCESS_DENIED: |directory_server_end| has insufficient rights. |
| zx::result<> Serve(fidl::ServerEnd<fuchsia_io::Directory> directory_server_end) { |
| return component_outgoing_dir_.Serve(std::move(directory_server_end)); |
| } |
| |
| // Serve a subdirectory at the root of this outgoing directory. |
| // |
| // The directory will be installed under the path |directory_name|. When |
| // a request is received under this path, then it will be forwarded to |
| // |remote_dir|. |
| // |
| // # Errors |
| // |
| // ZX_ERR_ALREADY_EXISTS: An entry with the provided name already exists. |
| // |
| // ZX_ERR_BAD_HANDLE: |remote_dir| is an invalid handle. |
| // |
| // ZX_ERR_INVALID_ARGS: |directory_name| is an empty string. |
| zx::result<> AddDirectory(fidl::ClientEnd<fuchsia_io::Directory> remote_dir, |
| cpp17::string_view directory_name) { |
| return component_outgoing_dir_.AddDirectory(std::move(remote_dir), directory_name); |
| } |
| |
| // Same as |AddDirectory| but allows setting the parent directory |
| // in which the directory will be installed. |
| zx::result<> AddDirectoryAt(fidl::ClientEnd<fuchsia_io::Directory> remote_dir, |
| cpp17::string_view path, cpp17::string_view directory_name) { |
| return component_outgoing_dir_.AddDirectoryAt(std::move(remote_dir), path, directory_name); |
| } |
| |
| // Removes an instance of a FIDL Service. |
| // |
| // # Errors |
| // |
| // ZX_ERR_NOT_FOUND: The instance was not found. |
| // |
| // # Example |
| // |
| // ``` |
| // outgoing.RemoveService<lib_example::MyService>("my-instance"); |
| // ``` |
| template <typename Service> |
| zx::result<> RemoveService(cpp17::string_view instance = kDefaultInstance) { |
| return RemoveService(Service::Name, instance); |
| } |
| |
| // Same as above but untyped. |
| zx::result<> RemoveService(cpp17::string_view service, |
| cpp17::string_view instance = kDefaultInstance) { |
| return component_outgoing_dir_.RemoveService(service, instance); |
| } |
| |
| // Removes the subdirectory on the provided |directory_name|. |
| // |
| // # Errors |
| // |
| // ZX_ERR_NOT_FOUND: No entry was found with provided name. |
| zx::result<> RemoveDirectory(cpp17::string_view directory_name) { |
| return component_outgoing_dir_.RemoveDirectory(directory_name); |
| } |
| |
| // Same as |RemoveDirectory| but allows specifying the parent directory |
| // that the directory will be removed from. The parent directory, |path|, |
| // will not be removed. |
| zx::result<> RemoveDirectoryAt(cpp17::string_view path, cpp17::string_view directory_name) { |
| return component_outgoing_dir_.RemoveDirectoryAt(path, directory_name); |
| } |
| |
| // Get the underlying component outgoing directory. These APIs will only support |
| // FIDL, they do not support DriverTransport. |
| component::OutgoingDirectory& component() { return component_outgoing_dir_; } |
| |
| private: |
| template <typename T> |
| static constexpr std::false_type always_false{}; |
| |
| // Registers |token| with the driver runtime. When the client attempts to connect using the |
| // token channel peer, we will call |handler|. |
| void RegisterRuntimeToken(zx::channel token, AnyHandler handler); |
| |
| component::OutgoingDirectory component_outgoing_dir_; |
| |
| fdf_dispatcher_t* dispatcher_ = nullptr; |
| }; |
| |
| } // namespace fdf |
| |
| #endif // LIB_DRIVER_OUTGOING_CPP_OUTGOING_DIRECTORY_H_ |