blob: a168107e872237ca3a373107a394331fe3e8b083 [file] [log] [blame]
// Copyright 2017 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.
#ifndef ZIRCON_SYSTEM_UTEST_ZXCRYPT_TEST_DEVICE_H_
#define ZIRCON_SYSTEM_UTEST_ZXCRYPT_TEST_DEVICE_H_
#include <lib/devmgr-integration-test/fixture.h>
#include <lib/fzl/fdio.h>
#include <lib/zx/channel.h>
#include <lib/zx/vmo.h>
#include <stddef.h>
#include <stdint.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/compiler.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <memory>
#include <block-client/client.h>
#include <crypto/secret.h>
#include <fbl/macros.h>
#include <fbl/mutex.h>
#include <fbl/unique_fd.h>
#include <fvm/format.h>
#include <ramdevice-client/ramdisk.h>
#include <zxcrypt/fdio-volume.h>
// TODO(34273): Replace these with *_STATUS, *_OK in zxtest.
#define EXPECT_ZX(expr, status) EXPECT_EQ(expr, status)
#define ASSERT_ZX(expr, status) ASSERT_EQ(expr, status)
#define EXPECT_OK(expr) EXPECT_EQ(expr, ZX_OK)
#define ASSERT_OK(expr) ASSERT_EQ(expr, ZX_OK)
// Value-parameterized tests: Consumers of this file can define an 'EACH_PARAM' macro as follows:
// #define EACH_PARAM(OP, Test)
// OP(Test, Class, Param1)
// OP(Test, Class, Param2)
// ...
// OP(Test, Class, ParamN)
// where |Param1| corresponds to an enum constant |Class::kParam1|, etc.
//
// Consumers can then use the following macros to automatically define and run tests for each
// parameter:
// bool TestSomething(Param param) {
// BEGIN_TEST;
// ...
// END_TEST;
// }
// DEFINE_EACH(TestSomething)
// ...
// BEGIN_TEST_CASE(SomeTest)
// RUN_EACH(TestSomething)
// END_TEST_CASE(SomeTest)
#define DEFINE_TEST_PARAM(Test, Class, Param) \
bool Test##_##Param(void) { return Test(Class::k##Param); }
#define RUN_TEST_PARAM(Test, Class, Param) RUN_TEST(Test##_##Param)
#define DEFINE_EACH(Test) EACH_PARAM(DEFINE_TEST_PARAM, Test)
#define RUN_EACH(Test) EACH_PARAM(RUN_TEST_PARAM, Test)
#define DEFINE_EACH_DEVICE(Test) \
bool Test##Raw(Volume::Version version) { return Test(version, false /* not FVM */); } \
DEFINE_EACH(Test##Raw) \
bool Test##Fvm(Volume::Version version) { return Test(version, true /* FVM */); } \
DEFINE_EACH(Test##Fvm)
#define RUN_EACH_DEVICE(Test) \
RUN_EACH(Test##Raw) \
RUN_EACH(Test##Fvm)
namespace zxcrypt {
namespace testing {
// Default disk geometry to use when testing device block related code.
const uint32_t kBlockCount = 64;
const uint32_t kBlockSize = 512;
const size_t kDeviceSize = kBlockCount * kBlockSize;
const uint32_t kSliceCount = kDeviceSize / fvm::kBlockSize;
// |zxcrypt::testing::Utils| is a collection of functions designed to make the zxcrypt
// unit test setup and tear down easier.
class TestDevice final {
public:
explicit TestDevice();
~TestDevice();
DISALLOW_COPY_ASSIGN_AND_MOVE(TestDevice);
// ACCESSORS
// Returns the size of the zxcrypt volume.
size_t size() const { return block_count_ * block_size_; }
// Returns a duplicated file descriptor representing the zxcrypt volume's underlying device;
// that is, the ramdisk or FVM partition.
fbl::unique_fd parent() const {
if (fvm_part_) {
return fvm_part_.duplicate();
} else {
return fbl::unique_fd(dup(ramdisk_get_block_fd(ramdisk_)));
}
}
fbl::unique_fd devfs_root() const { return devmgr_.devfs_root().duplicate(); }
// Returns a duplicated file descriptor representing t the zxcrypt volume.
fbl::unique_fd zxcrypt() const { return zxcrypt_.duplicate(); }
// Returns a connection to the parent device.
const zx::unowned_channel parent_channel() const {
return zx::unowned_channel(parent_caller_.borrow_channel());
}
// Returns a connection to the zxcrypt device.
const zx::unowned_channel zxcrypt_channel() const {
return zx::unowned_channel(zxcrypt_caller_.borrow_channel());
}
// Returns the block size of the zxcrypt device.
size_t block_size() const { return block_size_; }
// Returns the block size of the zxcrypt device.
size_t block_count() const { return block_count_; }
// Returns space reserved for metadata
size_t reserved_blocks() const { return volume_->reserved_blocks(); }
size_t reserved_slices() const { return volume_->reserved_slices(); }
// Returns a reference to the root key generated for this device.
const crypto::Secret& key() const { return key_; }
// API WRAPPERS
// These methods mirror the POSIX API, except that the file descriptors and buffers are
// provided automatically. |off| and |len| are in bytes.
ssize_t lseek(zx_off_t off) { return ::lseek(zxcrypt_.get(), off, SEEK_SET); }
ssize_t read(zx_off_t off, size_t len) {
return ::read(zxcrypt_.get(), as_read_.get() + off, len);
}
ssize_t write(zx_off_t off, size_t len) {
return ::write(zxcrypt_.get(), to_write_.get() + off, len);
}
// These methods mirror the syscall API, except that the VMO and buffers are provided
// automatically. |off| and |len| are in bytes.
zx_status_t vmo_read(zx_off_t off, size_t len) { return vmo_.read(as_read_.get() + off, 0, len); }
zx_status_t vmo_write(uint64_t off, uint64_t len) {
return vmo_.write(to_write_.get() + off, 0, len);
}
// Sends a request over the block fifo to read or write the blocks given by |off| and |len|,
// according to the given |opcode|. The data sent or received can be accessed using |vmo_write|
// or |vmo_read|, respectively. |off| and |len| are in blocks.
zx_status_t block_fifo_txn(uint16_t opcode, uint64_t off, uint64_t len) {
req_.opcode = opcode;
req_.length = static_cast<uint32_t>(len);
req_.dev_offset = off;
req_.vmo_offset = 0;
return ::block_fifo_txn(client_, &req_, 1);
}
// Sends |num| requests over the block fifo to read or write blocks.
zx_status_t block_fifo_txn(block_fifo_request_t* requests, size_t num) {
for (size_t i = 0; i < num; ++i) {
requests[i].group = req_.group;
requests[i].vmoid = req_.vmoid;
}
return ::block_fifo_txn(client_, requests, num);
}
// TEST HELPERS
// Launches an isolated devcoordinator. Must be called before calling
// any other methods on TestDevice.
bool SetupDevmgr();
// Allocates a new block device of at least |device_size| bytes grouped into blocks of
// |block_size| bytes each. If |fvm| is true, it will be formatted as an FVM partition with the
// appropriates number of slices of |fvm::kBlockSize| each. A file descriptor for the block
// device is returned via |out_fd|.
bool Create(size_t device_size, size_t block_size, bool fvm);
// Test helper that generates a key and creates a device according to |version| and |fvm|. It
// sets up the device as a zxcrypt volume and binds to it.
bool Bind(Volume::Version version, bool fvm);
// Test helper that rebinds the ramdisk and its children.
bool Rebind();
// Tells the underlying ramdisk to sleep until |num| transactions have been received. If
// |deferred| is true, the transactions will be handled on waking; else they will be failed.
bool SleepUntil(uint64_t num, bool deferred) __TA_EXCLUDES(lock_);
// Blocks until the ramdisk is awake.
bool WakeUp() __TA_EXCLUDES(lock_);
// Test helpers that perform a |lseek| and a |read| or |write| together. |off| and |len| are in
// bytes. |ReadFd| additionally checks that the data read matches what was written.
bool ReadFd(zx_off_t off, size_t len);
bool WriteFd(zx_off_t off, size_t len);
// Test helpers that perform a |lseek| and a |vmo_read| or |vmo_write| together. |off| and
// |len| are in blocks. |ReadVmo| additionally checks that the data read matches what was
// written.
bool ReadVmo(zx_off_t off, size_t len);
bool WriteVmo(zx_off_t off, size_t len);
// Test helper that flips a (pseudo)random bit in the key at the given |slot| in the given
// |block|. The call to |srand| in main.c guarantees the same bit will be chosen for a given
// test iteration.
bool Corrupt(uint64_t block, key_slot_t slot);
private:
// Allocates a new ramdisk of at least |device_size| bytes arranged into blocks of |block_size|
// bytes, and opens it.
bool CreateRamdisk(size_t device_size, size_t block_size);
// Destroys the ramdisk, killing any active transactions
void DestroyRamdisk();
// Creates a ramdisk of with enough blocks of |block_size| bytes to hold both FVM metadata and
// an FVM partition of at least |device_size| bytes. It formats the ramdisk to be an FVM
// device, and allocates a partition with a single slice of size fvm::kBlockSize.
bool CreateFvmPart(size_t device_size, size_t block_size);
// Binds the FVM driver to the open ramdisk.
bool BindFvmDriver();
// Connects the block client to the block server.
bool Connect();
// Disconnects the block client from the block server.
void Disconnect();
// Thread body to wake up the underlying ramdisk.
static int WakeThread(void* arg);
// The isolated devmgr instance which we'll talk to in order to create the
// underlying ramdisk. We do this so that the system's devmgr's
// block-watcher doesn't try to bind drivers to/mount/unseal our
// ramdisk-backed volumes.
devmgr_integration_test::IsolatedDevmgr devmgr_;
// The ramdisk client
ramdisk_client_t* ramdisk_;
// The pathname of the FVM partition.
char fvm_part_path_[PATH_MAX];
// A channel-exposing wrapper around the parent device.
fzl::UnownedFdioCaller parent_caller_;
// A channel-exposing wrapper around the zxcrypt device.
fzl::UnownedFdioCaller zxcrypt_caller_;
// File descriptor for the (optional) underlying FVM partition.
fbl::unique_fd fvm_part_;
// File descriptor for the zxcrypt volume.
fbl::unique_fd zxcrypt_;
// The zxcrypt volume
std::unique_ptr<FdioVolume> volume_;
// The cached block count.
size_t block_count_;
// The cached block size.
size_t block_size_;
// The root key for this device.
crypto::Secret key_;
// Client for the block I/O protocol to the block server.
fifo_client_t* client_;
// Request structure used to send messages via the block I/O protocol.
block_fifo_request_t req_;
// VMO attached to the zxcrypt device for use with the block I/O protocol.
zx::vmo vmo_;
// An internal write buffer, initially filled with pseudo-random data
std::unique_ptr<uint8_t[]> to_write_;
// An internal write buffer, initially filled with zeros.
std::unique_ptr<uint8_t[]> as_read_;
// Lock to coordinate waking thread
fbl::Mutex lock_;
// Thread used to manage sleeping/waking.
thrd_t tid_;
// It would be nice if thrd_t had a reserved invalid value...
bool need_join_;
// The number of transactions before waking.
uint64_t wake_after_ __TA_GUARDED(lock_);
// Timeout before waking regardless of transactions
zx::time wake_deadline_ __TA_GUARDED(lock_);
};
} // namespace testing
} // namespace zxcrypt
#endif // ZIRCON_SYSTEM_UTEST_ZXCRYPT_TEST_DEVICE_H_