blob: f3046dc316486b5c5f1ac28338743ebe314f6eff [file] [log] [blame]
// 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>
#include <unordered_set>
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|.");
return OutgoingDirectory(dispatcher);
}
explicit OutgoingDirectory(fdf_dispatcher_t* dispatcher = fdf::Dispatcher::GetCurrent()->get())
: component_outgoing_dir_(fdf_dispatcher_get_async_dispatcher(dispatcher)),
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, std::string_view instance = kDefaultInstance) {
component::ServiceInstanceHandler component_handler;
if constexpr (std::is_same_v<TransportHandler, fdf::ServiceInstanceHandler>) {
// If the driver transport is being used, create a handler that registers the runtime token
// and add that to the |component_handler|.
auto handlers = handler.TakeMemberHandlers();
if (handlers.empty()) {
return zx::make_result(ZX_ERR_INVALID_ARGS);
}
for (auto& [member_name, member_handler] : handlers) {
zx::result<> add_result = component_handler.AddAnyMember(
[this, m_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. Sharing the handler, rather than moving it, as this callback can
// be called multiple times.
RegisterRuntimeToken(std::move(server_end), m_handler.share());
},
member_name);
if (add_result.is_error()) {
return add_result;
}
}
// If one already exists, insert returns the iterator to the existing item instead of
// resetting it to an empty set.
auto [it, _inserted] = driver_services_.try_emplace(std::string(Service::Name));
it->second.insert(std::string(instance));
} else if constexpr (std::is_same_v<TransportHandler, component::ServiceInstanceHandler>) {
// If zircon transport is used, just take the user given handler and use that.
component_handler = std::move(handler);
// If one already exists, insert returns the iterator to the existing item instead of
// resetting it to an empty set.
auto [it, _inserted] = zircon_services_.try_emplace(std::string(Service::Name));
it->second.insert(std::string(instance));
} else {
static_assert(
always_false<TransportHandler>,
"TransportHandler must be either fidl::internal::ChannelTransport or fidl::internal::DriverTransport");
}
return component_outgoing_dir_.AddService<Service>(std::move(component_handler), instance);
}
// Same as above but strictly for services using the driver channel transport.
template <typename Service>
zx::result<> AddService(fdf::ServiceInstanceHandler handler,
std::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,
std::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,
std::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,
std::string_view path, std::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(std::string_view instance = kDefaultInstance) {
return RemoveService(Service::Name, instance);
}
// Same as above but untyped.
zx::result<> RemoveService(std::string_view service,
std::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(std::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(std::string_view path, std::string_view directory_name) {
return component_outgoing_dir_.RemoveDirectoryAt(path, directory_name);
}
std::unordered_map<std::string, std::unordered_set<std::string>> GetDriverServices() {
return driver_services_;
}
std::unordered_map<std::string, std::unordered_set<std::string>> GetZirconServices() {
return zircon_services_;
}
// 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::UnownedSynchronizedDispatcher dispatcher_;
std::unordered_map<std::string, std::unordered_set<std::string>> driver_services_;
std::unordered_map<std::string, std::unordered_set<std::string>> zircon_services_;
};
} // namespace fdf
#endif // LIB_DRIVER_OUTGOING_CPP_OUTGOING_DIRECTORY_H_