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