Coverage Report

Created: 2018-09-25 14:53

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