blob: 8c7eb728a053139be10bfeb78168ea80f21921a7 [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/c/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/fidl-utils/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;
netsvc::Paver paver_(std::move(chan));
ASSERT_FALSE(paver_.InProgress());
}
TEST(PaverTest, InitialExitCodeValid) {
zx::channel chan;
netsvc::Paver paver_(std::move(chan));
ASSERT_OK(paver_.exit_code());
}
namespace {
enum class Command {
kUnknown,
kQueryActiveConfiguration,
kSetActiveConfiguration,
kMarkActiveConfigurationSuccessful,
kForceRecoveryConfiguration,
kWriteAsset,
kWriteVolumes,
kWriteBootloader,
kWriteDataFile,
kWipeVolumes,
};
class FakePaver {
public:
zx_status_t Connect(async_dispatcher_t* dispatcher, zx::channel request) {
return fidl_bind(dispatcher, request.release(),
reinterpret_cast<fidl_dispatch_t*>(fuchsia_paver_Paver_dispatch),
this, &ops_);
}
zx_status_t QueryActiveConfiguration(fidl_txn_t* txn) {
last_command_ = Command::kQueryActiveConfiguration;
return fuchsia_paver_PaverQueryActiveConfiguration_reply(txn, ZX_ERR_NOT_SUPPORTED,
nullptr);
}
zx_status_t SetActiveConfiguration(fuchsia_paver_Configuration configuration,
fidl_txn_t* txn) {
last_command_ = Command::kSetActiveConfiguration;
return fuchsia_paver_PaverSetActiveConfiguration_reply(txn, ZX_ERR_NOT_SUPPORTED);
}
zx_status_t MarkActiveConfigurationSuccessful(fidl_txn_t* txn) {
last_command_ = Command::kMarkActiveConfigurationSuccessful;
return fuchsia_paver_PaverMarkActiveConfigurationSuccessful_reply(txn,
ZX_ERR_NOT_SUPPORTED);
}
zx_status_t ForceRecoveryConfiguration(fidl_txn_t* txn) {
last_command_ = Command::kForceRecoveryConfiguration;
return fuchsia_paver_PaverForceRecoveryConfiguration_reply(txn, ZX_ERR_NOT_SUPPORTED);
}
zx_status_t WriteAsset(fuchsia_paver_Configuration configuration,
fuchsia_paver_Asset asset, const fuchsia_mem_Buffer* payload,
fidl_txn_t* txn) {
last_command_ = Command::kWriteAsset;
zx_handle_close(payload->vmo);
auto status = payload->size == expected_payload_size_ ? ZX_OK : ZX_ERR_INVALID_ARGS;
return fuchsia_paver_PaverWriteAsset_reply(txn, status);
}
zx_status_t WriteVolumes(zx_handle_t payload_stream, fidl_txn_t* txn) {
last_command_ = Command::kWriteVolumes;
zx::channel stream(payload_stream);
// Register VMO.
zx::vmo vmo;
auto status = zx::vmo::create(1024, 0, &vmo);
if (status != ZX_OK) {
return fuchsia_paver_PaverWriteVolumes_reply(txn, status);
}
auto io_status = fuchsia_paver_PayloadStreamRegisterVmo(stream.get(), vmo.release(),
&status);
status = io_status == ZX_OK ? status : io_status;
if (status != ZX_OK) {
return fuchsia_paver_PaverWriteVolumes_reply(txn, status);
}
// Stream until EOF.
status = [&]() {
size_t data_transferred = 0;
for (;;) {
fuchsia_paver_ReadResult result;
auto io_status = fuchsia_paver_PayloadStreamReadData(stream.get(), &result);
if (io_status != ZX_OK) {
return io_status;
}
switch (result.tag) {
case fuchsia_paver_ReadResultTag_err:
return result.err;
case fuchsia_paver_ReadResultTag_eof:
return data_transferred == expected_payload_size_ ? ZX_OK : ZX_ERR_INVALID_ARGS;
case fuchsia_paver_ReadResultTag_info:
data_transferred += result.info.size;
continue;
default:
return ZX_ERR_INTERNAL;
}
}
}();
return fuchsia_paver_PaverWriteVolumes_reply(txn, status);
}
zx_status_t WriteBootloader(const fuchsia_mem_Buffer* payload, fidl_txn_t* txn) {
last_command_ = Command::kWriteBootloader;
zx_handle_close(payload->vmo);
auto status = payload->size == expected_payload_size_ ? ZX_OK : ZX_ERR_INVALID_ARGS;
return fuchsia_paver_PaverWriteBootloader_reply(txn, status);
}
zx_status_t WriteDataFile(const char* filename, size_t filename_len,
const fuchsia_mem_Buffer* payload, fidl_txn_t* txn) {
last_command_ = Command::kWriteDataFile;
zx_handle_close(payload->vmo);
auto status = payload->size == expected_payload_size_ ? ZX_OK : ZX_ERR_INVALID_ARGS;
return fuchsia_paver_PaverWriteDataFile_reply(txn, status);
}
zx_status_t WipeVolumes(fidl_txn_t* txn) {
last_command_ = Command::kWipeVolumes;
auto status = ZX_OK;
return fuchsia_paver_PaverWipeVolumes_reply(txn, status);
}
Command last_command() { return last_command_; }
void set_expected_payload_size(size_t size) { expected_payload_size_ = size; }
private:
using Binder = fidl::Binder<FakePaver>;
Command last_command_ = Command::kUnknown;
size_t expected_payload_size_ = 0;
static constexpr fuchsia_paver_Paver_ops_t ops_ = {
.QueryActiveConfiguration = Binder::BindMember<&FakePaver::QueryActiveConfiguration>,
.SetActiveConfiguration = Binder::BindMember<&FakePaver::SetActiveConfiguration>,
.MarkActiveConfigurationSuccessful =
Binder::BindMember<&FakePaver::MarkActiveConfigurationSuccessful>,
.ForceRecoveryConfiguration = Binder::BindMember<&FakePaver::ForceRecoveryConfiguration>,
.WriteAsset = Binder::BindMember<&FakePaver::WriteAsset>,
.WriteVolumes = Binder::BindMember<&FakePaver::WriteVolumes>,
.WriteBootloader = Binder::BindMember<&FakePaver::WriteBootloader>,
.WriteDataFile = Binder::BindMember<&FakePaver::WriteDataFile>,
.WipeVolumes = Binder::BindMember<&FakePaver::WipeVolumes>,
};
};
class FakeSvc {
public:
explicit FakeSvc(async_dispatcher_t* dispatcher)
: dispatcher_(dispatcher), vfs_(dispatcher) {
auto root_dir = fbl::MakeRefCounted<fs::PseudoDir>();
root_dir->AddEntry(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_;
};
} // namespace
class PaverTest : public zxtest::Test {
protected:
PaverTest()
: loop_(&kAsyncLoopConfigNoAttachToThread),
fake_svc_(loop_.dispatcher()),
paver_(std::move(fake_svc_.svc_chan())) {
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_;
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_EQ(paver_.exit_code(), ZX_OK);
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_EQ(paver_.exit_code(), ZX_OK);
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_EQ(paver_.exit_code(), ZX_OK);
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_EQ(paver_.exit_code(), ZX_OK);
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_EQ(paver_.exit_code(), ZX_OK);
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);
}