LCOV - code coverage report
Current view: top level - toolkit/components/url-classifier - ProtocolParser.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 145 551 26.3 %
Date: 2017-07-14 16:53:18 Functions: 19 46 41.3 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "ProtocolParser.h"
       7             : #include "LookupCache.h"
       8             : #include "nsNetCID.h"
       9             : #include "mozilla/Logging.h"
      10             : #include "prnetdb.h"
      11             : #include "prprf.h"
      12             : 
      13             : #include "nsUrlClassifierDBService.h"
      14             : #include "nsUrlClassifierUtils.h"
      15             : #include "nsPrintfCString.h"
      16             : #include "mozilla/Base64.h"
      17             : #include "RiceDeltaDecoder.h"
      18             : #include "mozilla/EndianUtils.h"
      19             : #include "mozilla/IntegerPrintfMacros.h"
      20             : #include "mozilla/SizePrintfMacros.h"
      21             : 
      22             : // MOZ_LOG=UrlClassifierProtocolParser:5
      23             : mozilla::LazyLogModule gUrlClassifierProtocolParserLog("UrlClassifierProtocolParser");
      24             : #define PARSER_LOG(args) MOZ_LOG(gUrlClassifierProtocolParserLog, mozilla::LogLevel::Debug, args)
      25             : 
      26             : namespace mozilla {
      27             : namespace safebrowsing {
      28             : 
      29             : // Updates will fail if fed chunks larger than this
      30             : const uint32_t MAX_CHUNK_SIZE = (1024 * 1024);
      31             : // Updates will fail if the total number of tocuhed chunks is larger than this
      32             : const uint32_t MAX_CHUNK_RANGE = 1000000;
      33             : 
      34             : const uint32_t DOMAIN_SIZE = 4;
      35             : 
      36             : // Parse one stringified range of chunks of the form "n" or "n-m" from a
      37             : // comma-separated list of chunks.  Upon return, 'begin' will point to the
      38             : // next range of chunks in the list of chunks.
      39             : static bool
      40           6 : ParseChunkRange(nsACString::const_iterator& aBegin,
      41             :                 const nsACString::const_iterator& aEnd,
      42             :                 uint32_t* aFirst, uint32_t* aLast)
      43             : {
      44           6 :   nsACString::const_iterator iter = aBegin;
      45           6 :   FindCharInReadable(',', iter, aEnd);
      46             : 
      47          12 :   nsAutoCString element(Substring(aBegin, iter));
      48           6 :   aBegin = iter;
      49           6 :   if (aBegin != aEnd)
      50           0 :     aBegin++;
      51             : 
      52           6 :   uint32_t numRead = PR_sscanf(element.get(), "%u-%u", aFirst, aLast);
      53           6 :   if (numRead == 2) {
      54           0 :     if (*aFirst > *aLast) {
      55           0 :       uint32_t tmp = *aFirst;
      56           0 :       *aFirst = *aLast;
      57           0 :       *aLast = tmp;
      58             :     }
      59           0 :     return true;
      60             :   }
      61             : 
      62           6 :   if (numRead == 1) {
      63           6 :     *aLast = *aFirst;
      64           6 :     return true;
      65             :   }
      66             : 
      67           0 :   return false;
      68             : }
      69             : 
      70             : ///////////////////////////////////////////////////////////////
      71             : // ProtocolParser implementation
      72             : 
      73           1 : ProtocolParser::ProtocolParser()
      74             :   : mUpdateStatus(NS_OK)
      75           1 :   , mUpdateWaitSec(0)
      76             : {
      77           1 : }
      78             : 
      79           2 : ProtocolParser::~ProtocolParser()
      80             : {
      81           1 :   CleanupUpdates();
      82           1 : }
      83             : 
      84             : nsresult
      85           1 : ProtocolParser::Init(nsICryptoHash* aHasher)
      86             : {
      87           1 :   mCryptoHash = aHasher;
      88           1 :   return NS_OK;
      89             : }
      90             : 
      91             : void
      92           1 : ProtocolParser::CleanupUpdates()
      93             : {
      94           1 :   for (uint32_t i = 0; i < mTableUpdates.Length(); i++) {
      95           0 :     delete mTableUpdates[i];
      96             :   }
      97           1 :   mTableUpdates.Clear();
      98           1 : }
      99             : 
     100             : TableUpdate *
     101           6 : ProtocolParser::GetTableUpdate(const nsACString& aTable)
     102             : {
     103          21 :   for (uint32_t i = 0; i < mTableUpdates.Length(); i++) {
     104          15 :     if (aTable.Equals(mTableUpdates[i]->TableName())) {
     105           0 :       return mTableUpdates[i];
     106             :     }
     107             :   }
     108             : 
     109             :   // We free automatically on destruction, ownership of these
     110             :   // updates can be transferred to DBServiceWorker, which passes
     111             :   // them back to Classifier when doing the updates, and that
     112             :   // will free them.
     113           6 :   TableUpdate *update = CreateTableUpdate(aTable);
     114           6 :   mTableUpdates.AppendElement(update);
     115           6 :   return update;
     116             : }
     117             : 
     118             : ///////////////////////////////////////////////////////////////////////
     119             : // ProtocolParserV2
     120             : 
     121           1 : ProtocolParserV2::ProtocolParserV2()
     122             :   : mState(PROTOCOL_STATE_CONTROL)
     123             :   , mResetRequested(false)
     124           1 :   , mTableUpdate(nullptr)
     125             : {
     126           1 : }
     127             : 
     128           2 : ProtocolParserV2::~ProtocolParserV2()
     129             : {
     130           3 : }
     131             : 
     132             : void
     133           6 : ProtocolParserV2::SetCurrentTable(const nsACString& aTable)
     134             : {
     135           6 :   auto update = GetTableUpdate(aTable);
     136           6 :   mTableUpdate = TableUpdate::Cast<TableUpdateV2>(update);
     137           6 : }
     138             : 
     139             : nsresult
     140           1 : ProtocolParserV2::AppendStream(const nsACString& aData)
     141             : {
     142           1 :   if (NS_FAILED(mUpdateStatus))
     143           0 :     return mUpdateStatus;
     144             : 
     145             :   nsresult rv;
     146           1 :   mPending.Append(aData);
     147             : #ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
     148           1 :   mRawUpdate.Append(aData);
     149             : #endif
     150             : 
     151           1 :   bool done = false;
     152          31 :   while (!done) {
     153          15 :     if (nsUrlClassifierDBService::ShutdownHasStarted()) {
     154           0 :       return NS_ERROR_ABORT;
     155             :     }
     156             : 
     157          15 :     if (mState == PROTOCOL_STATE_CONTROL) {
     158           8 :       rv = ProcessControl(&done);
     159           7 :     } else if (mState == PROTOCOL_STATE_CHUNK) {
     160           7 :       rv = ProcessChunk(&done);
     161             :     } else {
     162           0 :       NS_ERROR("Unexpected protocol state");
     163           0 :       rv = NS_ERROR_FAILURE;
     164             :     }
     165          15 :     if (NS_FAILED(rv)) {
     166           0 :       mUpdateStatus = rv;
     167           0 :       return rv;
     168             :     }
     169             :   }
     170           1 :   return NS_OK;
     171             : }
     172             : 
     173             : void
     174           1 : ProtocolParserV2::End()
     175             : {
     176             :   // Inbound data has already been processed in every AppendStream() call.
     177           1 : }
     178             : 
     179             : nsresult
     180           8 : ProtocolParserV2::ProcessControl(bool* aDone)
     181             : {
     182             :   nsresult rv;
     183             : 
     184          16 :   nsAutoCString line;
     185           8 :   *aDone = true;
     186          54 :   while (NextLine(line)) {
     187          30 :     PARSER_LOG(("Processing %s\n", line.get()));
     188             : 
     189          30 :     if (StringBeginsWith(line, NS_LITERAL_CSTRING("i:"))) {
     190             :       // Set the table name from the table header line.
     191           6 :       SetCurrentTable(Substring(line, 2));
     192          24 :     } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("n:"))) {
     193           6 :       if (PR_sscanf(line.get(), "n:%d", &mUpdateWaitSec) != 1) {
     194           0 :         PARSER_LOG(("Error parsing n: '%s' (%d)", line.get(), mUpdateWaitSec));
     195           0 :         return NS_ERROR_FAILURE;
     196             :       }
     197          18 :     } else if (line.EqualsLiteral("r:pleasereset")) {
     198           0 :       mResetRequested = true;
     199          18 :     } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("u:"))) {
     200           0 :       rv = ProcessForward(line);
     201           0 :       NS_ENSURE_SUCCESS(rv, rv);
     202          58 :     } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("a:")) ||
     203          40 :                StringBeginsWith(line, NS_LITERAL_CSTRING("s:"))) {
     204           7 :       rv = ProcessChunkControl(line);
     205           7 :       NS_ENSURE_SUCCESS(rv, rv);
     206           7 :       *aDone = false;
     207           7 :       return NS_OK;
     208          32 :     } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("ad:")) ||
     209          21 :                StringBeginsWith(line, NS_LITERAL_CSTRING("sd:"))) {
     210           6 :       rv = ProcessExpirations(line);
     211           6 :       NS_ENSURE_SUCCESS(rv, rv);
     212             :     }
     213             :   }
     214             : 
     215           1 :   *aDone = true;
     216           1 :   return NS_OK;
     217             : }
     218             : 
     219             : nsresult
     220           6 : ProtocolParserV2::ProcessExpirations(const nsCString& aLine)
     221             : {
     222           6 :   if (!mTableUpdate) {
     223           0 :     NS_WARNING("Got an expiration without a table.");
     224           0 :     return NS_ERROR_FAILURE;
     225             :   }
     226          12 :   const nsACString& list = Substring(aLine, 3);
     227           6 :   nsACString::const_iterator begin, end;
     228           6 :   list.BeginReading(begin);
     229           6 :   list.EndReading(end);
     230          18 :   while (begin != end) {
     231             :     uint32_t first, last;
     232           6 :     if (ParseChunkRange(begin, end, &first, &last)) {
     233           6 :       if (last < first) return NS_ERROR_FAILURE;
     234           6 :       if (last - first > MAX_CHUNK_RANGE) return NS_ERROR_FAILURE;
     235          12 :       for (uint32_t num = first; num <= last; num++) {
     236           6 :         if (aLine[0] == 'a') {
     237           6 :           nsresult rv = mTableUpdate->NewAddExpiration(num);
     238           6 :           if (NS_FAILED(rv)) {
     239           0 :             return rv;
     240             :           }
     241             :         } else {
     242           0 :           nsresult rv = mTableUpdate->NewSubExpiration(num);
     243           0 :           if (NS_FAILED(rv)) {
     244           0 :             return rv;
     245             :           }
     246             :         }
     247             :       }
     248             :     } else {
     249           0 :       return NS_ERROR_FAILURE;
     250             :     }
     251             :   }
     252           6 :   return NS_OK;
     253             : }
     254             : 
     255             : nsresult
     256           7 : ProtocolParserV2::ProcessChunkControl(const nsCString& aLine)
     257             : {
     258           7 :   if (!mTableUpdate) {
     259           0 :     NS_WARNING("Got a chunk before getting a table.");
     260           0 :     return NS_ERROR_FAILURE;
     261             :   }
     262             : 
     263           7 :   mState = PROTOCOL_STATE_CHUNK;
     264             :   char command;
     265             : 
     266           7 :   mChunkState.Clear();
     267             : 
     268           7 :   if (PR_sscanf(aLine.get(),
     269             :                 "%c:%d:%d:%d",
     270             :                 &command,
     271             :                 &mChunkState.num, &mChunkState.hashSize, &mChunkState.length)
     272             :       != 4)
     273             :   {
     274           0 :     NS_WARNING(("PR_sscanf failed"));
     275           0 :     return NS_ERROR_FAILURE;
     276             :   }
     277             : 
     278           7 :   if (mChunkState.length > MAX_CHUNK_SIZE) {
     279           0 :     NS_WARNING("Invalid length specified in update.");
     280           0 :     return NS_ERROR_FAILURE;
     281             :   }
     282             : 
     283           7 :   if (!(mChunkState.hashSize == PREFIX_SIZE || mChunkState.hashSize == COMPLETE_SIZE)) {
     284           0 :     NS_WARNING("Invalid hash size specified in update.");
     285           0 :     return NS_ERROR_FAILURE;
     286             :   }
     287             : 
     288          21 :   if (StringEndsWith(mTableUpdate->TableName(),
     289          42 :                      NS_LITERAL_CSTRING("-shavar")) ||
     290           7 :       StringEndsWith(mTableUpdate->TableName(),
     291          21 :                      NS_LITERAL_CSTRING("-simple"))) {
     292             :     // Accommodate test tables ending in -simple for now.
     293           7 :     mChunkState.type = (command == 'a') ? CHUNK_ADD : CHUNK_SUB;
     294           0 :   } else if (StringEndsWith(mTableUpdate->TableName(),
     295           0 :     NS_LITERAL_CSTRING("-digest256"))) {
     296           0 :     mChunkState.type = (command == 'a') ? CHUNK_ADD_DIGEST : CHUNK_SUB_DIGEST;
     297             :   }
     298             :   nsresult rv;
     299           7 :   switch (mChunkState.type) {
     300             :     case CHUNK_ADD:
     301           7 :       rv = mTableUpdate->NewAddChunk(mChunkState.num);
     302           7 :       if (NS_FAILED(rv)) {
     303           0 :         return rv;
     304             :       }
     305           7 :       break;
     306             :     case CHUNK_SUB:
     307           0 :       rv = mTableUpdate->NewSubChunk(mChunkState.num);
     308           0 :       if (NS_FAILED(rv)) {
     309           0 :         return rv;
     310             :       }
     311           0 :       break;
     312             :     case CHUNK_ADD_DIGEST:
     313           0 :       rv = mTableUpdate->NewAddChunk(mChunkState.num);
     314           0 :       if (NS_FAILED(rv)) {
     315           0 :         return rv;
     316             :       }
     317           0 :       break;
     318             :     case CHUNK_SUB_DIGEST:
     319           0 :       rv = mTableUpdate->NewSubChunk(mChunkState.num);
     320           0 :       if (NS_FAILED(rv)) {
     321           0 :         return rv;
     322             :       }
     323           0 :       break;
     324             :   }
     325             : 
     326           7 :   return NS_OK;
     327             : }
     328             : 
     329             : nsresult
     330           0 : ProtocolParserV2::ProcessForward(const nsCString& aLine)
     331             : {
     332           0 :   const nsACString& forward = Substring(aLine, 2);
     333           0 :   return AddForward(forward);
     334             : }
     335             : 
     336             : nsresult
     337           0 : ProtocolParserV2::AddForward(const nsACString& aUrl)
     338             : {
     339           0 :   if (!mTableUpdate) {
     340           0 :     NS_WARNING("Forward without a table name.");
     341           0 :     return NS_ERROR_FAILURE;
     342             :   }
     343             : 
     344           0 :   ForwardedUpdate *forward = mForwards.AppendElement();
     345           0 :   forward->table = mTableUpdate->TableName();
     346           0 :   forward->url.Assign(aUrl);
     347             : 
     348           0 :   return NS_OK;
     349             : }
     350             : 
     351             : nsresult
     352           7 : ProtocolParserV2::ProcessChunk(bool* aDone)
     353             : {
     354           7 :   if (!mTableUpdate) {
     355           0 :     NS_WARNING("Processing chunk without an active table.");
     356           0 :     return NS_ERROR_FAILURE;
     357             :   }
     358             : 
     359           7 :   NS_ASSERTION(mChunkState.num != 0, "Must have a chunk number.");
     360             : 
     361           7 :   if (mPending.Length() < mChunkState.length) {
     362           0 :     *aDone = true;
     363           0 :     return NS_OK;
     364             :   }
     365             : 
     366             :   // Pull the chunk out of the pending stream data.
     367          14 :   nsAutoCString chunk;
     368           7 :   chunk.Assign(Substring(mPending, 0, mChunkState.length));
     369           7 :   mPending.Cut(0, mChunkState.length);
     370             : 
     371           7 :   *aDone = false;
     372           7 :   mState = PROTOCOL_STATE_CONTROL;
     373             : 
     374          14 :   if (StringEndsWith(mTableUpdate->TableName(),
     375          14 :                      NS_LITERAL_CSTRING("-shavar"))) {
     376           0 :     return ProcessShaChunk(chunk);
     377             :   }
     378          14 :   if (StringEndsWith(mTableUpdate->TableName(),
     379          14 :              NS_LITERAL_CSTRING("-digest256"))) {
     380           0 :     return ProcessDigestChunk(chunk);
     381             :   }
     382           7 :   return ProcessPlaintextChunk(chunk);
     383             : }
     384             : 
     385             : /**
     386             :  * Process a plaintext chunk (currently only used in unit tests).
     387             :  */
     388             : nsresult
     389           7 : ProtocolParserV2::ProcessPlaintextChunk(const nsACString& aChunk)
     390             : {
     391           7 :   if (!mTableUpdate) {
     392           0 :     NS_WARNING("Chunk received with no table.");
     393           0 :     return NS_ERROR_FAILURE;
     394             :   }
     395             : 
     396           7 :   PARSER_LOG(("Handling a %d-byte simple chunk", aChunk.Length()));
     397             : 
     398          14 :   nsTArray<nsCString> lines;
     399           7 :   ParseString(PromiseFlatCString(aChunk), '\n', lines);
     400             : 
     401             :   // non-hashed tables need to be hashed
     402          14 :   for (uint32_t i = 0; i < lines.Length(); i++) {
     403           7 :     nsCString& line = lines[i];
     404             : 
     405           7 :     if (mChunkState.type == CHUNK_ADD) {
     406           7 :       if (mChunkState.hashSize == COMPLETE_SIZE) {
     407             :         Completion hash;
     408           7 :         hash.FromPlaintext(line, mCryptoHash);
     409           7 :         nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash);
     410           7 :         if (NS_FAILED(rv)) {
     411           0 :           return rv;
     412             :         }
     413             :       } else {
     414           0 :         NS_ASSERTION(mChunkState.hashSize == 4, "Only 32- or 4-byte hashes can be used for add chunks.");
     415             :         Prefix hash;
     416           0 :         hash.FromPlaintext(line, mCryptoHash);
     417           0 :         nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, hash);
     418           0 :         if (NS_FAILED(rv)) {
     419           0 :           return rv;
     420             :         }
     421             :       }
     422             :     } else {
     423           0 :       nsCString::const_iterator begin, iter, end;
     424           0 :       line.BeginReading(begin);
     425           0 :       line.EndReading(end);
     426           0 :       iter = begin;
     427             :       uint32_t addChunk;
     428           0 :       if (!FindCharInReadable(':', iter, end) ||
     429           0 :           PR_sscanf(lines[i].get(), "%d:", &addChunk) != 1) {
     430           0 :         NS_WARNING("Received sub chunk without associated add chunk.");
     431           0 :         return NS_ERROR_FAILURE;
     432             :       }
     433           0 :       iter++;
     434             : 
     435           0 :       if (mChunkState.hashSize == COMPLETE_SIZE) {
     436             :         Completion hash;
     437           0 :         hash.FromPlaintext(Substring(iter, end), mCryptoHash);
     438           0 :         nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
     439           0 :         if (NS_FAILED(rv)) {
     440           0 :           return rv;
     441             :         }
     442             :       } else {
     443           0 :         NS_ASSERTION(mChunkState.hashSize == 4, "Only 32- or 4-byte hashes can be used for add chunks.");
     444             :         Prefix hash;
     445           0 :         hash.FromPlaintext(Substring(iter, end), mCryptoHash);
     446           0 :         nsresult rv = mTableUpdate->NewSubPrefix(addChunk, hash, mChunkState.num);
     447           0 :         if (NS_FAILED(rv)) {
     448           0 :           return rv;
     449             :         }
     450             :       }
     451             :     }
     452             :   }
     453             : 
     454           7 :   return NS_OK;
     455             : }
     456             : 
     457             : nsresult
     458           0 : ProtocolParserV2::ProcessShaChunk(const nsACString& aChunk)
     459             : {
     460           0 :   uint32_t start = 0;
     461           0 :   while (start < aChunk.Length()) {
     462             :     // First four bytes are the domain key.
     463             :     Prefix domain;
     464           0 :     domain.Assign(Substring(aChunk, start, DOMAIN_SIZE));
     465           0 :     start += DOMAIN_SIZE;
     466             : 
     467             :     // Then a count of entries.
     468           0 :     uint8_t numEntries = static_cast<uint8_t>(aChunk[start]);
     469           0 :     start++;
     470             : 
     471           0 :     PARSER_LOG(("Handling a %d-byte shavar chunk containing %u entries"
     472             :                 " for domain %X", aChunk.Length(), numEntries,
     473             :                 domain.ToUint32()));
     474             : 
     475             :     nsresult rv;
     476           0 :     if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == PREFIX_SIZE) {
     477           0 :       rv = ProcessHostAdd(domain, numEntries, aChunk, &start);
     478           0 :     } else if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == COMPLETE_SIZE) {
     479           0 :       rv = ProcessHostAddComplete(numEntries, aChunk, &start);
     480           0 :     } else if (mChunkState.type == CHUNK_SUB && mChunkState.hashSize == PREFIX_SIZE) {
     481           0 :       rv = ProcessHostSub(domain, numEntries, aChunk, &start);
     482           0 :     } else if (mChunkState.type == CHUNK_SUB && mChunkState.hashSize == COMPLETE_SIZE) {
     483           0 :       rv = ProcessHostSubComplete(numEntries, aChunk, &start);
     484             :     } else {
     485           0 :       NS_WARNING("Unexpected chunk type/hash size!");
     486           0 :       PARSER_LOG(("Got an unexpected chunk type/hash size: %s:%d",
     487             :            mChunkState.type == CHUNK_ADD ? "add" : "sub",
     488             :            mChunkState.hashSize));
     489           0 :       return NS_ERROR_FAILURE;
     490             :     }
     491           0 :     NS_ENSURE_SUCCESS(rv, rv);
     492             :   }
     493             : 
     494           0 :   return NS_OK;
     495             : }
     496             : 
     497             : nsresult
     498           0 : ProtocolParserV2::ProcessDigestChunk(const nsACString& aChunk)
     499             : {
     500           0 :   PARSER_LOG(("Handling a %d-byte digest256 chunk", aChunk.Length()));
     501             : 
     502           0 :   if (mChunkState.type == CHUNK_ADD_DIGEST) {
     503           0 :     return ProcessDigestAdd(aChunk);
     504             :   }
     505           0 :   if (mChunkState.type == CHUNK_SUB_DIGEST) {
     506           0 :     return ProcessDigestSub(aChunk);
     507             :   }
     508           0 :   return NS_ERROR_UNEXPECTED;
     509             : }
     510             : 
     511             : nsresult
     512           0 : ProtocolParserV2::ProcessDigestAdd(const nsACString& aChunk)
     513             : {
     514             :   // The ABNF format for add chunks is (HASH)+, where HASH is 32 bytes.
     515           0 :   MOZ_ASSERT(aChunk.Length() % 32 == 0,
     516             :              "Chunk length in bytes must be divisible by 4");
     517           0 :   uint32_t start = 0;
     518           0 :   while (start < aChunk.Length()) {
     519             :     Completion hash;
     520           0 :     hash.Assign(Substring(aChunk, start, COMPLETE_SIZE));
     521           0 :     start += COMPLETE_SIZE;
     522           0 :     nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash);
     523           0 :     if (NS_FAILED(rv)) {
     524           0 :       return rv;
     525             :     }
     526             :   }
     527           0 :   return NS_OK;
     528             : }
     529             : 
     530             : nsresult
     531           0 : ProtocolParserV2::ProcessDigestSub(const nsACString& aChunk)
     532             : {
     533             :   // The ABNF format for sub chunks is (ADDCHUNKNUM HASH)+, where ADDCHUNKNUM
     534             :   // is a 4 byte chunk number, and HASH is 32 bytes.
     535           0 :   MOZ_ASSERT(aChunk.Length() % 36 == 0,
     536             :              "Chunk length in bytes must be divisible by 36");
     537           0 :   uint32_t start = 0;
     538           0 :   while (start < aChunk.Length()) {
     539             :     // Read ADDCHUNKNUM
     540           0 :     const nsACString& addChunkStr = Substring(aChunk, start, 4);
     541           0 :     start += 4;
     542             : 
     543             :     uint32_t addChunk;
     544           0 :     memcpy(&addChunk, addChunkStr.BeginReading(), 4);
     545           0 :     addChunk = PR_ntohl(addChunk);
     546             : 
     547             :     // Read the hash
     548             :     Completion hash;
     549           0 :     hash.Assign(Substring(aChunk, start, COMPLETE_SIZE));
     550           0 :     start += COMPLETE_SIZE;
     551             : 
     552           0 :     nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
     553           0 :     if (NS_FAILED(rv)) {
     554           0 :       return rv;
     555             :     }
     556             :   }
     557           0 :   return NS_OK;
     558             : }
     559             : 
     560             : nsresult
     561           0 : ProtocolParserV2::ProcessHostAdd(const Prefix& aDomain, uint8_t aNumEntries,
     562             :                                const nsACString& aChunk, uint32_t* aStart)
     563             : {
     564           0 :   NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE,
     565             :                "ProcessHostAdd should only be called for prefix hashes.");
     566             : 
     567           0 :   if (aNumEntries == 0) {
     568           0 :     nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, aDomain);
     569           0 :     if (NS_FAILED(rv)) {
     570           0 :       return rv;
     571             :     }
     572           0 :     return NS_OK;
     573             :   }
     574             : 
     575           0 :   if (*aStart + (PREFIX_SIZE * aNumEntries) > aChunk.Length()) {
     576           0 :     NS_WARNING("Chunk is not long enough to contain the expected entries.");
     577           0 :     return NS_ERROR_FAILURE;
     578             :   }
     579             : 
     580           0 :   for (uint8_t i = 0; i < aNumEntries; i++) {
     581             :     Prefix hash;
     582           0 :     hash.Assign(Substring(aChunk, *aStart, PREFIX_SIZE));
     583           0 :     PARSER_LOG(("Add prefix %X", hash.ToUint32()));
     584           0 :     nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, hash);
     585           0 :     if (NS_FAILED(rv)) {
     586           0 :       return rv;
     587             :     }
     588           0 :     *aStart += PREFIX_SIZE;
     589             :   }
     590             : 
     591           0 :   return NS_OK;
     592             : }
     593             : 
     594             : nsresult
     595           0 : ProtocolParserV2::ProcessHostSub(const Prefix& aDomain, uint8_t aNumEntries,
     596             :                                const nsACString& aChunk, uint32_t *aStart)
     597             : {
     598           0 :   NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE,
     599             :                "ProcessHostSub should only be called for prefix hashes.");
     600             : 
     601           0 :   if (aNumEntries == 0) {
     602           0 :     if ((*aStart) + 4 > aChunk.Length()) {
     603           0 :       NS_WARNING("Received a zero-entry sub chunk without an associated add.");
     604           0 :       return NS_ERROR_FAILURE;
     605             :     }
     606             : 
     607           0 :     const nsACString& addChunkStr = Substring(aChunk, *aStart, 4);
     608           0 :     *aStart += 4;
     609             : 
     610             :     uint32_t addChunk;
     611           0 :     memcpy(&addChunk, addChunkStr.BeginReading(), 4);
     612           0 :     addChunk = PR_ntohl(addChunk);
     613             : 
     614           0 :     PARSER_LOG(("Sub prefix (addchunk=%u)", addChunk));
     615           0 :     nsresult rv = mTableUpdate->NewSubPrefix(addChunk, aDomain, mChunkState.num);
     616           0 :     if (NS_FAILED(rv)) {
     617           0 :       return rv;
     618             :     }
     619           0 :     return NS_OK;
     620             :   }
     621             : 
     622           0 :   if (*aStart + ((PREFIX_SIZE + 4) * aNumEntries) > aChunk.Length()) {
     623           0 :     NS_WARNING("Chunk is not long enough to contain the expected entries.");
     624           0 :     return NS_ERROR_FAILURE;
     625             :   }
     626             : 
     627           0 :   for (uint8_t i = 0; i < aNumEntries; i++) {
     628           0 :     const nsACString& addChunkStr = Substring(aChunk, *aStart, 4);
     629           0 :     *aStart += 4;
     630             : 
     631             :     uint32_t addChunk;
     632           0 :     memcpy(&addChunk, addChunkStr.BeginReading(), 4);
     633           0 :     addChunk = PR_ntohl(addChunk);
     634             : 
     635             :     Prefix prefix;
     636           0 :     prefix.Assign(Substring(aChunk, *aStart, PREFIX_SIZE));
     637           0 :     *aStart += PREFIX_SIZE;
     638             : 
     639           0 :     PARSER_LOG(("Sub prefix %X (addchunk=%u)", prefix.ToUint32(), addChunk));
     640           0 :     nsresult rv = mTableUpdate->NewSubPrefix(addChunk, prefix, mChunkState.num);
     641           0 :     if (NS_FAILED(rv)) {
     642           0 :       return rv;
     643             :     }
     644             :   }
     645             : 
     646           0 :   return NS_OK;
     647             : }
     648             : 
     649             : nsresult
     650           0 : ProtocolParserV2::ProcessHostAddComplete(uint8_t aNumEntries,
     651             :                                        const nsACString& aChunk, uint32_t* aStart)
     652             : {
     653           0 :   NS_ASSERTION(mChunkState.hashSize == COMPLETE_SIZE,
     654             :                "ProcessHostAddComplete should only be called for complete hashes.");
     655             : 
     656           0 :   if (aNumEntries == 0) {
     657             :     // this is totally comprehensible.
     658             :     // My sarcasm detector is going off!
     659           0 :     NS_WARNING("Expected > 0 entries for a 32-byte hash add.");
     660           0 :     return NS_OK;
     661             :   }
     662             : 
     663           0 :   if (*aStart + (COMPLETE_SIZE * aNumEntries) > aChunk.Length()) {
     664           0 :     NS_WARNING("Chunk is not long enough to contain the expected entries.");
     665           0 :     return NS_ERROR_FAILURE;
     666             :   }
     667             : 
     668           0 :   for (uint8_t i = 0; i < aNumEntries; i++) {
     669             :     Completion hash;
     670           0 :     hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE));
     671           0 :     nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash);
     672           0 :     if (NS_FAILED(rv)) {
     673           0 :       return rv;
     674             :     }
     675           0 :     *aStart += COMPLETE_SIZE;
     676             :   }
     677             : 
     678           0 :   return NS_OK;
     679             : }
     680             : 
     681             : nsresult
     682           0 : ProtocolParserV2::ProcessHostSubComplete(uint8_t aNumEntries,
     683             :                                        const nsACString& aChunk, uint32_t* aStart)
     684             : {
     685           0 :   NS_ASSERTION(mChunkState.hashSize == COMPLETE_SIZE,
     686             :                "ProcessHostSubComplete should only be called for complete hashes.");
     687             : 
     688           0 :   if (aNumEntries == 0) {
     689             :     // this is totally comprehensible.
     690           0 :     NS_WARNING("Expected > 0 entries for a 32-byte hash sub.");
     691           0 :     return NS_OK;
     692             :   }
     693             : 
     694           0 :   if (*aStart + ((COMPLETE_SIZE + 4) * aNumEntries) > aChunk.Length()) {
     695           0 :     NS_WARNING("Chunk is not long enough to contain the expected entries.");
     696           0 :     return NS_ERROR_FAILURE;
     697             :   }
     698             : 
     699           0 :   for (uint8_t i = 0; i < aNumEntries; i++) {
     700             :     Completion hash;
     701           0 :     hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE));
     702           0 :     *aStart += COMPLETE_SIZE;
     703             : 
     704           0 :     const nsACString& addChunkStr = Substring(aChunk, *aStart, 4);
     705           0 :     *aStart += 4;
     706             : 
     707             :     uint32_t addChunk;
     708           0 :     memcpy(&addChunk, addChunkStr.BeginReading(), 4);
     709           0 :     addChunk = PR_ntohl(addChunk);
     710             : 
     711           0 :     nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
     712           0 :     if (NS_FAILED(rv)) {
     713           0 :       return rv;
     714             :     }
     715             :   }
     716             : 
     717           0 :   return NS_OK;
     718             : }
     719             : 
     720             : bool
     721          31 : ProtocolParserV2::NextLine(nsACString& aLine)
     722             : {
     723          31 :   int32_t newline = mPending.FindChar('\n');
     724          31 :   if (newline == kNotFound) {
     725           1 :     return false;
     726             :   }
     727          30 :   aLine.Assign(Substring(mPending, 0, newline));
     728          30 :   mPending.Cut(0, newline + 1);
     729          30 :   return true;
     730             : }
     731             : 
     732             : TableUpdate*
     733           6 : ProtocolParserV2::CreateTableUpdate(const nsACString& aTableName) const
     734             : {
     735           6 :   return new TableUpdateV2(aTableName);
     736             : }
     737             : 
     738             : ///////////////////////////////////////////////////////////////////////
     739             : // ProtocolParserProtobuf
     740             : 
     741           0 : ProtocolParserProtobuf::ProtocolParserProtobuf()
     742             : {
     743           0 : }
     744             : 
     745           0 : ProtocolParserProtobuf::~ProtocolParserProtobuf()
     746             : {
     747           0 : }
     748             : 
     749             : void
     750           0 : ProtocolParserProtobuf::SetCurrentTable(const nsACString& aTable)
     751             : {
     752             :   // Should never occur.
     753           0 :   MOZ_ASSERT_UNREACHABLE("SetCurrentTable shouldn't be called");
     754             : }
     755             : 
     756             : 
     757             : TableUpdate*
     758           0 : ProtocolParserProtobuf::CreateTableUpdate(const nsACString& aTableName) const
     759             : {
     760           0 :   return new TableUpdateV4(aTableName);
     761             : }
     762             : 
     763             : nsresult
     764           0 : ProtocolParserProtobuf::AppendStream(const nsACString& aData)
     765             : {
     766             :   // Protobuf data cannot be parsed progressively. Just save the incoming data.
     767           0 :   mPending.Append(aData);
     768           0 :   return NS_OK;
     769             : }
     770             : 
     771             : void
     772           0 : ProtocolParserProtobuf::End()
     773             : {
     774             :   // mUpdateStatus will be updated to success as long as not all
     775             :   // the responses are invalid.
     776           0 :   mUpdateStatus = NS_ERROR_FAILURE;
     777             : 
     778           0 :   FetchThreatListUpdatesResponse response;
     779           0 :   if (!response.ParseFromArray(mPending.get(), mPending.Length())) {
     780           0 :     NS_WARNING("ProtocolParserProtobuf failed parsing data.");
     781           0 :     return;
     782             :   }
     783             : 
     784           0 :   auto minWaitDuration = response.minimum_wait_duration();
     785           0 :   mUpdateWaitSec = minWaitDuration.seconds() +
     786           0 :                    minWaitDuration.nanos() / 1000000000;
     787             : 
     788           0 :   for (int i = 0; i < response.list_update_responses_size(); i++) {
     789           0 :     auto r = response.list_update_responses(i);
     790           0 :     nsresult rv = ProcessOneResponse(r);
     791           0 :     if (NS_SUCCEEDED(rv)) {
     792           0 :       mUpdateStatus = rv;
     793             :     } else {
     794           0 :       NS_WARNING("Failed to process one response.");
     795             :     }
     796             :   }
     797             : }
     798             : 
     799             : nsresult
     800           0 : ProtocolParserProtobuf::ProcessOneResponse(const ListUpdateResponse& aResponse)
     801             : {
     802             :   // A response must have a threat type.
     803           0 :   if (!aResponse.has_threat_type()) {
     804           0 :     NS_WARNING("Threat type not initialized. This seems to be an invalid response.");
     805           0 :     return NS_ERROR_FAILURE;
     806             :   }
     807             : 
     808             :   // Convert threat type to list name.
     809             :   nsCOMPtr<nsIUrlClassifierUtils> urlUtil =
     810           0 :     do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
     811           0 :   nsCString possibleListNames;
     812           0 :   nsresult rv = urlUtil->ConvertThreatTypeToListNames(aResponse.threat_type(),
     813           0 :                                                       possibleListNames);
     814           0 :   if (NS_FAILED(rv)) {
     815           0 :     PARSER_LOG(("Threat type to list name conversion error: %d",
     816             :                 aResponse.threat_type()));
     817           0 :     return NS_ERROR_FAILURE;
     818             :   }
     819             : 
     820             :   // Match the table name we received with one of the ones we requested.
     821             :   // We ignore the case where a threat type matches more than one list
     822             :   // per provider and return the first one. See bug 1287059."
     823           0 :   nsCString listName;
     824           0 :   nsTArray<nsCString> possibleListNameArray;
     825           0 :   Classifier::SplitTables(possibleListNames, possibleListNameArray);
     826           0 :   for (auto possibleName : possibleListNameArray) {
     827           0 :     if (mRequestedTables.Contains(possibleName)) {
     828           0 :       listName = possibleName;
     829           0 :       break;
     830             :     }
     831             :   }
     832             : 
     833           0 :   if (listName.IsEmpty()) {
     834           0 :     PARSER_LOG(("We received an update for a list we didn't ask for. Ignoring it."));
     835           0 :     return NS_ERROR_FAILURE;
     836             :   }
     837             : 
     838             :   // Test if this is a full update.
     839           0 :   bool isFullUpdate = false;
     840           0 :   if (aResponse.has_response_type()) {
     841           0 :     isFullUpdate =
     842           0 :       aResponse.response_type() == ListUpdateResponse::FULL_UPDATE;
     843             :   } else {
     844           0 :     NS_WARNING("Response type not initialized.");
     845           0 :     return NS_ERROR_FAILURE;
     846             :   }
     847             : 
     848             :   // Warn if there's no new state.
     849           0 :   if (!aResponse.has_new_client_state()) {
     850           0 :     NS_WARNING("New state not initialized.");
     851           0 :     return NS_ERROR_FAILURE;
     852             :   }
     853             : 
     854           0 :   auto tu = GetTableUpdate(nsCString(listName.get()));
     855           0 :   auto tuV4 = TableUpdate::Cast<TableUpdateV4>(tu);
     856           0 :   NS_ENSURE_TRUE(tuV4, NS_ERROR_FAILURE);
     857             : 
     858           0 :   nsCString state(aResponse.new_client_state().c_str(),
     859           0 :                   aResponse.new_client_state().size());
     860           0 :   tuV4->SetNewClientState(state);
     861             : 
     862           0 :   if (aResponse.has_checksum()) {
     863           0 :     tuV4->NewChecksum(aResponse.checksum().sha256());
     864             :   }
     865             : 
     866           0 :   PARSER_LOG(("==== Update for threat type '%d' ====", aResponse.threat_type()));
     867           0 :   PARSER_LOG(("* listName: %s\n", listName.get()));
     868           0 :   PARSER_LOG(("* newState: %s\n", aResponse.new_client_state().c_str()));
     869           0 :   PARSER_LOG(("* isFullUpdate: %s\n", (isFullUpdate ? "yes" : "no")));
     870           0 :   PARSER_LOG(("* hasChecksum: %s\n", (aResponse.has_checksum() ? "yes" : "no")));
     871             : 
     872           0 :   tuV4->SetFullUpdate(isFullUpdate);
     873             : 
     874           0 :   rv = ProcessAdditionOrRemoval(*tuV4, aResponse.additions(), true /*aIsAddition*/);
     875           0 :   NS_ENSURE_SUCCESS(rv, rv);
     876           0 :   rv = ProcessAdditionOrRemoval(*tuV4, aResponse.removals(), false);
     877           0 :   NS_ENSURE_SUCCESS(rv, rv);
     878             : 
     879           0 :   PARSER_LOG(("\n\n"));
     880             : 
     881           0 :   return NS_OK;
     882             : }
     883             : 
     884             : nsresult
     885           0 : ProtocolParserProtobuf::ProcessAdditionOrRemoval(TableUpdateV4& aTableUpdate,
     886             :                                                  const ThreatEntrySetList& aUpdate,
     887             :                                                  bool aIsAddition)
     888             : {
     889           0 :   nsresult ret = NS_OK;
     890             : 
     891           0 :   for (int i = 0; i < aUpdate.size(); i++) {
     892           0 :     auto update = aUpdate.Get(i);
     893           0 :     if (!update.has_compression_type()) {
     894           0 :       NS_WARNING(nsPrintfCString("%s with no compression type.",
     895           0 :                                   aIsAddition ? "Addition" : "Removal").get());
     896           0 :       continue;
     897             :     }
     898             : 
     899           0 :     switch (update.compression_type()) {
     900             :     case COMPRESSION_TYPE_UNSPECIFIED:
     901           0 :       NS_WARNING("Unspecified compression type.");
     902           0 :       break;
     903             : 
     904             :     case RAW:
     905           0 :       ret = (aIsAddition ? ProcessRawAddition(aTableUpdate, update)
     906             :                          : ProcessRawRemoval(aTableUpdate, update));
     907           0 :       break;
     908             : 
     909             :     case RICE:
     910           0 :       ret = (aIsAddition ? ProcessEncodedAddition(aTableUpdate, update)
     911             :                          : ProcessEncodedRemoval(aTableUpdate, update));
     912           0 :       break;
     913             :     }
     914             :   }
     915             : 
     916           0 :   return ret;
     917             : }
     918             : 
     919             : nsresult
     920           0 : ProtocolParserProtobuf::ProcessRawAddition(TableUpdateV4& aTableUpdate,
     921             :                                            const ThreatEntrySet& aAddition)
     922             : {
     923           0 :   if (!aAddition.has_raw_hashes()) {
     924           0 :     PARSER_LOG(("* No raw addition."));
     925           0 :     return NS_OK;
     926             :   }
     927             : 
     928           0 :   auto rawHashes = aAddition.raw_hashes();
     929           0 :   if (!rawHashes.has_prefix_size()) {
     930           0 :     NS_WARNING("Raw hash has no prefix size");
     931           0 :     return NS_OK;
     932             :   }
     933             : 
     934           0 :   auto prefixes = rawHashes.raw_hashes();
     935           0 :   if (4 == rawHashes.prefix_size()) {
     936             :     // Process fixed length prefixes separately.
     937           0 :     uint32_t* fixedLengthPrefixes = (uint32_t*)prefixes.c_str();
     938           0 :     size_t numOfFixedLengthPrefixes = prefixes.size() / 4;
     939           0 :     PARSER_LOG(("* Raw addition (4 bytes)"));
     940           0 :     PARSER_LOG(("  - # of prefixes: %" PRIuSIZE, numOfFixedLengthPrefixes));
     941           0 :     PARSER_LOG(("  - Memory address: 0x%p", fixedLengthPrefixes));
     942             :   } else {
     943             :     // TODO: Process variable length prefixes including full hashes.
     944             :     // See Bug 1283009.
     945           0 :     PARSER_LOG((" Raw addition (%d bytes)", rawHashes.prefix_size()));
     946             :   }
     947             : 
     948           0 :   if (!rawHashes.mutable_raw_hashes()) {
     949           0 :     PARSER_LOG(("Unable to get mutable raw hashes. Can't perform a string move."));
     950           0 :     return NS_ERROR_FAILURE;
     951             :   }
     952             : 
     953           0 :   aTableUpdate.NewPrefixes(rawHashes.prefix_size(),
     954           0 :                            *rawHashes.mutable_raw_hashes());
     955             : 
     956           0 :   return NS_OK;
     957             : }
     958             : 
     959             : nsresult
     960           0 : ProtocolParserProtobuf::ProcessRawRemoval(TableUpdateV4& aTableUpdate,
     961             :                                           const ThreatEntrySet& aRemoval)
     962             : {
     963           0 :   if (!aRemoval.has_raw_indices()) {
     964           0 :     NS_WARNING("A removal has no indices.");
     965           0 :     return NS_OK;
     966             :   }
     967             : 
     968             :   // indices is an array of int32.
     969           0 :   auto indices = aRemoval.raw_indices().indices();
     970           0 :   PARSER_LOG(("* Raw removal"));
     971           0 :   PARSER_LOG(("  - # of removal: %d", indices.size()));
     972             : 
     973           0 :   aTableUpdate.NewRemovalIndices((const uint32_t*)indices.data(),
     974           0 :                                  indices.size());
     975             : 
     976           0 :   return NS_OK;
     977             : }
     978             : 
     979             : static nsresult
     980           0 : DoRiceDeltaDecode(const RiceDeltaEncoding& aEncoding,
     981             :                   nsTArray<uint32_t>& aDecoded)
     982             : {
     983           0 :   if (!aEncoding.has_first_value()) {
     984           0 :     PARSER_LOG(("The encoding info is incomplete."));
     985           0 :     return NS_ERROR_FAILURE;
     986             :   }
     987           0 :   if (aEncoding.num_entries() > 0 &&
     988           0 :       (!aEncoding.has_rice_parameter() || !aEncoding.has_encoded_data())) {
     989           0 :     PARSER_LOG(("Rice parameter or encoded data is missing."));
     990           0 :     return NS_ERROR_FAILURE;
     991             :   }
     992             : 
     993           0 :   PARSER_LOG(("* Encoding info:"));
     994           0 :   PARSER_LOG(("  - First value: %" PRId64, aEncoding.first_value()));
     995           0 :   PARSER_LOG(("  - Num of entries: %d", aEncoding.num_entries()));
     996           0 :   PARSER_LOG(("  - Rice parameter: %d", aEncoding.rice_parameter()));
     997             : 
     998             :   // Set up the input buffer. Note that the bits should be read
     999             :   // from LSB to MSB so that we in-place reverse the bits before
    1000             :   // feeding to the decoder.
    1001           0 :   auto encoded = const_cast<RiceDeltaEncoding&>(aEncoding).mutable_encoded_data();
    1002           0 :   RiceDeltaDecoder decoder((uint8_t*)encoded->c_str(), encoded->size());
    1003             : 
    1004             :   // Setup the output buffer. The "first value" is included in
    1005             :   // the output buffer.
    1006           0 :   aDecoded.SetLength(aEncoding.num_entries() + 1);
    1007             : 
    1008             :   // Decode!
    1009           0 :   bool rv = decoder.Decode(aEncoding.rice_parameter(),
    1010           0 :                            aEncoding.first_value(), // first value.
    1011           0 :                            aEncoding.num_entries(), // # of entries (first value not included).
    1012           0 :                            &aDecoded[0]);
    1013             : 
    1014           0 :   NS_ENSURE_TRUE(rv, NS_ERROR_FAILURE);
    1015             : 
    1016           0 :   return NS_OK;
    1017             : }
    1018             : 
    1019             : nsresult
    1020           0 : ProtocolParserProtobuf::ProcessEncodedAddition(TableUpdateV4& aTableUpdate,
    1021             :                                                const ThreatEntrySet& aAddition)
    1022             : {
    1023           0 :   if (!aAddition.has_rice_hashes()) {
    1024           0 :     PARSER_LOG(("* No rice encoded addition."));
    1025           0 :     return NS_OK;
    1026             :   }
    1027             : 
    1028           0 :   nsTArray<uint32_t> decoded;
    1029           0 :   nsresult rv = DoRiceDeltaDecode(aAddition.rice_hashes(), decoded);
    1030           0 :   if (NS_FAILED(rv)) {
    1031           0 :     PARSER_LOG(("Failed to parse encoded prefixes."));
    1032           0 :     return rv;
    1033             :   }
    1034             : 
    1035             :   //  Say we have the following raw prefixes
    1036             :   //                              BE            LE
    1037             :   //   00 00 00 01                 1      16777216
    1038             :   //   00 00 02 00               512        131072
    1039             :   //   00 03 00 00            196608           768
    1040             :   //   04 00 00 00          67108864             4
    1041             :   //
    1042             :   // which can be treated as uint32 (big-endian) sorted in increasing order:
    1043             :   //
    1044             :   // [1, 512, 196608, 67108864]
    1045             :   //
    1046             :   // According to https://developers.google.com/safe-browsing/v4/compression,
    1047             :   // the following should be done prior to compression:
    1048             :   //
    1049             :   // 1) re-interpret in little-endian ==> [16777216, 131072, 768, 4]
    1050             :   // 2) sort in increasing order       ==> [4, 768, 131072, 16777216]
    1051             :   //
    1052             :   // In order to get the original byte stream from |decoded|
    1053             :   // ([4, 768, 131072, 16777216] in this case), we have to:
    1054             :   //
    1055             :   // 1) sort in big-endian order      ==> [16777216, 131072, 768, 4]
    1056             :   // 2) copy each uint32 in little-endian to the result string
    1057             :   //
    1058             : 
    1059             :   // The 4-byte prefixes have to be re-sorted in Big-endian increasing order.
    1060             :   struct CompareBigEndian
    1061             :   {
    1062           0 :     bool Equals(const uint32_t& aA, const uint32_t& aB) const
    1063             :     {
    1064           0 :       return aA == aB;
    1065             :     }
    1066             : 
    1067           0 :     bool LessThan(const uint32_t& aA, const uint32_t& aB) const
    1068             :     {
    1069           0 :       return NativeEndian::swapToBigEndian(aA) <
    1070           0 :              NativeEndian::swapToBigEndian(aB);
    1071             :     }
    1072             :   };
    1073           0 :   decoded.Sort(CompareBigEndian());
    1074             : 
    1075             :   // The encoded prefixes are always 4 bytes.
    1076           0 :   std::string prefixes;
    1077           0 :   for (size_t i = 0; i < decoded.Length(); i++) {
    1078             :     // Note that the third argument is the number of elements we want
    1079             :     // to copy (and swap) but not the number of bytes we want to copy.
    1080             :     char p[4];
    1081           0 :     NativeEndian::copyAndSwapToLittleEndian(p, &decoded[i], 1);
    1082           0 :     prefixes.append(p, 4);
    1083             :   }
    1084             : 
    1085           0 :   aTableUpdate.NewPrefixes(4, prefixes);
    1086             : 
    1087           0 :   return NS_OK;
    1088             : }
    1089             : 
    1090             : nsresult
    1091           0 : ProtocolParserProtobuf::ProcessEncodedRemoval(TableUpdateV4& aTableUpdate,
    1092             :                                               const ThreatEntrySet& aRemoval)
    1093             : {
    1094           0 :   if (!aRemoval.has_rice_indices()) {
    1095           0 :     PARSER_LOG(("* No rice encoded removal."));
    1096           0 :     return NS_OK;
    1097             :   }
    1098             : 
    1099           0 :   nsTArray<uint32_t> decoded;
    1100           0 :   nsresult rv = DoRiceDeltaDecode(aRemoval.rice_indices(), decoded);
    1101           0 :   if (NS_FAILED(rv)) {
    1102           0 :     PARSER_LOG(("Failed to decode encoded removal indices."));
    1103           0 :     return rv;
    1104             :   }
    1105             : 
    1106             :   // The encoded prefixes are always 4 bytes.
    1107           0 :   aTableUpdate.NewRemovalIndices(&decoded[0], decoded.Length());
    1108             : 
    1109           0 :   return NS_OK;
    1110             : }
    1111             : 
    1112             : } // namespace safebrowsing
    1113             : } // namespace mozilla

Generated by: LCOV version 1.13