[fidl][llcpp] LLCPP Service Server API
The OutgoingDirectory class is mostly copied from
//sdk/lib/sys/cpp, since code in //zircon cannot depend
on code in //sdk.
Once the LLCPP FIDL library is moved into the SDK, the
two classes can be merged, specializing on either HLCPP
or LLCPP.
BUG=8094
Change-Id: Id8a0695444c02f5a04af4fc6ebdc53edc373cbbc
diff --git a/zircon/system/ulib/fidl/BUILD.gn b/zircon/system/ulib/fidl/BUILD.gn
index bc0a0319..b3d3881 100644
--- a/zircon/system/ulib/fidl/BUILD.gn
+++ b/zircon/system/ulib/fidl/BUILD.gn
@@ -59,6 +59,7 @@
"lib/fidl/llcpp/array.h",
"lib/fidl/llcpp/coding.h",
"lib/fidl/llcpp/connect_service.h",
+ "lib/fidl/llcpp/service_handler_interface.h",
"lib/fidl/llcpp/decoded_message.h",
"lib/fidl/llcpp/encoded_message.h",
"lib/fidl/llcpp/sync_call.h",
diff --git a/zircon/system/ulib/fidl/include/lib/fidl/llcpp/service_handler_interface.h b/zircon/system/ulib/fidl/include/lib/fidl/llcpp/service_handler_interface.h
new file mode 100644
index 0000000..5f66ddb
--- /dev/null
+++ b/zircon/system/ulib/fidl/include/lib/fidl/llcpp/service_handler_interface.h
@@ -0,0 +1,46 @@
+// 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_FIDL_LLCPP_SERVICE_HANDLER_INTERFACE_H_
+#define LIB_FIDL_LLCPP_SERVICE_HANDLER_INTERFACE_H_
+
+#include <lib/fidl/llcpp/string_view.h>
+#include <lib/fit/function.h>
+#include <lib/fit/result.h>
+#include <lib/fit/string_view.h>
+#include <zircon/fidl.h>
+
+#ifdef __Fuchsia__
+#include <lib/zx/channel.h>
+#endif // __Fuchsia__
+
+namespace llcpp::fidl {
+
+#ifdef __Fuchsia__
+
+// Interface used by generated FIDL code for adding protocol members to a Service instance.
+// NOTE: This class is copied from the high-level C++ FIDL library in //sdk/lib/fidl/cpp.
+class ServiceHandlerInterface {
+ public:
+ virtual ~ServiceHandlerInterface() = default;
+
+ // User-defined action for handling a connection attempt to a member protocol.
+ using MemberHandler = fit::function<zx_status_t(zx::channel)>;
+
+ // Add a |member| to the instance, whose connection will be handled by |handler|.
+ //
+ // # Errors
+ //
+ // ZX_ERR_ALREADY_EXISTS: The member already exists.
+ virtual zx_status_t AddMember(fit::string_view member, MemberHandler handler) = 0;
+
+ // NOTE: This class is missing an |AddMember| overload that uses types only present
+ // in the high-level C++ library.
+};
+
+#endif // __Fuchsia__
+
+} // namespace llcpp::fidl
+
+#endif // LIB_FIDL_LLCPP_SERVICE_HANDLER_INTERFACE_H_
diff --git a/zircon/system/ulib/service/BUILD.gn b/zircon/system/ulib/service/BUILD.gn
index db2f8ca..98f3f59 100644
--- a/zircon/system/ulib/service/BUILD.gn
+++ b/zircon/system/ulib/service/BUILD.gn
@@ -4,16 +4,25 @@
library("service-llcpp") {
sdk = "source"
- sdk_headers = [ "lib/service/llcpp/service.h" ]
+ sdk_headers = [
+ "lib/service/llcpp/constants.h",
+ "lib/service/llcpp/service.h",
+ "lib/service/llcpp/service_handler.h",
+ "lib/service/llcpp/outgoing_directory.h",
+ ]
sources = [
+ "llcpp/outgoing_directory.cc",
"llcpp/service.cc",
]
public_deps = [
+ "$zx/system/fidl/fuchsia-io:llcpp.headers",
"$zx/system/ulib/zx:headers",
]
deps = [
"$zx/system/fidl/fuchsia-io:llcpp",
+ "$zx/system/ulib/async",
"$zx/system/ulib/fidl:fidl-llcpp",
+ "$zx/system/ulib/fs",
"$zx/system/ulib/zircon",
]
}
diff --git a/zircon/system/ulib/service/README.md b/zircon/system/ulib/service/README.md
index 45a1f66..6aff144b 100644
--- a/zircon/system/ulib/service/README.md
+++ b/zircon/system/ulib/service/README.md
@@ -53,4 +53,22 @@
To get started, see the documentation for `sys::OpenServiceAt<FidlService>`, in
`<lib/service/llcpp/service.h>`.
+## Server API
+
+The server API provides a virtual filesystem capable of serving the outgoing namespace of a
+component (`/svc`, etc.). It depends on `//zircon/system/ulib/fs` to actually implement the
+`fuchsia.io` protocols.
+
+The developer may not control allocations performed while serving the VFS. However, the
+developer defines how each member protocol of a FIDL Service is handled through callback
+functions registered with this library.
+
+See `<lib/service/llcpp/outgoing_directory.h>` for details.
+
+The server API is closely modelled (read "copied") after the High Level C++ FIDL `sys` library
+in the SDK (`//sdk/lib/sys/service`).
+
+Once the LLCPP FIDL library is promoted to the SDK, `llcpp::sys::OutgoingDirectory` and
+`sys::OutgoingDirectory` can be merged.
+
[FTP-041]: https://fuchsia.dev/fuchsia-src/development/languages/fidl/reference/ftp/ftp-041.md
\ No newline at end of file
diff --git a/zircon/system/ulib/service/include/lib/service/llcpp/constants.h b/zircon/system/ulib/service/include/lib/service/llcpp/constants.h
new file mode 100644
index 0000000..13fb521
--- /dev/null
+++ b/zircon/system/ulib/service/include/lib/service/llcpp/constants.h
@@ -0,0 +1,15 @@
+// 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_CONSTANTS_H_
+#define LIB_SERVICE_LLCPP_CONSTANTS_H_
+
+namespace llcpp::sys {
+
+// The name of the default FIDL Service instance.
+constexpr const char kDefaultInstance[] = "default";
+
+} // namespace llcpp::sys
+
+#endif // LIB_SERVICE_LLCPP_CONSTANTS_H_
diff --git a/zircon/system/ulib/service/include/lib/service/llcpp/outgoing_directory.h b/zircon/system/ulib/service/include/lib/service/llcpp/outgoing_directory.h
new file mode 100644
index 0000000..d744e1c
--- /dev/null
+++ b/zircon/system/ulib/service/include/lib/service/llcpp/outgoing_directory.h
@@ -0,0 +1,197 @@
+// 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_OUTGOING_DIRECTORY_H_
+#define LIB_SERVICE_LLCPP_OUTGOING_DIRECTORY_H_
+
+#include <lib/async/dispatcher.h>
+#include <lib/fit/string_view.h>
+#include <lib/service/llcpp/constants.h>
+#include <lib/service/llcpp/service_handler.h>
+
+#include <fs/pseudo_dir.h>
+#include <fs/service.h>
+#include <fs/synchronous_vfs.h>
+
+namespace llcpp::sys {
+
+// The directory provided by this component to the component manager.
+//
+// A component's outgoing directory contains services, data, and other objects
+// that can be consumed by either the component manager itself or by other
+// components in the system.
+//
+// The outgoing directory contains several subdirectories with well-known
+// names:
+//
+// * svc. This directory contains the services offered by this component to
+// other components.
+// * debug. This directory contains arbitrary debugging output offered by this
+// component.
+//
+// The outgoing directory may optionally contain other directories constructed
+// using |GetOrCreateDirectory|. Common optional directories include:
+//
+// * objects. This directory contains Inspect API files and interfaces for use
+// in component inspection.
+//
+// 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 is mostly copied from `//sdk/lib/sys/cpp`.
+//
+// This library relies on the LLCPP (Low-level C++) FIDL library in `//zircon`
+// and is not yet in the SDK. Code in `//zircon` cannot depend on libraries in
+// the SDK.
+//
+// The main difference between this class and the one in the SDK is that
+// this one uses LLCPP interfaces instead of HLCPP. The underlying
+// VFS implementations differ but can be merged at a future date.
+class OutgoingDirectory final {
+ public:
+ // Creates an OutgoingDirectory which will serve requests on |dispatcher|
+ // when |Serve(zx::channel)| or |ServeFromStartupInfo()| is called.
+ //
+ // |dispatcher| must not be null. See |async_get_default_dispatcher()|.
+ OutgoingDirectory(async_dispatcher_t* dispatcher);
+
+ // Outgoing objects cannot be copied.
+ OutgoingDirectory(const OutgoingDirectory&) = delete;
+ OutgoingDirectory& operator=(const OutgoingDirectory&) = delete;
+
+ // Starts serving the outgoing directory on the given channel.
+ //
+ // This object will implement the |fuchsia.io.Directory| interface using this
+ // channel.
+ //
+ // This object will serve the outgoing directory using the |async_dispatcher_t|
+ // provided in the constructor.
+ //
+ // # Errors
+ //
+ // ZX_ERR_BAD_HANDLE: |directory_request| is not a valid handle.
+ //
+ // ZX_ERR_ACCESS_DENIED: |directory_request| has insufficient rights.
+ zx_status_t Serve(zx::channel 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.
+ //
+ // This object will serve the outgoing directory using the |async_dispatcher_t|
+ // provided in the constructor.
+ //
+ // # 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_t ServeFromStartupInfo();
+
+ // Adds an instance of a 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.
+ //
+ // # Example
+ //
+ // ```
+ // llcpp::sys::ServiceHandler handler;
+ // ::llcpp::lib::example::MyService::Handler my_handler(&handler);
+ // my_handler.add_my_member([dispatcher](zx::channel request) {
+ // fidl::Bind(dispatcher, std::move(request), std::make_unique<FooProtocolImpl>());
+ // });
+ // outgoing.AddService<::llcpp::lib::example::MyService>(std::move(handler), "my-instance");
+ // ```
+ template <typename Service>
+ zx_status_t AddService(ServiceHandler handler,
+ fit::string_view instance = kDefaultInstance) const {
+ return AddNamedService(std::move(handler), Service::Name, std::move(instance));
+ }
+
+ // Adds an instance of a service.
+ //
+ // A |handler| is added to provide an |instance| of a |service|.
+ //
+ // # Errors
+ //
+ // ZX_ERR_ALREADY_EXISTS: The instance already exists.
+ //
+ // # Example
+ //
+ // ```
+ // llcpp::sys::ServiceHandler handler;
+ // handler.AddMember("my-member", ...);
+ // outgoing.AddNamedService(std::move(handler), "lib.example.MyService", "my-instance");
+ // ```
+ zx_status_t AddNamedService(ServiceHandler handler, fit::string_view service,
+ fit::string_view instance = kDefaultInstance) const;
+
+ // Removes an instance of a service.
+ //
+ // # Errors
+ //
+ // ZX_ERR_NOT_FOUND: The instance was not found.
+ //
+ // # Example
+ //
+ // ```
+ // outgoing.RemoveService<::llcpp::lib::example::MyService>("my-instance");
+ // ```
+ template <typename Service>
+ zx_status_t RemoveService(fit::string_view instance) const {
+ return RemoveNamedService(Service::Name, instance);
+ }
+
+ // Removes an instance of a service.
+ //
+ // # Errors
+ //
+ // ZX_ERR_NOT_FOUND: The instance was not found.
+ zx_status_t RemoveNamedService(fit::string_view service, fit::string_view instance) const;
+
+ // Gets the root directory.
+ fbl::RefPtr<fs::PseudoDir> root_dir() const { return root_; }
+
+ // Gets the directory to publish debug data.
+ fbl::RefPtr<fs::PseudoDir> debug_dir() const { return debug_; }
+
+ private:
+ // Serves the virtual filesystem.
+ fs::SynchronousVfs vfs_;
+
+ // The root of the outgoing directory itself.
+ fbl::RefPtr<fs::PseudoDir> root_;
+
+ // The service subdirectory of the root directory.
+ fbl::RefPtr<fs::PseudoDir> svc_;
+
+ // The debug subdirectory of the root directory.
+ fbl::RefPtr<fs::PseudoDir> debug_;
+};
+
+} // namespace llcpp::sys
+
+#endif // LIB_SERVICE_LLCPP_OUTGOING_DIRECTORY_H_
diff --git a/zircon/system/ulib/service/include/lib/service/llcpp/service.h b/zircon/system/ulib/service/include/lib/service/llcpp/service.h
index ae6a44e..61e9e73 100644
--- a/zircon/system/ulib/service/include/lib/service/llcpp/service.h
+++ b/zircon/system/ulib/service/include/lib/service/llcpp/service.h
@@ -8,15 +8,17 @@
#include <lib/fidl/llcpp/connect_service.h>
#include <lib/fidl/llcpp/string_view.h>
#include <lib/fit/result.h>
+#include <lib/fit/string_view.h>
+#include <lib/service/llcpp/constants.h>
#include <lib/zx/channel.h>
-namespace sys {
+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>
-fidl::result<typename FidlService::ServiceClient> OpenServiceAt(zx::unowned_channel dir);
+::fidl::result<typename FidlService::ServiceClient> OpenServiceAt(zx::unowned_channel 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
@@ -49,8 +51,8 @@
// Echo::SyncClient client = fidl::BindSyncClient(connect_result.take_value());
// ```
template <typename FidlService>
-fidl::result<typename FidlService::ServiceClient> OpenServiceAt(zx::unowned_channel dir,
- fidl::StringView instance);
+::fidl::result<typename FidlService::ServiceClient> OpenServiceAt(zx::unowned_channel dir,
+ fit::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
@@ -61,26 +63,25 @@
// 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_t OpenNamedServiceAt(zx::unowned_channel dir, fidl::StringView service_path,
- fidl::StringView instance, zx::channel remote);
+zx_status_t OpenNamedServiceAt(zx::unowned_channel dir, fit::string_view service_path,
+ fit::string_view instance, zx::channel remote);
namespace internal {
-zx_status_t DirectoryOpenFunc(zx::unowned_channel dir, fidl::StringView path, zx::channel remote);
+zx_status_t DirectoryOpenFunc(zx::unowned_channel dir, ::fidl::StringView path, zx::channel remote);
} // namespace internal
template <typename FidlService>
-fidl::result<typename FidlService::ServiceClient> OpenServiceAt(zx::unowned_channel dir,
- fidl::StringView instance) {
+::fidl::result<typename FidlService::ServiceClient> OpenServiceAt(zx::unowned_channel dir,
+ fit::string_view instance) {
zx::channel local, remote;
zx_status_t result = zx::channel::create(0, &local, &remote);
if (result != ZX_OK) {
return fit::error(result);
}
- result = OpenNamedServiceAt(std::move(dir), fidl::StringView(FidlService::Name), instance,
- std::move(remote));
+ result = OpenNamedServiceAt(std::move(dir), FidlService::Name, instance, std::move(remote));
if (result != ZX_OK) {
return fit::error(result);
}
@@ -89,10 +90,10 @@
}
template <typename FidlService>
-fidl::result<typename FidlService::ServiceClient> OpenServiceAt(zx::unowned_channel dir) {
- return OpenServiceAt<FidlService>(std::move(dir), fidl::StringView("default"));
+::fidl::result<typename FidlService::ServiceClient> OpenServiceAt(zx::unowned_channel dir) {
+ return OpenServiceAt<FidlService>(std::move(dir), kDefaultInstance);
}
-} // namespace sys
+} // namespace llcpp::sys
#endif // LIB_SERVICE_LLCPP_SERVICE_H_
diff --git a/zircon/system/ulib/service/include/lib/service/llcpp/service_handler.h b/zircon/system/ulib/service/include/lib/service/llcpp/service_handler.h
new file mode 100644
index 0000000..07fbda2
--- /dev/null
+++ b/zircon/system/ulib/service/include/lib/service/llcpp/service_handler.h
@@ -0,0 +1,54 @@
+// 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_HANDLER_H_
+#define LIB_SERVICE_LLCPP_SERVICE_HANDLER_H_
+
+#include <lib/fidl/llcpp/service_handler_interface.h>
+
+#include <fbl/ref_ptr.h>
+#include <fs/pseudo_dir.h>
+#include <fs/service.h>
+
+namespace llcpp::sys {
+
+// A handler for an instance of a FIDL Service.
+class ServiceHandler : public ::llcpp::fidl::ServiceHandlerInterface {
+ public:
+ ServiceHandler() = default;
+
+ // Disable copying.
+ ServiceHandler(const ServiceHandler&) = delete;
+ ServiceHandler& operator=(const ServiceHandler&) = delete;
+
+ // Enable moving.
+ ServiceHandler(ServiceHandler&&) = default;
+ ServiceHandler& operator=(ServiceHandler&&) = default;
+
+ // Add a |member| to the instance, whose connection will be handled by |handler|.
+ //
+ // # Errors
+ //
+ // ZX_ERR_ALREADY_EXISTS: The member already exists.
+ zx_status_t AddMember(fit::string_view member, MemberHandler handler) override {
+ // Bridge between fit::function and fbl::Function.
+ auto bridge_func = [handler = std::move(handler)](zx::channel request_channel) {
+ return handler(std::move(request_channel));
+ };
+ return dir_->AddEntry(std::move(member),
+ fbl::MakeRefCounted<fs::Service>(std::move(bridge_func)));
+ }
+
+ // Take the underlying pseudo-directory from the service handler.
+ //
+ // Once taken, the service handler is no longer safe to use.
+ fbl::RefPtr<fs::PseudoDir> TakeDirectory() { return std::move(dir_); }
+
+ private:
+ fbl::RefPtr<fs::PseudoDir> dir_ = fbl::MakeRefCounted<fs::PseudoDir>();
+};
+
+} // namespace llcpp::sys
+
+#endif // LIB_SERVICE_LLCPP_SERVICE_HANDLER_H_
diff --git a/zircon/system/ulib/service/llcpp/outgoing_directory.cc b/zircon/system/ulib/service/llcpp/outgoing_directory.cc
new file mode 100644
index 0000000..1726adc
--- /dev/null
+++ b/zircon/system/ulib/service/llcpp/outgoing_directory.cc
@@ -0,0 +1,72 @@
+// 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.
+
+#include <lib/fit/string_view.h>
+#include <lib/service/llcpp/outgoing_directory.h>
+#include <lib/service/llcpp/service_handler.h>
+#include <zircon/errors.h>
+#include <zircon/process.h>
+#include <zircon/processargs.h>
+
+#include <fbl/ref_counted.h>
+#include <fs/pseudo_dir.h>
+#include <fs/service.h>
+#include <fs/synchronous_vfs.h>
+
+namespace llcpp::sys {
+
+namespace {
+
+fbl::RefPtr<fs::PseudoDir> FindDir(fbl::RefPtr<fs::PseudoDir> root, fit::string_view name) {
+ fbl::RefPtr<fs::Vnode> node;
+ zx_status_t result = root->Lookup(&node, name);
+ if (result != ZX_OK || !node->IsDirectory()) {
+ return {};
+ }
+ return fbl::RefPtr<fs::PseudoDir>::Downcast(node);
+}
+
+fbl::RefPtr<fs::PseudoDir> FindOrCreateDir(fbl::RefPtr<fs::PseudoDir> root, fit::string_view name) {
+ auto dir = FindDir(root, name);
+ if (dir == nullptr) {
+ dir = fbl::MakeRefCounted<fs::PseudoDir>();
+ root->AddEntry(name, dir);
+ }
+ return dir;
+}
+
+} // namespace
+
+OutgoingDirectory::OutgoingDirectory(async_dispatcher_t* dispatcher)
+ : vfs_(dispatcher),
+ root_(fbl::MakeRefCounted<fs::PseudoDir>()),
+ svc_(fbl::MakeRefCounted<fs::PseudoDir>()),
+ debug_(fbl::MakeRefCounted<fs::PseudoDir>()) {
+ root_->AddEntry("svc", svc_);
+ root_->AddEntry("debug", debug_);
+}
+
+zx_status_t OutgoingDirectory::Serve(zx::channel request_directory) {
+ return vfs_.ServeDirectory(root_, std::move(request_directory));
+}
+
+zx_status_t OutgoingDirectory::ServeFromStartupInfo() {
+ return Serve(zx::channel(zx_take_startup_handle(PA_DIRECTORY_REQUEST)));
+}
+
+zx_status_t OutgoingDirectory::AddNamedService(ServiceHandler handler, fit::string_view service,
+ fit::string_view instance) const {
+ return FindOrCreateDir(svc_, service)->AddEntry(instance, handler.TakeDirectory());
+}
+
+zx_status_t OutgoingDirectory::RemoveNamedService(fit::string_view service,
+ fit::string_view instance) const {
+ fbl::RefPtr<fs::PseudoDir> service_dir = FindDir(svc_, service);
+ if (service_dir == nullptr) {
+ return ZX_ERR_IO_INVALID;
+ }
+ return service_dir->RemoveEntry(instance);
+}
+
+} // namespace llcpp::sys
diff --git a/zircon/system/ulib/service/llcpp/service.cc b/zircon/system/ulib/service/llcpp/service.cc
index cbd9e3e..3eb7382 100644
--- a/zircon/system/ulib/service/llcpp/service.cc
+++ b/zircon/system/ulib/service/llcpp/service.cc
@@ -10,7 +10,7 @@
#include <lib/service/llcpp/service.h>
#include <zircon/device/vfs.h>
-namespace sys {
+namespace llcpp::sys {
namespace {
@@ -19,9 +19,9 @@
// Max path length will be two path components, separated by a file separator.
constexpr uint64_t kMaxPath = (2 * kMaxFilename) + 1;
-fidl::result<fidl::StringView> ValidateAndJoinPath(fidl::Array<char, kMaxPath>* buffer,
- fidl::StringView service,
- fidl::StringView instance) {
+::fidl::result<::fidl::StringView> ValidateAndJoinPath(::fidl::Array<char, kMaxPath>* buffer,
+ ::fidl::StringView service,
+ ::fidl::StringView instance) {
if (service.empty() || service.size() > kMaxFilename) {
return fit::error(ZX_ERR_INVALID_ARGS);
}
@@ -40,14 +40,15 @@
path_cursor += service.size();
*path_cursor++ = '/';
memcpy(path_cursor, instance.data(), instance.size());
- return fit::ok(fidl::StringView(buffer->data(), path_size));
+ return fit::ok(::fidl::StringView(buffer->data(), path_size));
}
} // namespace
namespace internal {
-zx_status_t DirectoryOpenFunc(zx::unowned_channel dir, fidl::StringView path, zx::channel remote) {
+zx_status_t DirectoryOpenFunc(zx::unowned_channel dir, ::fidl::StringView path,
+ zx::channel remote) {
constexpr uint32_t flags =
::llcpp::fuchsia::io::OPEN_RIGHT_READABLE | ::llcpp::fuchsia::io::OPEN_RIGHT_WRITABLE;
::llcpp::fuchsia::io::Directory::ResultOf::Open result =
@@ -58,14 +59,15 @@
} // namespace internal
-zx_status_t OpenNamedServiceAt(zx::unowned_channel dir, fidl::StringView service,
- fidl::StringView instance, zx::channel remote) {
- fidl::Array<char, kMaxPath> path_buffer;
- fidl::result<fidl::StringView> path_result = ValidateAndJoinPath(&path_buffer, service, instance);
+zx_status_t OpenNamedServiceAt(zx::unowned_channel dir, fit::string_view service,
+ fit::string_view instance, zx::channel remote) {
+ ::fidl::Array<char, kMaxPath> path_buffer;
+ ::fidl::result<::fidl::StringView> path_result =
+ ValidateAndJoinPath(&path_buffer, ::fidl::StringView(service), ::fidl::StringView(instance));
if (!path_result.is_ok()) {
return path_result.error();
}
return internal::DirectoryOpenFunc(std::move(dir), path_result.take_value(), std::move(remote));
}
-} // namespace sys
+} // namespace llcpp::sys
diff --git a/zircon/system/utest/service/BUILD.gn b/zircon/system/utest/service/BUILD.gn
index 9e1e710..8c28797 100644
--- a/zircon/system/utest/service/BUILD.gn
+++ b/zircon/system/utest/service/BUILD.gn
@@ -15,11 +15,13 @@
test("service-llcpp-test") {
sources = [
"llcpp/client_test.cc",
+ "llcpp/server_test.cc",
]
deps = [
":fidl.service.test-llcpp",
"$zx/system/ulib/async-loop:async-loop-cpp",
"$zx/system/ulib/async-loop:async-loop-default",
+ "$zx/system/ulib/fdio",
"$zx/system/ulib/fidl-async",
"$zx/system/ulib/fidl-async:fidl-async-cpp",
"$zx/system/ulib/fs",
diff --git a/zircon/system/utest/service/generated/fidl_service_llcpp_test.h b/zircon/system/utest/service/generated/fidl_service_llcpp_test.h
index db746f6..9c4ca8d 100644
--- a/zircon/system/utest/service/generated/fidl_service_llcpp_test.h
+++ b/zircon/system/utest/service/generated/fidl_service_llcpp_test.h
@@ -6,6 +6,7 @@
#define ZIRCON_SYSTEM_UTEST_SERVICE_GENERATED_FIDL_SERVICE_LLCPP_TEST_H_
#include <lib/fidl/llcpp/connect_service.h>
+#include <lib/fidl/llcpp/service_handler_interface.h>
#include <lib/fidl/llcpp/string_view.h>
#ifdef __Fuchsia__
@@ -31,7 +32,7 @@
ServiceClient(zx::channel dir, ::fidl::internal::ConnectMemberFunc connect_func)
: dir_(std::move(dir)), connect_func_(connect_func) {}
- ::fidl::result<::fidl::ClientChannel<::llcpp::fidl::service::test::Echo>> ConnectFoo() {
+ ::fidl::result<::fidl::ClientChannel<::llcpp::fidl::service::test::Echo>> connect_foo() {
zx::channel local, remote;
zx_status_t result = zx::channel::create(0, &local, &remote);
if (result != ZX_OK) {
@@ -45,7 +46,7 @@
return ::fit::ok(::fidl::ClientChannel<::llcpp::fidl::service::test::Echo>(std::move(local)));
}
- ::fidl::result<::fidl::ClientChannel<::llcpp::fidl::service::test::Echo>> ConnectBar() {
+ ::fidl::result<::fidl::ClientChannel<::llcpp::fidl::service::test::Echo>> connect_bar() {
zx::channel local, remote;
zx_status_t result = zx::channel::create(0, &local, &remote);
if (result != ZX_OK) {
@@ -63,6 +64,23 @@
zx::channel dir_;
::fidl::internal::ConnectMemberFunc connect_func_;
};
+
+ class Handler final {
+ public:
+ explicit Handler(::llcpp::fidl::ServiceHandlerInterface* service_handler)
+ : service_handler_(service_handler) {}
+
+ zx_status_t add_foo(::llcpp::fidl::ServiceHandlerInterface::MemberHandler handler) {
+ return service_handler_->AddMember("foo", std::move(handler));
+ }
+
+ zx_status_t add_bar(::llcpp::fidl::ServiceHandlerInterface::MemberHandler handler) {
+ return service_handler_->AddMember("bar", std::move(handler));
+ }
+
+ private:
+ ::llcpp::fidl::ServiceHandlerInterface* service_handler_;
+ };
};
#endif // __Fuchsia__
diff --git a/zircon/system/utest/service/llcpp/client_test.cc b/zircon/system/utest/service/llcpp/client_test.cc
index 1d39b4f..c25f3c0 100644
--- a/zircon/system/utest/service/llcpp/client_test.cc
+++ b/zircon/system/utest/service/llcpp/client_test.cc
@@ -118,13 +118,13 @@
TEST_F(ClientTest, ConnectsToDefault) {
fidl::result<EchoService::ServiceClient> open_result =
- sys::OpenServiceAt<EchoService>(std::move(svc_));
+ llcpp::sys::OpenServiceAt<EchoService>(std::move(svc_));
ASSERT_TRUE(open_result.is_ok());
EchoService::ServiceClient service = open_result.take_value();
// Connect to the member 'foo'.
- fidl::result<fidl::ClientChannel<Echo>> connect_result = service.ConnectFoo();
+ fidl::result<fidl::ClientChannel<Echo>> connect_result = service.connect_foo();
ASSERT_TRUE(connect_result.is_ok());
Echo::SyncClient client = fidl::BindSyncClient(connect_result.take_value());
@@ -139,13 +139,13 @@
TEST_F(ClientTest, ConnectsToOther) {
fidl::result<EchoService::ServiceClient> open_result =
- sys::OpenServiceAt<EchoService>(std::move(svc_), fidl::StringView("other"));
+ llcpp::sys::OpenServiceAt<EchoService>(std::move(svc_), "other");
ASSERT_TRUE(open_result.is_ok());
EchoService::ServiceClient service = open_result.take_value();
// Connect to the member 'bar'.
- fidl::result<fidl::ClientChannel<Echo>> connect_result = service.ConnectBar();
+ fidl::result<fidl::ClientChannel<Echo>> connect_result = service.connect_bar();
ASSERT_TRUE(connect_result.is_ok());
Echo::SyncClient client = fidl::BindSyncClient(connect_result.take_value());
@@ -165,14 +165,14 @@
// Use an instance name that is too long.
zx::unowned_channel svc_copy(*svc_);
fidl::result<EchoService::ServiceClient> open_result =
- sys::OpenServiceAt<EchoService>(std::move(svc_copy), fidl::StringView(illegal_path));
+ llcpp::sys::OpenServiceAt<EchoService>(std::move(svc_copy), illegal_path);
ASSERT_TRUE(open_result.is_error());
ASSERT_EQ(open_result.error(), ZX_ERR_INVALID_ARGS);
// Use a service name that is too long.
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
- ASSERT_EQ(sys::OpenNamedServiceAt(std::move(svc_), fidl::StringView(illegal_path),
- fidl::StringView("default"), std::move(remote)),
- ZX_ERR_INVALID_ARGS);
+ ASSERT_EQ(
+ llcpp::sys::OpenNamedServiceAt(std::move(svc_), illegal_path, "default", std::move(remote)),
+ ZX_ERR_INVALID_ARGS);
}
diff --git a/zircon/system/utest/service/llcpp/server_test.cc b/zircon/system/utest/service/llcpp/server_test.cc
new file mode 100644
index 0000000..75ae1ff
--- /dev/null
+++ b/zircon/system/utest/service/llcpp/server_test.cc
@@ -0,0 +1,200 @@
+// 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.
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/async-loop/default.h>
+#include <lib/fdio/directory.h>
+#include <lib/fidl-async/cpp/bind.h>
+#include <lib/fit/defer.h>
+#include <lib/service/llcpp/outgoing_directory.h>
+#include <lib/service/llcpp/service.h>
+#include <lib/service/llcpp/service_handler.h>
+#include <lib/zx/channel.h>
+
+#include <iostream>
+
+#include <fbl/unique_fd.h>
+#include <zxtest/zxtest.h>
+
+#include "../generated/fidl_llcpp_test.h"
+#include "../generated/fidl_service_llcpp_test.h"
+
+namespace {
+
+using Echo = ::llcpp::fidl::service::test::Echo;
+using EchoService = ::llcpp::fidl::service::test::EchoService;
+
+class EchoCommon : public Echo::Interface {
+ public:
+ explicit EchoCommon(const char* prefix) : prefix_(prefix) {}
+
+ zx_status_t Connect(async_dispatcher_t* dispatcher, zx::channel request) {
+ return fidl::Bind(dispatcher, std::move(request), this);
+ }
+
+ void EchoString(fidl::StringView input, EchoStringCompleter::Sync completer) override {
+ std::string reply = prefix_ + ": " + std::string(input.data(), input.size());
+ completer.Reply(fidl::StringView(reply));
+ }
+
+ private:
+ std::string prefix_;
+};
+
+fbl::unique_fd OpenRootDir(const zx::channel& request) {
+ int root_fd = 0;
+ zx_status_t result = fdio_fd_create(fdio_service_clone(request.get()), &root_fd);
+ if (result != ZX_OK) {
+ return {};
+ }
+ return fbl::unique_fd(root_fd);
+}
+
+fbl::unique_fd OpenSvcDir(const zx::channel& request) {
+ fbl::unique_fd root_fd = OpenRootDir(request);
+ if (!root_fd.is_valid()) {
+ return {};
+ }
+ return fbl::unique_fd(openat(root_fd.get(), "svc", O_RDWR));
+}
+
+fbl::unique_fd OpenAt(const fbl::unique_fd& dirfd, const char* path, int flags) {
+ return fbl::unique_fd(openat(dirfd.get(), path, flags));
+}
+
+} // namespace
+
+class ServerTest : public zxtest::Test {
+ protected:
+ ServerTest() : loop_(&kAsyncLoopConfigNoAttachToCurrentThread), outgoing_(loop_.dispatcher()) {}
+
+ llcpp::sys::ServiceHandler SetUpInstance(Echo::Interface* foo_impl, Echo::Interface* bar_impl) {
+ llcpp::sys::ServiceHandler handler;
+ EchoService::Handler my_service(&handler);
+
+ my_service.add_foo([this, foo_impl](zx::channel request_channel) {
+ return fidl::Bind(loop_.dispatcher(), std::move(request_channel), foo_impl);
+ });
+
+ my_service.add_bar([this, bar_impl](zx::channel request_channel) {
+ return fidl::Bind(loop_.dispatcher(), std::move(request_channel), bar_impl);
+ });
+
+ return handler;
+ }
+
+ void SetUp() override {
+ loop_.StartThread("server-test-loop");
+
+ outgoing_.AddService<EchoService>(SetUpInstance(&default_foo_, &default_bar_));
+ outgoing_.AddService<EchoService>(SetUpInstance(&other_foo_, &other_bar_), "other");
+
+ zx::channel remote;
+ ASSERT_OK(zx::channel::create(0, &local_root_, &remote));
+
+ outgoing_.Serve(std::move(remote));
+ }
+
+ void TearDown() override { loop_.Shutdown(); }
+
+ EchoCommon default_foo_{"default-foo"};
+ EchoCommon default_bar_{"default-bar"};
+ EchoCommon other_foo_{"other-foo"};
+ EchoCommon other_bar_{"other-bar"};
+
+ async::Loop loop_;
+ zx::channel local_root_;
+ llcpp::sys::OutgoingDirectory outgoing_;
+};
+
+TEST_F(ServerTest, ConnectsToDefaultMember) {
+ // Open a copy of the local namespace (channel) as a file descriptor.
+ fbl::unique_fd svc_fd = OpenSvcDir(local_root_);
+ ASSERT_TRUE(svc_fd.is_valid());
+
+ // Extract the channel from `svc_fd`.
+ zx_handle_t svc_local;
+ ASSERT_OK(fdio_get_service_handle(svc_fd.release(), &svc_local));
+
+ // Connect to the `EchoService` at the 'default' instance.
+ fidl::result<EchoService::ServiceClient> open_result =
+ llcpp::sys::OpenServiceAt<EchoService>(zx::unowned_channel(svc_local));
+ ASSERT_TRUE(open_result.is_ok());
+
+ EchoService::ServiceClient service = open_result.take_value();
+
+ // Connect to the member 'foo'.
+ fidl::result<fidl::ClientChannel<Echo>> connect_result = service.connect_foo();
+ ASSERT_TRUE(connect_result.is_ok());
+
+ Echo::SyncClient client = fidl::BindSyncClient(connect_result.take_value());
+ Echo::ResultOf::EchoString echo_result = client.EchoString(fidl::StringView("hello"));
+ ASSERT_TRUE(echo_result.ok());
+
+ auto response = echo_result.Unwrap();
+
+ std::string result_string(response->response.data(), response->response.size());
+ ASSERT_EQ(result_string, "default-foo: hello");
+}
+
+TEST_F(ServerTest, ConnectsToOtherMember) {
+ // Open a copy of the local namespace (channel) as a file descriptor.
+ fbl::unique_fd svc_fd = OpenSvcDir(local_root_);
+ ASSERT_TRUE(svc_fd.is_valid());
+
+ // Extract the channel from `svc_fd`.
+ zx_handle_t svc_local;
+ ASSERT_OK(fdio_get_service_handle(svc_fd.release(), &svc_local));
+
+ // Connect to the `EchoService` at the 'default' instance.
+ fidl::result<EchoService::ServiceClient> open_result =
+ llcpp::sys::OpenServiceAt<EchoService>(zx::unowned_channel(svc_local), "other");
+ ASSERT_TRUE(open_result.is_ok());
+
+ EchoService::ServiceClient service = open_result.take_value();
+
+ // Connect to the member 'foo'.
+ fidl::result<fidl::ClientChannel<Echo>> connect_result = service.connect_foo();
+ ASSERT_TRUE(connect_result.is_ok());
+
+ Echo::SyncClient client = fidl::BindSyncClient(connect_result.take_value());
+ Echo::ResultOf::EchoString echo_result = client.EchoString(fidl::StringView("hello"));
+ ASSERT_TRUE(echo_result.ok());
+
+ auto response = echo_result.Unwrap();
+
+ std::string result_string(response->response.data(), response->response.size());
+ ASSERT_EQ(result_string, "other-foo: hello");
+}
+
+TEST_F(ServerTest, ListsMembers) {
+ // Open a copy of the local namespace (channel) as a file descriptor.
+ fbl::unique_fd svc_fd = OpenSvcDir(local_root_);
+ ASSERT_TRUE(svc_fd.is_valid());
+
+ // Open the 'default' instance of the test service.
+ fbl::unique_fd instance_fd = OpenAt(svc_fd, "fidl.service.test.EchoService/default", O_RDWR);
+ ASSERT_TRUE(instance_fd.is_valid());
+
+ // fdopendir takes ownership of `instance_fd`.
+ DIR* dir = fdopendir(instance_fd.release());
+ ASSERT_NE(dir, nullptr);
+ auto defer_closedir = fit::defer([dir] { closedir(dir); });
+
+ dirent* entry = readdir(dir);
+ ASSERT_NE(entry, nullptr);
+ ASSERT_EQ(std::string(entry->d_name), ".");
+
+ entry = readdir(dir);
+ ASSERT_NE(entry, nullptr);
+ ASSERT_EQ(std::string(entry->d_name), "foo");
+
+ entry = readdir(dir);
+ ASSERT_NE(entry, nullptr);
+ ASSERT_EQ(std::string(entry->d_name), "bar");
+
+ ASSERT_EQ(readdir(dir), nullptr);
+}