LookupCacheV4.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. //* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "LookupCacheV4.h"
  6. #include "HashStore.h"
  7. #include "mozilla/Unused.h"
  8. #include <string>
  9. // MOZ_LOG=UrlClassifierDbService:5
  10. extern mozilla::LazyLogModule gUrlClassifierDbServiceLog;
  11. #define LOG(args) MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args)
  12. #define LOG_ENABLED() MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug)
  13. #define METADATA_SUFFIX NS_LITERAL_CSTRING(".metadata")
  14. namespace mozilla {
  15. namespace safebrowsing {
  16. const int LookupCacheV4::VER = 4;
  17. // Prefixes coming from updates and VLPrefixSet are both stored in the HashTable
  18. // where the (key, value) pair is a prefix size and a lexicographic-sorted string.
  19. // The difference is prefixes from updates use std:string(to avoid additional copies)
  20. // and prefixes from VLPrefixSet use nsCString.
  21. // This class provides a common interface for the partial update algorithm to make it
  22. // easier to operate on two different kind prefix string map..
  23. class VLPrefixSet
  24. {
  25. public:
  26. explicit VLPrefixSet(const PrefixStringMap& aMap);
  27. explicit VLPrefixSet(const TableUpdateV4::PrefixStdStringMap& aMap);
  28. // This function will merge the prefix map in VLPrefixSet to aPrefixMap.
  29. void Merge(PrefixStringMap& aPrefixMap);
  30. // Find the smallest string from the map in VLPrefixSet.
  31. bool GetSmallestPrefix(nsDependentCSubstring& aOutString);
  32. // Return the number of prefixes in the map
  33. uint32_t Count() const { return mCount; }
  34. private:
  35. // PrefixString structure contains a lexicographic-sorted string with
  36. // a |pos| variable to indicate which substring we are pointing to right now.
  37. // |pos| increases each time GetSmallestPrefix finds the smallest string.
  38. struct PrefixString {
  39. PrefixString(const nsACString& aStr, uint32_t aSize)
  40. : pos(0)
  41. , size(aSize)
  42. {
  43. data.Rebind(aStr.BeginReading(), aStr.Length());
  44. }
  45. const char* get() {
  46. return pos < data.Length() ? data.BeginReading() + pos : nullptr;
  47. }
  48. void next() { pos += size; }
  49. uint32_t remaining() { return data.Length() - pos; }
  50. nsDependentCSubstring data;
  51. uint32_t pos;
  52. uint32_t size;
  53. };
  54. nsClassHashtable<nsUint32HashKey, PrefixString> mMap;
  55. uint32_t mCount;
  56. };
  57. nsresult
  58. LookupCacheV4::Init()
  59. {
  60. mVLPrefixSet = new VariableLengthPrefixSet();
  61. nsresult rv = mVLPrefixSet->Init(mTableName);
  62. NS_ENSURE_SUCCESS(rv, rv);
  63. return NS_OK;
  64. }
  65. nsresult
  66. LookupCacheV4::Has(const Completion& aCompletion,
  67. bool* aHas, bool* aComplete)
  68. {
  69. *aHas = false;
  70. uint32_t length = 0;
  71. nsDependentCSubstring fullhash;
  72. fullhash.Rebind((const char *)aCompletion.buf, COMPLETE_SIZE);
  73. nsresult rv = mVLPrefixSet->Matches(fullhash, &length);
  74. NS_ENSURE_SUCCESS(rv, rv);
  75. *aHas = length >= PREFIX_SIZE;
  76. *aComplete = length == COMPLETE_SIZE;
  77. if (LOG_ENABLED()) {
  78. uint32_t prefix = aCompletion.ToUint32();
  79. LOG(("Probe in V4 %s: %X, found %d, complete %d", mTableName.get(),
  80. prefix, *aHas, *aComplete));
  81. }
  82. return NS_OK;
  83. }
  84. nsresult
  85. LookupCacheV4::Build(PrefixStringMap& aPrefixMap)
  86. {
  87. return mVLPrefixSet->SetPrefixes(aPrefixMap);
  88. }
  89. nsresult
  90. LookupCacheV4::GetPrefixes(PrefixStringMap& aPrefixMap)
  91. {
  92. return mVLPrefixSet->GetPrefixes(aPrefixMap);
  93. }
  94. nsresult
  95. LookupCacheV4::ClearPrefixes()
  96. {
  97. // Clear by seting a empty map
  98. PrefixStringMap map;
  99. return mVLPrefixSet->SetPrefixes(map);
  100. }
  101. nsresult
  102. LookupCacheV4::StoreToFile(nsIFile* aFile)
  103. {
  104. return mVLPrefixSet->StoreToFile(aFile);
  105. }
  106. nsresult
  107. LookupCacheV4::LoadFromFile(nsIFile* aFile)
  108. {
  109. nsresult rv = mVLPrefixSet->LoadFromFile(aFile);
  110. if (NS_FAILED(rv)) {
  111. return rv;
  112. }
  113. nsCString state, checksum;
  114. rv = LoadMetadata(state, checksum);
  115. if (NS_FAILED(rv)) {
  116. return rv;
  117. }
  118. rv = VerifyChecksum(checksum);
  119. return rv;
  120. }
  121. size_t
  122. LookupCacheV4::SizeOfPrefixSet()
  123. {
  124. return mVLPrefixSet->SizeOfIncludingThis(moz_malloc_size_of);
  125. }
  126. static void
  127. AppendPrefixToMap(PrefixStringMap& prefixes, nsDependentCSubstring& prefix)
  128. {
  129. if (!prefix.Length()) {
  130. return;
  131. }
  132. nsCString* prefixString = prefixes.LookupOrAdd(prefix.Length());
  133. prefixString->Append(prefix.BeginReading(), prefix.Length());
  134. }
  135. // Read prefix into a buffer and also update the hash which
  136. // keeps track of the checksum
  137. static void
  138. UpdateChecksum(nsICryptoHash* aCrypto, const nsACString& aPrefix)
  139. {
  140. MOZ_ASSERT(aCrypto);
  141. aCrypto->Update(reinterpret_cast<uint8_t*>(const_cast<char*>(
  142. aPrefix.BeginReading())),
  143. aPrefix.Length());
  144. }
  145. // Please see https://bug1287058.bmoattachments.org/attachment.cgi?id=8795366
  146. // for detail about partial update algorithm.
  147. nsresult
  148. LookupCacheV4::ApplyUpdate(TableUpdateV4* aTableUpdate,
  149. PrefixStringMap& aInputMap,
  150. PrefixStringMap& aOutputMap)
  151. {
  152. MOZ_ASSERT(aOutputMap.IsEmpty());
  153. nsCOMPtr<nsICryptoHash> crypto;
  154. nsresult rv = InitCrypto(crypto);
  155. if (NS_FAILED(rv)) {
  156. return rv;
  157. }
  158. // oldPSet contains prefixes we already have or we just merged last round.
  159. // addPSet contains prefixes stored in tableUpdate which should be merged with oldPSet.
  160. VLPrefixSet oldPSet(aInputMap);
  161. VLPrefixSet addPSet(aTableUpdate->Prefixes());
  162. // RemovalIndiceArray is a sorted integer array indicating the index of prefix we should
  163. // remove from the old prefix set(according to lexigraphic order).
  164. // |removalIndex| is the current index of RemovalIndiceArray.
  165. // |numOldPrefixPicked| is used to record how many prefixes we picked from the old map.
  166. TableUpdateV4::RemovalIndiceArray& removalArray = aTableUpdate->RemovalIndices();
  167. uint32_t removalIndex = 0;
  168. int32_t numOldPrefixPicked = -1;
  169. nsDependentCSubstring smallestOldPrefix;
  170. nsDependentCSubstring smallestAddPrefix;
  171. bool isOldMapEmpty = false, isAddMapEmpty = false;
  172. // This is used to avoid infinite loop for partial update algorithm.
  173. // The maximum loops will be the number of old prefixes plus the number of add prefixes.
  174. int32_t index = oldPSet.Count() + addPSet.Count() + 1;
  175. for(;index > 0; index--) {
  176. // Get smallest prefix from the old prefix set if we don't have one
  177. if (smallestOldPrefix.IsEmpty() && !isOldMapEmpty) {
  178. isOldMapEmpty = !oldPSet.GetSmallestPrefix(smallestOldPrefix);
  179. }
  180. // Get smallest prefix from add prefix set if we don't have one
  181. if (smallestAddPrefix.IsEmpty() && !isAddMapEmpty) {
  182. isAddMapEmpty = !addPSet.GetSmallestPrefix(smallestAddPrefix);
  183. }
  184. bool pickOld;
  185. // If both prefix sets are not empty, then compare to find the smaller one.
  186. if (!isOldMapEmpty && !isAddMapEmpty) {
  187. if (smallestOldPrefix == smallestAddPrefix) {
  188. LOG(("Add prefix should not exist in the original prefix set."));
  189. return NS_ERROR_FAILURE;
  190. }
  191. // Compare the smallest string in old prefix set and add prefix set,
  192. // merge the smaller one into new map to ensure merged string still
  193. // follows lexigraphic order.
  194. pickOld = smallestOldPrefix < smallestAddPrefix;
  195. } else if (!isOldMapEmpty && isAddMapEmpty) {
  196. pickOld = true;
  197. } else if (isOldMapEmpty && !isAddMapEmpty) {
  198. pickOld = false;
  199. // If both maps are empty, then partial update is complete.
  200. } else {
  201. break;
  202. }
  203. if (pickOld) {
  204. numOldPrefixPicked++;
  205. // If the number of picks from old map matches the removalIndex, then this prefix
  206. // will be removed by not merging it to new map.
  207. if (removalIndex < removalArray.Length() &&
  208. numOldPrefixPicked == removalArray[removalIndex]) {
  209. removalIndex++;
  210. } else {
  211. AppendPrefixToMap(aOutputMap, smallestOldPrefix);
  212. UpdateChecksum(crypto, smallestOldPrefix);
  213. }
  214. smallestOldPrefix.SetLength(0);
  215. } else {
  216. AppendPrefixToMap(aOutputMap, smallestAddPrefix);
  217. UpdateChecksum(crypto, smallestAddPrefix);
  218. smallestAddPrefix.SetLength(0);
  219. }
  220. }
  221. // We expect index will be greater to 0 because max number of runs will be
  222. // the number of original prefix plus add prefix.
  223. if (index <= 0) {
  224. LOG(("There are still prefixes remaining after reaching maximum runs."));
  225. return NS_ERROR_FAILURE;
  226. }
  227. if (removalIndex < removalArray.Length()) {
  228. LOG(("There are still prefixes to remove after exhausting the old PrefixSet."));
  229. return NS_ERROR_FAILURE;
  230. }
  231. nsAutoCString checksum;
  232. crypto->Finish(false, checksum);
  233. if (aTableUpdate->Checksum().IsEmpty()) {
  234. LOG(("Update checksum missing."));
  235. // Generate our own checksum to tableUpdate to ensure there is always
  236. // checksum in .metadata
  237. std::string stdChecksum(checksum.BeginReading(), checksum.Length());
  238. aTableUpdate->NewChecksum(stdChecksum);
  239. } else if (aTableUpdate->Checksum() != checksum){
  240. LOG(("Checksum mismatch after applying partial update"));
  241. return NS_ERROR_FAILURE;
  242. }
  243. return NS_OK;
  244. }
  245. nsresult
  246. LookupCacheV4::InitCrypto(nsCOMPtr<nsICryptoHash>& aCrypto)
  247. {
  248. nsresult rv;
  249. aCrypto = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
  250. if (NS_WARN_IF(NS_FAILED(rv))) {
  251. return rv;
  252. }
  253. rv = aCrypto->Init(nsICryptoHash::SHA256);
  254. NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "InitCrypto failed");
  255. return rv;
  256. }
  257. nsresult
  258. LookupCacheV4::VerifyChecksum(const nsACString& aChecksum)
  259. {
  260. nsCOMPtr<nsICryptoHash> crypto;
  261. nsresult rv = InitCrypto(crypto);
  262. if (NS_FAILED(rv)) {
  263. return rv;
  264. }
  265. PrefixStringMap map;
  266. mVLPrefixSet->GetPrefixes(map);
  267. VLPrefixSet loadPSet(map);
  268. uint32_t index = loadPSet.Count() + 1;
  269. for(;index > 0; index--) {
  270. nsDependentCSubstring prefix;
  271. if (!loadPSet.GetSmallestPrefix(prefix)) {
  272. break;
  273. }
  274. UpdateChecksum(crypto, prefix);
  275. }
  276. nsAutoCString checksum;
  277. crypto->Finish(false, checksum);
  278. if (checksum != aChecksum) {
  279. LOG(("Checksum mismatch when loading prefixes from file."));
  280. return NS_ERROR_FILE_CORRUPTED;
  281. }
  282. return NS_OK;
  283. }
  284. //////////////////////////////////////////////////////////////////////////
  285. // A set of lightweight functions for reading/writing value from/to file.
  286. namespace {
  287. template<typename T>
  288. struct ValueTraits
  289. {
  290. static uint32_t Length(const T& aValue) { return sizeof(T); }
  291. static char* WritePtr(T& aValue, uint32_t aLength) { return (char*)&aValue; }
  292. static const char* ReadPtr(const T& aValue) { return (char*)&aValue; }
  293. static bool IsFixedLength() { return true; }
  294. };
  295. template<>
  296. struct ValueTraits<nsACString>
  297. {
  298. static bool IsFixedLength() { return false; }
  299. static uint32_t Length(const nsACString& aValue)
  300. {
  301. return aValue.Length();
  302. }
  303. static char* WritePtr(nsACString& aValue, uint32_t aLength)
  304. {
  305. aValue.SetLength(aLength);
  306. return aValue.BeginWriting();
  307. }
  308. static const char* ReadPtr(const nsACString& aValue)
  309. {
  310. return aValue.BeginReading();
  311. }
  312. };
  313. template<typename T> static nsresult
  314. WriteValue(nsIOutputStream *aOutputStream, const T& aValue)
  315. {
  316. uint32_t writeLength = ValueTraits<T>::Length(aValue);
  317. if (!ValueTraits<T>::IsFixedLength()) {
  318. // We need to write out the variable value length.
  319. nsresult rv = WriteValue(aOutputStream, writeLength);
  320. NS_ENSURE_SUCCESS(rv, rv);
  321. }
  322. // Write out the value.
  323. auto valueReadPtr = ValueTraits<T>::ReadPtr(aValue);
  324. uint32_t written;
  325. nsresult rv = aOutputStream->Write(valueReadPtr, writeLength, &written);
  326. if (NS_FAILED(rv) || written != writeLength) {
  327. LOG(("Failed to write the value."));
  328. return NS_FAILED(rv) ? rv : NS_ERROR_FAILURE;
  329. }
  330. return rv;
  331. }
  332. template<typename T> static nsresult
  333. ReadValue(nsIInputStream* aInputStream, T& aValue)
  334. {
  335. nsresult rv;
  336. uint32_t readLength;
  337. if (ValueTraits<T>::IsFixedLength()) {
  338. readLength = ValueTraits<T>::Length(aValue);
  339. } else {
  340. // Read the variable value length from file.
  341. nsresult rv = ReadValue(aInputStream, readLength);
  342. NS_ENSURE_SUCCESS(rv, rv);
  343. }
  344. // Read the value.
  345. uint32_t read;
  346. auto valueWritePtr = ValueTraits<T>::WritePtr(aValue, readLength);
  347. rv = aInputStream->Read(valueWritePtr, readLength, &read);
  348. if (NS_FAILED(rv) || read != readLength) {
  349. LOG(("Failed to read the value."));
  350. return NS_FAILED(rv) ? rv : NS_ERROR_FAILURE;
  351. }
  352. return rv;
  353. }
  354. } // end of unnamed namespace.
  355. ////////////////////////////////////////////////////////////////////////
  356. nsresult
  357. LookupCacheV4::WriteMetadata(TableUpdateV4* aTableUpdate)
  358. {
  359. NS_ENSURE_ARG_POINTER(aTableUpdate);
  360. if (nsUrlClassifierDBService::ShutdownHasStarted()) {
  361. return NS_ERROR_ABORT;
  362. }
  363. nsCOMPtr<nsIFile> metaFile;
  364. nsresult rv = mStoreDirectory->Clone(getter_AddRefs(metaFile));
  365. NS_ENSURE_SUCCESS(rv, rv);
  366. rv = metaFile->AppendNative(mTableName + METADATA_SUFFIX);
  367. NS_ENSURE_SUCCESS(rv, rv);
  368. nsCOMPtr<nsIOutputStream> outputStream;
  369. rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), metaFile,
  370. PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE);
  371. if (!NS_SUCCEEDED(rv)) {
  372. LOG(("Unable to create file to store metadata."));
  373. return rv;
  374. }
  375. // Write the state.
  376. rv = WriteValue(outputStream, aTableUpdate->ClientState());
  377. if (NS_FAILED(rv)) {
  378. LOG(("Failed to write the list state."));
  379. return rv;
  380. }
  381. // Write the checksum.
  382. rv = WriteValue(outputStream, aTableUpdate->Checksum());
  383. if (NS_FAILED(rv)) {
  384. LOG(("Failed to write the list checksum."));
  385. return rv;
  386. }
  387. return rv;
  388. }
  389. nsresult
  390. LookupCacheV4::LoadMetadata(nsACString& aState, nsACString& aChecksum)
  391. {
  392. nsCOMPtr<nsIFile> metaFile;
  393. nsresult rv = mStoreDirectory->Clone(getter_AddRefs(metaFile));
  394. NS_ENSURE_SUCCESS(rv, rv);
  395. rv = metaFile->AppendNative(mTableName + METADATA_SUFFIX);
  396. NS_ENSURE_SUCCESS(rv, rv);
  397. nsCOMPtr<nsIInputStream> localInFile;
  398. rv = NS_NewLocalFileInputStream(getter_AddRefs(localInFile), metaFile,
  399. PR_RDONLY | nsIFile::OS_READAHEAD);
  400. if (NS_FAILED(rv)) {
  401. LOG(("Unable to open metadata file."));
  402. return rv;
  403. }
  404. // Read the list state.
  405. rv = ReadValue(localInFile, aState);
  406. if (NS_FAILED(rv)) {
  407. LOG(("Failed to read state."));
  408. return rv;
  409. }
  410. // Read the checksum.
  411. rv = ReadValue(localInFile, aChecksum);
  412. if (NS_FAILED(rv)) {
  413. LOG(("Failed to read checksum."));
  414. return rv;
  415. }
  416. return rv;
  417. }
  418. VLPrefixSet::VLPrefixSet(const PrefixStringMap& aMap)
  419. : mCount(0)
  420. {
  421. for (auto iter = aMap.ConstIter(); !iter.Done(); iter.Next()) {
  422. uint32_t size = iter.Key();
  423. mMap.Put(size, new PrefixString(*iter.Data(), size));
  424. mCount += iter.Data()->Length() / size;
  425. }
  426. }
  427. VLPrefixSet::VLPrefixSet(const TableUpdateV4::PrefixStdStringMap& aMap)
  428. : mCount(0)
  429. {
  430. for (auto iter = aMap.ConstIter(); !iter.Done(); iter.Next()) {
  431. uint32_t size = iter.Key();
  432. mMap.Put(size, new PrefixString(iter.Data()->GetPrefixString(), size));
  433. mCount += iter.Data()->GetPrefixString().Length() / size;
  434. }
  435. }
  436. void
  437. VLPrefixSet::Merge(PrefixStringMap& aPrefixMap) {
  438. for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
  439. nsCString* prefixString = aPrefixMap.LookupOrAdd(iter.Key());
  440. PrefixString* str = iter.Data();
  441. if (str->get()) {
  442. prefixString->Append(str->get(), str->remaining());
  443. }
  444. }
  445. }
  446. bool
  447. VLPrefixSet::GetSmallestPrefix(nsDependentCSubstring& aOutString) {
  448. PrefixString* pick = nullptr;
  449. for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
  450. PrefixString* str = iter.Data();
  451. if (!str->get()) {
  452. continue;
  453. }
  454. if (aOutString.IsEmpty()) {
  455. aOutString.Rebind(str->get(), iter.Key());
  456. pick = str;
  457. continue;
  458. }
  459. nsDependentCSubstring cur(str->get(), iter.Key());
  460. if (cur < aOutString) {
  461. aOutString.Rebind(str->get(), iter.Key());
  462. pick = str;
  463. }
  464. }
  465. if (pick) {
  466. pick->next();
  467. }
  468. return pick != nullptr;
  469. }
  470. } // namespace safebrowsing
  471. } // namespace mozilla