Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/toolkit/components/url-classifier/tests/gtest/TestVariableLengthPrefixSet.cpp
Line
Count
Source (jump to first uncovered line)
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include <mozilla/RefPtr.h>
6
#include "nsString.h"
7
#include "nsTArray.h"
8
#include "nsClassHashtable.h"
9
#include "VariableLengthPrefixSet.h"
10
#include "nsAppDirectoryServiceDefs.h"
11
#include "nsIFile.h"
12
#include "gtest/gtest.h"
13
14
using namespace mozilla::safebrowsing;
15
16
typedef nsCString _Prefix;
17
typedef nsTArray<_Prefix> _PrefixArray;
18
19
// Create fullhash by appending random characters.
20
static nsCString* CreateFullHash(const nsACString& in)
21
0
{
22
0
  nsCString* out = new nsCString(in);
23
0
  out->SetLength(32);
24
0
  for (size_t i = in.Length(); i < 32; i++) {
25
0
    out->SetCharAt(char(rand() % 256), i);
26
0
  }
27
0
28
0
  return out;
29
0
}
30
31
// This function generate N prefixes with size between MIN and MAX.
32
// The output array will not be cleared, random result will append to it
33
static void RandomPrefixes(uint32_t N, uint32_t MIN, uint32_t MAX, _PrefixArray& array)
34
0
{
35
0
  array.SetCapacity(array.Length() + N);
36
0
37
0
  uint32_t range = (MAX - MIN + 1);
38
0
39
0
  for (uint32_t i = 0; i < N; i++) {
40
0
    uint32_t prefixSize = (rand() % range) + MIN;
41
0
    _Prefix prefix;
42
0
    prefix.SetLength(prefixSize);
43
0
44
0
    bool added = false;
45
0
    while(!added) {
46
0
      char* dst = prefix.BeginWriting();
47
0
      for (uint32_t j = 0; j < prefixSize; j++) {
48
0
        dst[j] = rand() % 256;
49
0
      }
50
0
51
0
      if (!array.Contains(prefix)) {
52
0
        array.AppendElement(prefix);
53
0
        added = true;
54
0
      }
55
0
    }
56
0
  }
57
0
}
58
59
static void CheckContent(VariableLengthPrefixSet* pset,
60
                         PrefixStringMap& expected)
61
0
{
62
0
  PrefixStringMap vlPSetMap;
63
0
  pset->GetPrefixes(vlPSetMap);
64
0
65
0
  for (auto iter = vlPSetMap.Iter(); !iter.Done(); iter.Next()) {
66
0
    nsCString* expectedPrefix = expected.Get(iter.Key());
67
0
    nsCString* resultPrefix = iter.Data();
68
0
69
0
    ASSERT_TRUE(resultPrefix->Equals(*expectedPrefix));
70
0
  }
71
0
}
72
73
// This test loops through all the prefixes and converts each prefix to
74
// fullhash by appending random characters, each converted fullhash
75
// should at least match its original length in the prefixSet.
76
static void DoExpectedLookup(VariableLengthPrefixSet* pset,
77
                             _PrefixArray& array)
78
0
{
79
0
  uint32_t matchLength = 0;
80
0
  for (uint32_t i = 0; i < array.Length(); i++) {
81
0
    const nsCString& prefix = array[i];
82
0
    UniquePtr<nsCString> fullhash(CreateFullHash(prefix));
83
0
84
0
    // Find match for prefix-generated full hash
85
0
    pset->Matches(*fullhash, &matchLength);
86
0
    MOZ_ASSERT(matchLength != 0);
87
0
88
0
    if (matchLength != prefix.Length()) {
89
0
      // Return match size is not the same as prefix size.
90
0
      // In this case it could be because the generated fullhash match other
91
0
      // prefixes, check if this prefix exist.
92
0
      bool found = false;
93
0
94
0
      for (uint32_t j = 0; j < array.Length(); j++) {
95
0
        if (array[j].Length() != matchLength) {
96
0
          continue;
97
0
        }
98
0
99
0
        if (0 == memcmp(fullhash->BeginReading(),
100
0
                        array[j].BeginReading(),
101
0
                        matchLength)) {
102
0
          found = true;
103
0
          break;
104
0
        }
105
0
      }
106
0
      ASSERT_TRUE(found);
107
0
    }
108
0
  }
109
0
}
110
111
static void DoRandomLookup(VariableLengthPrefixSet* pset,
112
                           uint32_t N,
113
                           _PrefixArray& array)
114
0
{
115
0
  for (uint32_t i = 0; i < N; i++) {
116
0
    // Random 32-bytes test fullhash
117
0
    char buf[32];
118
0
    for (uint32_t j = 0; j < 32; j++) {
119
0
      buf[j] = (char)(rand() % 256);
120
0
    }
121
0
122
0
    // Get the expected result.
123
0
    nsTArray<uint32_t> expected;
124
0
    for (uint32_t j = 0; j < array.Length(); j++) {
125
0
      const nsACString& str = array[j];
126
0
      if (0 == memcmp(buf, str.BeginReading(), str.Length())) {
127
0
        expected.AppendElement(str.Length());
128
0
      }
129
0
    }
130
0
131
0
    uint32_t matchLength = 0;
132
0
    pset->Matches(nsDependentCSubstring(buf, 32), &matchLength);
133
0
134
0
    ASSERT_TRUE(expected.IsEmpty() ? !matchLength : expected.Contains(matchLength));
135
0
  }
136
0
}
137
138
static void SetupPrefixMap(const _PrefixArray& array,
139
                             PrefixStringMap& map)
140
0
{
141
0
  map.Clear();
142
0
143
0
  // Buckets are keyed by prefix length and contain an array of
144
0
  // all prefixes of that length.
145
0
  nsClassHashtable<nsUint32HashKey, _PrefixArray> table;
146
0
147
0
  for (uint32_t i = 0; i < array.Length(); i++) {
148
0
    _PrefixArray* prefixes = table.Get(array[i].Length());
149
0
    if (!prefixes) {
150
0
      prefixes = new _PrefixArray();
151
0
      table.Put(array[i].Length(), prefixes);
152
0
    }
153
0
154
0
    prefixes->AppendElement(array[i]);
155
0
  }
156
0
157
0
  // The resulting map entries will be a concatenation of all
158
0
  // prefix data for the prefixes of a given size.
159
0
  for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
160
0
    uint32_t size = iter.Key();
161
0
    uint32_t count = iter.Data()->Length();
162
0
163
0
    _Prefix* str = new _Prefix();
164
0
    str->SetLength(size * count);
165
0
166
0
    char* dst = str->BeginWriting();
167
0
168
0
    iter.Data()->Sort();
169
0
    for (uint32_t i = 0; i < count; i++) {
170
0
      memcpy(dst, iter.Data()->ElementAt(i).get(), size);
171
0
      dst += size;
172
0
    }
173
0
174
0
    map.Put(size, str);
175
0
  }
176
0
}
177
178
179
// Test setting prefix set with only 4-bytes prefixes
180
TEST(UrlClassifierVLPrefixSet, FixedLengthSet)
181
0
{
182
0
  srand(time(nullptr));
183
0
184
0
  RefPtr<VariableLengthPrefixSet> pset = new VariableLengthPrefixSet;
185
0
  pset->Init(NS_LITERAL_CSTRING("test"));
186
0
187
0
  PrefixStringMap map;
188
0
  _PrefixArray array = { _Prefix("alph"), _Prefix("brav"), _Prefix("char"),
189
0
                         _Prefix("delt"), _Prefix("echo"), _Prefix("foxt"),
190
0
                       };
191
0
192
0
  SetupPrefixMap(array, map);
193
0
  pset->SetPrefixes(map);
194
0
195
0
  DoExpectedLookup(pset, array);
196
0
197
0
  DoRandomLookup(pset, 1000, array);
198
0
199
0
  CheckContent(pset, map);
200
0
201
0
  // Run random test
202
0
  array.Clear();
203
0
  map.Clear();
204
0
205
0
  RandomPrefixes(1500, 4, 4, array);
206
0
207
0
  SetupPrefixMap(array, map);
208
0
  pset->SetPrefixes(map);
209
0
210
0
  DoExpectedLookup(pset, array);
211
0
212
0
  DoRandomLookup(pset, 1000, array);
213
0
214
0
  CheckContent(pset, map);
215
0
}
216
217
// Test setting prefix set with only 5~32 bytes prefixes
218
TEST(UrlClassifierVLPrefixSet, VariableLengthSet)
219
0
{
220
0
  RefPtr<VariableLengthPrefixSet> pset = new VariableLengthPrefixSet;
221
0
  pset->Init(NS_LITERAL_CSTRING("test"));
222
0
223
0
  PrefixStringMap map;
224
0
  _PrefixArray array = { _Prefix("bravo"), _Prefix("charlie"), _Prefix("delta"),
225
0
                         _Prefix("EchoEchoEchoEchoEcho"), _Prefix("foxtrot"),
226
0
                         _Prefix("GolfGolfGolfGolfGolfGolfGolfGolf"),
227
0
                         _Prefix("hotel"), _Prefix("november"),
228
0
                         _Prefix("oscar"), _Prefix("quebec"), _Prefix("romeo"),
229
0
                         _Prefix("sierrasierrasierrasierrasierra"),
230
0
                         _Prefix("Tango"), _Prefix("whiskey"), _Prefix("yankee"),
231
0
                         _Prefix("ZuluZuluZuluZulu")
232
0
                       };
233
0
234
0
  SetupPrefixMap(array, map);
235
0
  pset->SetPrefixes(map);
236
0
237
0
  DoExpectedLookup(pset, array);
238
0
239
0
  DoRandomLookup(pset, 1000, array);
240
0
241
0
  CheckContent(pset, map);
242
0
243
0
  // Run random test
244
0
  array.Clear();
245
0
  map.Clear();
246
0
247
0
  RandomPrefixes(1500, 5, 32, array);
248
0
249
0
  SetupPrefixMap(array, map);
250
0
  pset->SetPrefixes(map);
251
0
252
0
  DoExpectedLookup(pset, array);
253
0
254
0
  DoRandomLookup(pset, 1000, array);
255
0
256
0
  CheckContent(pset, map);
257
0
258
0
}
259
260
// Test setting prefix set with both 4-bytes prefixes and 5~32 bytes prefixes
261
TEST(UrlClassifierVLPrefixSet, MixedPrefixSet)
262
0
{
263
0
  RefPtr<VariableLengthPrefixSet> pset = new VariableLengthPrefixSet;
264
0
  pset->Init(NS_LITERAL_CSTRING("test"));
265
0
266
0
  PrefixStringMap map;
267
0
  _PrefixArray array = { _Prefix("enus"), _Prefix("apollo"), _Prefix("mars"),
268
0
                         _Prefix("Hecatonchires cyclopes"),
269
0
                         _Prefix("vesta"), _Prefix("neptunus"), _Prefix("jupiter"),
270
0
                         _Prefix("diana"), _Prefix("minerva"), _Prefix("ceres"),
271
0
                         _Prefix("Aidos,Adephagia,Adikia,Aletheia"),
272
0
                         _Prefix("hecatonchires"), _Prefix("alcyoneus"), _Prefix("hades"),
273
0
                         _Prefix("vulcanus"), _Prefix("juno"), _Prefix("mercury"),
274
0
                         _Prefix("Stheno, Euryale and Medusa")
275
0
                       };
276
0
277
0
  SetupPrefixMap(array, map);
278
0
  pset->SetPrefixes(map);
279
0
280
0
  DoExpectedLookup(pset, array);
281
0
282
0
  DoRandomLookup(pset, 1000, array);
283
0
284
0
  CheckContent(pset, map);
285
0
286
0
  // Run random test
287
0
  array.Clear();
288
0
  map.Clear();
289
0
290
0
  RandomPrefixes(1500, 4, 32, array);
291
0
292
0
  SetupPrefixMap(array, map);
293
0
  pset->SetPrefixes(map);
294
0
295
0
  DoExpectedLookup(pset, array);
296
0
297
0
  DoRandomLookup(pset, 1000, array);
298
0
299
0
  CheckContent(pset, map);
300
0
}
301
302
// Test resetting prefix set
303
TEST(UrlClassifierVLPrefixSet, ResetPrefix)
304
0
{
305
0
  RefPtr<VariableLengthPrefixSet> pset = new VariableLengthPrefixSet;
306
0
  pset->Init(NS_LITERAL_CSTRING("test"));
307
0
308
0
  // First prefix set
309
0
  _PrefixArray array1 = { _Prefix("Iceland"), _Prefix("Peru"), _Prefix("Mexico"),
310
0
                          _Prefix("Australia"), _Prefix("Japan"), _Prefix("Egypt"),
311
0
                          _Prefix("America"), _Prefix("Finland"), _Prefix("Germany"),
312
0
                          _Prefix("Italy"), _Prefix("France"), _Prefix("Taiwan"),
313
0
                        };
314
0
  {
315
0
    PrefixStringMap map;
316
0
317
0
    SetupPrefixMap(array1, map);
318
0
    pset->SetPrefixes(map);
319
0
320
0
    DoExpectedLookup(pset, array1);
321
0
  }
322
0
323
0
  // Second
324
0
  _PrefixArray array2 = { _Prefix("Pikachu"), _Prefix("Bulbasaur"), _Prefix("Charmander"),
325
0
                          _Prefix("Blastoise"), _Prefix("Pidgey"), _Prefix("Mewtwo"),
326
0
                          _Prefix("Jigglypuff"), _Prefix("Persian"), _Prefix("Tentacool"),
327
0
                          _Prefix("Onix"), _Prefix("Eevee"), _Prefix("Jynx"),
328
0
                        };
329
0
  {
330
0
    PrefixStringMap map;
331
0
332
0
    SetupPrefixMap(array2, map);
333
0
    pset->SetPrefixes(map);
334
0
335
0
    DoExpectedLookup(pset, array2);
336
0
  }
337
0
338
0
  // Should not match any of the first prefix set
339
0
  uint32_t matchLength = 0;
340
0
  for (uint32_t i = 0; i < array1.Length(); i++) {
341
0
    UniquePtr<nsACString> fullhash(CreateFullHash(array1[i]));
342
0
343
0
    pset->Matches(*fullhash, &matchLength);
344
0
    ASSERT_TRUE(matchLength == 0);
345
0
  }
346
0
}
347
348
// Test only set one 4-bytes prefix and one full-length prefix
349
TEST(UrlClassifierVLPrefixSet, TinyPrefixSet)
350
0
{
351
0
  RefPtr<VariableLengthPrefixSet> pset = new VariableLengthPrefixSet;
352
0
  pset->Init(NS_LITERAL_CSTRING("test"));
353
0
354
0
  PrefixStringMap map;
355
0
  _PrefixArray array = { _Prefix("AAAA"),
356
0
                         _Prefix("11112222333344445555666677778888"),
357
0
                       };
358
0
359
0
  SetupPrefixMap(array, map);
360
0
  pset->SetPrefixes(map);
361
0
362
0
  DoExpectedLookup(pset, array);
363
0
364
0
  DoRandomLookup(pset, 1000, array);
365
0
366
0
  CheckContent(pset, map);
367
0
}
368
369
// Test empty prefix set and IsEmpty function
370
TEST(UrlClassifierVLPrefixSet, EmptyPrefixSet)
371
0
{
372
0
  RefPtr<VariableLengthPrefixSet> pset = new VariableLengthPrefixSet;
373
0
  pset->Init(NS_LITERAL_CSTRING("test"));
374
0
375
0
  bool empty;
376
0
  pset->IsEmpty(&empty);
377
0
  ASSERT_TRUE(empty);
378
0
379
0
  PrefixStringMap map;
380
0
  _PrefixArray array1;
381
0
382
0
  // Lookup an empty array should never match
383
0
  DoRandomLookup(pset, 100, array1);
384
0
385
0
  // Insert an 4-bytes prefix, then IsEmpty should return false
386
0
  _PrefixArray array2 = { _Prefix("test") };
387
0
  SetupPrefixMap(array2, map);
388
0
  pset->SetPrefixes(map);
389
0
390
0
  pset->IsEmpty(&empty);
391
0
  ASSERT_TRUE(!empty);
392
0
393
0
  _PrefixArray array3 = { _Prefix("test variable length") };
394
0
395
0
  // Insert an 5~32 bytes prefix, then IsEmpty should return false
396
0
  SetupPrefixMap(array3, map);
397
0
  pset->SetPrefixes(map);
398
0
399
0
  pset->IsEmpty(&empty);
400
0
  ASSERT_TRUE(!empty);
401
0
}
402
403
// Test prefix size should only between 4~32 bytes
404
TEST(UrlClassifierVLPrefixSet, MinMaxPrefixSet)
405
0
{
406
0
  RefPtr<VariableLengthPrefixSet> pset = new VariableLengthPrefixSet;
407
0
  pset->Init(NS_LITERAL_CSTRING("test"));
408
0
409
0
  PrefixStringMap map;
410
0
  {
411
0
    _PrefixArray array = { _Prefix("1234"),
412
0
                           _Prefix("ABCDEFGHIJKKMNOP"),
413
0
                           _Prefix("1aaa2bbb3ccc4ddd5eee6fff7ggg8hhh") };
414
0
415
0
    SetupPrefixMap(array, map);
416
0
    nsresult rv = pset->SetPrefixes(map);
417
0
    ASSERT_TRUE(rv == NS_OK);
418
0
  }
419
0
420
0
  // Prefix size less than 4-bytes should fail
421
0
  {
422
0
    _PrefixArray array = { _Prefix("123") };
423
0
424
0
    SetupPrefixMap(array, map);
425
0
    nsresult rv = pset->SetPrefixes(map);
426
0
    ASSERT_TRUE(NS_FAILED(rv));
427
0
  }
428
0
429
0
  // Prefix size greater than 32-bytes should fail
430
0
  {
431
0
    _PrefixArray array = { _Prefix("1aaa2bbb3ccc4ddd5eee6fff7ggg8hhh9") };
432
0
433
0
    SetupPrefixMap(array, map);
434
0
    nsresult rv = pset->SetPrefixes(map);
435
0
    ASSERT_TRUE(NS_FAILED(rv));
436
0
  }
437
0
}
438
439
// Test save then load prefix set with only 4-bytes prefixes
440
TEST(UrlClassifierVLPrefixSet, LoadSaveFixedLengthPrefixSet)
441
0
{
442
0
  RefPtr<VariableLengthPrefixSet> save = new VariableLengthPrefixSet;
443
0
  save->Init(NS_LITERAL_CSTRING("test-save"));
444
0
445
0
  _PrefixArray array;
446
0
  RandomPrefixes(10000, 4, 4, array);
447
0
448
0
  PrefixStringMap map;
449
0
  SetupPrefixMap(array, map);
450
0
  save->SetPrefixes(map);
451
0
452
0
  DoExpectedLookup(save, array);
453
0
454
0
  DoRandomLookup(save, 1000, array);
455
0
456
0
  CheckContent(save, map);
457
0
458
0
  nsCOMPtr<nsIFile> file;
459
0
  NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
460
0
                         getter_AddRefs(file));
461
0
  file->Append(NS_LITERAL_STRING("test.vlpset"));
462
0
463
0
  save->StoreToFile(file);
464
0
465
0
  RefPtr<VariableLengthPrefixSet> load = new VariableLengthPrefixSet;
466
0
  load->Init(NS_LITERAL_CSTRING("test-load"));
467
0
468
0
  load->LoadFromFile(file);
469
0
470
0
  DoExpectedLookup(load, array);
471
0
472
0
  DoRandomLookup(load, 1000, array);
473
0
474
0
  CheckContent(load, map);
475
0
476
0
  file->Remove(false);
477
0
}
478
479
// Test save then load prefix set with only 5~32 bytes prefixes
480
TEST(UrlClassifierVLPrefixSet, LoadSaveVariableLengthPrefixSet)
481
0
{
482
0
  RefPtr<VariableLengthPrefixSet> save = new VariableLengthPrefixSet;
483
0
  save->Init(NS_LITERAL_CSTRING("test-save"));
484
0
485
0
  _PrefixArray array;
486
0
  RandomPrefixes(10000, 5, 32, array);
487
0
488
0
  PrefixStringMap map;
489
0
  SetupPrefixMap(array, map);
490
0
  save->SetPrefixes(map);
491
0
492
0
  DoExpectedLookup(save, array);
493
0
494
0
  DoRandomLookup(save, 1000, array);
495
0
496
0
  CheckContent(save, map);
497
0
498
0
  nsCOMPtr<nsIFile> file;
499
0
  NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
500
0
                         getter_AddRefs(file));
501
0
  file->Append(NS_LITERAL_STRING("test.vlpset"));
502
0
503
0
  save->StoreToFile(file);
504
0
505
0
  RefPtr<VariableLengthPrefixSet> load = new VariableLengthPrefixSet;
506
0
  load->Init(NS_LITERAL_CSTRING("test-load"));
507
0
508
0
  load->LoadFromFile(file);
509
0
510
0
  DoExpectedLookup(load, array);
511
0
512
0
  DoRandomLookup(load, 1000, array);
513
0
514
0
  CheckContent(load, map);
515
0
516
0
  file->Remove(false);
517
0
}
518
519
// Test save then load prefix with both 4 bytes prefixes and 5~32 bytes prefixes
520
TEST(UrlClassifierVLPrefixSet, LoadSavePrefixSet)
521
0
{
522
0
  RefPtr<VariableLengthPrefixSet> save = new VariableLengthPrefixSet;
523
0
  save->Init(NS_LITERAL_CSTRING("test-save"));
524
0
525
0
  // Try to simulate the real case that most prefixes are 4bytes
526
0
  _PrefixArray array;
527
0
  RandomPrefixes(20000, 4, 4, array);
528
0
  RandomPrefixes(1000, 5, 32, array);
529
0
530
0
  PrefixStringMap map;
531
0
  SetupPrefixMap(array, map);
532
0
  save->SetPrefixes(map);
533
0
534
0
  DoExpectedLookup(save, array);
535
0
536
0
  DoRandomLookup(save, 1000, array);
537
0
538
0
  CheckContent(save, map);
539
0
540
0
  nsCOMPtr<nsIFile> file;
541
0
  NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
542
0
                         getter_AddRefs(file));
543
0
  file->Append(NS_LITERAL_STRING("test.vlpset"));
544
0
545
0
  save->StoreToFile(file);
546
0
547
0
  RefPtr<VariableLengthPrefixSet> load = new VariableLengthPrefixSet;
548
0
  load->Init(NS_LITERAL_CSTRING("test-load"));
549
0
550
0
  load->LoadFromFile(file);
551
0
552
0
  DoExpectedLookup(load, array);
553
0
554
0
  DoRandomLookup(load, 1000, array);
555
0
556
0
  CheckContent(load, map);
557
0
558
0
  file->Remove(false);
559
0
}