Merge from origin

This commit is contained in:
Sebastian Meßmer 2015-05-16 01:21:29 +02:00
commit e24f825259
93 changed files with 2167 additions and 1341 deletions

View File

@ -8,6 +8,7 @@ ADD_BOOST(filesystem system thread)
TARGET_LINK_LIBRARIES(${BII_BLOCK_TARGET} INTERFACE rt)
ACTIVATE_CPP14()
ENABLE_STYLE_WARNINGS()
# You can safely delete lines from here...

37
appveyor.yml Normal file
View File

@ -0,0 +1,37 @@
version: 0.9.{build}
os: Visual Studio 2015 RC
install:
- ps: wget http://www.cmake.org/files/v3.2/cmake-3.2.2-win32-x86.zip -OutFile cmake.zip
- cmd: echo "Unzipping cmake..."
- cmd: 7z x cmake.zip -o"C:\Program Files (x86)\" -y > nul
- cmd: set PATH=%PATH:CMake 2.8\bin=%;C:\Program Files (x86)\cmake-3.0.2-win32-x86\bin
- cmd: cmake --version
- cmd: echo "Downloading biicode..."
- ps: wget http://www.biicode.com/downloads/latest/win -OutFile bii-win.exe
- cmd: bii-win.exe /VERYSILENT
- cmd: set PATH=%PATH%;C:\Program Files (x86)\BiiCode\bii
- cmd: bii -v
- cmd: del bii-win.exe
- cmd: del cmake.zip
before_build:
- cmd: bii init -L
# copy files and folders
- cmd: dir
# - cmd: echo xcopy "%APPVEYOR_BUILD_FOLDER%" blocks\%block_user%\%block_name%\ /e
- cmd: bii configure -G "Visual Studio 14 2015"
build_script:
- cmd: bii build
test_script:
- cmd: cd bin
- cmd: amalulla_cpp-expression-parser_test-shunting-yard.exe
environment:
block_user:
"messmer"
block_name:
"blockstore"

View File

@ -1,13 +1,12 @@
# Biicode configuration file
[requirements]
cryptopp/cryptopp: 8
cryptopp/cryptopp: 9
google/gmock: 2
google/gtest: 10
google/gtest: 11
messmer/cmake: 3
messmer/cpp-utils: 2
messmer/parallelaccessstore: 0
messmer/tempfile: 4
[parent]
messmer/blockstore: 1

View File

@ -1,56 +0,0 @@
#include "Cache.h"
#include "PeriodicTask.h"
using std::unique_ptr;
using std::make_unique;
using std::mutex;
using std::lock_guard;
using std::pair;
namespace blockstore {
namespace caching {
constexpr uint32_t Cache::MAX_ENTRIES;
constexpr double Cache::PURGE_LIFETIME_SEC;
constexpr double Cache::PURGE_INTERVAL;
constexpr double Cache::MAX_LIFETIME_SEC;
Cache::Cache(): _cachedBlocks(), _timeoutFlusher(nullptr) {
//Don't initialize timeoutFlusher in the initializer list,
//because it then might already call Cache::popOldEntries() before Cache is done constructing
_timeoutFlusher = make_unique<PeriodicTask>(std::bind(&Cache::_popOldEntries, this), PURGE_INTERVAL);
}
Cache::~Cache() {
}
unique_ptr<Block> Cache::pop(const Key &key) {
lock_guard<mutex> lock(_mutex);
auto found = _cachedBlocks.pop(key);
if (found.get() == nullptr) {
return nullptr;
}
auto block = found->releaseBlock();
return block;
}
void Cache::push(unique_ptr<Block> block) {
lock_guard<mutex> lock(_mutex);
assert(_cachedBlocks.size() <= MAX_ENTRIES);
if (_cachedBlocks.size() == MAX_ENTRIES) {
_cachedBlocks.pop();
assert(_cachedBlocks.size() == MAX_ENTRIES-1);
}
Key key = block->key();
_cachedBlocks.push(key, make_unique<CacheEntry>(std::move(block)));
}
void Cache::_popOldEntries() {
lock_guard<mutex> lock(_mutex);
while(_cachedBlocks.size() > 0 && _cachedBlocks.peek().ageSeconds() > PURGE_LIFETIME_SEC) {
_cachedBlocks.pop();
}
}
}
}

View File

@ -1,44 +0,0 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_H_
#define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_H_
#include "CacheEntry.h"
#include "QueueMap.h"
#include "../../interface/Block.h"
#include <memory>
#include <mutex>
namespace blockstore {
namespace caching {
class PeriodicTask;
//TODO Test
//TODO Also throw blocks out after a timeout
class Cache {
public:
static constexpr uint32_t MAX_ENTRIES = 1000;
//TODO Experiment with good values
static constexpr double PURGE_LIFETIME_SEC = 0.5; //When an entry has this age, it will be purged from the cache
static constexpr double PURGE_INTERVAL = 0.5; // With this interval, we check for entries to purge
static constexpr double MAX_LIFETIME_SEC = PURGE_LIFETIME_SEC + PURGE_INTERVAL; // This is the oldest age an entry can reach (given purging works in an ideal world, i.e. with the ideal interval and in zero time)
Cache();
virtual ~Cache();
void push(std::unique_ptr<Block> block);
std::unique_ptr<Block> pop(const Key &key);
private:
void _popOldEntries();
mutable std::mutex _mutex;
QueueMap<Key, CacheEntry> _cachedBlocks;
std::unique_ptr<PeriodicTask> _timeoutFlusher;
};
}
}
#endif

View File

@ -1,2 +0,0 @@
#include "CacheEntry.h"
#include "../../interface/Block.h"

View File

@ -9,6 +9,7 @@
using std::unique_ptr;
using std::make_unique;
using cpputils::dynamic_pointer_move;
using cpputils::Data;
namespace blockstore {
namespace caching {
@ -27,13 +28,16 @@ unique_ptr<Block> CachingBlockStore::tryCreate(const Key &key, Data data) {
}
unique_ptr<Block> CachingBlockStore::load(const Key &key) {
auto block = _cache.pop(key);
if (block.get() != nullptr) {
return make_unique<CachedBlock>(std::move(block), this);
}
block = _baseBlockStore->load(key);
if (block.get() == nullptr) {
return nullptr;
boost::optional<unique_ptr<Block>> optBlock = _cache.pop(key);
unique_ptr<Block> block;
//TODO an optional<> class with .getOrElse() would make this code simpler. boost::optional<>::value_or_eval didn't seem to work with unique_ptr members.
if (optBlock) {
block = std::move(*optBlock);
} else {
block = _baseBlockStore->load(key);
if (block.get() == nullptr) {
return nullptr;
}
}
return make_unique<CachedBlock>(std::move(block), this);
}
@ -52,12 +56,12 @@ void CachingBlockStore::remove(std::unique_ptr<Block> block) {
}
uint64_t CachingBlockStore::numBlocks() const {
//TODO Add number of NewBlock instances
return _baseBlockStore->numBlocks() + _numNewBlocks;
}
void CachingBlockStore::release(unique_ptr<Block> block) {
_cache.push(std::move(block));
Key key = block->key();
_cache.push(key, std::move(block));
}
std::unique_ptr<Block> CachingBlockStore::tryCreateInBaseStore(const Key &key, Data data) {

View File

@ -2,32 +2,31 @@
#ifndef BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHINGBLOCKSTORE_H_
#define BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHINGBLOCKSTORE_H_
#include "Cache.h"
#include "cache/Cache.h"
#include "../../interface/BlockStore.h"
namespace blockstore {
namespace caching {
//TODO Rename this to CachingBlockStore and the other one to something else
//TODO Check that this blockstore allows parallel destructing of blocks (otherwise we won't encrypt blocks in parallel)
class CachingBlockStore: public BlockStore {
public:
CachingBlockStore(std::unique_ptr<BlockStore> baseBlockStore);
explicit CachingBlockStore(std::unique_ptr<BlockStore> baseBlockStore);
Key createKey() override;
std::unique_ptr<Block> tryCreate(const Key &key, Data data) override;
std::unique_ptr<Block> tryCreate(const Key &key, cpputils::Data data) override;
std::unique_ptr<Block> load(const Key &key) override;
void remove(std::unique_ptr<Block> block) override;
uint64_t numBlocks() const override;
void release(std::unique_ptr<Block> block);
std::unique_ptr<Block> tryCreateInBaseStore(const Key &key, Data data);
std::unique_ptr<Block> tryCreateInBaseStore(const Key &key, cpputils::Data data);
void removeFromBaseStore(std::unique_ptr<Block> block);
private:
std::unique_ptr<BlockStore> _baseBlockStore;
Cache _cache;
Cache<Key, std::unique_ptr<Block>> _cache;
uint32_t _numNewBlocks;
DISALLOW_COPY_AND_ASSIGN(CachingBlockStore);

View File

@ -3,6 +3,7 @@
using std::unique_ptr;
using std::make_unique;
using cpputils::Data;
namespace blockstore {
namespace caching {

View File

@ -3,7 +3,7 @@
#define BLOCKSTORE_IMPLEMENTATIONS_CACHING_NEWBLOCK_H_
#include "../../interface/BlockStore.h"
#include "../../utils/Data.h"
#include <messmer/cpp-utils/data/Data.h>
#include "messmer/cpp-utils/macros.h"
#include <memory>
@ -20,7 +20,7 @@ class CachingBlockStore;
// It only exists in the cache and it is created in the base block store when destructed.
class NewBlock: public Block {
public:
NewBlock(const Key &key, Data data, CachingBlockStore *blockStore);
NewBlock(const Key &key, cpputils::Data data, CachingBlockStore *blockStore);
virtual ~NewBlock();
const void *data() const override;
@ -35,7 +35,7 @@ public:
private:
CachingBlockStore *_blockStore;
Data _data;
cpputils::Data _data;
std::unique_ptr<Block> _baseBlock;
bool _dataChanged;

View File

@ -1,78 +0,0 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING2_MAP_H_
#define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING2_MAP_H_
#include <memory>
#include <unordered_map>
#include <cassert>
namespace blockstore {
namespace caching {
//TODO Test
//TODO Move to utils
template<class Key, class Value>
class QueueMap {
public:
QueueMap(): _entries(), _sentinel(nullptr, nullptr, &_sentinel, &_sentinel) {
}
virtual ~QueueMap() {}
void push(const Key &key, std::unique_ptr<Value> value) {
auto newEntry = std::make_unique<Entry>(&key, std::move(value), _sentinel.prev, &_sentinel);
_sentinel.prev->next = newEntry.get();
_sentinel.prev = newEntry.get();
auto insertResult = _entries.emplace(key, std::move(newEntry));
assert(insertResult.second == true);
}
std::unique_ptr<Value> pop(const Key &key) {
auto found = _entries.find(key);
if (found == _entries.end()) {
return nullptr;
}
_removeFromQueue(found->second.get());
auto value = std::move(found->second->value);
_entries.erase(found);
return value;
}
std::unique_ptr<Value> pop() {
return pop(*_sentinel.next->key);
}
const Value &peek() {
return *_sentinel.next->value;
}
uint32_t size() {
return _entries.size();
}
private:
struct Entry {
Entry(const Key *key_, std::unique_ptr<Value> value_, Entry *prev_, Entry *next_): key(nullptr), value(std::move(value_)), prev(prev_), next(next_) {
if (key_ != nullptr) {
key = std::make_unique<Key>(*key_);
}
}
std::unique_ptr<Key> key;
std::unique_ptr<Value> value;
Entry *prev;
Entry *next;
};
void _removeFromQueue(Entry *entry) {
entry->prev->next = entry->next;
entry->next->prev = entry->prev;
}
//TODO Double indirection unique_ptr<Entry> and Entry has unique_ptr<Value>. Necessary?
std::unordered_map<Key, std::unique_ptr<Entry>> _entries;
Entry _sentinel;
};
}
}
#endif

View File

@ -0,0 +1 @@
#include "Cache.h"

86
implementations/caching/cache/Cache.h vendored Normal file
View File

@ -0,0 +1,86 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_H_
#define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_H_
#include "CacheEntry.h"
#include "QueueMap.h"
#include "PeriodicTask.h"
#include <memory>
#include <mutex>
#include <boost/optional.hpp>
namespace blockstore {
namespace caching {
template<class Key, class Value>
class Cache {
public:
static constexpr uint32_t MAX_ENTRIES = 1000;
//TODO Experiment with good values
static constexpr double PURGE_LIFETIME_SEC = 0.5; //When an entry has this age, it will be purged from the cache
static constexpr double PURGE_INTERVAL = 0.5; // With this interval, we check for entries to purge
static constexpr double MAX_LIFETIME_SEC = PURGE_LIFETIME_SEC + PURGE_INTERVAL; // This is the oldest age an entry can reach (given purging works in an ideal world, i.e. with the ideal interval and in zero time)
Cache();
virtual ~Cache();
void push(const Key &key, Value value);
boost::optional<Value> pop(const Key &key);
private:
void _popOldEntries();
mutable std::mutex _mutex;
QueueMap<Key, CacheEntry<Key, Value>> _cachedBlocks;
std::unique_ptr<PeriodicTask> _timeoutFlusher;
};
template<class Key, class Value> constexpr uint32_t Cache<Key, Value>::MAX_ENTRIES;
template<class Key, class Value> constexpr double Cache<Key, Value>::PURGE_LIFETIME_SEC;
template<class Key, class Value> constexpr double Cache<Key, Value>::PURGE_INTERVAL;
template<class Key, class Value> constexpr double Cache<Key, Value>::MAX_LIFETIME_SEC;
template<class Key, class Value>
Cache<Key, Value>::Cache(): _cachedBlocks(), _timeoutFlusher(nullptr) {
//Don't initialize timeoutFlusher in the initializer list,
//because it then might already call Cache::popOldEntries() before Cache is done constructing
_timeoutFlusher = std::make_unique<PeriodicTask>(std::bind(&Cache::_popOldEntries, this), PURGE_INTERVAL);
}
template<class Key, class Value>
Cache<Key, Value>::~Cache() {
}
template<class Key, class Value>
boost::optional<Value> Cache<Key, Value>::pop(const Key &key) {
std::lock_guard<std::mutex> lock(_mutex);
auto found = _cachedBlocks.pop(key);
if (!found) {
return boost::none;
}
return found->releaseValue();
}
template<class Key, class Value>
void Cache<Key, Value>::push(const Key &key, Value value) {
std::lock_guard<std::mutex> lock(_mutex);
assert(_cachedBlocks.size() <= MAX_ENTRIES);
if (_cachedBlocks.size() == MAX_ENTRIES) {
_cachedBlocks.pop();
assert(_cachedBlocks.size() == MAX_ENTRIES-1);
}
_cachedBlocks.push(key, CacheEntry<Key, Value>(std::move(value)));
}
template<class Key, class Value>
void Cache<Key, Value>::_popOldEntries() {
std::lock_guard<std::mutex> lock(_mutex);
while(_cachedBlocks.size() > 0 && _cachedBlocks.peek()->ageSeconds() > PURGE_LIFETIME_SEC) {
_cachedBlocks.pop();
}
}
}
}
#endif

View File

@ -0,0 +1 @@
#include "CacheEntry.h"

View File

@ -8,12 +8,12 @@
#include <boost/date_time/posix_time/posix_time_types.hpp>
namespace blockstore {
class Block;
namespace caching {
template<class Key, class Value>
class CacheEntry {
public:
CacheEntry(std::unique_ptr<Block> block): _lastAccess(currentTime()), _block(std::move(block)) {
explicit CacheEntry(Value value): _lastAccess(currentTime()), _value(std::move(value)) {
}
CacheEntry(CacheEntry &&) = default;
@ -22,22 +22,13 @@ public:
return ((double)(currentTime() - _lastAccess).total_nanoseconds()) / ((double)1000000000);
}
std::unique_ptr<Block> releaseBlock() {
return std::move(_block);
}
void setNextEntry(const CacheEntry *entry) {
_nextEntry = entry;
}
const CacheEntry *nextEntry() {
return _nextEntry;
Value releaseValue() {
return std::move(_value);
}
private:
boost::posix_time::ptime _lastAccess;
std::unique_ptr<Block> _block;
const CacheEntry *_nextEntry;
Value _value;
static boost::posix_time::ptime currentTime() {
return boost::posix_time::microsec_clock::local_time();

View File

@ -9,17 +9,18 @@ namespace caching {
PeriodicTask::PeriodicTask(function<void ()> task, double intervalSec) : _thread(), _task(task), _intervalSec(intervalSec) {
_thread = boost::thread([this]() {
boost::chrono::nanoseconds interval((uint64_t)(UINT64_C(1000000000) * _intervalSec));
try {
boost::chrono::nanoseconds interval((uint64_t)(UINT64_C(1000000000) * _intervalSec));
try {
while(true) {
boost::this_thread::sleep_for(interval);
_task();
}
} catch (const boost::thread_interrupted &e) {
//Do nothing, exit thread.
} catch (...) {
cerr << "PeriodicTask crashed" << endl;
}
} catch (const boost::thread_interrupted &e) {
//Do nothing, exit thread.
} catch (...) {
//TODO Think about logging
cerr << "PeriodicTask crashed" << endl;
}
});
}

View File

@ -7,7 +7,6 @@
namespace blockstore {
namespace caching {
//TODO Test cases
class PeriodicTask {
public:
PeriodicTask(std::function<void ()> task, double intervalSec);

105
implementations/caching/cache/QueueMap.h vendored Normal file
View File

@ -0,0 +1,105 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING2_MAP_H_
#define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING2_MAP_H_
#include <memory>
#include <unordered_map>
#include <cassert>
#include <boost/optional.hpp>
#include <messmer/cpp-utils/macros.h>
namespace blockstore {
namespace caching {
// A class that is a queue and a map at the same time. We could also see it as an addressable queue.
template<class Key, class Value>
class QueueMap {
public:
QueueMap(): _entries(), _sentinel(&_sentinel, &_sentinel) {
}
virtual ~QueueMap() {
for (auto &entry : _entries) {
entry.second.release();
}
}
void push(const Key &key, Value value) {
auto newEntry = _entries.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(_sentinel.prev, &_sentinel));
if(newEntry.second != true) {
throw std::logic_error("There is already an element with this key");
}
newEntry.first->second.init(&newEntry.first->first, std::move(value));
//The following is ok, because std::unordered_map never invalidates pointers to its entries
_sentinel.prev->next = &newEntry.first->second;
_sentinel.prev = &newEntry.first->second;
}
boost::optional<Value> pop(const Key &key) {
auto found = _entries.find(key);
if (found == _entries.end()) {
return boost::none;
}
_removeFromQueue(found->second);
auto value = found->second.release();
_entries.erase(found);
return std::move(value);
}
boost::optional<Value> pop() {
if(_sentinel.next == &_sentinel) {
return boost::none;
}
return pop(*_sentinel.next->key);
}
boost::optional<const Value &> peek() {
if(_sentinel.next == &_sentinel) {
return boost::none;
}
return _sentinel.next->value();
}
uint32_t size() {
return _entries.size();
}
private:
class Entry {
public:
Entry(Entry *prev_, Entry *next_): prev(prev_), next(next_), key(nullptr), _value() {
}
void init(const Key *key_, Value value_) {
key = key_;
new(_value) Value(std::move(value_));
}
Value release() {
Value value = std::move(*reinterpret_cast<Value*>(_value));
reinterpret_cast<Value*>(_value)->~Value();
return value;
}
const Value &value() {
return *reinterpret_cast<Value*>(_value);
}
Entry *prev;
Entry *next;
const Key *key;
private:
alignas(Value) char _value[sizeof(Value)];
DISALLOW_COPY_AND_ASSIGN(Entry);
};
void _removeFromQueue(const Entry &entry) {
entry.prev->next = entry.next;
entry.next->prev = entry.prev;
}
std::unordered_map<Key, Entry> _entries;
Entry _sentinel;
DISALLOW_COPY_AND_ASSIGN(QueueMap);
};
}
}
#endif

View File

@ -1,91 +1 @@
#include "EncryptedBlock.h"
#include <cryptopp/cryptopp/modes.h>
#include "../../utils/BlockStoreUtils.h"
using CryptoPP::CFB_Mode;
using CryptoPP::AES;
using std::make_unique;
//TODO not only encryption, but also hmac
namespace blockstore {
namespace encrypted {
constexpr unsigned int EncryptedBlock::IV_SIZE;
std::unique_ptr<EncryptedBlock> EncryptedBlock::TryCreateNew(BlockStore *baseBlockStore, const Key &key, Data data, const EncryptionKey &encKey) {
Data encrypted = _encrypt(data, encKey);
auto baseBlock = baseBlockStore->tryCreate(key, std::move(encrypted));
if (baseBlock.get() == nullptr) {
//TODO Test this code branch
return nullptr;
}
return make_unique<EncryptedBlock>(std::move(baseBlock), encKey, std::move(data));
}
EncryptedBlock::EncryptedBlock(std::unique_ptr<Block> baseBlock, const EncryptionKey &encKey)
:EncryptedBlock(std::move(baseBlock), encKey, Data(USEABLE_BLOCK_SIZE(baseBlock->size()))) {
_decryptFromBaseBlock();
}
EncryptedBlock::EncryptedBlock(std::unique_ptr<Block> baseBlock, const EncryptionKey &encKey, Data plaintextData)
:Block(baseBlock->key()),
_baseBlock(std::move(baseBlock)),
_plaintextData(std::move(plaintextData)),
_encKey(encKey),
_dataChanged(false) {
}
EncryptedBlock::~EncryptedBlock() {
_encryptToBaseBlock();
}
const void *EncryptedBlock::data() const {
return _plaintextData.data();
}
void EncryptedBlock::write(const void *source, uint64_t offset, uint64_t size) {
assert(offset <= _plaintextData.size() && offset + size <= _plaintextData.size()); //Also check offset < _data->size() because of possible overflow in the addition
std::memcpy((uint8_t*)_plaintextData.data()+offset, source, size);
_dataChanged = true;
}
void EncryptedBlock::flush() {
_encryptToBaseBlock();
return _baseBlock->flush();
}
size_t EncryptedBlock::size() const {
return _plaintextData.size();
}
void EncryptedBlock::_decryptFromBaseBlock() {
const byte *iv = (byte*)_baseBlock->data();
const byte *data = (byte*)_baseBlock->data() + IV_SIZE;
auto decryption = CFB_Mode<AES>::Decryption((byte*)_encKey.data(), EncryptionKey::BINARY_LENGTH, iv);
decryption.ProcessData((byte*)_plaintextData.data(), data, _plaintextData.size());
}
void EncryptedBlock::_encryptToBaseBlock() {
if (_dataChanged) {
Data encrypted = _encrypt(_plaintextData, _encKey);
_baseBlock->write(encrypted.data(), 0, encrypted.size());
_dataChanged = false;
}
}
Data EncryptedBlock::_encrypt(const Data &plaintext, const EncryptionKey &encKey) {
FixedSizeData<IV_SIZE> iv = FixedSizeData<IV_SIZE>::CreateRandom();
auto encryption = CFB_Mode<AES>::Encryption(encKey.data(), EncryptionKey::BINARY_LENGTH, iv.data());
//TODO More performance when not using "Data encrypted" object, but encrypting directly to a target that was specified via a parameter using a specialized CryptoPP sink
Data encrypted(IV_SIZE + plaintext.size());
std::memcpy(encrypted.data(), iv.data(), IV_SIZE);
encryption.ProcessData((byte*)encrypted.data() + IV_SIZE, (byte*)plaintext.data(), plaintext.size());
return encrypted;
}
}
}

View File

@ -3,59 +3,156 @@
#define BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_ENCRYPTEDBLOCK_H_
#include "../../interface/Block.h"
#include "EncryptionKey.h"
#include "../../utils/Data.h"
#include <messmer/cpp-utils/data/Data.h>
#include "../../interface/BlockStore.h"
#include "messmer/cpp-utils/macros.h"
#include <memory>
#include <iostream>
#include <boost/optional.hpp>
#include "ciphers/Cipher.h"
namespace blockstore {
namespace encrypted {
class EncryptedBlockStore;
template<class Cipher> class EncryptedBlockStore;
//TODO Test EncryptedBlock
template<class Cipher>
class EncryptedBlock: public Block {
public:
BOOST_CONCEPT_ASSERT((CipherConcept<Cipher>));
static std::unique_ptr<EncryptedBlock> TryCreateNew(BlockStore *baseBlockStore, const Key &key, cpputils::Data data, const typename Cipher::EncryptionKey &encKey);
static std::unique_ptr<EncryptedBlock> TryDecrypt(std::unique_ptr<Block> baseBlock, const typename Cipher::EncryptionKey &key);
//TODO Storing key twice (in parent class and in object pointed to). Once would be enough.
EncryptedBlock(std::unique_ptr<Block> baseBlock, const EncryptionKey &encKey);
EncryptedBlock(std::unique_ptr<Block> baseBlock, const EncryptionKey &encKey, Data plaintextData);
EncryptedBlock(std::unique_ptr<Block> baseBlock, const typename Cipher::EncryptionKey &key, cpputils::Data plaintextWithHeader);
virtual ~EncryptedBlock();
static std::unique_ptr<EncryptedBlock> TryCreateNew(BlockStore *baseBlockStore, const Key &key, Data data, const EncryptionKey &encKey);
const void *data() const override;
void write(const void *source, uint64_t offset, uint64_t size) override;
void write(const void *source, uint64_t offset, uint64_t count) override;
void flush() override;
size_t size() const override;
static constexpr unsigned int BASE_BLOCK_SIZE(unsigned int useableBlockSize) {
return useableBlockSize + IV_SIZE;
}
static constexpr unsigned int USEABLE_BLOCK_SIZE(unsigned int baseBlockSize) {
return baseBlockSize - IV_SIZE;
}
std::unique_ptr<Block> releaseBlock();
private:
std::unique_ptr<Block> _baseBlock;
Data _plaintextData;
EncryptionKey _encKey;
cpputils::Data _plaintextWithHeader;
typename Cipher::EncryptionKey _encKey;
bool _dataChanged;
static constexpr unsigned int IV_SIZE = CryptoPP::AES::BLOCKSIZE;
byte *baseBlockIV();
byte *baseBlockData();
static constexpr unsigned int HEADER_LENGTH = Key::BINARY_LENGTH;
void _encryptToBaseBlock();
void _decryptFromBaseBlock();
static Data _encrypt(const Data &plaintext, const EncryptionKey &encKey);
static cpputils::Data _prependKeyHeaderToData(const Key &key, cpputils::Data data);
static bool _keyHeaderIsCorrect(const Key &key, const cpputils::Data &data);
DISALLOW_COPY_AND_ASSIGN(EncryptedBlock);
};
template<class Cipher>
constexpr unsigned int EncryptedBlock<Cipher>::HEADER_LENGTH;
template<class Cipher>
std::unique_ptr<EncryptedBlock<Cipher>> EncryptedBlock<Cipher>::TryCreateNew(BlockStore *baseBlockStore, const Key &key, cpputils::Data data, const typename Cipher::EncryptionKey &encKey) {
cpputils::Data plaintextWithHeader = _prependKeyHeaderToData(key, std::move(data));
cpputils::Data encrypted = Cipher::encrypt((byte*)plaintextWithHeader.data(), plaintextWithHeader.size(), encKey);
auto baseBlock = baseBlockStore->tryCreate(key, std::move(encrypted));
if (baseBlock.get() == nullptr) {
//TODO Test this code branch
return nullptr;
}
return std::make_unique<EncryptedBlock>(std::move(baseBlock), encKey, std::move(plaintextWithHeader));
}
template<class Cipher>
std::unique_ptr<EncryptedBlock<Cipher>> EncryptedBlock<Cipher>::TryDecrypt(std::unique_ptr<Block> baseBlock, const typename Cipher::EncryptionKey &encKey) {
//TODO Change BlockStore so we can read their "class Data" objects instead of "void *data()", and then we can change the Cipher interface to take Data objects instead of "byte *" + size
boost::optional<cpputils::Data> plaintextWithHeader = Cipher::decrypt((byte*)baseBlock->data(), baseBlock->size(), encKey);
if(!plaintextWithHeader) {
//Decryption failed (e.g. an authenticated cipher detected modifications to the ciphertext)
//TODO Think about logging
std::cerr << "Decrypting block " << baseBlock->key().ToString() << " failed. Was the block modified by an attacker?" << std::endl;
return nullptr;
}
if(!_keyHeaderIsCorrect(baseBlock->key(), *plaintextWithHeader)) {
//The stored key in the block data is incorrect - an attacker might have exchanged the contents with the encrypted data from a different block
//TODO Think about logging
std::cerr << "Decrypting block " << baseBlock->key().ToString() << " failed due to invalid block key. Was the block modified by an attacker?" << std::endl;
return nullptr;
}
return std::make_unique<EncryptedBlock<Cipher>>(std::move(baseBlock), encKey, std::move(*plaintextWithHeader));
}
template<class Cipher>
cpputils::Data EncryptedBlock<Cipher>::_prependKeyHeaderToData(const Key &key, cpputils::Data data) {
static_assert(HEADER_LENGTH >= Key::BINARY_LENGTH, "Key doesn't fit into the header");
cpputils::Data result(data.size() + HEADER_LENGTH);
std::memcpy(result.data(), key.data(), Key::BINARY_LENGTH);
std::memcpy((uint8_t*)result.data() + Key::BINARY_LENGTH, data.data(), data.size());
return result;
}
template<class Cipher>
bool EncryptedBlock<Cipher>::_keyHeaderIsCorrect(const Key &key, const cpputils::Data &data) {
return 0 == std::memcmp(key.data(), data.data(), Key::BINARY_LENGTH);
}
template<class Cipher>
EncryptedBlock<Cipher>::EncryptedBlock(std::unique_ptr<Block> baseBlock, const typename Cipher::EncryptionKey &encKey, cpputils::Data plaintextWithHeader)
:Block(baseBlock->key()),
_baseBlock(std::move(baseBlock)),
_plaintextWithHeader(std::move(plaintextWithHeader)),
_encKey(encKey),
_dataChanged(false) {
}
template<class Cipher>
EncryptedBlock<Cipher>::~EncryptedBlock() {
_encryptToBaseBlock();
}
template<class Cipher>
const void *EncryptedBlock<Cipher>::data() const {
return (uint8_t*)_plaintextWithHeader.data() + HEADER_LENGTH;
}
template<class Cipher>
void EncryptedBlock<Cipher>::write(const void *source, uint64_t offset, uint64_t count) {
assert(offset <= size() && offset + count <= size()); //Also check offset < size() because of possible overflow in the addition
std::memcpy((uint8_t*)_plaintextWithHeader.data()+HEADER_LENGTH+offset, source, count);
_dataChanged = true;
}
template<class Cipher>
void EncryptedBlock<Cipher>::flush() {
_encryptToBaseBlock();
return _baseBlock->flush();
}
template<class Cipher>
size_t EncryptedBlock<Cipher>::size() const {
return _plaintextWithHeader.size() - HEADER_LENGTH;
}
template<class Cipher>
void EncryptedBlock<Cipher>::_encryptToBaseBlock() {
if (_dataChanged) {
cpputils::Data encrypted = Cipher::encrypt((byte*)_plaintextWithHeader.data(), _plaintextWithHeader.size(), _encKey);
_baseBlock->write(encrypted.data(), 0, encrypted.size());
_dataChanged = false;
}
}
template<class Cipher>
std::unique_ptr<Block> EncryptedBlock<Cipher>::releaseBlock() {
return std::move(_baseBlock);
}
}
}

View File

@ -1,42 +1 @@
#include "EncryptedBlockStore.h"
#include "EncryptedBlock.h"
#include <messmer/cpp-utils/pointer.h>
#include "../../utils/BlockStoreUtils.h"
using std::unique_ptr;
using std::make_unique;
namespace blockstore {
namespace encrypted {
EncryptedBlockStore::EncryptedBlockStore(unique_ptr<BlockStore> baseBlockStore, const EncryptionKey &encKey)
: _baseBlockStore(std::move(baseBlockStore)), _encKey(encKey) {
}
Key EncryptedBlockStore::createKey() {
return _baseBlockStore->createKey();
}
unique_ptr<Block> EncryptedBlockStore::tryCreate(const Key &key, Data data) {
return EncryptedBlock::TryCreateNew(_baseBlockStore.get(), key, std::move(data), _encKey);
}
unique_ptr<Block> EncryptedBlockStore::load(const Key &key) {
auto block = _baseBlockStore->load(key);
if (block.get() == nullptr) {
return nullptr;
}
return make_unique<EncryptedBlock>(std::move(block), _encKey);
}
void EncryptedBlockStore::remove(unique_ptr<Block> block) {
return _baseBlockStore->remove(std::move(block));
}
uint64_t EncryptedBlockStore::numBlocks() const {
return _baseBlockStore->numBlocks();
}
}
}

View File

@ -4,28 +4,79 @@
#include "../../interface/BlockStore.h"
#include <messmer/cpp-utils/macros.h>
#include "EncryptionKey.h"
#include <messmer/cpp-utils/pointer.h>
#include "EncryptedBlock.h"
#include <iostream>
namespace blockstore {
namespace encrypted {
template<class Cipher>
class EncryptedBlockStore: public BlockStore {
public:
EncryptedBlockStore(std::unique_ptr<BlockStore> baseBlockStore, const EncryptionKey &encKey);
EncryptedBlockStore(std::unique_ptr<BlockStore> baseBlockStore, const typename Cipher::EncryptionKey &encKey);
//TODO Are createKey() tests included in generic BlockStoreTest? If not, add it!
Key createKey() override;
std::unique_ptr<Block> tryCreate(const Key &key, Data data) override;
std::unique_ptr<Block> tryCreate(const Key &key, cpputils::Data data) override;
std::unique_ptr<Block> load(const Key &key) override;
void remove(std::unique_ptr<Block> block) override;
uint64_t numBlocks() const override;
//This function should only be used by test cases
void __setKey(const typename Cipher::EncryptionKey &encKey);
private:
std::unique_ptr<BlockStore> _baseBlockStore;
EncryptionKey _encKey;
typename Cipher::EncryptionKey _encKey;
DISALLOW_COPY_AND_ASSIGN(EncryptedBlockStore);
};
template<class Cipher>
EncryptedBlockStore<Cipher>::EncryptedBlockStore(std::unique_ptr<BlockStore> baseBlockStore, const typename Cipher::EncryptionKey &encKey)
: _baseBlockStore(std::move(baseBlockStore)), _encKey(encKey) {
}
template<class Cipher>
Key EncryptedBlockStore<Cipher>::createKey() {
return _baseBlockStore->createKey();
}
template<class Cipher>
std::unique_ptr<Block> EncryptedBlockStore<Cipher>::tryCreate(const Key &key, cpputils::Data data) {
//TODO Test that this returns nullptr when base blockstore returns nullptr (for all pass-through-blockstores)
return EncryptedBlock<Cipher>::TryCreateNew(_baseBlockStore.get(), key, std::move(data), _encKey);
}
template<class Cipher>
std::unique_ptr<Block> EncryptedBlockStore<Cipher>::load(const Key &key) {
auto block = _baseBlockStore->load(key);
if (block.get() == nullptr) {
//TODO Test this path (for all pass-through-blockstores)
return nullptr;
}
return EncryptedBlock<Cipher>::TryDecrypt(std::move(block), _encKey);
}
template<class Cipher>
void EncryptedBlockStore<Cipher>::remove(std::unique_ptr<Block> block) {
auto baseBlock = cpputils::dynamic_pointer_move<EncryptedBlock<Cipher>>(block)->releaseBlock();
return _baseBlockStore->remove(std::move(baseBlock));
}
template<class Cipher>
uint64_t EncryptedBlockStore<Cipher>::numBlocks() const {
return _baseBlockStore->numBlocks();
}
template<class Cipher>
void EncryptedBlockStore<Cipher>::__setKey(const typename Cipher::EncryptionKey &encKey) {
_encKey = encKey;
}
}
}

View File

@ -1 +0,0 @@
#include "EncryptionKey.h"

View File

@ -1,16 +0,0 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_ENCRYPTIONKEY_H_
#define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_ENCRYPTIONKEY_H_
#include "../../utils/FixedSizeData.h"
namespace blockstore {
namespace encrypted {
//TODO Does EncryptionKey::GenerateRandom() use a PseudoRandomGenerator? Would be better to use real randomness.
using EncryptionKey = FixedSizeData<CryptoPP::AES::MAX_KEYLENGTH>;
}
}
#endif

View File

@ -0,0 +1,37 @@
#include <cryptopp/cryptopp/modes.h>
#include "AES256_CFB.h"
using CryptoPP::CFB_Mode;
using CryptoPP::AES;
using cpputils::Data;
using cpputils::FixedSizeData;
namespace blockstore {
namespace encrypted {
constexpr unsigned int AES256_CFB::IV_SIZE;
Data AES256_CFB::encrypt(const byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) {
FixedSizeData<IV_SIZE> iv = FixedSizeData<IV_SIZE>::CreateRandom();
auto encryption = CFB_Mode<AES>::Encryption(encKey.data(), encKey.BINARY_LENGTH, iv.data());
Data ciphertext(ciphertextSize(plaintextSize));
std::memcpy(ciphertext.data(), iv.data(), IV_SIZE);
encryption.ProcessData((byte*)ciphertext.data() + IV_SIZE, plaintext, plaintextSize);
return ciphertext;
}
boost::optional<Data> AES256_CFB::decrypt(const byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) {
if (ciphertextSize < IV_SIZE) {
return boost::none;
}
const byte *ciphertextIV = ciphertext;
const byte *ciphertextData = ciphertext + IV_SIZE;
auto decryption = CFB_Mode<AES>::Decryption((byte*)encKey.data(), encKey.BINARY_LENGTH, ciphertextIV);
Data plaintext(plaintextSize(ciphertextSize));
decryption.ProcessData((byte*)plaintext.data(), ciphertextData, plaintext.size());
return std::move(plaintext);
}
}
}

View File

@ -0,0 +1,39 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_CIPHERS_AES256_CFB_H_
#define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_CIPHERS_AES256_CFB_H_
#include <messmer/cpp-utils/data/FixedSizeData.h>
#include <messmer/cpp-utils/data/Data.h>
#include <cryptopp/cryptopp/aes.h>
#include <boost/optional.hpp>
#include "Cipher.h"
namespace blockstore {
namespace encrypted {
class AES256_CFB {
public:
BOOST_CONCEPT_ASSERT((CipherConcept<AES256_CFB>));
using EncryptionKey = cpputils::FixedSizeData<32>;
static_assert(32 == CryptoPP::AES::MAX_KEYLENGTH, "If AES offered larger keys, we should offer a variant with it");
static constexpr unsigned int ciphertextSize(unsigned int plaintextBlockSize) {
return plaintextBlockSize + IV_SIZE;
}
static constexpr unsigned int plaintextSize(unsigned int ciphertextBlockSize) {
return ciphertextBlockSize - IV_SIZE;
}
static cpputils::Data encrypt(const byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey);
static boost::optional<cpputils::Data> decrypt(const byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey);
private:
static constexpr unsigned int IV_SIZE = CryptoPP::AES::BLOCKSIZE;
};
}
}
#endif

View File

@ -0,0 +1,61 @@
#include <cryptopp/cryptopp/gcm.h>
#include "AES256_GCM.h"
using CryptoPP::GCM;
using CryptoPP::AES;
using CryptoPP::AuthenticatedEncryptionFilter;
using CryptoPP::AuthenticatedDecryptionFilter;
using CryptoPP::ArraySource;
using CryptoPP::ArraySink;
using CryptoPP::GCM_64K_Tables;
using CryptoPP::HashVerificationFilter;
using cpputils::Data;
using cpputils::FixedSizeData;
namespace blockstore {
namespace encrypted {
constexpr unsigned int AES256_GCM::IV_SIZE;
Data AES256_GCM::encrypt(const byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) {
FixedSizeData<IV_SIZE> iv = FixedSizeData<IV_SIZE>::CreateRandom();
GCM<AES, GCM_64K_Tables>::Encryption encryption;
encryption.SetKeyWithIV(encKey.data(), encKey.BINARY_LENGTH, iv.data(), IV_SIZE);
Data ciphertext(ciphertextSize(plaintextSize));
std::memcpy(ciphertext.data(), iv.data(), IV_SIZE);
ArraySource(plaintext, plaintextSize, true,
new AuthenticatedEncryptionFilter(encryption,
new ArraySink((byte*)ciphertext.data() + IV_SIZE, ciphertext.size() - IV_SIZE),
false, TAG_SIZE
)
);
return ciphertext;
}
boost::optional<Data> AES256_GCM::decrypt(const byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) {
if (ciphertextSize < IV_SIZE + TAG_SIZE) {
return boost::none;
}
const byte *ciphertextIV = ciphertext;
const byte *ciphertextData = ciphertext + IV_SIZE;
GCM<AES, GCM_64K_Tables>::Decryption decryption;
decryption.SetKeyWithIV((byte*)encKey.data(), encKey.BINARY_LENGTH, ciphertextIV, IV_SIZE);
Data plaintext(plaintextSize(ciphertextSize));
try {
ArraySource((byte*)ciphertextData, ciphertextSize - IV_SIZE, true,
new AuthenticatedDecryptionFilter(decryption,
new ArraySink((byte*)plaintext.data(), plaintext.size()),
AuthenticatedDecryptionFilter::DEFAULT_FLAGS, TAG_SIZE
)
);
return std::move(plaintext);
} catch (const HashVerificationFilter::HashVerificationFailed &e) {
return boost::none;
}
}
}
}

View File

@ -0,0 +1,39 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_CIPHERS_AES256_GCM_H_
#define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_CIPHERS_AES256_GCM_H_
#include <messmer/cpp-utils/data/FixedSizeData.h>
#include <messmer/cpp-utils/data/Data.h>
#include <cryptopp/cryptopp/aes.h>
#include "Cipher.h"
namespace blockstore {
namespace encrypted {
class AES256_GCM {
public:
BOOST_CONCEPT_ASSERT((CipherConcept<AES256_GCM>));
using EncryptionKey = cpputils::FixedSizeData<32>;
static_assert(32 == CryptoPP::AES::MAX_KEYLENGTH, "If AES offered larger keys, we should offer a variant with it");
static constexpr unsigned int ciphertextSize(unsigned int plaintextBlockSize) {
return plaintextBlockSize + IV_SIZE + TAG_SIZE;
}
static constexpr unsigned int plaintextSize(unsigned int ciphertextBlockSize) {
return ciphertextBlockSize - IV_SIZE - TAG_SIZE;
}
static cpputils::Data encrypt(const byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey);
static boost::optional<cpputils::Data> decrypt(const byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey);
private:
static constexpr unsigned int IV_SIZE = CryptoPP::AES::BLOCKSIZE;
static constexpr unsigned int TAG_SIZE = 16;
};
}
}
#endif

View File

@ -0,0 +1,33 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_CIPHERS_CIPHER_H_
#define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_CIPHERS_CIPHER_H_
#include <boost/concept_check.hpp>
#include <cstdint>
#include <messmer/cpp-utils/data/Data.h>
namespace blockstore {
namespace encrypted {
template<class X>
struct CipherConcept {
public:
BOOST_CONCEPT_USAGE(CipherConcept) {
same_type(UINT32_C(0), X::ciphertextSize(UINT32_C(5)));
same_type(UINT32_C(0), X::plaintextSize(UINT32_C(5)));
typename X::EncryptionKey key = X::EncryptionKey::CreateRandom();
same_type(cpputils::Data(0), X::encrypt((uint8_t*)nullptr, UINT32_C(0), key));
same_type(boost::optional<cpputils::Data>(cpputils::Data(0)), X::decrypt((uint8_t*)nullptr, UINT32_C(0), key));
}
private:
// Type deduction will fail unless the arguments have the same type.
template <typename T> void same_type(T const&, T const&);
};
}
}
#endif

View File

@ -9,6 +9,7 @@ using std::ostream;
using std::ifstream;
using std::ofstream;
using std::ios;
using cpputils::Data;
namespace blockstore {
namespace inmemory {

View File

@ -3,7 +3,7 @@
#define BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCK_H_
#include "../../interface/Block.h"
#include "../../utils/Data.h"
#include <messmer/cpp-utils/data/Data.h>
namespace blockstore {
namespace inmemory {
@ -11,7 +11,7 @@ class InMemoryBlockStore;
class InMemoryBlock: public Block {
public:
InMemoryBlock(const Key &key, Data size);
InMemoryBlock(const Key &key, cpputils::Data size);
InMemoryBlock(const InMemoryBlock &rhs);
virtual ~InMemoryBlock();
@ -23,7 +23,7 @@ public:
size_t size() const override;
private:
std::shared_ptr<Data> _data;
std::shared_ptr<cpputils::Data> _data;
};
}

View File

@ -9,6 +9,7 @@ using std::mutex;
using std::lock_guard;
using std::piecewise_construct;
using std::make_tuple;
using cpputils::Data;
namespace blockstore {
namespace inmemory {

View File

@ -16,7 +16,7 @@ class InMemoryBlockStore: public BlockStoreWithRandomKeys {
public:
InMemoryBlockStore();
std::unique_ptr<Block> tryCreate(const Key &key, Data data) override;
std::unique_ptr<Block> tryCreate(const Key &key, cpputils::Data data) override;
std::unique_ptr<Block> load(const Key &key) override;
void remove(std::unique_ptr<Block> block) override;
uint64_t numBlocks() const override;

View File

@ -1,19 +0,0 @@
#include "FileAlreadyExistsException.h"
namespace bf = boost::filesystem;
using std::runtime_error;
using std::string;
namespace blockstore {
namespace ondisk {
FileAlreadyExistsException::FileAlreadyExistsException(const bf::path &filepath)
: runtime_error(string("The file ")+filepath.c_str()+" already exists") {
}
FileAlreadyExistsException::~FileAlreadyExistsException() {
}
}
}

View File

@ -1,21 +0,0 @@
#pragma once
#ifndef BLOCKSTORE_IMPLEMENTATIONS_ONDISK_FILEALREADYEXISTSEXCEPTION_H_
#define BLOCKSTORE_IMPLEMENTATIONS_ONDISK_FILEALREADYEXISTSEXCEPTION_H_
#include <boost/filesystem/path.hpp>
#include <stdexcept>
namespace blockstore {
namespace ondisk {
class FileAlreadyExistsException: public std::runtime_error {
public:
FileAlreadyExistsException(const boost::filesystem::path &filepath);
virtual ~FileAlreadyExistsException();
};
}
}
#endif

View File

@ -1,7 +1,6 @@
#include <cstring>
#include <fstream>
#include <boost/filesystem.hpp>
#include "FileAlreadyExistsException.h"
#include "OnDiskBlock.h"
#include "OnDiskBlockStore.h"
#include "../../utils/FileDoesntExistException.h"
@ -13,6 +12,7 @@ using std::ostream;
using std::ifstream;
using std::ofstream;
using std::ios;
using cpputils::Data;
namespace bf = boost::filesystem;
@ -47,11 +47,15 @@ unique_ptr<OnDiskBlock> OnDiskBlock::LoadFromDisk(const bf::path &rootdir, const
//If it isn't a file, Data::LoadFromFile() would usually also crash. We still need this extra check
//upfront, because Data::LoadFromFile() doesn't crash if we give it the path of a directory
//instead the path of a file.
//TODO Data::LoadFromFile now returns boost::optional. Do we then still need this?
if(!bf::is_regular_file(filepath)) {
return nullptr;
}
Data data = Data::LoadFromFile(filepath);
return unique_ptr<OnDiskBlock>(new OnDiskBlock(key, filepath, std::move(data)));
boost::optional<Data> data = Data::LoadFromFile(filepath);
if (!data) {
return nullptr;
}
return unique_ptr<OnDiskBlock>(new OnDiskBlock(key, filepath, std::move(*data)));
} catch (const FileDoesntExistException &e) {
return nullptr;
}

View File

@ -4,7 +4,7 @@
#include <boost/filesystem/path.hpp>
#include "../../interface/Block.h"
#include "../../utils/Data.h"
#include <messmer/cpp-utils/data/Data.h>
#include <iostream>
#include "messmer/cpp-utils/macros.h"
@ -18,7 +18,7 @@ public:
virtual ~OnDiskBlock();
static std::unique_ptr<OnDiskBlock> LoadFromDisk(const boost::filesystem::path &rootdir, const Key &key);
static std::unique_ptr<OnDiskBlock> CreateOnDisk(const boost::filesystem::path &rootdir, const Key &key, Data data);
static std::unique_ptr<OnDiskBlock> CreateOnDisk(const boost::filesystem::path &rootdir, const Key &key, cpputils::Data data);
static void RemoveFromDisk(const boost::filesystem::path &rootdir, const Key &key);
const void *data() const override;
@ -30,10 +30,10 @@ public:
private:
const boost::filesystem::path _filepath;
Data _data;
cpputils::Data _data;
bool _dataChanged;
OnDiskBlock(const Key &key, const boost::filesystem::path &filepath, Data data);
OnDiskBlock(const Key &key, const boost::filesystem::path &filepath, cpputils::Data data);
void _fillDataWithZeroes();
void _storeToDisk() const;

View File

@ -4,6 +4,7 @@
using std::unique_ptr;
using std::make_unique;
using std::string;
using cpputils::Data;
namespace bf = boost::filesystem;

View File

@ -12,9 +12,9 @@ namespace ondisk {
class OnDiskBlockStore: public BlockStoreWithRandomKeys {
public:
OnDiskBlockStore(const boost::filesystem::path &rootdir);
explicit OnDiskBlockStore(const boost::filesystem::path &rootdir);
std::unique_ptr<Block> tryCreate(const Key &key, Data data) override;
std::unique_ptr<Block> tryCreate(const Key &key, cpputils::Data data) override;
std::unique_ptr<Block> load(const Key &key) override;
void remove(std::unique_ptr<Block> block) override;
uint64_t numBlocks() const override;

View File

@ -14,7 +14,7 @@ class ParallelAccessBlockStore;
class BlockRef: public Block, public parallelaccessstore::ParallelAccessStore<Block, BlockRef, Key>::ResourceRefBase {
public:
//TODO Unneccessarily storing Key twice here (in parent class and in _baseBlock).
BlockRef(Block *baseBlock): Block(baseBlock->key()), _baseBlock(baseBlock) {}
explicit BlockRef(Block *baseBlock): Block(baseBlock->key()), _baseBlock(baseBlock) {}
const void *data() const override {
return _baseBlock->data();

View File

@ -24,7 +24,7 @@ Key ParallelAccessBlockStore::createKey() {
return _baseBlockStore->createKey();
}
unique_ptr<Block> ParallelAccessBlockStore::tryCreate(const Key &key, Data data) {
unique_ptr<Block> ParallelAccessBlockStore::tryCreate(const Key &key, cpputils::Data data) {
auto block = _baseBlockStore->tryCreate(key, std::move(data));
if (block.get() == nullptr) {
//TODO Test this code branch

View File

@ -12,10 +12,10 @@ namespace parallelaccess {
//TODO Check that this blockstore allows parallel destructing of blocks (otherwise we won't encrypt blocks in parallel)
class ParallelAccessBlockStore: public BlockStore {
public:
ParallelAccessBlockStore(std::unique_ptr<BlockStore> baseBlockStore);
explicit ParallelAccessBlockStore(std::unique_ptr<BlockStore> baseBlockStore);
Key createKey() override;
std::unique_ptr<Block> tryCreate(const Key &key, Data data) override;
std::unique_ptr<Block> tryCreate(const Key &key, cpputils::Data data) override;
std::unique_ptr<Block> load(const Key &key) override;
void remove(std::unique_ptr<Block> block) override;
uint64_t numBlocks() const override;

View File

@ -10,7 +10,7 @@ namespace parallelaccess {
class ParallelAccessBlockStoreAdapter: public parallelaccessstore::ParallelAccessBaseStore<Block, Key> {
public:
ParallelAccessBlockStoreAdapter(BlockStore *baseBlockStore)
explicit ParallelAccessBlockStoreAdapter(BlockStore *baseBlockStore)
:_baseBlockStore(std::move(baseBlockStore)) {
}

View File

@ -10,6 +10,7 @@ using std::ifstream;
using std::ofstream;
using std::ios;
using std::string;
using cpputils::Data;
namespace blockstore {
namespace testfake {

View File

@ -3,7 +3,7 @@
#define BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCK_H_
#include "../../interface/Block.h"
#include "../../utils/Data.h"
#include <messmer/cpp-utils/data/Data.h>
#include "messmer/cpp-utils/macros.h"
@ -13,7 +13,7 @@ class FakeBlockStore;
class FakeBlock: public Block {
public:
FakeBlock(FakeBlockStore *store, const Key &key, std::shared_ptr<Data> data, bool dirty);
FakeBlock(FakeBlockStore *store, const Key &key, std::shared_ptr<cpputils::Data> data, bool dirty);
virtual ~FakeBlock();
const void *data() const override;
@ -25,7 +25,7 @@ public:
private:
FakeBlockStore *_store;
std::shared_ptr<Data> _data;
std::shared_ptr<cpputils::Data> _data;
bool _dataChanged;
DISALLOW_COPY_AND_ASSIGN(FakeBlock);

View File

@ -7,6 +7,7 @@ using std::make_shared;
using std::string;
using std::mutex;
using std::lock_guard;
using cpputils::Data;
namespace blockstore {
namespace testfake {

View File

@ -3,7 +3,7 @@
#define BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCKSTORE_H_
#include "../../interface/helpers/BlockStoreWithRandomKeys.h"
#include "../../utils/Data.h"
#include <messmer/cpp-utils/data/Data.h>
#include "messmer/cpp-utils/macros.h"
#include <mutex>
@ -31,24 +31,24 @@ class FakeBlockStore: public BlockStoreWithRandomKeys {
public:
FakeBlockStore();
std::unique_ptr<Block> tryCreate(const Key &key, Data data) override;
std::unique_ptr<Block> tryCreate(const Key &key, cpputils::Data data) override;
std::unique_ptr<Block> load(const Key &key) override;
void remove(std::unique_ptr<Block> block) override;
uint64_t numBlocks() const override;
void updateData(const Key &key, const Data &data);
void updateData(const Key &key, const cpputils::Data &data);
private:
std::map<std::string, Data> _blocks;
std::map<std::string, cpputils::Data> _blocks;
//This vector keeps a handle of the data regions for all created FakeBlock objects.
//This way, it is ensured that no two created FakeBlock objects will work on the
//same data region. Without this, it could happen that a test case creates a FakeBlock,
//destructs it, creates another one, and the new one gets the same memory region.
//We want to avoid this for the reasons mentioned above (overflow data).
std::vector<std::shared_ptr<Data>> _used_dataregions_for_blocks;
std::vector<std::shared_ptr<cpputils::Data>> _used_dataregions_for_blocks;
std::unique_ptr<Block> makeFakeBlockFromData(const Key &key, const Data &data, bool dirty);
std::unique_ptr<Block> makeFakeBlockFromData(const Key &key, const cpputils::Data &data, bool dirty);
DISALLOW_COPY_AND_ASSIGN(FakeBlockStore);
};

View File

@ -5,7 +5,7 @@
#include "Block.h"
#include <string>
#include <memory>
#include "../utils/Data.h"
#include <messmer/cpp-utils/data/Data.h>
namespace blockstore {
@ -15,20 +15,20 @@ public:
virtual Key createKey() = 0;
//Returns nullptr if key already exists
virtual std::unique_ptr<Block> tryCreate(const Key &key, Data data) = 0;
virtual std::unique_ptr<Block> tryCreate(const Key &key, cpputils::Data data) = 0;
//TODO Use boost::optional (if key doesn't exist)
// Return nullptr if block with this key doesn't exists
virtual std::unique_ptr<Block> load(const Key &key) = 0;
virtual void remove(std::unique_ptr<Block> block) = 0;
virtual uint64_t numBlocks() const = 0;
std::unique_ptr<Block> create(const Data &data) {
std::unique_ptr<Block> block(nullptr);
while(block.get() == nullptr) {
//TODO Copy necessary?
block = tryCreate(createKey(), data.copy());
}
return block;
std::unique_ptr<Block> create(const cpputils::Data &data) {
std::unique_ptr<Block> block(nullptr);
while(block.get() == nullptr) {
//TODO Copy necessary?
block = tryCreate(createKey(), data.copy());
}
return block;
}
};

View File

@ -0,0 +1,36 @@
#include <google/gtest/gtest.h>
#include <memory>
#include "../../../../implementations/caching/cache/Cache.h"
#include "testutils/MinimalKeyType.h"
#include "testutils/CopyableMovableValueType.h"
using namespace blockstore::caching;
using ::testing::Test;
using std::unique_ptr;
using std::make_unique;
//Test that Cache uses a move constructor for Value if possible
class CacheTest_MoveConstructor: public Test {
public:
CacheTest_MoveConstructor() {
CopyableMovableValueType::numCopyConstructorCalled = 0;
cache = make_unique<Cache<MinimalKeyType, CopyableMovableValueType>>();
}
unique_ptr<Cache<MinimalKeyType, CopyableMovableValueType>> cache;
};
TEST_F(CacheTest_MoveConstructor, MoveIntoCache) {
cache->push(MinimalKeyType::create(0), CopyableMovableValueType(2));
CopyableMovableValueType val = cache->pop(MinimalKeyType::create(0)).value();
val.value(); //Access it to avoid the compiler optimizing the assignment away
EXPECT_EQ(0, CopyableMovableValueType::numCopyConstructorCalled);
}
TEST_F(CacheTest_MoveConstructor, CopyIntoCache) {
CopyableMovableValueType value(2);
cache->push(MinimalKeyType::create(0), value);
CopyableMovableValueType val = cache->pop(MinimalKeyType::create(0)).value();
val.value(); //Access it to avoid the compiler optimizing the assignment away
EXPECT_EQ(1, CopyableMovableValueType::numCopyConstructorCalled);
}

View File

@ -0,0 +1,133 @@
#include "testutils/CacheTest.h"
#include "../../../../implementations/caching/cache/Cache.h"
#include "testutils/MinimalKeyType.h"
#include "testutils/MinimalValueType.h"
using ::testing::Test;
using namespace blockstore::caching;
class CacheTest_PushAndPop: public CacheTest {};
TEST_F(CacheTest_PushAndPop, PopNonExistingEntry_EmptyCache) {
EXPECT_EQ(boost::none, pop(10));
}
TEST_F(CacheTest_PushAndPop, PopNonExistingEntry_NonEmptyCache) {
push(9, 10);
EXPECT_EQ(boost::none, pop(10));
}
TEST_F(CacheTest_PushAndPop, PopNonExistingEntry_FullCache) {
//Add a lot of even numbered keys
for (unsigned int i = 0; i < Cache::MAX_ENTRIES; ++i) {
push(2*i, 2*i);
}
//Request an odd numbered key
EXPECT_EQ(boost::none, pop(9));
}
TEST_F(CacheTest_PushAndPop, OneEntry) {
push(10, 20);
EXPECT_EQ(20, pop(10).value());
}
TEST_F(CacheTest_PushAndPop, MultipleEntries) {
push(10, 20);
push(20, 30);
push(30, 40);
EXPECT_EQ(30, pop(20).value());
EXPECT_EQ(20, pop(10).value());
EXPECT_EQ(40, pop(30).value());
}
TEST_F(CacheTest_PushAndPop, FullCache) {
for(unsigned int i = 0; i < Cache::MAX_ENTRIES; ++i) {
push(i, 2*i);
}
for(unsigned int i = 0; i < Cache::MAX_ENTRIES; ++i) {
EXPECT_EQ(2*i, pop(i).value());
}
}
TEST_F(CacheTest_PushAndPop, FullCache_PushNonOrdered_PopOrdered) {
for(unsigned int i = 1; i < Cache::MAX_ENTRIES; i += 2) {
push(i, 2*i);
}
for(unsigned int i = 0; i < Cache::MAX_ENTRIES; i += 2) {
push(i, 2*i);
}
for(unsigned int i = 0; i < Cache::MAX_ENTRIES; ++i) {
EXPECT_EQ(2*i, pop(i).value());
}
}
TEST_F(CacheTest_PushAndPop, FullCache_PushOrdered_PopNonOrdered) {
for(unsigned int i = 0; i < Cache::MAX_ENTRIES; ++i) {
push(i, 2*i);
}
for(unsigned int i = 1; i < Cache::MAX_ENTRIES; i += 2) {
EXPECT_EQ(2*i, pop(i).value());
}
for(unsigned int i = 0; i < Cache::MAX_ENTRIES; i += 2) {
EXPECT_EQ(2*i, pop(i).value());
}
}
int roundDownToEven(int number) {
if (number % 2 == 0) {
return number;
} else {
return number - 1;
}
}
int roundDownToOdd(int number) {
if (number % 2 != 0) {
return number;
} else {
return number - 1;
}
}
TEST_F(CacheTest_PushAndPop, FullCache_PushNonOrdered_PopNonOrdered) {
for(int i = roundDownToEven(Cache::MAX_ENTRIES - 1); i >= 0; i -= 2) {
push(i, 2*i);
}
for(unsigned int i = 1; i < Cache::MAX_ENTRIES; i += 2) {
push(i, 2*i);
}
for(int i = roundDownToOdd(Cache::MAX_ENTRIES-1); i >= 0; i -= 2) {
EXPECT_EQ(2*i, pop(i).value());
}
for(unsigned int i = 0; i < Cache::MAX_ENTRIES; i += 2) {
EXPECT_EQ(2*i, pop(i).value());
}
}
TEST_F(CacheTest_PushAndPop, MoreThanFullCache) {
for(unsigned int i = 0; i < Cache::MAX_ENTRIES + 2; ++i) {
push(i, 2*i);
}
//Check that the oldest two elements got deleted automatically
EXPECT_EQ(boost::none, pop(0));
EXPECT_EQ(boost::none, pop(1));
//Check the other elements are still there
for(unsigned int i = 2; i < Cache::MAX_ENTRIES + 2; ++i) {
EXPECT_EQ(2*i, pop(i).value());
}
}
TEST_F(CacheTest_PushAndPop, AfterTimeout) {
constexpr double TIMEOUT1_SEC = Cache::MAX_LIFETIME_SEC * 3/4;
constexpr double TIMEOUT2_SEC = Cache::PURGE_LIFETIME_SEC * 3/4;
static_assert(TIMEOUT1_SEC + TIMEOUT2_SEC > Cache::MAX_LIFETIME_SEC, "Ensure that our chosen timeouts push the first entry out of the cache");
push(10, 20);
boost::this_thread::sleep_for(boost::chrono::milliseconds(static_cast<int>(1000 * TIMEOUT1_SEC)));
push(20, 30);
boost::this_thread::sleep_for(boost::chrono::milliseconds(static_cast<int>(1000 * TIMEOUT2_SEC)));
EXPECT_EQ(boost::none, pop(10));
EXPECT_EQ(30, pop(20).value());
}

View File

@ -0,0 +1,63 @@
#include <google/gtest/gtest.h>
#include "../../../../implementations/caching/cache/PeriodicTask.h"
#include <mutex>
#include <condition_variable>
#include <atomic>
using ::testing::Test;
using std::mutex;
using std::unique_lock;
using std::condition_variable;
using namespace blockstore::caching;
class AtomicCounter {
public:
AtomicCounter(int count): _mutex(), _cv(), _counter(count) {}
void decrease() {
unique_lock<mutex> lock(_mutex);
--_counter;
_cv.notify_all();
}
void waitForZero() {
unique_lock<mutex> lock(_mutex);
_cv.wait(lock, [this] () {return _counter <= 0;});
}
private:
mutex _mutex;
condition_variable _cv;
int _counter;
};
class PeriodicTaskTest: public Test {
};
TEST_F(PeriodicTaskTest, DoesntDeadlockInDestructorWhenDestructedImmediately) {
PeriodicTask task([](){}, 1);
}
TEST_F(PeriodicTaskTest, CallsCallbackAtLeast10Times) {
AtomicCounter counter(10);
PeriodicTask task([&counter](){
counter.decrease();
}, 0.001);
counter.waitForZero();
}
TEST_F(PeriodicTaskTest, DoesntCallCallbackAfterDestruction) {
std::atomic<int> callCount(0);
{
PeriodicTask task([&callCount](){
callCount += 1;
}, 0.001);
}
int callCountDirectlyAfterDestruction = callCount;
boost::this_thread::sleep_for(boost::chrono::seconds(1));
EXPECT_EQ(callCountDirectlyAfterDestruction, callCount);
}

View File

@ -0,0 +1,87 @@
#include "testutils/QueueMapTest.h"
// Tests that QueueMap calls destructors correctly.
// This is needed, because QueueMap does its own memory management.
class QueueMapTest_MemoryLeak: public QueueMapTest {
public:
void EXPECT_NUM_INSTANCES(int num) {
EXPECT_EQ(num, MinimalKeyType::instances);
EXPECT_EQ(num, MinimalValueType::instances);
}
};
TEST_F(QueueMapTest_MemoryLeak, Empty) {
EXPECT_NUM_INSTANCES(0);
}
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOne) {
push(2, 3);
EXPECT_NUM_INSTANCES(1);
}
TEST_F(QueueMapTest_MemoryLeak, AfterPushingTwo) {
push(2, 3);
push(3, 4);
EXPECT_NUM_INSTANCES(2);
}
TEST_F(QueueMapTest_MemoryLeak, AfterPushingTwoAndPoppingOldest) {
push(2, 3);
push(3, 4);
pop();
EXPECT_NUM_INSTANCES(1);
}
TEST_F(QueueMapTest_MemoryLeak, AfterPushingTwoAndPoppingFirst) {
push(2, 3);
push(3, 4);
pop(2);
EXPECT_NUM_INSTANCES(1);
}
TEST_F(QueueMapTest_MemoryLeak, AfterPushingTwoAndPoppingLast) {
push(2, 3);
push(3, 4);
pop(3);
EXPECT_NUM_INSTANCES(1);
}
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOne) {
push(2, 3);
pop();
EXPECT_NUM_INSTANCES(0);
}
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePerKey) {
push(2, 3);
pop(2);
EXPECT_NUM_INSTANCES(0);
}
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePushingOne) {
push(2, 3);
pop();
push(3, 4);
EXPECT_NUM_INSTANCES(1);
}
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePerKeyPushingOne) {
push(2, 3);
pop(2);
push(3, 4);
EXPECT_NUM_INSTANCES(1);
}
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePushingSame) {
push(2, 3);
pop();
push(2, 3);
EXPECT_NUM_INSTANCES(1);
}
TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePerKeyPushingSame) {
push(2, 3);
pop(2);
push(2, 3);
EXPECT_NUM_INSTANCES(1);
}

View File

@ -0,0 +1,51 @@
#include <google/gtest/gtest.h>
#include <memory>
#include "../../../../implementations/caching/cache/QueueMap.h"
#include "testutils/MinimalKeyType.h"
#include "testutils/CopyableMovableValueType.h"
using namespace blockstore::caching;
using ::testing::Test;
using std::unique_ptr;
using std::make_unique;
//Test that QueueMap uses a move constructor for Value if possible
class QueueMapTest_MoveConstructor: public Test {
public:
QueueMapTest_MoveConstructor() {
CopyableMovableValueType::numCopyConstructorCalled = 0;
map = make_unique<QueueMap<MinimalKeyType, CopyableMovableValueType>>();
}
unique_ptr<QueueMap<MinimalKeyType, CopyableMovableValueType>> map;
};
TEST_F(QueueMapTest_MoveConstructor, PushingAndPopping_MoveIntoMap) {
map->push(MinimalKeyType::create(0), CopyableMovableValueType(2));
CopyableMovableValueType val = map->pop().value();
val.value(); //Access it to avoid the compiler optimizing the assignment away
EXPECT_EQ(0, CopyableMovableValueType::numCopyConstructorCalled);
}
TEST_F(QueueMapTest_MoveConstructor, PushingAndPoppingPerKey_MoveIntoMap) {
map->push(MinimalKeyType::create(0), CopyableMovableValueType(2));
CopyableMovableValueType val = map->pop(MinimalKeyType::create(0)).value();
val.value(); //Access it to avoid the compiler optimizing the assignment away
EXPECT_EQ(0, CopyableMovableValueType::numCopyConstructorCalled);
}
TEST_F(QueueMapTest_MoveConstructor, PushingAndPopping_CopyIntoMap) {
CopyableMovableValueType value(2);
map->push(MinimalKeyType::create(0), value);
CopyableMovableValueType val = map->pop().value();
val.value(); //Access it to avoid the compiler optimizing the assignment away
EXPECT_EQ(1, CopyableMovableValueType::numCopyConstructorCalled);
}
TEST_F(QueueMapTest_MoveConstructor, PushingAndPoppingPerKey_CopyIntoMap) {
CopyableMovableValueType value(2);
map->push(MinimalKeyType::create(0), value);
CopyableMovableValueType val = map->pop(MinimalKeyType::create(0)).value();
val.value(); //Access it to avoid the compiler optimizing the assignment away
EXPECT_EQ(1, CopyableMovableValueType::numCopyConstructorCalled);
}

View File

@ -0,0 +1,34 @@
#include "testutils/QueueMapTest.h"
#include <boost/optional/optional_io.hpp>
class QueueMapPeekTest: public QueueMapTest {};
TEST_F(QueueMapPeekTest, PoppingFromEmpty) {
EXPECT_EQ(boost::none, peek());
}
TEST_F(QueueMapPeekTest, PushingOne) {
push(3, 2);
EXPECT_EQ(2, peek().value());
}
TEST_F(QueueMapPeekTest, PushingTwo) {
push(2, 3);
push(3, 4);
EXPECT_EQ(3, peek().value());
EXPECT_EQ(3, peek().value());
EXPECT_EQ(3, pop().value());
EXPECT_EQ(4, peek().value());
EXPECT_EQ(4, peek().value());
EXPECT_EQ(4, pop().value());
EXPECT_EQ(boost::none, peek());
EXPECT_EQ(boost::none, pop());
}
TEST_F(QueueMapPeekTest, AfterPushingTwoAndPoppingFirst) {
push(2, 3);
push(3, 4);
pop(2);
EXPECT_EQ(boost::none, pop(2));
EXPECT_EQ(4, peek().value());
}

View File

@ -0,0 +1,79 @@
#include "testutils/QueueMapTest.h"
class QueueMapTest_Size: public QueueMapTest {};
TEST_F(QueueMapTest_Size, Empty) {
EXPECT_EQ(0, size());
}
TEST_F(QueueMapTest_Size, AfterPushingOne) {
push(2, 3);
EXPECT_EQ(1, size());
}
TEST_F(QueueMapTest_Size, AfterPushingTwo) {
push(2, 3);
push(3, 4);
EXPECT_EQ(2, size());
}
TEST_F(QueueMapTest_Size, AfterPushingTwoAndPoppingOldest) {
push(2, 3);
push(3, 4);
pop();
EXPECT_EQ(1, size());
}
TEST_F(QueueMapTest_Size, AfterPushingTwoAndPoppingFirst) {
push(2, 3);
push(3, 4);
pop(2);
EXPECT_EQ(1, size());
}
TEST_F(QueueMapTest_Size, AfterPushingTwoAndPoppingLast) {
push(2, 3);
push(3, 4);
pop(3);
EXPECT_EQ(1, size());
}
TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOne) {
push(2, 3);
pop();
EXPECT_EQ(0, size());
}
TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePerKey) {
push(2, 3);
pop(2);
EXPECT_EQ(0, size());
}
TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePushingOne) {
push(2, 3);
pop();
push(3, 4);
EXPECT_EQ(1, size());
}
TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePerKeyPushingOne) {
push(2, 3);
pop(2);
push(3, 4);
EXPECT_EQ(1, size());
}
TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePushingSame) {
push(2, 3);
pop();
push(2, 3);
EXPECT_EQ(1, size());
}
TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePerKeyPushingSame) {
push(2, 3);
pop(2);
push(2, 3);
EXPECT_EQ(1, size());
}

View File

@ -0,0 +1,150 @@
#include "testutils/QueueMapTest.h"
class QueueMapTest_Values: public QueueMapTest {};
TEST_F(QueueMapTest_Values, PoppingFromEmpty) {
EXPECT_EQ(boost::none, pop());
}
TEST_F(QueueMapTest_Values, PoppingFromEmptyPerKey) {
EXPECT_EQ(boost::none, pop(2));
}
TEST_F(QueueMapTest_Values, PoppingNonexistingPerKey) {
push(3, 2);
EXPECT_EQ(boost::none, pop(2));
}
TEST_F(QueueMapTest_Values, PushingOne) {
push(3, 2);
EXPECT_EQ(2, pop(3).value());
EXPECT_EQ(boost::none, pop());
}
TEST_F(QueueMapTest_Values, PushingTwo) {
push(2, 3);
push(3, 4);
EXPECT_EQ(3, pop().value());
EXPECT_EQ(4, pop().value());
EXPECT_EQ(boost::none, pop());
}
TEST_F(QueueMapTest_Values, AfterPushingTwoAndPoppingFirst) {
push(2, 3);
push(3, 4);
pop(2);
EXPECT_EQ(boost::none, pop(2));
EXPECT_EQ(4, pop(3).value());
EXPECT_EQ(boost::none, pop());
}
TEST_F(QueueMapTest_Values, AfterPushingTwoAndPoppingLast) {
push(2, 3);
push(3, 4);
pop(3);
EXPECT_EQ(boost::none, pop(3));
EXPECT_EQ(3, pop(2).value());
EXPECT_EQ(boost::none, pop());
}
TEST_F(QueueMapTest_Values, AfterPushingOnePoppingOne) {
push(2, 3);
pop();
EXPECT_EQ(boost::none, pop());
EXPECT_EQ(boost::none, pop(2));
}
TEST_F(QueueMapTest_Values, AfterPushingOnePoppingOnePerKey) {
push(2, 3);
pop(2);
EXPECT_EQ(boost::none, pop());
EXPECT_EQ(boost::none, pop(2));
}
TEST_F(QueueMapTest_Values, AfterPushingOnePoppingOnePushingOne) {
push(2, 3);
pop();
push(3, 4);
EXPECT_EQ(boost::none, pop(2));
EXPECT_EQ(4, pop(3).value());
EXPECT_EQ(boost::none, pop());
}
TEST_F(QueueMapTest_Values, AfterPushingOnePoppingOnePerKeyPushingOne) {
push(2, 3);
pop(2);
push(3, 4);
EXPECT_EQ(boost::none, pop(2));
EXPECT_EQ(4, pop(3).value());
EXPECT_EQ(boost::none, pop());
}
TEST_F(QueueMapTest_Values, PushingSomePoppingMiddlePerKey) {
push(1, 2);
push(2, 3);
push(3, 4);
push(4, 5);
push(5, 6);
EXPECT_EQ(3, pop(2).value());
EXPECT_EQ(5, pop(4).value());
EXPECT_EQ(2, pop().value());
EXPECT_EQ(4, pop().value());
EXPECT_EQ(6, pop().value());
EXPECT_EQ(boost::none, pop());
}
TEST_F(QueueMapTest_Values, PushingSomePoppingFirstPerKey) {
push(1, 2);
push(2, 3);
push(3, 4);
push(4, 5);
push(5, 6);
EXPECT_EQ(2, pop(1).value());
EXPECT_EQ(3, pop(2).value());
EXPECT_EQ(4, pop().value());
EXPECT_EQ(5, pop().value());
EXPECT_EQ(6, pop().value());
EXPECT_EQ(boost::none, pop());
}
TEST_F(QueueMapTest_Values, PushingSomePoppingLastPerKey) {
push(1, 2);
push(2, 3);
push(3, 4);
push(4, 5);
push(5, 6);
EXPECT_EQ(6, pop(5).value());
EXPECT_EQ(5, pop(4).value());
EXPECT_EQ(2, pop().value());
EXPECT_EQ(3, pop().value());
EXPECT_EQ(4, pop().value());
EXPECT_EQ(boost::none, pop());
}
//This test forces the underlying datastructure (std::map or std::unordered_map) to grow and reallocate memory.
//So it tests, that QueueMap still works after reallocating memory.
TEST_F(QueueMapTest_Values, ManyValues) {
//Push 1 million entries
for (int i = 0; i < 1000000; ++i) {
push(i, 2*i);
}
//pop every other one by key
for (int i = 0; i < 1000000; i += 2) {
EXPECT_EQ(2*i, pop(i).value());
}
//pop the rest in queue order
for (int i = 1; i < 1000000; i += 2) {
EXPECT_EQ(2*i, peek().value());
EXPECT_EQ(2*i, pop().value());
}
EXPECT_EQ(0, size());
EXPECT_EQ(boost::none, pop());
EXPECT_EQ(boost::none, peek());
}
TEST_F(QueueMapTest_Values, PushAlreadyExistingValue) {
push(2, 3);
EXPECT_ANY_THROW(
push(2, 4);
);
}

View File

@ -0,0 +1,13 @@
#include "CacheTest.h"
void CacheTest::push(int key, int value) {
return _cache.push(MinimalKeyType::create(key), MinimalValueType::create(value));
}
boost::optional<int> CacheTest::pop(int key) {
boost::optional<MinimalValueType> entry = _cache.pop(MinimalKeyType::create(key));
if (!entry) {
return boost::none;
}
return entry->value();
}

View File

@ -0,0 +1,25 @@
#pragma once
#ifndef BLOCKS_MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_QUEUEMAPTEST_H_
#define BLOCKS_MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_QUEUEMAPTEST_H_
#include <google/gtest/gtest.h>
#include "../../../../../implementations/caching/cache/Cache.h"
#include "MinimalKeyType.h"
#include "MinimalValueType.h"
#include <boost/optional.hpp>
// This class is a parent class for tests on QueueMap.
// It offers functions to work with a QueueMap test object which is built using types having only the minimal type requirements.
// Furthermore, the class checks that there are no memory leaks left after destructing the QueueMap (by counting leftover instances of Keys/Values).
class CacheTest: public ::testing::Test {
public:
void push(int key, int value);
boost::optional<int> pop(int key);
using Cache = blockstore::caching::Cache<MinimalKeyType, MinimalValueType>;
private:
Cache _cache;
};
#endif

View File

@ -0,0 +1,3 @@
#include "CopyableMovableValueType.h"
int CopyableMovableValueType::numCopyConstructorCalled = 0;

View File

@ -0,0 +1,23 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_COPYABLEMOVABLEVALUETYPE_H_
#define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_COPYABLEMOVABLEVALUETYPE_H_
class CopyableMovableValueType {
public:
static int numCopyConstructorCalled;
CopyableMovableValueType(int value): _value(value) {}
CopyableMovableValueType(const CopyableMovableValueType &rhs): CopyableMovableValueType(rhs._value) {
++numCopyConstructorCalled;
}
CopyableMovableValueType(CopyableMovableValueType &&rhs): CopyableMovableValueType(rhs._value) {
//Don't increase numCopyConstructorCalled
}
int value() const {
return _value;
}
private:
int _value;
};
#endif

View File

@ -0,0 +1,3 @@
#include "MinimalKeyType.h"
int MinimalKeyType::instances = 0;

View File

@ -0,0 +1,47 @@
#pragma once
#ifndef BLOCKS_MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_MINIMALKEYTYPE_H_
#define BLOCKS_MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_MINIMALKEYTYPE_H_
#include <unordered_map>
// This is a not-default-constructible Key type
class MinimalKeyType {
public:
static int instances;
static MinimalKeyType create(int value) {
return MinimalKeyType(value);
}
MinimalKeyType(const MinimalKeyType &rhs): MinimalKeyType(rhs.value()) {
}
~MinimalKeyType() {
--instances;
}
int value() const {
return _value;
}
private:
MinimalKeyType(int value): _value(value) {
++instances;
}
int _value;
};
namespace std {
template <> struct hash<MinimalKeyType> {
size_t operator()(const MinimalKeyType &obj) const {
return obj.value();
}
};
}
inline bool operator==(const MinimalKeyType &lhs, const MinimalKeyType &rhs) {
return lhs.value() == rhs.value();
}
#endif

View File

@ -0,0 +1,3 @@
#include "MinimalValueType.h"
int MinimalValueType::instances = 0;

View File

@ -0,0 +1,44 @@
#pragma once
#ifndef BLOCKS_MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_MINIMALVALUETYPE_H_
#define BLOCKS_MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_MINIMALVALUETYPE_H_
#include <messmer/cpp-utils/macros.h>
#include <cassert>
// This is a not-default-constructible non-copyable but moveable Value type
class MinimalValueType {
public:
static int instances;
static MinimalValueType create(int value) {
return MinimalValueType(value);
}
MinimalValueType(MinimalValueType &&rhs): MinimalValueType(rhs.value()) {
rhs._isMoved = true;
}
~MinimalValueType() {
assert(!_isDestructed);
--instances;
_isDestructed = true;
}
int value() const {
assert(!_isMoved && !_isDestructed);
return _value;
}
private:
MinimalValueType(int value): _value(value), _isMoved(false), _isDestructed(false) {
++instances;
}
int _value;
bool _isMoved;
bool _isDestructed;
DISALLOW_COPY_AND_ASSIGN(MinimalValueType);
};
#endif

View File

@ -0,0 +1,45 @@
#include "QueueMapTest.h"
QueueMapTest::QueueMapTest() {
MinimalKeyType::instances = 0;
MinimalValueType::instances = 0;
_map = std::make_unique<blockstore::caching::QueueMap<MinimalKeyType, MinimalValueType>>();
}
QueueMapTest::~QueueMapTest() {
_map.reset();
EXPECT_EQ(0, MinimalKeyType::instances);
EXPECT_EQ(0, MinimalValueType::instances);
}
void QueueMapTest::push(int key, int value) {
_map->push(MinimalKeyType::create(key), MinimalValueType::create(value));
}
boost::optional<int> QueueMapTest::pop() {
auto elem = _map->pop();
if (!elem) {
return boost::none;
}
return elem.value().value();
}
boost::optional<int> QueueMapTest::pop(int key) {
auto elem = _map->pop(MinimalKeyType::create(key));
if (!elem) {
return boost::none;
}
return elem.value().value();
}
boost::optional<int> QueueMapTest::peek() {
auto elem = _map->peek();
if (!elem) {
return boost::none;
}
return elem.value().value();
}
int QueueMapTest::size() {
return _map->size();
}

View File

@ -0,0 +1,32 @@
#pragma once
#ifndef BLOCKS_MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_QUEUEMAPTEST_H_
#define BLOCKS_MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_QUEUEMAPTEST_H_
#include <google/gtest/gtest.h>
#include <memory>
#include "../../../../../implementations/caching/cache/QueueMap.h"
#include "MinimalKeyType.h"
#include "MinimalValueType.h"
#include <boost/optional.hpp>
// This class is a parent class for tests on QueueMap.
// It offers functions to work with a QueueMap test object which is built using types having only the minimal type requirements.
// Furthermore, the class checks that there are no memory leaks left after destructing the QueueMap (by counting leftover instances of Keys/Values).
class QueueMapTest: public ::testing::Test {
public:
QueueMapTest();
~QueueMapTest();
void push(int key, int value);
boost::optional<int> pop();
boost::optional<int> pop(int key);
boost::optional<int> peek();
int size();
private:
std::unique_ptr<blockstore::caching::QueueMap<MinimalKeyType, MinimalValueType>> _map;
};
#endif

View File

@ -0,0 +1,226 @@
#include <google/gtest/gtest.h>
#include "../../../implementations/encrypted/ciphers/Cipher.h"
#include "../../../implementations/encrypted/ciphers/AES256_CFB.h"
#include "../../../implementations/encrypted/ciphers/AES256_GCM.h"
#include "testutils/FakeAuthenticatedCipher.h"
#include <messmer/cpp-utils/data/DataFixture.h>
#include <messmer/cpp-utils/data/Data.h>
#include <boost/optional/optional_io.hpp>
using namespace blockstore::encrypted;
using cpputils::Data;
using cpputils::DataFixture;
template<class Cipher>
class CipherTest: public ::testing::Test {
public:
BOOST_CONCEPT_ASSERT((CipherConcept<Cipher>));
typename Cipher::EncryptionKey encKey = createKeyFixture();
static typename Cipher::EncryptionKey createKeyFixture(int seed = 0) {
Data data = DataFixture::generate(Cipher::EncryptionKey::BINARY_LENGTH, seed);
return Cipher::EncryptionKey::FromBinary(data.data());
}
void CheckEncryptThenDecryptIsIdentity(const Data &plaintext) {
Data ciphertext = Encrypt(plaintext);
Data decrypted = Decrypt(ciphertext);
EXPECT_EQ(plaintext, decrypted);
}
void CheckEncryptIsIndeterministic(const Data &plaintext) {
Data ciphertext = Encrypt(plaintext);
Data ciphertext2 = Encrypt(plaintext);
EXPECT_NE(ciphertext, ciphertext2);
}
void CheckEncryptedSize(const Data &plaintext) {
Data ciphertext = Encrypt(plaintext);
EXPECT_EQ(Cipher::ciphertextSize(plaintext.size()), ciphertext.size());
}
void ExpectDoesntDecrypt(const Data &ciphertext) {
auto decrypted = Cipher::decrypt((byte*)ciphertext.data(), ciphertext.size(), this->encKey);
EXPECT_FALSE(decrypted);
}
Data Encrypt(const Data &plaintext) {
return Cipher::encrypt((byte*)plaintext.data(), plaintext.size(), this->encKey);
}
Data Decrypt(const Data &ciphertext) {
return Cipher::decrypt((byte*)ciphertext.data(), ciphertext.size(), this->encKey).value();
}
static Data CreateZeroes(unsigned int size) {
return std::move(Data(size).FillWithZeroes());
}
static Data CreateData(unsigned int size, unsigned int seed = 0) {
return DataFixture::generate(size, seed);
}
};
TYPED_TEST_CASE_P(CipherTest);
constexpr std::initializer_list<unsigned int> SIZES = {0, 1, 100, 1024, 5000, 1048576, 20971520};
TYPED_TEST_P(CipherTest, Size) {
for (auto size: SIZES) {
EXPECT_EQ(size, TypeParam::ciphertextSize(TypeParam::plaintextSize(size)));
EXPECT_EQ(size, TypeParam::plaintextSize(TypeParam::ciphertextSize(size)));
}
}
TYPED_TEST_P(CipherTest, EncryptThenDecrypt_Zeroes) {
for (auto size: SIZES) {
Data plaintext = this->CreateZeroes(size);
this->CheckEncryptThenDecryptIsIdentity(plaintext);
}
}
TYPED_TEST_P(CipherTest, EncryptThenDecrypt_Data) {
for (auto size: SIZES) {
Data plaintext = this->CreateData(size);
this->CheckEncryptThenDecryptIsIdentity(plaintext);
}
}
TYPED_TEST_P(CipherTest, EncryptIsIndeterministic_Zeroes) {
for (auto size: SIZES) {
Data plaintext = this->CreateZeroes(size);
this->CheckEncryptIsIndeterministic(plaintext);
}
}
TYPED_TEST_P(CipherTest, EncryptIsIndeterministic_Data) {
for (auto size: SIZES) {
Data plaintext = this->CreateData(size);
this->CheckEncryptIsIndeterministic(plaintext);
}
}
TYPED_TEST_P(CipherTest, EncryptedSize) {
for (auto size: SIZES) {
Data plaintext = this->CreateData(size);
this->CheckEncryptedSize(plaintext);
}
}
TYPED_TEST_P(CipherTest, TryDecryptDataThatIsTooSmall) {
Data tooSmallCiphertext(TypeParam::ciphertextSize(0) - 1);
this->ExpectDoesntDecrypt(tooSmallCiphertext);
}
TYPED_TEST_P(CipherTest, TryDecryptDataThatIsMuchTooSmall_0) {
static_assert(TypeParam::ciphertextSize(0) > 0, "If this fails, the test case doesn't make sense.");
Data tooSmallCiphertext(0);
this->ExpectDoesntDecrypt(tooSmallCiphertext);
}
TYPED_TEST_P(CipherTest, TryDecryptDataThatIsMuchTooSmall_1) {
static_assert(TypeParam::ciphertextSize(0) > 1, "If this fails, the test case doesn't make sense.");
Data tooSmallCiphertext(1);
this->ExpectDoesntDecrypt(tooSmallCiphertext);
}
REGISTER_TYPED_TEST_CASE_P(CipherTest,
Size,
EncryptThenDecrypt_Zeroes,
EncryptThenDecrypt_Data,
EncryptIsIndeterministic_Zeroes,
EncryptIsIndeterministic_Data,
EncryptedSize,
TryDecryptDataThatIsTooSmall,
TryDecryptDataThatIsMuchTooSmall_0,
TryDecryptDataThatIsMuchTooSmall_1
);
template<class Cipher>
class AuthenticatedCipherTest: public CipherTest<Cipher> {
public:
Data zeroes1 = CipherTest<Cipher>::CreateZeroes(1);
Data plaintext1 = CipherTest<Cipher>::CreateData(1);
Data zeroes2 = CipherTest<Cipher>::CreateZeroes(100 * 1024);
Data plaintext2 = CipherTest<Cipher>::CreateData(100 * 1024);
};
TYPED_TEST_CASE_P(AuthenticatedCipherTest);
TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Zeroes_Size1) {
Data ciphertext = this->Encrypt(this->zeroes1);
*(byte*)ciphertext.data() = *(byte*)ciphertext.data() + 1;
this->ExpectDoesntDecrypt(ciphertext);
}
TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Data_Size1) {
Data ciphertext = this->Encrypt(this->plaintext1);
*(byte*)ciphertext.data() = *(byte*)ciphertext.data() + 1;
this->ExpectDoesntDecrypt(ciphertext);
}
TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Zeroes) {
Data ciphertext = this->Encrypt(this->zeroes2);
*(byte*)ciphertext.data() = *(byte*)ciphertext.data() + 1;
this->ExpectDoesntDecrypt(ciphertext);
}
TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Data) {
Data ciphertext = this->Encrypt(this->plaintext2);
*(byte*)ciphertext.data() = *(byte*)ciphertext.data() + 1;
this->ExpectDoesntDecrypt(ciphertext);
}
TYPED_TEST_P(AuthenticatedCipherTest, ModifyLastByte_Zeroes) {
Data ciphertext = this->Encrypt(this->zeroes2);
((byte*)ciphertext.data())[ciphertext.size() - 1] = ((byte*)ciphertext.data())[ciphertext.size() - 1] + 1;
this->ExpectDoesntDecrypt(ciphertext);
}
TYPED_TEST_P(AuthenticatedCipherTest, ModifyLastByte_Data) {
Data ciphertext = this->Encrypt(this->plaintext2);
((byte*)ciphertext.data())[ciphertext.size() - 1] = ((byte*)ciphertext.data())[ciphertext.size() - 1] + 1;
this->ExpectDoesntDecrypt(ciphertext);
}
TYPED_TEST_P(AuthenticatedCipherTest, ModifyMiddleByte_Zeroes) {
Data ciphertext = this->Encrypt(this->zeroes2);
((byte*)ciphertext.data())[ciphertext.size()/2] = ((byte*)ciphertext.data())[ciphertext.size()/2] + 1;
this->ExpectDoesntDecrypt(ciphertext);
}
TYPED_TEST_P(AuthenticatedCipherTest, ModifyMiddleByte_Data) {
Data ciphertext = this->Encrypt(this->plaintext2);
((byte*)ciphertext.data())[ciphertext.size()/2] = ((byte*)ciphertext.data())[ciphertext.size()/2] + 1;
this->ExpectDoesntDecrypt(ciphertext);
}
TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptZeroesData) {
this->ExpectDoesntDecrypt(this->zeroes2);
}
TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptRandomData) {
this->ExpectDoesntDecrypt(this->plaintext2);
}
REGISTER_TYPED_TEST_CASE_P(AuthenticatedCipherTest,
ModifyFirstByte_Zeroes_Size1,
ModifyFirstByte_Zeroes,
ModifyFirstByte_Data_Size1,
ModifyFirstByte_Data,
ModifyLastByte_Zeroes,
ModifyLastByte_Data,
ModifyMiddleByte_Zeroes,
ModifyMiddleByte_Data,
TryDecryptZeroesData,
TryDecryptRandomData
);
INSTANTIATE_TYPED_TEST_CASE_P(Fake, CipherTest, FakeAuthenticatedCipher);
INSTANTIATE_TYPED_TEST_CASE_P(Fake, AuthenticatedCipherTest, FakeAuthenticatedCipher);
INSTANTIATE_TYPED_TEST_CASE_P(AES256_CFB, CipherTest, AES256_CFB); //CFB mode is not authenticated
INSTANTIATE_TYPED_TEST_CASE_P(AES256_GCM, CipherTest, AES256_GCM);
INSTANTIATE_TYPED_TEST_CASE_P(AES256_GCM, AuthenticatedCipherTest, AES256_GCM);

View File

@ -1,24 +0,0 @@
#include "../../../implementations/encrypted/EncryptedBlockStore.h"
#include "../../../implementations/testfake/FakeBlockStore.h"
#include "../../testutils/BlockStoreTest.h"
#include "google/gtest/gtest.h"
using blockstore::BlockStore;
using blockstore::encrypted::EncryptedBlockStore;
using blockstore::encrypted::EncryptionKey;
using blockstore::testfake::FakeBlockStore;
using std::unique_ptr;
using std::make_unique;
class EncryptedBlockStoreTestFixture: public BlockStoreTestFixture {
public:
unique_ptr<BlockStore> createBlockStore() override {
return make_unique<EncryptedBlockStore>(make_unique<FakeBlockStore>(), EncryptionKey::FromString("1491BB4932A389EE14BC7090A272EE5517627CFA147A971A8E6E747E0C772972"));
}
};
INSTANTIATE_TYPED_TEST_CASE_P(Encrypted, BlockStoreTest, EncryptedBlockStoreTestFixture);
//TODO Add specific tests, for example loading it with a different key doesn't work

View File

@ -0,0 +1,40 @@
#include "../../../implementations/encrypted/ciphers/AES256_GCM.h"
#include "../../../implementations/encrypted/ciphers/AES256_CFB.h"
#include "../../../implementations/encrypted/ciphers/Cipher.h"
#include "../../../implementations/encrypted/EncryptedBlockStore.h"
#include "../../../implementations/testfake/FakeBlockStore.h"
#include "../../testutils/BlockStoreTest.h"
#include "testutils/FakeAuthenticatedCipher.h"
#include "google/gtest/gtest.h"
using ::testing::Test;
using blockstore::BlockStore;
using blockstore::encrypted::EncryptedBlockStore;
using blockstore::testfake::FakeBlockStore;
using blockstore::encrypted::AES256_GCM;
using blockstore::encrypted::AES256_CFB;
using std::unique_ptr;
using std::make_unique;
using cpputils::Data;
using cpputils::DataFixture;
template<class Cipher>
class EncryptedBlockStoreTestFixture: public BlockStoreTestFixture {
public:
unique_ptr<BlockStore> createBlockStore() override {
return make_unique<EncryptedBlockStore<Cipher>>(make_unique<FakeBlockStore>(), 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, 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>);

View File

@ -0,0 +1,113 @@
#include <google/gtest/gtest.h>
#include "testutils/FakeAuthenticatedCipher.h"
#include "../../../implementations/encrypted/EncryptedBlockStore.h"
#include "../../../implementations/testfake/FakeBlockStore.h"
#include "../../../utils/BlockStoreUtils.h"
#include <messmer/cpp-utils/data/DataFixture.h>
using ::testing::Test;
using std::unique_ptr;
using std::make_unique;
using cpputils::DataFixture;
using cpputils::Data;
using blockstore::testfake::FakeBlockStore;
using namespace blockstore::encrypted;
class EncryptedBlockStoreTest: public Test {
public:
static constexpr unsigned int BLOCKSIZE = 1024;
EncryptedBlockStoreTest():
_baseBlockStore(make_unique<FakeBlockStore>()),
baseBlockStore(_baseBlockStore.get()),
blockStore(make_unique<EncryptedBlockStore<FakeAuthenticatedCipher>>(std::move(_baseBlockStore), FakeAuthenticatedCipher::Key1())),
data(DataFixture::generate(BLOCKSIZE)) {
}
unique_ptr<FakeBlockStore> _baseBlockStore;
FakeBlockStore *baseBlockStore;
unique_ptr<EncryptedBlockStore<FakeAuthenticatedCipher>> blockStore;
Data data;
blockstore::Key CreateBlockDirectlyWithFixtureAndReturnKey() {
return blockStore->create(data)->key();
}
blockstore::Key CreateBlockWriteFixtureToItAndReturnKey() {
auto block = blockStore->create(Data(data.size()));
block->write(data.data(), 0, data.size());
return block->key();
}
void ModifyBaseBlock(const blockstore::Key &key) {
auto block = baseBlockStore->load(key);
uint8_t middle_byte = ((byte*)block->data())[10];
uint8_t new_middle_byte = middle_byte + 1;
block->write(&new_middle_byte, 10, 1);
}
blockstore::Key CopyBaseBlock(const blockstore::Key &key) {
auto source = baseBlockStore->load(key);
return blockstore::utils::copyToNewBlock(baseBlockStore, *source)->key();
}
};
TEST_F(EncryptedBlockStoreTest, LoadingWithSameKeyWorks_WriteOnCreate) {
auto key = CreateBlockDirectlyWithFixtureAndReturnKey();
auto loaded = blockStore->load(key);
EXPECT_NE(nullptr, loaded.get());
EXPECT_EQ(data.size(), loaded->size());
EXPECT_EQ(0, std::memcmp(data.data(), loaded->data(), data.size()));
}
TEST_F(EncryptedBlockStoreTest, LoadingWithSameKeyWorks_WriteSeparately) {
auto key = CreateBlockWriteFixtureToItAndReturnKey();
auto loaded = blockStore->load(key);
EXPECT_NE(nullptr, loaded.get());
EXPECT_EQ(data.size(), loaded->size());
EXPECT_EQ(0, std::memcmp(data.data(), loaded->data(), data.size()));
}
TEST_F(EncryptedBlockStoreTest, LoadingWithDifferentKeyDoesntWork_WriteOnCreate) {
auto key = CreateBlockDirectlyWithFixtureAndReturnKey();
blockStore->__setKey(FakeAuthenticatedCipher::Key2());
auto loaded = blockStore->load(key);
EXPECT_EQ(nullptr, loaded.get());
}
TEST_F(EncryptedBlockStoreTest, LoadingWithDifferentKeyDoesntWork_WriteSeparately) {
auto key = CreateBlockWriteFixtureToItAndReturnKey();
blockStore->__setKey(FakeAuthenticatedCipher::Key2());
auto loaded = blockStore->load(key);
EXPECT_EQ(nullptr, loaded.get());
}
TEST_F(EncryptedBlockStoreTest, LoadingModifiedBlockFails_WriteOnCreate) {
auto key = CreateBlockDirectlyWithFixtureAndReturnKey();
ModifyBaseBlock(key);
auto loaded = blockStore->load(key);
EXPECT_EQ(nullptr, loaded.get());
}
TEST_F(EncryptedBlockStoreTest, LoadingModifiedBlockFails_WriteSeparately) {
auto key = CreateBlockWriteFixtureToItAndReturnKey();
ModifyBaseBlock(key);
auto loaded = blockStore->load(key);
EXPECT_EQ(nullptr, loaded.get());
}
TEST_F(EncryptedBlockStoreTest, LoadingWithDifferentBlockIdFails_WriteOnCreate) {
auto key = CreateBlockDirectlyWithFixtureAndReturnKey();
auto key2 = CopyBaseBlock(key);
auto loaded = blockStore->load(key2);
EXPECT_EQ(nullptr, loaded.get());
}
TEST_F(EncryptedBlockStoreTest, LoadingWithDifferentBlockIdFails_WriteSeparately) {
auto key = CreateBlockWriteFixtureToItAndReturnKey();
auto key2 = CopyBaseBlock(key);
auto loaded = blockStore->load(key2);
EXPECT_EQ(nullptr, loaded.get());
}

View File

@ -0,0 +1,3 @@
#include "FakeAuthenticatedCipher.h"
constexpr unsigned int FakeKey::BINARY_LENGTH;

View File

@ -0,0 +1,102 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_ENCRYPTED_TESTUTILS_FAKEAUTHENTICATEDCIPHER_H_
#define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_ENCRYPTED_TESTUTILS_FAKEAUTHENTICATEDCIPHER_H_
#include "../../../../implementations/encrypted/ciphers/Cipher.h"
#include <messmer/cpp-utils/data/FixedSizeData.h>
struct FakeKey {
FakeKey(uint8_t value_):value(value_) {}
static FakeKey CreateRandom() {
return FakeKey(rand());
}
static FakeKey FromBinary(const void *data) {
return FakeKey(*(uint8_t*)data);
}
static constexpr unsigned int BINARY_LENGTH = 1;
uint8_t value;
};
// This is a fake cipher that uses an indeterministic caesar chiffre and a 4-byte parity for a simple authentication mechanism
class FakeAuthenticatedCipher {
public:
BOOST_CONCEPT_ASSERT((blockstore::encrypted::CipherConcept<FakeAuthenticatedCipher>));
using EncryptionKey = FakeKey;
static EncryptionKey Key1() {
return FakeKey(5);
}
static EncryptionKey Key2() {
return FakeKey(63);
}
static constexpr unsigned int ciphertextSize(unsigned int plaintextBlockSize) {
return plaintextBlockSize + 5;
}
static constexpr unsigned int plaintextSize(unsigned int ciphertextBlockSize) {
return ciphertextBlockSize - 5;
}
static cpputils::Data encrypt(const byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) {
cpputils::Data result(ciphertextSize(plaintextSize));
//Add a random IV
uint8_t iv = rand();
std::memcpy(result.data(), &iv, 1);
//Use caesar chiffre on plaintext
_caesar((byte*)result.data() + 1, plaintext, plaintextSize, encKey.value + iv);
//Add parity information
int32_t parity = _parity((byte*)result.data(), plaintextSize + 1);
std::memcpy((byte*)result.data() + plaintextSize + 1, &parity, 4);
return result;
}
static boost::optional<cpputils::Data> decrypt(const byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) {
//We need at least 5 bytes (iv + parity)
if (ciphertextSize < 5) {
return boost::none;
}
//Check parity
int32_t expectedParity = _parity(ciphertext, plaintextSize(ciphertextSize) + 1);
int32_t actualParity = *(int32_t*)(ciphertext + plaintextSize(ciphertextSize) + 1);
if (expectedParity != actualParity) {
return boost::none;
}
//Decrypt caesar chiffre from ciphertext
int32_t iv = *(int32_t*)ciphertext;
cpputils::Data result(plaintextSize(ciphertextSize));
_caesar((byte*)result.data(), ciphertext + 1, plaintextSize(ciphertextSize), -(encKey.value+iv));
return std::move(result);
}
private:
static int32_t _parity(const byte *data, unsigned int size) {
int32_t parity = 34343435; // some init value
int32_t *intData = (int32_t*)data;
unsigned int intSize = size / sizeof(int32_t);
for (unsigned int i = 0; i < intSize; ++i) {
parity += intData[i];
}
unsigned int remainingBytes = size - 4 * intSize;
for (unsigned int i = 0; i < remainingBytes; ++i) {
parity += (data[4*intSize + i] << (24 - 8*i));
}
return parity;
}
static void _caesar(byte *dst, const byte *src, unsigned int size, uint8_t key) {
for (unsigned int i = 0; i < size; ++i) {
dst[i] = src[i] + key;
}
}
};
#endif

View File

@ -4,7 +4,7 @@
#include "../../testutils/BlockStoreWithRandomKeysTest.h"
#include "google/gtest/gtest.h"
#include "messmer/tempfile/src/TempDir.h"
#include "messmer/cpp-utils/tempfile/TempDir.h"
using blockstore::BlockStore;
@ -14,7 +14,7 @@ using blockstore::ondisk::OnDiskBlockStore;
using std::unique_ptr;
using std::make_unique;
using tempfile::TempDir;
using cpputils::TempDir;
class OnDiskBlockStoreTestFixture: public BlockStoreTestFixture {
public:

View File

@ -1,18 +1,17 @@
#include "../../../../implementations/ondisk/FileAlreadyExistsException.h"
#include "../../../../implementations/ondisk/OnDiskBlock.h"
#include "google/gtest/gtest.h"
#include "messmer/tempfile/src/TempFile.h"
#include "messmer/tempfile/src/TempDir.h"
#include <messmer/cpp-utils/tempfile/TempFile.h>
#include <messmer/cpp-utils/tempfile/TempDir.h>
using ::testing::Test;
using ::testing::WithParamInterface;
using ::testing::Values;
using tempfile::TempFile;
using tempfile::TempDir;
using std::unique_ptr;
using cpputils::Data;
using cpputils::TempFile;
using cpputils::TempDir;
using namespace blockstore;
using namespace blockstore::ondisk;
@ -63,13 +62,13 @@ public:
INSTANTIATE_TEST_CASE_P(OnDiskBlockCreateSizeTest, OnDiskBlockCreateSizeTest, Values(0, 1, 5, 1024, 10*1024*1024));
TEST_P(OnDiskBlockCreateSizeTest, OnDiskSizeIsCorrect) {
Data fileContent = Data::LoadFromFile(file.path());
Data fileContent = Data::LoadFromFile(file.path()).value();
EXPECT_EQ(GetParam(), fileContent.size());
}
TEST_P(OnDiskBlockCreateSizeTest, OnDiskBlockIsZeroedOut) {
Data fileContent = Data::LoadFromFile(file.path());
EXPECT_EQ(0, std::memcmp(ZEROES.data(), fileContent.data(), fileContent.size()));
Data fileContent = Data::LoadFromFile(file.path()).value();
EXPECT_EQ(ZEROES, fileContent);
}
// This test is also tested by OnDiskBlockStoreTest, but there the block is created using the BlockStore interface.

View File

@ -1,19 +1,19 @@
#include "../../../../implementations/ondisk/FileAlreadyExistsException.h"
#include "../../../../implementations/ondisk/OnDiskBlock.h"
#include "../../../testutils/DataBlockFixture.h"
#include <messmer/cpp-utils/data/DataFixture.h>
#include "google/gtest/gtest.h"
#include "messmer/tempfile/src/TempFile.h"
#include "messmer/tempfile/src/TempDir.h"
#include <messmer/cpp-utils/tempfile/TempFile.h>
#include <messmer/cpp-utils/tempfile/TempDir.h>
using ::testing::Test;
using ::testing::WithParamInterface;
using ::testing::Values;
using tempfile::TempFile;
using tempfile::TempDir;
using std::unique_ptr;
using cpputils::Data;
using cpputils::DataFixture;
using cpputils::TempFile;
using cpputils::TempDir;
using namespace blockstore;
using namespace blockstore::ondisk;
@ -27,27 +27,23 @@ public:
: dir(),
key(Key::FromString("1491BB4932A389EE14BC7090AC772972")),
file(dir.path() / key.ToString(), false),
randomData(GetParam()) {
randomData(DataFixture::generate(GetParam())) {
}
TempDir dir;
Key key;
TempFile file;
DataBlockFixture randomData;
Data randomData;
unique_ptr<OnDiskBlock> CreateBlockAndLoadItFromDisk() {
{
Data data(randomData.size());
std::memcpy(data.data(), randomData.data(), randomData.size());
auto block = OnDiskBlock::CreateOnDisk(dir.path(), key, std::move(data));
OnDiskBlock::CreateOnDisk(dir.path(), key, randomData.copy());
}
return OnDiskBlock::LoadFromDisk(dir.path(), key);
}
unique_ptr<OnDiskBlock> CreateBlock() {
Data data(randomData.size());
std::memcpy(data.data(), randomData.data(), randomData.size());
return OnDiskBlock::CreateOnDisk(dir.path(), key, std::move(data));
return OnDiskBlock::CreateOnDisk(dir.path(), key, randomData.copy());
}
void WriteDataToBlock(const unique_ptr<OnDiskBlock> &block) {
@ -60,9 +56,8 @@ public:
}
void EXPECT_STORED_FILE_DATA_CORRECT() {
Data actual = Data::LoadFromFile(file.path());
EXPECT_EQ(randomData.size(), actual.size());
EXPECT_EQ(0, std::memcmp(randomData.data(), actual.data(), randomData.size()));
Data actual = Data::LoadFromFile(file.path()).value();
EXPECT_EQ(randomData, actual);
}
};
INSTANTIATE_TEST_CASE_P(OnDiskBlockFlushTest, OnDiskBlockFlushTest, Values((size_t)0, (size_t)1, (size_t)1024, (size_t)4096, (size_t)10*1024*1024));

View File

@ -1,23 +1,24 @@
#include "../../../../implementations/ondisk/OnDiskBlock.h"
#include "../../../testutils/DataBlockFixture.h"
#include <messmer/cpp-utils/data/DataFixture.h>
#include "../../../../utils/FileDoesntExistException.h"
#include "google/gtest/gtest.h"
#include "../../../../utils/Data.h"
#include "messmer/tempfile/src/TempFile.h"
#include "messmer/tempfile/src/TempDir.h"
#include <messmer/cpp-utils/data/Data.h>
#include <messmer/cpp-utils/tempfile/TempFile.h>
#include <messmer/cpp-utils/tempfile/TempDir.h>
#include <fstream>
using ::testing::Test;
using ::testing::WithParamInterface;
using ::testing::Values;
using tempfile::TempFile;
using tempfile::TempDir;
using std::ofstream;
using std::unique_ptr;
using std::ios;
using cpputils::Data;
using cpputils::DataFixture;
using cpputils::TempFile;
using cpputils::TempDir;
using namespace blockstore;
using namespace blockstore::ondisk;
@ -40,18 +41,15 @@ public:
data.StoreToFile(file.path());
}
void StoreData(const DataBlockFixture &data) {
//TODO Implement data.StoreToFile(filepath) instead
Data dataobj(data.size());
std::memcpy(dataobj.data(), data.data(), data.size());
dataobj.StoreToFile(file.path());
void StoreData(const Data &data) {
data.StoreToFile(file.path());
}
unique_ptr<OnDiskBlock> LoadBlock() {
return OnDiskBlock::LoadFromDisk(dir.path(), key);
}
void EXPECT_BLOCK_DATA_EQ(const DataBlockFixture &expected, const OnDiskBlock &actual) {
void EXPECT_BLOCK_DATA_EQ(const Data &expected, const OnDiskBlock &actual) {
EXPECT_EQ(expected.size(), actual.size());
EXPECT_EQ(0, std::memcmp(expected.data(), actual.data(), expected.size()));
}
@ -67,7 +65,7 @@ TEST_P(OnDiskBlockLoadTest, FileSizeIsCorrect) {
}
TEST_P(OnDiskBlockLoadTest, LoadedDataIsCorrect) {
DataBlockFixture randomData(GetParam());
Data randomData = DataFixture::generate(GetParam());
StoreData(randomData);
auto block = LoadBlock();

View File

@ -1,7 +1,7 @@
#include "../../../interface/helpers/BlockStoreWithRandomKeys.h"
#include "google/gtest/gtest.h"
#include "google/gmock/gmock.h"
#include "../../testutils/DataBlockFixture.h"
#include <messmer/cpp-utils/data/DataFixture.h>
using ::testing::Test;
using ::testing::_;
@ -13,6 +13,8 @@ using ::testing::ByRef;
using std::string;
using std::unique_ptr;
using std::make_unique;
using cpputils::Data;
using cpputils::DataFixture;
using namespace blockstore;
@ -26,7 +28,7 @@ public:
return unique_ptr<Block>(do_load(key));
}
MOCK_METHOD1(do_load, Block*(const Key &));
void remove(unique_ptr<Block> block) {}
void remove(unique_ptr<Block> block) {UNUSED(block);}
MOCK_CONST_METHOD0(numBlocks, uint64_t());
};
@ -47,7 +49,7 @@ public:
const blockstore::Key key = Key::FromString("1491BB4932A389EE14BC7090AC772972");
Data createDataWithSize(size_t size) {
DataBlockFixture fixture(size);
Data fixture(DataFixture::generate(size));
Data data(size);
std::memcpy(data.data(), fixture.data(), size);
return data;

View File

@ -4,8 +4,6 @@
#include "google/gtest/gtest.h"
#include "DataBlockFixture.h"
#include "../../interface/BlockStore.h"
class BlockStoreTestFixture {
@ -28,14 +26,14 @@ TYPED_TEST_CASE_P(BlockStoreTest);
TYPED_TEST_P(BlockStoreTest, TwoCreatedBlocksHaveDifferentKeys) {
auto blockStore = this->fixture.createBlockStore();
auto block1 = blockStore->create(blockstore::Data(1024));
auto block2 = blockStore->create(blockstore::Data(1024));
auto block1 = blockStore->create(cpputils::Data(1024));
auto block2 = blockStore->create(cpputils::Data(1024));
EXPECT_NE(block1->key(), block2->key());
}
TYPED_TEST_P(BlockStoreTest, BlockIsNotLoadableAfterDeleting) {
auto blockStore = this->fixture.createBlockStore();
auto blockkey = blockStore->create(blockstore::Data(1024))->key();
auto blockkey = blockStore->create(cpputils::Data(1024))->key();
auto block = blockStore->load(blockkey);
EXPECT_NE(nullptr, block.get());
blockStore->remove(std::move(block));
@ -49,55 +47,55 @@ TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectOnEmptyBlockstore) {
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingOneBlock) {
auto blockStore = this->fixture.createBlockStore();
auto block = blockStore->create(blockstore::Data(1));
auto block = blockStore->create(cpputils::Data(1));
EXPECT_EQ(1, blockStore->numBlocks());
}
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingOneBlock_AfterClosingBlock) {
auto blockStore = this->fixture.createBlockStore();
blockStore->create(blockstore::Data(1));
blockStore->create(cpputils::Data(1));
EXPECT_EQ(1, blockStore->numBlocks());
}
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterRemovingTheLastBlock) {
auto blockStore = this->fixture.createBlockStore();
auto block = blockStore->create(blockstore::Data(1));
auto block = blockStore->create(cpputils::Data(1));
blockStore->remove(std::move(block));
EXPECT_EQ(0, blockStore->numBlocks());
}
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocks) {
auto blockStore = this->fixture.createBlockStore();
auto block1 = blockStore->create(blockstore::Data(1));
auto block2 = blockStore->create(blockstore::Data(0));
auto block1 = blockStore->create(cpputils::Data(1));
auto block2 = blockStore->create(cpputils::Data(0));
EXPECT_EQ(2, blockStore->numBlocks());
}
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingFirstBlock) {
auto blockStore = this->fixture.createBlockStore();
blockStore->create(blockstore::Data(1));
auto block2 = blockStore->create(blockstore::Data(0));
blockStore->create(cpputils::Data(1));
auto block2 = blockStore->create(cpputils::Data(0));
EXPECT_EQ(2, blockStore->numBlocks());
}
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingSecondBlock) {
auto blockStore = this->fixture.createBlockStore();
auto block1 = blockStore->create(blockstore::Data(1));
blockStore->create(blockstore::Data(0));
auto block1 = blockStore->create(cpputils::Data(1));
blockStore->create(cpputils::Data(0));
EXPECT_EQ(2, blockStore->numBlocks());
}
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingBothBlocks) {
auto blockStore = this->fixture.createBlockStore();
blockStore->create(blockstore::Data(1));
blockStore->create(blockstore::Data(0));
blockStore->create(cpputils::Data(1));
blockStore->create(cpputils::Data(0));
EXPECT_EQ(2, blockStore->numBlocks());
}
TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterRemovingABlock) {
auto blockStore = this->fixture.createBlockStore();
auto block = blockStore->create(blockstore::Data(1));
blockStore->create(blockstore::Data(1));
auto block = blockStore->create(cpputils::Data(1));
blockStore->create(cpputils::Data(1));
blockStore->remove(std::move(block));
EXPECT_EQ(1, blockStore->numBlocks());
}

View File

@ -12,15 +12,12 @@ public:
BlockStoreDataParametrizedTest(std::unique_ptr<blockstore::BlockStore> blockStore_, const DataRange &testData_)
: blockStore(std::move(blockStore_)),
testData(testData_),
foregroundData(testData.count), backgroundData(testData.blocksize) {
DataBlockFixture _foregroundData(testData.count);
DataBlockFixture _backgroundData(testData.blocksize);
std::memcpy(foregroundData.data(), _foregroundData.data(), foregroundData.size());
std::memcpy(backgroundData.data(), _backgroundData.data(), backgroundData.size());
foregroundData(cpputils::DataFixture::generate(testData.count, 0)),
backgroundData(cpputils::DataFixture::generate(testData.blocksize, 1)) {
}
void TestWriteAndReadImmediately() {
auto block = blockStore->create(blockstore::Data(testData.blocksize).FillWithZeroes());
auto block = blockStore->create(cpputils::Data(testData.blocksize).FillWithZeroes());
block->write(foregroundData.data(), testData.offset, testData.count);
EXPECT_DATA_READS_AS(foregroundData, *block, testData.offset, testData.count);
@ -36,7 +33,7 @@ public:
}
void TestOverwriteAndRead() {
auto block = blockStore->create(blockstore::Data(testData.blocksize));
auto block = blockStore->create(cpputils::Data(testData.blocksize));
block->write(backgroundData.data(), 0, testData.blocksize);
block->write(foregroundData.data(), testData.offset, testData.count);
EXPECT_DATA_READS_AS(foregroundData, *block, testData.offset, testData.count);
@ -46,30 +43,25 @@ public:
private:
std::unique_ptr<blockstore::BlockStore> blockStore;
DataRange testData;
blockstore::Data foregroundData;
blockstore::Data backgroundData;
cpputils::Data foregroundData;
cpputils::Data backgroundData;
void EXPECT_DATA_EQ(const blockstore::Data &expected, const blockstore::Data &actual) {
EXPECT_EQ(expected.size(), actual.size());
EXPECT_EQ(0, std::memcmp(expected.data(), actual.data(), expected.size()));
}
blockstore::Key CreateBlockWriteToItAndReturnKey(const blockstore::Data &to_write) {
auto newblock = blockStore->create(blockstore::Data(testData.blocksize).FillWithZeroes());
blockstore::Key CreateBlockWriteToItAndReturnKey(const cpputils::Data &to_write) {
auto newblock = blockStore->create(cpputils::Data(testData.blocksize).FillWithZeroes());
newblock->write(to_write.data(), testData.offset, testData.count);
return newblock->key();
}
void EXPECT_DATA_READS_AS(const blockstore::Data &expected, const blockstore::Block &block, off_t offset, size_t count) {
blockstore::Data read(count);
void EXPECT_DATA_READS_AS(const cpputils::Data &expected, const blockstore::Block &block, off_t offset, size_t count) {
cpputils::Data read(count);
std::memcpy(read.data(), (uint8_t*)block.data() + offset, count);
EXPECT_DATA_EQ(expected, read);
EXPECT_EQ(expected, read);
}
void EXPECT_DATA_READS_AS_OUTSIDE_OF(const blockstore::Data &expected, const blockstore::Block &block, off_t start, size_t count) {
blockstore::Data begin(start);
blockstore::Data end(testData.blocksize - count - start);
void EXPECT_DATA_READS_AS_OUTSIDE_OF(const cpputils::Data &expected, const blockstore::Block &block, off_t start, size_t count) {
cpputils::Data begin(start);
cpputils::Data end(testData.blocksize - count - start);
std::memcpy(begin.data(), expected.data(), start);
std::memcpy(end.data(), (uint8_t*)expected.data()+start+count, end.size());
@ -79,7 +71,7 @@ private:
}
void EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(const blockstore::Block &block, off_t start, size_t count) {
blockstore::Data ZEROES(testData.blocksize);
cpputils::Data ZEROES(testData.blocksize);
ZEROES.FillWithZeroes();
EXPECT_DATA_READS_AS_OUTSIDE_OF(ZEROES, block, start, count);
}

View File

@ -1,6 +1,7 @@
// This file is meant to be included by BlockStoreTest.h only
#include "../../utils/Data.h"
#include <messmer/cpp-utils/data/Data.h>
#include <messmer/cpp-utils/data/DataFixture.h>
class BlockStoreSizeParameterizedTest {
public:
@ -18,39 +19,34 @@ public:
}
void TestCreatedBlockData() {
DataBlockFixture dataFixture(size);
blockstore::Data data(size);
std::memcpy(data.data(), dataFixture.data(), size);
auto block = blockStore->create(data);
EXPECT_EQ(0, std::memcmp(dataFixture.data(), block->data(), size));
cpputils::Data data = cpputils::DataFixture::generate(size);
auto block = blockStore->create(data);
EXPECT_EQ(0, std::memcmp(data.data(), block->data(), size));
}
void TestLoadingUnchangedBlockData() {
DataBlockFixture dataFixture(size);
blockstore::Data data(size);
std::memcpy(data.data(), dataFixture.data(), size);
blockstore::Key key = blockStore->create(data)->key();
cpputils::Data data = cpputils::DataFixture::generate(size);
blockstore::Key key = blockStore->create(data)->key();
auto loaded_block = blockStore->load(key);
EXPECT_EQ(0, std::memcmp(dataFixture.data(), loaded_block->data(), size));
EXPECT_EQ(0, std::memcmp(data.data(), loaded_block->data(), size));
}
void TestLoadedBlockIsCorrect() {
DataBlockFixture randomData(size);
cpputils::Data randomData = cpputils::DataFixture::generate(size);
auto loaded_block = StoreDataToBlockAndLoadIt(randomData);
EXPECT_EQ(size, loaded_block->size());
EXPECT_EQ(0, std::memcmp(randomData.data(), loaded_block->data(), size));
}
void TestLoadedBlockIsCorrectWhenLoadedDirectlyAfterFlushing() {
DataBlockFixture randomData(size);
cpputils::Data randomData = cpputils::DataFixture::generate(size);
auto loaded_block = StoreDataToBlockAndLoadItDirectlyAfterFlushing(randomData);
EXPECT_EQ(size, loaded_block->size());
EXPECT_EQ(0, std::memcmp(randomData.data(), loaded_block->data(), size));
}
void TestAfterCreate_FlushingDoesntChangeBlock() {
DataBlockFixture randomData(size);
cpputils::Data randomData = cpputils::DataFixture::generate(size);
auto block = CreateBlock();
WriteDataToBlock(block.get(), randomData);
block->flush();
@ -59,7 +55,7 @@ public:
}
void TestAfterLoad_FlushingDoesntChangeBlock() {
DataBlockFixture randomData(size);
cpputils::Data randomData = cpputils::DataFixture::generate(size);
auto block = CreateBlockAndLoadIt();
WriteDataToBlock(block.get(), randomData);
block->flush();
@ -68,10 +64,10 @@ public:
}
void TestAfterCreate_FlushesWhenDestructed() {
DataBlockFixture randomData(size);
cpputils::Data randomData = cpputils::DataFixture::generate(size);
blockstore::Key key = key;
{
auto block = blockStore->create(blockstore::Data(size));
auto block = blockStore->create(cpputils::Data(size));
key = block->key();
WriteDataToBlock(block.get(), randomData);
}
@ -80,7 +76,7 @@ public:
}
void TestAfterLoad_FlushesWhenDestructed() {
DataBlockFixture randomData(size);
cpputils::Data randomData = cpputils::DataFixture::generate(size);
blockstore::Key key = key;
{
key = CreateBlock()->key();
@ -102,26 +98,22 @@ private:
std::unique_ptr<blockstore::BlockStore> blockStore;
size_t size;
blockstore::Data ZEROES(size_t size) {
blockstore::Data ZEROES(size);
cpputils::Data ZEROES(size_t size) {
cpputils::Data ZEROES(size);
ZEROES.FillWithZeroes();
return ZEROES;
}
std::unique_ptr<blockstore::Block> StoreDataToBlockAndLoadIt(const DataBlockFixture &data) {
std::unique_ptr<blockstore::Block> StoreDataToBlockAndLoadIt(const cpputils::Data &data) {
blockstore::Key key = StoreDataToBlockAndGetKey(data);
return blockStore->load(key);
}
blockstore::Key StoreDataToBlockAndGetKey(const DataBlockFixture &dataFixture) {
blockstore::Data data(dataFixture.size());
std::memcpy(data.data(), dataFixture.data(), dataFixture.size());
blockstore::Key StoreDataToBlockAndGetKey(const cpputils::Data &data) {
return blockStore->create(data)->key();
}
std::unique_ptr<blockstore::Block> StoreDataToBlockAndLoadItDirectlyAfterFlushing(const DataBlockFixture &dataFixture) {
blockstore::Data data(dataFixture.size());
std::memcpy(data.data(), dataFixture.data(), dataFixture.size());
std::unique_ptr<blockstore::Block> StoreDataToBlockAndLoadItDirectlyAfterFlushing(const cpputils::Data &data) {
auto block = blockStore->create(data);
block->flush();
return blockStore->load(block->key());
@ -133,14 +125,14 @@ private:
}
std::unique_ptr<blockstore::Block> CreateBlock() {
return blockStore->create(blockstore::Data(size));
return blockStore->create(cpputils::Data(size));
}
void WriteDataToBlock(blockstore::Block *block, const DataBlockFixture &randomData) {
void WriteDataToBlock(blockstore::Block *block, const cpputils::Data &randomData) {
block->write(randomData.data(), 0, randomData.size());
}
void EXPECT_BLOCK_DATA_CORRECT(const blockstore::Block &block, const DataBlockFixture &randomData) {
void EXPECT_BLOCK_DATA_CORRECT(const blockstore::Block &block, const cpputils::Data &randomData) {
EXPECT_EQ(randomData.size(), block.size());
EXPECT_EQ(0, std::memcmp(randomData.data(), block.data(), randomData.size()));
}

View File

@ -3,7 +3,6 @@
#define TEST_BLOCKSTORE_IMPLEMENTATIONS_TESTUTILS_BLOCKSTOREWITHRANDOMKEYSTEST_H_
#include <google/gtest/gtest.h>
#include "DataBlockFixture.h"
#include "../../interface/BlockStore.h"
@ -31,45 +30,45 @@ TYPED_TEST_CASE_P(BlockStoreWithRandomKeysTest);
TYPED_TEST_P(BlockStoreWithRandomKeysTest, CreateTwoBlocksWithSameKeyAndSameSize) {
auto blockStore = this->fixture.createBlockStore();
auto block = blockStore->tryCreate(this->key, blockstore::Data(1024));
auto block = blockStore->tryCreate(this->key, cpputils::Data(1024));
block->flush();
auto block2 = blockStore->tryCreate(this->key, blockstore::Data(1024));
auto block2 = blockStore->tryCreate(this->key, cpputils::Data(1024));
EXPECT_TRUE((bool)block);
EXPECT_FALSE((bool)block2);
}
TYPED_TEST_P(BlockStoreWithRandomKeysTest, CreateTwoBlocksWithSameKeyAndDifferentSize) {
auto blockStore = this->fixture.createBlockStore();
auto block = blockStore->tryCreate(this->key, blockstore::Data(1024));
auto block = blockStore->tryCreate(this->key, cpputils::Data(1024));
block->flush();
auto block2 = blockStore->tryCreate(this->key, blockstore::Data(4096));
auto block2 = blockStore->tryCreate(this->key, cpputils::Data(4096));
EXPECT_TRUE((bool)block);
EXPECT_FALSE((bool)block2);
}
TYPED_TEST_P(BlockStoreWithRandomKeysTest, CreateTwoBlocksWithSameKeyAndFirstNullSize) {
auto blockStore = this->fixture.createBlockStore();
auto block = blockStore->tryCreate(this->key, blockstore::Data(0));
auto block = blockStore->tryCreate(this->key, cpputils::Data(0));
block->flush();
auto block2 = blockStore->tryCreate(this->key, blockstore::Data(1024));
auto block2 = blockStore->tryCreate(this->key, cpputils::Data(1024));
EXPECT_TRUE((bool)block);
EXPECT_FALSE((bool)block2);
}
TYPED_TEST_P(BlockStoreWithRandomKeysTest, CreateTwoBlocksWithSameKeyAndSecondNullSize) {
auto blockStore = this->fixture.createBlockStore();
auto block = blockStore->tryCreate(this->key, blockstore::Data(1024));
auto block = blockStore->tryCreate(this->key, cpputils::Data(1024));
block->flush();
auto block2 = blockStore->tryCreate(this->key, blockstore::Data(0));
auto block2 = blockStore->tryCreate(this->key, cpputils::Data(0));
EXPECT_TRUE((bool)block);
EXPECT_FALSE((bool)block2);
}
TYPED_TEST_P(BlockStoreWithRandomKeysTest, CreateTwoBlocksWithSameKeyAndBothNullSize) {
auto blockStore = this->fixture.createBlockStore();
auto block = blockStore->tryCreate(this->key, blockstore::Data(0));
auto block = blockStore->tryCreate(this->key, cpputils::Data(0));
block->flush();
auto block2 = blockStore->tryCreate(this->key, blockstore::Data(0));
auto block2 = blockStore->tryCreate(this->key, cpputils::Data(0));
EXPECT_TRUE((bool)block);
EXPECT_FALSE((bool)block2);
}

View File

@ -1,87 +0,0 @@
#include "DataBlockFixture.h"
#include <algorithm>
#include <cstring>
using std::min;
DataBlockFixture::DataBlockFixture(size_t size, long long int IV): _fileData(new char[size]), _size(size) {
fillFileWithRandomData(IV);
}
DataBlockFixture::~DataBlockFixture() {
delete[] _fileData;
}
void DataBlockFixture::fillFileWithRandomData(long long int IV) {
long long int val = IV;
for(size_t i=0; i<_size/sizeof(long long int); ++i) {
//MMIX linear congruential generator
val *= 6364136223846793005L;
val += 1442695040888963407;
reinterpret_cast<long long int*>(_fileData)[i] = val;
}
uint64_t alreadyWritten = (_size/sizeof(long long int))*sizeof(long long int);
val *= 6364136223846793005L;
val += 1442695040888963407;
char *remainingBytes = reinterpret_cast<char*>(&val);
//Fill remaining bytes
for(size_t i=0; i<_size-alreadyWritten; ++i) {
reinterpret_cast<char*>(_fileData)[alreadyWritten + i] = remainingBytes[i];
}
}
const char *DataBlockFixture::data() const {
return _fileData;
}
int DataBlockFixture::read(void *buf, size_t count, off_t offset) {
size_t realCount = min(count, _size - offset);
memcpy(buf, _fileData+offset, realCount);
return realCount;
}
size_t DataBlockFixture::size() const {
return _size;
}
bool DataBlockFixture::fileContentEqual(const char *content, size_t count, off_t offset) {
return 0 == memcmp(content, _fileData + offset, count);
}
DataBlockFixtureWriteable::DataBlockFixtureWriteable(size_t size, long long int IV)
:DataBlockFixture(size, IV), _originalSize(size) {
_originalFileData = new char[size];
memcpy(_originalFileData, _fileData, size);
}
DataBlockFixtureWriteable::~DataBlockFixtureWriteable() {
delete[] _originalFileData;
}
void DataBlockFixtureWriteable::write(const void *buf, size_t count, off_t offset) {
extendFileSizeIfNecessary(count + offset);
memcpy(_fileData+offset, buf, count);
}
void DataBlockFixtureWriteable::extendFileSizeIfNecessary(size_t size) {
if (size > _size) {
extendFileSize(size);
}
}
void DataBlockFixtureWriteable::extendFileSize(size_t size) {
char *newfile = new char[size];
memcpy(newfile, _fileData, _size);
delete[] _fileData;
_fileData = newfile;
_size = size;
}
bool DataBlockFixtureWriteable::sizeUnchanged() {
return _size == _originalSize;
}
bool DataBlockFixtureWriteable::regionUnchanged(off_t offset, size_t count) {
return 0 == memcmp(_fileData+offset, _originalFileData+offset, count);
}

View File

@ -1,47 +0,0 @@
#pragma once
#ifndef TEST_TESTUTILS_DATABLOCKFIXTURE_H_
#define TEST_TESTUTILS_DATABLOCKFIXTURE_H_
#include <cstdio>
class DataBlockFixture {
public:
DataBlockFixture(size_t size, long long int IV = 1);
virtual ~DataBlockFixture();
int read(void *buf, size_t count, off_t offset);
// Return true, iff the given data is equal to the data of the file at the given offset.
bool fileContentEqual(const char *content, size_t count, off_t offset);
const char *data() const;
size_t size() const;
protected:
char *_fileData;
size_t _size;
private:
void fillFileWithRandomData(long long int IV);
};
class DataBlockFixtureWriteable: public DataBlockFixture {
public:
DataBlockFixtureWriteable(size_t size, long long int IV = 1);
virtual ~DataBlockFixtureWriteable();
void write(const void *buf, size_t count, off_t offset);
bool sizeUnchanged();
bool regionUnchanged(off_t offset, size_t count);
private:
void extendFileSizeIfNecessary(size_t size);
void extendFileSize(size_t size);
char *_originalFileData;
size_t _originalSize;
};
#endif

View File

@ -1,5 +1,5 @@
#include "../../implementations/testfake/FakeBlockStore.h"
#include "../testutils/DataBlockFixture.h"
#include <messmer/cpp-utils/data/DataFixture.h>
#include "../../utils/BlockStoreUtils.h"
#include "google/gtest/gtest.h"
@ -11,6 +11,8 @@ using ::testing::Values;
using std::make_unique;
using std::unique_ptr;
using cpputils::Data;
using cpputils::DataFixture;
using namespace blockstore;
using namespace blockstore::utils;
@ -22,13 +24,13 @@ public:
unsigned int SIZE = 1024 * 1024;
BlockStoreUtilsTest():
ZEROES(SIZE),
dataFixture(SIZE),
dataFixture(DataFixture::generate(SIZE)),
blockStore(make_unique<FakeBlockStore>()) {
ZEROES.FillWithZeroes();
}
Data ZEROES;
DataBlockFixture dataFixture;
Data dataFixture;
unique_ptr<BlockStore> blockStore;
};

View File

@ -1,170 +0,0 @@
#include "../testutils/DataBlockFixture.h"
#include "../../utils/Data.h"
#include "../../utils/FileDoesntExistException.h"
#include "google/gtest/gtest.h"
#include "messmer/tempfile/src/TempFile.h"
#include <fstream>
using ::testing::Test;
using ::testing::WithParamInterface;
using ::testing::Values;
using tempfile::TempFile;
using std::ifstream;
using std::ofstream;
namespace bf = boost::filesystem;
using namespace blockstore;
class DataTest: public Test {
public:
bool DataIsZeroes(const Data &data) {
for (size_t i = 0; i != data.size(); ++ i) {
if (((char*)data.data())[i] != 0) {
return false;
}
}
return true;
}
void FillData(const DataBlockFixture &fillData, Data *data) {
ASSERT_EQ(fillData.size(), data->size());
std::memcpy(data->data(), fillData.data(), fillData.size());
}
void EXPECT_DATA_CORRECT(const DataBlockFixture &expectedData, const Data &data) {
ASSERT_EQ(expectedData.size(), data.size());
EXPECT_EQ(0, std::memcmp(expectedData.data(), data.data(), expectedData.size()));
}
};
class DataTestWithSizeParam: public DataTest, public WithParamInterface<size_t> {
public:
DataBlockFixture randomData;
DataTestWithSizeParam(): randomData(GetParam()) {}
void FillData(Data *data) {
DataTest::FillData(randomData, data);
}
void StoreData(const bf::path &filepath) {
ofstream file(filepath.c_str(), std::ios::binary | std::ios::trunc);
file.write(randomData.data(), randomData.size());
}
void EXPECT_STORED_FILE_DATA_CORRECT(const bf::path &filepath) {
EXPECT_EQ(randomData.size(), bf::file_size(filepath));
ifstream file(filepath.c_str(), std::ios::binary);
char *read_data = new char[randomData.size()];
file.read(read_data, randomData.size());
EXPECT_EQ(0, std::memcmp(randomData.data(), read_data, randomData.size()));
delete[] read_data;
}
void EXPECT_DATA_CORRECT(const Data &data) {
DataTest::EXPECT_DATA_CORRECT(randomData, data);
}
};
INSTANTIATE_TEST_CASE_P(DataTestWithSizeParam, DataTestWithSizeParam, Values(0, 1, 2, 1024, 4096, 10*1024*1024));
// Working on a large data area without a crash is a good indicator that we
// are actually working on memory that was validly allocated for us.
TEST_P(DataTestWithSizeParam, WriteAndCheck) {
Data data(GetParam());
FillData(&data);
EXPECT_DATA_CORRECT(data);
}
TEST_P(DataTestWithSizeParam, Size) {
Data data(GetParam());
EXPECT_EQ(GetParam(), data.size());
}
TEST_P(DataTestWithSizeParam, CheckStoredFile) {
Data data(GetParam());
FillData(&data);
TempFile file;
data.StoreToFile(file.path());
EXPECT_STORED_FILE_DATA_CORRECT(file.path());
}
TEST_P(DataTestWithSizeParam, CheckLoadedData) {
TempFile file;
StoreData(file.path());
Data data = Data::LoadFromFile(file.path());
EXPECT_DATA_CORRECT(data);
}
TEST_P(DataTestWithSizeParam, StoreDoesntChangeData) {
Data data(GetParam());
FillData(&data);
TempFile file;
data.StoreToFile(file.path());
EXPECT_DATA_CORRECT(data);
}
TEST_P(DataTestWithSizeParam, StoreAndLoad) {
Data data(GetParam());
FillData(&data);
TempFile file;
data.StoreToFile(file.path());
Data loaded_data = Data::LoadFromFile(file.path());
EXPECT_DATA_CORRECT(loaded_data);
}
TEST_P(DataTestWithSizeParam, Copy) {
Data data(GetParam());
FillData(&data);
Data copy = data.copy();
EXPECT_DATA_CORRECT(copy);
}
TEST_F(DataTest, InitializeWithZeroes) {
Data data(10*1024);
data.FillWithZeroes();
EXPECT_TRUE(DataIsZeroes(data));
}
TEST_F(DataTest, FillModifiedDataWithZeroes) {
Data data(10*1024);
DataBlockFixture randomData(10*1024);
FillData(randomData, &data);
EXPECT_FALSE(DataIsZeroes(data));
data.FillWithZeroes();
EXPECT_TRUE(DataIsZeroes(data));
}
//Needs 64bit for representation. This value isn't in the size param list, because the list is also used for read/write checks.
TEST_F(DataTest, LargesizeSize) {
size_t size = 10L*1024*1024*1024;
Data data(size);
EXPECT_EQ(size, data.size());
}
TEST_F(DataTest, LoadingNonexistingFile) {
TempFile file(false); // Pass false to constructor, so the tempfile is not created
EXPECT_THROW(
Data::LoadFromFile(file.path()),
FileDoesntExistException
);
}

View File

@ -1,144 +0,0 @@
#include "../testutils/DataBlockFixture.h"
#include "../../utils/Data.h"
#include "../../utils/Key.h"
#include "google/gtest/gtest.h"
using ::testing::Test;
using ::testing::WithParamInterface;
using ::testing::Values;
using std::string;
using namespace blockstore;
//TODO Test blockstore::FixedSizeData instead of blockstore::Key
class KeyTest: public Test {
public:
const string KEY1_AS_STRING = "1491BB4932A389EE14BC7090AC772972";
const string KEY2_AS_STRING = "272EE5517627CFA147A971A8E6E747E0";
const DataBlockFixture KEY3_AS_BINARY;
const DataBlockFixture KEY4_AS_BINARY;
KeyTest() : KEY3_AS_BINARY(Key::BINARY_LENGTH, 1), KEY4_AS_BINARY(Key::BINARY_LENGTH, 2) {}
void EXPECT_DATA_EQ(const DataBlockFixture &expected, const Data &actual) {
EXPECT_EQ(expected.size(), actual.size());
EXPECT_EQ(0, std::memcmp(expected.data(), actual.data(), expected.size()));
}
};
TEST_F(KeyTest, CanGenerateRandomKeysWithoutCrashing) {
Key result = Key::CreateRandom();
}
TEST_F(KeyTest, CreatedRandomKeysHaveCorrectLength) {
Key key = Key::CreateRandom();
EXPECT_EQ(Key::STRING_LENGTH, key.ToString().size());
}
TEST_F(KeyTest, EqualsTrue) {
Key key1_1 = Key::FromString(KEY1_AS_STRING);
Key key1_2 = Key::FromString(KEY1_AS_STRING);
EXPECT_TRUE(key1_1 == key1_2);
EXPECT_TRUE(key1_2 == key1_1);
}
TEST_F(KeyTest, EqualsFalse) {
Key key1_1 = Key::FromString(KEY1_AS_STRING);
Key key2_1 = Key::FromString(KEY2_AS_STRING);
EXPECT_FALSE(key1_1 == key2_1);
EXPECT_FALSE(key2_1 == key1_1);
}
TEST_F(KeyTest, NotEqualsFalse) {
Key key1_1 = Key::FromString(KEY1_AS_STRING);
Key key1_2 = Key::FromString(KEY1_AS_STRING);
EXPECT_FALSE(key1_1 != key1_2);
EXPECT_FALSE(key1_2 != key1_1);
}
TEST_F(KeyTest, NotEqualsTrue) {
Key key1_1 = Key::FromString(KEY1_AS_STRING);
Key key2_1 = Key::FromString(KEY2_AS_STRING);
EXPECT_TRUE(key1_1 != key2_1);
EXPECT_TRUE(key2_1 != key1_1);
}
class KeyTestWithStringKeyParam: public KeyTest, public WithParamInterface<string> {};
INSTANTIATE_TEST_CASE_P(KeyTestWithStringKeyParam, KeyTestWithStringKeyParam, Values("2898B4B8A13CA63CBE0F0278CCE465DB", "6FFEBAD90C0DAA2B79628F0627CE9841"));
TEST_P(KeyTestWithStringKeyParam, FromAndToString) {
Key key = Key::FromString(GetParam());
EXPECT_EQ(GetParam(), key.ToString());
}
TEST_P(KeyTestWithStringKeyParam, ToAndFromString) {
Key key = Key::FromString(GetParam());
Key key2 = Key::FromString(key.ToString());
EXPECT_EQ(key, key2);
}
class KeyTestWithBinaryKeyParam: public KeyTest, public WithParamInterface<const DataBlockFixture*> {
public:
static const DataBlockFixture VALUE1;
static const DataBlockFixture VALUE2;
};
const DataBlockFixture KeyTestWithBinaryKeyParam::VALUE1(Key::BINARY_LENGTH, 3);
const DataBlockFixture KeyTestWithBinaryKeyParam::VALUE2(Key::BINARY_LENGTH, 4);
INSTANTIATE_TEST_CASE_P(KeyTestWithBinaryKeyParam, KeyTestWithBinaryKeyParam, Values(&KeyTestWithBinaryKeyParam::VALUE1, &KeyTestWithBinaryKeyParam::VALUE2));
TEST_P(KeyTestWithBinaryKeyParam, FromAndToBinary) {
Key key = Key::FromBinary((uint8_t*)GetParam()->data());
Data keydata(Key::BINARY_LENGTH);
key.ToBinary(keydata.data());
EXPECT_DATA_EQ(*GetParam(), keydata);
}
TEST_P(KeyTestWithBinaryKeyParam, ToAndFromBinary) {
Key key = Key::FromBinary((uint8_t*)GetParam()->data());
Data stored(Key::BINARY_LENGTH);
key.ToBinary(stored.data());
Key loaded = Key::FromBinary(stored.data());
EXPECT_EQ(key, loaded);
}
class KeyTestWithKeyParam: public KeyTest, public WithParamInterface<Key> {};
INSTANTIATE_TEST_CASE_P(KeyTestWithKeyParam, KeyTestWithKeyParam, Values(Key::FromString("2898B4B8A13CA63CBE0F0278CCE465DB"), Key::FromString("6FFEBAD90C0DAA2B79628F0627CE9841")));
TEST_P(KeyTestWithKeyParam, CopyConstructor) {
Key copy(GetParam());
EXPECT_EQ(GetParam(), copy);
}
TEST_F(KeyTest, CopyConstructorDoesntChangeSource) {
Key key1 = Key::FromString(KEY1_AS_STRING);
Key key2(key1);
EXPECT_EQ(KEY1_AS_STRING, key1.ToString());
}
TEST_P(KeyTestWithKeyParam, IsEqualAfterAssignment1) {
Key key2 = Key::FromString(KEY2_AS_STRING);
EXPECT_NE(GetParam(), key2);
key2 = GetParam();
EXPECT_EQ(GetParam(), key2);
}
TEST_F(KeyTest, AssignmentDoesntChangeSource) {
Key key1 = Key::FromString(KEY1_AS_STRING);
Key key2 = Key::FromString(KEY2_AS_STRING);
key2 = key1;
EXPECT_EQ(KEY1_AS_STRING, key1.ToString());
}
// This tests that a Key object is very lightweight
// (we will often pass keys around)
TEST_F(KeyTest, KeyIsLightweightObject) {
EXPECT_EQ(Key::BINARY_LENGTH, sizeof(Key));
}

View File

@ -1,10 +1,11 @@
#include "../interface/BlockStore.h"
#include "BlockStoreUtils.h"
#include "Data.h"
#include <messmer/cpp-utils/data/Data.h>
#include <memory>
#include <cassert>
using std::unique_ptr;
using cpputils::Data;
namespace blockstore {
namespace utils {

View File

@ -1,100 +0,0 @@
#include "Data.h"
#include "FileDoesntExistException.h"
#include <stdexcept>
#include <fstream>
using std::istream;
using std::ofstream;
using std::ifstream;
using std::ios;
using std::unique_ptr;
using std::make_unique;
namespace bf = boost::filesystem;
namespace blockstore {
Data::Data(size_t size)
: _size(size), _data(std::malloc(size)) {
if (nullptr == _data) {
throw std::bad_alloc();
}
}
Data::Data(Data &&rhs)
: _size(rhs._size), _data(rhs._data) {
// Make rhs invalid, so the memory doesn't get freed in its destructor.
rhs._data = nullptr;
}
Data::~Data() {
std::free(_data);
_data = nullptr;
}
Data Data::copy() const {
Data copy(_size);
std::memcpy(copy._data, _data, _size);
return copy;
}
void *Data::data() {
return const_cast<void*>(const_cast<const Data*>(this)->data());
}
const void *Data::data() const {
return _data;
}
size_t Data::size() const {
return _size;
}
Data &Data::FillWithZeroes() {
std::memset(_data, 0, _size);
return *this;
}
void Data::StoreToFile(const bf::path &filepath) const {
ofstream file(filepath.c_str(), ios::binary | ios::trunc);
file.write((const char*)_data, _size);
}
Data Data::LoadFromFile(const bf::path &filepath) {
ifstream file(filepath.c_str(), ios::binary);
_assertFileExists(file, filepath);
size_t size = _getStreamSize(file);
Data result(size);
result._readFromStream(file);
return result;
}
void Data::_assertFileExists(const ifstream &file, const bf::path &filepath) {
if (!file.good()) {
throw FileDoesntExistException(filepath);
}
}
size_t Data::_getStreamSize(istream &stream) {
auto current_pos = stream.tellg();
//Retrieve length
stream.seekg(0, stream.end);
auto endpos = stream.tellg();
//Restore old position
stream.seekg(current_pos, stream.beg);
return endpos - current_pos;
}
void Data::_readFromStream(istream &stream) {
stream.read((char*)_data, _size);
}
bool operator==(const Data &lhs, const Data &rhs) {
return lhs.size() == rhs.size() && 0 == memcmp(lhs.data(), rhs.data(), lhs.size());
}
}

View File

@ -1,46 +0,0 @@
#pragma once
#ifndef BLOCKSTORE_IMPLEMENTATIONS_ONDISK_DATA_H_
#define BLOCKSTORE_IMPLEMENTATIONS_ONDISK_DATA_H_
#include <cstdlib>
#include <boost/filesystem/path.hpp>
#include <messmer/cpp-utils/macros.h>
#include <memory>
namespace blockstore {
class Data {
public:
explicit Data(size_t size);
Data(Data &&rhs); // move constructor
virtual ~Data();
Data copy() const;
void *data();
const void *data() const;
size_t size() const;
Data &FillWithZeroes();
void StoreToFile(const boost::filesystem::path &filepath) const;
static Data LoadFromFile(const boost::filesystem::path &filepath);
private:
size_t _size;
void *_data;
static void _assertFileExists(const std::ifstream &file, const boost::filesystem::path &filepath);
static size_t _getStreamSize(std::istream &stream);
void _readFromStream(std::istream &stream);
DISALLOW_COPY_AND_ASSIGN(Data);
};
//TODO Test operator==
bool operator==(const Data &lhs, const Data &rhs);
}
#endif

View File

@ -10,7 +10,7 @@ namespace blockstore {
class FileDoesntExistException: public std::runtime_error {
public:
FileDoesntExistException(const boost::filesystem::path &filepath);
explicit FileDoesntExistException(const boost::filesystem::path &filepath);
virtual ~FileDoesntExistException();
};

View File

@ -1,112 +0,0 @@
#pragma once
#ifndef BLOCKSTORE_UTILS_data_H_
#define BLOCKSTORE_UTILS_data_H_
#include <cryptopp/cryptopp/hex.h>
#include <cryptopp/cryptopp/osrng.h>
#include <string>
#include <cstring>
namespace blockstore {
template<int SIZE>
class FixedSizeData {
public:
//Non-virtual destructor because we want objects to be small
~FixedSizeData() {}
static constexpr unsigned int BINARY_LENGTH = SIZE;
static constexpr unsigned int STRING_LENGTH = 2 * BINARY_LENGTH; // Hex encoding
static FixedSizeData<SIZE> CreateRandom();
static FixedSizeData<SIZE> FromString(const std::string &data);
std::string ToString() const;
static FixedSizeData<SIZE> FromBinary(const void *source);
void ToBinary(void *target) const;
const unsigned char *data() const;
private:
FixedSizeData() {}
static CryptoPP::AutoSeededRandomPool &RandomPool();
unsigned char _data[BINARY_LENGTH];
};
template<int SIZE> bool operator==(const FixedSizeData<SIZE> &lhs, const FixedSizeData<SIZE> &rhs);
template<int SIZE> bool operator!=(const FixedSizeData<SIZE> &lhs, const FixedSizeData<SIZE> &rhs);
// ----- Implementation -----
template<int SIZE> constexpr unsigned int FixedSizeData<SIZE>::BINARY_LENGTH;
template<int SIZE> constexpr unsigned int FixedSizeData<SIZE>::STRING_LENGTH;
template<int SIZE>
CryptoPP::AutoSeededRandomPool &FixedSizeData<SIZE>::RandomPool() {
static CryptoPP::AutoSeededRandomPool singleton;
return singleton;
}
template<int SIZE>
FixedSizeData<SIZE> FixedSizeData<SIZE>::CreateRandom() {
FixedSizeData<SIZE> result;
RandomPool().GenerateBlock(result._data, BINARY_LENGTH);
return result;
}
template<int SIZE>
FixedSizeData<SIZE> FixedSizeData<SIZE>::FromString(const std::string &data) {
assert(data.size() == STRING_LENGTH);
FixedSizeData<SIZE> result;
CryptoPP::StringSource(data, true,
new CryptoPP::HexDecoder(
new CryptoPP::ArraySink(result._data, BINARY_LENGTH)
)
);
return result;
}
template<int SIZE>
std::string FixedSizeData<SIZE>::ToString() const {
std::string result;
CryptoPP::ArraySource(_data, BINARY_LENGTH, true,
new CryptoPP::HexEncoder(
new CryptoPP::StringSink(result)
)
);
assert(result.size() == STRING_LENGTH);
return result;
}
template<int SIZE>
const unsigned char *FixedSizeData<SIZE>::data() const {
return _data;
}
template<int SIZE>
void FixedSizeData<SIZE>::ToBinary(void *target) const {
std::memcpy(target, _data, BINARY_LENGTH);
}
template<int SIZE>
FixedSizeData<SIZE> FixedSizeData<SIZE>::FromBinary(const void *source) {
FixedSizeData<SIZE> result;
std::memcpy(result._data, source, BINARY_LENGTH);
return result;
}
template<int SIZE>
bool operator==(const FixedSizeData<SIZE> &lhs, const FixedSizeData<SIZE> &rhs) {
return 0 == std::memcmp(lhs.data(), rhs.data(), FixedSizeData<SIZE>::BINARY_LENGTH);
}
template<int SIZE>
bool operator!=(const FixedSizeData<SIZE> &lhs, const FixedSizeData<SIZE> &rhs) {
return !operator==(lhs, rhs);
}
}
#endif

View File

@ -3,12 +3,12 @@
#define BLOCKSTORE_UTILS_KEY_H_
#include <string>
#include "FixedSizeData.h"
#include <messmer/cpp-utils/data/FixedSizeData.h>
namespace blockstore {
// A key here is NOT a key for encryption, but a key as used in key->value mappings ("access handle for a block").
using Key = FixedSizeData<16>;
using Key = cpputils::FixedSizeData<16>;
}