Line data Source code
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 :
6 : #include "LookupCache.h"
7 : #include "HashStore.h"
8 : #include "nsISeekableStream.h"
9 : #include "mozilla/ArrayUtils.h"
10 : #include "mozilla/Telemetry.h"
11 : #include "mozilla/Logging.h"
12 : #include "nsNetUtil.h"
13 : #include "prprf.h"
14 : #include "Classifier.h"
15 : #include "nsUrlClassifierInfo.h"
16 :
17 : // We act as the main entry point for all the real lookups,
18 : // so note that those are not done to the actual HashStore.
19 : // The latter solely exists to store the data needed to handle
20 : // the updates from the protocol.
21 :
22 : // This module provides a front for PrefixSet, mUpdateCompletions,
23 : // and mGetHashCache, which together contain everything needed to
24 : // provide a classification as long as the data is up to date.
25 :
26 : // PrefixSet stores and provides lookups for 4-byte prefixes.
27 : // mUpdateCompletions contains 32-byte completions which were
28 : // contained in updates. They are retrieved from HashStore/.sbtore
29 : // on startup.
30 : // mGetHashCache contains 32-byte completions which were
31 : // returned from the gethash server. They are not serialized,
32 : // only cached until the next update.
33 :
34 : // Name of the persistent PrefixSet storage
35 : #define PREFIXSET_SUFFIX ".pset"
36 :
37 : #define V2_CACHE_DURATION_SEC (15 * 60)
38 :
39 : // MOZ_LOG=UrlClassifierDbService:5
40 : extern mozilla::LazyLogModule gUrlClassifierDbServiceLog;
41 : #define LOG(args) MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args)
42 : #define LOG_ENABLED() MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug)
43 :
44 : namespace mozilla {
45 : namespace safebrowsing {
46 :
47 : const int CacheResultV2::VER = CacheResult::V2;
48 : const int CacheResultV4::VER = CacheResult::V4;
49 :
50 : const int LookupCacheV2::VER = 2;
51 :
52 : static
53 0 : void CStringToHexString(const nsACString& aIn, nsACString& aOut)
54 : {
55 : static const char* const lut = "0123456789ABCDEF";
56 :
57 0 : size_t len = aIn.Length();
58 0 : MOZ_ASSERT(len <= COMPLETE_SIZE);
59 :
60 0 : aOut.SetCapacity(2 * len);
61 0 : for (size_t i = 0; i < aIn.Length(); ++i) {
62 0 : const char c = static_cast<const char>(aIn[i]);
63 0 : aOut.Append(lut[(c >> 4) & 0x0F]);
64 0 : aOut.Append(lut[c & 15]);
65 : }
66 0 : }
67 :
68 13 : LookupCache::LookupCache(const nsACString& aTableName,
69 : const nsACString& aProvider,
70 13 : nsIFile* aRootStoreDir)
71 : : mPrimed(false)
72 : , mTableName(aTableName)
73 : , mProvider(aProvider)
74 13 : , mRootStoreDirectory(aRootStoreDir)
75 : {
76 13 : UpdateRootDirHandle(mRootStoreDirectory);
77 13 : }
78 :
79 : nsresult
80 13 : LookupCache::Open()
81 : {
82 13 : LOG(("Loading PrefixSet"));
83 13 : nsresult rv = LoadPrefixSet();
84 13 : NS_ENSURE_SUCCESS(rv, rv);
85 :
86 13 : return NS_OK;
87 : }
88 :
89 : nsresult
90 19 : LookupCache::UpdateRootDirHandle(nsIFile* aNewRootStoreDirectory)
91 : {
92 : nsresult rv;
93 :
94 19 : if (aNewRootStoreDirectory != mRootStoreDirectory) {
95 6 : rv = aNewRootStoreDirectory->Clone(getter_AddRefs(mRootStoreDirectory));
96 6 : NS_ENSURE_SUCCESS(rv, rv);
97 : }
98 :
99 19 : rv = Classifier::GetPrivateStoreDirectory(mRootStoreDirectory,
100 : mTableName,
101 : mProvider,
102 38 : getter_AddRefs(mStoreDirectory));
103 :
104 19 : if (NS_FAILED(rv)) {
105 0 : LOG(("Failed to get private store directory for %s", mTableName.get()));
106 0 : mStoreDirectory = mRootStoreDirectory;
107 : }
108 :
109 19 : if (LOG_ENABLED()) {
110 0 : nsString path;
111 0 : mStoreDirectory->GetPath(path);
112 0 : LOG(("Private store directory for %s is %s", mTableName.get(),
113 : NS_ConvertUTF16toUTF8(path).get()));
114 : }
115 :
116 19 : return rv;
117 : }
118 :
119 : nsresult
120 6 : LookupCache::WriteFile()
121 : {
122 6 : if (nsUrlClassifierDBService::ShutdownHasStarted()) {
123 0 : return NS_ERROR_ABORT;
124 : }
125 :
126 12 : nsCOMPtr<nsIFile> psFile;
127 6 : nsresult rv = mStoreDirectory->Clone(getter_AddRefs(psFile));
128 6 : NS_ENSURE_SUCCESS(rv, rv);
129 :
130 6 : rv = psFile->AppendNative(mTableName + NS_LITERAL_CSTRING(PREFIXSET_SUFFIX));
131 6 : NS_ENSURE_SUCCESS(rv, rv);
132 :
133 6 : rv = StoreToFile(psFile);
134 6 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "failed to store the prefixset");
135 :
136 6 : return NS_OK;
137 : }
138 :
139 : nsresult
140 0 : LookupCache::CheckCache(const Completion& aCompletion,
141 : bool* aHas,
142 : bool* aConfirmed)
143 : {
144 : // Shouldn't call this function if prefix is not in the database.
145 0 : MOZ_ASSERT(*aHas);
146 :
147 0 : *aConfirmed = false;
148 :
149 0 : uint32_t prefix = aCompletion.ToUint32();
150 :
151 0 : CachedFullHashResponse* fullHashResponse = mFullHashCache.Get(prefix);
152 0 : if (!fullHashResponse) {
153 0 : return NS_OK;
154 : }
155 :
156 0 : int64_t nowSec = PR_Now() / PR_USEC_PER_SEC;
157 : int64_t expiryTimeSec;
158 :
159 0 : FullHashExpiryCache& fullHashes = fullHashResponse->fullHashes;
160 : nsDependentCSubstring completion(
161 0 : reinterpret_cast<const char*>(aCompletion.buf), COMPLETE_SIZE);
162 :
163 : // Check if we can find the fullhash in positive cache
164 0 : if (fullHashes.Get(completion, &expiryTimeSec)) {
165 0 : if (nowSec <= expiryTimeSec) {
166 : // Url is NOT safe.
167 0 : *aConfirmed = true;
168 0 : LOG(("Found a valid fullhash in the positive cache"));
169 : } else {
170 : // Trigger a gethash request in this case(aConfirmed is false).
171 0 : LOG(("Found an expired fullhash in the positive cache"));
172 :
173 : // Remove fullhash entry from the cache when the negative cache
174 : // is also expired because whether or not the fullhash is cached
175 : // locally, we will need to consult the server next time we
176 : // lookup this hash. We may as well remove it from our cache.
177 0 : if (fullHashResponse->negativeCacheExpirySec < expiryTimeSec) {
178 0 : fullHashes.Remove(completion);
179 0 : if (fullHashes.Count() == 0 &&
180 0 : fullHashResponse->negativeCacheExpirySec < nowSec) {
181 0 : mFullHashCache.Remove(prefix);
182 : }
183 : }
184 : }
185 0 : return NS_OK;
186 : }
187 :
188 : // Check negative cache.
189 0 : if (fullHashResponse->negativeCacheExpirySec >= nowSec) {
190 : // Url is safe.
191 0 : LOG(("Found a valid prefix in the negative cache"));
192 0 : *aHas = false;
193 : } else {
194 0 : LOG(("Found an expired prefix in the negative cache"));
195 0 : if (fullHashes.Count() == 0) {
196 0 : mFullHashCache.Remove(prefix);
197 : }
198 : }
199 :
200 0 : return NS_OK;
201 : }
202 :
203 : // This function remove cache entries whose negative cache time is expired.
204 : // It is possible that a cache entry whose positive cache time is not yet
205 : // expired but still being removed after calling this API. Right now we call
206 : // this on every update.
207 : void
208 6 : LookupCache::InvalidateExpiredCacheEntries()
209 : {
210 6 : int64_t nowSec = PR_Now() / PR_USEC_PER_SEC;
211 :
212 6 : for (auto iter = mFullHashCache.Iter(); !iter.Done(); iter.Next()) {
213 0 : CachedFullHashResponse* response = iter.Data();
214 0 : if (response->negativeCacheExpirySec < nowSec) {
215 0 : iter.Remove();
216 : }
217 : }
218 6 : }
219 :
220 : void
221 6 : LookupCache::CopyFullHashCache(const LookupCache* aSource)
222 : {
223 6 : if (!aSource) {
224 0 : return;
225 : }
226 :
227 6 : CopyClassHashTable<FullHashResponseMap>(aSource->mFullHashCache,
228 6 : mFullHashCache);
229 : }
230 :
231 : void
232 0 : LookupCache::ClearCache()
233 : {
234 0 : mFullHashCache.Clear();
235 0 : }
236 :
237 : void
238 0 : LookupCache::ClearAll()
239 : {
240 0 : ClearCache();
241 0 : ClearPrefixes();
242 0 : mPrimed = false;
243 0 : }
244 :
245 : void
246 0 : LookupCache::GetCacheInfo(nsIUrlClassifierCacheInfo** aCache)
247 : {
248 0 : MOZ_ASSERT(aCache);
249 :
250 0 : RefPtr<nsUrlClassifierCacheInfo> info = new nsUrlClassifierCacheInfo;
251 0 : info->table = mTableName;
252 :
253 0 : for (auto iter = mFullHashCache.ConstIter(); !iter.Done(); iter.Next()) {
254 0 : RefPtr<nsUrlClassifierCacheEntry> entry = new nsUrlClassifierCacheEntry;
255 :
256 : // Set prefix of the cache entry.
257 0 : nsAutoCString prefix(reinterpret_cast<const char*>(&iter.Key()), PREFIX_SIZE);
258 0 : CStringToHexString(prefix, entry->prefix);
259 :
260 : // Set expiry of the cache entry.
261 0 : CachedFullHashResponse* response = iter.Data();
262 0 : entry->expirySec = response->negativeCacheExpirySec;
263 :
264 : // Set positive cache.
265 0 : FullHashExpiryCache& fullHashes = response->fullHashes;
266 0 : for (auto iter2 = fullHashes.ConstIter(); !iter2.Done(); iter2.Next()) {
267 : RefPtr<nsUrlClassifierPositiveCacheEntry> match =
268 0 : new nsUrlClassifierPositiveCacheEntry;
269 :
270 : // Set fullhash of positive cache entry.
271 0 : CStringToHexString(iter2.Key(), match->fullhash);
272 :
273 : // Set expiry of positive cache entry.
274 0 : match->expirySec = iter2.Data();
275 :
276 0 : entry->matches.AppendElement(
277 0 : static_cast<nsIUrlClassifierPositiveCacheEntry*>(match));
278 : }
279 :
280 0 : info->entries.AppendElement(static_cast<nsIUrlClassifierCacheEntry*>(entry));
281 : }
282 :
283 0 : NS_ADDREF(*aCache = info);
284 :
285 0 : return;
286 : }
287 :
288 : /* static */ bool
289 1 : LookupCache::IsCanonicalizedIP(const nsACString& aHost)
290 : {
291 : // The canonicalization process will have left IP addresses in dotted
292 : // decimal with no surprises.
293 : uint32_t i1, i2, i3, i4;
294 : char c;
295 1 : if (PR_sscanf(PromiseFlatCString(aHost).get(), "%u.%u.%u.%u%c",
296 : &i1, &i2, &i3, &i4, &c) == 4) {
297 0 : return (i1 <= 0xFF && i2 <= 0xFF && i3 <= 0xFF && i4 <= 0xFF);
298 : }
299 :
300 1 : return false;
301 : }
302 :
303 : /* static */ nsresult
304 1 : LookupCache::GetLookupFragments(const nsACString& aSpec,
305 : nsTArray<nsCString>* aFragments)
306 :
307 : {
308 1 : aFragments->Clear();
309 :
310 1 : nsACString::const_iterator begin, end, iter;
311 1 : aSpec.BeginReading(begin);
312 1 : aSpec.EndReading(end);
313 :
314 1 : iter = begin;
315 1 : if (!FindCharInReadable('/', iter, end)) {
316 0 : return NS_OK;
317 : }
318 :
319 2 : const nsACString& host = Substring(begin, iter++);
320 2 : nsAutoCString path;
321 1 : path.Assign(Substring(iter, end));
322 :
323 : /**
324 : * From the protocol doc:
325 : * For the hostname, the client will try at most 5 different strings. They
326 : * are:
327 : * a) The exact hostname of the url
328 : * b) The 4 hostnames formed by starting with the last 5 components and
329 : * successivly removing the leading component. The top-level component
330 : * can be skipped. This is not done if the hostname is a numerical IP.
331 : */
332 2 : nsTArray<nsCString> hosts;
333 1 : hosts.AppendElement(host);
334 :
335 1 : if (!IsCanonicalizedIP(host)) {
336 1 : host.BeginReading(begin);
337 1 : host.EndReading(end);
338 1 : int numHostComponents = 0;
339 1 : while (RFindInReadable(NS_LITERAL_CSTRING("."), begin, end) &&
340 : numHostComponents < MAX_HOST_COMPONENTS) {
341 : // don't bother checking toplevel domains
342 0 : if (++numHostComponents >= 2) {
343 0 : host.EndReading(iter);
344 0 : hosts.AppendElement(Substring(end, iter));
345 : }
346 0 : end = begin;
347 0 : host.BeginReading(begin);
348 : }
349 : }
350 :
351 : /**
352 : * From the protocol doc:
353 : * For the path, the client will also try at most 6 different strings.
354 : * They are:
355 : * a) the exact path of the url, including query parameters
356 : * b) the exact path of the url, without query parameters
357 : * c) the 4 paths formed by starting at the root (/) and
358 : * successively appending path components, including a trailing
359 : * slash. This behavior should only extend up to the next-to-last
360 : * path component, that is, a trailing slash should never be
361 : * appended that was not present in the original url.
362 : */
363 2 : nsTArray<nsCString> paths;
364 2 : nsAutoCString pathToAdd;
365 :
366 1 : path.BeginReading(begin);
367 1 : path.EndReading(end);
368 1 : iter = begin;
369 1 : if (FindCharInReadable('?', iter, end)) {
370 0 : pathToAdd = Substring(begin, iter);
371 0 : paths.AppendElement(pathToAdd);
372 0 : end = iter;
373 : }
374 :
375 1 : int numPathComponents = 1;
376 1 : iter = begin;
377 1 : while (FindCharInReadable('/', iter, end) &&
378 : numPathComponents < MAX_PATH_COMPONENTS) {
379 0 : iter++;
380 0 : pathToAdd.Assign(Substring(begin, iter));
381 0 : paths.AppendElement(pathToAdd);
382 0 : numPathComponents++;
383 : }
384 :
385 : // If we haven't already done so, add the full path
386 1 : if (!pathToAdd.Equals(path)) {
387 1 : paths.AppendElement(path);
388 : }
389 : // Check an empty path (for whole-domain blacklist entries)
390 1 : paths.AppendElement(EmptyCString());
391 :
392 2 : for (uint32_t hostIndex = 0; hostIndex < hosts.Length(); hostIndex++) {
393 3 : for (uint32_t pathIndex = 0; pathIndex < paths.Length(); pathIndex++) {
394 4 : nsCString key;
395 2 : key.Assign(hosts[hostIndex]);
396 2 : key.Append('/');
397 2 : key.Append(paths[pathIndex]);
398 2 : LOG(("Checking fragment %s", key.get()));
399 :
400 2 : aFragments->AppendElement(key);
401 : }
402 : }
403 :
404 1 : return NS_OK;
405 : }
406 :
407 : /* static */ nsresult
408 0 : LookupCache::GetHostKeys(const nsACString& aSpec,
409 : nsTArray<nsCString>* aHostKeys)
410 : {
411 0 : nsACString::const_iterator begin, end, iter;
412 0 : aSpec.BeginReading(begin);
413 0 : aSpec.EndReading(end);
414 :
415 0 : iter = begin;
416 0 : if (!FindCharInReadable('/', iter, end)) {
417 0 : return NS_OK;
418 : }
419 :
420 0 : const nsACString& host = Substring(begin, iter);
421 :
422 0 : if (IsCanonicalizedIP(host)) {
423 0 : nsCString *key = aHostKeys->AppendElement();
424 0 : if (!key)
425 0 : return NS_ERROR_OUT_OF_MEMORY;
426 :
427 0 : key->Assign(host);
428 0 : key->Append("/");
429 0 : return NS_OK;
430 : }
431 :
432 0 : nsTArray<nsCString> hostComponents;
433 0 : ParseString(PromiseFlatCString(host), '.', hostComponents);
434 :
435 0 : if (hostComponents.Length() < 2) {
436 : // no host or toplevel host, this won't match anything in the db
437 0 : return NS_OK;
438 : }
439 :
440 : // First check with two domain components
441 0 : int32_t last = int32_t(hostComponents.Length()) - 1;
442 0 : nsCString *lookupHost = aHostKeys->AppendElement();
443 0 : if (!lookupHost)
444 0 : return NS_ERROR_OUT_OF_MEMORY;
445 :
446 0 : lookupHost->Assign(hostComponents[last - 1]);
447 0 : lookupHost->Append(".");
448 0 : lookupHost->Append(hostComponents[last]);
449 0 : lookupHost->Append("/");
450 :
451 : // Now check with three domain components
452 0 : if (hostComponents.Length() > 2) {
453 0 : nsCString *lookupHost2 = aHostKeys->AppendElement();
454 0 : if (!lookupHost2)
455 0 : return NS_ERROR_OUT_OF_MEMORY;
456 0 : lookupHost2->Assign(hostComponents[last - 2]);
457 0 : lookupHost2->Append(".");
458 0 : lookupHost2->Append(*lookupHost);
459 : }
460 :
461 0 : return NS_OK;
462 : }
463 :
464 : nsresult
465 13 : LookupCache::LoadPrefixSet()
466 : {
467 26 : nsCOMPtr<nsIFile> psFile;
468 13 : nsresult rv = mStoreDirectory->Clone(getter_AddRefs(psFile));
469 13 : NS_ENSURE_SUCCESS(rv, rv);
470 :
471 13 : rv = psFile->AppendNative(mTableName + NS_LITERAL_CSTRING(PREFIXSET_SUFFIX));
472 13 : NS_ENSURE_SUCCESS(rv, rv);
473 :
474 : bool exists;
475 13 : rv = psFile->Exists(&exists);
476 13 : NS_ENSURE_SUCCESS(rv, rv);
477 :
478 13 : if (exists) {
479 12 : LOG(("stored PrefixSet exists, loading from disk"));
480 12 : rv = LoadFromFile(psFile);
481 12 : if (NS_FAILED(rv)) {
482 0 : return rv;
483 : }
484 12 : mPrimed = true;
485 : } else {
486 1 : LOG(("no (usable) stored PrefixSet found"));
487 : }
488 :
489 : #ifdef DEBUG
490 13 : if (mPrimed) {
491 12 : uint32_t size = SizeOfPrefixSet();
492 12 : LOG(("SB tree done, size = %d bytes\n", size));
493 : }
494 : #endif
495 :
496 13 : return NS_OK;
497 : }
498 :
499 : #if defined(DEBUG)
500 : static
501 0 : nsCString GetFormattedTimeString(int64_t aCurTimeSec)
502 : {
503 : PRExplodedTime pret;
504 0 : PR_ExplodeTime(aCurTimeSec * PR_USEC_PER_SEC, PR_GMTParameters, &pret);
505 :
506 0 : return nsPrintfCString(
507 : "%04d-%02d-%02d %02d:%02d:%02d UTC",
508 0 : pret.tm_year, pret.tm_month + 1, pret.tm_mday,
509 0 : pret.tm_hour, pret.tm_min, pret.tm_sec);
510 : }
511 :
512 : void
513 0 : LookupCache::DumpCache()
514 : {
515 0 : if (!LOG_ENABLED()) {
516 0 : return;
517 : }
518 :
519 0 : for (auto iter = mFullHashCache.ConstIter(); !iter.Done(); iter.Next()) {
520 0 : CachedFullHashResponse* response = iter.Data();
521 :
522 0 : nsAutoCString prefix;
523 : CStringToHexString(
524 0 : nsCString(reinterpret_cast<const char*>(&iter.Key()), PREFIX_SIZE), prefix);
525 0 : LOG(("Cache prefix(%s): %s, Expiry: %s", mTableName.get(), prefix.get(),
526 : GetFormattedTimeString(response->negativeCacheExpirySec).get()));
527 :
528 0 : FullHashExpiryCache& fullHashes = response->fullHashes;
529 0 : for (auto iter2 = fullHashes.ConstIter(); !iter2.Done(); iter2.Next()) {
530 0 : nsAutoCString fullhash;
531 0 : CStringToHexString(iter2.Key(), fullhash);
532 0 : LOG((" - %s, Expiry: %s", fullhash.get(),
533 : GetFormattedTimeString(iter2.Data()).get()));
534 : }
535 : }
536 : }
537 : #endif
538 :
539 : nsresult
540 13 : LookupCacheV2::Init()
541 : {
542 13 : mPrefixSet = new nsUrlClassifierPrefixSet();
543 13 : nsresult rv = mPrefixSet->Init(mTableName);
544 13 : NS_ENSURE_SUCCESS(rv, rv);
545 :
546 13 : return NS_OK;
547 : }
548 :
549 : nsresult
550 13 : LookupCacheV2::Open()
551 : {
552 13 : nsresult rv = LookupCache::Open();
553 13 : NS_ENSURE_SUCCESS(rv, rv);
554 :
555 13 : LOG(("Reading Completions"));
556 13 : rv = ReadCompletions();
557 13 : NS_ENSURE_SUCCESS(rv, rv);
558 :
559 13 : return NS_OK;
560 : }
561 :
562 : void
563 0 : LookupCacheV2::ClearAll()
564 : {
565 0 : LookupCache::ClearAll();
566 0 : mUpdateCompletions.Clear();
567 0 : }
568 :
569 : nsresult
570 4 : LookupCacheV2::Has(const Completion& aCompletion,
571 : bool* aHas,
572 : uint32_t* aMatchLength,
573 : bool* aConfirmed)
574 : {
575 4 : *aHas = *aConfirmed = false;
576 4 : *aMatchLength = 0;
577 :
578 4 : uint32_t prefix = aCompletion.ToUint32();
579 :
580 : bool found;
581 4 : nsresult rv = mPrefixSet->Contains(prefix, &found);
582 4 : NS_ENSURE_SUCCESS(rv, rv);
583 :
584 4 : if (found) {
585 0 : *aHas = true;
586 0 : *aMatchLength = PREFIX_SIZE;
587 4 : } else if (mUpdateCompletions.BinaryIndexOf(aCompletion) !=
588 : nsTArray<Completion>::NoIndex) {
589 : // Completions is found in database, confirm the result
590 0 : *aHas = true;
591 0 : *aMatchLength = COMPLETE_SIZE;
592 0 : *aConfirmed = true;
593 : }
594 :
595 4 : if (*aHas && !(*aConfirmed)) {
596 0 : rv = CheckCache(aCompletion, aHas, aConfirmed);
597 : }
598 :
599 4 : LOG(("Probe in %s: %X, has %d, confirmed %d",
600 : mTableName.get(), prefix, *aHas, *aConfirmed));
601 :
602 4 : return rv;
603 : }
604 :
605 : bool
606 0 : LookupCacheV2::IsEmpty()
607 : {
608 : bool isEmpty;
609 0 : mPrefixSet->IsEmpty(&isEmpty);
610 0 : return isEmpty;
611 : }
612 :
613 : nsresult
614 6 : LookupCacheV2::Build(AddPrefixArray& aAddPrefixes,
615 : AddCompleteArray& aAddCompletes)
616 : {
617 6 : Telemetry::Accumulate(Telemetry::URLCLASSIFIER_LC_COMPLETIONS,
618 12 : static_cast<uint32_t>(aAddCompletes.Length()));
619 :
620 6 : mUpdateCompletions.Clear();
621 6 : mUpdateCompletions.SetCapacity(aAddCompletes.Length());
622 13 : for (uint32_t i = 0; i < aAddCompletes.Length(); i++) {
623 7 : mUpdateCompletions.AppendElement(aAddCompletes[i].CompleteHash());
624 : }
625 6 : aAddCompletes.Clear();
626 6 : mUpdateCompletions.Sort();
627 :
628 6 : Telemetry::Accumulate(Telemetry::URLCLASSIFIER_LC_PREFIXES,
629 12 : static_cast<uint32_t>(aAddPrefixes.Length()));
630 :
631 6 : nsresult rv = ConstructPrefixSet(aAddPrefixes);
632 6 : NS_ENSURE_SUCCESS(rv, rv);
633 6 : mPrimed = true;
634 :
635 6 : return NS_OK;
636 : }
637 :
638 : nsresult
639 6 : LookupCacheV2::GetPrefixes(FallibleTArray<uint32_t>& aAddPrefixes)
640 : {
641 6 : if (!mPrimed) {
642 : // This can happen if its a new table, so no error.
643 0 : LOG(("GetPrefixes from empty LookupCache"));
644 0 : return NS_OK;
645 : }
646 6 : return mPrefixSet->GetPrefixesNative(aAddPrefixes);
647 : }
648 :
649 : void
650 0 : LookupCacheV2::AddGethashResultToCache(AddCompleteArray& aAddCompletes,
651 : MissPrefixArray& aMissPrefixes,
652 : int64_t aExpirySec)
653 : {
654 0 : int64_t defaultExpirySec = PR_Now() / PR_USEC_PER_SEC + V2_CACHE_DURATION_SEC;
655 0 : if (aExpirySec != 0) {
656 0 : defaultExpirySec = aExpirySec;
657 : }
658 :
659 0 : for (const AddComplete& add : aAddCompletes) {
660 : nsDependentCSubstring fullhash(
661 0 : reinterpret_cast<const char*>(add.CompleteHash().buf), COMPLETE_SIZE);
662 :
663 : CachedFullHashResponse* response =
664 0 : mFullHashCache.LookupOrAdd(add.ToUint32());
665 0 : response->negativeCacheExpirySec = defaultExpirySec;
666 :
667 0 : FullHashExpiryCache& fullHashes = response->fullHashes;
668 0 : fullHashes.Put(fullhash, defaultExpirySec);
669 : }
670 :
671 0 : for (const Prefix& prefix : aMissPrefixes) {
672 : CachedFullHashResponse* response =
673 0 : mFullHashCache.LookupOrAdd(prefix.ToUint32());
674 :
675 0 : response->negativeCacheExpirySec = defaultExpirySec;
676 : }
677 0 : }
678 :
679 : nsresult
680 13 : LookupCacheV2::ReadCompletions()
681 : {
682 26 : HashStore store(mTableName, mProvider, mRootStoreDirectory);
683 :
684 13 : nsresult rv = store.Open();
685 13 : NS_ENSURE_SUCCESS(rv, rv);
686 :
687 13 : mUpdateCompletions.Clear();
688 :
689 13 : const AddCompleteArray& addComplete = store.AddCompletes();
690 27 : for (uint32_t i = 0; i < addComplete.Length(); i++) {
691 14 : mUpdateCompletions.AppendElement(addComplete[i].complete);
692 : }
693 :
694 13 : return NS_OK;
695 : }
696 :
697 : nsresult
698 0 : LookupCacheV2::ClearPrefixes()
699 : {
700 0 : return mPrefixSet->SetPrefixes(nullptr, 0);
701 : }
702 :
703 : nsresult
704 6 : LookupCacheV2::StoreToFile(nsIFile* aFile)
705 : {
706 6 : return mPrefixSet->StoreToFile(aFile);
707 : }
708 :
709 : nsresult
710 12 : LookupCacheV2::LoadFromFile(nsIFile* aFile)
711 : {
712 12 : return mPrefixSet->LoadFromFile(aFile);
713 : }
714 :
715 : size_t
716 12 : LookupCacheV2::SizeOfPrefixSet()
717 : {
718 12 : return mPrefixSet->SizeOfIncludingThis(moz_malloc_size_of);
719 : }
720 :
721 : #ifdef DEBUG
722 : template <class T>
723 6 : static void EnsureSorted(T* aArray)
724 : {
725 6 : typename T::elem_type* start = aArray->Elements();
726 6 : typename T::elem_type* end = aArray->Elements() + aArray->Length();
727 6 : typename T::elem_type* iter = start;
728 6 : typename T::elem_type* previous = start;
729 :
730 6 : while (iter != end) {
731 0 : previous = iter;
732 0 : ++iter;
733 0 : if (iter != end) {
734 0 : MOZ_ASSERT(*previous <= *iter);
735 : }
736 : }
737 6 : return;
738 : }
739 : #endif
740 :
741 : nsresult
742 6 : LookupCacheV2::ConstructPrefixSet(AddPrefixArray& aAddPrefixes)
743 : {
744 12 : Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_PS_CONSTRUCT_TIME> timer;
745 :
746 12 : nsTArray<uint32_t> array;
747 6 : if (!array.SetCapacity(aAddPrefixes.Length(), fallible)) {
748 0 : return NS_ERROR_OUT_OF_MEMORY;
749 : }
750 :
751 6 : for (uint32_t i = 0; i < aAddPrefixes.Length(); i++) {
752 0 : array.AppendElement(aAddPrefixes[i].PrefixHash().ToUint32());
753 : }
754 6 : aAddPrefixes.Clear();
755 :
756 : #ifdef DEBUG
757 : // PrefixSet requires sorted order
758 6 : EnsureSorted(&array);
759 : #endif
760 :
761 : // construct new one, replace old entries
762 6 : nsresult rv = mPrefixSet->SetPrefixes(array.Elements(), array.Length());
763 6 : NS_ENSURE_SUCCESS(rv, rv);
764 :
765 : #ifdef DEBUG
766 : uint32_t size;
767 6 : size = mPrefixSet->SizeOfIncludingThis(moz_malloc_size_of);
768 6 : LOG(("SB tree done, size = %d bytes\n", size));
769 : #endif
770 :
771 6 : mPrimed = true;
772 :
773 6 : return NS_OK;
774 : }
775 :
776 : #if defined(DEBUG)
777 : void
778 6 : LookupCacheV2::DumpCompletions()
779 : {
780 6 : if (!LOG_ENABLED())
781 6 : return;
782 :
783 0 : for (uint32_t i = 0; i < mUpdateCompletions.Length(); i++) {
784 0 : nsAutoCString str;
785 0 : mUpdateCompletions[i].ToHexString(str);
786 0 : LOG(("Update: %s", str.get()));
787 : }
788 : }
789 : #endif
790 :
791 : } // namespace safebrowsing
792 : } // namespace mozilla
|