blob: 56240ad21dbf99e63a28e9f03a382dfdd4caf466 [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_COMPONENT_OUTGOING_CPP_OUTGOING_DIRECTORY_H_
#define LIB_COMPONENT_OUTGOING_CPP_OUTGOING_DIRECTORY_H_
#include <fidl/fuchsia.io/cpp/markers.h>
#include <lib/async/cpp/sequence_checker.h>
#include <lib/async/cpp/task.h>
#include <lib/async/dispatcher.h>
#include <lib/component/outgoing/cpp/handlers.h>
#include <lib/fidl/cpp/wire/connect_service.h>
#include <lib/fidl/cpp/wire/traits.h>
#include <lib/fit/function.h>
#include <lib/svc/dir.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <map>
#include <memory>
#include <stack>
#include <type_traits>
namespace component {
// The directory containing handles to capabilities this component provides.
// Entries served from this outgoing directory should correspond to the
// component manifest's `capabilities` declarations.
//
// The outgoing directory contains one special subdirectory, named `svc`. This
// directory contains the FIDL Services and Protocols offered by this component
// to other components. For example the FIDL Protocol `fuchsia.foo.Bar` will be
// hosted under the path `/svc/fuchsia.foo.Bar`.
//
// # Thread safety
//
// This class is thread-unsafe. Instances must be managed and used from a
// [synchronized async dispatcher][synchronized-dispatcher]. See
// https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/thread-safe-async#synchronized-dispatcher
//
// # Server lifetimes
//
// This class supports a simpler usage style where it owns the server
// implementations published to the directory, and an advanced usage style
// where it borrows the server and where the user manages the server lifetimes.
//
// When the outgoing directory owns the server implementation (e.g. see
// |AddProtocol|), removing the published capability or destroying the outgoing
// directory will destroy the server object and teardown all connections to it.
//
// When the outgoing directory borrows the server implementation (usually via
// callbacks e.g. see |AddUnmanagedProtocol|), removing the published
// capability or destroying the outgoing directory synchronously stops all
// future attempts to connect to the server. Thereafter, the user may teardown
// existing connections and destroy the server.
//
// # Maintainer's Note
//
// This class' API is semantically identical to the one found in
// `//sdk/lib/sys/cpp`. This exists in order to offer equivalent facilities to
// the new C++ bindings. The other class is designed for the older, HLCPP
// (High-Level C++) FIDL bindings. It is expected that once all clients of HLCPP
// are migrated to the new C++ bindings, that library will be removed.
class OutgoingDirectory final {
public:
// The name of the default FIDL Service instance.
static constexpr const char kDefaultServiceInstance[] = "default";
// The path referencing the outgoing services directory.
static constexpr const char kServiceDirectory[] = "svc";
// Creates an OutgoingDirectory which will serve requests when
// |Serve| or |ServeFromStartupInfo()| is called.
//
// |dispatcher| must not be nullptr. If it is, this method will panic.
explicit OutgoingDirectory(async_dispatcher_t* dispatcher);
OutgoingDirectory() = delete;
// 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;
// Destroying the directory will stop any future attempts to connect to the
// services and protocols published within.
~OutgoingDirectory();
// Starts serving the outgoing directory on the given channel.
//
// This should be invoked after the outgoing directory has been populated,
// i.e. after |AddProtocol|. While |OutgoingDirectory| does not require
// calling |AddProtocol| before |Serve|, if you call them in the other order
// there is a chance that requests that arrive in between will be dropped.
//
// 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);
// Starts serving the outgoing directory on the channel provided to this
// process at startup as |PA_DIRECTORY_REQUEST|.
//
// This object will implement the |fuchsia.io.Directory| interface using this
// channel.
//
// # Errors
//
// ZX_ERR_BAD_HANDLE: the process did not receive a |PA_DIRECTORY_REQUEST|
// startup handle or it was already taken.
//
// ZX_ERR_ACCESS_DENIED: The |PA_DIRECTORY_REQUEST| handle has insufficient
// rights.
zx::result<> ServeFromStartupInfo();
// Adds a FIDL Protocol server instance.
//
// |impl| will be used to handle requests for this protocol.
// |name| is used to determine where to host the protocol. This protocol will
// be hosted under the path /svc/{name}, where `name` is the discoverable name
// of the protocol, by default. A pointer to |impl| is returned on the success
// case of this function call. This pointer will be valid until either this
// |OutgoingDirectory| is destroyed or |RemoveProtocol| is invoked with the
// provided |name|.
//
// Note, if and when |RemoveProtocol| is called for the provided |name|, this
// object will asynchronously close down the associated server end channel and
// stop receiving requests. This method provides no facilities for waiting
// until teardown is complete. If such control is desired, then the
// |TypedHandler| overload of this method listed below ought to be used.
//
// # Errors
//
// ZX_ERR_ALREADY_EXISTS: An entry already exists for this protocol.
//
// ZX_ERR_INVALID_ARGS: |impl| is nullptr or |name| is an empty string.
//
// # Examples
//
// See sample use cases in test case(s) located at
// //sdk/lib/component/tests/outgoing_directory_test.cc
template <typename Protocol, typename ServerImpl,
typename = std::enable_if_t<fidl::IsProtocolV<Protocol>>>
zx::result<> AddProtocol(std::unique_ptr<ServerImpl> impl,
std::string_view name = fidl::DiscoverableProtocolName<Protocol>) {
return AddProtocolAt<Protocol>(kServiceDirectory, std::move(impl), name);
}
// Same as |AddProtocol| except that a typed handler is used. The
// |handler| is a callback that handles connection requests for this
// particular protocol.
//
// # Note
//
// Active connections are never torn down when/if |RemoveProtocol| is invoked
// with the same |name|. Users of this method should manage teardown of
// all active connections.
template <typename Protocol, typename = std::enable_if_t<fidl::IsProtocolV<Protocol>>>
zx::result<> AddUnmanagedProtocol(
TypedHandler<Protocol> handler,
std::string_view name = fidl::DiscoverableProtocolName<Protocol>) {
static_assert(fidl::IsProtocol<Protocol>(), "Type of |Protocol| must be FIDL protocol");
return AddUnmanagedProtocolAt<Protocol>(kServiceDirectory, std::move(handler), name);
}
// Same as above but is untyped. This method is generally discouraged but
// is made available if a generic handler needs to be provided.
zx::result<> AddUnmanagedProtocol(AnyHandler handler, std::string_view name);
// Same as |AddProtocol| but allows setting the parent directory in
// which the protocol will be installed.
template <typename Protocol, typename ServerImpl,
typename = std::enable_if_t<fidl::IsProtocolV<Protocol>>>
zx::result<> AddProtocolAt(std::string_view path, std::unique_ptr<ServerImpl> impl,
std::string_view name = fidl::DiscoverableProtocolName<Protocol>) {
static_assert(fidl::IsProtocol<Protocol>(), "Type of |Protocol| must be FIDL protocol");
if (impl == nullptr || inner().dispatcher_ == nullptr) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
return AddUnmanagedProtocolAt<Protocol>(
path,
[dispatcher = inner().dispatcher_, impl = std::move(impl),
unbind_protocol_callbacks = &inner().unbind_protocol_callbacks_,
name = std::string(name)](fidl::ServerEnd<Protocol> request) {
fidl::ServerBindingRef<Protocol> server =
fidl::BindServer(dispatcher, std::move(request), impl.get());
auto cb = [server = std::move(server)]() mutable { server.Unbind(); };
// We don't have to check for entry existing because the |AddProtocol|
// overload being invoked here will do that internally.
AppendUnbindConnectionCallback(unbind_protocol_callbacks, name, std::move(cb));
},
name);
}
// Same as |AddProtocol| but uses a typed handler and allows the usage of
// setting the parent directory in which the protocol will be installed.
template <typename Protocol, typename = std::enable_if_t<fidl::IsProtocolV<Protocol>>>
zx::result<> AddUnmanagedProtocolAt(
std::string_view path, TypedHandler<Protocol> handler,
std::string_view name = fidl::DiscoverableProtocolName<Protocol>) {
static_assert(fidl::IsProtocol<Protocol>(), "Type of |Protocol| must be FIDL protocol");
auto bridge_func = [handler = std::move(handler)](zx::channel request) {
fidl::ServerEnd<Protocol> server_end(std::move(request));
(void)handler(std::move(server_end));
};
return AddUnmanagedProtocolAt(std::move(bridge_func), path, name);
}
// Same as |AddProtocol| but is untyped and allows the usage of setting the
// parent directory in which the protocol will be installed.
zx::result<> AddUnmanagedProtocolAt(AnyHandler handler, std::string_view path,
std::string_view name);
// 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 generated class |Service::Handler| helps the caller populate a
// |ServiceInstanceHandler|.
//
// # Errors
//
// ZX_ERR_ALREADY_EXISTS: The instance already exists.
//
// ZX_ERR_INVALID_ARGS: |instance| is an empty string or |handler| is empty.
//
// # Example
//
// See sample use cases in test case(s) located at
// //sdk/lib/sys/component/cpp/outgoing_directory_test.cc
template <typename Service, typename = std::enable_if_t<fidl::IsServiceV<Service>>>
zx::result<> AddService(ServiceInstanceHandler handler,
std::string_view instance = kDefaultServiceInstance) {
static_assert(fidl::IsService<Service>(), "Type of |Service| must be FIDL service");
return AddService(std::move(handler), Service::Name, instance);
}
// Same as above but is untyped.
zx::result<> AddService(ServiceInstanceHandler handler, std::string_view service,
std::string_view instance = kDefaultServiceInstance);
template <typename Service, typename = std::enable_if_t<fidl::IsServiceV<Service>>>
zx::result<> AddServiceAt(ServiceInstanceHandler handler, std::string_view path,
std::string_view instance = kDefaultServiceInstance) {
static_assert(fidl::IsService<Service>(), "Type of |Service| must be FIDL service");
return AddServiceAt(std::move(handler), path, Service::Name, instance);
}
// Same as above but is untyped.
zx::result<> AddServiceAt(ServiceInstanceHandler handler, std::string_view path,
std::string_view service,
std::string_view instance = kDefaultServiceInstance);
// 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);
// 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);
// Removes a FIDL Protocol entry with the path `/svc/{name}`.
//
// Removing the protocol will stop any future attempts to connect to this
// protocol.
//
// # Errors
//
// ZX_ERR_NOT_FOUND: The protocol entry was not found.
//
// # Example
//
// ```
// outgoing.RemoveProtocol<lib_example::MyProtocol>();
// ```
template <typename Protocol, typename = std::enable_if_t<fidl::IsProtocolV<Protocol>>>
zx::result<> RemoveProtocol(std::string_view name = fidl::DiscoverableProtocolName<Protocol>) {
return RemoveProtocol(name);
}
// Same as above but untyped.
zx::result<> RemoveProtocol(std::string_view name);
// Removes a FIDL Protocol entry located in the provided |directory|.
// Unlike |RemoveProtocol| which looks for the protocol to remove in the
// path `/svc/{name}`, this method uses the directory name provided, e.g.
// `/{path}/{name}`.
//
// # Errors
//
// ZX_ERR_NOT_FOUND: The protocol entry was not found.
//
// # Example
//
// ```
// outgoing.RemoveProtocolAt<lib_example::MyProtocol>("diagnostics");
// ```
template <typename Protocol, typename = std::enable_if_t<fidl::IsProtocolV<Protocol>>>
zx::result<> RemoveProtocolAt(std::string_view path,
std::string_view name = fidl::DiscoverableProtocolName<Protocol>) {
return RemoveProtocolAt(path, name);
}
// Same as above but untyped.
zx::result<> RemoveProtocolAt(std::string_view directory, std::string_view name);
// Removes an instance of a FIDL Service.
//
// Removing the service will stop any future attempts to connect to members
// within service. Connections to intermediate directories will be closed.
//
// # Errors
//
// ZX_ERR_NOT_FOUND: The instance was not found.
//
// # Example
//
// ```
// outgoing.RemoveService<lib_example::MyService>("my-instance");
// ```
template <typename Service, typename = std::enable_if_t<fidl::IsServiceV<Service>>>
zx::result<> RemoveService(std::string_view instance = kDefaultServiceInstance) {
return RemoveService(Service::Name, instance);
}
// Same as above but untyped.
zx::result<> RemoveService(std::string_view service,
std::string_view instance = kDefaultServiceInstance);
template <typename Service, typename = std::enable_if_t<fidl::IsServiceV<Service>>>
zx::result<> RemoveServiceAt(std::string_view path,
std::string_view instance = kDefaultServiceInstance) {
return RemoveServiceAt(path, Service::Name, instance);
}
zx::result<> RemoveServiceAt(std::string_view path, std::string_view service,
std::string_view 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);
// 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);
private:
// |svc_dir_add_service_by_path| takes in a void* |context| that is passed to
// the |handler| callback passed as the last argument to the function call.
// This library will pass in a casted void* pointer to this object, and when
// the `svc` library invokes this library's connection handler, the |context|
// will be casted back to |OnConnectContext*|.
struct OnConnectContext {
AnyHandler handler;
};
// Function pointer that matches type of |svc_dir_add_service| handler.
// Internally, it calls the |AnyMemberHandler| instance populated via |AddAnyMember|.
static void OnConnect(void* context, const char* service_name, zx_handle_t handle);
// Callback invoked during teardown of a FIDL protocol entry. This callback
// will close all active connections on the associated channel.
using UnbindConnectionCallback = fit::callback<void()>;
using UnbindCallbackMap = std::map<std::string, std::vector<UnbindConnectionCallback>>;
static void AppendUnbindConnectionCallback(UnbindCallbackMap* unbind_protocol_callbacks,
const std::string& name,
UnbindConnectionCallback callback);
void UnbindAllConnections(std::string_view name);
static std::string MakePath(std::vector<std::string_view> strings);
struct Inner {
Inner(async_dispatcher_t* dispatcher, svc_dir_t* root);
~Inner();
Inner(Inner&&) noexcept = delete;
Inner& operator=(Inner&&) noexcept = delete;
async_dispatcher_t* dispatcher_;
async::synchronization_checker checker_;
// This task will be scheduled on the async dispatcher to let |checker|
// run additional verifications inside a dispatcher task.
std::optional<async::Task> synchronization_check_dispatcher_task_;
// |root_| is the outgoing directory implementation.
// It is thread-unsafe, hence guarded by our synchronization checker using
// clang thread-safety annotations. The annotations would help the compiler
// statically verify that all accesses are checked for mutual exclusion.
svc_dir_t* root_ __TA_GUARDED(checker_);
// Mapping of all registered protocol handlers. Key represents a path to
// the directory in which the protocol ought to be installed. For example,
// a path may look like `svc/fuchsia.FooService/some_instance`.
// The value contains a map of each of the entry's handlers.
//
// For FIDL Protocols, entries will be stored under "svc" entry
// of this type, and then their name will be used as a key for the internal
// map.
//
// For FIDL Services, entries will be stored by instance,
// e.g. `svc/fuchsia.FooService/default`, and then the member names will be
// used as the keys for the internal maps.
//
// The OnConnectContext has to be stored in the heap because its pointer
// is used by |OnConnect|, a static function, during channel connection attempt.
std::map<std::string, std::map<std::string, std::unique_ptr<OnConnectContext>>>
registered_handlers_ = {};
// Protocol bindings used to initiate teardown when protocol is removed. We
// store this in a callback as opposed to a map of fidl::ServerBindingRef<T>
// because that object is template parameterized and therefore can't be
// stored in a homogeneous container.
UnbindCallbackMap unbind_protocol_callbacks_;
};
Inner& inner() { return *inner_; }
const Inner& inner() const { return *inner_; }
// Wrapped in |unique_ptr| so that we can capture in a lambda without risk of
// it becoming invalid.
std::unique_ptr<Inner> inner_;
};
} // namespace component
#endif // LIB_COMPONENT_OUTGOING_CPP_OUTGOING_DIRECTORY_H_