Merge from origin
This commit is contained in:
commit
e24f825259
@ -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
37
appveyor.yml
Normal 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"
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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
|
@ -1,2 +0,0 @@
|
||||
#include "CacheEntry.h"
|
||||
#include "../../interface/Block.h"
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using std::unique_ptr;
|
||||
using std::make_unique;
|
||||
using cpputils::Data;
|
||||
|
||||
namespace blockstore {
|
||||
namespace caching {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
1
implementations/caching/cache/Cache.cpp
vendored
Normal file
1
implementations/caching/cache/Cache.cpp
vendored
Normal file
@ -0,0 +1 @@
|
||||
#include "Cache.h"
|
86
implementations/caching/cache/Cache.h
vendored
Normal file
86
implementations/caching/cache/Cache.h
vendored
Normal 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
|
1
implementations/caching/cache/CacheEntry.cpp
vendored
Normal file
1
implementations/caching/cache/CacheEntry.cpp
vendored
Normal file
@ -0,0 +1 @@
|
||||
#include "CacheEntry.h"
|
@ -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();
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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
105
implementations/caching/cache/QueueMap.h
vendored
Normal 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
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
#include "EncryptionKey.h"
|
@ -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
|
37
implementations/encrypted/ciphers/AES256_CFB.cpp
Normal file
37
implementations/encrypted/ciphers/AES256_CFB.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
39
implementations/encrypted/ciphers/AES256_CFB.h
Normal file
39
implementations/encrypted/ciphers/AES256_CFB.h
Normal 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
|
61
implementations/encrypted/ciphers/AES256_GCM.cpp
Normal file
61
implementations/encrypted/ciphers/AES256_GCM.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
39
implementations/encrypted/ciphers/AES256_GCM.h
Normal file
39
implementations/encrypted/ciphers/AES256_GCM.h
Normal 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
|
33
implementations/encrypted/ciphers/Cipher.h
Normal file
33
implementations/encrypted/ciphers/Cipher.h
Normal 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
|
@ -9,6 +9,7 @@ using std::ostream;
|
||||
using std::ifstream;
|
||||
using std::ofstream;
|
||||
using std::ios;
|
||||
using cpputils::Data;
|
||||
|
||||
namespace blockstore {
|
||||
namespace inmemory {
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -4,6 +4,7 @@
|
||||
using std::unique_ptr;
|
||||
using std::make_unique;
|
||||
using std::string;
|
||||
using cpputils::Data;
|
||||
|
||||
namespace bf = boost::filesystem;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)) {
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ using std::ifstream;
|
||||
using std::ofstream;
|
||||
using std::ios;
|
||||
using std::string;
|
||||
using cpputils::Data;
|
||||
|
||||
namespace blockstore {
|
||||
namespace testfake {
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
36
test/implementations/caching/cache/CacheTest_MoveConstructor.cpp
vendored
Normal file
36
test/implementations/caching/cache/CacheTest_MoveConstructor.cpp
vendored
Normal 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);
|
||||
}
|
133
test/implementations/caching/cache/CacheTest_PushAndPop.cpp
vendored
Normal file
133
test/implementations/caching/cache/CacheTest_PushAndPop.cpp
vendored
Normal 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());
|
||||
}
|
63
test/implementations/caching/cache/PeriodicTaskTest.cpp
vendored
Normal file
63
test/implementations/caching/cache/PeriodicTaskTest.cpp
vendored
Normal 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);
|
||||
}
|
87
test/implementations/caching/cache/QueueMapTest_MemoryLeak.cpp
vendored
Normal file
87
test/implementations/caching/cache/QueueMapTest_MemoryLeak.cpp
vendored
Normal 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);
|
||||
}
|
51
test/implementations/caching/cache/QueueMapTest_MoveConstructor.cpp
vendored
Normal file
51
test/implementations/caching/cache/QueueMapTest_MoveConstructor.cpp
vendored
Normal 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);
|
||||
}
|
34
test/implementations/caching/cache/QueueMapTest_Peek.cpp
vendored
Normal file
34
test/implementations/caching/cache/QueueMapTest_Peek.cpp
vendored
Normal 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());
|
||||
}
|
79
test/implementations/caching/cache/QueueMapTest_Size.cpp
vendored
Normal file
79
test/implementations/caching/cache/QueueMapTest_Size.cpp
vendored
Normal 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());
|
||||
}
|
150
test/implementations/caching/cache/QueueMapTest_Values.cpp
vendored
Normal file
150
test/implementations/caching/cache/QueueMapTest_Values.cpp
vendored
Normal 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);
|
||||
);
|
||||
}
|
13
test/implementations/caching/cache/testutils/CacheTest.cpp
vendored
Normal file
13
test/implementations/caching/cache/testutils/CacheTest.cpp
vendored
Normal 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();
|
||||
}
|
25
test/implementations/caching/cache/testutils/CacheTest.h
vendored
Normal file
25
test/implementations/caching/cache/testutils/CacheTest.h
vendored
Normal 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
|
3
test/implementations/caching/cache/testutils/CopyableMovableValueType.cpp
vendored
Normal file
3
test/implementations/caching/cache/testutils/CopyableMovableValueType.cpp
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
#include "CopyableMovableValueType.h"
|
||||
|
||||
int CopyableMovableValueType::numCopyConstructorCalled = 0;
|
23
test/implementations/caching/cache/testutils/CopyableMovableValueType.h
vendored
Normal file
23
test/implementations/caching/cache/testutils/CopyableMovableValueType.h
vendored
Normal 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
|
3
test/implementations/caching/cache/testutils/MinimalKeyType.cpp
vendored
Normal file
3
test/implementations/caching/cache/testutils/MinimalKeyType.cpp
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
#include "MinimalKeyType.h"
|
||||
|
||||
int MinimalKeyType::instances = 0;
|
47
test/implementations/caching/cache/testutils/MinimalKeyType.h
vendored
Normal file
47
test/implementations/caching/cache/testutils/MinimalKeyType.h
vendored
Normal 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
|
3
test/implementations/caching/cache/testutils/MinimalValueType.cpp
vendored
Normal file
3
test/implementations/caching/cache/testutils/MinimalValueType.cpp
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
#include "MinimalValueType.h"
|
||||
|
||||
int MinimalValueType::instances = 0;
|
44
test/implementations/caching/cache/testutils/MinimalValueType.h
vendored
Normal file
44
test/implementations/caching/cache/testutils/MinimalValueType.h
vendored
Normal 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
|
45
test/implementations/caching/cache/testutils/QueueMapTest.cpp
vendored
Normal file
45
test/implementations/caching/cache/testutils/QueueMapTest.cpp
vendored
Normal 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();
|
||||
}
|
32
test/implementations/caching/cache/testutils/QueueMapTest.h
vendored
Normal file
32
test/implementations/caching/cache/testutils/QueueMapTest.h
vendored
Normal 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
|
226
test/implementations/encrypted/CipherTest.cpp
Normal file
226
test/implementations/encrypted/CipherTest.cpp
Normal 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);
|
@ -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
|
@ -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>);
|
@ -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());
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
#include "FakeAuthenticatedCipher.h"
|
||||
|
||||
constexpr unsigned int FakeKey::BINARY_LENGTH;
|
@ -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
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
}
|
@ -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));
|
||||
}
|
@ -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 {
|
||||
|
100
utils/Data.cpp
100
utils/Data.cpp
@ -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());
|
||||
}
|
||||
|
||||
}
|
46
utils/Data.h
46
utils/Data.h
@ -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
|
@ -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();
|
||||
};
|
||||
|
||||
|
@ -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
|
@ -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>;
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user