| // Copyright 2018 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 "src/sys/appmgr/namespace.h" | 
 |  | 
 | #include <fuchsia/sys/cpp/fidl.h> | 
 | #include <lib/async/dispatcher.h> | 
 | #include <lib/fdio/directory.h> | 
 | #include <lib/fdio/fd.h> | 
 | #include <lib/fdio/fdio.h> | 
 | #include <lib/gtest/real_loop_fixture.h> | 
 | #include <lib/sys/cpp/testing/service_directory_provider.h> | 
 | #include <lib/vfs/cpp/pseudo_dir.h> | 
 | #include <lib/vfs/cpp/service.h> | 
 | #include <lib/zx/channel.h> | 
 | #include <zircon/errors.h> | 
 | #include <zircon/status.h> | 
 |  | 
 | #include <map> | 
 | #include <memory> | 
 | #include <string> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include <gmock/gmock.h> | 
 | #include <gtest/gtest.h> | 
 |  | 
 | #include "lib/fidl/cpp/interface_handle.h" | 
 | #include "lib/fidl/cpp/interface_request.h" | 
 |  | 
 | using fuchsia::sys::ServiceList; | 
 | using fuchsia::sys::ServiceListPtr; | 
 | using fuchsia::sys::ServiceProvider; | 
 | using fuchsia::sys::ServiceProviderPtr; | 
 |  | 
 | namespace component { | 
 | namespace { | 
 |  | 
 | class NamespaceGuard { | 
 |  public: | 
 |   explicit NamespaceGuard(fxl::RefPtr<Namespace> ns) : ns_(std::move(ns)) {} | 
 |   NamespaceGuard(std::nullptr_t) : ns_(nullptr) {} | 
 |   ~NamespaceGuard() { Kill(); } | 
 |   Namespace* operator->() const { return ns_.get(); } | 
 |  | 
 |   fxl::RefPtr<Namespace>& ns() { return ns_; } | 
 |  | 
 |   void Kill() { | 
 |     if (ns_) { | 
 |       ns_->FlushAndShutdown(ns_); | 
 |     } | 
 |     ns_ = nullptr; | 
 |   } | 
 |  | 
 |  private: | 
 |   fxl::RefPtr<Namespace> ns_; | 
 | }; | 
 |  | 
 | class NamespaceTest : public ::gtest::RealLoopFixture { | 
 |  protected: | 
 |   NamespaceGuard MakeNamespace(ServiceListPtr additional_services, | 
 |                                NamespaceGuard parent = nullptr) { | 
 |     if (parent.ns().get() == nullptr) { | 
 |       return NamespaceGuard( | 
 |           fxl::MakeRefCounted<Namespace>(nullptr, std::move(additional_services), nullptr)); | 
 |     } | 
 |     return NamespaceGuard(Namespace::CreateChildNamespace(parent.ns(), nullptr, | 
 |                                                           std::move(additional_services), nullptr)); | 
 |   } | 
 |  | 
 |   zx_status_t ConnectToService(zx_handle_t svc_dir, const std::string& name) { | 
 |     zx::channel h1, h2; | 
 |     zx_status_t r = zx::channel::create(0, &h1, &h2); | 
 |     if (r != ZX_OK) | 
 |       return r; | 
 |     fdio_service_connect_at(svc_dir, name.c_str(), h2.release()); | 
 |     return ZX_OK; | 
 |   } | 
 | }; | 
 |  | 
 | class NamespaceHostDirectoryTest : public NamespaceTest { | 
 |  protected: | 
 |   zx::channel OpenAsDirectory() { | 
 |     fidl::InterfaceHandle<fuchsia::io::Directory> dir; | 
 |     directory_.Serve(fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE, | 
 |                      dir.NewRequest().TakeChannel(), dispatcher()); | 
 |     return dir.TakeChannel(); | 
 |   } | 
 |  | 
 |   zx_status_t AddService(const std::string& name) { | 
 |     auto cb = [this, name](zx::channel channel, async_dispatcher_t* dispatcher) { | 
 |       ++connection_ctr_[name]; | 
 |     }; | 
 |     return directory_.AddEntry(name, std::make_unique<vfs::Service>(cb)); | 
 |   } | 
 |  | 
 |   vfs::PseudoDir directory_; | 
 |   std::map<std::string, int> connection_ctr_; | 
 | }; | 
 |  | 
 | class NamespaceProviderTest : public NamespaceTest { | 
 |  protected: | 
 |   NamespaceGuard MakeNamespace(ServiceListPtr additional_services) { | 
 |     return NamespaceGuard( | 
 |         fxl::MakeRefCounted<Namespace>(nullptr, std::move(additional_services), nullptr)); | 
 |   } | 
 |  | 
 |   zx_status_t AddService(const std::string& name) { | 
 |     fidl::InterfaceRequestHandler<fuchsia::io::Node> cb = | 
 |         [this, name](fidl::InterfaceRequest<fuchsia::io::Node> request) { | 
 |           ++connection_ctr_[name]; | 
 |         }; | 
 |     provider_.AddService(std::move(cb), name); | 
 |     return ZX_OK; | 
 |   } | 
 |  | 
 |   sys::testing::ServiceDirectoryProvider provider_; | 
 |   std::map<std::string, int> connection_ctr_; | 
 | }; | 
 |  | 
 | std::pair<std::string, int> StringIntPair(const std::string& s, int i) { return {s, i}; } | 
 |  | 
 | TEST_F(NamespaceHostDirectoryTest, AdditionalServices) { | 
 |   static constexpr char kService1[] = "fuchsia.test.TestService1"; | 
 |   static constexpr char kService2[] = "fuchsia.test.TestService2"; | 
 |   ServiceListPtr service_list(new ServiceList); | 
 |   service_list->names.push_back(kService1); | 
 |   service_list->names.push_back(kService2); | 
 |   AddService(kService1); | 
 |   AddService(kService2); | 
 |   ServiceProviderPtr service_provider; | 
 |   service_list->host_directory = OpenAsDirectory(); | 
 |   auto ns = MakeNamespace(std::move(service_list)); | 
 |  | 
 |   zx::channel svc_dir = ns->OpenServicesAsDirectory(); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService1)); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); | 
 |   // fdio_service_connect_at does not return an error if connection failed. | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), "fuchsia.test.NotExists")); | 
 |   RunLoopUntilIdle(); | 
 |   std::vector<std::pair<std::string, int>> connection_ctr_vec; | 
 |   for (const auto& e : connection_ctr_) { | 
 |     connection_ctr_vec.push_back(e); | 
 |   } | 
 |   EXPECT_THAT(connection_ctr_vec, | 
 |               ::testing::ElementsAre(StringIntPair(kService1, 1), StringIntPair(kService2, 2))); | 
 | } | 
 |  | 
 | TEST_F(NamespaceHostDirectoryTest, AdditionalServices_InheritParent) { | 
 |   static constexpr char kService1[] = "fuchsia.test.TestService1"; | 
 |   static constexpr char kService2[] = "fuchsia.test.TestService2"; | 
 |   ServiceListPtr parent_service_list(new ServiceList); | 
 |   parent_service_list->names.push_back(kService1); | 
 |   ServiceListPtr service_list(new ServiceList); | 
 |   service_list->names.push_back(kService2); | 
 |   AddService(kService1); | 
 |   AddService(kService2); | 
 |   parent_service_list->host_directory = OpenAsDirectory(); | 
 |   service_list->host_directory = OpenAsDirectory(); | 
 |   auto parent_ns = MakeNamespace(std::move(parent_service_list)); | 
 |   auto ns = MakeNamespace(std::move(service_list), parent_ns); | 
 |  | 
 |   zx::channel svc_dir = ns->OpenServicesAsDirectory(); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService1)); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); | 
 |   // fdio_service_connect_at does not return an error if connection failed. | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), "fuchsia.test.NotExists")); | 
 |   RunLoopUntilIdle(); | 
 |   std::vector<std::pair<std::string, int>> connection_ctr_vec; | 
 |   for (const auto& e : connection_ctr_) { | 
 |     connection_ctr_vec.push_back(e); | 
 |   } | 
 |   EXPECT_THAT(connection_ctr_vec, | 
 |               ::testing::ElementsAre(StringIntPair(kService1, 1), StringIntPair(kService2, 1))); | 
 | } | 
 |  | 
 | TEST_F(NamespaceProviderTest, AdditionalServices) { | 
 |   static constexpr char kService1[] = "fuchsia.test.TestService1"; | 
 |   static constexpr char kService2[] = "fuchsia.test.TestService2"; | 
 |   ServiceListPtr service_list(new ServiceList); | 
 |   ServiceProviderPtr service_provider; | 
 |   service_list->names.push_back(kService1); | 
 |   service_list->names.push_back(kService2); | 
 |   EXPECT_EQ(ZX_OK, AddService(kService1)); | 
 |   EXPECT_EQ(ZX_OK, AddService(kService2)); | 
 |  | 
 |   service_list->host_directory = provider_.service_directory()->CloneChannel().TakeChannel(); | 
 |   auto ns = MakeNamespace(std::move(service_list)); | 
 |  | 
 |   zx::channel svc_dir = ns->OpenServicesAsDirectory(); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService1)); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); | 
 |   // fdio_service_connect_at does not return an error if connection failed. | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), "fuchsia.test.NotExists")); | 
 |   RunLoopUntilIdle(); | 
 |   std::vector<std::pair<std::string, int>> connection_ctr_vec; | 
 |   for (const auto& e : connection_ctr_) { | 
 |     connection_ctr_vec.push_back(e); | 
 |   } | 
 |   EXPECT_THAT(connection_ctr_vec, | 
 |               ::testing::ElementsAre(StringIntPair(kService1, 1), StringIntPair(kService2, 2))); | 
 | } | 
 |  | 
 | // test that service is connected even when namespace dies right after connect request. | 
 | TEST_F(NamespaceHostDirectoryTest, AdditionalServices_NsDies) { | 
 |   static constexpr char kService1[] = "fuchsia.test.TestService1"; | 
 |   static constexpr char kService2[] = "fuchsia.test.TestService2"; | 
 |   ServiceListPtr service_list(new ServiceList); | 
 |   service_list->names.push_back(kService1); | 
 |   service_list->names.push_back(kService2); | 
 |   AddService(kService1); | 
 |   AddService(kService2); | 
 |   ServiceProviderPtr service_provider; | 
 |   service_list->host_directory = OpenAsDirectory(); | 
 |   auto ns = MakeNamespace(std::move(service_list)); | 
 |  | 
 |   zx::channel svc_dir = ns->OpenServicesAsDirectory(); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService1)); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); | 
 |   // fdio_service_connect_at does not return an error if connection failed. | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), "fuchsia.test.NotExists")); | 
 |   ns.Kill(); | 
 |   RunLoopUntilIdle(); | 
 |   std::vector<std::pair<std::string, int>> connection_ctr_vec; | 
 |   for (const auto& e : connection_ctr_) { | 
 |     connection_ctr_vec.push_back(e); | 
 |   } | 
 |   EXPECT_THAT(connection_ctr_vec, | 
 |               ::testing::ElementsAre(StringIntPair(kService1, 1), StringIntPair(kService2, 2))); | 
 |   connection_ctr_.clear(); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService1));  // cannot make anymore connections | 
 |   RunLoopUntilIdle(); | 
 |   // we should not see anymore processed conenction requests. | 
 |   EXPECT_EQ(0u, connection_ctr_.size()); | 
 | } | 
 |  | 
 | // test that service in parent is connected even when namespace dies right after connect request. | 
 | TEST_F(NamespaceHostDirectoryTest, AdditionalServices_InheritParent_nsDies) { | 
 |   static constexpr char kService1[] = "fuchsia.test.TestService1"; | 
 |   static constexpr char kService2[] = "fuchsia.test.TestService2"; | 
 |   ServiceListPtr parent_service_list(new ServiceList); | 
 |   parent_service_list->names.push_back(kService1); | 
 |   ServiceListPtr service_list(new ServiceList); | 
 |   service_list->names.push_back(kService2); | 
 |   AddService(kService1); | 
 |   AddService(kService2); | 
 |   parent_service_list->host_directory = OpenAsDirectory(); | 
 |   service_list->host_directory = OpenAsDirectory(); | 
 |   auto parent_ns = MakeNamespace(std::move(parent_service_list)); | 
 |   auto ns = MakeNamespace(std::move(service_list), parent_ns); | 
 |  | 
 |   zx::channel svc_dir = ns->OpenServicesAsDirectory(); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService1)); | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); | 
 |   // fdio_service_connect_at does not return an error if connection failed. | 
 |   EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), "fuchsia.test.NotExists")); | 
 |   ns.Kill(); | 
 |   RunLoopUntilIdle(); | 
 |   std::vector<std::pair<std::string, int>> connection_ctr_vec; | 
 |   for (const auto& e : connection_ctr_) { | 
 |     connection_ctr_vec.push_back(e); | 
 |   } | 
 |   EXPECT_THAT(connection_ctr_vec, | 
 |               ::testing::ElementsAre(StringIntPair(kService1, 1), StringIntPair(kService2, 1))); | 
 | } | 
 |  | 
 | }  // namespace | 
 | }  // namespace component |