diff --git a/src/blockstore/implementations/versioncounting/KnownBlockVersions.cpp b/src/blockstore/implementations/versioncounting/KnownBlockVersions.cpp index 5de05ffb..38d155fb 100644 --- a/src/blockstore/implementations/versioncounting/KnownBlockVersions.cpp +++ b/src/blockstore/implementations/versioncounting/KnownBlockVersions.cpp @@ -1,13 +1,36 @@ +#include #include "KnownBlockVersions.h" +namespace bf = boost::filesystem; +using std::unordered_map; +using std::pair; +using std::string; +using boost::optional; +using boost::none; + namespace blockstore { namespace versioncounting { -KnownBlockVersions::KnownBlockVersions() - :_knownVersions() { +const string KnownBlockVersions::HEADER = "cryfs.integritydata.knownblockversions;0\0"; + +KnownBlockVersions::KnownBlockVersions(const bf::path &stateFilePath) + :_knownVersions(_loadStateFile(stateFilePath)), _stateFilePath(stateFilePath), _valid(true) { +} + +KnownBlockVersions::KnownBlockVersions(KnownBlockVersions &&rhs) + : _knownVersions(std::move(rhs._knownVersions)), _stateFilePath(std::move(rhs._stateFilePath)), _valid(true) { + rhs._valid = false; +} + +KnownBlockVersions::~KnownBlockVersions() { + if (_valid) { + _saveStateFile(); + } } bool KnownBlockVersions::checkAndUpdateVersion(const Key &key, uint64_t version) { + ASSERT(_valid, "Object not valid due to a std::move"); + auto found = _knownVersions.find(key); if (found == _knownVersions.end()) { _knownVersions.emplace(key, version); @@ -28,5 +51,55 @@ void KnownBlockVersions::updateVersion(const Key &key, uint64_t version) { } } +unordered_map KnownBlockVersions::_loadStateFile(const bf::path &stateFilePath) { + std::ifstream file(stateFilePath.native().c_str()); + if (!file.good()) { + return unordered_map(); + } + _checkHeader(&file); + + unordered_map result; + optional> entry = _readEntry(&file); + while(none != entry) { + result.insert(*entry); + entry = _readEntry(&file); + } + ASSERT(file.eof(), "Didn't read until end of file"); + return result; +}; + +void KnownBlockVersions::_checkHeader(std::ifstream *file) { + char actualHeader[HEADER.size()]; + file->read(actualHeader, HEADER.size()); + if (HEADER != string(actualHeader, HEADER.size())) { + throw std::runtime_error("Invalid local state: Invalid integrity file header."); + } +} + +optional> KnownBlockVersions::_readEntry(std::ifstream *file) { + ASSERT(file->good(), "Error reading file"); + pair result(Key::Null(), 0); + + file->read((char*)result.first.data(), result.first.BINARY_LENGTH); + if (file->eof()) { + // Couldn't read another entry. File end. + return none; + } + ASSERT(file->good(), "Error reading file"); + file->read((char*)&result.second, sizeof(result.second)); + ASSERT(file->good(), "Error reading file"); + + return result; +}; + +void KnownBlockVersions::_saveStateFile() const { + std::ofstream file(_stateFilePath.native().c_str()); + file.write(HEADER.c_str(), HEADER.size()); + for (const auto &entry : _knownVersions) { + file.write((char*)entry.first.data(), entry.first.BINARY_LENGTH); + file.write((char*)&entry.second, sizeof(entry.second)); + } +} + } } diff --git a/src/blockstore/implementations/versioncounting/KnownBlockVersions.h b/src/blockstore/implementations/versioncounting/KnownBlockVersions.h index e469183f..0b774a2e 100644 --- a/src/blockstore/implementations/versioncounting/KnownBlockVersions.h +++ b/src/blockstore/implementations/versioncounting/KnownBlockVersions.h @@ -4,14 +4,17 @@ #include #include +#include +#include namespace blockstore { namespace versioncounting { class KnownBlockVersions final { public: - KnownBlockVersions(); - KnownBlockVersions(KnownBlockVersions &&rhs) = default; + KnownBlockVersions(const boost::filesystem::path &stateFilePath); + KnownBlockVersions(KnownBlockVersions &&rhs); + ~KnownBlockVersions(); __attribute__((warn_unused_result)) bool checkAndUpdateVersion(const Key &key, uint64_t version); @@ -20,6 +23,16 @@ namespace blockstore { private: std::unordered_map _knownVersions; + boost::filesystem::path _stateFilePath; + bool _valid; + + static const std::string HEADER; + + static std::unordered_map _loadStateFile(const boost::filesystem::path &stateFilePath); + static void _checkHeader(std::ifstream *file); + static boost::optional> _readEntry(std::ifstream *file); + void _saveStateFile() const; + DISALLOW_COPY_AND_ASSIGN(KnownBlockVersions); }; diff --git a/test/blockstore/implementations/versioncounting/KnownBlockVersionsTest.cpp b/test/blockstore/implementations/versioncounting/KnownBlockVersionsTest.cpp index f7b2450e..372d2d64 100644 --- a/test/blockstore/implementations/versioncounting/KnownBlockVersionsTest.cpp +++ b/test/blockstore/implementations/versioncounting/KnownBlockVersionsTest.cpp @@ -1,12 +1,18 @@ #include #include +#include using blockstore::versioncounting::KnownBlockVersions; +using cpputils::TempFile; class KnownBlockVersionsTest : public ::testing::Test { public: - blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972"); + KnownBlockVersionsTest() :stateFile(false), testobj(stateFile.path()) {} + blockstore::Key key = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972"); + blockstore::Key key2 = blockstore::Key::FromString("C772972491BB4932A1389EE14BC7090A"); + + TempFile stateFile; KnownBlockVersions testobj; }; @@ -75,3 +81,45 @@ TEST_F(KnownBlockVersionsTest, checkAndUpdate_existingEntry_invalidDoesntModifyE EXPECT_FALSE(testobj.checkAndUpdateVersion(key, 99)); EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 100)); } + +TEST_F(KnownBlockVersionsTest, checkAndUpdate_twoEntriesDontInfluenceEachOther) { + testobj.updateVersion(key, 100); + testobj.updateVersion(key2, 100); + + testobj.updateVersion(key, 150); + + EXPECT_FALSE(testobj.checkAndUpdateVersion(key, 149)); + EXPECT_TRUE(testobj.checkAndUpdateVersion(key, 150)); + EXPECT_FALSE(testobj.checkAndUpdateVersion(key2, 99)); + EXPECT_TRUE(testobj.checkAndUpdateVersion(key2, 100)); +} + +TEST_F(KnownBlockVersionsTest, saveAndLoad_empty) { + TempFile stateFile(false); + KnownBlockVersions(stateFile.path()); + + EXPECT_TRUE(KnownBlockVersions(stateFile.path()).checkAndUpdateVersion(key, 0)); +} + +TEST_F(KnownBlockVersionsTest, saveAndLoad_oneentry) { + TempFile stateFile(false); + KnownBlockVersions(stateFile.path()).updateVersion(key, 100); + + EXPECT_FALSE(KnownBlockVersions(stateFile.path()).checkAndUpdateVersion(key, 99)); + EXPECT_TRUE(KnownBlockVersions(stateFile.path()).checkAndUpdateVersion(key, 100)); +} + +TEST_F(KnownBlockVersionsTest, saveAndLoad_twoentries) { + TempFile stateFile(false); + { + KnownBlockVersions obj(stateFile.path()); + obj.updateVersion(key, 100); + obj.updateVersion(key2, 50); + } + + KnownBlockVersions obj(stateFile.path()); + EXPECT_FALSE(obj.checkAndUpdateVersion(key, 99)); + EXPECT_TRUE(obj.checkAndUpdateVersion(key, 100)); + EXPECT_FALSE(obj.checkAndUpdateVersion(key2, 49)); + EXPECT_TRUE(obj.checkAndUpdateVersion(key2, 50)); +} diff --git a/test/blockstore/implementations/versioncounting/VersionCountingBlockStoreTest_Generic.cpp b/test/blockstore/implementations/versioncounting/VersionCountingBlockStoreTest_Generic.cpp index 9a465746..35bd34a5 100644 --- a/test/blockstore/implementations/versioncounting/VersionCountingBlockStoreTest_Generic.cpp +++ b/test/blockstore/implementations/versioncounting/VersionCountingBlockStoreTest_Generic.cpp @@ -2,6 +2,7 @@ #include "blockstore/implementations/testfake/FakeBlockStore.h" #include "../../testutils/BlockStoreTest.h" #include +#include using ::testing::Test; @@ -14,11 +15,15 @@ using cpputils::Data; using cpputils::DataFixture; using cpputils::make_unique_ref; using cpputils::unique_ref; +using cpputils::TempFile; class VersionCountingBlockStoreTestFixture: public BlockStoreTestFixture { public: + VersionCountingBlockStoreTestFixture() :stateFile(false) {} + + TempFile stateFile; unique_ref createBlockStore() override { - return make_unique_ref(make_unique_ref(), KnownBlockVersions()); + return make_unique_ref(make_unique_ref(), KnownBlockVersions(stateFile.path())); } }; diff --git a/test/blockstore/implementations/versioncounting/VersionCountingBlockStoreTest_Specific.cpp b/test/blockstore/implementations/versioncounting/VersionCountingBlockStoreTest_Specific.cpp index 9355c7b3..4e062666 100644 --- a/test/blockstore/implementations/versioncounting/VersionCountingBlockStoreTest_Specific.cpp +++ b/test/blockstore/implementations/versioncounting/VersionCountingBlockStoreTest_Specific.cpp @@ -3,6 +3,7 @@ #include "blockstore/implementations/testfake/FakeBlockStore.h" #include "blockstore/utils/BlockStoreUtils.h" #include +#include using ::testing::Test; @@ -10,6 +11,7 @@ using cpputils::DataFixture; using cpputils::Data; using cpputils::unique_ref; using cpputils::make_unique_ref; +using cpputils::TempFile; using blockstore::testfake::FakeBlockStore; @@ -19,10 +21,12 @@ class VersionCountingBlockStoreTest: public Test { public: static constexpr unsigned int BLOCKSIZE = 1024; VersionCountingBlockStoreTest(): + stateFile(false), baseBlockStore(new FakeBlockStore), - blockStore(make_unique_ref(std::move(cpputils::nullcheck(std::unique_ptr(baseBlockStore)).value()), KnownBlockVersions())), + blockStore(make_unique_ref(std::move(cpputils::nullcheck(std::unique_ptr(baseBlockStore)).value()), KnownBlockVersions(stateFile.path()))), data(DataFixture::generate(BLOCKSIZE)) { } + TempFile stateFile; FakeBlockStore *baseBlockStore; unique_ref blockStore; Data data;