blob: 453642898c330f92faf7392c602d888885c71f59 [file] [log] [blame]
// 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_host/driver_host.h"
#include <fidl/fuchsia.driverhost.test/cpp/wire.h>
#include <fuchsia/io/cpp/fidl_test_base.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fdf/cpp/env.h>
#include <lib/fdf/internal.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/inspect/cpp/reader.h>
#include <lib/inspect/testing/cpp/inspect.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "fidl/fuchsia.data/cpp/wire_types.h"
#include "src/storage/lib/vfs/cpp/pseudo_dir.h"
#include "src/storage/lib/vfs/cpp/service.h"
#include "src/storage/lib/vfs/cpp/synchronous_vfs.h"
namespace fdf {
using namespace fuchsia_driver_framework;
} // namespace fdf
namespace fdata = fuchsia_data;
namespace fdh = fuchsia_driver_host;
namespace fio = fuchsia::io;
namespace frunner = fuchsia_component_runner;
namespace ftest = fuchsia_driverhost_test;
using Completer = fidl::Server<fdh::DriverHost>::StartCompleter::Sync;
using namespace inspect::testing;
class FakeContext : public fpromise::context {
public:
fpromise::executor* executor() const override {
EXPECT_TRUE(false);
return nullptr;
}
fpromise::suspended_task suspend_task() override {
EXPECT_TRUE(false);
return fpromise::suspended_task();
}
};
class TestFile : public fio::testing::File_TestBase {
public:
TestFile(std::string_view path) : path_(std::move(path)) {}
private:
void GetBackingMemory(fio::VmoFlags flags, GetBackingMemoryCallback callback) override {
EXPECT_EQ(fio::VmoFlags::READ | fio::VmoFlags::EXECUTE | fio::VmoFlags::PRIVATE_CLONE, flags);
auto endpoints = fidl::Endpoints<fuchsia_io::File>::Create();
EXPECT_EQ(ZX_OK, fdio_open(path_.data(),
static_cast<uint32_t>(fio::OpenFlags::RIGHT_READABLE |
fio::OpenFlags::RIGHT_EXECUTABLE),
endpoints.server.channel().release()));
fidl::WireSyncClient<fuchsia_io::File> file(std::move(endpoints.client));
fidl::WireResult result = file->GetBackingMemory(fuchsia_io::wire::VmoFlags(uint32_t(flags)));
EXPECT_TRUE(result.ok()) << result.FormatDescription();
auto* res = result.Unwrap();
if (res->is_error()) {
callback(fio::File_GetBackingMemory_Result::WithErr(std::move(res->error_value())));
}
callback(fio::File_GetBackingMemory_Result::WithResponse(
fio::File_GetBackingMemory_Response(std::move(res->value()->vmo))));
}
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(fio::OpenFlags flags, std::string path,
fidl::InterfaceRequest<fio::Node> object)>;
void SetOpenHandler(OpenHandler open_handler) { open_handler_ = std::move(open_handler); }
private:
void Open(fio::OpenFlags flags, fio::ModeType 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, fidl::WriteOptions) override {
epitaph_ = message->status();
return ZX_OK;
}
void Close(zx_status_t epitaph) override { epitaph_ = epitaph; }
zx_status_t& epitaph_;
};
struct StartDriverResult {
fidl::ClientEnd<fdh::Driver> driver;
fidl::ClientEnd<fuchsia_io::Directory> outgoing_dir;
};
class DriverHostTest : public testing::Test {
protected:
void SetUp() override { fdf_env_start(); }
async::Loop& loop() { return loop_; }
fidl::Server<fdh::DriverHost>& driver_host() { return *driver_host_; }
void set_driver_host(std::unique_ptr<driver_host::DriverHost> driver_host) {
driver_host_ = std::move(driver_host);
}
void AddEntry(fs::Service::Connector connector) {
EXPECT_EQ(ZX_OK, svc_dir_->AddEntry(fidl::DiscoverableProtocolName<ftest::Incoming>,
fbl::MakeRefCounted<fs::Service>(std::move(connector))));
}
StartDriverResult StartDriver(std::vector<fdf::NodeSymbol> symbols = {},
fidl::ClientEnd<fuchsia_driver_framework::Node>* node = nullptr,
zx_status_t expected_epitaph = ZX_OK) {
auto pkg_endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
auto svc_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
EXPECT_TRUE(svc_endpoints.is_ok());
std::vector<frunner::ComponentNamespaceEntry> ns_entries;
ns_entries.push_back({{.path = "/pkg", .directory = std::move(pkg_endpoints.client)}});
ns_entries.push_back({{.path = "/svc", .directory = std::move(svc_endpoints->client)}});
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(pkg_endpoints.server.TakeChannel(), loop_.dispatcher());
pkg_directory.SetOpenHandler(
[this, &file_binding](fio::OpenFlags flags, std::string path, auto object) {
EXPECT_EQ(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::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_endpoints->server)));
std::vector<fdata::DictionaryEntry> program_entries = {
{{
.key = "binary",
.value = std::make_unique<fdata::DictionaryValue>(
fdata::DictionaryValue::WithStr("driver/library.so")),
}},
};
auto outgoing_dir_endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
auto driver_endpoints = fidl::CreateEndpoints<fdh::Driver>();
EXPECT_TRUE(driver_endpoints.is_ok());
auto driver_host_endpoints = fidl::CreateEndpoints<fdh::DriverHost>();
EXPECT_TRUE(driver_host_endpoints.is_ok());
fidl::BindServer(loop().dispatcher(), std::move(driver_host_endpoints->server), &driver_host());
fidl::Client client(std::move(driver_host_endpoints->client), loop().dispatcher());
zx_status_t epitaph = ZX_ERR_BAD_STATE;
{
fdata::Dictionary dictionary = {{.entries = std::move(program_entries)}};
fdf::DriverStartArgs driver_start_args = {{
.node = node != nullptr ? std::optional(std::move(*node)) : std::nullopt,
.symbols = std::move(symbols),
.url = "fuchsia-pkg://fuchsia.com/driver#meta/driver.cm",
.program = std::move(dictionary),
.incoming = std::move(ns_entries),
.outgoing_dir = std::move(std::move(outgoing_dir_endpoints.server)),
}};
client->Start({std::move(driver_start_args), std::move(driver_endpoints->server)})
.Then([&](auto result) {
if (result.is_ok()) {
epitaph = ZX_OK;
loop().Quit();
return;
}
if (result.error_value().is_framework_error()) {
epitaph = result.error_value().framework_error().status();
loop().Quit();
return;
}
epitaph = result.error_value().domain_error();
loop().Quit();
});
}
EXPECT_EQ(ZX_ERR_CANCELED, loop().Run());
loop().ResetQuit();
EXPECT_EQ(expected_epitaph, epitaph);
return {
.driver = std::move(driver_endpoints->client),
.outgoing_dir = std::move(outgoing_dir_endpoints.client),
};
}
inspect::Hierarchy Inspect() {
FakeContext context;
auto inspector = driver_host_->Inspect()(context).take_value();
return inspect::ReadFromInspector(inspector)(context).take_value();
}
private:
inspect::Inspector inspector_;
async::Loop loop_{&kAsyncLoopConfigNeverAttachToThread};
std::unique_ptr<driver_host::DriverHost> driver_host_ =
std::make_unique<driver_host::DriverHost>(inspector_, 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();
// This will schedule the Stop task on the driver's dispatcher.
// We do not check the return value of |RunUntilIdle|, as we cannot
// guarantee that the Stop task will or will not complete (and quit the loop)
// by the time |RunUntilIdle| returns.
loop().RunUntilIdle();
fdf_internal_wait_until_all_dispatchers_destroyed();
EXPECT_EQ(ZX_ERR_CANCELED, 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();
EXPECT_EQ(ZX_OK, loop().RunUntilIdle());
fdf_internal_wait_until_all_dispatchers_idle();
EXPECT_EQ(ZX_OK, loop().RunUntilIdle());
EXPECT_EQ(ASYNC_LOOP_RUNNABLE, loop().GetState());
driver_2.reset();
loop().RunUntilIdle();
fdf_internal_wait_until_all_dispatchers_destroyed();
EXPECT_EQ(ZX_ERR_CANCELED, 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();
// Note, we skip the leading '/' in the default path to form a relative path.
auto path = fidl::DiscoverableProtocolDefaultPath<ftest::Outgoing> + 1;
auto client_end = component::ConnectAt<ftest::Outgoing>(outgoing_dir, path);
ASSERT_TRUE(client_end.is_ok());
class EventHandler : public fidl::WireAsyncEventHandler<ftest::Outgoing> {
public:
EventHandler() = default;
zx_status_t status() const { return status_; }
void on_fidl_error(fidl::UnbindInfo info) override { status_ = info.status(); }
private:
zx_status_t status_ = ZX_ERR_INVALID_ARGS;
};
EventHandler event_handler;
fidl::WireClient<ftest::Outgoing> outgoing(std::move(*client_end), loop().dispatcher(),
&event_handler);
while (event_handler.status() == ZX_ERR_INVALID_ARGS) {
EXPECT_EQ(ZX_OK, loop().RunUntilIdle());
}
EXPECT_EQ(ZX_ERR_STOP, event_handler.status());
driver.reset();
loop().RunUntilIdle();
fdf_internal_wait_until_all_dispatchers_destroyed();
EXPECT_EQ(ZX_ERR_CANCELED, 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](auto request) {
connected = true;
return ZX_OK;
});
auto [driver, outgoing_dir] = StartDriver();
EXPECT_EQ(ZX_OK, loop().RunUntilIdle());
EXPECT_TRUE(connected);
driver.reset();
loop().RunUntilIdle();
fdf_internal_wait_until_all_dispatchers_destroyed();
EXPECT_EQ(ZX_ERR_CANCELED, loop().RunUntilIdle());
EXPECT_EQ(ASYNC_LOOP_QUIT, loop().GetState());
}
// Start a single driver, and return an error on start.
TEST_F(DriverHostTest, Start_ReturnError) {
zx_status_t error = ZX_ERR_STOP;
std::vector<fdf::NodeSymbol> symbols = {
{{.name = "error", .address = reinterpret_cast<zx_vaddr_t>(&error)}},
};
auto [driver, outgoing_dir] = StartDriver(std::move(symbols), nullptr, error);
driver.reset();
loop().RunUntilIdle();
fdf_internal_wait_until_all_dispatchers_destroyed();
EXPECT_EQ(ZX_OK, loop().RunUntilIdle());
// We never started our first driver, so the driver host would not attempt to
// quit the loop after the last driver has stopped.
EXPECT_EQ(ASYNC_LOOP_RUNNABLE, 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) {
std::vector<fdf::NodeSymbol> symbols = {
{{.name = "func", .address = reinterpret_cast<zx_vaddr_t>(Func)}},
};
auto [driver, outgoing_dir] = StartDriver(std::move(symbols));
EXPECT_TRUE(called);
driver.reset();
loop().RunUntilIdle();
fdf_internal_wait_until_all_dispatchers_destroyed();
EXPECT_EQ(ZX_ERR_CANCELED, loop().RunUntilIdle());
EXPECT_EQ(ASYNC_LOOP_QUIT, loop().GetState());
}
// Start two drivers, and verify that different dispatchers are used.
TEST_F(DriverHostTest, Start_DifferentDispatcher) {
fdf_dispatcher_t* dispatcher_1 = nullptr;
std::vector<fdf::NodeSymbol> symbols_1 = {
{{.name = "dispatcher", .address = reinterpret_cast<zx_vaddr_t>(&dispatcher_1)}},
};
auto [driver_1, outgoing_dir_1] = StartDriver(std::move(symbols_1));
fdf_dispatcher_t* dispatcher_2 = nullptr;
std::vector<fdf::NodeSymbol> symbols_2 = {
{{.name = "dispatcher", .address = reinterpret_cast<zx_vaddr_t>(&dispatcher_2)}},
};
auto [driver_2, outgoing_dir_2] = StartDriver(std::move(symbols_2));
EXPECT_NE(dispatcher_1, dispatcher_2);
driver_1.reset();
driver_2.reset();
loop().RunUntilIdle();
fdf_internal_wait_until_all_dispatchers_destroyed();
EXPECT_EQ(ZX_ERR_CANCELED, loop().RunUntilIdle());
EXPECT_EQ(ASYNC_LOOP_QUIT, loop().GetState());
}
// Start a driver with invalid arguments.
TEST_F(DriverHostTest, Start_InvalidStartArgs) {
auto call_start = [&](fdf::DriverStartArgs start_args) -> zx_status_t {
auto driver_host_endpoints = fidl::CreateEndpoints<fdh::DriverHost>();
fidl::BindServer(loop().dispatcher(), std::move(driver_host_endpoints->server), &driver_host());
fidl::Client client(std::move(driver_host_endpoints->client), loop().dispatcher());
auto endpoints = fidl::CreateEndpoints<fdh::Driver>();
zx_status_t status = ZX_OK;
client->Start({std::move(start_args), std::move(endpoints->server)}).Then([&](auto result) {
if (result.is_ok()) {
status = ZX_OK;
return;
}
if (result.error_value().is_framework_error()) {
status = result.error_value().framework_error().status();
return;
}
status = result.error_value().domain_error();
});
loop().RunUntilIdle();
fdf_internal_wait_until_all_dispatchers_idle();
loop().RunUntilIdle();
return status;
};
// DriverStartArgs::ns is missing "/pkg" entry.
EXPECT_EQ(ZX_ERR_INVALID_ARGS, call_start(fdf::DriverStartArgs()));
// DriverStartArgs::ns not set.
EXPECT_EQ(ZX_ERR_INVALID_ARGS, call_start(fdf::DriverStartArgs{{
.url = "fuchsia-pkg://fuchsia.com/driver#meta/driver.cm",
}}));
// DriverStartArgs::ns is missing "/pkg" entry.
EXPECT_EQ(ZX_ERR_INVALID_ARGS, call_start(fdf::DriverStartArgs{{
.url = "fuchsia-pkg://fuchsia.com/driver#meta/driver.cm",
}}));
{
// DriverStartArgs::program not set.
std::vector<frunner::ComponentNamespaceEntry> entries;
entries.push_back({{.path = "/pkg", .directory = fidl::ClientEnd<fuchsia_io::Directory>()}});
EXPECT_EQ(ZX_ERR_INVALID_ARGS, call_start(fdf::DriverStartArgs{{
.url = "fuchsia-pkg://fuchsia.com/driver#meta/driver.cm",
.incoming = std::move(entries),
}}));
}
{
// DriverStartArgs::program is missing "binary" entry.
std::vector<frunner::ComponentNamespaceEntry> entries;
entries.push_back({{.path = "/pkg", .directory = fidl::ClientEnd<fuchsia_io::Directory>()}});
EXPECT_EQ(ZX_ERR_INVALID_ARGS, call_start(fdf::DriverStartArgs{{
.url = "fuchsia-pkg://fuchsia.com/driver#meta/driver.cm",
.program = fdata::Dictionary{},
.incoming = std::move(entries),
}}));
}
}
// Start a driver with an invalid client-end.
TEST_F(DriverHostTest, InvalidHandleRights) {
bool connected = false;
AddEntry([&connected](auto request) {
connected = true;
return ZX_OK;
});
auto endpoints = fidl::Endpoints<fdf::Node>::Create();
auto& client_end = endpoints.client.channel();
ASSERT_EQ(ZX_OK, client_end.replace(ZX_RIGHT_TRANSFER, &client_end));
// This should fail when node rights are not ZX_DEFAULT_CHANNEL_RIGHTS.
StartDriver({}, &endpoints.client, ZX_ERR_INVALID_ARGS);
EXPECT_FALSE(connected);
}
// Start a driver with an invalid binary.
TEST_F(DriverHostTest, Start_InvalidBinary) {
auto pkg_endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
std::vector<frunner::ComponentNamespaceEntry> ns_entries;
ns_entries.push_back({{.path = "/pkg", .directory = std::move(pkg_endpoints.client)}});
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(pkg_endpoints.server.TakeChannel(), loop().dispatcher());
pkg_directory.SetOpenHandler(
[this, &file_binding](fio::OpenFlags flags, std::string path, auto object) {
EXPECT_EQ(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE, flags);
EXPECT_EQ("driver/library.so", path);
file_binding.Bind(object.TakeChannel(), loop().dispatcher());
});
std::vector<fdata::DictionaryEntry> program_entries = {{{
.key = "binary",
.value = std::make_unique<fdata::DictionaryValue>(
fdata::DictionaryValue::WithStr("driver/library.so")),
}}};
auto driver_host_endpoints = fidl::CreateEndpoints<fdh::DriverHost>();
fidl::BindServer(loop().dispatcher(), std::move(driver_host_endpoints->server), &driver_host());
fidl::Client client(std::move(driver_host_endpoints->client), loop().dispatcher());
auto driver_endpoints = fidl::CreateEndpoints<fdh::Driver>();
ASSERT_TRUE(driver_endpoints.is_ok());
zx_status_t status = ZX_OK;
fdata::Dictionary dictionary{{.entries = std::move(program_entries)}};
fdf::DriverStartArgs driver_start_args{{
.url = "fuchsia-pkg://fuchsia.com/driver#meta/driver.cm",
.program = std::move(dictionary),
.incoming = std::move(ns_entries),
}};
client->Start({std::move(driver_start_args), std::move(driver_endpoints->server)})
.Then([&](auto result) {
if (result.is_ok()) {
status = ZX_OK;
return;
}
if (result.error_value().is_framework_error()) {
status = result.error_value().framework_error().status();
return;
}
status = result.error_value().domain_error();
});
EXPECT_EQ(ZX_OK, loop().RunUntilIdle());
fdf_internal_wait_until_all_dispatchers_idle();
EXPECT_EQ(ZX_OK, loop().RunUntilIdle());
EXPECT_EQ(ZX_ERR_NOT_FOUND, status);
}
// Start multiple drivers and inspect the driver host.
TEST_F(DriverHostTest, StartAndInspect) {
auto [driver_1, outgoing_dir_1] = StartDriver();
auto [driver_2, outgoing_dir_2] = StartDriver();
EXPECT_THAT(Inspect(),
AllOf(NodeMatches(NameMatches("root")),
ChildrenMatch(UnorderedElementsAre(
AllOf(NodeMatches(AllOf(
NameMatches("driver-1"),
PropertyList(UnorderedElementsAre(StringIs(
"url", "fuchsia-pkg://fuchsia.com/driver#meta/driver.cm")))))),
AllOf(NodeMatches(AllOf(
NameMatches("driver-2"),
PropertyList(UnorderedElementsAre(StringIs(
"url", "fuchsia-pkg://fuchsia.com/driver#meta/driver.cm"))))))))));
driver_1.reset();
driver_2.reset();
loop().RunUntilIdle();
fdf_internal_wait_until_all_dispatchers_destroyed();
EXPECT_EQ(ZX_ERR_CANCELED, loop().RunUntilIdle());
EXPECT_EQ(ASYNC_LOOP_QUIT, loop().GetState());
}