toolkit/components/url-classifier/LookupCache.cpp

branch
TOR_BUG_9701
changeset 14
925c144e1f1f
equal deleted inserted replaced
-1:000000000000 0:3a07006eeebc
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/Telemetry.h"
10 #include "prlog.h"
11 #include "prprf.h"
12
13 // We act as the main entry point for all the real lookups,
14 // so note that those are not done to the actual HashStore.
15 // The latter solely exists to store the data needed to handle
16 // the updates from the protocol.
17
18 // This module has its own store, which stores the Completions,
19 // mostly caching lookups that have happened over the net.
20 // The prefixes are cached/checked by looking them up in the
21 // PrefixSet.
22
23 // Data format for the ".cache" files:
24 // uint32_t magic Identify the file type
25 // uint32_t version Version identifier for file format
26 // uint32_t numCompletions Amount of completions stored
27 // 0...numCompletions 256-bit Completions
28
29 // Name of the lookupcomplete cache
30 #define CACHE_SUFFIX ".cache"
31
32 // Name of the persistent PrefixSet storage
33 #define PREFIXSET_SUFFIX ".pset"
34
35 // NSPR_LOG_MODULES=UrlClassifierDbService:5
36 extern PRLogModuleInfo *gUrlClassifierDbServiceLog;
37 #if defined(PR_LOGGING)
38 #define LOG(args) PR_LOG(gUrlClassifierDbServiceLog, PR_LOG_DEBUG, args)
39 #define LOG_ENABLED() PR_LOG_TEST(gUrlClassifierDbServiceLog, 4)
40 #else
41 #define LOG(args)
42 #define LOG_ENABLED() (false)
43 #endif
44
45 namespace mozilla {
46 namespace safebrowsing {
47
48 const uint32_t LOOKUPCACHE_MAGIC = 0x1231af3e;
49 const uint32_t CURRENT_VERSION = 2;
50
51 LookupCache::LookupCache(const nsACString& aTableName, nsIFile* aStoreDir)
52 : mPrimed(false)
53 , mTableName(aTableName)
54 , mStoreDirectory(aStoreDir)
55 {
56 }
57
58 nsresult
59 LookupCache::Init()
60 {
61 mPrefixSet = new nsUrlClassifierPrefixSet();
62 nsresult rv = mPrefixSet->Init(mTableName);
63 NS_ENSURE_SUCCESS(rv, rv);
64
65 return NS_OK;
66 }
67
68 LookupCache::~LookupCache()
69 {
70 }
71
72 nsresult
73 LookupCache::Open()
74 {
75 nsCOMPtr<nsIFile> storeFile;
76
77 nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
78 NS_ENSURE_SUCCESS(rv, rv);
79
80 rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(CACHE_SUFFIX));
81 NS_ENSURE_SUCCESS(rv, rv);
82
83 nsCOMPtr<nsIInputStream> inputStream;
84 rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), storeFile,
85 PR_RDONLY | nsIFile::OS_READAHEAD);
86
87 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
88 Reset();
89 return rv;
90 }
91
92 if (rv == NS_ERROR_FILE_NOT_FOUND) {
93 // Simply lacking a .cache file is a recoverable error,
94 // as unlike the .pset/.sbstore files it is a pure cache.
95 // Just create a new empty one.
96 ClearCompleteCache();
97 } else {
98 // Read in the .cache file
99 rv = ReadHeader(inputStream);
100 NS_ENSURE_SUCCESS(rv, rv);
101 LOG(("ReadCompletions"));
102 rv = ReadCompletions(inputStream);
103 NS_ENSURE_SUCCESS(rv, rv);
104
105 rv = inputStream->Close();
106 NS_ENSURE_SUCCESS(rv, rv);
107 }
108
109 LOG(("Loading PrefixSet"));
110 rv = LoadPrefixSet();
111 NS_ENSURE_SUCCESS(rv, rv);
112
113 return NS_OK;
114 }
115
116 nsresult
117 LookupCache::UpdateDirHandle(nsIFile* aStoreDirectory)
118 {
119 return aStoreDirectory->Clone(getter_AddRefs(mStoreDirectory));
120 }
121
122 nsresult
123 LookupCache::Reset()
124 {
125 LOG(("LookupCache resetting"));
126
127 nsCOMPtr<nsIFile> storeFile;
128 nsCOMPtr<nsIFile> prefixsetFile;
129 nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
130 NS_ENSURE_SUCCESS(rv, rv);
131 rv = mStoreDirectory->Clone(getter_AddRefs(prefixsetFile));
132 NS_ENSURE_SUCCESS(rv, rv);
133
134 rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(CACHE_SUFFIX));
135 NS_ENSURE_SUCCESS(rv, rv);
136 rv = prefixsetFile->AppendNative(mTableName + NS_LITERAL_CSTRING(PREFIXSET_SUFFIX));
137 NS_ENSURE_SUCCESS(rv, rv);
138
139 rv = storeFile->Remove(false);
140 NS_ENSURE_SUCCESS(rv, rv);
141 rv = prefixsetFile->Remove(false);
142 NS_ENSURE_SUCCESS(rv, rv);
143
144 ClearAll();
145
146 return NS_OK;
147 }
148
149
150 nsresult
151 LookupCache::Build(AddPrefixArray& aAddPrefixes,
152 AddCompleteArray& aAddCompletes)
153 {
154 Telemetry::Accumulate(Telemetry::URLCLASSIFIER_LC_COMPLETIONS,
155 static_cast<uint32_t>(aAddCompletes.Length()));
156
157 mCompletions.Clear();
158 mCompletions.SetCapacity(aAddCompletes.Length());
159 for (uint32_t i = 0; i < aAddCompletes.Length(); i++) {
160 mCompletions.AppendElement(aAddCompletes[i].CompleteHash());
161 }
162 aAddCompletes.Clear();
163 mCompletions.Sort();
164
165 Telemetry::Accumulate(Telemetry::URLCLASSIFIER_LC_PREFIXES,
166 static_cast<uint32_t>(aAddPrefixes.Length()));
167
168 nsresult rv = ConstructPrefixSet(aAddPrefixes);
169 NS_ENSURE_SUCCESS(rv, rv);
170 mPrimed = true;
171
172 return NS_OK;
173 }
174
175 #if defined(DEBUG) && defined(PR_LOGGING)
176 void
177 LookupCache::Dump()
178 {
179 if (!LOG_ENABLED())
180 return;
181
182 for (uint32_t i = 0; i < mCompletions.Length(); i++) {
183 nsAutoCString str;
184 mCompletions[i].ToString(str);
185 LOG(("Completion: %s", str.get()));
186 }
187 }
188 #endif
189
190 nsresult
191 LookupCache::Has(const Completion& aCompletion,
192 bool* aHas, bool* aComplete)
193 {
194 *aHas = *aComplete = false;
195
196 uint32_t prefix = aCompletion.ToUint32();
197
198 bool found;
199 nsresult rv = mPrefixSet->Contains(prefix, &found);
200 NS_ENSURE_SUCCESS(rv, rv);
201
202 LOG(("Probe in %s: %X, found %d", mTableName.get(), prefix, found));
203
204 if (found) {
205 *aHas = true;
206 }
207
208 if (mCompletions.BinaryIndexOf(aCompletion) != nsTArray<Completion>::NoIndex) {
209 LOG(("Complete in %s", mTableName.get()));
210 *aComplete = true;
211 *aHas = true;
212 }
213
214 return NS_OK;
215 }
216
217 nsresult
218 LookupCache::WriteFile()
219 {
220 nsCOMPtr<nsIFile> storeFile;
221 nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
222 NS_ENSURE_SUCCESS(rv, rv);
223 rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(CACHE_SUFFIX));
224 NS_ENSURE_SUCCESS(rv, rv);
225
226 nsCOMPtr<nsIOutputStream> out;
227 rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(out), storeFile,
228 PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE);
229 NS_ENSURE_SUCCESS(rv, rv);
230
231 UpdateHeader();
232 LOG(("Writing %d completions", mHeader.numCompletions));
233
234 uint32_t written;
235 rv = out->Write(reinterpret_cast<char*>(&mHeader), sizeof(mHeader), &written);
236 NS_ENSURE_SUCCESS(rv, rv);
237
238 rv = WriteTArray(out, mCompletions);
239 NS_ENSURE_SUCCESS(rv, rv);
240
241 nsCOMPtr<nsISafeOutputStream> safeOut = do_QueryInterface(out);
242 rv = safeOut->Finish();
243 NS_ENSURE_SUCCESS(rv, rv);
244
245 rv = EnsureSizeConsistent();
246 NS_ENSURE_SUCCESS(rv, rv);
247
248 nsCOMPtr<nsIFile> psFile;
249 rv = mStoreDirectory->Clone(getter_AddRefs(psFile));
250 NS_ENSURE_SUCCESS(rv, rv);
251
252 rv = psFile->AppendNative(mTableName + NS_LITERAL_CSTRING(PREFIXSET_SUFFIX));
253 NS_ENSURE_SUCCESS(rv, rv);
254
255 rv = mPrefixSet->StoreToFile(psFile);
256 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to store the prefixset");
257
258 return NS_OK;
259 }
260
261 void
262 LookupCache::ClearAll()
263 {
264 ClearCompleteCache();
265 mPrefixSet->SetPrefixes(nullptr, 0);
266 mPrimed = false;
267 }
268
269 void
270 LookupCache::ClearCompleteCache()
271 {
272 mCompletions.Clear();
273 UpdateHeader();
274 }
275
276 void
277 LookupCache::UpdateHeader()
278 {
279 mHeader.magic = LOOKUPCACHE_MAGIC;
280 mHeader.version = CURRENT_VERSION;
281 mHeader.numCompletions = mCompletions.Length();
282 }
283
284 nsresult
285 LookupCache::EnsureSizeConsistent()
286 {
287 nsCOMPtr<nsIFile> storeFile;
288 nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
289 NS_ENSURE_SUCCESS(rv, rv);
290 rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(CACHE_SUFFIX));
291 NS_ENSURE_SUCCESS(rv, rv);
292
293 int64_t fileSize;
294 rv = storeFile->GetFileSize(&fileSize);
295 NS_ENSURE_SUCCESS(rv, rv);
296
297 if (fileSize < 0) {
298 return NS_ERROR_FAILURE;
299 }
300
301 int64_t expectedSize = sizeof(mHeader)
302 + mHeader.numCompletions*sizeof(Completion);
303 if (expectedSize != fileSize) {
304 NS_WARNING("File length does not match. Probably corrupted.");
305 Reset();
306 return NS_ERROR_FILE_CORRUPTED;
307 }
308
309 return NS_OK;
310 }
311
312 nsresult
313 LookupCache::ReadHeader(nsIInputStream* aInputStream)
314 {
315 if (!aInputStream) {
316 ClearCompleteCache();
317 return NS_OK;
318 }
319
320 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aInputStream);
321 nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
322 NS_ENSURE_SUCCESS(rv, rv);
323
324 void *buffer = &mHeader;
325 rv = NS_ReadInputStreamToBuffer(aInputStream,
326 &buffer,
327 sizeof(Header));
328 NS_ENSURE_SUCCESS(rv, rv);
329
330 if (mHeader.magic != LOOKUPCACHE_MAGIC || mHeader.version != CURRENT_VERSION) {
331 NS_WARNING("Unexpected header data in the store.");
332 Reset();
333 return NS_ERROR_FILE_CORRUPTED;
334 }
335 LOG(("%d completions present", mHeader.numCompletions));
336
337 rv = EnsureSizeConsistent();
338 NS_ENSURE_SUCCESS(rv, rv);
339
340 return NS_OK;
341 }
342
343 nsresult
344 LookupCache::ReadCompletions(nsIInputStream* aInputStream)
345 {
346 if (!mHeader.numCompletions) {
347 mCompletions.Clear();
348 return NS_OK;
349 }
350
351 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aInputStream);
352 nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, sizeof(Header));
353 NS_ENSURE_SUCCESS(rv, rv);
354
355 rv = ReadTArray(aInputStream, &mCompletions, mHeader.numCompletions);
356 NS_ENSURE_SUCCESS(rv, rv);
357
358 LOG(("Read %d completions", mCompletions.Length()));
359
360 return NS_OK;
361 }
362
363 /* static */ bool
364 LookupCache::IsCanonicalizedIP(const nsACString& aHost)
365 {
366 // The canonicalization process will have left IP addresses in dotted
367 // decimal with no surprises.
368 uint32_t i1, i2, i3, i4;
369 char c;
370 if (PR_sscanf(PromiseFlatCString(aHost).get(), "%u.%u.%u.%u%c",
371 &i1, &i2, &i3, &i4, &c) == 4) {
372 return (i1 <= 0xFF && i2 <= 0xFF && i3 <= 0xFF && i4 <= 0xFF);
373 }
374
375 return false;
376 }
377
378 /* static */ nsresult
379 LookupCache::GetKey(const nsACString& aSpec,
380 Completion* aHash,
381 nsCOMPtr<nsICryptoHash>& aCryptoHash)
382 {
383 nsACString::const_iterator begin, end, iter;
384 aSpec.BeginReading(begin);
385 aSpec.EndReading(end);
386
387 iter = begin;
388 if (!FindCharInReadable('/', iter, end)) {
389 return NS_OK;
390 }
391
392 const nsCSubstring& host = Substring(begin, iter);
393
394 if (IsCanonicalizedIP(host)) {
395 nsAutoCString key;
396 key.Assign(host);
397 key.Append("/");
398 return aHash->FromPlaintext(key, aCryptoHash);
399 }
400
401 nsTArray<nsCString> hostComponents;
402 ParseString(PromiseFlatCString(host), '.', hostComponents);
403
404 if (hostComponents.Length() < 2)
405 return NS_ERROR_FAILURE;
406
407 int32_t last = int32_t(hostComponents.Length()) - 1;
408 nsAutoCString lookupHost;
409
410 if (hostComponents.Length() > 2) {
411 lookupHost.Append(hostComponents[last - 2]);
412 lookupHost.Append(".");
413 }
414
415 lookupHost.Append(hostComponents[last - 1]);
416 lookupHost.Append(".");
417 lookupHost.Append(hostComponents[last]);
418 lookupHost.Append("/");
419
420 return aHash->FromPlaintext(lookupHost, aCryptoHash);
421 }
422
423 /* static */ nsresult
424 LookupCache::GetLookupFragments(const nsACString& aSpec,
425 nsTArray<nsCString>* aFragments)
426
427 {
428 aFragments->Clear();
429
430 nsACString::const_iterator begin, end, iter;
431 aSpec.BeginReading(begin);
432 aSpec.EndReading(end);
433
434 iter = begin;
435 if (!FindCharInReadable('/', iter, end)) {
436 return NS_OK;
437 }
438
439 const nsCSubstring& host = Substring(begin, iter++);
440 nsAutoCString path;
441 path.Assign(Substring(iter, end));
442
443 /**
444 * From the protocol doc:
445 * For the hostname, the client will try at most 5 different strings. They
446 * are:
447 * a) The exact hostname of the url
448 * b) The 4 hostnames formed by starting with the last 5 components and
449 * successivly removing the leading component. The top-level component
450 * can be skipped. This is not done if the hostname is a numerical IP.
451 */
452 nsTArray<nsCString> hosts;
453 hosts.AppendElement(host);
454
455 if (!IsCanonicalizedIP(host)) {
456 host.BeginReading(begin);
457 host.EndReading(end);
458 int numHostComponents = 0;
459 while (RFindInReadable(NS_LITERAL_CSTRING("."), begin, end) &&
460 numHostComponents < MAX_HOST_COMPONENTS) {
461 // don't bother checking toplevel domains
462 if (++numHostComponents >= 2) {
463 host.EndReading(iter);
464 hosts.AppendElement(Substring(end, iter));
465 }
466 end = begin;
467 host.BeginReading(begin);
468 }
469 }
470
471 /**
472 * From the protocol doc:
473 * For the path, the client will also try at most 6 different strings.
474 * They are:
475 * a) the exact path of the url, including query parameters
476 * b) the exact path of the url, without query parameters
477 * c) the 4 paths formed by starting at the root (/) and
478 * successively appending path components, including a trailing
479 * slash. This behavior should only extend up to the next-to-last
480 * path component, that is, a trailing slash should never be
481 * appended that was not present in the original url.
482 */
483 nsTArray<nsCString> paths;
484 nsAutoCString pathToAdd;
485
486 path.BeginReading(begin);
487 path.EndReading(end);
488 iter = begin;
489 if (FindCharInReadable('?', iter, end)) {
490 pathToAdd = Substring(begin, iter);
491 paths.AppendElement(pathToAdd);
492 end = iter;
493 }
494
495 int numPathComponents = 1;
496 iter = begin;
497 while (FindCharInReadable('/', iter, end) &&
498 numPathComponents < MAX_PATH_COMPONENTS) {
499 iter++;
500 pathToAdd.Assign(Substring(begin, iter));
501 paths.AppendElement(pathToAdd);
502 numPathComponents++;
503 }
504
505 // If we haven't already done so, add the full path
506 if (!pathToAdd.Equals(path)) {
507 paths.AppendElement(path);
508 }
509 // Check an empty path (for whole-domain blacklist entries)
510 paths.AppendElement(EmptyCString());
511
512 for (uint32_t hostIndex = 0; hostIndex < hosts.Length(); hostIndex++) {
513 for (uint32_t pathIndex = 0; pathIndex < paths.Length(); pathIndex++) {
514 nsCString key;
515 key.Assign(hosts[hostIndex]);
516 key.Append('/');
517 key.Append(paths[pathIndex]);
518 LOG(("Checking fragment %s", key.get()));
519
520 aFragments->AppendElement(key);
521 }
522 }
523
524 return NS_OK;
525 }
526
527 /* static */ nsresult
528 LookupCache::GetHostKeys(const nsACString& aSpec,
529 nsTArray<nsCString>* aHostKeys)
530 {
531 nsACString::const_iterator begin, end, iter;
532 aSpec.BeginReading(begin);
533 aSpec.EndReading(end);
534
535 iter = begin;
536 if (!FindCharInReadable('/', iter, end)) {
537 return NS_OK;
538 }
539
540 const nsCSubstring& host = Substring(begin, iter);
541
542 if (IsCanonicalizedIP(host)) {
543 nsCString *key = aHostKeys->AppendElement();
544 if (!key)
545 return NS_ERROR_OUT_OF_MEMORY;
546
547 key->Assign(host);
548 key->Append("/");
549 return NS_OK;
550 }
551
552 nsTArray<nsCString> hostComponents;
553 ParseString(PromiseFlatCString(host), '.', hostComponents);
554
555 if (hostComponents.Length() < 2) {
556 // no host or toplevel host, this won't match anything in the db
557 return NS_OK;
558 }
559
560 // First check with two domain components
561 int32_t last = int32_t(hostComponents.Length()) - 1;
562 nsCString *lookupHost = aHostKeys->AppendElement();
563 if (!lookupHost)
564 return NS_ERROR_OUT_OF_MEMORY;
565
566 lookupHost->Assign(hostComponents[last - 1]);
567 lookupHost->Append(".");
568 lookupHost->Append(hostComponents[last]);
569 lookupHost->Append("/");
570
571 // Now check with three domain components
572 if (hostComponents.Length() > 2) {
573 nsCString *lookupHost2 = aHostKeys->AppendElement();
574 if (!lookupHost2)
575 return NS_ERROR_OUT_OF_MEMORY;
576 lookupHost2->Assign(hostComponents[last - 2]);
577 lookupHost2->Append(".");
578 lookupHost2->Append(*lookupHost);
579 }
580
581 return NS_OK;
582 }
583
584 bool LookupCache::IsPrimed()
585 {
586 return mPrimed;
587 }
588
589 #ifdef DEBUG
590 template <class T>
591 static void EnsureSorted(T* aArray)
592 {
593 typename T::elem_type* start = aArray->Elements();
594 typename T::elem_type* end = aArray->Elements() + aArray->Length();
595 typename T::elem_type* iter = start;
596 typename T::elem_type* previous = start;
597
598 while (iter != end) {
599 previous = iter;
600 ++iter;
601 if (iter != end) {
602 MOZ_ASSERT(*previous <= *iter);
603 }
604 }
605 return;
606 }
607 #endif
608
609 nsresult
610 LookupCache::ConstructPrefixSet(AddPrefixArray& aAddPrefixes)
611 {
612 Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_PS_CONSTRUCT_TIME> timer;
613
614 nsTArray<uint32_t> array;
615 array.SetCapacity(aAddPrefixes.Length());
616
617 for (uint32_t i = 0; i < aAddPrefixes.Length(); i++) {
618 array.AppendElement(aAddPrefixes[i].PrefixHash().ToUint32());
619 }
620 aAddPrefixes.Clear();
621
622 #ifdef DEBUG
623 // PrefixSet requires sorted order
624 EnsureSorted(&array);
625 #endif
626
627 // construct new one, replace old entries
628 nsresult rv = mPrefixSet->SetPrefixes(array.Elements(), array.Length());
629 if (NS_FAILED(rv)) {
630 goto error_bailout;
631 }
632
633 #ifdef DEBUG
634 uint32_t size;
635 size = mPrefixSet->SizeOfIncludingThis(moz_malloc_size_of);
636 LOG(("SB tree done, size = %d bytes\n", size));
637 #endif
638
639 mPrimed = true;
640
641 return NS_OK;
642
643 error_bailout:
644 Telemetry::Accumulate(Telemetry::URLCLASSIFIER_PS_FAILURE, 1);
645 return rv;
646 }
647
648 nsresult
649 LookupCache::LoadPrefixSet()
650 {
651 nsCOMPtr<nsIFile> psFile;
652 nsresult rv = mStoreDirectory->Clone(getter_AddRefs(psFile));
653 NS_ENSURE_SUCCESS(rv, rv);
654
655 rv = psFile->AppendNative(mTableName + NS_LITERAL_CSTRING(PREFIXSET_SUFFIX));
656 NS_ENSURE_SUCCESS(rv, rv);
657
658 bool exists;
659 rv = psFile->Exists(&exists);
660 NS_ENSURE_SUCCESS(rv, rv);
661
662 if (exists) {
663 LOG(("stored PrefixSet exists, loading from disk"));
664 rv = mPrefixSet->LoadFromFile(psFile);
665 if (NS_FAILED(rv)) {
666 if (rv == NS_ERROR_FILE_CORRUPTED) {
667 Reset();
668 }
669 return rv;
670 }
671 mPrimed = true;
672 } else {
673 LOG(("no (usable) stored PrefixSet found"));
674 }
675
676 #ifdef DEBUG
677 if (mPrimed) {
678 uint32_t size = mPrefixSet->SizeOfIncludingThis(moz_malloc_size_of);
679 LOG(("SB tree done, size = %d bytes\n", size));
680 }
681 #endif
682
683 return NS_OK;
684 }
685
686 nsresult
687 LookupCache::GetPrefixes(nsTArray<uint32_t>* aAddPrefixes)
688 {
689 if (!mPrimed) {
690 // This can happen if its a new table, so no error.
691 LOG(("GetPrefixes from empty LookupCache"));
692 return NS_OK;
693 }
694 uint32_t cnt;
695 uint32_t *arr;
696 nsresult rv = mPrefixSet->GetPrefixes(&cnt, &arr);
697 NS_ENSURE_SUCCESS(rv, rv);
698 if (!aAddPrefixes->AppendElements(arr, cnt))
699 return NS_ERROR_FAILURE;
700 nsMemory::Free(arr);
701 return NS_OK;
702 }
703
704
705 }
706 }

mercurial