diff --git a/dom/media/MediaCache.cpp b/dom/media/MediaCache.cpp index 5734a740cba5..28859ff3476f 100644 --- a/dom/media/MediaCache.cpp +++ b/dom/media/MediaCache.cpp @@ -2471,6 +2471,72 @@ MediaCacheStream::ThrottleReadahead(bool bThrottle) } } +uint32_t +MediaCacheStream::ReadPartialBlock(int64_t aOffset, Span aBuffer) +{ + mMediaCache->GetReentrantMonitor().AssertCurrentThreadIn(); + MOZ_ASSERT(IsOffsetAllowed(aOffset)); + + if (OffsetToBlockIndexUnchecked(mChannelOffset) != + OffsetToBlockIndexUnchecked(aOffset) || + aOffset >= mChannelOffset) { + // Not in the partial block or no data to read. + return 0; + } + + auto source = MakeSpan( + mPartialBlockBuffer.get() + OffsetInBlock(aOffset), + OffsetInBlock(mChannelOffset) - OffsetInBlock(aOffset)); + // We have |source.Length() <= BLOCK_SIZE < INT32_MAX| to guarantee + // that |bytesToRead| can fit into a uint32_t. + uint32_t bytesToRead = std::min(aBuffer.Length(), source.Length()); + memcpy(aBuffer.Elements(), source.Elements(), bytesToRead); + return bytesToRead; +} + +Result +MediaCacheStream::ReadBlockFromCache(int64_t aOffset, Span aBuffer) +{ + mMediaCache->GetReentrantMonitor().AssertCurrentThreadIn(); + MOZ_ASSERT(IsOffsetAllowed(aOffset)); + + // OffsetToBlockIndexUnchecked() is always non-negative. + uint32_t index = OffsetToBlockIndexUnchecked(aOffset); + int32_t cacheBlock = index < mBlocks.Length() ? mBlocks[index] : -1; + if (cacheBlock < 0) { + // Not in the cache. + return 0; + } + + if (aBuffer.Length() > size_t(BLOCK_SIZE)) { + // Clamp the buffer to avoid overflow below since we will read at most + // BLOCK_SIZE bytes. + aBuffer = aBuffer.First(BLOCK_SIZE); + } + // |BLOCK_SIZE - OffsetInBlock(aOffset)| <= BLOCK_SIZE + int32_t bytesToRead = + std::min(BLOCK_SIZE - OffsetInBlock(aOffset), aBuffer.Length()); + int32_t bytesRead = 0; + nsresult rv = + mMediaCache->ReadCacheFile(cacheBlock * BLOCK_SIZE + OffsetInBlock(aOffset), + aBuffer.Elements(), + bytesToRead, + &bytesRead); + + // Ensure |cacheBlock * BLOCK_SIZE + OffsetInBlock(aOffset)| won't overflow. + static_assert(INT64_MAX >= BLOCK_SIZE * (uint32_t(INT32_MAX) + 1), + "BLOCK_SIZE too large!"); + + if (NS_FAILED(rv)) { + nsCString name; + GetErrorName(rv, name); + LOGE("Stream %p ReadCacheFile failed, rv=%s", this, name.Data()); + return mozilla::Err(rv); + } + + return bytesRead; +} + int64_t MediaCacheStream::Tell() { @@ -2615,67 +2681,49 @@ MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer, } nsresult -MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, int64_t aCount) +MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) { ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor()); + // The buffer we are about to fill. + auto buffer = MakeSpan(aBuffer, aCount); + // Read one block (or part of a block) at a time - uint32_t count = 0; int64_t streamOffset = aOffset; - while (count < aCount) { + while (!buffer.IsEmpty()) { if (mClosed) { // We need to check |mClosed| in each iteration which might be changed // after calling |mMediaCache->ReadCacheFile|. return NS_ERROR_FAILURE; } - int32_t streamBlock = OffsetToBlockIndex(streamOffset); - if (streamBlock < 0) { - break; - } - uint32_t offsetInStreamBlock = - uint32_t(streamOffset - streamBlock*BLOCK_SIZE); - int64_t size = std::min(aCount - count, BLOCK_SIZE - offsetInStreamBlock); - if (mStreamLength >= 0) { - // Don't try to read beyond the end of the stream - int64_t bytesRemaining = mStreamLength - streamOffset; - if (bytesRemaining <= 0) { - return NS_ERROR_FAILURE; - } - size = std::min(size, bytesRemaining); - // Clamp size until 64-bit file size issues are fixed. - size = std::min(size, int64_t(INT32_MAX)); + if (!IsOffsetAllowed(streamOffset)) { + LOGE("Stream %p invalid offset=%" PRId64, this, streamOffset); + return NS_ERROR_ILLEGAL_VALUE; } - int32_t bytes; - int32_t channelBlock = OffsetToBlockIndexUnchecked(mChannelOffset); - int32_t cacheBlock = - size_t(streamBlock) < mBlocks.Length() ? mBlocks[streamBlock] : -1; - if (channelBlock == streamBlock && streamOffset < mChannelOffset) { - // We can just use the data in mPartialBlockBuffer. In fact we should - // use it rather than waiting for the block to fill and land in - // the cache. - // Clamp bytes until 64-bit file size issues are fixed. - int64_t toCopy = std::min(size, mChannelOffset - streamOffset); - bytes = std::min(toCopy, int64_t(INT32_MAX)); - MOZ_ASSERT(bytes >= 0 && bytes <= toCopy, "Bytes out of range."); - memcpy(aBuffer + count, - mPartialBlockBuffer.get() + offsetInStreamBlock, bytes); - } else { - if (cacheBlock < 0) { - // We expect all blocks to be cached! Fail! - return NS_ERROR_FAILURE; - } - int64_t offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock; - MOZ_ASSERT(size >= 0 && size <= INT32_MAX, "Size out of range."); - nsresult rv = mMediaCache->ReadCacheFile( - offset, aBuffer + count, int32_t(size), &bytes); - if (NS_FAILED(rv)) { - return rv; - } + Result rv = ReadBlockFromCache(streamOffset, buffer); + if (rv.isErr()) { + return rv.unwrapErr(); } - streamOffset += bytes; - count += bytes; + + uint32_t bytes = rv.unwrap(); + if (bytes > 0) { + // Read data from the cache successfully. Let's try next block. + streamOffset += bytes; + buffer = buffer.From(bytes); + continue; + } + + // The partial block is our last chance to get data. + bytes = ReadPartialBlock(streamOffset, buffer); + if (bytes < buffer.Length()) { + // Not enough data to read. + return NS_ERROR_FAILURE; + } + + // Return for we've got all the requested bytes. + return NS_OK; } return NS_OK; diff --git a/dom/media/MediaCache.h b/dom/media/MediaCache.h index 45a8bd6e3190..7d830ce42c31 100644 --- a/dom/media/MediaCache.h +++ b/dom/media/MediaCache.h @@ -8,6 +8,7 @@ #define MediaCache_h_ #include "Intervals.h" +#include "mozilla/Result.h" #include "mozilla/UniquePtr.h" #include "nsCOMPtr.h" #include "nsHashKeys.h" @@ -314,9 +315,7 @@ public: // in the cache. Will not mark blocks as read. Can be called from the main // thread. It's the caller's responsibility to wrap the call in a pin/unpin, // and also to check that the range they want is cached before calling this. - nsresult ReadFromCache(char* aBuffer, - int64_t aOffset, - int64_t aCount); + nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount); // IsDataCachedToEndOfStream returns true if all the data from // aOffset to the end of the stream (the server-reported end, if the @@ -426,6 +425,16 @@ private: int32_t mCount; }; + // Read data from the partial block and return the number of bytes read + // successfully. 0 if aOffset is not an offset in the partial block or there + // is nothing to read. + uint32_t ReadPartialBlock(int64_t aOffset, Span aBuffer); + + // Read data from the cache block specified by aOffset. Return the number of + // bytes read successfully or an error code if any failure. + Result ReadBlockFromCache(int64_t aOffset, + Span aBuffer); + // Returns the end of the bytes starting at the given offset // which are in cache. // This method assumes that the cache monitor is held and can be called on