* Prevent rollback to the "newest" version of a client when this version was superseded by a version from a different client.
* Use mutex/locks to secure access to KnownBlockVersions
This commit is contained in:
parent
263c540cd0
commit
4d1f7a46b9
@ -1,13 +1,13 @@
|
||||
#include <fstream>
|
||||
#include <cpp-utils/random/Random.h>
|
||||
#include "KnownBlockVersions.h"
|
||||
#include <cpp-utils/data/Serializer.h>
|
||||
#include <cpp-utils/data/Deserializer.h>
|
||||
|
||||
namespace bf = boost::filesystem;
|
||||
using std::unordered_map;
|
||||
using std::pair;
|
||||
using std::string;
|
||||
using std::unique_lock;
|
||||
using std::mutex;
|
||||
using boost::optional;
|
||||
using boost::none;
|
||||
using cpputils::Data;
|
||||
@ -21,30 +21,50 @@ namespace versioncounting {
|
||||
const string KnownBlockVersions::HEADER = "cryfs.integritydata.knownblockversions;0";
|
||||
|
||||
KnownBlockVersions::KnownBlockVersions(const bf::path &stateFilePath)
|
||||
:_knownVersions(), _stateFilePath(stateFilePath), _myClientId(0), _valid(true) {
|
||||
:_knownVersions(), _lastUpdateClientId(), _stateFilePath(stateFilePath), _myClientId(0), _mutex(), _valid(true) {
|
||||
unique_lock<mutex> lock(_mutex);
|
||||
_loadStateFile();
|
||||
}
|
||||
|
||||
KnownBlockVersions::KnownBlockVersions(KnownBlockVersions &&rhs)
|
||||
: _knownVersions(std::move(rhs._knownVersions)), _stateFilePath(std::move(rhs._stateFilePath)), _myClientId(rhs._myClientId), _valid(true) {
|
||||
: _knownVersions(), _lastUpdateClientId(), _stateFilePath(), _myClientId(0), _mutex(), _valid(true) {
|
||||
unique_lock<mutex> rhsLock(rhs._mutex);
|
||||
unique_lock<mutex> lock(_mutex);
|
||||
_knownVersions = std::move(rhs._knownVersions);
|
||||
_lastUpdateClientId = std::move(rhs._lastUpdateClientId);
|
||||
_stateFilePath = std::move(rhs._stateFilePath);
|
||||
_myClientId = rhs._myClientId;
|
||||
rhs._valid = false;
|
||||
}
|
||||
|
||||
KnownBlockVersions::~KnownBlockVersions() {
|
||||
unique_lock<mutex> lock(_mutex);
|
||||
if (_valid) {
|
||||
_saveStateFile();
|
||||
}
|
||||
}
|
||||
|
||||
bool KnownBlockVersions::checkAndUpdateVersion(uint32_t clientId, const Key &key, uint64_t version) {
|
||||
unique_lock<mutex> lock(_mutex);
|
||||
|
||||
ASSERT(version > 0, "Version has to be >0"); // Otherwise we wouldn't handle notexisting entries correctly.
|
||||
ASSERT(_valid, "Object not valid due to a std::move");
|
||||
|
||||
uint64_t &found = _knownVersions[{clientId, key}]; // If the entry doesn't exist, this creates it with value 0.
|
||||
if (found > version) {
|
||||
// This client already published a newer block version. Rollbacks are not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t &lastUpdateClientId = _lastUpdateClientId[key]; // If entry doesn't exist, this creates it with value 0. However, in this case, found == 0 (and version > 0), which means found != version.
|
||||
if (found == version && lastUpdateClientId != clientId) {
|
||||
// This is a roll back to the "newest" block of client [clientId], which was since then superseded by a version from client _lastUpdateClientId[key].
|
||||
// This is not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
found = version;
|
||||
lastUpdateClientId = clientId;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -67,19 +87,46 @@ void KnownBlockVersions::_loadStateFile() {
|
||||
throw std::runtime_error("Invalid local state: Invalid integrity file header.");
|
||||
}
|
||||
_myClientId = deserializer.readUint32();
|
||||
uint64_t numEntries = deserializer.readUint64();
|
||||
|
||||
_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.
|
||||
for (uint64_t i = 0 ; i < numEntries; ++i) {
|
||||
auto entry = _readEntry(&deserializer);
|
||||
_knownVersions.insert(entry);
|
||||
}
|
||||
_deserializeKnownVersions(&deserializer);
|
||||
_deserializeLastUpdateClientIds(&deserializer);
|
||||
|
||||
deserializer.finished();
|
||||
};
|
||||
|
||||
pair<ClientIdAndBlockKey, uint64_t> KnownBlockVersions::_readEntry(Deserializer *deserializer) {
|
||||
|
||||
void KnownBlockVersions::_saveStateFile() const {
|
||||
Serializer serializer(
|
||||
Serializer::StringSize(HEADER) + sizeof(uint32_t) +
|
||||
sizeof(uint64_t) + _knownVersions.size() * (sizeof(uint32_t) + Key::BINARY_LENGTH + sizeof(uint64_t)) +
|
||||
sizeof(uint64_t) + _lastUpdateClientId.size() * (Key::BINARY_LENGTH + sizeof(uint32_t)));
|
||||
serializer.writeString(HEADER);
|
||||
serializer.writeUint32(_myClientId);
|
||||
_serializeKnownVersions(&serializer);
|
||||
_serializeLastUpdateClientIds(&serializer);
|
||||
|
||||
serializer.finished().StoreToFile(_stateFilePath);
|
||||
}
|
||||
|
||||
void KnownBlockVersions::_deserializeKnownVersions(Deserializer *deserializer) {
|
||||
uint64_t numEntries = deserializer->readUint64();
|
||||
_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.
|
||||
for (uint64_t i = 0 ; i < numEntries; ++i) {
|
||||
auto entry = _deserializeKnownVersionsEntry(deserializer);
|
||||
_knownVersions.insert(entry);
|
||||
}
|
||||
}
|
||||
|
||||
void KnownBlockVersions::_serializeKnownVersions(Serializer *serializer) const {
|
||||
uint64_t numEntries = _knownVersions.size();
|
||||
serializer->writeUint64(numEntries);
|
||||
|
||||
for (const auto &entry : _knownVersions) {
|
||||
_serializeKnownVersionsEntry(serializer, entry);
|
||||
}
|
||||
}
|
||||
|
||||
pair<ClientIdAndBlockKey, uint64_t> KnownBlockVersions::_deserializeKnownVersionsEntry(Deserializer *deserializer) {
|
||||
uint32_t clientId = deserializer->readUint32();
|
||||
Key blockKey = deserializer->readFixedSizeData<Key::BINARY_LENGTH>();
|
||||
uint64_t version = deserializer->readUint64();
|
||||
@ -87,21 +134,41 @@ pair<ClientIdAndBlockKey, uint64_t> KnownBlockVersions::_readEntry(Deserializer
|
||||
return {{clientId, blockKey}, version};
|
||||
};
|
||||
|
||||
void KnownBlockVersions::_saveStateFile() const {
|
||||
uint64_t numEntries = _knownVersions.size();
|
||||
void KnownBlockVersions::_serializeKnownVersionsEntry(Serializer *serializer, const pair<ClientIdAndBlockKey, uint64_t> &entry) {
|
||||
serializer->writeUint32(entry.first.clientId);
|
||||
serializer->writeFixedSizeData<Key::BINARY_LENGTH>(entry.first.blockKey);
|
||||
serializer->writeUint64(entry.second);
|
||||
}
|
||||
|
||||
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) {
|
||||
serializer.writeUint32(entry.first.clientId);
|
||||
serializer.writeFixedSizeData<Key::BINARY_LENGTH>(entry.first.blockKey);
|
||||
serializer.writeUint64(entry.second);
|
||||
void KnownBlockVersions::_deserializeLastUpdateClientIds(Deserializer *deserializer) {
|
||||
uint64_t numEntries = deserializer->readUint64();
|
||||
_lastUpdateClientId.clear();
|
||||
_lastUpdateClientId.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) {
|
||||
auto entry = _deserializeLastUpdateClientIdEntry(deserializer);
|
||||
_lastUpdateClientId.insert(entry);
|
||||
}
|
||||
}
|
||||
|
||||
serializer.finished().StoreToFile(_stateFilePath);
|
||||
void KnownBlockVersions::_serializeLastUpdateClientIds(Serializer *serializer) const {
|
||||
uint64_t numEntries = _lastUpdateClientId.size();
|
||||
serializer->writeUint64(numEntries);
|
||||
|
||||
for (const auto &entry : _lastUpdateClientId) {
|
||||
_serializeLastUpdateClientIdEntry(serializer, entry);
|
||||
}
|
||||
}
|
||||
|
||||
pair<Key, uint32_t> KnownBlockVersions::_deserializeLastUpdateClientIdEntry(Deserializer *deserializer) {
|
||||
Key blockKey = deserializer->readFixedSizeData<Key::BINARY_LENGTH>();
|
||||
uint32_t clientId = deserializer->readUint32();
|
||||
|
||||
return {blockKey, clientId};
|
||||
};
|
||||
|
||||
void KnownBlockVersions::_serializeLastUpdateClientIdEntry(Serializer *serializer, const pair<Key, uint32_t> &entry) {
|
||||
serializer->writeFixedSizeData<Key::BINARY_LENGTH>(entry.first);
|
||||
serializer->writeUint32(entry.second);
|
||||
}
|
||||
|
||||
uint32_t KnownBlockVersions::myClientId() const {
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include <boost/optional.hpp>
|
||||
#include "ClientIdAndBlockKey.h"
|
||||
#include <cpp-utils/data/Deserializer.h>
|
||||
#include <cpp-utils/data/Serializer.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace blockstore {
|
||||
namespace versioncounting {
|
||||
@ -27,16 +29,30 @@ namespace blockstore {
|
||||
|
||||
private:
|
||||
std::unordered_map<ClientIdAndBlockKey, uint64_t> _knownVersions;
|
||||
std::unordered_map<Key, uint32_t> _lastUpdateClientId; // The client who last updated the block
|
||||
|
||||
boost::filesystem::path _stateFilePath;
|
||||
uint32_t _myClientId;
|
||||
mutable std::mutex _mutex;
|
||||
bool _valid;
|
||||
|
||||
static const std::string HEADER;
|
||||
|
||||
void _loadStateFile();
|
||||
static std::pair<ClientIdAndBlockKey, uint64_t> _readEntry(cpputils::Deserializer *deserializer);
|
||||
void _saveStateFile() const;
|
||||
|
||||
void _deserializeKnownVersions(cpputils::Deserializer *deserializer);
|
||||
void _serializeKnownVersions(cpputils::Serializer *serializer) const;
|
||||
|
||||
static std::pair<ClientIdAndBlockKey, uint64_t> _deserializeKnownVersionsEntry(cpputils::Deserializer *deserializer);
|
||||
static void _serializeKnownVersionsEntry(cpputils::Serializer *serializer, const std::pair<ClientIdAndBlockKey, uint64_t> &entry);
|
||||
|
||||
void _deserializeLastUpdateClientIds(cpputils::Deserializer *deserializer);
|
||||
void _serializeLastUpdateClientIds(cpputils::Serializer *serializer) const;
|
||||
|
||||
static std::pair<Key, uint32_t> _deserializeLastUpdateClientIdEntry(cpputils::Deserializer *deserializer);
|
||||
static void _serializeLastUpdateClientIdEntry(cpputils::Serializer *serializer, const std::pair<Key, uint32_t> &entry);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(KnownBlockVersions);
|
||||
};
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace blockstore {
|
||||
namespace versioncounting {
|
||||
constexpr unsigned int VersionCountingBlock::CLIENTID_HEADER_OFFSET;
|
||||
constexpr unsigned int VersionCountingBlock::VERSION_HEADER_OFFSET;
|
||||
constexpr unsigned int VersionCountingBlock::HEADER_LENGTH;
|
||||
constexpr uint16_t VersionCountingBlock::FORMAT_VERSION_HEADER;
|
||||
constexpr uint64_t VersionCountingBlock::VERSION_ZERO;
|
||||
|
@ -16,10 +16,10 @@
|
||||
#include <cpp-utils/data/DataUtils.h>
|
||||
#include <mutex>
|
||||
#include <cpp-utils/logging/logging.h>
|
||||
#include "../../../../vendor/googletest/gtest-1.7.0/googletest/include/gtest/gtest_prod.h"
|
||||
|
||||
namespace blockstore {
|
||||
namespace versioncounting {
|
||||
class VersionCountingBlockStore;
|
||||
|
||||
// TODO Is an implementation that doesn't keep an in-memory copy but just passes through write() calls to the underlying block store (including a write call to the version number each time) faster?
|
||||
|
||||
@ -59,12 +59,16 @@ private:
|
||||
|
||||
// This header is prepended to blocks to allow future versions to have compatibility.
|
||||
static constexpr uint16_t FORMAT_VERSION_HEADER = 0;
|
||||
static constexpr uint64_t VERSION_ZERO = 0;
|
||||
static constexpr unsigned int HEADER_LENGTH = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t) + sizeof(VERSION_ZERO);
|
||||
static constexpr uint64_t VERSION_ZERO = 1; // lowest block version is '1', because that is required by class KnownBlockVersions.
|
||||
|
||||
std::mutex _mutex;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(VersionCountingBlock);
|
||||
|
||||
public:
|
||||
static constexpr unsigned int CLIENTID_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER);
|
||||
static constexpr unsigned int VERSION_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t);
|
||||
static constexpr unsigned int HEADER_LENGTH = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t) + sizeof(VERSION_ZERO);
|
||||
};
|
||||
|
||||
|
||||
@ -84,8 +88,8 @@ inline cpputils::Data VersionCountingBlock::_prependHeaderToData(uint32_t myClie
|
||||
static_assert(HEADER_LENGTH == sizeof(FORMAT_VERSION_HEADER) + sizeof(myClientId) + sizeof(version), "Wrong header length");
|
||||
cpputils::Data result(data.size() + HEADER_LENGTH);
|
||||
std::memcpy(result.dataOffset(0), &FORMAT_VERSION_HEADER, sizeof(FORMAT_VERSION_HEADER));
|
||||
std::memcpy(result.dataOffset(sizeof(FORMAT_VERSION_HEADER)), &myClientId, sizeof(myClientId));
|
||||
std::memcpy(result.dataOffset(sizeof(FORMAT_VERSION_HEADER)+sizeof(myClientId)), &version, sizeof(version));
|
||||
std::memcpy(result.dataOffset(CLIENTID_HEADER_OFFSET), &myClientId, sizeof(myClientId));
|
||||
std::memcpy(result.dataOffset(VERSION_HEADER_OFFSET), &version, sizeof(version));
|
||||
std::memcpy((uint8_t*)result.dataOffset(HEADER_LENGTH), data.data(), data.size());
|
||||
return result;
|
||||
}
|
||||
@ -112,13 +116,13 @@ 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));
|
||||
std::memcpy(&clientId, data.dataOffset(CLIENTID_HEADER_OFFSET), sizeof(clientId));
|
||||
return clientId;
|
||||
}
|
||||
|
||||
inline uint64_t VersionCountingBlock::_readVersion(const cpputils::Data &data) {
|
||||
uint64_t version;
|
||||
std::memcpy(&version, data.dataOffset(sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t)), sizeof(version));
|
||||
std::memcpy(&version, data.dataOffset(VERSION_HEADER_OFFSET), sizeof(version));
|
||||
return version;
|
||||
}
|
||||
|
||||
@ -170,8 +174,8 @@ inline void VersionCountingBlock::_storeToBaseBlock() {
|
||||
if (_dataChanged) {
|
||||
++_version;
|
||||
uint32_t myClientId = _knownBlockVersions->myClientId();
|
||||
std::memcpy(_dataWithHeader.dataOffset(sizeof(FORMAT_VERSION_HEADER)), &myClientId, sizeof(myClientId));
|
||||
std::memcpy(_dataWithHeader.dataOffset(sizeof(FORMAT_VERSION_HEADER) + sizeof(myClientId)), &_version, sizeof(_version));
|
||||
std::memcpy(_dataWithHeader.dataOffset(CLIENTID_HEADER_OFFSET), &myClientId, sizeof(myClientId));
|
||||
std::memcpy(_dataWithHeader.dataOffset(VERSION_HEADER_OFFSET), &_version, sizeof(_version));
|
||||
_baseBlock->write(_dataWithHeader.data(), 0, _dataWithHeader.size());
|
||||
_knownBlockVersions->updateVersion(key(), _version);
|
||||
_dataChanged = false;
|
||||
|
@ -19,24 +19,24 @@ public:
|
||||
|
||||
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));
|
||||
EXPECT_TRUE(testobj->checkAndUpdateVersion(clientId, key, version+1));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, update_newEntry_zero) {
|
||||
testobj.updateVersion(key, 0);
|
||||
TEST_F(KnownBlockVersionsTest, update_newEntry_lowversion) {
|
||||
testobj.updateVersion(key, 1);
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, update_newEntry_nonzero) {
|
||||
TEST_F(KnownBlockVersionsTest, update_newEntry_highversion) {
|
||||
testobj.updateVersion(key, 100);
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, update_existingEntry_equal_zero) {
|
||||
testobj.updateVersion(key, 0);
|
||||
testobj.updateVersion(key, 0);
|
||||
TEST_F(KnownBlockVersionsTest, update_existingEntry_equal_lowversion) {
|
||||
testobj.updateVersion(key, 1);
|
||||
testobj.updateVersion(key, 1);
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, update_existingEntry_equal_nonzero) {
|
||||
TEST_F(KnownBlockVersionsTest, update_existingEntry_equal_highversion) {
|
||||
testobj.updateVersion(key, 100);
|
||||
testobj.updateVersion(key, 100);
|
||||
}
|
||||
@ -58,20 +58,20 @@ TEST_F(KnownBlockVersionsTest, update_updatesOwnClientId) {
|
||||
EXPECT_VERSION_IS(100, &testobj, key, testobj.myClientId());
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_newEntry_zero) {
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 0));
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_newEntry_lowversion) {
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 1));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_newEntry_nonzero) {
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_newEntry_highversion) {
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_equal_zero) {
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 0));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 0));
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_equal_lowversion) {
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 1));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 1));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_equal_nonzero) {
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_equal_highversion) {
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
|
||||
}
|
||||
@ -114,11 +114,22 @@ TEST_F(KnownBlockVersionsTest, checkAndUpdate_twoEntriesDontInfluenceEachOther_d
|
||||
EXPECT_VERSION_IS(100, &testobj, key, clientId2);
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_allowsRollbackToSameClientWithSameVersionNumber) {
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, checkAndUpdate_doesntAllowRollbackToOldClientWithSameVersionNumber) {
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, key, 100));
|
||||
EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, key, 10));
|
||||
EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, key, 100));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, saveAndLoad_empty) {
|
||||
TempFile stateFile(false);
|
||||
KnownBlockVersions(stateFile.path());
|
||||
|
||||
EXPECT_TRUE(KnownBlockVersions(stateFile.path()).checkAndUpdateVersion(clientId, key, 0));
|
||||
EXPECT_TRUE(KnownBlockVersions(stateFile.path()).checkAndUpdateVersion(clientId, key, 1));
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, saveAndLoad_oneentry) {
|
||||
@ -143,3 +154,16 @@ TEST_F(KnownBlockVersionsTest, saveAndLoad_threeentries) {
|
||||
EXPECT_VERSION_IS(50, &obj, key2, obj.myClientId());
|
||||
EXPECT_VERSION_IS(150, &obj, key, clientId);
|
||||
}
|
||||
|
||||
TEST_F(KnownBlockVersionsTest, saveAndLoad_lastUpdateClientIdIsStored) {
|
||||
{
|
||||
KnownBlockVersions obj(stateFile.path());
|
||||
EXPECT_TRUE(obj.checkAndUpdateVersion(clientId, key, 100));
|
||||
EXPECT_TRUE(obj.checkAndUpdateVersion(clientId2, key, 10));
|
||||
}
|
||||
|
||||
KnownBlockVersions obj(stateFile.path());
|
||||
EXPECT_FALSE(obj.checkAndUpdateVersion(clientId, key, 100));
|
||||
EXPECT_TRUE(obj.checkAndUpdateVersion(clientId2, key, 10));
|
||||
EXPECT_TRUE(obj.checkAndUpdateVersion(clientId, key, 101));
|
||||
}
|
||||
|
@ -46,6 +46,13 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
Data loadBlock(const blockstore::Key &key) {
|
||||
auto block = blockStore->load(key).value();
|
||||
Data result(block->size());
|
||||
std::memcpy(result.data(), block->data(), data.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
void modifyBlock(const blockstore::Key &key) {
|
||||
auto block = blockStore->load(key).value();
|
||||
uint64_t data = 5;
|
||||
@ -58,11 +65,27 @@ public:
|
||||
block->write(data.data(), 0, data.size());
|
||||
}
|
||||
|
||||
void decreaseVersionNumber(const blockstore::Key &key) {
|
||||
auto baseBlock = baseBlockStore->load(key).value();
|
||||
uint64_t version = *(uint64_t*)((uint8_t*)baseBlock->data()+VersionCountingBlock::VERSION_HEADER_OFFSET);
|
||||
ASSERT(version > 1, "Can't decrease the lowest allowed version number");
|
||||
version -= 1;
|
||||
baseBlock->write((char*)&version, VersionCountingBlock::VERSION_HEADER_OFFSET, sizeof(version));
|
||||
}
|
||||
|
||||
void changeClientId(const blockstore::Key &key) {
|
||||
auto baseBlock = baseBlockStore->load(key).value();
|
||||
uint32_t clientId = *(uint32_t*)((uint8_t*)baseBlock->data()+VersionCountingBlock::CLIENTID_HEADER_OFFSET);
|
||||
clientId += 1;
|
||||
baseBlock->write((char*)&clientId, VersionCountingBlock::CLIENTID_HEADER_OFFSET, sizeof(clientId));
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(VersionCountingBlockStoreTest);
|
||||
};
|
||||
|
||||
TEST_F(VersionCountingBlockStoreTest, DoesntAllowRollbacks) {
|
||||
// Test that a decreasing version number is not allowed
|
||||
TEST_F(VersionCountingBlockStoreTest, RollbackPrevention_DoesntAllowDecreasingVersionNumberForSameClient_1) {
|
||||
auto key = CreateBlockReturnKey();
|
||||
Data oldBaseBlock = loadBaseBlock(key);
|
||||
modifyBlock(key);
|
||||
@ -70,6 +93,40 @@ TEST_F(VersionCountingBlockStoreTest, DoesntAllowRollbacks) {
|
||||
EXPECT_EQ(boost::none, blockStore->load(key));
|
||||
}
|
||||
|
||||
TEST_F(VersionCountingBlockStoreTest, RollbackPrevention_DoesntAllowDecreasingVersionNumberForSameClient_2) {
|
||||
auto key = CreateBlockReturnKey();
|
||||
// Increase the version number
|
||||
modifyBlock(key);
|
||||
// Decrease the version number again
|
||||
decreaseVersionNumber(key);
|
||||
EXPECT_EQ(boost::none, blockStore->load(key));
|
||||
}
|
||||
|
||||
// Test that a different client doesn't need to have a higher version number (i.e. version numbers are per client).
|
||||
TEST_F(VersionCountingBlockStoreTest, RollbackPrevention_DoesAllowDecreasingVersionNumberForDifferentClient) {
|
||||
auto key = CreateBlockReturnKey();
|
||||
// Increase the version number
|
||||
modifyBlock(key);
|
||||
// Fake a modification by a different client with lower version numbers
|
||||
changeClientId(key);
|
||||
decreaseVersionNumber(key);
|
||||
EXPECT_NE(boost::none, blockStore->load(key));
|
||||
}
|
||||
|
||||
// Test that it doesn't allow a rollback to the "newest" block of a client, when this block was superseded by a version of a different client
|
||||
TEST_F(VersionCountingBlockStoreTest, RollbackPrevention_DoesntAllowSameVersionNumberForOldClient) {
|
||||
auto key = CreateBlockReturnKey();
|
||||
// Increase the version number
|
||||
modifyBlock(key);
|
||||
Data oldBaseBlock = loadBaseBlock(key);
|
||||
// Fake a modification by a different client with lower version numbers
|
||||
changeClientId(key);
|
||||
loadBlock(key); // make the block store know about this other client's modification
|
||||
// Rollback to old client
|
||||
rollbackBaseBlock(key, oldBaseBlock);
|
||||
EXPECT_EQ(boost::none, blockStore->load(key));
|
||||
}
|
||||
|
||||
TEST_F(VersionCountingBlockStoreTest, PhysicalBlockSize_zerophysical) {
|
||||
EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(0));
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user