| // Copyright 2020 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/devices/bin/driver_host2/driver_host.h" |
| |
| #include <fuchsia/driverhost/test/llcpp/fidl.h> |
| #include <fuchsia/io/cpp/fidl_test_base.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fidl/cpp/binding.h> |
| #include <lib/gtest/test_loop_fixture.h> |
| |
| #include <fbl/string_printf.h> |
| #include <fs/pseudo_dir.h> |
| #include <fs/service.h> |
| #include <fs/synchronous_vfs.h> |
| #include <gtest/gtest.h> |
| |
| namespace fdata = llcpp::fuchsia::data; |
| namespace fdf = llcpp::fuchsia::driver::framework; |
| namespace fio = fuchsia::io; |
| namespace fmem = fuchsia::mem; |
| namespace frunner = llcpp::fuchsia::component::runner; |
| namespace ftest = llcpp::fuchsia::driverhost::test; |
| |
| using Completer = fdf::DriverHost::Interface::StartCompleter::Sync; |
| |
| class TestFile : public fio::testing::File_TestBase { |
| public: |
| TestFile(std::string_view path) : path_(std::move(path)) {} |
| |
| private: |
| void GetBuffer(uint32_t flags, GetBufferCallback callback) override { |
| EXPECT_EQ(fio::VMO_FLAG_READ | fio::VMO_FLAG_EXEC | fio::VMO_FLAG_PRIVATE, flags); |
| zx::channel client_end, server_end; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &client_end, &server_end)); |
| EXPECT_EQ(ZX_OK, fdio_open(path_.data(), fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE, |
| server_end.release())); |
| |
| llcpp::fuchsia::io::File::SyncClient file(std::move(client_end)); |
| auto result = file.GetBuffer(flags); |
| EXPECT_TRUE(result.ok()); |
| auto buffer = fmem::Buffer::New(); |
| buffer->vmo = std::move(result->buffer->vmo); |
| buffer->size = result->buffer->size; |
| callback(ZX_OK, std::move(buffer)); |
| } |
| |
| void NotImplemented_(const std::string& name) override { |
| printf("Not implemented: File::%s\n", name.data()); |
| } |
| |
| std::string_view path_; |
| }; |
| |
| class TestDirectory : public fio::testing::Directory_TestBase { |
| public: |
| using OpenHandler = fit::function<void(uint32_t flags, std::string path, |
| fidl::InterfaceRequest<fio::Node> object)>; |
| |
| void SetOpenHandler(OpenHandler open_handler) { open_handler_ = std::move(open_handler); } |
| |
| private: |
| void Open(uint32_t flags, uint32_t mode, std::string path, |
| fidl::InterfaceRequest<fio::Node> object) override { |
| open_handler_(flags, std::move(path), std::move(object)); |
| } |
| |
| void NotImplemented_(const std::string& name) override { |
| printf("Not implemented: Directory::%s\n", name.data()); |
| } |
| |
| OpenHandler open_handler_; |
| }; |
| |
| class TestTransaction : public fidl::Transaction { |
| public: |
| TestTransaction(zx_status_t* epitaph) : epitaph_(epitaph) {} |
| |
| private: |
| std::unique_ptr<Transaction> TakeOwnership() override { |
| return std::make_unique<TestTransaction>(epitaph_); |
| } |
| |
| zx_status_t Reply(fidl::OutgoingMessage* message) override { |
| EXPECT_TRUE(false); |
| return ZX_OK; |
| } |
| |
| void Close(zx_status_t epitaph) override { *epitaph_ = epitaph; } |
| |
| zx_status_t* const epitaph_; |
| }; |
| |
| struct StartDriverResult { |
| zx::channel driver; |
| zx::channel outgoing_dir; |
| }; |
| |
| class DriverHostTest : public gtest::TestLoopFixture { |
| public: |
| void TearDown() override { |
| loop_.Shutdown(); |
| TestLoopFixture::TearDown(); |
| } |
| |
| protected: |
| async::Loop& loop() { return loop_; } |
| fdf::DriverHost::Interface* driver_host() { return &driver_host_; } |
| |
| void AddEntry(fs::Service::Connector connector) { |
| EXPECT_EQ(ZX_OK, svc_dir_->AddEntry(ftest::Incoming::Name, |
| fbl::MakeRefCounted<fs::Service>(std::move(connector)))); |
| } |
| |
| StartDriverResult StartDriver(fidl::VectorView<fdf::NodeSymbol> symbols = {}, |
| fidl::tracking_ptr<zx::channel> node = nullptr) { |
| zx_status_t epitaph = ZX_OK; |
| TestTransaction transaction(&epitaph); |
| |
| zx::channel pkg_client_end, pkg_server_end; |
| EXPECT_EQ(ZX_OK, zx::channel::create(0, &pkg_client_end, &pkg_server_end)); |
| zx::channel svc_client_end, svc_server_end; |
| EXPECT_EQ(ZX_OK, zx::channel::create(0, &svc_client_end, &svc_server_end)); |
| frunner::ComponentNamespaceEntry ns_entries[] = { |
| frunner::ComponentNamespaceEntry::Builder( |
| std::make_unique<frunner::ComponentNamespaceEntry::Frame>()) |
| .set_path(std::make_unique<fidl::StringView>("/pkg")) |
| .set_directory(std::make_unique<zx::channel>(std::move(pkg_client_end))) |
| .build(), |
| frunner::ComponentNamespaceEntry::Builder( |
| std::make_unique<frunner::ComponentNamespaceEntry::Frame>()) |
| .set_path(std::make_unique<fidl::StringView>("/svc")) |
| .set_directory(std::make_unique<zx::channel>(std::move(svc_client_end))) |
| .build(), |
| }; |
| TestFile file("/pkg/driver/test_driver.so"); |
| fidl::Binding<fio::File> file_binding(&file); |
| TestDirectory pkg_directory; |
| fidl::Binding<fio::Directory> pkg_binding(&pkg_directory); |
| pkg_binding.Bind(std::move(pkg_server_end), loop().dispatcher()); |
| pkg_directory.SetOpenHandler( |
| [this, &file_binding](uint32_t flags, std::string path, auto object) { |
| EXPECT_EQ(fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE, flags); |
| EXPECT_EQ("driver/library.so", path); |
| file_binding.Bind(object.TakeChannel(), loop().dispatcher()); |
| }); |
| EXPECT_EQ(ZX_OK, vfs_.ServeDirectory(svc_dir_, std::move(svc_server_end))); |
| fdata::DictionaryEntry program_entries[] = { |
| { |
| .key = "binary", |
| .value = fdata::DictionaryValue::WithStr( |
| std::make_unique<fidl::StringView>("driver/library.so")), |
| }, |
| }; |
| zx::channel outgoing_dir_client_end, outgoing_dir_server_end; |
| EXPECT_EQ(ZX_OK, zx::channel::create(0, &outgoing_dir_client_end, &outgoing_dir_server_end)); |
| zx::channel driver_client_end, driver_server_end; |
| EXPECT_EQ(ZX_OK, zx::channel::create(0, &driver_client_end, &driver_server_end)); |
| { |
| Completer completer(&transaction); |
| driver_host()->Start( |
| fdf::DriverStartArgs::Builder(std::make_unique<fdf::DriverStartArgs::Frame>()) |
| .set_node(std::move(node)) |
| .set_symbols(std::make_unique<fidl::VectorView<fdf::NodeSymbol>>(std::move(symbols))) |
| .set_ns(std::make_unique<fidl::VectorView<frunner::ComponentNamespaceEntry>>( |
| fidl::unowned_vec(ns_entries))) |
| .set_program(std::make_unique<fdata::Dictionary>( |
| fdata::Dictionary::Builder(std::make_unique<fdata::Dictionary::Frame>()) |
| .set_entries(std::make_unique<fidl::VectorView<fdata::DictionaryEntry>>( |
| fidl::unowned_vec(program_entries))) |
| .build())) |
| .set_outgoing_dir(std::make_unique<zx::channel>(std::move(outgoing_dir_server_end))) |
| .build(), |
| std::move(driver_server_end), completer); |
| } |
| loop().RunUntilIdle(); |
| EXPECT_EQ(ZX_OK, epitaph); |
| |
| return { |
| .driver = std::move(driver_client_end), |
| .outgoing_dir = std::move(outgoing_dir_client_end), |
| }; |
| } |
| |
| private: |
| async::Loop loop_{&kAsyncLoopConfigNoAttachToCurrentThread}; |
| DriverHost driver_host_{&loop_}; |
| fs::SynchronousVfs vfs_{loop_.dispatcher()}; |
| fbl::RefPtr<fs::PseudoDir> svc_dir_ = fbl::MakeRefCounted<fs::PseudoDir>(); |
| }; |
| |
| // Start a single driver in the driver host. |
| TEST_F(DriverHostTest, Start_SingleDriver) { |
| auto [driver, outgoing_dir] = StartDriver(); |
| |
| // Stop the driver. As it's the last driver in the driver host, this will |
| // cause the driver host to stop. |
| driver.reset(); |
| loop().RunUntilIdle(); |
| EXPECT_EQ(ASYNC_LOOP_QUIT, loop().GetState()); |
| } |
| |
| // Start multiple drivers in the driver host. |
| TEST_F(DriverHostTest, Start_MultipleDrivers) { |
| auto [driver_1, outgoing_dir_1] = StartDriver(); |
| auto [driver_2, outgoing_dir_2] = StartDriver(); |
| |
| driver_1.reset(); |
| loop().RunUntilIdle(); |
| EXPECT_EQ(ASYNC_LOOP_RUNNABLE, loop().GetState()); |
| |
| driver_2.reset(); |
| loop().RunUntilIdle(); |
| EXPECT_EQ(ASYNC_LOOP_QUIT, loop().GetState()); |
| } |
| |
| // Start a single driver, and connect to its outgoing service. |
| TEST_F(DriverHostTest, Start_OutgoingServices) { |
| auto [driver, outgoing_dir] = StartDriver(); |
| |
| auto path = fbl::StringPrintf("svc/%s", ftest::Outgoing::Name); |
| zx::channel client_end, server_end; |
| EXPECT_EQ(ZX_OK, zx::channel::create(0, &client_end, &server_end)); |
| zx_status_t status = |
| fdio_service_connect_at(outgoing_dir.get(), path.data(), server_end.release()); |
| EXPECT_EQ(ZX_OK, status); |
| |
| status = ZX_ERR_INVALID_ARGS; |
| fidl::Client<ftest::Outgoing> outgoing( |
| std::move(client_end), loop().dispatcher(), |
| [&status](fidl::UnbindInfo info) { status = info.status; }); |
| loop().RunUntilIdle(); |
| EXPECT_EQ(ZX_ERR_STOP, status); |
| |
| driver.reset(); |
| loop().RunUntilIdle(); |
| EXPECT_EQ(ASYNC_LOOP_QUIT, loop().GetState()); |
| } |
| |
| // Start a single driver, and receive an incoming connection to our service. |
| TEST_F(DriverHostTest, Start_IncomingServices) { |
| bool connected = false; |
| AddEntry([&connected](zx::channel request) { |
| connected = true; |
| return ZX_OK; |
| }); |
| auto [driver, outgoing_dir] = StartDriver(); |
| EXPECT_TRUE(connected); |
| |
| driver.reset(); |
| loop().RunUntilIdle(); |
| EXPECT_EQ(ASYNC_LOOP_QUIT, loop().GetState()); |
| } |
| |
| static bool called = false; |
| void func() { called = true; } |
| |
| // Start a single driver, and receive a call to a shared function. |
| TEST_F(DriverHostTest, Start_NodeSymbols) { |
| fdf::NodeSymbol symbols[] = { |
| fdf::NodeSymbol::Builder(std::make_unique<fdf::NodeSymbol::Frame>()) |
| .set_name(std::make_unique<fidl::StringView>("func")) |
| .set_address(std::make_unique<zx_vaddr_t>(reinterpret_cast<zx_vaddr_t>(func))) |
| .build(), |
| }; |
| auto [driver, outgoing_dir] = StartDriver(fidl::unowned_vec(symbols)); |
| EXPECT_TRUE(called); |
| |
| driver.reset(); |
| loop().RunUntilIdle(); |
| EXPECT_EQ(ASYNC_LOOP_QUIT, loop().GetState()); |
| } |
| |
| // Start a driver with invalid arguments. |
| TEST_F(DriverHostTest, Start_InvalidStartArgs) { |
| zx_status_t epitaph = ZX_OK; |
| TestTransaction transaction(&epitaph); |
| |
| // DriverStartArgs::ns not set. |
| zx::channel driver_client_end, driver_server_end; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &driver_client_end, &driver_server_end)); |
| { |
| Completer completer(&transaction); |
| driver_host()->Start(fdf::DriverStartArgs(), std::move(driver_server_end), completer); |
| } |
| EXPECT_EQ(ZX_ERR_INVALID_ARGS, epitaph); |
| |
| // DriverStartArgs::ns is missing "/pkg" entry. |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &driver_client_end, &driver_server_end)); |
| { |
| Completer completer(&transaction); |
| driver_host()->Start( |
| fdf::DriverStartArgs::Builder(std::make_unique<fdf::DriverStartArgs::Frame>()) |
| .set_ns(std::make_unique<fidl::VectorView<frunner::ComponentNamespaceEntry>>()) |
| .build(), |
| std::move(driver_server_end), completer); |
| } |
| EXPECT_EQ(ZX_ERR_NOT_FOUND, epitaph); |
| |
| // DriverStartArgs::program not set. |
| frunner::ComponentNamespaceEntry entries1[] = { |
| frunner::ComponentNamespaceEntry::Builder( |
| std::make_unique<frunner::ComponentNamespaceEntry::Frame>()) |
| .set_path(std::make_unique<fidl::StringView>("/pkg")) |
| .set_directory(std::make_unique<zx::channel>()) |
| .build(), |
| }; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &driver_client_end, &driver_server_end)); |
| { |
| Completer completer(&transaction); |
| driver_host()->Start( |
| fdf::DriverStartArgs::Builder(std::make_unique<fdf::DriverStartArgs::Frame>()) |
| .set_ns(std::make_unique<fidl::VectorView<frunner::ComponentNamespaceEntry>>( |
| fidl::unowned_vec(entries1))) |
| .build(), |
| std::move(driver_server_end), completer); |
| } |
| EXPECT_EQ(ZX_ERR_INVALID_ARGS, epitaph); |
| |
| // DriverStartArgs::program is missing "binary" entry. |
| frunner::ComponentNamespaceEntry entries2[] = { |
| frunner::ComponentNamespaceEntry::Builder( |
| std::make_unique<frunner::ComponentNamespaceEntry::Frame>()) |
| .set_path(std::make_unique<fidl::StringView>("/pkg")) |
| .set_directory(std::make_unique<zx::channel>()) |
| .build(), |
| }; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &driver_client_end, &driver_server_end)); |
| { |
| Completer completer(&transaction); |
| driver_host()->Start( |
| fdf::DriverStartArgs::Builder(std::make_unique<fdf::DriverStartArgs::Frame>()) |
| .set_ns(std::make_unique<fidl::VectorView<frunner::ComponentNamespaceEntry>>( |
| fidl::unowned_vec(entries2))) |
| .set_program(std::make_unique<fdata::Dictionary>()) |
| .build(), |
| std::move(driver_server_end), completer); |
| } |
| EXPECT_EQ(ZX_ERR_NOT_FOUND, epitaph); |
| } |
| |
| TEST_F(DriverHostTest, InvalidHandleRights) { |
| bool connected = false; |
| AddEntry([&connected](zx::channel request) { |
| connected = true; |
| return ZX_OK; |
| }); |
| zx::channel client, server; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &client, &server)); |
| ASSERT_EQ(ZX_OK, client.replace(ZX_RIGHT_TRANSFER, &client)); |
| // This should fail when node rights are not ZX_DEFAULT_CHANNEL_RIGHTS. |
| StartDriver({}, fidl::unowned_ptr(&client)); |
| EXPECT_FALSE(connected); |
| } |
| |
| // Start a driver with an invalid binary. |
| TEST_F(DriverHostTest, Start_InvalidBinary) { |
| zx_status_t epitaph = ZX_OK; |
| TestTransaction transaction(&epitaph); |
| |
| zx::channel pkg_client_end, pkg_server_end; |
| EXPECT_EQ(ZX_OK, zx::channel::create(0, &pkg_client_end, &pkg_server_end)); |
| frunner::ComponentNamespaceEntry ns_entries[] = { |
| frunner::ComponentNamespaceEntry::Builder( |
| std::make_unique<frunner::ComponentNamespaceEntry::Frame>()) |
| .set_path(std::make_unique<fidl::StringView>("/pkg")) |
| .set_directory(std::make_unique<zx::channel>(std::move(pkg_client_end))) |
| .build(), |
| }; |
| TestFile file("/pkg/driver/test_not_driver.so"); |
| fidl::Binding<fio::File> file_binding(&file); |
| TestDirectory pkg_directory; |
| fidl::Binding<fio::Directory> pkg_binding(&pkg_directory); |
| pkg_binding.Bind(std::move(pkg_server_end), loop().dispatcher()); |
| pkg_directory.SetOpenHandler( |
| [this, &file_binding](uint32_t flags, std::string path, auto object) { |
| EXPECT_EQ(fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE, flags); |
| EXPECT_EQ("driver/library.so", path); |
| file_binding.Bind(object.TakeChannel(), loop().dispatcher()); |
| }); |
| fdata::DictionaryEntry program_entries[] = { |
| { |
| .key = "binary", |
| .value = fdata::DictionaryValue::WithStr( |
| std::make_unique<fidl::StringView>("driver/library.so")), |
| }, |
| }; |
| zx::channel driver_client_end, driver_server_end; |
| EXPECT_EQ(ZX_OK, zx::channel::create(0, &driver_client_end, &driver_server_end)); |
| { |
| Completer completer(&transaction); |
| driver_host()->Start( |
| fdf::DriverStartArgs::Builder(std::make_unique<fdf::DriverStartArgs::Frame>()) |
| .set_ns(std::make_unique<fidl::VectorView<frunner::ComponentNamespaceEntry>>( |
| fidl::unowned_vec(ns_entries))) |
| .set_program(std::make_unique<fdata::Dictionary>( |
| fdata::Dictionary::Builder(std::make_unique<fdata::Dictionary::Frame>()) |
| .set_entries(std::make_unique<fidl::VectorView<fdata::DictionaryEntry>>( |
| fidl::unowned_vec(program_entries))) |
| .build())) |
| .build(), |
| std::move(driver_server_end), completer); |
| } |
| loop().RunUntilIdle(); |
| EXPECT_EQ(ZX_ERR_NOT_FOUND, epitaph); |
| } |