1 //* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 // Originally based on Chrome sources:
3 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 //    * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 //    * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 //    * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 
32 #include "HashStore.h"
33 #include "nsICryptoHash.h"
34 #include "nsISeekableStream.h"
35 #include "nsIStreamConverterService.h"
36 #include "nsNetUtil.h"
37 #include "nsCheckSummedOutputStream.h"
38 #include "prio.h"
39 #include "mozilla/Logging.h"
40 #include "zlib.h"
41 #include "Classifier.h"
42 #include "nsUrlClassifierDBService.h"
43 
44 // Main store for SafeBrowsing protocol data. We store
45 // known add/sub chunks, prefixes and completions in memory
46 // during an update, and serialize to disk.
47 // We do not store the add prefixes, those are retrieved by
48 // decompressing the PrefixSet cache whenever we need to apply
49 // an update.
50 //
51 // byte slicing: Many of the 4-byte values stored here are strongly
52 // correlated in the upper bytes, and uncorrelated in the lower
53 // bytes. Because zlib/DEFLATE requires match lengths of at least
54 // 3 to achieve good compression, and we don't get those if only
55 // the upper 16-bits are correlated, it is worthwhile to slice 32-bit
56 // values into 4 1-byte slices and compress the slices individually.
57 // The slices corresponding to MSBs will compress very well, and the
58 // slice corresponding to LSB almost nothing. Because of this, we
59 // only apply DEFLATE to the 3 most significant bytes, and store the
60 // LSB uncompressed.
61 //
62 // byte sliced (numValues) data format:
63 //    uint32_t compressed-size
64 //    compressed-size bytes    zlib DEFLATE data
65 //        0...numValues        byte MSB of 4-byte numValues data
66 //    uint32_t compressed-size
67 //    compressed-size bytes    zlib DEFLATE data
68 //        0...numValues        byte 2nd byte of 4-byte numValues data
69 //    uint32_t compressed-size
70 //    compressed-size bytes    zlib DEFLATE data
71 //        0...numValues        byte 3rd byte of 4-byte numValues data
72 //    0...numValues            byte LSB of 4-byte numValues data
73 //
74 // Store data format:
75 //    uint32_t magic
76 //    uint32_t version
77 //    uint32_t numAddChunks
78 //    uint32_t numSubChunks
79 //    uint32_t numAddPrefixes
80 //    uint32_t numSubPrefixes
81 //    uint32_t numAddCompletes
82 //    uint32_t numSubCompletes
83 //    0...numAddChunks               uint32_t addChunk
84 //    0...numSubChunks               uint32_t subChunk
85 //    byte sliced (numAddPrefixes)   uint32_t add chunk of AddPrefixes
86 //    byte sliced (numSubPrefixes)   uint32_t add chunk of SubPrefixes
87 //    byte sliced (numSubPrefixes)   uint32_t sub chunk of SubPrefixes
88 //    byte sliced (numSubPrefixes)   uint32_t SubPrefixes
89 //    0...numAddCompletes            32-byte Completions + uint32_t addChunk
90 //    0...numSubCompletes            32-byte Completions + uint32_t addChunk
91 //                                                       + uint32_t subChunk
92 //    16-byte MD5 of all preceding data
93 
94 // Name of the SafeBrowsing store
95 #define STORE_SUFFIX ".sbstore"
96 
97 // MOZ_LOG=UrlClassifierDbService:5
98 extern mozilla::LazyLogModule gUrlClassifierDbServiceLog;
99 #define LOG(args) MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args)
100 #define LOG_ENABLED() MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug)
101 
102 // Either the return was successful or we call the Reset function (unless we
103 // hit an OOM).  Used while reading in the store.
104 #define SUCCESS_OR_RESET(res)                                             \
105   do {                                                                    \
106     nsresult __rv = res; /* Don't evaluate |res| more than once */        \
107     if (__rv == NS_ERROR_OUT_OF_MEMORY) {                                 \
108       NS_WARNING("SafeBrowsing OOM.");                                    \
109       return __rv;                                                        \
110     }                                                                     \
111     if (NS_FAILED(__rv)) {                                                \
112       NS_WARNING("SafeBrowsing store corrupted or out of date.");         \
113       Reset();                                                            \
114       return __rv;                                                        \
115     }                                                                     \
116   } while(0)
117 
118 namespace mozilla {
119 namespace safebrowsing {
120 
121 const uint32_t STORE_MAGIC = 0x1231af3b;
122 const uint32_t CURRENT_VERSION = 3;
123 
124 nsresult
NewAddPrefix(uint32_t aAddChunk,const Prefix & aHash)125 TableUpdateV2::NewAddPrefix(uint32_t aAddChunk, const Prefix& aHash)
126 {
127   AddPrefix *add = mAddPrefixes.AppendElement(fallible);
128   if (!add) return NS_ERROR_OUT_OF_MEMORY;
129   add->addChunk = aAddChunk;
130   add->prefix = aHash;
131   return NS_OK;
132 }
133 
134 nsresult
NewSubPrefix(uint32_t aAddChunk,const Prefix & aHash,uint32_t aSubChunk)135 TableUpdateV2::NewSubPrefix(uint32_t aAddChunk, const Prefix& aHash, uint32_t aSubChunk)
136 {
137   SubPrefix *sub = mSubPrefixes.AppendElement(fallible);
138   if (!sub) return NS_ERROR_OUT_OF_MEMORY;
139   sub->addChunk = aAddChunk;
140   sub->prefix = aHash;
141   sub->subChunk = aSubChunk;
142   return NS_OK;
143 }
144 
145 nsresult
NewAddComplete(uint32_t aAddChunk,const Completion & aHash)146 TableUpdateV2::NewAddComplete(uint32_t aAddChunk, const Completion& aHash)
147 {
148   AddComplete *add = mAddCompletes.AppendElement(fallible);
149   if (!add) return NS_ERROR_OUT_OF_MEMORY;
150   add->addChunk = aAddChunk;
151   add->complete = aHash;
152   return NS_OK;
153 }
154 
155 nsresult
NewSubComplete(uint32_t aAddChunk,const Completion & aHash,uint32_t aSubChunk)156 TableUpdateV2::NewSubComplete(uint32_t aAddChunk, const Completion& aHash, uint32_t aSubChunk)
157 {
158   SubComplete *sub = mSubCompletes.AppendElement(fallible);
159   if (!sub) return NS_ERROR_OUT_OF_MEMORY;
160   sub->addChunk = aAddChunk;
161   sub->complete = aHash;
162   sub->subChunk = aSubChunk;
163   return NS_OK;
164 }
165 
166 void
NewPrefixes(int32_t aSize,std::string & aPrefixes)167 TableUpdateV4::NewPrefixes(int32_t aSize, std::string& aPrefixes)
168 {
169   NS_ENSURE_TRUE_VOID(aPrefixes.size() % aSize == 0);
170   NS_ENSURE_TRUE_VOID(!mPrefixesMap.Get(aSize));
171 
172   if (LOG_ENABLED() && 4 == aSize) {
173     int numOfPrefixes = aPrefixes.size() / 4;
174     uint32_t* p = (uint32_t*)aPrefixes.c_str();
175 
176     // Dump the first/last 10 fixed-length prefixes for debugging.
177     LOG(("* The first 10 (maximum) fixed-length prefixes: "));
178     for (int i = 0; i < std::min(10, numOfPrefixes); i++) {
179       uint8_t* c = (uint8_t*)&p[i];
180       LOG(("%.2X%.2X%.2X%.2X", c[0], c[1], c[2], c[3]));
181     }
182 
183     LOG(("* The last 10 (maximum) fixed-length prefixes: "));
184     for (int i = std::max(0, numOfPrefixes - 10); i < numOfPrefixes; i++) {
185       uint8_t* c = (uint8_t*)&p[i];
186       LOG(("%.2X%.2X%.2X%.2X", c[0], c[1], c[2], c[3]));
187     }
188 
189     LOG(("---- %d fixed-length prefixes in total.", aPrefixes.size() / aSize));
190   }
191 
192   PrefixStdString* prefix = new PrefixStdString(aPrefixes);
193   mPrefixesMap.Put(aSize, prefix);
194 }
195 
196 void
NewRemovalIndices(const uint32_t * aIndices,size_t aNumOfIndices)197 TableUpdateV4::NewRemovalIndices(const uint32_t* aIndices, size_t aNumOfIndices)
198 {
199   for (size_t i = 0; i < aNumOfIndices; i++) {
200     mRemovalIndiceArray.AppendElement(aIndices[i]);
201   }
202 }
203 
204 void
NewChecksum(const std::string & aChecksum)205 TableUpdateV4::NewChecksum(const std::string& aChecksum)
206 {
207   mChecksum.Assign(aChecksum.data(), aChecksum.size());
208 }
209 
HashStore(const nsACString & aTableName,const nsACString & aProvider,nsIFile * aRootStoreDir)210 HashStore::HashStore(const nsACString& aTableName,
211                      const nsACString& aProvider,
212                      nsIFile* aRootStoreDir)
213   : mTableName(aTableName)
214   , mInUpdate(false)
215   , mFileSize(0)
216 {
217   nsresult rv = Classifier::GetPrivateStoreDirectory(aRootStoreDir,
218                                                      aTableName,
219                                                      aProvider,
220                                                      getter_AddRefs(mStoreDirectory));
221   if (NS_FAILED(rv)) {
222     LOG(("Failed to get private store directory for %s", mTableName.get()));
223     mStoreDirectory = aRootStoreDir;
224   }
225 }
226 
~HashStore()227 HashStore::~HashStore()
228 {
229 }
230 
231 nsresult
Reset()232 HashStore::Reset()
233 {
234   LOG(("HashStore resetting"));
235 
236   nsCOMPtr<nsIFile> storeFile;
237   nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
238   NS_ENSURE_SUCCESS(rv, rv);
239 
240   rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(STORE_SUFFIX));
241   NS_ENSURE_SUCCESS(rv, rv);
242 
243   rv = storeFile->Remove(false);
244   NS_ENSURE_SUCCESS(rv, rv);
245 
246   mFileSize = 0;
247 
248   return NS_OK;
249 }
250 
251 nsresult
CheckChecksum(uint32_t aFileSize)252 HashStore::CheckChecksum(uint32_t aFileSize)
253 {
254   if (!mInputStream) {
255     return NS_OK;
256   }
257 
258   // Check for file corruption by
259   // comparing the stored checksum to actual checksum of data
260   nsAutoCString hash;
261   nsAutoCString compareHash;
262   char *data;
263   uint32_t read;
264 
265   nsresult rv = CalculateChecksum(hash, aFileSize, true);
266   NS_ENSURE_SUCCESS(rv, rv);
267 
268   compareHash.GetMutableData(&data, hash.Length());
269 
270   if (hash.Length() > aFileSize) {
271     NS_WARNING("SafeBrowing file not long enough to store its hash");
272     return NS_ERROR_FAILURE;
273   }
274   nsCOMPtr<nsISeekableStream> seekIn = do_QueryInterface(mInputStream);
275   rv = seekIn->Seek(nsISeekableStream::NS_SEEK_SET, aFileSize - hash.Length());
276   NS_ENSURE_SUCCESS(rv, rv);
277 
278   rv = mInputStream->Read(data, hash.Length(), &read);
279   NS_ENSURE_SUCCESS(rv, rv);
280   NS_ASSERTION(read == hash.Length(), "Could not read hash bytes");
281 
282   if (!hash.Equals(compareHash)) {
283     NS_WARNING("Safebrowing file failed checksum.");
284     return NS_ERROR_FAILURE;
285   }
286 
287   return NS_OK;
288 }
289 
290 nsresult
Open()291 HashStore::Open()
292 {
293   nsCOMPtr<nsIFile> storeFile;
294   nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
295   NS_ENSURE_SUCCESS(rv, rv);
296 
297   rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(".sbstore"));
298   NS_ENSURE_SUCCESS(rv, rv);
299 
300   nsCOMPtr<nsIInputStream> origStream;
301   rv = NS_NewLocalFileInputStream(getter_AddRefs(origStream), storeFile,
302                                   PR_RDONLY | nsIFile::OS_READAHEAD);
303 
304   if (rv == NS_ERROR_FILE_NOT_FOUND) {
305     UpdateHeader();
306     return NS_OK;
307   } else {
308     SUCCESS_OR_RESET(rv);
309   }
310 
311   int64_t fileSize;
312   rv = storeFile->GetFileSize(&fileSize);
313   NS_ENSURE_SUCCESS(rv, rv);
314 
315   if (fileSize < 0 || fileSize > UINT32_MAX) {
316     return NS_ERROR_FAILURE;
317   }
318 
319   mFileSize = static_cast<uint32_t>(fileSize);
320   mInputStream = NS_BufferInputStream(origStream, mFileSize);
321 
322   rv = ReadHeader();
323   SUCCESS_OR_RESET(rv);
324 
325   rv = SanityCheck();
326   SUCCESS_OR_RESET(rv);
327 
328   return NS_OK;
329 }
330 
331 nsresult
ReadHeader()332 HashStore::ReadHeader()
333 {
334   if (!mInputStream) {
335     UpdateHeader();
336     return NS_OK;
337   }
338 
339   nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mInputStream);
340   nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
341   NS_ENSURE_SUCCESS(rv, rv);
342 
343   void *buffer = &mHeader;
344   rv = NS_ReadInputStreamToBuffer(mInputStream,
345                                   &buffer,
346                                   sizeof(Header));
347   NS_ENSURE_SUCCESS(rv, rv);
348 
349   return NS_OK;
350 }
351 
352 nsresult
SanityCheck()353 HashStore::SanityCheck()
354 {
355   if (mHeader.magic != STORE_MAGIC || mHeader.version != CURRENT_VERSION) {
356     NS_WARNING("Unexpected header data in the store.");
357     return NS_ERROR_FAILURE;
358   }
359 
360   return NS_OK;
361 }
362 
363 nsresult
CalculateChecksum(nsAutoCString & aChecksum,uint32_t aFileSize,bool aChecksumPresent)364 HashStore::CalculateChecksum(nsAutoCString& aChecksum,
365                              uint32_t aFileSize,
366                              bool aChecksumPresent)
367 {
368   aChecksum.Truncate();
369 
370   // Reset mInputStream to start
371   nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mInputStream);
372   nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
373 
374   nsCOMPtr<nsICryptoHash> hash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
375   NS_ENSURE_SUCCESS(rv, rv);
376 
377   // Size of MD5 hash in bytes
378   const uint32_t CHECKSUM_SIZE = 16;
379 
380   // MD5 is not a secure hash function, but since this is a filesystem integrity
381   // check, this usage is ok.
382   rv = hash->Init(nsICryptoHash::MD5);
383   NS_ENSURE_SUCCESS(rv, rv);
384 
385   if (!aChecksumPresent) {
386     // Hash entire file
387     rv = hash->UpdateFromStream(mInputStream, UINT32_MAX);
388   } else {
389     // Hash everything but last checksum bytes
390     if (aFileSize < CHECKSUM_SIZE) {
391       NS_WARNING("SafeBrowsing file isn't long enough to store its checksum");
392       return NS_ERROR_FAILURE;
393     }
394     rv = hash->UpdateFromStream(mInputStream, aFileSize - CHECKSUM_SIZE);
395   }
396   NS_ENSURE_SUCCESS(rv, rv);
397 
398   rv = hash->Finish(false, aChecksum);
399   NS_ENSURE_SUCCESS(rv, rv);
400 
401   return NS_OK;
402 }
403 
404 void
UpdateHeader()405 HashStore::UpdateHeader()
406 {
407   mHeader.magic = STORE_MAGIC;
408   mHeader.version = CURRENT_VERSION;
409 
410   mHeader.numAddChunks = mAddChunks.Length();
411   mHeader.numSubChunks = mSubChunks.Length();
412   mHeader.numAddPrefixes = mAddPrefixes.Length();
413   mHeader.numSubPrefixes = mSubPrefixes.Length();
414   mHeader.numAddCompletes = mAddCompletes.Length();
415   mHeader.numSubCompletes = mSubCompletes.Length();
416 }
417 
418 nsresult
ReadChunkNumbers()419 HashStore::ReadChunkNumbers()
420 {
421   if (!mInputStream || AlreadyReadChunkNumbers()) {
422     return NS_OK;
423   }
424 
425   nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mInputStream);
426   nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
427                                sizeof(Header));
428   NS_ENSURE_SUCCESS(rv, rv);
429 
430   rv = mAddChunks.Read(mInputStream, mHeader.numAddChunks);
431   NS_ENSURE_SUCCESS(rv, rv);
432   NS_ASSERTION(mAddChunks.Length() == mHeader.numAddChunks, "Read the right amount of add chunks.");
433 
434   rv = mSubChunks.Read(mInputStream, mHeader.numSubChunks);
435   NS_ENSURE_SUCCESS(rv, rv);
436   NS_ASSERTION(mSubChunks.Length() == mHeader.numSubChunks, "Read the right amount of sub chunks.");
437 
438   return NS_OK;
439 }
440 
441 nsresult
ReadHashes()442 HashStore::ReadHashes()
443 {
444   if (!mInputStream) {
445     // BeginUpdate has been called but Open hasn't initialized mInputStream,
446     // because the existing HashStore is empty.
447     return NS_OK;
448   }
449 
450   nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mInputStream);
451 
452   uint32_t offset = sizeof(Header);
453   offset += (mHeader.numAddChunks + mHeader.numSubChunks) * sizeof(uint32_t);
454   nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
455   NS_ENSURE_SUCCESS(rv, rv);
456 
457   rv = ReadAddPrefixes();
458   NS_ENSURE_SUCCESS(rv, rv);
459 
460   rv = ReadSubPrefixes();
461   NS_ENSURE_SUCCESS(rv, rv);
462 
463   // If completions was read before, then we are done here.
464   if (AlreadyReadCompletions()) {
465     return NS_OK;
466   }
467 
468   rv = ReadTArray(mInputStream, &mAddCompletes, mHeader.numAddCompletes);
469   NS_ENSURE_SUCCESS(rv, rv);
470 
471   rv = ReadTArray(mInputStream, &mSubCompletes, mHeader.numSubCompletes);
472   NS_ENSURE_SUCCESS(rv, rv);
473 
474   return NS_OK;
475 }
476 
477 
478 nsresult
ReadCompletions()479 HashStore::ReadCompletions()
480 {
481   if (!mInputStream || AlreadyReadCompletions()) {
482     return NS_OK;
483   }
484 
485   nsCOMPtr<nsIFile> storeFile;
486   nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
487   NS_ENSURE_SUCCESS(rv, rv);
488 
489   rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(STORE_SUFFIX));
490   NS_ENSURE_SUCCESS(rv, rv);
491 
492   uint32_t offset = mFileSize -
493                     sizeof(struct AddComplete) * mHeader.numAddCompletes -
494                     sizeof(struct SubComplete) * mHeader.numSubCompletes -
495                     nsCheckSummedOutputStream::CHECKSUM_SIZE;
496 
497   nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mInputStream);
498 
499   rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
500   NS_ENSURE_SUCCESS(rv, rv);
501 
502   rv = ReadTArray(mInputStream, &mAddCompletes, mHeader.numAddCompletes);
503   NS_ENSURE_SUCCESS(rv, rv);
504 
505   rv = ReadTArray(mInputStream, &mSubCompletes, mHeader.numSubCompletes);
506   NS_ENSURE_SUCCESS(rv, rv);
507 
508   return NS_OK;
509 }
510 
511 nsresult
PrepareForUpdate()512 HashStore::PrepareForUpdate()
513 {
514   nsresult rv = CheckChecksum(mFileSize);
515   SUCCESS_OR_RESET(rv);
516 
517   rv = ReadChunkNumbers();
518   SUCCESS_OR_RESET(rv);
519 
520   rv = ReadHashes();
521   SUCCESS_OR_RESET(rv);
522 
523   return NS_OK;
524 }
525 
526 nsresult
BeginUpdate()527 HashStore::BeginUpdate()
528 {
529   // Check wether the file is corrupted and read the rest of the store
530   // in memory.
531   nsresult rv = PrepareForUpdate();
532   NS_ENSURE_SUCCESS(rv, rv);
533 
534   // Close input stream, won't be needed any more and
535   // we will rewrite ourselves.
536   if (mInputStream) {
537     rv = mInputStream->Close();
538     NS_ENSURE_SUCCESS(rv, rv);
539   }
540 
541   mInUpdate = true;
542 
543   return NS_OK;
544 }
545 
546 template<class T>
547 static nsresult
Merge(ChunkSet * aStoreChunks,FallibleTArray<T> * aStorePrefixes,ChunkSet & aUpdateChunks,FallibleTArray<T> & aUpdatePrefixes,bool aAllowMerging=false)548 Merge(ChunkSet* aStoreChunks,
549       FallibleTArray<T>* aStorePrefixes,
550       ChunkSet& aUpdateChunks,
551       FallibleTArray<T>& aUpdatePrefixes,
552       bool aAllowMerging = false)
553 {
554   EntrySort(aUpdatePrefixes);
555 
556   T* updateIter = aUpdatePrefixes.Elements();
557   T* updateEnd = aUpdatePrefixes.Elements() + aUpdatePrefixes.Length();
558 
559   T* storeIter = aStorePrefixes->Elements();
560   T* storeEnd = aStorePrefixes->Elements() + aStorePrefixes->Length();
561 
562   // use a separate array so we can keep the iterators valid
563   // if the nsTArray grows
564   nsTArray<T> adds;
565 
566   for (; updateIter != updateEnd; updateIter++) {
567     // skip this chunk if we already have it, unless we're
568     // merging completions, in which case we'll always already
569     // have the chunk from the original prefix
570     if (aStoreChunks->Has(updateIter->Chunk()))
571       if (!aAllowMerging)
572         continue;
573     // XXX: binary search for insertion point might be faster in common
574     // case?
575     while (storeIter < storeEnd && (storeIter->Compare(*updateIter) < 0)) {
576       // skip forward to matching element (or not...)
577       storeIter++;
578     }
579     // no match, add
580     if (storeIter == storeEnd
581         || storeIter->Compare(*updateIter) != 0) {
582       if (!adds.AppendElement(*updateIter))
583         return NS_ERROR_OUT_OF_MEMORY;
584     }
585   }
586 
587   // Chunks can be empty, but we should still report we have them
588   // to make the chunkranges continuous.
589   aStoreChunks->Merge(aUpdateChunks);
590 
591   if (!aStorePrefixes->AppendElements(adds, fallible))
592     return NS_ERROR_OUT_OF_MEMORY;
593 
594   EntrySort(*aStorePrefixes);
595 
596   return NS_OK;
597 }
598 
599 nsresult
ApplyUpdate(TableUpdate & aUpdate)600 HashStore::ApplyUpdate(TableUpdate &aUpdate)
601 {
602   auto updateV2 = TableUpdate::Cast<TableUpdateV2>(&aUpdate);
603   NS_ENSURE_TRUE(updateV2, NS_ERROR_FAILURE);
604 
605   TableUpdateV2& update = *updateV2;
606 
607   nsresult rv = mAddExpirations.Merge(update.AddExpirations());
608   NS_ENSURE_SUCCESS(rv, rv);
609 
610   rv = mSubExpirations.Merge(update.SubExpirations());
611   NS_ENSURE_SUCCESS(rv, rv);
612 
613   rv = Expire();
614   NS_ENSURE_SUCCESS(rv, rv);
615 
616   rv = Merge(&mAddChunks, &mAddPrefixes,
617              update.AddChunks(), update.AddPrefixes());
618   NS_ENSURE_SUCCESS(rv, rv);
619 
620   rv = Merge(&mAddChunks, &mAddCompletes,
621              update.AddChunks(), update.AddCompletes(), true);
622   NS_ENSURE_SUCCESS(rv, rv);
623 
624   rv = Merge(&mSubChunks, &mSubPrefixes,
625              update.SubChunks(), update.SubPrefixes());
626   NS_ENSURE_SUCCESS(rv, rv);
627 
628   rv = Merge(&mSubChunks, &mSubCompletes,
629              update.SubChunks(), update.SubCompletes(), true);
630   NS_ENSURE_SUCCESS(rv, rv);
631 
632   return NS_OK;
633 }
634 
635 nsresult
Rebuild()636 HashStore::Rebuild()
637 {
638   NS_ASSERTION(mInUpdate, "Must be in update to rebuild.");
639 
640   nsresult rv = ProcessSubs();
641   NS_ENSURE_SUCCESS(rv, rv);
642 
643   UpdateHeader();
644 
645   return NS_OK;
646 }
647 
648 void
ClearCompletes()649 HashStore::ClearCompletes()
650 {
651   NS_ASSERTION(mInUpdate, "Must be in update to clear completes.");
652 
653   mAddCompletes.Clear();
654   mSubCompletes.Clear();
655 
656   UpdateHeader();
657 }
658 
659 template<class T>
660 static void
ExpireEntries(FallibleTArray<T> * aEntries,ChunkSet & aExpirations)661 ExpireEntries(FallibleTArray<T>* aEntries, ChunkSet& aExpirations)
662 {
663   T* addIter = aEntries->Elements();
664   T* end = aEntries->Elements() + aEntries->Length();
665 
666   for (T *iter = addIter; iter != end; iter++) {
667     if (!aExpirations.Has(iter->Chunk())) {
668       *addIter = *iter;
669       addIter++;
670     }
671   }
672 
673   aEntries->TruncateLength(addIter - aEntries->Elements());
674 }
675 
676 nsresult
Expire()677 HashStore::Expire()
678 {
679   ExpireEntries(&mAddPrefixes, mAddExpirations);
680   ExpireEntries(&mAddCompletes, mAddExpirations);
681   ExpireEntries(&mSubPrefixes, mSubExpirations);
682   ExpireEntries(&mSubCompletes, mSubExpirations);
683 
684   mAddChunks.Remove(mAddExpirations);
685   mSubChunks.Remove(mSubExpirations);
686 
687   mAddExpirations.Clear();
688   mSubExpirations.Clear();
689 
690   return NS_OK;
691 }
692 
693 template<class T>
DeflateWriteTArray(nsIOutputStream * aStream,nsTArray<T> & aIn)694 nsresult DeflateWriteTArray(nsIOutputStream* aStream, nsTArray<T>& aIn)
695 {
696   uLongf insize = aIn.Length() * sizeof(T);
697   uLongf outsize = compressBound(insize);
698   FallibleTArray<char> outBuff;
699   if (!outBuff.SetLength(outsize, fallible)) {
700     return NS_ERROR_OUT_OF_MEMORY;
701   }
702 
703   int zerr = compress(reinterpret_cast<Bytef*>(outBuff.Elements()),
704                       &outsize,
705                       reinterpret_cast<const Bytef*>(aIn.Elements()),
706                       insize);
707   if (zerr != Z_OK) {
708     return NS_ERROR_FAILURE;
709   }
710   LOG(("DeflateWriteTArray: %d in %d out", insize, outsize));
711 
712   outBuff.TruncateLength(outsize);
713 
714   // Length of compressed data stream
715   uint32_t dataLen = outBuff.Length();
716   uint32_t written;
717   nsresult rv = aStream->Write(reinterpret_cast<char*>(&dataLen), sizeof(dataLen), &written);
718   NS_ENSURE_SUCCESS(rv, rv);
719 
720   NS_ASSERTION(written == sizeof(dataLen), "Error writing deflate length");
721 
722   // Store to stream
723   rv = WriteTArray(aStream, outBuff);
724   NS_ENSURE_SUCCESS(rv, rv);
725 
726   return NS_OK;
727 }
728 
729 template<class T>
InflateReadTArray(nsIInputStream * aStream,FallibleTArray<T> * aOut,uint32_t aExpectedSize)730 nsresult InflateReadTArray(nsIInputStream* aStream, FallibleTArray<T>* aOut,
731                            uint32_t aExpectedSize)
732 {
733 
734   uint32_t inLen;
735   uint32_t read;
736   nsresult rv = aStream->Read(reinterpret_cast<char*>(&inLen), sizeof(inLen), &read);
737   NS_ENSURE_SUCCESS(rv, rv);
738 
739   NS_ASSERTION(read == sizeof(inLen), "Error reading inflate length");
740 
741   FallibleTArray<char> inBuff;
742   if (!inBuff.SetLength(inLen, fallible)) {
743     return NS_ERROR_OUT_OF_MEMORY;
744   }
745 
746   rv = ReadTArray(aStream, &inBuff, inLen);
747   NS_ENSURE_SUCCESS(rv, rv);
748 
749   uLongf insize = inLen;
750   uLongf outsize = aExpectedSize * sizeof(T);
751   if (!aOut->SetLength(aExpectedSize, fallible)) {
752     return NS_ERROR_OUT_OF_MEMORY;
753   }
754 
755   int zerr = uncompress(reinterpret_cast<Bytef*>(aOut->Elements()),
756                         &outsize,
757                         reinterpret_cast<const Bytef*>(inBuff.Elements()),
758                         insize);
759   if (zerr != Z_OK) {
760     return NS_ERROR_FAILURE;
761   }
762   LOG(("InflateReadTArray: %d in %d out", insize, outsize));
763 
764   NS_ASSERTION(outsize == aExpectedSize * sizeof(T), "Decompression size mismatch");
765 
766   return NS_OK;
767 }
768 
769 static nsresult
ByteSliceWrite(nsIOutputStream * aOut,nsTArray<uint32_t> & aData)770 ByteSliceWrite(nsIOutputStream* aOut, nsTArray<uint32_t>& aData)
771 {
772   nsTArray<uint8_t> slice;
773   uint32_t count = aData.Length();
774 
775   // Only process one slice at a time to avoid using too much memory.
776   if (!slice.SetLength(count, fallible)) {
777     return NS_ERROR_OUT_OF_MEMORY;
778   }
779 
780   // Process slice 1.
781   for (uint32_t i = 0; i < count; i++) {
782     slice[i] = (aData[i] >> 24);
783   }
784 
785   nsresult rv = DeflateWriteTArray(aOut, slice);
786   NS_ENSURE_SUCCESS(rv, rv);
787 
788   // Process slice 2.
789   for (uint32_t i = 0; i < count; i++) {
790     slice[i] = ((aData[i] >> 16) & 0xFF);
791   }
792 
793   rv = DeflateWriteTArray(aOut, slice);
794   NS_ENSURE_SUCCESS(rv, rv);
795 
796   // Process slice 3.
797   for (uint32_t i = 0; i < count; i++) {
798     slice[i] = ((aData[i] >> 8) & 0xFF);
799   }
800 
801   rv = DeflateWriteTArray(aOut, slice);
802   NS_ENSURE_SUCCESS(rv, rv);
803 
804   // Process slice 4.
805   for (uint32_t i = 0; i < count; i++) {
806     slice[i] = (aData[i] & 0xFF);
807   }
808 
809   // The LSB slice is generally uncompressible, don't bother
810   // compressing it.
811   rv = WriteTArray(aOut, slice);
812   NS_ENSURE_SUCCESS(rv, rv);
813 
814   return NS_OK;
815 }
816 
817 static nsresult
ByteSliceRead(nsIInputStream * aInStream,FallibleTArray<uint32_t> * aData,uint32_t count)818 ByteSliceRead(nsIInputStream* aInStream, FallibleTArray<uint32_t>* aData, uint32_t count)
819 {
820   FallibleTArray<uint8_t> slice1;
821   FallibleTArray<uint8_t> slice2;
822   FallibleTArray<uint8_t> slice3;
823   FallibleTArray<uint8_t> slice4;
824 
825   nsresult rv = InflateReadTArray(aInStream, &slice1, count);
826   NS_ENSURE_SUCCESS(rv, rv);
827 
828   rv = InflateReadTArray(aInStream, &slice2, count);
829   NS_ENSURE_SUCCESS(rv, rv);
830 
831   rv = InflateReadTArray(aInStream, &slice3, count);
832   NS_ENSURE_SUCCESS(rv, rv);
833 
834   rv = ReadTArray(aInStream, &slice4, count);
835   NS_ENSURE_SUCCESS(rv, rv);
836 
837   if (!aData->SetCapacity(count, fallible)) {
838     return NS_ERROR_OUT_OF_MEMORY;
839   }
840 
841   for (uint32_t i = 0; i < count; i++) {
842     aData->AppendElement((slice1[i] << 24) |
843                            (slice2[i] << 16) |
844                            (slice3[i] << 8) |
845                            (slice4[i]),
846                          fallible);
847   }
848 
849   return NS_OK;
850 }
851 
852 nsresult
ReadAddPrefixes()853 HashStore::ReadAddPrefixes()
854 {
855   FallibleTArray<uint32_t> chunks;
856   uint32_t count = mHeader.numAddPrefixes;
857 
858   nsresult rv = ByteSliceRead(mInputStream, &chunks, count);
859   NS_ENSURE_SUCCESS(rv, rv);
860 
861   if (!mAddPrefixes.SetCapacity(count, fallible)) {
862     return NS_ERROR_OUT_OF_MEMORY;
863   }
864   for (uint32_t i = 0; i < count; i++) {
865     AddPrefix *add = mAddPrefixes.AppendElement(fallible);
866     add->prefix.FromUint32(0);
867     add->addChunk = chunks[i];
868   }
869 
870   return NS_OK;
871 }
872 
873 nsresult
ReadSubPrefixes()874 HashStore::ReadSubPrefixes()
875 {
876   FallibleTArray<uint32_t> addchunks;
877   FallibleTArray<uint32_t> subchunks;
878   FallibleTArray<uint32_t> prefixes;
879   uint32_t count = mHeader.numSubPrefixes;
880 
881   nsresult rv = ByteSliceRead(mInputStream, &addchunks, count);
882   NS_ENSURE_SUCCESS(rv, rv);
883 
884   rv = ByteSliceRead(mInputStream, &subchunks, count);
885   NS_ENSURE_SUCCESS(rv, rv);
886 
887   rv = ByteSliceRead(mInputStream, &prefixes, count);
888   NS_ENSURE_SUCCESS(rv, rv);
889 
890   if (!mSubPrefixes.SetCapacity(count, fallible)) {
891     return NS_ERROR_OUT_OF_MEMORY;
892   }
893   for (uint32_t i = 0; i < count; i++) {
894     SubPrefix *sub = mSubPrefixes.AppendElement(fallible);
895     sub->addChunk = addchunks[i];
896     sub->prefix.FromUint32(prefixes[i]);
897     sub->subChunk = subchunks[i];
898   }
899 
900   return NS_OK;
901 }
902 
903 // Split up PrefixArray back into the constituents
904 nsresult
WriteAddPrefixes(nsIOutputStream * aOut)905 HashStore::WriteAddPrefixes(nsIOutputStream* aOut)
906 {
907   nsTArray<uint32_t> chunks;
908   uint32_t count = mAddPrefixes.Length();
909   if (!chunks.SetCapacity(count, fallible)) {
910     return NS_ERROR_OUT_OF_MEMORY;
911   }
912 
913   for (uint32_t i = 0; i < count; i++) {
914     chunks.AppendElement(mAddPrefixes[i].Chunk());
915   }
916 
917   nsresult rv = ByteSliceWrite(aOut, chunks);
918   NS_ENSURE_SUCCESS(rv, rv);
919 
920   return NS_OK;
921 }
922 
923 nsresult
WriteSubPrefixes(nsIOutputStream * aOut)924 HashStore::WriteSubPrefixes(nsIOutputStream* aOut)
925 {
926   nsTArray<uint32_t> addchunks;
927   nsTArray<uint32_t> subchunks;
928   nsTArray<uint32_t> prefixes;
929   uint32_t count = mSubPrefixes.Length();
930   addchunks.SetCapacity(count);
931   subchunks.SetCapacity(count);
932   prefixes.SetCapacity(count);
933 
934   for (uint32_t i = 0; i < count; i++) {
935     addchunks.AppendElement(mSubPrefixes[i].AddChunk());
936     prefixes.AppendElement(mSubPrefixes[i].PrefixHash().ToUint32());
937     subchunks.AppendElement(mSubPrefixes[i].Chunk());
938   }
939 
940   nsresult rv = ByteSliceWrite(aOut, addchunks);
941   NS_ENSURE_SUCCESS(rv, rv);
942 
943   rv = ByteSliceWrite(aOut, subchunks);
944   NS_ENSURE_SUCCESS(rv, rv);
945 
946   rv = ByteSliceWrite(aOut, prefixes);
947   NS_ENSURE_SUCCESS(rv, rv);
948 
949   return NS_OK;
950 }
951 
952 nsresult
WriteFile()953 HashStore::WriteFile()
954 {
955   NS_ASSERTION(mInUpdate, "Must be in update to write database.");
956   if (nsUrlClassifierDBService::ShutdownHasStarted()) {
957     return NS_ERROR_ABORT;
958   }
959 
960   nsCOMPtr<nsIFile> storeFile;
961   nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
962   NS_ENSURE_SUCCESS(rv, rv);
963   rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(".sbstore"));
964   NS_ENSURE_SUCCESS(rv, rv);
965 
966   nsCOMPtr<nsIOutputStream> out;
967   rv = NS_NewCheckSummedOutputStream(getter_AddRefs(out), storeFile);
968   NS_ENSURE_SUCCESS(rv, rv);
969 
970   uint32_t written;
971   rv = out->Write(reinterpret_cast<char*>(&mHeader), sizeof(mHeader), &written);
972   NS_ENSURE_SUCCESS(rv, rv);
973 
974   // Write chunk numbers.
975   rv = mAddChunks.Write(out);
976   NS_ENSURE_SUCCESS(rv, rv);
977 
978   rv = mSubChunks.Write(out);
979   NS_ENSURE_SUCCESS(rv, rv);
980 
981   // Write hashes.
982   rv = WriteAddPrefixes(out);
983   NS_ENSURE_SUCCESS(rv, rv);
984 
985   rv = WriteSubPrefixes(out);
986   NS_ENSURE_SUCCESS(rv, rv);
987 
988   rv = WriteTArray(out, mAddCompletes);
989   NS_ENSURE_SUCCESS(rv, rv);
990 
991   rv = WriteTArray(out, mSubCompletes);
992   NS_ENSURE_SUCCESS(rv, rv);
993 
994   nsCOMPtr<nsISafeOutputStream> safeOut = do_QueryInterface(out, &rv);
995   NS_ENSURE_SUCCESS(rv, rv);
996 
997   rv = safeOut->Finish();
998   NS_ENSURE_SUCCESS(rv, rv);
999 
1000   return NS_OK;
1001 }
1002 
1003 template <class T>
1004 static void
Erase(FallibleTArray<T> * array,T * iterStart,T * iterEnd)1005 Erase(FallibleTArray<T>* array, T* iterStart, T* iterEnd)
1006 {
1007   uint32_t start = iterStart - array->Elements();
1008   uint32_t count = iterEnd - iterStart;
1009 
1010   if (count > 0) {
1011     array->RemoveElementsAt(start, count);
1012   }
1013 }
1014 
1015 // Find items matching between |subs| and |adds|, and remove them,
1016 // recording the item from |adds| in |adds_removed|.  To minimize
1017 // copies, the inputs are processing in parallel, so |subs| and |adds|
1018 // should be compatibly ordered (either by SBAddPrefixLess or
1019 // SBAddPrefixHashLess).
1020 //
1021 // |predAS| provides add < sub, |predSA| provides sub < add, for the
1022 // tightest compare appropriate (see calls in SBProcessSubs).
1023 template<class TSub, class TAdd>
1024 static void
KnockoutSubs(FallibleTArray<TSub> * aSubs,FallibleTArray<TAdd> * aAdds)1025 KnockoutSubs(FallibleTArray<TSub>* aSubs, FallibleTArray<TAdd>* aAdds)
1026 {
1027   // Keep a pair of output iterators for writing kept items.  Due to
1028   // deletions, these may lag the main iterators.  Using erase() on
1029   // individual items would result in O(N^2) copies.  Using a list
1030   // would work around that, at double or triple the memory cost.
1031   TAdd* addOut = aAdds->Elements();
1032   TAdd* addIter = aAdds->Elements();
1033 
1034   TSub* subOut = aSubs->Elements();
1035   TSub* subIter = aSubs->Elements();
1036 
1037   TAdd* addEnd = addIter + aAdds->Length();
1038   TSub* subEnd = subIter + aSubs->Length();
1039 
1040   while (addIter != addEnd && subIter != subEnd) {
1041     // additer compare, so it compares on add chunk
1042     int32_t cmp = addIter->Compare(*subIter);
1043     if (cmp > 0) {
1044       // If |*sub_iter| < |*add_iter|, retain the sub.
1045       *subOut = *subIter;
1046       ++subOut;
1047       ++subIter;
1048     } else if (cmp < 0) {
1049       // If |*add_iter| < |*sub_iter|, retain the add.
1050       *addOut = *addIter;
1051       ++addOut;
1052       ++addIter;
1053     } else {
1054       // Drop equal items
1055       ++addIter;
1056       ++subIter;
1057     }
1058   }
1059 
1060   Erase(aAdds, addOut, addIter);
1061   Erase(aSubs, subOut, subIter);
1062 }
1063 
1064 // Remove items in |removes| from |fullHashes|.  |fullHashes| and
1065 // |removes| should be ordered by SBAddPrefix component.
1066 template <class T>
1067 static void
RemoveMatchingPrefixes(const SubPrefixArray & aSubs,FallibleTArray<T> * aFullHashes)1068 RemoveMatchingPrefixes(const SubPrefixArray& aSubs, FallibleTArray<T>* aFullHashes)
1069 {
1070   // Where to store kept items.
1071   T* out = aFullHashes->Elements();
1072   T* hashIter = out;
1073   T* hashEnd = aFullHashes->Elements() + aFullHashes->Length();
1074 
1075   SubPrefix const * removeIter = aSubs.Elements();
1076   SubPrefix const * removeEnd = aSubs.Elements() + aSubs.Length();
1077 
1078   while (hashIter != hashEnd && removeIter != removeEnd) {
1079     int32_t cmp = removeIter->CompareAlt(*hashIter);
1080     if (cmp > 0) {
1081       // Keep items less than |*removeIter|.
1082       *out = *hashIter;
1083       ++out;
1084       ++hashIter;
1085     } else if (cmp < 0) {
1086       // No hit for |*removeIter|, bump it forward.
1087       ++removeIter;
1088     } else {
1089       // Drop equal items, there may be multiple hits.
1090       do {
1091         ++hashIter;
1092       } while (hashIter != hashEnd &&
1093                !(removeIter->CompareAlt(*hashIter) < 0));
1094       ++removeIter;
1095     }
1096   }
1097   Erase(aFullHashes, out, hashIter);
1098 }
1099 
1100 static void
RemoveDeadSubPrefixes(SubPrefixArray & aSubs,ChunkSet & aAddChunks)1101 RemoveDeadSubPrefixes(SubPrefixArray& aSubs, ChunkSet& aAddChunks)
1102 {
1103   SubPrefix * subIter = aSubs.Elements();
1104   SubPrefix * subEnd = aSubs.Elements() + aSubs.Length();
1105 
1106   for (SubPrefix * iter = subIter; iter != subEnd; iter++) {
1107     bool hasChunk = aAddChunks.Has(iter->AddChunk());
1108     // Keep the subprefix if the chunk it refers to is one
1109     // we haven't seen it yet.
1110     if (!hasChunk) {
1111       *subIter = *iter;
1112       subIter++;
1113     }
1114   }
1115 
1116   LOG(("Removed %u dead SubPrefix entries.", subEnd - subIter));
1117   aSubs.TruncateLength(subIter - aSubs.Elements());
1118 }
1119 
1120 #ifdef DEBUG
1121 template <class T>
EnsureSorted(FallibleTArray<T> * aArray)1122 static void EnsureSorted(FallibleTArray<T>* aArray)
1123 {
1124   T* start = aArray->Elements();
1125   T* end = aArray->Elements() + aArray->Length();
1126   T* iter = start;
1127   T* previous = start;
1128 
1129   while (iter != end) {
1130     previous = iter;
1131     ++iter;
1132     if (iter != end) {
1133       MOZ_ASSERT(iter->Compare(*previous) >= 0);
1134     }
1135   }
1136 
1137   return;
1138 }
1139 #endif
1140 
1141 nsresult
ProcessSubs()1142 HashStore::ProcessSubs()
1143 {
1144 #ifdef DEBUG
1145   EnsureSorted(&mAddPrefixes);
1146   EnsureSorted(&mSubPrefixes);
1147   EnsureSorted(&mAddCompletes);
1148   EnsureSorted(&mSubCompletes);
1149   LOG(("All databases seem to have a consistent sort order."));
1150 #endif
1151 
1152   RemoveMatchingPrefixes(mSubPrefixes, &mAddCompletes);
1153   RemoveMatchingPrefixes(mSubPrefixes, &mSubCompletes);
1154 
1155   // Remove any remaining subbed prefixes from both addprefixes
1156   // and addcompletes.
1157   KnockoutSubs(&mSubPrefixes, &mAddPrefixes);
1158   KnockoutSubs(&mSubCompletes, &mAddCompletes);
1159 
1160   // Remove any remaining subprefixes referring to addchunks that
1161   // we have (and hence have been processed above).
1162   RemoveDeadSubPrefixes(mSubPrefixes, mAddChunks);
1163 
1164 #ifdef DEBUG
1165   EnsureSorted(&mAddPrefixes);
1166   EnsureSorted(&mSubPrefixes);
1167   EnsureSorted(&mAddCompletes);
1168   EnsureSorted(&mSubCompletes);
1169   LOG(("All databases seem to have a consistent sort order."));
1170 #endif
1171 
1172   return NS_OK;
1173 }
1174 
1175 nsresult
AugmentAdds(const nsTArray<uint32_t> & aPrefixes)1176 HashStore::AugmentAdds(const nsTArray<uint32_t>& aPrefixes)
1177 {
1178   uint32_t cnt = aPrefixes.Length();
1179   if (cnt != mAddPrefixes.Length()) {
1180     LOG(("Amount of prefixes in cache not consistent with store (%d vs %d)",
1181          aPrefixes.Length(), mAddPrefixes.Length()));
1182     return NS_ERROR_FAILURE;
1183   }
1184   for (uint32_t i = 0; i < cnt; i++) {
1185     mAddPrefixes[i].prefix.FromUint32(aPrefixes[i]);
1186   }
1187   return NS_OK;
1188 }
1189 
1190 ChunkSet&
AddChunks()1191 HashStore::AddChunks()
1192 {
1193   ReadChunkNumbers();
1194 
1195   return mAddChunks;
1196 }
1197 
1198 ChunkSet&
SubChunks()1199 HashStore::SubChunks()
1200 {
1201   ReadChunkNumbers();
1202 
1203   return mSubChunks;
1204 }
1205 
1206 AddCompleteArray&
AddCompletes()1207 HashStore::AddCompletes()
1208 {
1209   ReadCompletions();
1210 
1211   return mAddCompletes;
1212 }
1213 
1214 SubCompleteArray&
SubCompletes()1215 HashStore::SubCompletes()
1216 {
1217   ReadCompletions();
1218 
1219   return mSubCompletes;
1220 }
1221 
1222 bool
AlreadyReadChunkNumbers()1223 HashStore::AlreadyReadChunkNumbers()
1224 {
1225   // If there are chunks but chunk set not yet contains any data
1226   // Then we haven't read chunk numbers.
1227   if ((mHeader.numAddChunks != 0 && mAddChunks.Length() == 0) ||
1228       (mHeader.numSubChunks != 0 && mSubChunks.Length() == 0)) {
1229     return false;
1230   }
1231   return true;
1232 }
1233 
1234 bool
AlreadyReadCompletions()1235 HashStore::AlreadyReadCompletions()
1236 {
1237   // If there are completions but completion set not yet contains any data
1238   // Then we haven't read completions.
1239   if ((mHeader.numAddCompletes != 0 && mAddCompletes.Length() == 0) ||
1240       (mHeader.numSubCompletes != 0 && mSubCompletes.Length() == 0)) {
1241     return false;
1242   }
1243   return true;
1244 }
1245 
1246 } // namespace safebrowsing
1247 } // namespace mozilla
1248