blob: ef2d0b55e61cc68a849e733e126a01aeb169356a [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_SYS_COMPONENT_LLCPP_OUTGOING_DIRECTORY_H_
#define LIB_SYS_COMPONENT_LLCPP_OUTGOING_DIRECTORY_H_
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/async/dispatcher.h>
#include <lib/fit/function.h>
#include <lib/stdcompat/string_view.h>
#include <lib/svc/dir.h>
#include <lib/sys/component/llcpp/constants.h>
#include <lib/sys/component/llcpp/handlers.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <map>
#include <memory>
#include <stack>
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`.
//
// This class is thread-hostile.
//
// # Simple usage
//
// Instances of this class should be owned and managed on the same thread
// that services their connections.
//
// # Advanced usage
//
// You can use a background thread to service connections provided:
// async_dispatcher_t for the background thread is stopped or suspended
// prior to destroying the class object.
//
// # 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
// LLCPP (Low-level C++) FIDL bindings support. The other class is designed for
// HLCPP( High-Level C++) FIDL bindings.
class OutgoingDirectory final {
public:
// Creates an OutgoingDirectory which will serve requests when
// |Serve(zx::channel)| or |ServeFromStartupInfo()| is called.
//
// |dispatcher| must not be nullptr. If it is, this method will panic.
static OutgoingDirectory Create(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;
~OutgoingDirectory();
// Starts serving the outgoing directory on the given channel. This should
// be invoked after the outgoing directory has been populated, e.g. via
// |AddProtocol|.
//
// This object will implement the |fuchsia.io.Directory| interface using this
// channel.
//
// # Errors
//
// ZX_ERR_BAD_HANDLE: |directory_request| is not a valid handle.
//
// ZX_ERR_ACCESS_DENIED: |directory_request| has insufficient rights.
zx::status<> Serve(fidl::ServerEnd<fuchsia_io::Directory> directory_request);
// 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.
//
// If |dispatcher| is nullptr, then the global dispatcher set via
// |async_get_default_dispatcher| will be used.
//
// # 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: |directory_request| has insufficient rights.
zx::status<> ServeFromStartupInfo();
// Adds a FIDL Protocol 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.
//
// 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/sys/component/llcpp/outgoing_directory_test.cc
template <typename Protocol>
zx::status<> AddProtocol(fidl::WireServer<Protocol>* impl,
cpp17::string_view name = fidl::DiscoverableProtocolName<Protocol>) {
return AddProtocolAt(kServiceDirectory, impl, name);
}
// Same as above but allows overriding the parent directory in which the
// protocol will be hosted.
template <typename Protocol>
zx::status<> AddProtocolAt(cpp17::string_view path, fidl::WireServer<Protocol>* impl,
cpp17::string_view name = fidl::DiscoverableProtocolName<Protocol>) {
if (impl == nullptr || dispatcher_ == nullptr) {
return zx::make_status(ZX_ERR_INVALID_ARGS);
}
return AddProtocolAt<Protocol>(
path,
[=](fidl::ServerEnd<Protocol> request) {
fidl::ServerBindingRef<Protocol> server =
fidl::BindServer(dispatcher_, std::move(request), impl);
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(name, std::move(cb));
},
name);
}
// Same as above but overloaded to support servers implementations speaking
// FIDL C++ natural types: |fidl::Server<P>|, part of the unified bindings.
template <typename Protocol>
zx::status<> AddProtocol(fidl::Server<Protocol>* impl,
cpp17::string_view name = fidl::DiscoverableProtocolName<Protocol>) {
if (impl == nullptr || dispatcher_ == nullptr) {
return zx::make_status(ZX_ERR_INVALID_ARGS);
}
return AddProtocol<Protocol>(
[=](fidl::ServerEnd<Protocol> request) {
// This object is safe to drop. Server will still begin to operate
// past its lifetime.
auto _server = fidl::BindServer(dispatcher_, std::move(request), impl);
},
name);
}
// Adds a FIDL Protocol instance.
//
// A |handler| is added to handle connection requests for the this particular
// 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.
//
// # 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.
//
// # Errors
//
// ZX_ERR_ALREADY_EXISTS: An entry already exists for this protocol.
//
// ZX_ERR_INVALID_ARGS: |name| is an empty string.
//
// # Examples
//
// See sample use cases in test case(s) located at
// //sdk/lib/sys/component/llcpp/outgoing_directory_test.cc
template <typename Protocol>
zx::status<> AddProtocol(TypedHandler<Protocol> handler,
cpp17::string_view name = fidl::DiscoverableProtocolName<Protocol>) {
return AddProtocolAt<Protocol>(kServiceDirectory, std::move(handler), name);
}
template <typename Protocol>
zx::status<> AddProtocolAt(cpp17::string_view path, TypedHandler<Protocol> handler,
cpp17::string_view name = fidl::DiscoverableProtocolName<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 AddNamedProtocolAt(path, name, std::move(bridge_func));
}
// Same as above but is untyped. This method is generally discouraged but
// is made available if a generic handler needs to be provided.
zx::status<> AddNamedProtocol(AnyHandler handler, cpp17::string_view name);
zx::status<> AddNamedProtocolAt(cpp17::string_view path, cpp17::string_view name,
AnyHandler handler);
// 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 |ServiceHandler|.
//
// # 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/llcpp/outgoing_directory_test.cc
template <typename Service>
zx::status<> AddService(ServiceHandler handler, cpp17::string_view instance = kDefaultInstance) {
return AddNamedService(std::move(handler), Service::Name, instance);
}
// Adds an instance of a FIDL Service.
//
// A |handler| is added to provide an |instance| of a |service|.
//
// # Errors
//
// ZX_ERR_ALREADY_EXISTS: The instance already exists.
//
// ZX_ERR_INVALID_ARGS: |service| or |instance| is an empty string. Also,
// if |handler| is empty.
//
// # Example
//
// See sample use cases in test case(s) located at
// //sdk/lib/sys/component/llcpp/outgoing_directory_test.cc
zx::status<> AddNamedService(ServiceHandler handler, cpp17::string_view service,
cpp17::string_view instance = kDefaultInstance);
// 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::status<> AddDirectory(fidl::ClientEnd<fuchsia_io::Directory> remote_dir,
cpp17::string_view directory_name);
// Removes a FIDL Protocol entry.
//
// # Errors
//
// ZX_ERR_NOT_FOUND: The protocol entry was not found.
//
// # Example
//
// ```
// outgoing.RemoveProtocol<lib_example::MyProtocol>();
// ```
template <typename Protocol>
zx::status<> RemoveProtocol(cpp17::string_view name = fidl::DiscoverableProtocolName<Protocol>) {
return RemoveNamedProtocol(name);
}
// Same as above but untyped.
zx::status<> RemoveNamedProtocol(cpp17::string_view name);
template <typename Protocol>
zx::status<> RemoveProtocolAt(
cpp17::string_view directory,
cpp17::string_view name = fidl::DiscoverableProtocolName<Protocol>) {
return RemoveNamedProtocolAt(directory, name);
}
zx::status<> RemoveNamedProtocolAt(cpp17::string_view directory, cpp17::string_view 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::status<> RemoveService(cpp17::string_view instance = kDefaultInstance) {
return RemoveNamedService(Service::Name, instance);
}
// Removes an instance of a FIDL Service.
//
// # Errors
//
// ZX_ERR_NOT_FOUND: The instance was not found.
zx::status<> RemoveNamedService(cpp17::string_view service,
cpp17::string_view instance = kDefaultInstance);
// Removes the subdirectory on the provided |directory_name|.
//
// # Errors
//
// ZX_ERR_NOT_FOUND: No entry was found with provided name.
zx::status<> RemoveDirectory(cpp17::string_view directory_name);
private:
OutgoingDirectory(async_dispatcher_t* dispatcher, svc_dir_t* root);
// |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;
OutgoingDirectory* self;
};
// 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()>;
void AppendUnbindConnectionCallback(cpp17::string_view name, UnbindConnectionCallback callback);
void UnbindAllConnections(cpp17::string_view name);
static std::string MakePath(cpp17::string_view service, cpp17::string_view instance);
async_dispatcher_t* dispatcher_ = nullptr;
svc_dir_t* root_ = nullptr;
// 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.
std::map<std::string, std::vector<UnbindConnectionCallback>> unbind_protocol_callbacks_ = {};
};
} // namespace component
#endif // LIB_SYS_COMPONENT_LLCPP_OUTGOING_DIRECTORY_H_