michael@0: //* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: // Originally based on Chrome sources: michael@0: // Copyright (c) 2010 The Chromium Authors. All rights reserved. michael@0: // michael@0: // Redistribution and use in source and binary forms, with or without michael@0: // modification, are permitted provided that the following conditions are michael@0: // met: michael@0: // michael@0: // * Redistributions of source code must retain the above copyright michael@0: // notice, this list of conditions and the following disclaimer. michael@0: // * Redistributions in binary form must reproduce the above michael@0: // copyright notice, this list of conditions and the following disclaimer michael@0: // in the documentation and/or other materials provided with the michael@0: // distribution. michael@0: // * Neither the name of Google Inc. nor the names of its michael@0: // contributors may be used to endorse or promote products derived from michael@0: // this software without specific prior written permission. michael@0: // michael@0: // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: michael@0: #include "HashStore.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsICryptoHash.h" michael@0: #include "nsISeekableStream.h" michael@0: #include "nsIStreamConverterService.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsCheckSummedOutputStream.h" michael@0: #include "prlog.h" michael@0: #include "zlib.h" michael@0: michael@0: // Main store for SafeBrowsing protocol data. We store michael@0: // known add/sub chunks, prefixes and completions in memory michael@0: // during an update, and serialize to disk. michael@0: // We do not store the add prefixes, those are retrieved by michael@0: // decompressing the PrefixSet cache whenever we need to apply michael@0: // an update. michael@0: // michael@0: // byte slicing: Many of the 4-byte values stored here are strongly michael@0: // correlated in the upper bytes, and uncorrelated in the lower michael@0: // bytes. Because zlib/DEFLATE requires match lengths of at least michael@0: // 3 to achieve good compression, and we don't get those if only michael@0: // the upper 16-bits are correlated, it is worthwhile to slice 32-bit michael@0: // values into 4 1-byte slices and compress the slices individually. michael@0: // The slices corresponding to MSBs will compress very well, and the michael@0: // slice corresponding to LSB almost nothing. Because of this, we michael@0: // only apply DEFLATE to the 3 most significant bytes, and store the michael@0: // LSB uncompressed. michael@0: // michael@0: // byte sliced (numValues) data format: michael@0: // uint32_t compressed-size michael@0: // compressed-size bytes zlib DEFLATE data michael@0: // 0...numValues byte MSB of 4-byte numValues data michael@0: // uint32_t compressed-size michael@0: // compressed-size bytes zlib DEFLATE data michael@0: // 0...numValues byte 2nd byte of 4-byte numValues data michael@0: // uint32_t compressed-size michael@0: // compressed-size bytes zlib DEFLATE data michael@0: // 0...numValues byte 3rd byte of 4-byte numValues data michael@0: // 0...numValues byte LSB of 4-byte numValues data michael@0: // michael@0: // Store data format: michael@0: // uint32_t magic michael@0: // uint32_t version michael@0: // uint32_t numAddChunks michael@0: // uint32_t numSubChunks michael@0: // uint32_t numAddPrefixes michael@0: // uint32_t numSubPrefixes michael@0: // uint32_t numAddCompletes michael@0: // uint32_t numSubCompletes michael@0: // 0...numAddChunks uint32_t addChunk michael@0: // 0...numSubChunks uint32_t subChunk michael@0: // byte sliced (numAddPrefixes) uint32_t add chunk of AddPrefixes michael@0: // byte sliced (numSubPrefixes) uint32_t add chunk of SubPrefixes michael@0: // byte sliced (numSubPrefixes) uint32_t sub chunk of SubPrefixes michael@0: // byte sliced (numSubPrefixes) uint32_t SubPrefixes michael@0: // 0...numAddCompletes 32-byte Completions + uint32_t addChunk michael@0: // 0...numSubCompletes 32-byte Completions + uint32_t addChunk michael@0: // + uint32_t subChunk michael@0: // 16-byte MD5 of all preceding data michael@0: michael@0: // Name of the SafeBrowsing store michael@0: #define STORE_SUFFIX ".sbstore" michael@0: michael@0: // NSPR_LOG_MODULES=UrlClassifierDbService:5 michael@0: extern PRLogModuleInfo *gUrlClassifierDbServiceLog; michael@0: #if defined(PR_LOGGING) michael@0: #define LOG(args) PR_LOG(gUrlClassifierDbServiceLog, PR_LOG_DEBUG, args) michael@0: #define LOG_ENABLED() PR_LOG_TEST(gUrlClassifierDbServiceLog, 4) michael@0: #else michael@0: #define LOG(args) michael@0: #define LOG_ENABLED() (false) michael@0: #endif michael@0: michael@0: // Either the return was successful or we call the Reset function (unless we michael@0: // hit an OOM). Used while reading in the store. michael@0: #define SUCCESS_OR_RESET(res) \ michael@0: do { \ michael@0: nsresult __rv = res; /* Don't evaluate |res| more than once */ \ michael@0: if (__rv == NS_ERROR_OUT_OF_MEMORY) { \ michael@0: NS_WARNING("SafeBrowsing OOM."); \ michael@0: return __rv; \ michael@0: } \ michael@0: if (NS_FAILED(__rv)) { \ michael@0: NS_WARNING("SafeBrowsing store corrupted or out of date."); \ michael@0: Reset(); \ michael@0: return __rv; \ michael@0: } \ michael@0: } while(0) michael@0: michael@0: namespace mozilla { michael@0: namespace safebrowsing { michael@0: michael@0: const uint32_t STORE_MAGIC = 0x1231af3b; michael@0: const uint32_t CURRENT_VERSION = 3; michael@0: michael@0: void michael@0: TableUpdate::NewAddPrefix(uint32_t aAddChunk, const Prefix& aHash) michael@0: { michael@0: AddPrefix *add = mAddPrefixes.AppendElement(); michael@0: add->addChunk = aAddChunk; michael@0: add->prefix = aHash; michael@0: } michael@0: michael@0: void michael@0: TableUpdate::NewSubPrefix(uint32_t aAddChunk, const Prefix& aHash, uint32_t aSubChunk) michael@0: { michael@0: SubPrefix *sub = mSubPrefixes.AppendElement(); michael@0: sub->addChunk = aAddChunk; michael@0: sub->prefix = aHash; michael@0: sub->subChunk = aSubChunk; michael@0: } michael@0: michael@0: void michael@0: TableUpdate::NewAddComplete(uint32_t aAddChunk, const Completion& aHash) michael@0: { michael@0: AddComplete *add = mAddCompletes.AppendElement(); michael@0: add->addChunk = aAddChunk; michael@0: add->complete = aHash; michael@0: } michael@0: michael@0: void michael@0: TableUpdate::NewSubComplete(uint32_t aAddChunk, const Completion& aHash, uint32_t aSubChunk) michael@0: { michael@0: SubComplete *sub = mSubCompletes.AppendElement(); michael@0: sub->addChunk = aAddChunk; michael@0: sub->complete = aHash; michael@0: sub->subChunk = aSubChunk; michael@0: } michael@0: michael@0: michael@0: HashStore::HashStore(const nsACString& aTableName, nsIFile* aStoreDir) michael@0: : mTableName(aTableName) michael@0: , mStoreDirectory(aStoreDir) michael@0: , mInUpdate(false) michael@0: { michael@0: } michael@0: michael@0: HashStore::~HashStore() michael@0: { michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::Reset() michael@0: { michael@0: LOG(("HashStore resetting")); michael@0: michael@0: nsCOMPtr storeFile; michael@0: nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(STORE_SUFFIX)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = storeFile->Remove(false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::CheckChecksum(nsIFile* aStoreFile, michael@0: uint32_t aFileSize) michael@0: { michael@0: // Check for file corruption by michael@0: // comparing the stored checksum to actual checksum of data michael@0: nsAutoCString hash; michael@0: nsAutoCString compareHash; michael@0: char *data; michael@0: uint32_t read; michael@0: michael@0: nsresult rv = CalculateChecksum(hash, aFileSize, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: compareHash.GetMutableData(&data, hash.Length()); michael@0: michael@0: if (hash.Length() > aFileSize) { michael@0: NS_WARNING("SafeBrowing file not long enough to store its hash"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: nsCOMPtr seekIn = do_QueryInterface(mInputStream); michael@0: rv = seekIn->Seek(nsISeekableStream::NS_SEEK_SET, aFileSize - hash.Length()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mInputStream->Read(data, hash.Length(), &read); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ASSERTION(read == hash.Length(), "Could not read hash bytes"); michael@0: michael@0: if (!hash.Equals(compareHash)) { michael@0: NS_WARNING("Safebrowing file failed checksum."); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::Open() michael@0: { michael@0: nsCOMPtr storeFile; michael@0: nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(".sbstore")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr origStream; michael@0: rv = NS_NewLocalFileInputStream(getter_AddRefs(origStream), storeFile, michael@0: PR_RDONLY | nsIFile::OS_READAHEAD); michael@0: michael@0: if (rv == NS_ERROR_FILE_NOT_FOUND) { michael@0: UpdateHeader(); michael@0: return NS_OK; michael@0: } else { michael@0: SUCCESS_OR_RESET(rv); michael@0: } michael@0: michael@0: int64_t fileSize; michael@0: rv = storeFile->GetFileSize(&fileSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (fileSize < 0 || fileSize > UINT32_MAX) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: uint32_t fileSize32 = static_cast(fileSize); michael@0: michael@0: rv = NS_NewBufferedInputStream(getter_AddRefs(mInputStream), origStream, michael@0: fileSize32); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = CheckChecksum(storeFile, fileSize32); michael@0: SUCCESS_OR_RESET(rv); michael@0: michael@0: rv = ReadHeader(); michael@0: SUCCESS_OR_RESET(rv); michael@0: michael@0: rv = SanityCheck(); michael@0: SUCCESS_OR_RESET(rv); michael@0: michael@0: rv = ReadChunkNumbers(); michael@0: SUCCESS_OR_RESET(rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::ReadHeader() michael@0: { michael@0: if (!mInputStream) { michael@0: UpdateHeader(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr seekable = do_QueryInterface(mInputStream); michael@0: nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: void *buffer = &mHeader; michael@0: rv = NS_ReadInputStreamToBuffer(mInputStream, michael@0: &buffer, michael@0: sizeof(Header)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::SanityCheck() michael@0: { michael@0: if (mHeader.magic != STORE_MAGIC || mHeader.version != CURRENT_VERSION) { michael@0: NS_WARNING("Unexpected header data in the store."); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::CalculateChecksum(nsAutoCString& aChecksum, michael@0: uint32_t aFileSize, michael@0: bool aChecksumPresent) michael@0: { michael@0: aChecksum.Truncate(); michael@0: michael@0: // Reset mInputStream to start michael@0: nsCOMPtr seekable = do_QueryInterface(mInputStream); michael@0: nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); michael@0: michael@0: nsCOMPtr hash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Size of MD5 hash in bytes michael@0: const uint32_t CHECKSUM_SIZE = 16; michael@0: michael@0: // MD5 is not a secure hash function, but since this is a filesystem integrity michael@0: // check, this usage is ok. michael@0: rv = hash->Init(nsICryptoHash::MD5); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!aChecksumPresent) { michael@0: // Hash entire file michael@0: rv = hash->UpdateFromStream(mInputStream, UINT32_MAX); michael@0: } else { michael@0: // Hash everything but last checksum bytes michael@0: if (aFileSize < CHECKSUM_SIZE) { michael@0: NS_WARNING("SafeBrowsing file isn't long enough to store its checksum"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: rv = hash->UpdateFromStream(mInputStream, aFileSize - CHECKSUM_SIZE); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = hash->Finish(false, aChecksum); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: HashStore::UpdateHeader() michael@0: { michael@0: mHeader.magic = STORE_MAGIC; michael@0: mHeader.version = CURRENT_VERSION; michael@0: michael@0: mHeader.numAddChunks = mAddChunks.Length(); michael@0: mHeader.numSubChunks = mSubChunks.Length(); michael@0: mHeader.numAddPrefixes = mAddPrefixes.Length(); michael@0: mHeader.numSubPrefixes = mSubPrefixes.Length(); michael@0: mHeader.numAddCompletes = mAddCompletes.Length(); michael@0: mHeader.numSubCompletes = mSubCompletes.Length(); michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::ReadChunkNumbers() michael@0: { michael@0: NS_ENSURE_STATE(mInputStream); michael@0: michael@0: nsCOMPtr seekable = do_QueryInterface(mInputStream); michael@0: nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, michael@0: sizeof(Header)); michael@0: michael@0: rv = mAddChunks.Read(mInputStream, mHeader.numAddChunks); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ASSERTION(mAddChunks.Length() == mHeader.numAddChunks, "Read the right amount of add chunks."); michael@0: michael@0: rv = mSubChunks.Read(mInputStream, mHeader.numSubChunks); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ASSERTION(mSubChunks.Length() == mHeader.numSubChunks, "Read the right amount of sub chunks."); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::ReadHashes() michael@0: { michael@0: if (!mInputStream) { michael@0: // BeginUpdate has been called but Open hasn't initialized mInputStream, michael@0: // because the existing HashStore is empty. michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr seekable = do_QueryInterface(mInputStream); michael@0: michael@0: uint32_t offset = sizeof(Header); michael@0: offset += (mHeader.numAddChunks + mHeader.numSubChunks) * sizeof(uint32_t); michael@0: nsresult rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset); michael@0: michael@0: rv = ReadAddPrefixes(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ReadSubPrefixes(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ReadTArray(mInputStream, &mAddCompletes, mHeader.numAddCompletes); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ReadTArray(mInputStream, &mSubCompletes, mHeader.numSubCompletes); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::BeginUpdate() michael@0: { michael@0: // Read the rest of the store in memory. michael@0: nsresult rv = ReadHashes(); michael@0: SUCCESS_OR_RESET(rv); michael@0: michael@0: // Close input stream, won't be needed any more and michael@0: // we will rewrite ourselves. michael@0: if (mInputStream) { michael@0: rv = mInputStream->Close(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mInUpdate = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: template michael@0: static nsresult michael@0: Merge(ChunkSet* aStoreChunks, michael@0: FallibleTArray* aStorePrefixes, michael@0: ChunkSet& aUpdateChunks, michael@0: FallibleTArray& aUpdatePrefixes, michael@0: bool aAllowMerging = false) michael@0: { michael@0: EntrySort(aUpdatePrefixes); michael@0: michael@0: T* updateIter = aUpdatePrefixes.Elements(); michael@0: T* updateEnd = aUpdatePrefixes.Elements() + aUpdatePrefixes.Length(); michael@0: michael@0: T* storeIter = aStorePrefixes->Elements(); michael@0: T* storeEnd = aStorePrefixes->Elements() + aStorePrefixes->Length(); michael@0: michael@0: // use a separate array so we can keep the iterators valid michael@0: // if the nsTArray grows michael@0: nsTArray adds; michael@0: michael@0: for (; updateIter != updateEnd; updateIter++) { michael@0: // skip this chunk if we already have it, unless we're michael@0: // merging completions, in which case we'll always already michael@0: // have the chunk from the original prefix michael@0: if (aStoreChunks->Has(updateIter->Chunk())) michael@0: if (!aAllowMerging) michael@0: continue; michael@0: // XXX: binary search for insertion point might be faster in common michael@0: // case? michael@0: while (storeIter < storeEnd && (storeIter->Compare(*updateIter) < 0)) { michael@0: // skip forward to matching element (or not...) michael@0: storeIter++; michael@0: } michael@0: // no match, add michael@0: if (storeIter == storeEnd michael@0: || storeIter->Compare(*updateIter) != 0) { michael@0: if (!adds.AppendElement(*updateIter)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: } michael@0: michael@0: // Chunks can be empty, but we should still report we have them michael@0: // to make the chunkranges continuous. michael@0: aStoreChunks->Merge(aUpdateChunks); michael@0: michael@0: aStorePrefixes->AppendElements(adds); michael@0: EntrySort(*aStorePrefixes); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::ApplyUpdate(TableUpdate &update) michael@0: { michael@0: nsresult rv = mAddExpirations.Merge(update.AddExpirations()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mSubExpirations.Merge(update.SubExpirations()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = Expire(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = Merge(&mAddChunks, &mAddPrefixes, michael@0: update.AddChunks(), update.AddPrefixes()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = Merge(&mAddChunks, &mAddCompletes, michael@0: update.AddChunks(), update.AddCompletes(), true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = Merge(&mSubChunks, &mSubPrefixes, michael@0: update.SubChunks(), update.SubPrefixes()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = Merge(&mSubChunks, &mSubCompletes, michael@0: update.SubChunks(), update.SubCompletes(), true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::Rebuild() michael@0: { michael@0: NS_ASSERTION(mInUpdate, "Must be in update to rebuild."); michael@0: michael@0: nsresult rv = ProcessSubs(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: UpdateHeader(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: HashStore::ClearCompletes() michael@0: { michael@0: NS_ASSERTION(mInUpdate, "Must be in update to clear completes."); michael@0: michael@0: mAddCompletes.Clear(); michael@0: mSubCompletes.Clear(); michael@0: michael@0: UpdateHeader(); michael@0: } michael@0: michael@0: template michael@0: static void michael@0: ExpireEntries(FallibleTArray* aEntries, ChunkSet& aExpirations) michael@0: { michael@0: T* addIter = aEntries->Elements(); michael@0: T* end = aEntries->Elements() + aEntries->Length(); michael@0: michael@0: for (T *iter = addIter; iter != end; iter++) { michael@0: if (!aExpirations.Has(iter->Chunk())) { michael@0: *addIter = *iter; michael@0: addIter++; michael@0: } michael@0: } michael@0: michael@0: aEntries->SetLength(addIter - aEntries->Elements()); michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::Expire() michael@0: { michael@0: ExpireEntries(&mAddPrefixes, mAddExpirations); michael@0: ExpireEntries(&mAddCompletes, mAddExpirations); michael@0: ExpireEntries(&mSubPrefixes, mSubExpirations); michael@0: ExpireEntries(&mSubCompletes, mSubExpirations); michael@0: michael@0: mAddChunks.Remove(mAddExpirations); michael@0: mSubChunks.Remove(mSubExpirations); michael@0: michael@0: mAddExpirations.Clear(); michael@0: mSubExpirations.Clear(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: template michael@0: nsresult DeflateWriteTArray(nsIOutputStream* aStream, nsTArray& aIn) michael@0: { michael@0: uLongf insize = aIn.Length() * sizeof(T); michael@0: uLongf outsize = compressBound(insize); michael@0: FallibleTArray outBuff; michael@0: if (!outBuff.SetLength(outsize)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: int zerr = compress(reinterpret_cast(outBuff.Elements()), michael@0: &outsize, michael@0: reinterpret_cast(aIn.Elements()), michael@0: insize); michael@0: if (zerr != Z_OK) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: LOG(("DeflateWriteTArray: %d in %d out", insize, outsize)); michael@0: michael@0: outBuff.TruncateLength(outsize); michael@0: michael@0: // Length of compressed data stream michael@0: uint32_t dataLen = outBuff.Length(); michael@0: uint32_t written; michael@0: nsresult rv = aStream->Write(reinterpret_cast(&dataLen), sizeof(dataLen), &written); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(written == sizeof(dataLen), "Error writing deflate length"); michael@0: michael@0: // Store to stream michael@0: rv = WriteTArray(aStream, outBuff); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: template michael@0: nsresult InflateReadTArray(nsIInputStream* aStream, FallibleTArray* aOut, michael@0: uint32_t aExpectedSize) michael@0: { michael@0: michael@0: uint32_t inLen; michael@0: uint32_t read; michael@0: nsresult rv = aStream->Read(reinterpret_cast(&inLen), sizeof(inLen), &read); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(read == sizeof(inLen), "Error reading inflate length"); michael@0: michael@0: FallibleTArray inBuff; michael@0: if (!inBuff.SetLength(inLen)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: rv = ReadTArray(aStream, &inBuff, inLen); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uLongf insize = inLen; michael@0: uLongf outsize = aExpectedSize * sizeof(T); michael@0: if (!aOut->SetLength(aExpectedSize)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: int zerr = uncompress(reinterpret_cast(aOut->Elements()), michael@0: &outsize, michael@0: reinterpret_cast(inBuff.Elements()), michael@0: insize); michael@0: if (zerr != Z_OK) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: LOG(("InflateReadTArray: %d in %d out", insize, outsize)); michael@0: michael@0: NS_ASSERTION(outsize == aExpectedSize * sizeof(T), "Decompression size mismatch"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: ByteSliceWrite(nsIOutputStream* aOut, nsTArray& aData) michael@0: { michael@0: nsTArray slice1; michael@0: nsTArray slice2; michael@0: nsTArray slice3; michael@0: nsTArray slice4; michael@0: uint32_t count = aData.Length(); michael@0: michael@0: slice1.SetCapacity(count); michael@0: slice2.SetCapacity(count); michael@0: slice3.SetCapacity(count); michael@0: slice4.SetCapacity(count); michael@0: michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: slice1.AppendElement( aData[i] >> 24); michael@0: slice2.AppendElement((aData[i] >> 16) & 0xFF); michael@0: slice3.AppendElement((aData[i] >> 8) & 0xFF); michael@0: slice4.AppendElement( aData[i] & 0xFF); michael@0: } michael@0: michael@0: nsresult rv = DeflateWriteTArray(aOut, slice1); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = DeflateWriteTArray(aOut, slice2); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = DeflateWriteTArray(aOut, slice3); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // The LSB slice is generally uncompressible, don't bother michael@0: // compressing it. michael@0: rv = WriteTArray(aOut, slice4); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: ByteSliceRead(nsIInputStream* aInStream, FallibleTArray* aData, uint32_t count) michael@0: { michael@0: FallibleTArray slice1; michael@0: FallibleTArray slice2; michael@0: FallibleTArray slice3; michael@0: FallibleTArray slice4; michael@0: michael@0: nsresult rv = InflateReadTArray(aInStream, &slice1, count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = InflateReadTArray(aInStream, &slice2, count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = InflateReadTArray(aInStream, &slice3, count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ReadTArray(aInStream, &slice4, count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!aData->SetCapacity(count)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: aData->AppendElement((slice1[i] << 24) | (slice2[i] << 16) michael@0: | (slice3[i] << 8) | (slice4[i])); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::ReadAddPrefixes() michael@0: { michael@0: FallibleTArray chunks; michael@0: uint32_t count = mHeader.numAddPrefixes; michael@0: michael@0: nsresult rv = ByteSliceRead(mInputStream, &chunks, count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!mAddPrefixes.SetCapacity(count)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: AddPrefix *add = mAddPrefixes.AppendElement(); michael@0: add->prefix.FromUint32(0); michael@0: add->addChunk = chunks[i]; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::ReadSubPrefixes() michael@0: { michael@0: FallibleTArray addchunks; michael@0: FallibleTArray subchunks; michael@0: FallibleTArray prefixes; michael@0: uint32_t count = mHeader.numSubPrefixes; michael@0: michael@0: nsresult rv = ByteSliceRead(mInputStream, &addchunks, count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ByteSliceRead(mInputStream, &subchunks, count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ByteSliceRead(mInputStream, &prefixes, count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!mSubPrefixes.SetCapacity(count)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: SubPrefix *sub = mSubPrefixes.AppendElement(); michael@0: sub->addChunk = addchunks[i]; michael@0: sub->prefix.FromUint32(prefixes[i]); michael@0: sub->subChunk = subchunks[i]; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Split up PrefixArray back into the constituents michael@0: nsresult michael@0: HashStore::WriteAddPrefixes(nsIOutputStream* aOut) michael@0: { michael@0: nsTArray chunks; michael@0: uint32_t count = mAddPrefixes.Length(); michael@0: chunks.SetCapacity(count); michael@0: michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: chunks.AppendElement(mAddPrefixes[i].Chunk()); michael@0: } michael@0: michael@0: nsresult rv = ByteSliceWrite(aOut, chunks); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::WriteSubPrefixes(nsIOutputStream* aOut) michael@0: { michael@0: nsTArray addchunks; michael@0: nsTArray subchunks; michael@0: nsTArray prefixes; michael@0: uint32_t count = mSubPrefixes.Length(); michael@0: addchunks.SetCapacity(count); michael@0: subchunks.SetCapacity(count); michael@0: prefixes.SetCapacity(count); michael@0: michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: addchunks.AppendElement(mSubPrefixes[i].AddChunk()); michael@0: prefixes.AppendElement(mSubPrefixes[i].PrefixHash().ToUint32()); michael@0: subchunks.AppendElement(mSubPrefixes[i].Chunk()); michael@0: } michael@0: michael@0: nsresult rv = ByteSliceWrite(aOut, addchunks); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ByteSliceWrite(aOut, subchunks); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ByteSliceWrite(aOut, prefixes); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::WriteFile() michael@0: { michael@0: NS_ASSERTION(mInUpdate, "Must be in update to write database."); michael@0: michael@0: nsCOMPtr storeFile; michael@0: nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(".sbstore")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr out; michael@0: rv = NS_NewCheckSummedOutputStream(getter_AddRefs(out), storeFile, michael@0: PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t written; michael@0: rv = out->Write(reinterpret_cast(&mHeader), sizeof(mHeader), &written); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Write chunk numbers. michael@0: rv = mAddChunks.Write(out); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mSubChunks.Write(out); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Write hashes. michael@0: rv = WriteAddPrefixes(out); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = WriteSubPrefixes(out); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = WriteTArray(out, mAddCompletes); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = WriteTArray(out, mSubCompletes); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr safeOut = do_QueryInterface(out, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = safeOut->Finish(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: template michael@0: static void michael@0: Erase(FallibleTArray* array, T* iterStart, T* iterEnd) michael@0: { michael@0: uint32_t start = iterStart - array->Elements(); michael@0: uint32_t count = iterEnd - iterStart; michael@0: michael@0: if (count > 0) { michael@0: array->RemoveElementsAt(start, count); michael@0: } michael@0: } michael@0: michael@0: // Find items matching between |subs| and |adds|, and remove them, michael@0: // recording the item from |adds| in |adds_removed|. To minimize michael@0: // copies, the inputs are processing in parallel, so |subs| and |adds| michael@0: // should be compatibly ordered (either by SBAddPrefixLess or michael@0: // SBAddPrefixHashLess). michael@0: // michael@0: // |predAS| provides add < sub, |predSA| provides sub < add, for the michael@0: // tightest compare appropriate (see calls in SBProcessSubs). michael@0: template michael@0: static void michael@0: KnockoutSubs(FallibleTArray* aSubs, FallibleTArray* aAdds) michael@0: { michael@0: // Keep a pair of output iterators for writing kept items. Due to michael@0: // deletions, these may lag the main iterators. Using erase() on michael@0: // individual items would result in O(N^2) copies. Using a list michael@0: // would work around that, at double or triple the memory cost. michael@0: TAdd* addOut = aAdds->Elements(); michael@0: TAdd* addIter = aAdds->Elements(); michael@0: michael@0: TSub* subOut = aSubs->Elements(); michael@0: TSub* subIter = aSubs->Elements(); michael@0: michael@0: TAdd* addEnd = addIter + aAdds->Length(); michael@0: TSub* subEnd = subIter + aSubs->Length(); michael@0: michael@0: while (addIter != addEnd && subIter != subEnd) { michael@0: // additer compare, so it compares on add chunk michael@0: int32_t cmp = addIter->Compare(*subIter); michael@0: if (cmp > 0) { michael@0: // If |*sub_iter| < |*add_iter|, retain the sub. michael@0: *subOut = *subIter; michael@0: ++subOut; michael@0: ++subIter; michael@0: } else if (cmp < 0) { michael@0: // If |*add_iter| < |*sub_iter|, retain the add. michael@0: *addOut = *addIter; michael@0: ++addOut; michael@0: ++addIter; michael@0: } else { michael@0: // Drop equal items michael@0: ++addIter; michael@0: ++subIter; michael@0: } michael@0: } michael@0: michael@0: Erase(aAdds, addOut, addIter); michael@0: Erase(aSubs, subOut, subIter); michael@0: } michael@0: michael@0: // Remove items in |removes| from |fullHashes|. |fullHashes| and michael@0: // |removes| should be ordered by SBAddPrefix component. michael@0: template michael@0: static void michael@0: RemoveMatchingPrefixes(const SubPrefixArray& aSubs, FallibleTArray* aFullHashes) michael@0: { michael@0: // Where to store kept items. michael@0: T* out = aFullHashes->Elements(); michael@0: T* hashIter = out; michael@0: T* hashEnd = aFullHashes->Elements() + aFullHashes->Length(); michael@0: michael@0: SubPrefix const * removeIter = aSubs.Elements(); michael@0: SubPrefix const * removeEnd = aSubs.Elements() + aSubs.Length(); michael@0: michael@0: while (hashIter != hashEnd && removeIter != removeEnd) { michael@0: int32_t cmp = removeIter->CompareAlt(*hashIter); michael@0: if (cmp > 0) { michael@0: // Keep items less than |*removeIter|. michael@0: *out = *hashIter; michael@0: ++out; michael@0: ++hashIter; michael@0: } else if (cmp < 0) { michael@0: // No hit for |*removeIter|, bump it forward. michael@0: ++removeIter; michael@0: } else { michael@0: // Drop equal items, there may be multiple hits. michael@0: do { michael@0: ++hashIter; michael@0: } while (hashIter != hashEnd && michael@0: !(removeIter->CompareAlt(*hashIter) < 0)); michael@0: ++removeIter; michael@0: } michael@0: } michael@0: Erase(aFullHashes, out, hashIter); michael@0: } michael@0: michael@0: static void michael@0: RemoveDeadSubPrefixes(SubPrefixArray& aSubs, ChunkSet& aAddChunks) michael@0: { michael@0: SubPrefix * subIter = aSubs.Elements(); michael@0: SubPrefix * subEnd = aSubs.Elements() + aSubs.Length(); michael@0: michael@0: for (SubPrefix * iter = subIter; iter != subEnd; iter++) { michael@0: bool hasChunk = aAddChunks.Has(iter->AddChunk()); michael@0: // Keep the subprefix if the chunk it refers to is one michael@0: // we haven't seen it yet. michael@0: if (!hasChunk) { michael@0: *subIter = *iter; michael@0: subIter++; michael@0: } michael@0: } michael@0: michael@0: LOG(("Removed %u dead SubPrefix entries.", subEnd - subIter)); michael@0: aSubs.SetLength(subIter - aSubs.Elements()); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: template michael@0: static void EnsureSorted(FallibleTArray* aArray) michael@0: { michael@0: T* start = aArray->Elements(); michael@0: T* end = aArray->Elements() + aArray->Length(); michael@0: T* iter = start; michael@0: T* previous = start; michael@0: michael@0: while (iter != end) { michael@0: previous = iter; michael@0: ++iter; michael@0: if (iter != end) { michael@0: MOZ_ASSERT(iter->Compare(*previous) >= 0); michael@0: } michael@0: } michael@0: michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: nsresult michael@0: HashStore::ProcessSubs() michael@0: { michael@0: #ifdef DEBUG michael@0: EnsureSorted(&mAddPrefixes); michael@0: EnsureSorted(&mSubPrefixes); michael@0: EnsureSorted(&mAddCompletes); michael@0: EnsureSorted(&mSubCompletes); michael@0: LOG(("All databases seem to have a consistent sort order.")); michael@0: #endif michael@0: michael@0: RemoveMatchingPrefixes(mSubPrefixes, &mAddCompletes); michael@0: RemoveMatchingPrefixes(mSubPrefixes, &mSubCompletes); michael@0: michael@0: // Remove any remaining subbed prefixes from both addprefixes michael@0: // and addcompletes. michael@0: KnockoutSubs(&mSubPrefixes, &mAddPrefixes); michael@0: KnockoutSubs(&mSubCompletes, &mAddCompletes); michael@0: michael@0: // Remove any remaining subprefixes referring to addchunks that michael@0: // we have (and hence have been processed above). michael@0: RemoveDeadSubPrefixes(mSubPrefixes, mAddChunks); michael@0: michael@0: #ifdef DEBUG michael@0: EnsureSorted(&mAddPrefixes); michael@0: EnsureSorted(&mSubPrefixes); michael@0: EnsureSorted(&mAddCompletes); michael@0: EnsureSorted(&mSubCompletes); michael@0: LOG(("All databases seem to have a consistent sort order.")); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HashStore::AugmentAdds(const nsTArray& aPrefixes) michael@0: { michael@0: uint32_t cnt = aPrefixes.Length(); michael@0: if (cnt != mAddPrefixes.Length()) { michael@0: LOG(("Amount of prefixes in cache not consistent with store (%d vs %d)", michael@0: aPrefixes.Length(), mAddPrefixes.Length())); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: for (uint32_t i = 0; i < cnt; i++) { michael@0: mAddPrefixes[i].prefix.FromUint32(aPrefixes[i]); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: } michael@0: }