[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);
+}