blob: 09608bd1af981dbdafe7f8238cf6595a5739b8f2 [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_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);
}