diff --git a/platform/country_file.hpp b/platform/country_file.hpp index c7f253ce5d..33f756ca2b 100644 --- a/platform/country_file.hpp +++ b/platform/country_file.hpp @@ -23,6 +23,9 @@ public: void SetRemoteSizes(TMwmSize mapSize, TMwmSize routingSize); TMwmSize GetRemoteSize(MapOptions file) const; + void SetSha1(string const & base64Sha1) { m_sha1 = base64Sha1; } + string const & GetSha1() const { return m_sha1; } + inline bool operator<(const CountryFile & rhs) const { return m_name < rhs.m_name; } inline bool operator==(const CountryFile & rhs) const { return m_name == rhs.m_name; } inline bool operator!=(const CountryFile & rhs) const { return !(*this == rhs); } @@ -34,6 +37,8 @@ private: string m_name; TMwmSize m_mapSize = 0; TMwmSize m_routingSize = 0; + /// \note SHA1 is encoded to base64. + string m_sha1; }; /// \returns This method returns file name with extension. For example Abkhazia.mwm or diff --git a/platform/local_country_file.cpp b/platform/local_country_file.cpp index 44a7505196..54179e4155 100644 --- a/platform/local_country_file.cpp +++ b/platform/local_country_file.cpp @@ -4,6 +4,7 @@ #include "coding/internal/file_data.hpp" #include "coding/file_name_utils.hpp" +#include "coding/sha1.hpp" #include "base/logging.hpp" @@ -106,6 +107,11 @@ bool LocalCountryFile::operator==(LocalCountryFile const & rhs) const m_version == rhs.m_version && m_files == rhs.m_files; } +bool LocalCountryFile::ValidateIntegrity() const +{ + return coding::SHA1::CalculateBase64(GetPath(MapOptions::Map)) == m_countryFile.GetSha1(); +} + // static LocalCountryFile LocalCountryFile::MakeForTesting(string const & countryFileName, int64_t version) { diff --git a/platform/local_country_file.hpp b/platform/local_country_file.hpp index d86f848344..0a1b73ae86 100644 --- a/platform/local_country_file.hpp +++ b/platform/local_country_file.hpp @@ -76,6 +76,8 @@ public: bool operator==(LocalCountryFile const & rhs) const; bool operator!=(LocalCountryFile const & rhs) const { return !(*this == rhs); } + bool ValidateIntegrity() const; + // Creates LocalCountryFile for test purposes, for a country region // with countryFileName (without any extensions). Automatically // performs sync with disk. diff --git a/storage/country.cpp b/storage/country.cpp index 8388becbca..13b8854682 100644 --- a/storage/country.cpp +++ b/storage/country.cpp @@ -28,7 +28,8 @@ class StoreSingleMwmInterface { public: virtual ~StoreSingleMwmInterface() = default; - virtual Country * InsertToCountryTree(TCountryId const & id, TMwmSize mapSize, size_t depth, + virtual Country * InsertToCountryTree(TCountryId const & id, TMwmSize mapSize, + string const & mapSha1, size_t depth, TCountryId const & parent) = 0; virtual void InsertOldMwmMapping(TCountryId const & newId, TCountryId const & oldId) = 0; virtual void InsertAffiliation(TCountryId const & countryId, string const & affilation) = 0; @@ -53,14 +54,15 @@ public: } // StoreSingleMwmInterface overrides: - Country * InsertToCountryTree(TCountryId const & id, TMwmSize mapSize, size_t depth, - TCountryId const & parent) override + Country * InsertToCountryTree(TCountryId const & id, TMwmSize mapSize, string const & mapSha1, + size_t depth, TCountryId const & parent) override { Country country(id, parent); if (mapSize) { CountryFile countryFile(id); countryFile.SetRemoteSizes(mapSize, 0 /* routingSize */); + countryFile.SetSha1(mapSha1); country.SetFile(countryFile); } return &m_countries.AddAtDepth(depth, country); @@ -90,7 +92,8 @@ class StoreFile2InfoSingleMwms : public StoreSingleMwmInterface public: StoreFile2InfoSingleMwms(map & file2info) : m_file2info(file2info) {} // StoreSingleMwmInterface overrides: - Country * InsertToCountryTree(TCountryId const & id, TMwmSize /* mapSize */, size_t /* depth */, + Country * InsertToCountryTree(TCountryId const & id, TMwmSize /* mapSize */, + string const & /* mapSha1 */, size_t /* depth */, TCountryId const & /* parent */) override { CountryInfo info(id); @@ -134,8 +137,12 @@ TMwmSubtreeAttrs LoadGroupSingleMwmsImpl(size_t depth, json_t * node, TCountryId int nodeSize; FromJSONObjectOptionalField(node, "s", nodeSize); ASSERT_LESS_OR_EQUAL(0, nodeSize, ()); + + string nodeHash; + FromJSONObjectOptionalField(node, "sha1_base64", nodeHash); + // We expect that mwm and routing files should be less than 2GB. - Country * addedNode = store.InsertToCountryTree(id, nodeSize, depth, parent); + Country * addedNode = store.InsertToCountryTree(id, nodeSize, nodeHash, depth, parent); TMwmCounter mwmCounter = 0; TMwmSize mwmSize = 0; diff --git a/storage/storage.cpp b/storage/storage.cpp index a5296d5c7c..b3974b7347 100644 --- a/storage/storage.cpp +++ b/storage/storage.cpp @@ -73,6 +73,20 @@ TCountryTreeNode const & LeafNodeFromCountryId(TCountryTree const & root, CHECK(node, ("Node with id =", countryId, "not found in country tree as a leaf.")); return *node; } + +bool ValidateIntegrity(TLocalFilePtr mapLocalFile, string const & countryId, int64_t version, + string const & source) +{ + if (mapLocalFile->ValidateIntegrity()) + return true; + + alohalytics::LogEvent("$MapIntegrityFailure", + alohalytics::TStringMap({{"mwm", countryId}, + {"version", strings::to_string(version)}, + {"source", source}})); + base::DeleteFileX(mapLocalFile->GetPath(MapOptions::Map)); + return false; +} } // namespace void GetQueuedCountries(Storage::TQueue const & queue, TCountriesSet & resultCountries) @@ -961,6 +975,13 @@ void Storage::RegisterDownloadedFiles(TCountryId const & countryId, MapOptions o return; } + static string const kSourceKey = "map"; + if (!ValidateIntegrity(localFile, countryId, GetCurrentDataVersion(), kSourceKey)) + { + fn(false /* isSuccess */); + return; + } + RegisterCountryFiles(localFile); fn(true); } @@ -1531,14 +1552,22 @@ void Storage::ApplyDiff(TCountryId const & countryId, functionGetCountryName(), + GetCurrentDataVersion(), kSourceKey)) { - if (result) + applyResult = false; + } + + GetPlatform().RunTask(Platform::Thread::Gui, [this, fn, diffFile, applyResult] + { + if (applyResult) { RegisterCountryFiles(diffFile); Platform::DisableBackupForFile(diffFile->GetPath(MapOptions::Map)); } - fn(result); + fn(applyResult); }); }); }