Add test cases for BlockStore2 and fix existing BlockStore2 implementations
This commit is contained in:
parent
70bfc47a2f
commit
dbf54b9563
@ -9,6 +9,7 @@ set(SOURCES
|
|||||||
implementations/testfake/FakeBlock.cpp
|
implementations/testfake/FakeBlock.cpp
|
||||||
implementations/inmemory/InMemoryBlock.cpp
|
implementations/inmemory/InMemoryBlock.cpp
|
||||||
implementations/inmemory/InMemoryBlockStore.cpp
|
implementations/inmemory/InMemoryBlockStore.cpp
|
||||||
|
implementations/inmemory/InMemoryBlockStore2.cpp
|
||||||
implementations/parallelaccess/ParallelAccessBlockStore.cpp
|
implementations/parallelaccess/ParallelAccessBlockStore.cpp
|
||||||
implementations/parallelaccess/BlockRef.cpp
|
implementations/parallelaccess/BlockRef.cpp
|
||||||
implementations/parallelaccess/ParallelAccessBlockStoreAdapter.cpp
|
implementations/parallelaccess/ParallelAccessBlockStoreAdapter.cpp
|
||||||
|
@ -21,7 +21,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
boost::future<bool> tryCreate(const Key &key, const cpputils::Data &data) override {
|
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);
|
return _baseBlockStore->tryCreate(key, encrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,34 +31,43 @@ public:
|
|||||||
|
|
||||||
boost::future<boost::optional<cpputils::Data>> load(const Key &key) const override {
|
boost::future<boost::optional<cpputils::Data>> load(const Key &key) const override {
|
||||||
auto loaded = _baseBlockStore->load(key);
|
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();
|
auto data = data_.get();
|
||||||
if (boost::none == data) {
|
if (boost::none == data) {
|
||||||
return boost::optional<cpputils::Data>(boost::none);
|
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 {
|
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);
|
return _baseBlockStore->store(key, encrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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);
|
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> _tryDecrypt(const Key &key, const cpputils::Data &data) const {
|
||||||
boost::optional<cpputils::Data> decrypted = Cipher::decrypt((byte*)data.data(), data.size(), _encKey);
|
auto ciphertext = _checkAndRemoveFormatHeader(data);
|
||||||
if (boost::none != decrypted && !_keyHeaderIsCorrect(key, *decrypted)) {
|
boost::optional<cpputils::Data> decrypted = Cipher::decrypt((byte*)ciphertext.data(), ciphertext.size(), _encKey);
|
||||||
|
if (boost::none == decrypted) {
|
||||||
// TODO Warning
|
// TODO Warning
|
||||||
return boost::none;
|
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) {
|
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);
|
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;
|
cpputils::unique_ref<BlockStore2> _baseBlockStore;
|
||||||
typename Cipher::EncryptionKey _encKey;
|
typename Cipher::EncryptionKey _encKey;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
@ -1,7 +1,12 @@
|
|||||||
#include "OnDiskBlockStore2.h"
|
#include "OnDiskBlockStore2.h"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
namespace blockstore {
|
namespace blockstore {
|
||||||
namespace ondisk {
|
namespace ondisk {
|
||||||
|
|
||||||
|
const string OnDiskBlockStore2::FORMAT_VERSION_HEADER_PREFIX = "cryfs;block;";
|
||||||
|
const string OnDiskBlockStore2::FORMAT_VERSION_HEADER = OnDiskBlockStore2::FORMAT_VERSION_HEADER_PREFIX + "0";
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,48 +7,95 @@
|
|||||||
#include <cpp-utils/macros.h>
|
#include <cpp-utils/macros.h>
|
||||||
#include <cpp-utils/pointer/unique_ref.h>
|
#include <cpp-utils/pointer/unique_ref.h>
|
||||||
#include "OnDiskBlockStore.h"
|
#include "OnDiskBlockStore.h"
|
||||||
|
#include <cpp-utils/logging/logging.h>
|
||||||
|
|
||||||
namespace blockstore {
|
namespace blockstore {
|
||||||
namespace ondisk {
|
namespace ondisk {
|
||||||
|
|
||||||
//TODO Implement without basing on OnDiskBlockStore
|
|
||||||
|
|
||||||
class OnDiskBlockStore2 final: public BlockStore2 {
|
class OnDiskBlockStore2 final: public BlockStore2 {
|
||||||
public:
|
public:
|
||||||
explicit OnDiskBlockStore2(cpputils::unique_ref<OnDiskBlockStore> base)
|
explicit OnDiskBlockStore2(const boost::filesystem::path& path)
|
||||||
: _base(std::move(base)) {}
|
: _rootDir(path) {}
|
||||||
|
|
||||||
boost::future<bool> tryCreate(const Key &key, const cpputils::Data &data) override {
|
boost::future<bool> tryCreate(const Key &key, const cpputils::Data &data) override {
|
||||||
auto created = _base->tryCreate(key, data.copy());
|
auto filepath = _getFilepath(key);
|
||||||
if (boost::none == created) {
|
if (boost::filesystem::exists(filepath)) {
|
||||||
return boost::make_ready_future(false);
|
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 {
|
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);
|
return boost::make_ready_future(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::future<boost::optional<cpputils::Data>> load(const Key &key) const override {
|
boost::future<boost::optional<cpputils::Data>> load(const Key &key) const override {
|
||||||
auto block = _base->load(key);
|
auto fileContent = cpputils::Data::LoadFromFile(_getFilepath(key));
|
||||||
if (boost::none == block) {
|
if (fileContent == boost::none) {
|
||||||
return boost::make_ready_future<boost::optional<cpputils::Data>>(boost::none);
|
return boost::make_ready_future(boost::optional<cpputils::Data>(boost::none));
|
||||||
}
|
}
|
||||||
cpputils::Data data((*block)->size());
|
return boost::make_ready_future(boost::optional<cpputils::Data>(_checkAndRemoveHeader(std::move(*fileContent))));
|
||||||
std::memcpy(data.data(), (*block)->data(), data.size());
|
|
||||||
return boost::make_ready_future<boost::optional<cpputils::Data>>(std::move(data));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::future<void> store(const Key &key, const cpputils::Data &data) override {
|
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();
|
return boost::make_ready_future();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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);
|
DISALLOW_COPY_AND_ASSIGN(OnDiskBlockStore2);
|
||||||
};
|
};
|
||||||
|
@ -23,6 +23,7 @@ namespace blockstore {
|
|||||||
: _reason("Integrity violation: " + reason) {
|
: _reason("Integrity violation: " + reason) {
|
||||||
}
|
}
|
||||||
friend class VersionCountingBlockStore;
|
friend class VersionCountingBlockStore;
|
||||||
|
friend class VersionCountingBlockStore2;
|
||||||
|
|
||||||
std::string _reason;
|
std::string _reason;
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "../../interface/BlockStore2.h"
|
#include "../../interface/BlockStore2.h"
|
||||||
#include <cpp-utils/macros.h>
|
#include <cpp-utils/macros.h>
|
||||||
#include "KnownBlockVersions.h"
|
#include "KnownBlockVersions.h"
|
||||||
|
#include "IntegrityViolationError.h"
|
||||||
|
|
||||||
namespace blockstore {
|
namespace blockstore {
|
||||||
namespace versioncounting {
|
namespace versioncounting {
|
||||||
@ -19,42 +20,57 @@ public:
|
|||||||
|
|
||||||
boost::future<bool> tryCreate(const Key &key, const cpputils::Data &data) override {
|
boost::future<bool> tryCreate(const Key &key, const cpputils::Data &data) override {
|
||||||
_checkNoPastIntegrityViolations();
|
_checkNoPastIntegrityViolations();
|
||||||
uint64_t version = blockStore->knownBlockVersions()->incrementVersion(key);
|
uint64_t version = _knownBlockVersions.incrementVersion(key);
|
||||||
cpputils::Data dataWithHeader = _prependHeaderToData(blockStore->knownBlockVersions()->myClientId(), version, data);
|
cpputils::Data dataWithHeader = _prependHeaderToData(_knownBlockVersions.myClientId(), version, data);
|
||||||
return baseBlockStore_->tryCreate(key, dataWithHeader);
|
return _baseBlockStore->tryCreate(key, dataWithHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::future<bool> remove(const Key &key) override {
|
boost::future<bool> remove(const Key &key) override {
|
||||||
_checkNoPastIntegrityViolations();
|
_checkNoPastIntegrityViolations();
|
||||||
_knownBlockVersions.markBlockAsDeleted(key);
|
_knownBlockVersions.markBlockAsDeleted(key);
|
||||||
return baseBlockStore->remove(key);
|
return _baseBlockStore->remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::future<boost::optional<cpputils::Data>> load(const Key &key) const override {
|
boost::future<boost::optional<cpputils::Data>> load(const Key &key) const override {
|
||||||
_checkNoPastIntegrityViolations();
|
_checkNoPastIntegrityViolations();
|
||||||
auto loaded = baseBlockStore_->load(key);
|
return _baseBlockStore->load(key).then([this, key] (boost::future<boost::optional<cpputils::Data>> loaded_) {
|
||||||
loaded.then([this, key] (boost::future<boost::optional<cpputils::Data>> loaded_) {
|
|
||||||
auto loaded = loaded_.get();
|
auto loaded = loaded_.get();
|
||||||
if (boost::none == loaded) {
|
if (boost::none == loaded) {
|
||||||
if (_missingBlockIsIntegrityViolation && _knownBlockVersions.blockShouldExist(key)) {
|
if (_missingBlockIsIntegrityViolation && _knownBlockVersions.blockShouldExist(key)) {
|
||||||
integrityViolationDetected("A block that should exist wasn't found. Did an attacker delete it?");
|
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)) {
|
_checkHeader(key, *loaded);
|
||||||
return boost::none;
|
return boost::optional<cpputils::Data>(_removeHeader(*loaded));
|
||||||
}
|
|
||||||
return *loaded;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::future<void> store(const Key &key, const cpputils::Data &data) override {
|
boost::future<void> store(const Key &key, const cpputils::Data &data) override {
|
||||||
_checkNoPastIntegrityViolations();
|
_checkNoPastIntegrityViolations();
|
||||||
TODO Need to load first so it can see if the version number changed by another
|
//TODO There's a bug in the next branch in override(). They have to load and read the old version number too.
|
||||||
THIS BUG IS ALSO IN THE NEXT BRANCH (i.e. without these changes here)
|
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 uint64_t VERSION_ZERO = 0;
|
||||||
static constexpr unsigned int CLIENTID_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER);
|
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);
|
static constexpr unsigned int VERSION_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t);
|
||||||
@ -62,10 +78,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// This header is prepended to blocks to allow future versions to have compatibility.
|
cpputils::Data _prependHeaderToData(uint32_t myClientId, uint64_t version, const cpputils::Data &data) {
|
||||||
static constexpr uint16_t FORMAT_VERSION_HEADER = 0;
|
|
||||||
|
|
||||||
cpputils::Data VersionCountingBlock::_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");
|
static_assert(HEADER_LENGTH == sizeof(FORMAT_VERSION_HEADER) + sizeof(myClientId) + sizeof(version), "Wrong header length");
|
||||||
cpputils::Data result(data.size() + HEADER_LENGTH);
|
cpputils::Data result(data.size() + HEADER_LENGTH);
|
||||||
std::memcpy(result.dataOffset(0), &FORMAT_VERSION_HEADER, sizeof(FORMAT_VERSION_HEADER));
|
std::memcpy(result.dataOffset(0), &FORMAT_VERSION_HEADER, sizeof(FORMAT_VERSION_HEADER));
|
||||||
@ -75,32 +88,45 @@ private:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkHeader(const Key &key, const cpputils::Data &data) {
|
void _checkHeader(const Key &key, const cpputils::Data &data) const {
|
||||||
_checkFormatHeader(data);
|
_checkFormatHeader(data);
|
||||||
_checkVersionHeader(key, 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) {
|
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?");
|
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) {
|
void _checkVersionHeader(const Key &key, const cpputils::Data &data) const {
|
||||||
uint32_t clientId;
|
uint32_t clientId = _readClientId(data);
|
||||||
std::memcpy(&clientId, _dataWithHeader.dataOffset(CLIENTID_HEADER_OFFSET), sizeof(clientId));
|
uint64_t version = _readVersion(data);
|
||||||
|
|
||||||
uint64_t version;
|
if(!_knownBlockVersions.checkAndUpdateVersion(clientId, key, version)) {
|
||||||
std::memcpy(&version, _dataWithHeader.dataOffset(VERSION_HEADER_OFFSET), sizeof(version));
|
|
||||||
|
|
||||||
if(!_knownBlockVersions.checkAndUpdateVersion(lastClientId, 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?");
|
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) {
|
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. " +
|
"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), " +
|
"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: " +
|
"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;
|
_integrityViolationDetected = true;
|
||||||
throw IntegrityViolationError(reason);
|
throw IntegrityViolationError(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
cpputils::unique_ref<BlockStore2> _baseBlockStore;
|
cpputils::unique_ref<BlockStore2> _baseBlockStore;
|
||||||
KnownBlockVersions _knownBlockVersions;
|
mutable KnownBlockVersions _knownBlockVersions;
|
||||||
const bool _missingBlockIsIntegrityViolation;
|
const bool _missingBlockIsIntegrityViolation;
|
||||||
mutable bool _integrityViolationDetected;
|
mutable bool _integrityViolationDetected;
|
||||||
|
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
#include <boost/thread/future.hpp>
|
#include <boost/thread/future.hpp>
|
||||||
#include <cpp-utils/random/Random.h>
|
#include <cpp-utils/random/Random.h>
|
||||||
|
|
||||||
|
// TODO warn_unused_result for all boost::future interfaces
|
||||||
|
|
||||||
namespace blockstore {
|
namespace blockstore {
|
||||||
|
|
||||||
class BlockStore2 {
|
class BlockStore2 {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "../macros.h"
|
#include "../macros.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include "../assert/assert.h"
|
||||||
|
|
||||||
namespace cpputils {
|
namespace cpputils {
|
||||||
|
|
||||||
@ -21,6 +22,9 @@ public:
|
|||||||
|
|
||||||
Data copy() const;
|
Data copy() const;
|
||||||
|
|
||||||
|
//TODO Test copyAndRemovePrefix
|
||||||
|
Data copyAndRemovePrefix(size_t prefixSize) const;
|
||||||
|
|
||||||
void *data();
|
void *data();
|
||||||
const void *data() const;
|
const void *data() const;
|
||||||
|
|
||||||
@ -94,6 +98,13 @@ inline Data Data::copy() const {
|
|||||||
return copy;
|
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() {
|
inline void *Data::data() {
|
||||||
return const_cast<void*>(const_cast<const Data*>(this)->data());
|
return const_cast<void*>(const_cast<const Data*>(this)->data());
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
#include <cpp-utils/crypto/symmetric/ciphers.h>
|
#include <cpp-utils/crypto/symmetric/ciphers.h>
|
||||||
#include <cpp-utils/crypto/symmetric/Cipher.h>
|
#include <cpp-utils/crypto/symmetric/Cipher.h>
|
||||||
#include "blockstore/implementations/encrypted/EncryptedBlockStore.h"
|
#include "blockstore/implementations/encrypted/EncryptedBlockStore.h"
|
||||||
|
#include "blockstore/implementations/encrypted/EncryptedBlockStore2.h"
|
||||||
#include "blockstore/implementations/testfake/FakeBlockStore.h"
|
#include "blockstore/implementations/testfake/FakeBlockStore.h"
|
||||||
|
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
|
||||||
#include "../../testutils/BlockStoreTest.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.
|
//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 "../../../cpp-utils/crypto/symmetric/testutils/FakeAuthenticatedCipher.h"
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
@ -10,8 +13,11 @@
|
|||||||
using ::testing::Test;
|
using ::testing::Test;
|
||||||
|
|
||||||
using blockstore::BlockStore;
|
using blockstore::BlockStore;
|
||||||
|
using blockstore::BlockStore2;
|
||||||
using blockstore::encrypted::EncryptedBlockStore;
|
using blockstore::encrypted::EncryptedBlockStore;
|
||||||
|
using blockstore::encrypted::EncryptedBlockStore2;
|
||||||
using blockstore::testfake::FakeBlockStore;
|
using blockstore::testfake::FakeBlockStore;
|
||||||
|
using blockstore::inmemory::InMemoryBlockStore2;
|
||||||
using cpputils::AES256_GCM;
|
using cpputils::AES256_GCM;
|
||||||
using cpputils::AES256_CFB;
|
using cpputils::AES256_CFB;
|
||||||
using cpputils::FakeAuthenticatedCipher;
|
using cpputils::FakeAuthenticatedCipher;
|
||||||
@ -38,3 +44,21 @@ private:
|
|||||||
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_FakeCipher, BlockStoreTest, EncryptedBlockStoreTestFixture<FakeAuthenticatedCipher>);
|
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_GCM, BlockStoreTest, EncryptedBlockStoreTestFixture<AES256_GCM>);
|
||||||
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_AES256_CFB, BlockStoreTest, EncryptedBlockStoreTestFixture<AES256_CFB>);
|
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>);
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
#include "blockstore/implementations/inmemory/InMemoryBlock.h"
|
#include "blockstore/implementations/inmemory/InMemoryBlock.h"
|
||||||
#include "blockstore/implementations/inmemory/InMemoryBlockStore.h"
|
#include "blockstore/implementations/inmemory/InMemoryBlockStore.h"
|
||||||
|
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
|
||||||
#include "../../testutils/BlockStoreTest.h"
|
#include "../../testutils/BlockStoreTest.h"
|
||||||
|
#include "../../testutils/BlockStore2Test.h"
|
||||||
#include "../../testutils/BlockStoreWithRandomKeysTest.h"
|
#include "../../testutils/BlockStoreWithRandomKeysTest.h"
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
|
||||||
|
|
||||||
|
|
||||||
using blockstore::BlockStore;
|
using blockstore::BlockStore;
|
||||||
|
using blockstore::BlockStore2;
|
||||||
using blockstore::BlockStoreWithRandomKeys;
|
using blockstore::BlockStoreWithRandomKeys;
|
||||||
using blockstore::inmemory::InMemoryBlockStore;
|
using blockstore::inmemory::InMemoryBlockStore;
|
||||||
|
using blockstore::inmemory::InMemoryBlockStore2;
|
||||||
using cpputils::unique_ref;
|
using cpputils::unique_ref;
|
||||||
using cpputils::make_unique_ref;
|
using cpputils::make_unique_ref;
|
||||||
|
|
||||||
@ -29,3 +33,12 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
INSTANTIATE_TYPED_TEST_CASE_P(InMemory, BlockStoreWithRandomKeysTest, InMemoryBlockStoreWithRandomKeysTestFixture);
|
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);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#include "blockstore/implementations/ondisk/OnDiskBlock.h"
|
#include "blockstore/implementations/ondisk/OnDiskBlock.h"
|
||||||
#include "blockstore/implementations/ondisk/OnDiskBlockStore.h"
|
#include "blockstore/implementations/ondisk/OnDiskBlockStore.h"
|
||||||
|
#include "blockstore/implementations/ondisk/OnDiskBlockStore2.h"
|
||||||
#include "../../testutils/BlockStoreTest.h"
|
#include "../../testutils/BlockStoreTest.h"
|
||||||
|
#include "../../testutils/BlockStore2Test.h"
|
||||||
#include "../../testutils/BlockStoreWithRandomKeysTest.h"
|
#include "../../testutils/BlockStoreWithRandomKeysTest.h"
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
@ -10,6 +12,8 @@
|
|||||||
using blockstore::BlockStore;
|
using blockstore::BlockStore;
|
||||||
using blockstore::BlockStoreWithRandomKeys;
|
using blockstore::BlockStoreWithRandomKeys;
|
||||||
using blockstore::ondisk::OnDiskBlockStore;
|
using blockstore::ondisk::OnDiskBlockStore;
|
||||||
|
using blockstore::BlockStore2;
|
||||||
|
using blockstore::ondisk::OnDiskBlockStore2;
|
||||||
|
|
||||||
using cpputils::TempDir;
|
using cpputils::TempDir;
|
||||||
using cpputils::unique_ref;
|
using cpputils::unique_ref;
|
||||||
@ -40,3 +44,16 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
INSTANTIATE_TYPED_TEST_CASE_P(OnDisk, BlockStoreWithRandomKeysTest, OnDiskBlockStoreWithRandomKeysTestFixture);
|
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);
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
#include "blockstore/implementations/versioncounting/VersionCountingBlockStore.h"
|
#include "blockstore/implementations/versioncounting/VersionCountingBlockStore.h"
|
||||||
|
#include "blockstore/implementations/versioncounting/VersionCountingBlockStore2.h"
|
||||||
#include "blockstore/implementations/testfake/FakeBlockStore.h"
|
#include "blockstore/implementations/testfake/FakeBlockStore.h"
|
||||||
|
#include "blockstore/implementations/inmemory/InMemoryBlockStore2.h"
|
||||||
#include "../../testutils/BlockStoreTest.h"
|
#include "../../testutils/BlockStoreTest.h"
|
||||||
|
#include "../../testutils/BlockStore2Test.h"
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <cpp-utils/tempfile/TempFile.h>
|
#include <cpp-utils/tempfile/TempFile.h>
|
||||||
|
|
||||||
using ::testing::Test;
|
using ::testing::Test;
|
||||||
|
|
||||||
using blockstore::BlockStore;
|
using blockstore::BlockStore;
|
||||||
|
using blockstore::BlockStore2;
|
||||||
using blockstore::versioncounting::VersionCountingBlockStore;
|
using blockstore::versioncounting::VersionCountingBlockStore;
|
||||||
|
using blockstore::versioncounting::VersionCountingBlockStore2;
|
||||||
using blockstore::versioncounting::KnownBlockVersions;
|
using blockstore::versioncounting::KnownBlockVersions;
|
||||||
using blockstore::testfake::FakeBlockStore;
|
using blockstore::testfake::FakeBlockStore;
|
||||||
|
using blockstore::inmemory::InMemoryBlockStore2;
|
||||||
|
|
||||||
using cpputils::Data;
|
using cpputils::Data;
|
||||||
using cpputils::DataFixture;
|
using cpputils::DataFixture;
|
||||||
@ -17,16 +23,30 @@ using cpputils::make_unique_ref;
|
|||||||
using cpputils::unique_ref;
|
using cpputils::unique_ref;
|
||||||
using cpputils::TempFile;
|
using cpputils::TempFile;
|
||||||
|
|
||||||
template<bool SINGLECLIENT>
|
template<bool MissingBlockIsIntegrityViolation>
|
||||||
class VersionCountingBlockStoreTestFixture: public BlockStoreTestFixture {
|
class VersionCountingBlockStoreTestFixture: public BlockStoreTestFixture {
|
||||||
public:
|
public:
|
||||||
VersionCountingBlockStoreTestFixture() :stateFile(false) {}
|
VersionCountingBlockStoreTestFixture() :stateFile(false) {}
|
||||||
|
|
||||||
TempFile stateFile;
|
TempFile stateFile;
|
||||||
unique_ref<BlockStore> createBlockStore() override {
|
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_multiclient, BlockStoreTest, VersionCountingBlockStoreTestFixture<false>);
|
||||||
INSTANTIATE_TYPED_TEST_CASE_P(VersionCounting_singleclient, BlockStoreTest, VersionCountingBlockStoreTestFixture<true>);
|
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>);
|
||||||
|
263
test/blockstore/testutils/BlockStore2Test.h
Normal file
263
test/blockstore/testutils/BlockStore2Test.h
Normal 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
|
Loading…
Reference in New Issue
Block a user