blob: a147d14a814ec0ea102b159a54513cdfacbde798 [file] [log] [blame]
// 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 "paver.h"
#include <algorithm>
#include <fs/pseudo_dir.h>
#include <fs/service.h>
#include <fs/synchronous_vfs.h>
#include <fuchsia/paver/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/driver-integration-test/fixture.h>
#include <lib/fidl-async/cpp/bind.h>
#include <lib/zx/channel.h>
#include <zircon/boot/netboot.h>
#include <zxtest/zxtest.h>
namespace {
constexpr char kFakeData[] = "lalala";
}
TEST(PaverTest, Constructor) { netsvc::Paver paver(zx::channel); }
TEST(PaverTest, GetSingleton) { ASSERT_NOT_NULL(netsvc::Paver::Get()); }
TEST(PaverTest, InitialInProgressFalse) {
zx::channel chan;
fbl::unique_fd fd;
netsvc::Paver paver_(std::move(chan), std::move(fd));
ASSERT_FALSE(paver_.InProgress());
}
TEST(PaverTest, InitialExitCodeValid) {
zx::channel chan;
fbl::unique_fd fd;
netsvc::Paver paver_(std::move(chan), std::move(fd));
ASSERT_OK(paver_.exit_code());
}
namespace {
enum class Command {
kUnknown,
kInitializeAbr,
kQueryActiveConfiguration,
kQueryConfigurationStatus,
kSetConfigurationActive,
kSetConfigurationUnbootable,
kSetActiveConfigurationHealthy,
kReadAsset,
kWriteAsset,
kWriteVolumes,
kWriteBootloader,
kWriteDataFile,
kWipeVolumes,
kInitializePartitionTables,
kWipePartitionTables,
};
class FakePaver : public ::llcpp::fuchsia::paver::Paver::Interface {
public:
zx_status_t Connect(async_dispatcher_t* dispatcher, zx::channel request) {
return fidl::Bind(dispatcher, std::move(request), this);
}
void InitializeAbr(InitializeAbrCompleter::Sync completer) {
last_command_ = Command::kInitializeAbr;
if (abr_supported_) {
abr_initialized_ = true;
completer.Reply(abr_supported_ ? ZX_OK : ZX_ERR_NOT_SUPPORTED);
} else {
completer.Reply(ZX_ERR_NOT_SUPPORTED);
}
}
void QueryActiveConfiguration(QueryActiveConfigurationCompleter::Sync completer) {
last_command_ = Command::kQueryActiveConfiguration;
::llcpp::fuchsia::paver::Paver_QueryActiveConfiguration_Result result;
if (abr_supported_ && abr_initialized_) {
::llcpp::fuchsia::paver::Paver_QueryActiveConfiguration_Response response;
response.configuration = ::llcpp::fuchsia::paver::Configuration::A;
result.set_response(response);
} else {
result.set_err(ZX_ERR_NOT_SUPPORTED);
}
completer.Reply(std::move(result));
}
void QueryConfigurationStatus(::llcpp::fuchsia::paver::Configuration configuration,
QueryConfigurationStatusCompleter::Sync completer) {
last_command_ = Command::kQueryConfigurationStatus;
::llcpp::fuchsia::paver::Paver_QueryConfigurationStatus_Result result;
if (abr_supported_ && abr_initialized_) {
::llcpp::fuchsia::paver::Paver_QueryConfigurationStatus_Response response;
response.status = ::llcpp::fuchsia::paver::ConfigurationStatus::HEALTHY;
} else {
result.set_err(ZX_ERR_NOT_SUPPORTED);
}
completer.Reply(std::move(result));
}
void SetConfigurationActive(::llcpp::fuchsia::paver::Configuration configuration,
SetConfigurationActiveCompleter::Sync completer) {
last_command_ = Command::kSetConfigurationActive;
zx_status_t status = ZX_ERR_NOT_SUPPORTED;
if (abr_supported_ && abr_initialized_) {
if (configuration == ::llcpp::fuchsia::paver::Configuration::A) {
status = ZX_OK;
} else {
status = ZX_ERR_INVALID_ARGS;
}
}
completer.Reply(status);
}
void SetConfigurationUnbootable(::llcpp::fuchsia::paver::Configuration configuration,
SetConfigurationUnbootableCompleter::Sync completer) {
last_command_ = Command::kSetConfigurationUnbootable;
zx_status_t status = ZX_ERR_NOT_SUPPORTED;
if (abr_supported_ && abr_initialized_) {
if (configuration == ::llcpp::fuchsia::paver::Configuration::RECOVERY) {
status = ZX_ERR_INVALID_ARGS;
} else {
status = ZX_OK;
}
}
completer.Reply(status);
}
void SetActiveConfigurationHealthy(
SetActiveConfigurationHealthyCompleter::Sync completer) {
last_command_ = Command::kSetActiveConfigurationHealthy;
completer.Reply(abr_supported_ && abr_initialized_ ? ZX_OK : ZX_ERR_NOT_SUPPORTED);
}
void ReadAsset(::llcpp::fuchsia::paver::Configuration configuration,
::llcpp::fuchsia::paver::Asset asset, ReadAssetCompleter::Sync completer) {
last_command_ = Command::kReadAsset;
::llcpp::fuchsia::paver::Paver_ReadAsset_Result result;
result.set_err(ZX_ERR_NOT_SUPPORTED);
completer.Reply(std::move(result));
}
void WriteAsset(::llcpp::fuchsia::paver::Configuration configuration,
::llcpp::fuchsia::paver::Asset asset, ::llcpp::fuchsia::mem::Buffer payload,
WriteAssetCompleter::Sync completer) {
last_command_ = Command::kWriteAsset;
auto status = payload.size == expected_payload_size_ ? ZX_OK : ZX_ERR_INVALID_ARGS;
completer.Reply(status);
}
void WriteVolumes(zx::channel payload_stream, WriteVolumesCompleter::Sync completer) {
last_command_ = Command::kWriteVolumes;
// Register VMO.
zx::vmo vmo;
auto status = zx::vmo::create(1024, 0, &vmo);
if (status != ZX_OK) {
completer.Reply(status);
return;
}
::llcpp::fuchsia::paver::PayloadStream::SyncClient stream(std::move(payload_stream));
auto result = stream.RegisterVmo(std::move(vmo));
status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
completer.Reply(status);
return;
}
// Stream until EOF.
status = [&]() {
size_t data_transferred = 0;
for (;;) {
auto result = stream.ReadData();
if (!result.ok()) {
return result.status();
}
const auto& response = result.value();
switch (response.result.which()) {
case ::llcpp::fuchsia::paver::ReadResult::Tag::kErr:
return response.result.err();
case ::llcpp::fuchsia::paver::ReadResult::Tag::kEof:
return data_transferred == expected_payload_size_ ? ZX_OK : ZX_ERR_INVALID_ARGS;
case ::llcpp::fuchsia::paver::ReadResult::Tag::kInfo:
data_transferred += response.result.info().size;
continue;
default:
return ZX_ERR_INTERNAL;
}
}
}();
completer.Reply(status);
}
void WriteBootloader(::llcpp::fuchsia::mem::Buffer payload,
WriteBootloaderCompleter::Sync completer) {
last_command_ = Command::kWriteBootloader;
auto status = payload.size == expected_payload_size_ ? ZX_OK : ZX_ERR_INVALID_ARGS;
completer.Reply(status);
}
void WriteDataFile(fidl::StringView filename, ::llcpp::fuchsia::mem::Buffer payload,
WriteDataFileCompleter::Sync completer) {
last_command_ = Command::kWriteDataFile;
auto status = payload.size == expected_payload_size_ ? ZX_OK : ZX_ERR_INVALID_ARGS;
completer.Reply(status);
}
void WipeVolumes(zx::channel block_device, WipeVolumesCompleter::Sync completer) {
last_command_ = Command::kWipeVolumes;
auto status = ZX_OK;
completer.Reply(status);
}
void InitializePartitionTables(zx::channel block_device,
InitializePartitionTablesCompleter::Sync completer) {
last_command_ = Command::kInitializePartitionTables;
auto status = ZX_ERR_NOT_SUPPORTED;
return completer.Reply(status);
}
void WipePartitionTables(zx::channel block_device, WipePartitionTablesCompleter::Sync completer) {
last_command_ = Command::kWipePartitionTables;
completer.Reply(ZX_OK);
}
Command last_command() { return last_command_; }
void set_expected_payload_size(size_t size) { expected_payload_size_ = size; }
void set_abr_supported(bool supported) { abr_supported_ = supported; }
private:
Command last_command_ = Command::kUnknown;
size_t expected_payload_size_ = 0;
bool abr_supported_ = false;
bool abr_initialized_ = false;
};
class FakeSvc {
public:
explicit FakeSvc(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher), vfs_(dispatcher) {
auto root_dir = fbl::MakeRefCounted<fs::PseudoDir>();
root_dir->AddEntry(::llcpp::fuchsia::paver::Paver::Name,
fbl::MakeRefCounted<fs::Service>([this](zx::channel request) {
return fake_paver_.Connect(dispatcher_, std::move(request));
}));
zx::channel svc_remote;
ASSERT_OK(zx::channel::create(0, &svc_local_, &svc_remote));
vfs_.ServeDirectory(root_dir, std::move(svc_remote));
}
FakePaver& fake_paver() { return fake_paver_; }
zx::channel& svc_chan() { return svc_local_; }
private:
async_dispatcher_t* dispatcher_;
fs::SynchronousVfs vfs_;
FakePaver fake_paver_;
zx::channel svc_local_;
};
class FakeDev {
public:
FakeDev() {
driver_integration_test::IsolatedDevmgr::Args args;
args.driver_search_paths.push_back("/boot/driver");
ASSERT_OK(driver_integration_test::IsolatedDevmgr::Create(&args, &devmgr_));
fbl::unique_fd fd;
ASSERT_OK(
devmgr_integration_test::RecursiveWaitForFile(devmgr_.devfs_root(), "sys/platform", &fd));
ASSERT_OK(
devmgr_integration_test::RecursiveWaitForFile(devmgr_.devfs_root(), "misc/sysinfo", &fd));
}
driver_integration_test::IsolatedDevmgr devmgr_;
};
} // namespace
class PaverTest : public zxtest::Test {
protected:
PaverTest()
: loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
fake_svc_(loop_.dispatcher()),
paver_(std::move(fake_svc_.svc_chan()), fake_dev_.devmgr_.devfs_root().duplicate()) {
paver_.set_timeout(zx::msec(500));
loop_.StartThread("paver-test-loop");
}
~PaverTest() {
// Need to make sure paver thread exits.
Wait();
loop_.Shutdown();
}
void Wait() {
while (paver_.InProgress())
continue;
}
async::Loop loop_;
FakeSvc fake_svc_;
FakeDev fake_dev_;
netsvc::Paver paver_;
};
TEST_F(PaverTest, OpenWriteInvalidFile) {
char invalid_file_name[32] = {};
ASSERT_NE(paver_.OpenWrite(invalid_file_name, 0), TFTP_NO_ERROR);
paver_.Close();
}
TEST_F(PaverTest, OpenWriteInvalidSize) {
ASSERT_NE(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 0), TFTP_NO_ERROR);
}
TEST_F(PaverTest, OpenWriteValidFile) {
ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
paver_.Close();
}
TEST_F(PaverTest, OpenTwice) {
ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
ASSERT_NE(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
paver_.Close();
}
TEST_F(PaverTest, WriteWithoutOpen) {
size_t size = sizeof(kFakeData);
ASSERT_NE(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
}
TEST_F(PaverTest, WriteAfterClose) {
size_t size = sizeof(kFakeData);
ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
paver_.Close();
// TODO(surajmalhotra): Should we ensure this fails?
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
}
TEST_F(PaverTest, TimeoutNoWrites) {
ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
paver_.Close();
Wait();
ASSERT_NE(paver_.exit_code(), ZX_OK);
}
TEST_F(PaverTest, TimeoutPartialWrite) {
size_t size = sizeof(kFakeData);
ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_NE(paver_.exit_code(), ZX_OK);
}
TEST_F(PaverTest, WriteCompleteSingle) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_expected_payload_size(size);
ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteBootloader);
}
TEST_F(PaverTest, WriteCompleteManySmallWrites) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_expected_payload_size(1024);
ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
for (size_t offset = 0; offset < 1024; offset += sizeof(kFakeData)) {
size = std::min(sizeof(kFakeData), 1024 - offset);
ASSERT_EQ(paver_.Write(kFakeData, &size, offset), TFTP_NO_ERROR);
ASSERT_EQ(size, std::min(sizeof(kFakeData), 1024 - offset));
}
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
}
TEST_F(PaverTest, Overwrite) {
size_t size = sizeof(kFakeData);
ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 2), TFTP_NO_ERROR);
ASSERT_NE(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
paver_.Close();
Wait();
ASSERT_NE(paver_.exit_code(), ZX_OK);
}
TEST_F(PaverTest, CloseChannelBetweenWrites) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_expected_payload_size(2 * size);
ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 2 * size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
loop_.Shutdown();
ASSERT_EQ(paver_.Write(kFakeData, &size, size), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_EQ(paver_.exit_code(), ZX_ERR_PEER_CLOSED);
}
TEST_F(PaverTest, WriteZirconA) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_expected_payload_size(size);
ASSERT_EQ(paver_.OpenWrite(NB_ZIRCONA_FILENAME, size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteAsset);
}
TEST_F(PaverTest, WriteVbMetaA) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_expected_payload_size(size);
ASSERT_EQ(paver_.OpenWrite(NB_VBMETAA_FILENAME, size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteAsset);
}
TEST_F(PaverTest, WriteZirconAWithABRSupported) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_abr_supported(true);
fake_svc_.fake_paver().set_expected_payload_size(size);
ASSERT_EQ(paver_.OpenWrite(NB_ZIRCONA_FILENAME, size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteAsset);
}
TEST_F(PaverTest, WriteZirconBWithABRSupported) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_abr_supported(true);
fake_svc_.fake_paver().set_expected_payload_size(size);
ASSERT_EQ(paver_.OpenWrite(NB_ZIRCONB_FILENAME, size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteAsset);
}
TEST_F(PaverTest, WriteZirconRWithABRSupported) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_abr_supported(true);
fake_svc_.fake_paver().set_expected_payload_size(size);
ASSERT_EQ(paver_.OpenWrite(NB_ZIRCONR_FILENAME, size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteAsset);
}
TEST_F(PaverTest, WriteVbMetaAWithABRSupported) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_abr_supported(true);
fake_svc_.fake_paver().set_expected_payload_size(size);
ASSERT_EQ(paver_.OpenWrite(NB_VBMETAA_FILENAME, size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kSetConfigurationActive);
}
TEST_F(PaverTest, WriteVbMetaBWithABRSupported) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_abr_supported(true);
fake_svc_.fake_paver().set_expected_payload_size(size);
ASSERT_EQ(paver_.OpenWrite(NB_VBMETAB_FILENAME, size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteAsset);
}
TEST_F(PaverTest, WriteVbMetaRWithABRSupported) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_abr_supported(true);
fake_svc_.fake_paver().set_expected_payload_size(size);
ASSERT_EQ(paver_.OpenWrite(NB_VBMETAR_FILENAME, size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteAsset);
}
TEST_F(PaverTest, WriteZirconAWithABRSupportedTwice) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_abr_supported(true);
fake_svc_.fake_paver().set_expected_payload_size(size);
for (int i = 0 ; i < 2; i++) {
ASSERT_EQ(paver_.OpenWrite(NB_ZIRCONA_FILENAME, size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteAsset);
}
}
TEST_F(PaverTest, WriteSshAuth) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_expected_payload_size(size);
ASSERT_EQ(paver_.OpenWrite(NB_SSHAUTH_FILENAME, size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteDataFile);
}
TEST_F(PaverTest, WriteFvm) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_expected_payload_size(size);
ASSERT_EQ(paver_.OpenWrite(NB_FVM_FILENAME, size), TFTP_NO_ERROR);
ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
ASSERT_EQ(size, sizeof(kFakeData));
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteVolumes);
}
TEST_F(PaverTest, WriteFvmManySmallWrites) {
size_t size = sizeof(kFakeData);
fake_svc_.fake_paver().set_expected_payload_size(1024);
ASSERT_EQ(paver_.OpenWrite(NB_FVM_FILENAME, 1024), TFTP_NO_ERROR);
for (size_t offset = 0; offset < 1024; offset += sizeof(kFakeData)) {
size = std::min(sizeof(kFakeData), 1024 - offset);
ASSERT_EQ(paver_.Write(kFakeData, &size, offset), TFTP_NO_ERROR);
ASSERT_EQ(size, std::min(sizeof(kFakeData), 1024 - offset));
}
paver_.Close();
Wait();
ASSERT_OK(paver_.exit_code());
ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteVolumes);
}