From 4d1f7a46b9199e59b9256b6bfd12a5e5d728186b Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Wed, 22 Jun 2016 16:27:35 -0700 Subject: [PATCH] * 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 --- .../versioncounting/KnownBlockVersions.cpp | 117 ++++++++++++++---- .../versioncounting/KnownBlockVersions.h | 18 ++- .../versioncounting/VersionCountingBlock.cpp | 2 + .../versioncounting/VersionCountingBlock.h | 22 ++-- .../KnownBlockVersionsTest.cpp | 56 ++++++--- ...VersionCountingBlockStoreTest_Specific.cpp | 59 ++++++++- 6 files changed, 222 insertions(+), 52 deletions(-) diff --git a/src/blockstore/implementations/versioncounting/KnownBlockVersions.cpp b/src/blockstore/implementations/versioncounting/KnownBlockVersions.cpp index 6c0d23f0..99a13010 100644 --- a/src/blockstore/implementations/versioncounting/KnownBlockVersions.cpp +++ b/src/blockstore/implementations/versioncounting/KnownBlockVersions.cpp @@ -1,13 +1,13 @@ #include #include #include "KnownBlockVersions.h" -#include -#include 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 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 rhsLock(rhs._mutex); + unique_lock 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 lock(_mutex); if (_valid) { _saveStateFile(); } } bool KnownBlockVersions::checkAndUpdateVersion(uint32_t clientId, const Key &key, uint64_t version) { + unique_lock 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(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 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(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 KnownBlockVersions::_deserializeKnownVersionsEntry(Deserializer *deserializer) { uint32_t clientId = deserializer->readUint32(); Key blockKey = deserializer->readFixedSizeData(); uint64_t version = deserializer->readUint64(); @@ -87,21 +134,41 @@ pair KnownBlockVersions::_readEntry(Deserializer return {{clientId, blockKey}, version}; }; -void KnownBlockVersions::_saveStateFile() const { - uint64_t numEntries = _knownVersions.size(); +void KnownBlockVersions::_serializeKnownVersionsEntry(Serializer *serializer, const pair &entry) { + serializer->writeUint32(entry.first.clientId); + serializer->writeFixedSizeData(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(entry.first.blockKey); - serializer.writeUint64(entry.second); +void KnownBlockVersions::_deserializeLastUpdateClientIds(Deserializer *deserializer) { + uint64_t numEntries = deserializer->readUint64(); + _lastUpdateClientId.clear(); + _lastUpdateClientId.reserve(static_cast(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 KnownBlockVersions::_deserializeLastUpdateClientIdEntry(Deserializer *deserializer) { + Key blockKey = deserializer->readFixedSizeData(); + uint32_t clientId = deserializer->readUint32(); + + return {blockKey, clientId}; +}; + +void KnownBlockVersions::_serializeLastUpdateClientIdEntry(Serializer *serializer, const pair &entry) { + serializer->writeFixedSizeData(entry.first); + serializer->writeUint32(entry.second); } uint32_t KnownBlockVersions::myClientId() const { diff --git a/src/blockstore/implementations/versioncounting/KnownBlockVersions.h b/src/blockstore/implementations/versioncounting/KnownBlockVersions.h index 4e696f37..43e6deda 100644 --- a/src/blockstore/implementations/versioncounting/KnownBlockVersions.h +++ b/src/blockstore/implementations/versioncounting/KnownBlockVersions.h @@ -8,6 +8,8 @@ #include #include "ClientIdAndBlockKey.h" #include +#include +#include namespace blockstore { namespace versioncounting { @@ -27,16 +29,30 @@ namespace blockstore { private: std::unordered_map _knownVersions; + std::unordered_map _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 _readEntry(cpputils::Deserializer *deserializer); void _saveStateFile() const; + void _deserializeKnownVersions(cpputils::Deserializer *deserializer); + void _serializeKnownVersions(cpputils::Serializer *serializer) const; + + static std::pair _deserializeKnownVersionsEntry(cpputils::Deserializer *deserializer); + static void _serializeKnownVersionsEntry(cpputils::Serializer *serializer, const std::pair &entry); + + void _deserializeLastUpdateClientIds(cpputils::Deserializer *deserializer); + void _serializeLastUpdateClientIds(cpputils::Serializer *serializer) const; + + static std::pair _deserializeLastUpdateClientIdEntry(cpputils::Deserializer *deserializer); + static void _serializeLastUpdateClientIdEntry(cpputils::Serializer *serializer, const std::pair &entry); + DISALLOW_COPY_AND_ASSIGN(KnownBlockVersions); }; diff --git a/src/blockstore/implementations/versioncounting/VersionCountingBlock.cpp b/src/blockstore/implementations/versioncounting/VersionCountingBlock.cpp index f02b735d..d6e5388d 100644 --- a/src/blockstore/implementations/versioncounting/VersionCountingBlock.cpp +++ b/src/blockstore/implementations/versioncounting/VersionCountingBlock.cpp @@ -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; diff --git a/src/blockstore/implementations/versioncounting/VersionCountingBlock.h b/src/blockstore/implementations/versioncounting/VersionCountingBlock.h index 726bdd1b..3a554c0f 100644 --- a/src/blockstore/implementations/versioncounting/VersionCountingBlock.h +++ b/src/blockstore/implementations/versioncounting/VersionCountingBlock.h @@ -16,10 +16,10 @@ #include #include #include +#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; diff --git a/test/blockstore/implementations/versioncounting/KnownBlockVersionsTest.cpp b/test/blockstore/implementations/versioncounting/KnownBlockVersionsTest.cpp index fefd9279..9a4a144e 100644 --- a/test/blockstore/implementations/versioncounting/KnownBlockVersionsTest.cpp +++ b/test/blockstore/implementations/versioncounting/KnownBlockVersionsTest.cpp @@ -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)); +} diff --git a/test/blockstore/implementations/versioncounting/VersionCountingBlockStoreTest_Specific.cpp b/test/blockstore/implementations/versioncounting/VersionCountingBlockStoreTest_Specific.cpp index 4e062666..911f3596 100644 --- a/test/blockstore/implementations/versioncounting/VersionCountingBlockStoreTest_Specific.cpp +++ b/test/blockstore/implementations/versioncounting/VersionCountingBlockStoreTest_Specific.cpp @@ -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)); }