Coverage Report

Created: 2018-09-25 14:53

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