diff options
Diffstat (limited to 'toolkit/components/url-classifier/ChunkSet.cpp')
-rw-r--r-- | toolkit/components/url-classifier/ChunkSet.cpp | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/toolkit/components/url-classifier/ChunkSet.cpp b/toolkit/components/url-classifier/ChunkSet.cpp new file mode 100644 index 0000000000..af488d43d7 --- /dev/null +++ b/toolkit/components/url-classifier/ChunkSet.cpp @@ -0,0 +1,248 @@ +//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "ChunkSet.h" + +#include "nsReadableUtils.h" + +namespace mozilla { +namespace safebrowsing { + +const size_t ChunkSet::IO_BUFFER_SIZE; + +nsresult ChunkSet::Serialize(nsACString& aChunkStr) { + // Truncate and append rather than assigning because that's more efficient if + // aString is an nsAutoCString. + aChunkStr.Truncate(); + StringJoinAppend(aChunkStr, ","_ns, mRanges, + [](nsACString& dst, const Range& range) { + dst.AppendInt((int32_t)range.Begin()); + if (range.Begin() != range.End()) { + dst.Append('-'); + dst.AppendInt((int32_t)range.End()); + } + }); + + return NS_OK; +} + +nsresult ChunkSet::Set(uint32_t aChunk) { + if (!Has(aChunk)) { + Range chunkRange(aChunk, aChunk); + + if (mRanges.Length() == 0) { + if (!mRanges.AppendElement(chunkRange, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; + } + + if (mRanges.LastElement().Precedes(chunkRange)) { + mRanges.LastElement().End(aChunk); + } else if (chunkRange.Precedes(mRanges[0])) { + mRanges[0].Begin(aChunk); + } else { + ChunkSet tmp; + if (!tmp.mRanges.AppendElement(chunkRange, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return Merge(tmp); + } + } + + return NS_OK; +} + +bool ChunkSet::Has(uint32_t aChunk) const { + size_t idx; + return BinarySearchIf(mRanges, 0, mRanges.Length(), + Range::IntersectionComparator(Range(aChunk, aChunk)), + &idx); + // IntersectionComparator works because we create a + // single-chunk range. +} + +nsresult ChunkSet::Merge(const ChunkSet& aOther) { + size_t oldLen = mRanges.Length(); + + for (const Range& mergeRange : aOther.mRanges) { + if (!HasSubrange(mergeRange)) { + if (!mRanges.InsertElementSorted(mergeRange, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + + if (oldLen < mRanges.Length()) { + for (size_t i = 1; i < mRanges.Length(); i++) { + while (mRanges[i - 1].FoldLeft(mRanges[i])) { + mRanges.RemoveElementAt(i); + + if (i == mRanges.Length()) { + return NS_OK; + } + } + } + } + + return NS_OK; +} + +uint32_t ChunkSet::Length() const { + uint32_t len = 0; + for (const Range& range : mRanges) { + len += range.Length(); + } + + return len; +} + +nsresult ChunkSet::Remove(const ChunkSet& aOther) { + for (const Range& removalRange : aOther.mRanges) { + if (mRanges.Length() == 0) { + return NS_OK; + } + + if (mRanges.LastElement().End() < removalRange.Begin() || + aOther.mRanges.LastElement().End() < mRanges[0].Begin()) { + return NS_OK; + } + + size_t intersectionIdx; + while (BinarySearchIf(mRanges, 0, mRanges.Length(), + Range::IntersectionComparator(removalRange), + &intersectionIdx)) { + ChunkSet remains; + nsresult rv = mRanges[intersectionIdx].Remove(removalRange, remains); + + if (NS_FAILED(rv)) { + return rv; + } + + mRanges.RemoveElementAt(intersectionIdx); + if (!mRanges.InsertElementsAt(intersectionIdx, remains.mRanges, + fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + + return NS_OK; +} + +void ChunkSet::Clear() { mRanges.Clear(); } + +nsresult ChunkSet::Write(nsIOutputStream* aOut) const { + nsTArray<uint32_t> chunks(IO_BUFFER_SIZE); + + for (const Range& range : mRanges) { + for (uint32_t chunk = range.Begin(); chunk <= range.End(); chunk++) { + chunks.AppendElement(chunk); + + if (chunks.Length() == chunks.Capacity()) { + nsresult rv = WriteTArray(aOut, chunks); + + if (NS_FAILED(rv)) { + return rv; + } + + chunks.Clear(); + } + } + } + + nsresult rv = WriteTArray(aOut, chunks); + + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +nsresult ChunkSet::Read(nsIInputStream* aIn, uint32_t aNumElements) { + nsTArray<uint32_t> chunks(IO_BUFFER_SIZE); + + while (aNumElements != 0) { + chunks.Clear(); + + uint32_t numToRead = + aNumElements > IO_BUFFER_SIZE ? IO_BUFFER_SIZE : aNumElements; + + nsresult rv = ReadTArray(aIn, &chunks, numToRead); + + if (NS_FAILED(rv)) { + return rv; + } + + aNumElements -= numToRead; + + for (uint32_t c : chunks) { + rv = Set(c); + + if (NS_FAILED(rv)) { + return rv; + } + } + } + + return NS_OK; +} + +bool ChunkSet::HasSubrange(const Range& aSubrange) const { + for (const Range& range : mRanges) { + if (range.Contains(aSubrange)) { + return true; + } else if (!(aSubrange.Begin() > range.End() || + range.Begin() > aSubrange.End())) { + // In this case, aSubrange overlaps this range but is not a subrange. + // because the ChunkSet implementation ensures that there are no + // overlapping ranges, this means that aSubrange cannot be a subrange of + // any of the following ranges + return false; + } + } + + return false; +} + +uint32_t ChunkSet::Range::Length() const { return mEnd - mBegin + 1; } + +nsresult ChunkSet::Range::Remove(const Range& aRange, + ChunkSet& aRemainderSet) const { + if (mBegin < aRange.mBegin && aRange.mBegin <= mEnd) { + // aRange overlaps & follows this range + Range range(mBegin, aRange.mBegin - 1); + if (!aRemainderSet.mRanges.AppendElement(range, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (mBegin <= aRange.mEnd && aRange.mEnd < mEnd) { + // aRange overlaps & precedes this range + Range range(aRange.mEnd + 1, mEnd); + if (!aRemainderSet.mRanges.AppendElement(range, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return NS_OK; +} + +bool ChunkSet::Range::FoldLeft(const Range& aRange) { + if (Contains(aRange)) { + return true; + } else if (Precedes(aRange) || + (mBegin <= aRange.mBegin && aRange.mBegin <= mEnd)) { + mEnd = aRange.mEnd; + return true; + } + + return false; +} + +} // namespace safebrowsing +} // namespace mozilla |