CacheFileUtils.cpp 19.5 KB
Newer Older
1
2
3
4
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

5
#include "CacheIndex.h"
6
7
8
#include "CacheLog.h"
#include "CacheFileUtils.h"
#include "LoadContextInfo.h"
9
#include "mozilla/Tokenizer.h"
10
#include "mozilla/Telemetry.h"
11
#include "nsCOMPtr.h"
12
#include "nsAutoPtr.h"
13
#include "nsString.h"
14
#include <algorithm>
15
#include "mozilla/Unused.h"
16
17
18
19
20

namespace mozilla {
namespace net {
namespace CacheFileUtils {

21
22
23
// This designates the format for the "alt-data" metadata.
// When the format changes we need to update the version.
static uint32_t const kAltDataVersion = 1;
24
const char* kAltDataKey = "alt-data";
25

26
27
static uint32_t const kBaseDomainAccessInfoVersion = 1;

28
namespace {
29
30
31
32

/**
 * A simple recursive descent parser for the mapping key.
 */
33
34
class KeyParser : protected Tokenizer {
 public:
35
  explicit KeyParser(nsACString const& aInput)
36
37
38
39
40
41
42
      : Tokenizer(aInput),
        isAnonymous(false)
        // Initialize the cache key to a zero length by default
        ,
        lastTag(0) {}

 private:
43
  // Results
44
  OriginAttributes originAttribs;
45
46
  bool isAnonymous;
  nsCString idEnhance;
47
  nsDependentCSubstring cacheKey;
48

49
50
  // Keeps the last tag name, used for alphabetical sort checking
  char lastTag;
51

52
53
54
55
56
57
58
  // Classifier for the 'tag' character valid range.
  // Explicitly using unsigned char as 127 is -1 when signed and it would only
  // produce a warning.
  static bool TagChar(const char aChar) {
    unsigned char c = static_cast<unsigned char>(aChar);
    return c >= ' ' && c <= '\x7f';
  }
59

60
  bool ParseTags() {
61
    // Expects to be at the tag name or at the end
62
    if (CheckEOF()) {
63
      return true;
64
    }
65

66
67
    char tag;
    if (!ReadChar(&TagChar, &tag)) {
68
      return false;
69
    }
70

71
72
73
74
    // Check the alphabetical order, hard-fail on disobedience
    if (!(lastTag < tag || tag == ':')) {
      return false;
    }
75
76
77
    lastTag = tag;

    switch (tag) {
78
79
80
81
82
83
84
85
86
87
88
89
      case ':':
        // last possible tag, when present there is the cacheKey following,
        // not terminated with ',' and no need to unescape.
        cacheKey.Rebind(mCursor, mEnd - mCursor);
        return true;
      case 'O': {
        nsAutoCString originSuffix;
        if (!ParseValue(&originSuffix) ||
            !originAttribs.PopulateFromSuffix(originSuffix)) {
          return false;
        }
        break;
90
      }
91
92
93
94
95
96
97
98
99
100
101
102
      case 'p':
        originAttribs.SyncAttributesWithPrivateBrowsing(true);
        break;
      case 'b':
        // Leaving to be able to read and understand oldformatted entries
        originAttribs.mInIsolatedMozBrowser = true;
        break;
      case 'a':
        isAnonymous = true;
        break;
      case 'i': {
        // Leaving to be able to read and understand oldformatted entries
103
104
        uint32_t deprecatedAppId = 0;
        if (!ReadInteger(&deprecatedAppId)) {
105
106
107
          return false;  // not a valid 32-bit integer
        }
        break;
108
      }
109
110
111
112
113
114
115
116
117
118
      case '~':
        if (!ParseValue(&idEnhance)) {
          return false;
        }
        break;
      default:
        if (!ParseValue()) {  // skip any tag values, optional
          return false;
        }
        break;
119
120
121
    }

    // We expect a comma after every tag
122
    if (!CheckChar(',')) {
123
      return false;
124
    }
125

126
    // Recurse to the next tag
127
    return ParseTags();
128
129
  }

130
  bool ParseValue(nsACString* result = nullptr) {
131
    // If at the end, fail since we expect a comma ; value may be empty tho
132
    if (CheckEOF()) {
133
      return false;
134
    }
135

136
137
138
139
140
    Token t;
    while (Next(t)) {
      if (!Token::Char(',').Equals(t)) {
        if (result) {
          result->Append(t.Fragment());
141
142
143
144
        }
        continue;
      }

145
146
147
148
149
150
      if (CheckChar(',')) {
        // Two commas in a row, escaping
        if (result) {
          result->Append(',');
        }
        continue;
151
152
      }

153
154
155
      // We must give the comma back since the upper calls expect it
      Rollback();
      return true;
156
157
    }

158
    return false;
159
160
  }

161
162
 public:
  already_AddRefed<LoadContextInfo> Parse() {
163
    RefPtr<LoadContextInfo> info;
164
    if (ParseTags()) {
165
      info = GetLoadContextInfo(isAnonymous, originAttribs);
166
    }
167
168

    return info.forget();
169
  }
170

171
  void URISpec(nsACString& result) { result.Assign(cacheKey); }
172

173
  void IdEnhance(nsACString& result) { result.Assign(idEnhance); }
174
175
};

176
}  // namespace
177

178
179
180
already_AddRefed<nsILoadContextInfo> ParseKey(const nsACString& aKey,
                                              nsACString* aIdEnhance,
                                              nsACString* aURISpec) {
181
  KeyParser parser(aKey);
182
  RefPtr<LoadContextInfo> info = parser.Parse();
183

184
  if (info) {
185
186
    if (aIdEnhance) parser.IdEnhance(*aIdEnhance);
    if (aURISpec) parser.URISpec(*aURISpec);
187
188
  }

189
  return info.forget();
190
191
}

192
void AppendKeyPrefix(nsILoadContextInfo* aInfo, nsACString& _retval) {
193
194
195
  /**
   * This key is used to salt file hashes.  When form of the key is changed
   * cache entries will fail to find on disk.
196
197
198
   *
   * IMPORTANT NOTE:
   * Keep the attributes list sorted according their ASCII code.
199
   */
200

201
  OriginAttributes const* oa = aInfo->OriginAttributesPtr();
202
203
204
205
  nsAutoCString suffix;
  oa->CreateSuffix(suffix);
  if (!suffix.IsEmpty()) {
    AppendTagWithValue(_retval, 'O', suffix);
206
207
  }

208
209
  if (aInfo->IsAnonymous()) {
    _retval.AppendLiteral("a,");
210
  }
211
212

  if (aInfo->IsPrivate()) {
213
    _retval.AppendLiteral("p,");
214
215
216
  }
}

217
218
void AppendTagWithValue(nsACString& aTarget, char const aTag,
                        const nsACString& aValue) {
219
220
221
222
223
  aTarget.Append(aTag);

  // First check the value string to save some memory copying
  // for cases we don't need to escape at all (most likely).
  if (!aValue.IsEmpty()) {
224
    if (!aValue.Contains(',')) {
225
226
227
228
      // No need to escape
      aTarget.Append(aValue);
    } else {
      nsAutoCString escapedValue(aValue);
229
230
      escapedValue.ReplaceSubstring(NS_LITERAL_CSTRING(","),
                                    NS_LITERAL_CSTRING(",,"));
231
232
233
234
235
236
      aTarget.Append(escapedValue);
    }
  }

  aTarget.Append(',');
}
237

238
239
nsresult KeyMatchesLoadContextInfo(const nsACString& aKey,
                                   nsILoadContextInfo* aInfo, bool* _retval) {
240
241
242
243
244
245
246
247
248
249
  nsCOMPtr<nsILoadContextInfo> info = ParseKey(aKey);

  if (!info) {
    return NS_ERROR_FAILURE;
  }

  *_retval = info->Equals(aInfo);
  return NS_OK;
}

250
ValidityPair::ValidityPair(uint32_t aOffset, uint32_t aLen)
251
    : mOffset(aOffset), mLen(aLen) {}
252

253
bool ValidityPair::CanBeMerged(const ValidityPair& aOther) const {
254
255
256
257
258
259
  // The pairs can be merged into a single one if the start of one of the pairs
  // is placed anywhere in the validity interval of other pair or exactly after
  // its end.
  return IsInOrFollows(aOther.mOffset) || aOther.IsInOrFollows(mOffset);
}

260
bool ValidityPair::IsInOrFollows(uint32_t aOffset) const {
261
262
263
  return mOffset <= aOffset && mOffset + mLen >= aOffset;
}

264
bool ValidityPair::LessThan(const ValidityPair& aOther) const {
265
266
267
268
269
270
271
272
273
274
275
  if (mOffset < aOther.mOffset) {
    return true;
  }

  if (mOffset == aOther.mOffset && mLen < aOther.mLen) {
    return true;
  }

  return false;
}

276
void ValidityPair::Merge(const ValidityPair& aOther) {
277
278
279
280
281
282
283
284
285
  MOZ_ASSERT(CanBeMerged(aOther));

  uint32_t offset = std::min(mOffset, aOther.mOffset);
  uint32_t end = std::max(mOffset + mLen, aOther.mOffset + aOther.mLen);

  mOffset = offset;
  mLen = end - offset;
}

286
void ValidityMap::Log() const {
287
  LOG(("ValidityMap::Log() - number of pairs: %zu", mMap.Length()));
288
  for (uint32_t i = 0; i < mMap.Length(); i++) {
289
290
291
292
    LOG(("    (%u, %u)", mMap[i].Offset() + 0, mMap[i].Len() + 0));
  }
}

293
uint32_t ValidityMap::Length() const { return mMap.Length(); }
294

295
void ValidityMap::AddPair(uint32_t aOffset, uint32_t aLen) {
296
297
298
299
300
301
302
303
304
305
  ValidityPair pair(aOffset, aLen);

  if (mMap.Length() == 0) {
    mMap.AppendElement(pair);
    return;
  }

  // Find out where to place this pair into the map, it can overlap only with
  // one preceding pair and all subsequent pairs.
  uint32_t pos = 0;
306
  for (pos = mMap.Length(); pos > 0;) {
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
    --pos;

    if (mMap[pos].LessThan(pair)) {
      // The new pair should be either inserted after pos or merged with it.
      if (mMap[pos].CanBeMerged(pair)) {
        // Merge with the preceding pair
        mMap[pos].Merge(pair);
      } else {
        // They don't overlap, element must be placed after pos element
        ++pos;
        if (pos == mMap.Length()) {
          mMap.AppendElement(pair);
        } else {
          mMap.InsertElementAt(pos, pair);
        }
      }

      break;
    }

    if (pos == 0) {
      // The new pair should be placed in front of all existing pairs.
      mMap.InsertElementAt(0, pair);
    }
  }

  // pos now points to merged or inserted pair, check whether it overlaps with
  // subsequent pairs.
  while (pos + 1 < mMap.Length()) {
    if (mMap[pos].CanBeMerged(mMap[pos + 1])) {
      mMap[pos].Merge(mMap[pos + 1]);
      mMap.RemoveElementAt(pos + 1);
    } else {
      break;
    }
  }
}

345
void ValidityMap::Clear() { mMap.Clear(); }
346

347
348
size_t ValidityMap::SizeOfExcludingThis(
    mozilla::MallocSizeOf mallocSizeOf) const {
349
  return mMap.ShallowSizeOfExcludingThis(mallocSizeOf);
350
351
}

352
ValidityPair& ValidityMap::operator[](uint32_t aIdx) {
353
354
355
  return mMap.ElementAt(aIdx);
}

356
357
StaticMutex DetailedCacheHitTelemetry::sLock;
uint32_t DetailedCacheHitTelemetry::sRecordCnt = 0;
358
359
DetailedCacheHitTelemetry::HitRate
    DetailedCacheHitTelemetry::sHRStats[kNumOfRanges];
360

361
DetailedCacheHitTelemetry::HitRate::HitRate() { Reset(); }
362

363
void DetailedCacheHitTelemetry::HitRate::AddRecord(ERecType aType) {
364
365
366
367
368
369
370
  if (aType == HIT) {
    ++mHitCnt;
  } else {
    ++mMissCnt;
  }
}

371
372
uint32_t DetailedCacheHitTelemetry::HitRate::GetHitRateBucket(
    uint32_t aNumOfBuckets) const {
373
  uint32_t bucketIdx = (aNumOfBuckets * mHitCnt) / (mHitCnt + mMissCnt);
374
375
  if (bucketIdx ==
      aNumOfBuckets) {  // make sure 100% falls into the last bucket
376
377
378
379
380
381
    --bucketIdx;
  }

  return bucketIdx;
}

382
uint32_t DetailedCacheHitTelemetry::HitRate::Count() {
383
384
385
  return mHitCnt + mMissCnt;
}

386
void DetailedCacheHitTelemetry::HitRate::Reset() {
387
388
389
390
391
  mHitCnt = 0;
  mMissCnt = 0;
}

// static
392
393
void DetailedCacheHitTelemetry::AddRecord(ERecType aType,
                                          TimeStamp aLoadStart) {
394
395
396
397
398
399
400
401
402
403
404
405
406
407
  bool isUpToDate = false;
  CacheIndex::IsUpToDate(&isUpToDate);
  if (!isUpToDate) {
    // Ignore the record when the entry file count might be incorrect
    return;
  }

  uint32_t entryCount;
  nsresult rv = CacheIndex::GetEntryFileCount(&entryCount);
  if (NS_FAILED(rv)) {
    return;
  }

  uint32_t rangeIdx = entryCount / kRangeSize;
408
  if (rangeIdx >= kNumOfRanges) {  // The last range has no upper limit.
409
410
411
    rangeIdx = kNumOfRanges - 1;
  }

412
413
  uint32_t hitMissValue = 2 * rangeIdx;  // 2 values per range
  if (aType == MISS) {                   // The order is HIT, MISS
414
415
416
417
418
419
420
    ++hitMissValue;
  }

  StaticMutexAutoLock lock(sLock);

  if (aType == MISS) {
    mozilla::Telemetry::AccumulateTimeDelta(
421
        mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS, aLoadStart);
422
423
  } else {
    mozilla::Telemetry::AccumulateTimeDelta(
424
        mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS, aLoadStart);
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
  }

  Telemetry::Accumulate(Telemetry::NETWORK_CACHE_HIT_MISS_STAT_PER_CACHE_SIZE,
                        hitMissValue);

  sHRStats[rangeIdx].AddRecord(aType);
  ++sRecordCnt;

  if (sRecordCnt < kTotalSamplesReportLimit) {
    return;
  }

  sRecordCnt = 0;

  for (uint32_t i = 0; i < kNumOfRanges; ++i) {
    if (sHRStats[i].Count() >= kHitRateSamplesReportLimit) {
      // The telemetry enums are grouped by buckets as follows:
      // Telemetry value : 0,1,2,3, ... ,19,20,21,22, ... ,398,399
      // Hit rate bucket : 0,0,0,0, ... , 0, 1, 1, 1, ... , 19, 19
      // Cache size range: 0,1,2,3, ... ,19, 0, 1, 2, ... , 18, 19
445
446
      uint32_t bucketOffset =
          sHRStats[i].GetHitRateBucket(kHitRateBuckets) * kNumOfRanges;
447
448
449
450
451
452
453
454

      Telemetry::Accumulate(Telemetry::NETWORK_CACHE_HIT_RATE_PER_CACHE_SIZE,
                            bucketOffset + i);
      sHRStats[i].Reset();
    }
  }
}

455
456
StaticMutex CachePerfStats::sLock;
CachePerfStats::PerfData CachePerfStats::sData[CachePerfStats::LAST];
457
458
uint32_t CachePerfStats::sCacheSlowCnt = 0;
uint32_t CachePerfStats::sCacheNotSlowCnt = 0;
459
460

CachePerfStats::MMA::MMA(uint32_t aTotalWeight, bool aFilter)
461
    : mSum(0), mSumSq(0), mCnt(0), mWeight(aTotalWeight), mFilter(aFilter) {}
462

463
void CachePerfStats::MMA::AddValue(uint32_t aValue) {
464
465
466
467
  if (mFilter) {
    // Filter high spikes
    uint32_t avg = GetAverage();
    uint32_t stddev = GetStdDev();
468
    uint32_t maxdiff = avg + (3 * stddev);
469
470
471
472
473
474
475
476
477
478
    if (avg && aValue > avg + maxdiff) {
      return;
    }
  }

  if (mCnt < mWeight) {
    // Compute arithmetic average until we have at least mWeight values
    CheckedInt<uint64_t> newSumSq = CheckedInt<uint64_t>(aValue) * aValue;
    newSumSq += mSumSq;
    if (!newSumSq.isValid()) {
479
      return;  // ignore this value
480
481
482
483
484
485
486
487
    }
    mSumSq = newSumSq.value();
    mSum += aValue;
    ++mCnt;
  } else {
    CheckedInt<uint64_t> newSumSq = mSumSq - mSumSq / mCnt;
    newSumSq += static_cast<uint64_t>(aValue) * aValue;
    if (!newSumSq.isValid()) {
488
      return;  // ignore this value
489
490
491
492
493
494
495
496
497
498
    }
    mSumSq = newSumSq.value();

    // Compute modified moving average for more values:
    // newAvg = ((weight - 1) * oldAvg + newValue) / weight
    mSum -= GetAverage();
    mSum += aValue;
  }
}

499
uint32_t CachePerfStats::MMA::GetAverage() {
500
501
502
503
504
505
506
  if (mCnt == 0) {
    return 0;
  }

  return mSum / mCnt;
}

507
uint32_t CachePerfStats::MMA::GetStdDev() {
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
  if (mCnt == 0) {
    return 0;
  }

  uint32_t avg = GetAverage();
  uint64_t avgSq = static_cast<uint64_t>(avg) * avg;
  uint64_t variance = mSumSq / mCnt;
  if (variance < avgSq) {
    // Due to rounding error when using integer data type, it can happen that
    // average of squares of the values is smaller than square of the average
    // of the values. In this case fix mSumSq.
    variance = avgSq;
    mSumSq = variance * mCnt;
  }

  variance -= avgSq;
524
  return sqrt(static_cast<double>(variance));
525
526
527
}

CachePerfStats::PerfData::PerfData()
528
    : mFilteredAvg(50, true), mShortAvg(3, false) {}
529

530
void CachePerfStats::PerfData::AddValue(uint32_t aValue, bool aShortOnly) {
531
532
533
534
535
536
  if (!aShortOnly) {
    mFilteredAvg.AddValue(aValue);
  }
  mShortAvg.AddValue(aValue);
}

537
uint32_t CachePerfStats::PerfData::GetAverage(bool aFiltered) {
538
539
540
  return aFiltered ? mFilteredAvg.GetAverage() : mShortAvg.GetAverage();
}

541
uint32_t CachePerfStats::PerfData::GetStdDev(bool aFiltered) {
542
543
544
545
  return aFiltered ? mFilteredAvg.GetStdDev() : mShortAvg.GetStdDev();
}

// static
546
547
void CachePerfStats::AddValue(EDataType aType, uint32_t aValue,
                              bool aShortOnly) {
548
549
550
551
552
  StaticMutexAutoLock lock(sLock);
  sData[aType].AddValue(aValue, aShortOnly);
}

// static
553
uint32_t CachePerfStats::GetAverage(EDataType aType, bool aFiltered) {
554
555
556
557
  StaticMutexAutoLock lock(sLock);
  return sData[aType].GetAverage(aFiltered);
}

558
// static
559
uint32_t CachePerfStats::GetStdDev(EDataType aType, bool aFiltered) {
560
561
562
563
  StaticMutexAutoLock lock(sLock);
  return sData[aType].GetStdDev(aFiltered);
}

564
565
// static
bool CachePerfStats::IsCacheSlow() {
566
567
  StaticMutexAutoLock lock(sLock);

568
569
570
571
  // Compare mShortAvg with mFilteredAvg to find out whether cache is getting
  // slower. Use only data about single IO operations because ENTRY_OPEN can be
  // affected by more factors than a slow disk.
  for (uint32_t i = 0; i < ENTRY_OPEN; ++i) {
572
573
574
575
576
577
578
579
    if (i == IO_WRITE) {
      // Skip this data type. IsCacheSlow is used for determining cache slowness
      // when opening entries. Writes have low priority and it's normal that
      // they are delayed a lot, but this doesn't necessarily affect opening
      // cache entries.
      continue;
    }

580
581
582
583
584
585
586
    uint32_t avgLong = sData[i].GetAverage(true);
    if (avgLong == 0) {
      // We have no perf data yet, skip this data type.
      continue;
    }
    uint32_t avgShort = sData[i].GetAverage(false);
    uint32_t stddevLong = sData[i].GetStdDev(true);
587
    uint32_t maxdiff = avgLong + (3 * stddevLong);
588
589

    if (avgShort > avgLong + maxdiff) {
590
591
592
593
      LOG(
          ("CachePerfStats::IsCacheSlow() - result is slow based on perf "
           "type %u [avgShort=%u, avgLong=%u, stddevLong=%u]",
           i, avgShort, avgLong, stddevLong));
594
      ++sCacheSlowCnt;
595
596
597
598
      return true;
    }
  }

599
  ++sCacheNotSlowCnt;
600
601
602
  return false;
}

603
// static
604
void CachePerfStats::GetSlowStats(uint32_t* aSlow, uint32_t* aNotSlow) {
605
606
607
608
  *aSlow = sCacheSlowCnt;
  *aNotSlow = sCacheNotSlowCnt;
}

609
void FreeBuffer(void* aBuf) {
610
611
612
613
614
615
616
617
618
#ifndef NS_FREE_PERMANENT_DATA
  if (CacheObserver::ShuttingDown()) {
    return;
  }
#endif

  free(aBuf);
}

619
620
nsresult ParseAlternativeDataInfo(const char* aInfo, int64_t* _offset,
                                  nsACString* _type) {
621
622
623
624
625
626
627
  // The format is: "1;12345,javascript/binary"
  //         <version>;<offset>,<type>
  mozilla::Tokenizer p(aInfo, nullptr, "/");
  uint32_t altDataVersion = 0;
  int64_t altDataOffset = -1;

  // The metadata format has a wrong version number.
628
629
630
631
632
  if (!p.ReadInteger(&altDataVersion) || altDataVersion != kAltDataVersion) {
    LOG(
        ("ParseAlternativeDataInfo() - altDataVersion=%u, "
         "expectedVersion=%u",
         altDataVersion, kAltDataVersion));
633
634
635
    return NS_ERROR_NOT_AVAILABLE;
  }

636
  if (!p.CheckChar(';') || !p.ReadInteger(&altDataOffset) ||
637
638
639
640
641
642
643
644
645
      !p.CheckChar(',')) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  // The requested alt-data representation is not available
  if (altDataOffset < 0) {
    return NS_ERROR_NOT_AVAILABLE;
  }

646
647
648
649
  if (_offset) {
    *_offset = altDataOffset;
  }

650
651
652
653
654
655
656
  if (_type) {
    mozilla::Unused << p.ReadUntil(Tokenizer::Token::EndOfFile(), *_type);
  }

  return NS_OK;
}

657
658
void BuildAlternativeDataInfo(const char* aInfo, int64_t aOffset,
                              nsACString& _retval) {
659
660
661
662
663
664
665
666
  _retval.Truncate();
  _retval.AppendInt(kAltDataVersion);
  _retval.Append(';');
  _retval.AppendInt(aOffset);
  _retval.Append(',');
  _retval.Append(aInfo);
}

667
668
669
nsresult ParseBaseDomainAccessInfo(const char* aInfo, uint32_t aTrID,
                                   const uint32_t* aSearchSiteID, bool* _found,
                                   uint16_t* _count) {
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
  // The format is: "1;12;339456,490536687,1964820,"
  //         <version>;<telemetry_report_ID>;<siteID>,<siteID>,
  mozilla::Tokenizer p(aInfo);
  uint32_t i = 0;
  uint16_t siteIDCnt = 0;

  // Check version and telemetry report ID
  if (!p.ReadInteger(&i) || i != kBaseDomainAccessInfoVersion ||
      !p.CheckChar(';') || !p.ReadInteger(&i) || i != aTrID ||
      !p.CheckChar(';')) {
    LOG(
        ("ParseBaseDomainAccessInfo() - cannot parse info [info=%s, version=%u,"
         " trID=%u]",
         aInfo, kBaseDomainAccessInfoVersion, aTrID));
    return NS_ERROR_NOT_AVAILABLE;
  }

  do {
    if (!p.ReadInteger(&i) || !p.CheckChar(',')) {
      LOG(
          ("ParseBaseDomainAccessInfo() - cannot parse site ID [info=%s, "
           "siteIDCnt=%d]",
           aInfo, siteIDCnt));
      return NS_ERROR_NOT_AVAILABLE;
    }

    // If aSearchSiteID was provided, we don't need the total count of IDs.
    // Just return true and don't process the rest of data.
    if (aSearchSiteID && *aSearchSiteID == i) {
      *_found = true;
      return NS_OK;
    }

    ++siteIDCnt;
  } while (!p.CheckEOF());

  if (_count) {
    *_count = siteIDCnt;
  }

  return NS_OK;
}

713
714
void BuildOrAppendBaseDomainAccessInfo(const char* aOldInfo, uint32_t aTrID,
                                       uint32_t aSiteID, nsACString& _retval) {
715
716
717
718
719
720
721
722
723
724
725
726
727
728
  if (aOldInfo) {
    _retval.Assign(aOldInfo);
  } else {
    _retval.Truncate();
    _retval.AppendInt(kBaseDomainAccessInfoVersion);
    _retval.Append(';');
    _retval.AppendInt(aTrID);
    _retval.Append(';');
  }

  _retval.AppendInt(aSiteID);
  _retval.Append(',');
}

729
730
731
}  // namespace CacheFileUtils
}  // namespace net
}  // namespace mozilla