Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/netwerk/cache2/CacheFileUtils.cpp
Line
Count
Source (jump to first uncovered line)
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "CacheIndex.h"
6
#include "CacheLog.h"
7
#include "CacheFileUtils.h"
8
#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
namespace mozilla {
19
namespace net {
20
namespace CacheFileUtils {
21
22
// This designates the format for the "alt-data" metadata.
23
// When the format changes we need to update the version.
24
static uint32_t const kAltDataVersion = 1;
25
const char *kAltDataKey = "alt-data";
26
27
namespace {
28
29
/**
30
 * A simple recursive descent parser for the mapping key.
31
 */
32
class KeyParser : protected Tokenizer
33
{
34
public:
35
  explicit KeyParser(nsACString const& aInput)
36
    : Tokenizer(aInput)
37
    , isAnonymous(false)
38
    // Initialize the cache key to a zero length by default
39
    , lastTag(0)
40
0
  {
41
0
  }
42
43
private:
44
  // Results
45
  OriginAttributes originAttribs;
46
  bool isAnonymous;
47
  nsCString idEnhance;
48
  nsDependentCSubstring cacheKey;
49
50
  // Keeps the last tag name, used for alphabetical sort checking
51
  char lastTag;
52
53
  // Classifier for the 'tag' character valid range
54
  static bool TagChar(const char aChar)
55
0
  {
56
0
    return aChar >= ' ' && aChar <= '~';
57
0
  }
58
59
  bool ParseTags()
60
0
  {
61
0
    // Expects to be at the tag name or at the end
62
0
    if (CheckEOF()) {
63
0
      return true;
64
0
    }
65
0
66
0
    char tag;
67
0
    if (!ReadChar(&TagChar, &tag)) {
68
0
      return false;
69
0
    }
70
0
71
0
    // Check the alphabetical order, hard-fail on disobedience
72
0
    if (!(lastTag < tag || tag == ':')) {
73
0
      return false;
74
0
    }
75
0
    lastTag = tag;
76
0
77
0
    switch (tag) {
78
0
    case ':':
79
0
      // last possible tag, when present there is the cacheKey following,
80
0
      // not terminated with ',' and no need to unescape.
81
0
      cacheKey.Rebind(mCursor, mEnd - mCursor);
82
0
      return true;
83
0
    case 'O': {
84
0
      nsAutoCString originSuffix;
85
0
      if (!ParseValue(&originSuffix) || !originAttribs.PopulateFromSuffix(originSuffix)) {
86
0
        return false;
87
0
      }
88
0
      break;
89
0
    }
90
0
    case 'p':
91
0
      originAttribs.SyncAttributesWithPrivateBrowsing(true);
92
0
      break;
93
0
    case 'b':
94
0
      // Leaving to be able to read and understand oldformatted entries
95
0
      originAttribs.mInIsolatedMozBrowser = true;
96
0
      break;
97
0
    case 'a':
98
0
      isAnonymous = true;
99
0
      break;
100
0
    case 'i': {
101
0
      // Leaving to be able to read and understand oldformatted entries
102
0
      if (!ReadInteger(&originAttribs.mAppId)) {
103
0
        return false; // not a valid 32-bit integer
104
0
      }
105
0
      break;
106
0
    }
107
0
    case '~':
108
0
      if (!ParseValue(&idEnhance)) {
109
0
        return false;
110
0
      }
111
0
      break;
112
0
    default:
113
0
      if (!ParseValue()) { // skip any tag values, optional
114
0
        return false;
115
0
      }
116
0
      break;
117
0
    }
118
0
119
0
    // We expect a comma after every tag
120
0
    if (!CheckChar(',')) {
121
0
      return false;
122
0
    }
123
0
124
0
    // Recurse to the next tag
125
0
    return ParseTags();
126
0
  }
127
128
  bool ParseValue(nsACString *result = nullptr)
129
0
  {
130
0
    // If at the end, fail since we expect a comma ; value may be empty tho
131
0
    if (CheckEOF()) {
132
0
      return false;
133
0
    }
134
0
135
0
    Token t;
136
0
    while (Next(t)) {
137
0
      if (!Token::Char(',').Equals(t)) {
138
0
        if (result) {
139
0
          result->Append(t.Fragment());
140
0
        }
141
0
        continue;
142
0
      }
143
0
144
0
      if (CheckChar(',')) {
145
0
        // Two commas in a row, escaping
146
0
        if (result) {
147
0
          result->Append(',');
148
0
        }
149
0
        continue;
150
0
      }
151
0
152
0
      // We must give the comma back since the upper calls expect it
153
0
      Rollback();
154
0
      return true;
155
0
    }
156
0
157
0
    return false;
158
0
  }
159
160
public:
161
  already_AddRefed<LoadContextInfo> Parse()
162
0
  {
163
0
    RefPtr<LoadContextInfo> info;
164
0
    if (ParseTags()) {
165
0
      info = GetLoadContextInfo(isAnonymous, originAttribs);
166
0
    }
167
0
168
0
    return info.forget();
169
0
  }
170
171
  void URISpec(nsACString &result)
172
0
  {
173
0
    result.Assign(cacheKey);
174
0
  }
175
176
  void IdEnhance(nsACString &result)
177
0
  {
178
0
    result.Assign(idEnhance);
179
0
  }
180
};
181
182
} // namespace
183
184
already_AddRefed<nsILoadContextInfo>
185
ParseKey(const nsACString& aKey,
186
         nsACString* aIdEnhance,
187
         nsACString* aURISpec)
188
0
{
189
0
  KeyParser parser(aKey);
190
0
  RefPtr<LoadContextInfo> info = parser.Parse();
191
0
192
0
  if (info) {
193
0
    if (aIdEnhance)
194
0
      parser.IdEnhance(*aIdEnhance);
195
0
    if (aURISpec)
196
0
      parser.URISpec(*aURISpec);
197
0
  }
198
0
199
0
  return info.forget();
200
0
}
201
202
void
203
AppendKeyPrefix(nsILoadContextInfo* aInfo, nsACString &_retval)
204
0
{
205
0
  /**
206
0
   * This key is used to salt file hashes.  When form of the key is changed
207
0
   * cache entries will fail to find on disk.
208
0
   *
209
0
   * IMPORTANT NOTE:
210
0
   * Keep the attributes list sorted according their ASCII code.
211
0
   */
212
0
213
0
  OriginAttributes const *oa = aInfo->OriginAttributesPtr();
214
0
  nsAutoCString suffix;
215
0
  oa->CreateSuffix(suffix);
216
0
  if (!suffix.IsEmpty()) {
217
0
    AppendTagWithValue(_retval, 'O', suffix);
218
0
  }
219
0
220
0
  if (aInfo->IsAnonymous()) {
221
0
    _retval.AppendLiteral("a,");
222
0
  }
223
0
224
0
  if (aInfo->IsPrivate()) {
225
0
    _retval.AppendLiteral("p,");
226
0
  }
227
0
}
228
229
void
230
AppendTagWithValue(nsACString& aTarget, char const aTag, const nsACString& aValue)
231
0
{
232
0
  aTarget.Append(aTag);
233
0
234
0
  // First check the value string to save some memory copying
235
0
  // for cases we don't need to escape at all (most likely).
236
0
  if (!aValue.IsEmpty()) {
237
0
    if (!aValue.Contains(',')) {
238
0
      // No need to escape
239
0
      aTarget.Append(aValue);
240
0
    } else {
241
0
      nsAutoCString escapedValue(aValue);
242
0
      escapedValue.ReplaceSubstring(
243
0
        NS_LITERAL_CSTRING(","), NS_LITERAL_CSTRING(",,"));
244
0
      aTarget.Append(escapedValue);
245
0
    }
246
0
  }
247
0
248
0
  aTarget.Append(',');
249
0
}
250
251
nsresult
252
KeyMatchesLoadContextInfo(const nsACString &aKey, nsILoadContextInfo *aInfo,
253
                          bool *_retval)
254
0
{
255
0
  nsCOMPtr<nsILoadContextInfo> info = ParseKey(aKey);
256
0
257
0
  if (!info) {
258
0
    return NS_ERROR_FAILURE;
259
0
  }
260
0
261
0
  *_retval = info->Equals(aInfo);
262
0
  return NS_OK;
263
0
}
264
265
ValidityPair::ValidityPair(uint32_t aOffset, uint32_t aLen)
266
  : mOffset(aOffset), mLen(aLen)
267
0
{}
268
269
bool
270
ValidityPair::CanBeMerged(const ValidityPair& aOther) const
271
0
{
272
0
  // The pairs can be merged into a single one if the start of one of the pairs
273
0
  // is placed anywhere in the validity interval of other pair or exactly after
274
0
  // its end.
275
0
  return IsInOrFollows(aOther.mOffset) || aOther.IsInOrFollows(mOffset);
276
0
}
277
278
bool
279
ValidityPair::IsInOrFollows(uint32_t aOffset) const
280
0
{
281
0
  return mOffset <= aOffset && mOffset + mLen >= aOffset;
282
0
}
283
284
bool
285
ValidityPair::LessThan(const ValidityPair& aOther) const
286
0
{
287
0
  if (mOffset < aOther.mOffset) {
288
0
    return true;
289
0
  }
290
0
291
0
  if (mOffset == aOther.mOffset && mLen < aOther.mLen) {
292
0
    return true;
293
0
  }
294
0
295
0
  return false;
296
0
}
297
298
void
299
ValidityPair::Merge(const ValidityPair& aOther)
300
0
{
301
0
  MOZ_ASSERT(CanBeMerged(aOther));
302
0
303
0
  uint32_t offset = std::min(mOffset, aOther.mOffset);
304
0
  uint32_t end = std::max(mOffset + mLen, aOther.mOffset + aOther.mLen);
305
0
306
0
  mOffset = offset;
307
0
  mLen = end - offset;
308
0
}
309
310
void
311
ValidityMap::Log() const
312
0
{
313
0
  LOG(("ValidityMap::Log() - number of pairs: %zu", mMap.Length()));
314
0
  for (uint32_t i=0; i<mMap.Length(); i++) {
315
0
    LOG(("    (%u, %u)", mMap[i].Offset() + 0, mMap[i].Len() + 0));
316
0
  }
317
0
}
318
319
uint32_t
320
ValidityMap::Length() const
321
0
{
322
0
  return mMap.Length();
323
0
}
324
325
void
326
ValidityMap::AddPair(uint32_t aOffset, uint32_t aLen)
327
0
{
328
0
  ValidityPair pair(aOffset, aLen);
329
0
330
0
  if (mMap.Length() == 0) {
331
0
    mMap.AppendElement(pair);
332
0
    return;
333
0
  }
334
0
335
0
  // Find out where to place this pair into the map, it can overlap only with
336
0
  // one preceding pair and all subsequent pairs.
337
0
  uint32_t pos = 0;
338
0
  for (pos = mMap.Length(); pos > 0; ) {
339
0
    --pos;
340
0
341
0
    if (mMap[pos].LessThan(pair)) {
342
0
      // The new pair should be either inserted after pos or merged with it.
343
0
      if (mMap[pos].CanBeMerged(pair)) {
344
0
        // Merge with the preceding pair
345
0
        mMap[pos].Merge(pair);
346
0
      } else {
347
0
        // They don't overlap, element must be placed after pos element
348
0
        ++pos;
349
0
        if (pos == mMap.Length()) {
350
0
          mMap.AppendElement(pair);
351
0
        } else {
352
0
          mMap.InsertElementAt(pos, pair);
353
0
        }
354
0
      }
355
0
356
0
      break;
357
0
    }
358
0
359
0
    if (pos == 0) {
360
0
      // The new pair should be placed in front of all existing pairs.
361
0
      mMap.InsertElementAt(0, pair);
362
0
    }
363
0
  }
364
0
365
0
  // pos now points to merged or inserted pair, check whether it overlaps with
366
0
  // subsequent pairs.
367
0
  while (pos + 1 < mMap.Length()) {
368
0
    if (mMap[pos].CanBeMerged(mMap[pos + 1])) {
369
0
      mMap[pos].Merge(mMap[pos + 1]);
370
0
      mMap.RemoveElementAt(pos + 1);
371
0
    } else {
372
0
      break;
373
0
    }
374
0
  }
375
0
}
376
377
void
378
ValidityMap::Clear()
379
0
{
380
0
  mMap.Clear();
381
0
}
382
383
size_t
384
ValidityMap::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
385
0
{
386
0
  return mMap.ShallowSizeOfExcludingThis(mallocSizeOf);
387
0
}
388
389
ValidityPair&
390
ValidityMap::operator[](uint32_t aIdx)
391
0
{
392
0
  return mMap.ElementAt(aIdx);
393
0
}
394
395
StaticMutex DetailedCacheHitTelemetry::sLock;
396
uint32_t DetailedCacheHitTelemetry::sRecordCnt = 0;
397
DetailedCacheHitTelemetry::HitRate DetailedCacheHitTelemetry::sHRStats[kNumOfRanges];
398
399
DetailedCacheHitTelemetry::HitRate::HitRate()
400
60
{
401
60
  Reset();
402
60
}
403
404
void
405
DetailedCacheHitTelemetry::HitRate::AddRecord(ERecType aType)
406
0
{
407
0
  if (aType == HIT) {
408
0
    ++mHitCnt;
409
0
  } else {
410
0
    ++mMissCnt;
411
0
  }
412
0
}
413
414
uint32_t
415
DetailedCacheHitTelemetry::HitRate::GetHitRateBucket(uint32_t aNumOfBuckets) const
416
0
{
417
0
  uint32_t bucketIdx = (aNumOfBuckets * mHitCnt) / (mHitCnt + mMissCnt);
418
0
  if (bucketIdx == aNumOfBuckets) { // make sure 100% falls into the last bucket
419
0
    --bucketIdx;
420
0
  }
421
0
422
0
  return bucketIdx;
423
0
}
424
425
uint32_t
426
DetailedCacheHitTelemetry::HitRate::Count()
427
0
{
428
0
  return mHitCnt + mMissCnt;
429
0
}
430
431
void
432
DetailedCacheHitTelemetry::HitRate::Reset()
433
60
{
434
60
  mHitCnt = 0;
435
60
  mMissCnt = 0;
436
60
}
437
438
// static
439
void
440
DetailedCacheHitTelemetry::AddRecord(ERecType aType, TimeStamp aLoadStart)
441
0
{
442
0
  bool isUpToDate = false;
443
0
  CacheIndex::IsUpToDate(&isUpToDate);
444
0
  if (!isUpToDate) {
445
0
    // Ignore the record when the entry file count might be incorrect
446
0
    return;
447
0
  }
448
0
449
0
  uint32_t entryCount;
450
0
  nsresult rv = CacheIndex::GetEntryFileCount(&entryCount);
451
0
  if (NS_FAILED(rv)) {
452
0
    return;
453
0
  }
454
0
455
0
  uint32_t rangeIdx = entryCount / kRangeSize;
456
0
  if (rangeIdx >= kNumOfRanges) { // The last range has no upper limit.
457
0
    rangeIdx = kNumOfRanges - 1;
458
0
  }
459
0
460
0
  uint32_t hitMissValue = 2 * rangeIdx; // 2 values per range
461
0
  if (aType == MISS) { // The order is HIT, MISS
462
0
    ++hitMissValue;
463
0
  }
464
0
465
0
  StaticMutexAutoLock lock(sLock);
466
0
467
0
  if (aType == MISS) {
468
0
    mozilla::Telemetry::AccumulateTimeDelta(
469
0
      mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS,
470
0
      aLoadStart);
471
0
  } else {
472
0
    mozilla::Telemetry::AccumulateTimeDelta(
473
0
      mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS,
474
0
      aLoadStart);
475
0
  }
476
0
477
0
  Telemetry::Accumulate(Telemetry::NETWORK_CACHE_HIT_MISS_STAT_PER_CACHE_SIZE,
478
0
                        hitMissValue);
479
0
480
0
  sHRStats[rangeIdx].AddRecord(aType);
481
0
  ++sRecordCnt;
482
0
483
0
  if (sRecordCnt < kTotalSamplesReportLimit) {
484
0
    return;
485
0
  }
486
0
487
0
  sRecordCnt = 0;
488
0
489
0
  for (uint32_t i = 0; i < kNumOfRanges; ++i) {
490
0
    if (sHRStats[i].Count() >= kHitRateSamplesReportLimit) {
491
0
      // The telemetry enums are grouped by buckets as follows:
492
0
      // Telemetry value : 0,1,2,3, ... ,19,20,21,22, ... ,398,399
493
0
      // Hit rate bucket : 0,0,0,0, ... , 0, 1, 1, 1, ... , 19, 19
494
0
      // Cache size range: 0,1,2,3, ... ,19, 0, 1, 2, ... , 18, 19
495
0
      uint32_t bucketOffset = sHRStats[i].GetHitRateBucket(kHitRateBuckets) *
496
0
                              kNumOfRanges;
497
0
498
0
      Telemetry::Accumulate(Telemetry::NETWORK_CACHE_HIT_RATE_PER_CACHE_SIZE,
499
0
                            bucketOffset + i);
500
0
      sHRStats[i].Reset();
501
0
    }
502
0
  }
503
0
}
504
505
StaticMutex CachePerfStats::sLock;
506
CachePerfStats::PerfData CachePerfStats::sData[CachePerfStats::LAST];
507
uint32_t CachePerfStats::sCacheSlowCnt = 0;
508
uint32_t CachePerfStats::sCacheNotSlowCnt = 0;
509
510
CachePerfStats::MMA::MMA(uint32_t aTotalWeight, bool aFilter)
511
  : mSum(0)
512
  , mSumSq(0)
513
  , mCnt(0)
514
  , mWeight(aTotalWeight)
515
  , mFilter(aFilter)
516
24
{
517
24
}
518
519
void
520
CachePerfStats::MMA::AddValue(uint32_t aValue)
521
0
{
522
0
  if (mFilter) {
523
0
    // Filter high spikes
524
0
    uint32_t avg = GetAverage();
525
0
    uint32_t stddev = GetStdDev();
526
0
    uint32_t maxdiff = avg + (3 * stddev);
527
0
    if (avg && aValue > avg + maxdiff) {
528
0
      return;
529
0
    }
530
0
  }
531
0
532
0
  if (mCnt < mWeight) {
533
0
    // Compute arithmetic average until we have at least mWeight values
534
0
    CheckedInt<uint64_t> newSumSq = CheckedInt<uint64_t>(aValue) * aValue;
535
0
    newSumSq += mSumSq;
536
0
    if (!newSumSq.isValid()) {
537
0
      return; // ignore this value
538
0
    }
539
0
    mSumSq = newSumSq.value();
540
0
    mSum += aValue;
541
0
    ++mCnt;
542
0
  } else {
543
0
    CheckedInt<uint64_t> newSumSq = mSumSq - mSumSq / mCnt;
544
0
    newSumSq += static_cast<uint64_t>(aValue) * aValue;
545
0
    if (!newSumSq.isValid()) {
546
0
      return; // ignore this value
547
0
    }
548
0
    mSumSq = newSumSq.value();
549
0
550
0
    // Compute modified moving average for more values:
551
0
    // newAvg = ((weight - 1) * oldAvg + newValue) / weight
552
0
    mSum -= GetAverage();
553
0
    mSum += aValue;
554
0
  }
555
0
}
556
557
uint32_t
558
CachePerfStats::MMA::GetAverage()
559
0
{
560
0
  if (mCnt == 0) {
561
0
    return 0;
562
0
  }
563
0
564
0
  return mSum / mCnt;
565
0
}
566
567
uint32_t
568
CachePerfStats::MMA::GetStdDev()
569
0
{
570
0
  if (mCnt == 0) {
571
0
    return 0;
572
0
  }
573
0
574
0
  uint32_t avg = GetAverage();
575
0
  uint64_t avgSq = static_cast<uint64_t>(avg) * avg;
576
0
  uint64_t variance = mSumSq / mCnt;
577
0
  if (variance < avgSq) {
578
0
    // Due to rounding error when using integer data type, it can happen that
579
0
    // average of squares of the values is smaller than square of the average
580
0
    // of the values. In this case fix mSumSq.
581
0
    variance = avgSq;
582
0
    mSumSq = variance * mCnt;
583
0
  }
584
0
585
0
  variance -= avgSq;
586
0
  return sqrt(static_cast<double>(variance));
587
0
}
588
589
CachePerfStats::PerfData::PerfData()
590
  : mFilteredAvg(50, true)
591
  , mShortAvg(3, false)
592
12
{
593
12
}
594
595
void
596
CachePerfStats::PerfData::AddValue(uint32_t aValue, bool aShortOnly)
597
0
{
598
0
  if (!aShortOnly) {
599
0
    mFilteredAvg.AddValue(aValue);
600
0
  }
601
0
  mShortAvg.AddValue(aValue);
602
0
}
603
604
uint32_t
605
CachePerfStats::PerfData::GetAverage(bool aFiltered)
606
0
{
607
0
  return aFiltered ? mFilteredAvg.GetAverage() : mShortAvg.GetAverage();
608
0
}
609
610
uint32_t
611
CachePerfStats::PerfData::GetStdDev(bool aFiltered)
612
0
{
613
0
  return aFiltered ? mFilteredAvg.GetStdDev() : mShortAvg.GetStdDev();
614
0
}
615
616
// static
617
void
618
CachePerfStats::AddValue(EDataType aType, uint32_t aValue, bool aShortOnly)
619
0
{
620
0
  StaticMutexAutoLock lock(sLock);
621
0
  sData[aType].AddValue(aValue, aShortOnly);
622
0
}
623
624
// static
625
uint32_t
626
CachePerfStats::GetAverage(EDataType aType, bool aFiltered)
627
0
{
628
0
  StaticMutexAutoLock lock(sLock);
629
0
  return sData[aType].GetAverage(aFiltered);
630
0
}
631
632
// static
633
uint32_t
634
CachePerfStats::GetStdDev(EDataType aType, bool aFiltered)
635
0
{
636
0
  StaticMutexAutoLock lock(sLock);
637
0
  return sData[aType].GetStdDev(aFiltered);
638
0
}
639
640
//static
641
bool
642
CachePerfStats::IsCacheSlow()
643
0
{
644
0
  // Compare mShortAvg with mFilteredAvg to find out whether cache is getting
645
0
  // slower. Use only data about single IO operations because ENTRY_OPEN can be
646
0
  // affected by more factors than a slow disk.
647
0
  for (uint32_t i = 0; i < ENTRY_OPEN; ++i) {
648
0
    if (i == IO_WRITE) {
649
0
      // Skip this data type. IsCacheSlow is used for determining cache slowness
650
0
      // when opening entries. Writes have low priority and it's normal that
651
0
      // they are delayed a lot, but this doesn't necessarily affect opening
652
0
      // cache entries.
653
0
      continue;
654
0
    }
655
0
656
0
    uint32_t avgLong = sData[i].GetAverage(true);
657
0
    if (avgLong == 0) {
658
0
      // We have no perf data yet, skip this data type.
659
0
      continue;
660
0
    }
661
0
    uint32_t avgShort = sData[i].GetAverage(false);
662
0
    uint32_t stddevLong = sData[i].GetStdDev(true);
663
0
    uint32_t maxdiff = avgLong + (3 * stddevLong);
664
0
665
0
    if (avgShort > avgLong + maxdiff) {
666
0
      LOG(("CachePerfStats::IsCacheSlow() - result is slow based on perf "
667
0
           "type %u [avgShort=%u, avgLong=%u, stddevLong=%u]", i, avgShort,
668
0
           avgLong, stddevLong));
669
0
      ++sCacheSlowCnt;
670
0
      return true;
671
0
    }
672
0
  }
673
0
674
0
  ++sCacheNotSlowCnt;
675
0
  return false;
676
0
}
677
678
//static
679
void
680
CachePerfStats::GetSlowStats(uint32_t *aSlow, uint32_t *aNotSlow)
681
0
{
682
0
  *aSlow = sCacheSlowCnt;
683
0
  *aNotSlow = sCacheNotSlowCnt;
684
0
}
685
686
void
687
0
FreeBuffer(void *aBuf) {
688
0
#ifndef NS_FREE_PERMANENT_DATA
689
0
  if (CacheObserver::ShuttingDown()) {
690
0
    return;
691
0
  }
692
0
#endif
693
0
694
0
  free(aBuf);
695
0
}
696
697
nsresult
698
ParseAlternativeDataInfo(const char *aInfo, int64_t *_offset, nsACString *_type)
699
0
{
700
0
  // The format is: "1;12345,javascript/binary"
701
0
  //         <version>;<offset>,<type>
702
0
  mozilla::Tokenizer p(aInfo, nullptr, "/");
703
0
  uint32_t altDataVersion = 0;
704
0
  int64_t altDataOffset = -1;
705
0
706
0
  // The metadata format has a wrong version number.
707
0
  if (!p.ReadInteger(&altDataVersion) ||
708
0
      altDataVersion != kAltDataVersion) {
709
0
    LOG(("ParseAlternativeDataInfo() - altDataVersion=%u, "
710
0
         "expectedVersion=%u", altDataVersion, kAltDataVersion));
711
0
    return NS_ERROR_NOT_AVAILABLE;
712
0
  }
713
0
714
0
  if (!p.CheckChar(';') ||
715
0
      !p.ReadInteger(&altDataOffset) ||
716
0
      !p.CheckChar(',')) {
717
0
    return NS_ERROR_NOT_AVAILABLE;
718
0
  }
719
0
720
0
  // The requested alt-data representation is not available
721
0
  if (altDataOffset < 0) {
722
0
    return NS_ERROR_NOT_AVAILABLE;
723
0
  }
724
0
725
0
  if (_offset) {
726
0
    *_offset = altDataOffset;
727
0
  }
728
0
729
0
  if (_type) {
730
0
    mozilla::Unused << p.ReadUntil(Tokenizer::Token::EndOfFile(), *_type);
731
0
  }
732
0
733
0
  return NS_OK;
734
0
}
735
736
void
737
BuildAlternativeDataInfo(const char *aInfo, int64_t aOffset, nsACString &_retval)
738
0
{
739
0
  _retval.Truncate();
740
0
  _retval.AppendInt(kAltDataVersion);
741
0
  _retval.Append(';');
742
0
  _retval.AppendInt(aOffset);
743
0
  _retval.Append(',');
744
0
  _retval.Append(aInfo);
745
0
}
746
747
} // namespace CacheFileUtils
748
} // namespace net
749
} // namespace mozilla