Add test cases for BlockStore2 and fix existing BlockStore2 implementations

This commit is contained in:
Sebastian Messmer 2017-02-21 22:27:46 +00:00
parent 70bfc47a2f
commit dbf54b9563
15 changed files with 612 additions and 60 deletions

View File

@ -9,6 +9,7 @@ set(SOURCES
implementations/testfake/FakeBlock.cpp
implementations/inmemory/InMemoryBlock.cpp
implementations/inmemory/InMemoryBlockStore.cpp
implementations/inmemory/InMemoryBlockStore2.cpp
implementations/parallelaccess/ParallelAccessBlockStore.cpp
implementations/parallelaccess/BlockRef.cpp
implementations/parallelaccess/ParallelAccessBlockStoreAdapter.cpp

View File

@ -21,7 +21,7 @@ public:
}
boost::future<bool> tryCreate(const Key &key, const cpputils::Data &data) override {
cpputils::Data encrypted = _encrypt(data);
cpputils::Data encrypted = _encrypt(key, data);
return _baseBlockStore->tryCreate(key, encrypted);
}
@ -31,34 +31,43 @@ public:
boost::future<boost::optional<cpputils::Data>> load(const Key &key) const override {
auto loaded = _baseBlockStore->load(key);
return loaded.then([this] (boost::future<boost::optional<cpputils::Data>> data_) {
return loaded.then([this, key] (boost::future<boost::optional<cpputils::Data>> data_) {
auto data = data_.get();
if (boost::none == data) {
return boost::optional<cpputils::Data>(boost::none);
}
return _tryDecrypt(data);
return _tryDecrypt(key, *data);
});
}
boost::future<void> store(const Key &key, const cpputils::Data &data) override {
cpputils::Data encrypted = _encrypt(data);
cpputils::Data encrypted = _encrypt(key, data);
return _baseBlockStore->store(key, encrypted);
}
private:
cpputils::Data _encrypt(const Key &key, const cpputils::Data &data) {
// This header is prepended to blocks to allow future versions to have compatibility.
static constexpr uint16_t FORMAT_VERSION_HEADER = 0;
cpputils::Data _encrypt(const Key &key, const cpputils::Data &data) const {
cpputils::Data plaintextWithHeader = _prependKeyHeaderToData(key, data);
return Cipher::encrypt((byte*)plaintextWithHeader.data(), plaintextWithHeader.size(), _encKey);
cpputils::Data encrypted = Cipher::encrypt((byte*)plaintextWithHeader.data(), plaintextWithHeader.size(), _encKey);
return _prependFormatHeaderToData(encrypted);
}
boost::optional<cpputils::Data> _tryDecrypt(const Key &key, const cpputils::Data &data) {
boost::optional<cpputils::Data> decrypted = Cipher::decrypt((byte*)data.data(), data.size(), _encKey);
if (boost::none != decrypted && !_keyHeaderIsCorrect(key, *decrypted)) {
boost::optional<cpputils::Data> _tryDecrypt(const Key &key, const cpputils::Data &data) const {
auto ciphertext = _checkAndRemoveFormatHeader(data);
boost::optional<cpputils::Data> decrypted = Cipher::decrypt((byte*)ciphertext.data(), ciphertext.size(), _encKey);
if (boost::none == decrypted) {
// TODO Warning
return boost::none;
}
return decrypted;
if (!_keyHeaderIsCorrect(key, *decrypted)) {
// TODO Warning
return boost::none;
}
return _removeKeyHeader(*decrypted);
}
static cpputils::Data _prependKeyHeaderToData(const Key &key, const cpputils::Data &data) {
@ -72,6 +81,24 @@ private:
return 0 == std::memcmp(key.data(), data.data(), Key::BINARY_LENGTH);
}
static cpputils::Data _prependFormatHeaderToData(const cpputils::Data &data) {
cpputils::Data dataWithHeader(sizeof(FORMAT_VERSION_HEADER) + data.size());
std::memcpy(dataWithHeader.dataOffset(0), &FORMAT_VERSION_HEADER, sizeof(FORMAT_VERSION_HEADER));
std::memcpy(dataWithHeader.dataOffset(sizeof(FORMAT_VERSION_HEADER)), data.data(), data.size());
return dataWithHeader;
}
static cpputils::Data _removeKeyHeader(const cpputils::Data &data) {
return data.copyAndRemovePrefix(Key::BINARY_LENGTH);
}
static cpputils::Data _checkAndRemoveFormatHeader(const cpputils::Data &data) {
if (*reinterpret_cast<decltype(FORMAT_VERSION_HEADER)*>(data.data()) != FORMAT_VERSION_HEADER) {
throw std::runtime_error("The encrypted block has the wrong format. Was it created with a newer version of CryFS?");
}
return data.copyAndRemovePrefix(sizeof(FORMAT_VERSION_HEADER));
}
cpputils::unique_ref<BlockStore2> _baseBlockStore;
typename Cipher::EncryptionKey _encKey;

View File

@ -0,0 +1,65 @@
#include "InMemoryBlockStore2.h"
#include <memory>
#include <cpp-utils/assert/assert.h>
#include <cpp-utils/system/get_total_memory.h>
using std::make_unique;
using std::string;
using std::mutex;
using std::lock_guard;
using std::piecewise_construct;
using std::make_tuple;
using std::make_pair;
using cpputils::Data;
using cpputils::unique_ref;
using cpputils::make_unique_ref;
using boost::optional;
using boost::none;
using boost::future;
using boost::make_ready_future;
namespace blockstore {
namespace inmemory {
InMemoryBlockStore2::InMemoryBlockStore2()
: _blocks() {}
future<bool> InMemoryBlockStore2::tryCreate(const Key &key, const Data &data) {
auto result = _blocks.insert(make_pair(key, data.copy()));
return make_ready_future(result.second); // Return if insertion was successful (i.e. key didn't exist yet)
}
future<bool> InMemoryBlockStore2::remove(const Key &key) {
auto found = _blocks.find(key);
if (found == _blocks.end()) {
// Key not found
return make_ready_future(false);
}
_blocks.erase(found);
return make_ready_future(true);
}
future<optional<Data>> InMemoryBlockStore2::load(const Key &key) const {
auto found = _blocks.find(key);
if (found == _blocks.end()) {
return make_ready_future(optional<Data>(none));
}
return make_ready_future(optional<Data>(found->second.copy()));
}
future<void> InMemoryBlockStore2::store(const Key &key, const Data &data) {
auto found = _blocks.find(key);
if (found == _blocks.end()) {
return tryCreate(key, data).then([] (future<bool> success) {
if (!success.get()) {
throw std::runtime_error("Could neither save nor create the block in InMemoryBlockStore::store()");
}
});
}
found->second = data.copy();
return make_ready_future();
}
}
}

View File

@ -0,0 +1,30 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCKSTORE2_H_
#define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCKSTORE2_H_
#include "../../interface/BlockStore2.h"
#include <cpp-utils/macros.h>
#include <unordered_map>
namespace blockstore {
namespace inmemory {
class InMemoryBlockStore2 final: public BlockStore2 {
public:
InMemoryBlockStore2();
boost::future<bool> tryCreate(const Key &key, const cpputils::Data &data) override;
boost::future<bool> remove(const Key &key) override;
boost::future<boost::optional<cpputils::Data>> load(const Key &key) const override;
boost::future<void> store(const Key &key, const cpputils::Data &data) override;
private:
std::unordered_map<Key, cpputils::Data> _blocks;
DISALLOW_COPY_AND_ASSIGN(InMemoryBlockStore2);
};
}
}
#endif

View File

@ -1,7 +1,12 @@
#include "OnDiskBlockStore2.h"
using std::string;
namespace blockstore {
namespace ondisk {
const string OnDiskBlockStore2::FORMAT_VERSION_HEADER_PREFIX = "cryfs;block;";
const string OnDiskBlockStore2::FORMAT_VERSION_HEADER = OnDiskBlockStore2::FORMAT_VERSION_HEADER_PREFIX + "0";
}
}

View File

@ -7,48 +7,95 @@
#include <cpp-utils/macros.h>
#include <cpp-utils/pointer/unique_ref.h>
#include "OnDiskBlockStore.h"
#include <cpp-utils/logging/logging.h>
namespace blockstore {
namespace ondisk {
//TODO Implement without basing on OnDiskBlockStore
class OnDiskBlockStore2 final: public BlockStore2 {
public:
explicit OnDiskBlockStore2(cpputils::unique_ref<OnDiskBlockStore> base)
: _base(std::move(base)) {}
explicit OnDiskBlockStore2(const boost::filesystem::path& path)
: _rootDir(path) {}
boost::future<bool> tryCreate(const Key &key, const cpputils::Data &data) override {
auto created = _base->tryCreate(key, data.copy());
if (boost::none == created) {
auto filepath = _getFilepath(key);
if (boost::filesystem::exists(filepath)) {
return boost::make_ready_future(false);
} else {
return boost::make_ready_future(true);
}
store(key, data).wait();
return boost::make_ready_future(true);
}
boost::future<bool> remove(const Key &key) override {
_base->remove(key);
auto filepath = _getFilepath(key);
if (!boost::filesystem::is_regular_file(filepath)) { // TODO Is this branch necessary?
return boost::make_ready_future(false);
}
bool retval = boost::filesystem::remove(filepath);
if (!retval) {
cpputils::logging::LOG(cpputils::logging::ERROR, "Couldn't find block {} to remove", key.ToString());
return boost::make_ready_future(false);
}
if (boost::filesystem::is_empty(filepath.parent_path())) {
boost::filesystem::remove(filepath.parent_path());
}
return boost::make_ready_future(true);
}
boost::future<boost::optional<cpputils::Data>> load(const Key &key) const override {
auto block = _base->load(key);
if (boost::none == block) {
return boost::make_ready_future<boost::optional<cpputils::Data>>(boost::none);
auto fileContent = cpputils::Data::LoadFromFile(_getFilepath(key));
if (fileContent == boost::none) {
return boost::make_ready_future(boost::optional<cpputils::Data>(boost::none));
}
cpputils::Data data((*block)->size());
std::memcpy(data.data(), (*block)->data(), data.size());
return boost::make_ready_future<boost::optional<cpputils::Data>>(std::move(data));
return boost::make_ready_future(boost::optional<cpputils::Data>(_checkAndRemoveHeader(std::move(*fileContent))));
}
boost::future<void> store(const Key &key, const cpputils::Data &data) override {
_base->overwrite(key, data.copy());
cpputils::Data fileContent(formatVersionHeaderSize() + data.size());
std::memcpy(fileContent.data(), FORMAT_VERSION_HEADER.c_str(), formatVersionHeaderSize());
std::memcpy(fileContent.dataOffset(formatVersionHeaderSize()), data.data(), data.size());
auto filepath = _getFilepath(key);
boost::filesystem::create_directory(filepath.parent_path()); // TODO Instead create all of them once at fs creation time?
fileContent.StoreToFile(filepath);
return boost::make_ready_future();
}
private:
cpputils::unique_ref<OnDiskBlockStore> _base;
boost::filesystem::path _rootDir;
static const std::string FORMAT_VERSION_HEADER_PREFIX;
static const std::string FORMAT_VERSION_HEADER;
boost::filesystem::path _getFilepath(const Key &key) const {
std::string keyStr = key.ToString();
return _rootDir / keyStr.substr(0,3) / keyStr.substr(3);
}
static cpputils::Data _checkAndRemoveHeader(const cpputils::Data &data) {
if (!_isAcceptedCryfsHeader(data)) {
if (_isOtherCryfsHeader(data)) {
throw std::runtime_error("This block is not supported yet. Maybe it was created with a newer version of CryFS?");
} else {
throw std::runtime_error("This is not a valid block.");
}
}
cpputils::Data result(data.size() - formatVersionHeaderSize());
std::memcpy(result.data(), data.dataOffset(formatVersionHeaderSize()), result.size());
return result;
}
static bool _isAcceptedCryfsHeader(const cpputils::Data &data) {
return 0 == std::memcmp(data.data(), FORMAT_VERSION_HEADER.c_str(), formatVersionHeaderSize());
}
static bool _isOtherCryfsHeader(const cpputils::Data &data) {
return 0 == std::memcmp(data.data(), FORMAT_VERSION_HEADER_PREFIX.c_str(), FORMAT_VERSION_HEADER_PREFIX.size());
}
static unsigned int formatVersionHeaderSize() {
return FORMAT_VERSION_HEADER.size() + 1; // +1 because of the null byte
}
DISALLOW_COPY_AND_ASSIGN(OnDiskBlockStore2);
};

View File

@ -23,6 +23,7 @@ namespace blockstore {
: _reason("Integrity violation: " + reason) {
}
friend class VersionCountingBlockStore;
friend class VersionCountingBlockStore2;
std::string _reason;
};

View File

@ -5,6 +5,7 @@
#include "../../interface/BlockStore2.h"
#include <cpp-utils/macros.h>
#include "KnownBlockVersions.h"
#include "IntegrityViolationError.h"
namespace blockstore {
namespace versioncounting {
@ -19,42 +20,57 @@ public:
boost::future<bool> tryCreate(const Key &key, const cpputils::Data &data) override {
_checkNoPastIntegrityViolations();
uint64_t version = blockStore->knownBlockVersions()->incrementVersion(key);
cpputils::Data dataWithHeader = _prependHeaderToData(blockStore->knownBlockVersions()->myClientId(), version, data);
return baseBlockStore_->tryCreate(key, dataWithHeader);
uint64_t version = _knownBlockVersions.incrementVersion(key);
cpputils::Data dataWithHeader = _prependHeaderToData(_knownBlockVersions.myClientId(), version, data);
return _baseBlockStore->tryCreate(key, dataWithHeader);
}
boost::future<bool> remove(const Key &key) override {
_checkNoPastIntegrityViolations();
_knownBlockVersions.markBlockAsDeleted(key);
return baseBlockStore->remove(key);
return _baseBlockStore->remove(key);
}
boost::future<boost::optional<cpputils::Data>> load(const Key &key) const override {
_checkNoPastIntegrityViolations();
auto loaded = baseBlockStore_->load(key);
loaded.then([this, key] (boost::future<boost::optional<cpputils::Data>> loaded_) {
return _baseBlockStore->load(key).then([this, key] (boost::future<boost::optional<cpputils::Data>> loaded_) {
auto loaded = loaded_.get();
if (boost::none == loaded) {
if (_missingBlockIsIntegrityViolation && _knownBlockVersions.blockShouldExist(key)) {
integrityViolationDetected("A block that should exist wasn't found. Did an attacker delete it?");
}
return boost::none;
return boost::optional<cpputils::Data>(boost::none);
}
if (!_checkHeader(key, *loaded)) {
return boost::none;
}
return *loaded;
_checkHeader(key, *loaded);
return boost::optional<cpputils::Data>(_removeHeader(*loaded));
});
}
boost::future<void> store(const Key &key, const cpputils::Data &data) override {
_checkNoPastIntegrityViolations();
TODO Need to load first so it can see if the version number changed by another
THIS BUG IS ALSO IN THE NEXT BRANCH (i.e. without these changes here)
//...
//TODO There's a bug in the next branch in override(). They have to load and read the old version number too.
return load(key).then([this, key, data = data.copy()] (boost::future<boost::optional<cpputils::Data>> loaded_) {
auto loaded = loaded_.get();
if (boost::none == loaded) {
return tryCreate(key, data).then([] (boost::future<bool> success) {
if (!success.get()) {
throw std::runtime_error("Could neither store nor create the block in VersionCountingBlockStore::store");
}
});
}
// Loading the block already read the newest version number into _knownBlockVersions, now we only have to increment it
// TODO Check that (with a caching blockstore below) this doesn't impact performance
uint64_t version = _knownBlockVersions.incrementVersion(key);
cpputils::Data dataWithHeader = _prependHeaderToData(_knownBlockVersions.myClientId(), version, data);
return _baseBlockStore->store(key, dataWithHeader);
});
}
private:
// This header is prepended to blocks to allow future versions to have compatibility.
static constexpr uint16_t FORMAT_VERSION_HEADER = 0;
public:
static constexpr uint64_t VERSION_ZERO = 0;
static constexpr unsigned int CLIENTID_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER);
static constexpr unsigned int VERSION_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t);
@ -62,10 +78,7 @@ public:
private:
// This header is prepended to blocks to allow future versions to have compatibility.
static constexpr uint16_t FORMAT_VERSION_HEADER = 0;
cpputils::Data VersionCountingBlock::_prependHeaderToData(uint32_t myClientId, uint64_t version, const cpputils::Data &data) {
cpputils::Data _prependHeaderToData(uint32_t myClientId, uint64_t version, const cpputils::Data &data) {
static_assert(HEADER_LENGTH == sizeof(FORMAT_VERSION_HEADER) + sizeof(myClientId) + sizeof(version), "Wrong header length");
cpputils::Data result(data.size() + HEADER_LENGTH);
std::memcpy(result.dataOffset(0), &FORMAT_VERSION_HEADER, sizeof(FORMAT_VERSION_HEADER));
@ -75,32 +88,45 @@ private:
return result;
}
void _checkHeader(const Key &key, const cpputils::Data &data) {
void _checkHeader(const Key &key, const cpputils::Data &data) const {
_checkFormatHeader(data);
_checkVersionHeader(key, data);
}
void _checkFormatHeader(const cpputils::Data &data) {
void _checkFormatHeader(const cpputils::Data &data) const {
if (*reinterpret_cast<decltype(FORMAT_VERSION_HEADER)*>(data.data()) != FORMAT_VERSION_HEADER) {
throw std::runtime_error("The versioned block has the wrong format. Was it created with a newer version of CryFS?");
}
}
void _checkVersionHeader(const Key &key, const cpputils::Data &data) {
uint32_t clientId;
std::memcpy(&clientId, _dataWithHeader.dataOffset(CLIENTID_HEADER_OFFSET), sizeof(clientId));
void _checkVersionHeader(const Key &key, const cpputils::Data &data) const {
uint32_t clientId = _readClientId(data);
uint64_t version = _readVersion(data);
uint64_t version;
std::memcpy(&version, _dataWithHeader.dataOffset(VERSION_HEADER_OFFSET), sizeof(version));
if(!_knownBlockVersions.checkAndUpdateVersion(lastClientId, key, version)) {
if(!_knownBlockVersions.checkAndUpdateVersion(clientId, key, version)) {
integrityViolationDetected("The block version number is too low. Did an attacker try to roll back the block or to re-introduce a deleted block?");
}
}
void _checkNoPastIntegrityViolations() {
static uint32_t _readClientId(const cpputils::Data &data) {
uint32_t clientId;
std::memcpy(&clientId, data.dataOffset(CLIENTID_HEADER_OFFSET), sizeof(clientId));
return clientId;
}
static uint64_t _readVersion(const cpputils::Data &data) {
uint64_t version;
std::memcpy(&version, data.dataOffset(VERSION_HEADER_OFFSET), sizeof(version));
return version;
}
cpputils::Data _removeHeader(const cpputils::Data &data) const {
return data.copyAndRemovePrefix(HEADER_LENGTH);
}
void _checkNoPastIntegrityViolations() const {
if (_integrityViolationDetected) {
throw std::runtime_error(string() +
throw std::runtime_error(std::string() +
"There was an integrity violation detected. Preventing any further access to the file system. " +
"If you want to reset the integrity data (i.e. accept changes made by a potential attacker), " +
"please unmount the file system and delete the following file before re-mounting it: " +
@ -108,13 +134,13 @@ private:
}
}
void integrityViolationDetected(const string &reason) const {
void integrityViolationDetected(const std::string &reason) const {
_integrityViolationDetected = true;
throw IntegrityViolationError(reason);
}
cpputils::unique_ref<BlockStore2> _baseBlockStore;
KnownBlockVersions _knownBlockVersions;
mutable KnownBlockVersions _knownBlockVersions;
const bool _missingBlockIsIntegrityViolation;
mutable bool _integrityViolationDetected;

View File

@ -10,6 +10,8 @@
#include <boost/thread/future.hpp>
#include <cpp-utils/random/Random.h>
// TODO warn_unused_result for all boost::future interfaces
namespace blockstore {
class BlockStore2 {

View File

@ -8,6 +8,7 @@
#include "../macros.h"
#include <memory>
#include <fstream>
#include "../assert/assert.h"
namespace cpputils {
@ -21,6 +22,9 @@ public:
Data copy() const;
//TODO Test copyAndRemovePrefix
Data copyAndRemovePrefix(size_t prefixSize) const;
void *data();
const void *data() const;
@ -94,6 +98,13 @@ inline Data Data::copy() const {
return copy;
}
inline Data Data::copyAndRemovePrefix(size_t prefixSize) const {
ASSERT(prefixSize <= _size, "Can't remove more than there is");
Data copy(_size - prefixSize);
std::memcpy(copy.data(), dataOffset(prefixSize), copy.size());
return copy;
}
inline void *Data::data() {
return const_cast<void*>(const_cast<const Data*>(this)->data());
}

View File

@ -1,8 +1,11 @@
#include <cpp-utils/crypto/symmetric/ciphers.h>
#include <cpp-utils/crypto/symmetric/Cipher.h>
#include "blockstore/implementations/encrypted/EncryptedBlockStore.h"
#include "blockstore/implementations/encrypted/EncryptedBlockStore2.h"
#include "blockstore/implementations/testfake/FakeBlockStore.h"
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
#include "../../testutils/BlockStoreTest.h"
#include "../../testutils/BlockStore2Test.h"
//TODO Move FakeAuthenticatedCipher out of test folder to normal folder. Dependencies should not point into tests of other modules.
#include "../../../cpp-utils/crypto/symmetric/testutils/FakeAuthenticatedCipher.h"
#include <gtest/gtest.h>
@ -10,8 +13,11 @@
using ::testing::Test;
using blockstore::BlockStore;
using blockstore::BlockStore2;
using blockstore::encrypted::EncryptedBlockStore;
using blockstore::encrypted::EncryptedBlockStore2;
using blockstore::testfake::FakeBlockStore;
using blockstore::inmemory::InMemoryBlockStore2;
using cpputils::AES256_GCM;
using cpputils::AES256_CFB;
using cpputils::FakeAuthenticatedCipher;
@ -38,3 +44,21 @@ private:
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_FakeCipher, BlockStoreTest, EncryptedBlockStoreTestFixture<FakeAuthenticatedCipher>);
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_AES256_GCM, BlockStoreTest, EncryptedBlockStoreTestFixture<AES256_GCM>);
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_AES256_CFB, BlockStoreTest, EncryptedBlockStoreTestFixture<AES256_CFB>);
template<class Cipher>
class EncryptedBlockStore2TestFixture: public BlockStore2TestFixture {
public:
unique_ref<BlockStore2> createBlockStore() override {
return make_unique_ref<EncryptedBlockStore2<Cipher>>(make_unique_ref<InMemoryBlockStore2>(), createKeyFixture());
}
private:
static typename Cipher::EncryptionKey createKeyFixture(int seed = 0) {
Data data = DataFixture::generate(Cipher::EncryptionKey::BINARY_LENGTH, seed);
return Cipher::EncryptionKey::FromBinary(data.data());
}
};
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_FakeCipher, BlockStore2Test, EncryptedBlockStore2TestFixture<FakeAuthenticatedCipher>);
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_AES256_GCM, BlockStore2Test, EncryptedBlockStore2TestFixture<AES256_GCM>);
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_AES256_CFB, BlockStore2Test, EncryptedBlockStore2TestFixture<AES256_CFB>);

View File

@ -1,14 +1,18 @@
#include "blockstore/implementations/inmemory/InMemoryBlock.h"
#include "blockstore/implementations/inmemory/InMemoryBlockStore.h"
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
#include "../../testutils/BlockStoreTest.h"
#include "../../testutils/BlockStore2Test.h"
#include "../../testutils/BlockStoreWithRandomKeysTest.h"
#include <gtest/gtest.h>
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
using blockstore::BlockStore;
using blockstore::BlockStore2;
using blockstore::BlockStoreWithRandomKeys;
using blockstore::inmemory::InMemoryBlockStore;
using blockstore::inmemory::InMemoryBlockStore2;
using cpputils::unique_ref;
using cpputils::make_unique_ref;
@ -29,3 +33,12 @@ public:
};
INSTANTIATE_TYPED_TEST_CASE_P(InMemory, BlockStoreWithRandomKeysTest, InMemoryBlockStoreWithRandomKeysTestFixture);
class InMemoryBlockStore2TestFixture: public BlockStore2TestFixture {
public:
unique_ref<BlockStore2> createBlockStore() override {
return make_unique_ref<InMemoryBlockStore2>();
}
};
INSTANTIATE_TYPED_TEST_CASE_P(InMemory, BlockStore2Test, InMemoryBlockStore2TestFixture);

View File

@ -1,6 +1,8 @@
#include "blockstore/implementations/ondisk/OnDiskBlock.h"
#include "blockstore/implementations/ondisk/OnDiskBlockStore.h"
#include "blockstore/implementations/ondisk/OnDiskBlockStore2.h"
#include "../../testutils/BlockStoreTest.h"
#include "../../testutils/BlockStore2Test.h"
#include "../../testutils/BlockStoreWithRandomKeysTest.h"
#include <gtest/gtest.h>
@ -10,6 +12,8 @@
using blockstore::BlockStore;
using blockstore::BlockStoreWithRandomKeys;
using blockstore::ondisk::OnDiskBlockStore;
using blockstore::BlockStore2;
using blockstore::ondisk::OnDiskBlockStore2;
using cpputils::TempDir;
using cpputils::unique_ref;
@ -40,3 +44,16 @@ private:
};
INSTANTIATE_TYPED_TEST_CASE_P(OnDisk, BlockStoreWithRandomKeysTest, OnDiskBlockStoreWithRandomKeysTestFixture);
class OnDiskBlockStore2TestFixture: public BlockStore2TestFixture {
public:
OnDiskBlockStore2TestFixture(): tempdir() {}
unique_ref<BlockStore2> createBlockStore() override {
return make_unique_ref<OnDiskBlockStore2>(tempdir.path());
}
private:
TempDir tempdir;
};
INSTANTIATE_TYPED_TEST_CASE_P(OnDisk, BlockStore2Test, OnDiskBlockStore2TestFixture);

View File

@ -1,15 +1,21 @@
#include "blockstore/implementations/versioncounting/VersionCountingBlockStore.h"
#include "blockstore/implementations/versioncounting/VersionCountingBlockStore2.h"
#include "blockstore/implementations/testfake/FakeBlockStore.h"
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
#include "../../testutils/BlockStoreTest.h"
#include "../../testutils/BlockStore2Test.h"
#include <gtest/gtest.h>
#include <cpp-utils/tempfile/TempFile.h>
using ::testing::Test;
using blockstore::BlockStore;
using blockstore::BlockStore2;
using blockstore::versioncounting::VersionCountingBlockStore;
using blockstore::versioncounting::VersionCountingBlockStore2;
using blockstore::versioncounting::KnownBlockVersions;
using blockstore::testfake::FakeBlockStore;
using blockstore::inmemory::InMemoryBlockStore2;
using cpputils::Data;
using cpputils::DataFixture;
@ -17,16 +23,30 @@ using cpputils::make_unique_ref;
using cpputils::unique_ref;
using cpputils::TempFile;
template<bool SINGLECLIENT>
template<bool MissingBlockIsIntegrityViolation>
class VersionCountingBlockStoreTestFixture: public BlockStoreTestFixture {
public:
VersionCountingBlockStoreTestFixture() :stateFile(false) {}
TempFile stateFile;
unique_ref<BlockStore> createBlockStore() override {
return make_unique_ref<VersionCountingBlockStore>(make_unique_ref<FakeBlockStore>(), stateFile.path(), 0x12345678, SINGLECLIENT);
return make_unique_ref<VersionCountingBlockStore>(make_unique_ref<FakeBlockStore>(), stateFile.path(), 0x12345678, MissingBlockIsIntegrityViolation);
}
};
INSTANTIATE_TYPED_TEST_CASE_P(VersionCounting_multiclient, BlockStoreTest, VersionCountingBlockStoreTestFixture<false>);
INSTANTIATE_TYPED_TEST_CASE_P(VersionCounting_singleclient, BlockStoreTest, VersionCountingBlockStoreTestFixture<true>);
template<bool MissingBlockIsIntegrityViolation>
class VersionCountingBlockStore2TestFixture: public BlockStore2TestFixture {
public:
VersionCountingBlockStore2TestFixture() :stateFile(false) {}
TempFile stateFile;
unique_ref<BlockStore2> createBlockStore() override {
return make_unique_ref<VersionCountingBlockStore2>(make_unique_ref<InMemoryBlockStore2>(), stateFile.path(), 0x12345678, MissingBlockIsIntegrityViolation);
}
};
INSTANTIATE_TYPED_TEST_CASE_P(VersionCounting_multiclient, BlockStore2Test, VersionCountingBlockStore2TestFixture<false>);
INSTANTIATE_TYPED_TEST_CASE_P(VersionCounting_singleclient, BlockStore2Test, VersionCountingBlockStore2TestFixture<true>);

View File

@ -0,0 +1,263 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_TESTUTILS_BLOCKSTORE2TEST_H_
#define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_TESTUTILS_BLOCKSTORE2TEST_H_
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cpp-utils/data/DataFixture.h>
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
#include "blockstore/interface/BlockStore2.h"
namespace boost {
inline void PrintTo(const optional<cpputils::Data> &, ::std::ostream *os) {
*os << "optional<Data>";
}
}
class BlockStore2TestFixture {
public:
virtual ~BlockStore2TestFixture() {}
virtual cpputils::unique_ref<blockstore::BlockStore2> createBlockStore() = 0;
};
template<class ConcreteBlockStoreTestFixture>
class BlockStore2Test: public ::testing::Test {
public:
BlockStore2Test() :fixture(), blockStore(this->fixture.createBlockStore()) {}
BOOST_STATIC_ASSERT_MSG(
(std::is_base_of<BlockStore2TestFixture, ConcreteBlockStoreTestFixture>::value),
"Given test fixture for instantiating the (type parameterized) BlockStoreTest must inherit from BlockStoreTestFixture"
);
ConcreteBlockStoreTestFixture fixture;
cpputils::unique_ref<blockstore::BlockStore2> blockStore;
};
TYPED_TEST_CASE_P(BlockStore2Test);
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenCallingTryCreateOnExistingBlock_thenFails) {
blockstore::Key key = this->blockStore->create(cpputils::Data(1024)).get();
EXPECT_FALSE(this->blockStore->tryCreate(key, cpputils::Data(1024)).get());
}
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenCallingTryCreateOnNonExistingBlock_thenSucceeds) {
blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
EXPECT_TRUE(this->blockStore->tryCreate(key, cpputils::Data(1024)).get());
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenCallingTryCreateOnNonExistingBlock_thenSucceeds) {
this->blockStore->create(cpputils::Data(512));
blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
EXPECT_TRUE(this->blockStore->tryCreate(key, cpputils::Data(1024)).get());
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenLoadExistingBlock_thenSucceeds) {
blockstore::Key key = this->blockStore->create(cpputils::Data(1024)).get();
EXPECT_NE(boost::none, this->blockStore->load(key).get());
}
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenLoadNonexistingBlock_thenFails) {
const blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
EXPECT_EQ(boost::none, this->blockStore->load(key).get());
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenLoadNonexistingBlock_thenFails) {
this->blockStore->create(cpputils::Data(512));
const blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
EXPECT_EQ(boost::none, this->blockStore->load(key).get());
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringExistingBlock_thenSucceeds) {
blockstore::Key key = this->blockStore->create(cpputils::Data(1024)).get();
this->blockStore->store(key, cpputils::Data(1024)).wait();
}
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringNonexistingBlock_thenSucceeds) {
const blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
this->blockStore->store(key, cpputils::Data(1024)).wait();
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringNonexistingBlock_thenSucceeds) {
this->blockStore->create(cpputils::Data(512));
const blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
this->blockStore->store(key, cpputils::Data(1024)).wait();
}
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenCreatingTwoBlocks_thenTheyGetDifferentKeys) {
blockstore::Key key1 = this->blockStore->create(cpputils::Data(1024)).get();
blockstore::Key key2 = this->blockStore->create(cpputils::Data(1024)).get();
EXPECT_NE(key1, key2);
}
TYPED_TEST_P(BlockStore2Test, givenOtherwiseEmptyBlockStore_whenRemovingBlock_thenBlockIsNotLoadableAnymore) {
blockstore::Key key = this->blockStore->create(cpputils::Data(1024)).get();
EXPECT_NE(boost::none, this->blockStore->load(key).get());
this->blockStore->remove(key).get();
EXPECT_EQ(boost::none, this->blockStore->load(key).get());
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenRemovingBlock_thenBlockIsNotLoadableAnymore) {
blockstore::Key key = this->blockStore->create(cpputils::Data(1024)).get();
this->blockStore->create(cpputils::Data(512));
EXPECT_NE(boost::none, this->blockStore->load(key).get());
this->blockStore->remove(key).get();
EXPECT_EQ(boost::none, this->blockStore->load(key).get());
}
TYPED_TEST_P(BlockStore2Test, givenOtherwiseEmptyBlockStore_whenRemovingExistingBlock_thenSucceeds) {
blockstore::Key key = this->blockStore->create(cpputils::Data(1024)).get();
EXPECT_EQ(true, this->blockStore->remove(key).get());
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenRemovingExistingBlock_thenSucceeds) {
blockstore::Key key = this->blockStore->create(cpputils::Data(1024)).get();
this->blockStore->create(cpputils::Data(512));
EXPECT_EQ(true, this->blockStore->remove(key).get());
}
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenRemovingNonexistingBlock_thenFails) {
const blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
auto result = this->blockStore->remove(key).get();
EXPECT_EQ(false, result);
}
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads) {
auto key = this->blockStore->create(cpputils::Data(0)).get();
auto loaded = this->blockStore->load(key).get().value();
EXPECT_EQ(0u, loaded.size());
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads) {
this->blockStore->create(cpputils::Data(512));
auto key = this->blockStore->create(cpputils::Data(0)).get();
auto loaded = this->blockStore->load(key).get().value();
EXPECT_EQ(0u, loaded.size());
}
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads) {
cpputils::Data data = cpputils::DataFixture::generate(1024);
auto key = this->blockStore->create(data.copy()).get();
auto loaded = this->blockStore->load(key).get().value();
EXPECT_EQ(loaded, data);
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads) {
this->blockStore->create(cpputils::Data(512));
cpputils::Data data = cpputils::DataFixture::generate(1024);
auto key = this->blockStore->create(data.copy()).get();
auto loaded = this->blockStore->load(key).get().value();
EXPECT_EQ(loaded, data);
}
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringAndLoadingNonExistingEmptyBlock_thenCorrectBlockLoads) {
const blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
this->blockStore->store(key, cpputils::Data(0)).wait();
auto loaded = this->blockStore->load(key).get().value();
EXPECT_EQ(0u, loaded.size());
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringAndLoadingNonExistingEmptyBlock_thenCorrectBlockLoads) {
this->blockStore->create(cpputils::Data(512));
const blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
this->blockStore->store(key, cpputils::Data(0)).wait();
auto loaded = this->blockStore->load(key).get().value();
EXPECT_EQ(0u, loaded.size());
}
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringAndLoadingNonExistingNonEmptyBlock_thenCorrectBlockLoads) {
const blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
cpputils::Data data = cpputils::DataFixture::generate(1024);
this->blockStore->store(key, data.copy()).wait();
auto loaded = this->blockStore->load(key).get().value();
EXPECT_EQ(data, loaded);
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringAndLoadingNonExistingNonEmptyBlock_thenCorrectBlockLoads) {
this->blockStore->create(cpputils::Data(512));
const blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
cpputils::Data data = cpputils::DataFixture::generate(1024);
this->blockStore->store(key, data.copy()).wait();
auto loaded = this->blockStore->load(key).get().value();
EXPECT_EQ(data, loaded);
}
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringAndLoadingExistingEmptyBlock_thenCorrectBlockLoads) {
auto key = this->blockStore->create(cpputils::Data(512)).get();
this->blockStore->store(key, cpputils::Data(0)).wait();
auto loaded = this->blockStore->load(key).get().value();
EXPECT_EQ(0u, loaded.size());
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringAndLoadingExistingEmptyBlock_thenCorrectBlockLoads) {
this->blockStore->create(cpputils::Data(512)).get();
auto key = this->blockStore->create(cpputils::Data(512)).get();
this->blockStore->store(key, cpputils::Data(0)).wait();
auto loaded = this->blockStore->load(key).get().value();
EXPECT_EQ(0u, loaded.size());
}
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringAndLoadingExistingNonEmptyBlock_thenCorrectBlockLoads) {
auto key = this->blockStore->create(cpputils::Data(512)).get();
cpputils::Data data = cpputils::DataFixture::generate(1024);
this->blockStore->store(key, data.copy()).wait();
auto loaded = this->blockStore->load(key).get().value();
EXPECT_EQ(data, loaded);
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringAndLoadingExistingNonEmptyBlock_thenCorrectBlockLoads) {
this->blockStore->create(cpputils::Data(512)).get();
auto key = this->blockStore->create(cpputils::Data(512)).get();
cpputils::Data data = cpputils::DataFixture::generate(1024);
this->blockStore->store(key, data.copy()).wait();
auto loaded = this->blockStore->load(key).get().value();
EXPECT_EQ(data, loaded);
}
TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenLoadingNonExistingBlock_thenFails) {
const blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
EXPECT_EQ(boost::none, this->blockStore->load(key).get());
}
TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenLoadingNonExistingBlock_thenFails) {
this->blockStore->create(cpputils::Data(512)).get();
const blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
EXPECT_EQ(boost::none, this->blockStore->load(key).get());
}
REGISTER_TYPED_TEST_CASE_P(BlockStore2Test,
givenNonEmptyBlockStore_whenCallingTryCreateOnExistingBlock_thenFails,
givenEmptyBlockStore_whenCallingTryCreateOnNonExistingBlock_thenSucceeds,
givenNonEmptyBlockStore_whenCallingTryCreateOnNonExistingBlock_thenSucceeds,
givenNonEmptyBlockStore_whenLoadExistingBlock_thenSucceeds,
givenEmptyBlockStore_whenLoadNonexistingBlock_thenFails,
givenNonEmptyBlockStore_whenLoadNonexistingBlock_thenFails,
givenNonEmptyBlockStore_whenStoringExistingBlock_thenSucceeds,
givenEmptyBlockStore_whenStoringNonexistingBlock_thenSucceeds,
givenNonEmptyBlockStore_whenStoringNonexistingBlock_thenSucceeds,
givenEmptyBlockStore_whenCreatingTwoBlocks_thenTheyGetDifferentKeys,
givenOtherwiseEmptyBlockStore_whenRemovingBlock_thenBlockIsNotLoadableAnymore,
givenNonEmptyBlockStore_whenRemovingBlock_thenBlockIsNotLoadableAnymore,
givenOtherwiseEmptyBlockStore_whenRemovingExistingBlock_thenSucceeds,
givenNonEmptyBlockStore_whenRemovingExistingBlock_thenSucceeds,
givenEmptyBlockStore_whenRemovingNonexistingBlock_thenFails,
givenEmptyBlockStore_whenCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads,
givenNonEmptyBlockStore_whenCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads,
givenEmptyBlockStore_whenCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads,
givenNonEmptyBlockStore_whenCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads,
givenEmptyBlockStore_whenStoringAndLoadingNonExistingEmptyBlock_thenCorrectBlockLoads,
givenNonEmptyBlockStore_whenStoringAndLoadingNonExistingEmptyBlock_thenCorrectBlockLoads,
givenEmptyBlockStore_whenStoringAndLoadingNonExistingNonEmptyBlock_thenCorrectBlockLoads,
givenNonEmptyBlockStore_whenStoringAndLoadingNonExistingNonEmptyBlock_thenCorrectBlockLoads,
givenEmptyBlockStore_whenStoringAndLoadingExistingEmptyBlock_thenCorrectBlockLoads,
givenNonEmptyBlockStore_whenStoringAndLoadingExistingEmptyBlock_thenCorrectBlockLoads,
givenEmptyBlockStore_whenStoringAndLoadingExistingNonEmptyBlock_thenCorrectBlockLoads,
givenNonEmptyBlockStore_whenStoringAndLoadingExistingNonEmptyBlock_thenCorrectBlockLoads,
givenEmptyBlockStore_whenLoadingNonExistingBlock_thenFails,
givenNonEmptyBlockStore_whenLoadingNonExistingBlock_thenFails
);
#endif