* Block versions don't have to be globally nondecreasing, but only per client id. This solves potential synchronization conflicts in a multi-client setting.

* Use cpputils::Serializer and cpputils::Deserializer instead of std::ifstream for storing/loading the block version list
This commit is contained in:
Sebastian Messmer 2016-06-22 11:10:02 -07:00
parent de692c1ee4
commit ea3de7360c
7 changed files with 168 additions and 100 deletions

View File

@ -0,0 +1,33 @@
#pragma once
#ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_VERSIONCOUNTING_CLIENTIDANDBLOCKKEY_H_
#define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_VERSIONCOUNTING_CLIENTIDANDBLOCKKEY_H_
#include <utility>
namespace blockstore {
namespace versioncounting {
struct ClientIdAndBlockKey {
uint32_t clientId;
Key blockKey;
};
}
}
// Allow using it in std::unordered_set / std::unordered_map
namespace std {
template<> struct hash<blockstore::versioncounting::ClientIdAndBlockKey> {
size_t operator()(const blockstore::versioncounting::ClientIdAndBlockKey &ref) const {
return std::hash<uint32_t>()(ref.clientId) ^ std::hash<blockstore::Key>()(ref.blockKey);
}
};
template<> struct equal_to<blockstore::versioncounting::ClientIdAndBlockKey> {
size_t operator()(const blockstore::versioncounting::ClientIdAndBlockKey &lhs, const blockstore::versioncounting::ClientIdAndBlockKey &rhs) const {
return lhs.clientId == rhs.clientId && lhs.blockKey == rhs.blockKey;
}
};
}
#endif

View File

@ -1,6 +1,8 @@
#include <fstream> #include <fstream>
#include <cpp-utils/random/Random.h> #include <cpp-utils/random/Random.h>
#include "KnownBlockVersions.h" #include "KnownBlockVersions.h"
#include <cpp-utils/data/Serializer.h>
#include <cpp-utils/data/Deserializer.h>
namespace bf = boost::filesystem; namespace bf = boost::filesystem;
using std::unordered_map; using std::unordered_map;
@ -8,7 +10,10 @@ using std::pair;
using std::string; using std::string;
using boost::optional; using boost::optional;
using boost::none; using boost::none;
using cpputils::Data;
using cpputils::Random; using cpputils::Random;
using cpputils::Serializer;
using cpputils::Deserializer;
namespace blockstore { namespace blockstore {
namespace versioncounting { namespace versioncounting {
@ -31,10 +36,10 @@ KnownBlockVersions::~KnownBlockVersions() {
} }
} }
bool KnownBlockVersions::checkAndUpdateVersion(const Key &key, uint64_t version) { bool KnownBlockVersions::checkAndUpdateVersion(uint32_t clientId, const Key &key, uint64_t version) {
ASSERT(_valid, "Object not valid due to a std::move"); ASSERT(_valid, "Object not valid due to a std::move");
uint64_t &found = _knownVersions[key]; // If the entry doesn't exist, this creates it with value 0. uint64_t &found = _knownVersions[{clientId, key}]; // If the entry doesn't exist, this creates it with value 0.
if (found > version) { if (found > version) {
return false; return false;
} }
@ -44,73 +49,59 @@ bool KnownBlockVersions::checkAndUpdateVersion(const Key &key, uint64_t version)
} }
void KnownBlockVersions::updateVersion(const Key &key, uint64_t version) { void KnownBlockVersions::updateVersion(const Key &key, uint64_t version) {
if (!checkAndUpdateVersion(key, version)) { if (!checkAndUpdateVersion(_myClientId, key, version)) {
throw std::logic_error("Tried to decrease block version"); throw std::logic_error("Tried to decrease block version");
} }
} }
void KnownBlockVersions::_loadStateFile() { void KnownBlockVersions::_loadStateFile() {
std::ifstream file(_stateFilePath.native().c_str()); optional<Data> file = Data::LoadFromFile(_stateFilePath);
if (!file.good()) { if (file == none) {
// File doesn't exist means we loaded empty state. Assign a random client id. // File doesn't exist means we loaded empty state. Assign a random client id.
_myClientId = *reinterpret_cast<uint32_t*>(Random::PseudoRandom().getFixedSize<sizeof(uint32_t)>().data()); _myClientId = *reinterpret_cast<uint32_t*>(Random::PseudoRandom().getFixedSize<sizeof(uint32_t)>().data());
return; return;
} }
_checkHeader(&file);
file.read((char*)&_myClientId, sizeof(_myClientId)); Deserializer deserializer(&*file);
ASSERT(file.good(), "Error reading file"); if (HEADER != deserializer.readString()) {
uint64_t numEntries; throw std::runtime_error("Invalid local state: Invalid integrity file header.");
file.read((char*)&numEntries, sizeof(numEntries)); }
ASSERT(file.good(), "Error reading file"); _myClientId = deserializer.readUint32();
uint64_t numEntries = deserializer.readUint64();
_knownVersions.clear(); _knownVersions.clear();
_knownVersions.reserve(static_cast<uint64_t>(1.2 * numEntries)); // Reserve for factor 1.2 more, so the file system doesn't immediately have to resize it on the first new block. _knownVersions.reserve(static_cast<uint64_t>(1.2 * numEntries)); // Reserve for factor 1.2 more, so the file system doesn't immediately have to resize it on the first new block.
for (uint64_t i = 0 ; i < numEntries; ++i) { for (uint64_t i = 0 ; i < numEntries; ++i) {
auto entry = _readEntry(&file); auto entry = _readEntry(&deserializer);
_knownVersions.insert(entry); _knownVersions.insert(entry);
} }
_checkIsEof(&file); deserializer.finished();
}; };
void KnownBlockVersions::_checkHeader(std::ifstream *file) { pair<ClientIdAndBlockKey, uint64_t> KnownBlockVersions::_readEntry(Deserializer *deserializer) {
char actualHeader[HEADER.size()]; uint32_t clientId = deserializer->readUint32();
file->read(actualHeader, HEADER.size()); Key blockKey = deserializer->readFixedSizeData<Key::BINARY_LENGTH>();
ASSERT(file->good(), "Error reading file"); uint64_t version = deserializer->readUint64();
if (HEADER != string(actualHeader, HEADER.size())) {
throw std::runtime_error("Invalid local state: Invalid integrity file header.");
}
}
pair<Key, uint64_t> KnownBlockVersions::_readEntry(std::ifstream *file) { return {{clientId, blockKey}, version};
pair<Key, uint64_t> result(Key::Null(), 0);
file->read((char*)result.first.data(), result.first.BINARY_LENGTH);
ASSERT(file->good(), "Error reading file");
file->read((char*)&result.second, sizeof(result.second));
ASSERT(file->good(), "Error reading file");
return result;
}; };
void KnownBlockVersions::_checkIsEof(std::ifstream *file) {
char dummy;
file->read(&dummy, sizeof(dummy));
if (!file->eof()) {
throw std::runtime_error("There are more entries in the file than advertised");
}
}
void KnownBlockVersions::_saveStateFile() const { void KnownBlockVersions::_saveStateFile() const {
std::ofstream file(_stateFilePath.native().c_str());
file.write(HEADER.c_str(), HEADER.size());
file.write((char*)&_myClientId, sizeof(_myClientId));
uint64_t numEntries = _knownVersions.size(); uint64_t numEntries = _knownVersions.size();
file.write((char*)&numEntries, sizeof(numEntries));
Serializer serializer(Serializer::StringSize(HEADER) + sizeof(uint32_t) + sizeof(uint64_t) + numEntries * (sizeof(uint32_t) + Key::BINARY_LENGTH + sizeof(uint64_t)));
serializer.writeString(HEADER);
serializer.writeUint32(_myClientId);
serializer.writeUint64(numEntries);
for (const auto &entry : _knownVersions) { for (const auto &entry : _knownVersions) {
file.write((char*)entry.first.data(), entry.first.BINARY_LENGTH); serializer.writeUint32(entry.first.clientId);
file.write((char*)&entry.second, sizeof(entry.second)); serializer.writeFixedSizeData<Key::BINARY_LENGTH>(entry.first.blockKey);
serializer.writeUint64(entry.second);
} }
serializer.finished().StoreToFile(_stateFilePath);
} }
uint32_t KnownBlockVersions::myClientId() const { uint32_t KnownBlockVersions::myClientId() const {

View File

@ -6,6 +6,8 @@
#include <blockstore/utils/Key.h> #include <blockstore/utils/Key.h>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include "ClientIdAndBlockKey.h"
#include <cpp-utils/data/Deserializer.h>
namespace blockstore { namespace blockstore {
namespace versioncounting { namespace versioncounting {
@ -17,14 +19,14 @@ namespace blockstore {
~KnownBlockVersions(); ~KnownBlockVersions();
__attribute__((warn_unused_result)) __attribute__((warn_unused_result))
bool checkAndUpdateVersion(const Key &key, uint64_t version); bool checkAndUpdateVersion(uint32_t clientId, const Key &key, uint64_t version);
void updateVersion(const Key &key, uint64_t version); void updateVersion(const Key &key, uint64_t version);
uint32_t myClientId() const; uint32_t myClientId() const;
private: private:
std::unordered_map<Key, uint64_t> _knownVersions; std::unordered_map<ClientIdAndBlockKey, uint64_t> _knownVersions;
boost::filesystem::path _stateFilePath; boost::filesystem::path _stateFilePath;
uint32_t _myClientId; uint32_t _myClientId;
bool _valid; bool _valid;
@ -32,9 +34,7 @@ namespace blockstore {
static const std::string HEADER; static const std::string HEADER;
void _loadStateFile(); void _loadStateFile();
static void _checkHeader(std::ifstream *file); static std::pair<ClientIdAndBlockKey, uint64_t> _readEntry(cpputils::Deserializer *deserializer);
static std::pair<Key, uint64_t> _readEntry(std::ifstream *file);
static void _checkIsEof(std::ifstream *file);
void _saveStateFile() const; void _saveStateFile() const;
DISALLOW_COPY_AND_ASSIGN(KnownBlockVersions); DISALLOW_COPY_AND_ASSIGN(KnownBlockVersions);

View File

@ -54,7 +54,8 @@ private:
static cpputils::Data _prependHeaderToData(uint32_t myClientId, uint64_t version, cpputils::Data data); static cpputils::Data _prependHeaderToData(uint32_t myClientId, uint64_t version, cpputils::Data data);
static void _checkFormatHeader(const cpputils::Data &data); static void _checkFormatHeader(const cpputils::Data &data);
static uint64_t _readVersion(const cpputils::Data &data); static uint64_t _readVersion(const cpputils::Data &data);
static bool _versionIsNondecreasing(const Key &key, uint64_t version, KnownBlockVersions *knownBlockVersions); static uint32_t _readClientId(const cpputils::Data &data);
static bool _versionIsNondecreasing(uint32_t clientId, const Key &key, uint64_t version, KnownBlockVersions *knownBlockVersions);
// This header is prepended to blocks to allow future versions to have compatibility. // This header is prepended to blocks to allow future versions to have compatibility.
static constexpr uint16_t FORMAT_VERSION_HEADER = 0; static constexpr uint16_t FORMAT_VERSION_HEADER = 0;
@ -93,8 +94,9 @@ inline boost::optional<cpputils::unique_ref<VersionCountingBlock>> VersionCounti
cpputils::Data data(baseBlock->size()); cpputils::Data data(baseBlock->size());
std::memcpy(data.data(), baseBlock->data(), data.size()); std::memcpy(data.data(), baseBlock->data(), data.size());
_checkFormatHeader(data); _checkFormatHeader(data);
uint32_t lastClientId = _readClientId(data);
uint64_t version = _readVersion(data); uint64_t version = _readVersion(data);
if(!_versionIsNondecreasing(baseBlock->key(), version, knownBlockVersions)) { if(!_versionIsNondecreasing(lastClientId, baseBlock->key(), version, knownBlockVersions)) {
//The stored key in the block data is incorrect - an attacker might have exchanged the contents with the encrypted data from a different block //The stored key in the block data is incorrect - an attacker might have exchanged the contents with the encrypted data from a different block
cpputils::logging::LOG(cpputils::logging::WARN) << "Decrypting block " << baseBlock->key().ToString() << " failed due to wrong version number. Was the block rolled back by an attacker?"; cpputils::logging::LOG(cpputils::logging::WARN) << "Decrypting block " << baseBlock->key().ToString() << " failed due to wrong version number. Was the block rolled back by an attacker?";
return boost::none; return boost::none;
@ -108,14 +110,20 @@ inline void VersionCountingBlock::_checkFormatHeader(const cpputils::Data &data)
} }
} }
inline uint32_t VersionCountingBlock::_readClientId(const cpputils::Data &data) {
uint32_t clientId;
std::memcpy(&clientId, data.dataOffset(sizeof(FORMAT_VERSION_HEADER)), sizeof(clientId));
return clientId;
}
inline uint64_t VersionCountingBlock::_readVersion(const cpputils::Data &data) { inline uint64_t VersionCountingBlock::_readVersion(const cpputils::Data &data) {
uint64_t version; uint64_t version;
std::memcpy(&version, data.dataOffset(sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t)), sizeof(version)); std::memcpy(&version, data.dataOffset(sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t)), sizeof(version));
return version; return version;
} }
inline bool VersionCountingBlock::_versionIsNondecreasing(const Key &key, uint64_t version, KnownBlockVersions *knownBlockVersions) { inline bool VersionCountingBlock::_versionIsNondecreasing(uint32_t clientId, const Key &key, uint64_t version, KnownBlockVersions *knownBlockVersions) {
return knownBlockVersions->checkAndUpdateVersion(key, version); return knownBlockVersions->checkAndUpdateVersion(clientId, key, version);
} }
inline VersionCountingBlock::VersionCountingBlock(cpputils::unique_ref<Block> baseBlock, cpputils::Data dataWithHeader, uint64_t version, KnownBlockVersions *knownBlockVersions) inline VersionCountingBlock::VersionCountingBlock(cpputils::unique_ref<Block> baseBlock, cpputils::Data dataWithHeader, uint64_t version, KnownBlockVersions *knownBlockVersions)

View File

@ -5,6 +5,7 @@
#include "Data.h" #include "Data.h"
#include "../macros.h" #include "../macros.h"
#include "../assert/assert.h" #include "../assert/assert.h"
#include "FixedSizeData.h"
namespace cpputils { namespace cpputils {
class Deserializer final { class Deserializer final {
@ -21,6 +22,7 @@ namespace cpputils {
int64_t readInt64(); int64_t readInt64();
std::string readString(); std::string readString();
Data readData(); Data readData();
template<size_t SIZE> FixedSizeData<SIZE> readFixedSizeData();
Data readTailData(); Data readTailData();
void finished(); void finished();
@ -28,6 +30,7 @@ namespace cpputils {
private: private:
template<typename DataType> DataType _read(); template<typename DataType> DataType _read();
Data _readData(size_t size); Data _readData(size_t size);
void _readData(void *target, size_t size);
size_t _pos; size_t _pos;
const Data *_source; const Data *_source;
@ -96,8 +99,19 @@ namespace cpputils {
inline Data Deserializer::_readData(size_t size) { inline Data Deserializer::_readData(size_t size) {
Data result(size); Data result(size);
std::memcpy(static_cast<char*>(result.data()), static_cast<const char*>(_source->dataOffset(_pos)), size); _readData(result.data(), size);
return result;
}
inline void Deserializer::_readData(void *target, size_t size) {
std::memcpy(static_cast<char*>(target), static_cast<const char*>(_source->dataOffset(_pos)), size);
_pos += size; _pos += size;
}
template<size_t SIZE>
inline FixedSizeData<SIZE> Deserializer::readFixedSizeData() {
FixedSizeData<SIZE> result(FixedSizeData<SIZE>::Null());
_readData(result.data(), SIZE);
return result; return result;
} }

View File

@ -3,6 +3,7 @@
#define MESSMER_CPPUTILS_DATA_SERIALIZER_H #define MESSMER_CPPUTILS_DATA_SERIALIZER_H
#include "Data.h" #include "Data.h"
#include "FixedSizeData.h"
#include "../macros.h" #include "../macros.h"
#include "../assert/assert.h" #include "../assert/assert.h"
#include <string> #include <string>
@ -24,6 +25,7 @@ namespace cpputils {
void writeInt64(int64_t value); void writeInt64(int64_t value);
void writeString(const std::string &value); void writeString(const std::string &value);
void writeData(const Data &value); void writeData(const Data &value);
template<size_t SIZE> void writeFixedSizeData(const FixedSizeData<SIZE> &value);
// Write the data as last element when serializing. // Write the data as last element when serializing.
// It does not store a data size but limits the size by the size of the serialization result // It does not store a data size but limits the size by the size of the serialization result
@ -36,7 +38,7 @@ namespace cpputils {
private: private:
template<typename DataType> void _write(DataType obj); template<typename DataType> void _write(DataType obj);
void _writeData(const Data &value); void _writeData(const void *data, size_t count);
size_t _pos; size_t _pos;
Data _result; Data _result;
@ -91,33 +93,33 @@ namespace cpputils {
inline void Serializer::writeData(const Data &data) { inline void Serializer::writeData(const Data &data) {
writeUint64(data.size()); writeUint64(data.size());
_writeData(data); _writeData(data.data(), data.size());
} }
inline size_t Serializer::DataSize(const Data &data) { inline size_t Serializer::DataSize(const Data &data) {
return sizeof(uint64_t) + data.size(); return sizeof(uint64_t) + data.size();
} }
inline void Serializer::writeTailData(const Data &data) { template<size_t SIZE>
ASSERT(_pos + data.size() == _result.size(), "Not enough data given to write until the end of the stream"); inline void Serializer::writeFixedSizeData(const FixedSizeData<SIZE> &data) {
_writeData(data); _writeData(data.data(), SIZE);
} }
inline void Serializer::_writeData(const Data &data) { inline void Serializer::writeTailData(const Data &data) {
if (_pos + data.size() > _result.size()) { ASSERT(_pos + data.size() == _result.size(), "Not enough data given to write until the end of the stream");
_writeData(data.data(), data.size());
}
inline void Serializer::_writeData(const void *data, size_t count) {
if (_pos + count > _result.size()) {
throw std::runtime_error("Serialization failed - size overflow"); throw std::runtime_error("Serialization failed - size overflow");
} }
std::memcpy(static_cast<char*>(_result.dataOffset(_pos)), static_cast<const char*>(data.data()), data.size()); std::memcpy(static_cast<char*>(_result.dataOffset(_pos)), static_cast<const char*>(data), count);
_pos += data.size(); _pos += count;
} }
inline void Serializer::writeString(const std::string &value) { inline void Serializer::writeString(const std::string &value) {
size_t size = value.size() + 1; // +1 for the nullbyte _writeData(value.c_str(), value.size() + 1); // +1 for the nullbyte
if (_pos + size > _result.size()) {
throw std::runtime_error("Serialization failed - size overflow");
}
std::memcpy(static_cast<char*>(_result.dataOffset(_pos)), value.c_str(), size);
_pos += size;
} }
inline size_t Serializer::StringSize(const std::string &value) { inline size_t Serializer::StringSize(const std::string &value) {

View File

@ -11,9 +11,16 @@ public:
blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972"); blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
blockstore::Key key2 = blockstore::Key::FromString("C772972491BB4932A1389EE14BC7090A"); blockstore::Key key2 = blockstore::Key::FromString("C772972491BB4932A1389EE14BC7090A");
uint32_t clientId = 0x12345678;
uint32_t clientId2 = 0x23456789;
TempFile stateFile; TempFile stateFile;
KnownBlockVersions testobj; KnownBlockVersions testobj;
void EXPECT_VERSION_IS(uint64_t version, KnownBlockVersions *testobj, blockstore::Key &key, uint32_t clientId) {
EXPECT_FALSE(testobj->checkAndUpdateVersion(clientId, key, version-1));
EXPECT_TRUE(testobj->checkAndUpdateVersion(clientId, key, version));
}
}; };
TEST_F(KnownBlockVersionsTest, update_newEntry_zero) { TEST_F(KnownBlockVersionsTest, update_newEntry_zero) {
@ -46,80 +53,93 @@ TEST_F(KnownBlockVersionsTest, update_existingEntry_invalid) {
); );
} }
TEST_F(KnownBlockVersionsTest, update_updatesOwnClientId) {
testobj.updateVersion(key, 100);
EXPECT_VERSION_IS(100, &testobj, key, testobj.myClientId());
}
TEST_F(KnownBlockVersionsTest, checkAndUpdate_newEntry_zero) { TEST_F(KnownBlockVersionsTest, checkAndUpdate_newEntry_zero) {
EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 0)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 0));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdate_newEntry_nonzero) { TEST_F(KnownBlockVersionsTest, checkAndUpdate_newEntry_nonzero) {
EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_equal_zero) { TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_equal_zero) {
EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 0)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 0));
EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 0)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 0));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_equal_nonzero) { TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_equal_nonzero) {
EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_nonequal) { TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_nonequal) {
EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 101)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 101));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_invalid) { TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_invalid) {
EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
EXPECT_FALSE(testobj.checkAndUpdateVersion(key, 99)); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 99));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_invalidDoesntModifyEntry) { TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_invalidDoesntModifyEntry) {
EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
EXPECT_FALSE(testobj.checkAndUpdateVersion(key, 99)); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 99));
EXPECT_FALSE(testobj.checkAndUpdateVersion(key, 99)); EXPECT_VERSION_IS(100, &testobj, key, clientId);
EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 100));
} }
TEST_F(KnownBlockVersionsTest, checkAndUpdate_twoEntriesDontInfluenceEachOther) { TEST_F(KnownBlockVersionsTest, checkAndUpdate_twoEntriesDontInfluenceEachOther_differentKeys) {
testobj.updateVersion(key, 100); // Setup
testobj.updateVersion(key2, 100); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key2, 100));
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 150));
testobj.updateVersion(key, 150); // Checks
EXPECT_VERSION_IS(150, &testobj, key, clientId);
EXPECT_VERSION_IS(100, &testobj, key2, clientId);
}
EXPECT_FALSE(testobj.checkAndUpdateVersion(key, 149)); TEST_F(KnownBlockVersionsTest, checkAndUpdate_twoEntriesDontInfluenceEachOther_differentClientIds) {
EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 150)); // Setup
EXPECT_FALSE(testobj.checkAndUpdateVersion(key2, 99)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
EXPECT_TRUE(testobj.checkAndUpdateVersion(key2, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 100));
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 150));
EXPECT_VERSION_IS(150, &testobj, key, clientId);
EXPECT_VERSION_IS(100, &testobj, key, clientId2);
} }
TEST_F(KnownBlockVersionsTest, saveAndLoad_empty) { TEST_F(KnownBlockVersionsTest, saveAndLoad_empty) {
TempFile stateFile(false); TempFile stateFile(false);
KnownBlockVersions(stateFile.path()); KnownBlockVersions(stateFile.path());
EXPECT_TRUE(KnownBlockVersions(stateFile.path()).checkAndUpdateVersion(key, 0)); EXPECT_TRUE(KnownBlockVersions(stateFile.path()).checkAndUpdateVersion(clientId, key, 0));
} }
TEST_F(KnownBlockVersionsTest, saveAndLoad_oneentry) { TEST_F(KnownBlockVersionsTest, saveAndLoad_oneentry) {
TempFile stateFile(false); TempFile stateFile(false);
KnownBlockVersions(stateFile.path()).updateVersion(key, 100); EXPECT_TRUE(KnownBlockVersions(stateFile.path()).checkAndUpdateVersion(clientId, key, 100));
EXPECT_FALSE(KnownBlockVersions(stateFile.path()).checkAndUpdateVersion(key, 99)); KnownBlockVersions obj(stateFile.path());
EXPECT_TRUE(KnownBlockVersions(stateFile.path()).checkAndUpdateVersion(key, 100)); EXPECT_VERSION_IS(100, &obj, key, clientId);
} }
TEST_F(KnownBlockVersionsTest, saveAndLoad_twoentries) { TEST_F(KnownBlockVersionsTest, saveAndLoad_threeentries) {
TempFile stateFile(false); TempFile stateFile(false);
{ {
KnownBlockVersions obj(stateFile.path()); KnownBlockVersions obj(stateFile.path());
obj.updateVersion(key, 100); obj.updateVersion(key, 100);
obj.updateVersion(key2, 50); obj.updateVersion(key2, 50);
EXPECT_TRUE(obj.checkAndUpdateVersion(clientId, key, 150));
} }
KnownBlockVersions obj(stateFile.path()); KnownBlockVersions obj(stateFile.path());
EXPECT_FALSE(obj.checkAndUpdateVersion(key, 99)); EXPECT_VERSION_IS(100, &obj, key, obj.myClientId());
EXPECT_TRUE(obj.checkAndUpdateVersion(key, 100)); EXPECT_VERSION_IS(50, &obj, key2, obj.myClientId());
EXPECT_FALSE(obj.checkAndUpdateVersion(key2, 49)); EXPECT_VERSION_IS(150, &obj, key, clientId);
EXPECT_TRUE(obj.checkAndUpdateVersion(key2, 50));
} }