tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MediaResource.cpp (16427B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "MediaResource.h"
      8 
      9 #include "mozilla/ErrorNames.h"
     10 #include "mozilla/Logging.h"
     11 #include "mozilla/MathAlgorithms.h"
     12 #include "mozilla/SchedulerGroup.h"
     13 
     14 using mozilla::media::TimeUnit;
     15 
     16 #undef ILOG
     17 
     18 mozilla::LazyLogModule gMediaResourceIndexLog("MediaResourceIndex");
     19 // Debug logging macro with object pointer and class name.
     20 #define ILOG(msg, ...)                                             \
     21  DDMOZ_LOG(gMediaResourceIndexLog, mozilla::LogLevel::Debug, msg, \
     22            ##__VA_ARGS__)
     23 
     24 namespace mozilla {
     25 
     26 static const uint32_t kMediaResourceIndexCacheSize = 8192;
     27 static_assert(IsPowerOfTwo(kMediaResourceIndexCacheSize),
     28              "kMediaResourceIndexCacheSize cache size must be a power of 2");
     29 
     30 MediaResourceIndex::MediaResourceIndex(MediaResource* aResource)
     31    : mResource(aResource),
     32      mOffset(0),
     33      mCacheBlockSize(
     34          aResource->ShouldCacheReads() ? kMediaResourceIndexCacheSize : 0),
     35      mCachedOffset(0),
     36      mCachedBytes(0),
     37      mCachedBlock(MakeUnique<char[]>(mCacheBlockSize)) {
     38  DDLINKCHILD("resource", aResource);
     39 }
     40 
     41 nsresult MediaResourceIndex::Read(char* aBuffer, uint32_t aCount,
     42                                  uint32_t* aBytes) {
     43  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
     44 
     45  // We purposefuly don't check that we may attempt to read past
     46  // mResource->GetLength() as the resource's length may change over time.
     47 
     48  nsresult rv = ReadAt(mOffset, aBuffer, aCount, aBytes);
     49  if (NS_FAILED(rv)) {
     50    return rv;
     51  }
     52  mOffset += *aBytes;
     53  if (mOffset < 0) {
     54    // Very unlikely overflow; just return to position 0.
     55    mOffset = 0;
     56  }
     57  return NS_OK;
     58 }
     59 
     60 static nsCString ResultName(nsresult aResult) {
     61  nsCString name;
     62  GetErrorName(aResult, name);
     63  return name;
     64 }
     65 
     66 nsresult MediaResourceIndex::ReadAt(int64_t aOffset, char* aBuffer,
     67                                    uint32_t aCount, uint32_t* aBytes) {
     68  if (mCacheBlockSize == 0) {
     69    return UncachedReadAt(aOffset, aBuffer, aCount, aBytes);
     70  }
     71 
     72  *aBytes = 0;
     73 
     74  if (aCount == 0) {
     75    return NS_OK;
     76  }
     77 
     78  const int64_t endOffset = aOffset + aCount;
     79  if (aOffset < 0 || endOffset < aOffset) {
     80    return NS_ERROR_ILLEGAL_VALUE;
     81  }
     82 
     83  const int64_t lastBlockOffset = CacheOffsetContaining(endOffset - 1);
     84 
     85  if (mCachedBytes != 0 && mCachedOffset + mCachedBytes >= aOffset &&
     86      mCachedOffset < endOffset) {
     87    // There is data in the cache that is not completely before aOffset and not
     88    // completely after endOffset, so it could be usable (with potential
     89    // top-up).
     90    if (aOffset < mCachedOffset) {
     91      // We need to read before the cached data.
     92      const uint32_t toRead = uint32_t(mCachedOffset - aOffset);
     93      MOZ_ASSERT(toRead > 0);
     94      MOZ_ASSERT(toRead < aCount);
     95      uint32_t read = 0;
     96      nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read);
     97      if (NS_FAILED(rv)) {
     98        ILOG("ReadAt(%" PRIu32 "@%" PRId64
     99             ") uncached read before cache -> %s, %" PRIu32,
    100             aCount, aOffset, ResultName(rv).get(), *aBytes);
    101        return rv;
    102      }
    103      *aBytes = read;
    104      if (read < toRead) {
    105        // Could not read everything we wanted, we're done.
    106        ILOG("ReadAt(%" PRIu32 "@%" PRId64
    107             ") uncached read before cache, incomplete -> OK, %" PRIu32,
    108             aCount, aOffset, *aBytes);
    109        return NS_OK;
    110      }
    111      ILOG("ReadAt(%" PRIu32 "@%" PRId64
    112           ") uncached read before cache: %" PRIu32 ", remaining: %" PRIu32
    113           "@%" PRId64 "...",
    114           aCount, aOffset, read, aCount - read, aOffset + read);
    115      aOffset += read;
    116      aBuffer += read;
    117      aCount -= read;
    118      // We should have reached the cache.
    119      MOZ_ASSERT(aOffset == mCachedOffset);
    120    }
    121    MOZ_ASSERT(aOffset >= mCachedOffset);
    122 
    123    // We've reached our cache.
    124    const uint32_t toCopy =
    125        std::min(aCount, uint32_t(mCachedOffset + mCachedBytes - aOffset));
    126    // Note that we could in fact be just after the last byte of the cache, in
    127    // which case we can't actually read from it! (But we will top-up next.)
    128    if (toCopy != 0) {
    129      memcpy(aBuffer, &mCachedBlock[IndexInCache(aOffset)], toCopy);
    130      *aBytes += toCopy;
    131      aCount -= toCopy;
    132      if (aCount == 0) {
    133        // All done!
    134        ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied everything (%" PRIu32
    135             ") from cache(%" PRIu32 "@%" PRId64 ") :-D -> OK, %" PRIu32,
    136             aCount, aOffset, toCopy, mCachedBytes, mCachedOffset, *aBytes);
    137        return NS_OK;
    138      }
    139      aOffset += toCopy;
    140      aBuffer += toCopy;
    141      ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied %" PRIu32
    142           " from cache(%" PRIu32 "@%" PRId64 ") :-), remaining: %" PRIu32
    143           "@%" PRId64 "...",
    144           aCount + toCopy, aOffset - toCopy, toCopy, mCachedBytes,
    145           mCachedOffset, aCount, aOffset);
    146    }
    147 
    148    if (aOffset - 1 >= lastBlockOffset) {
    149      // We were already reading cached data from the last block, we need more
    150      // from it -> try to top-up, read what we can, and we'll be done.
    151      MOZ_ASSERT(aOffset == mCachedOffset + mCachedBytes);
    152      MOZ_ASSERT(endOffset <= lastBlockOffset + mCacheBlockSize);
    153      return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
    154    }
    155 
    156    // We were not in the last block (but we may just have crossed the line now)
    157    MOZ_ASSERT(aOffset <= lastBlockOffset);
    158    // Continue below...
    159  } else if (aOffset >= lastBlockOffset) {
    160    // There was nothing we could get from the cache.
    161    // But we're already in the last block -> Cache or read what we can.
    162    // Make sure to invalidate the cache first.
    163    mCachedBytes = 0;
    164    return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
    165  }
    166 
    167  // If we're here, either there was nothing usable in the cache, or we've just
    168  // read what was in the cache but there's still more to read.
    169 
    170  if (aOffset < lastBlockOffset) {
    171    // We need to read before the last block.
    172    // Start with an uncached read up to the last block.
    173    const uint32_t toRead = uint32_t(lastBlockOffset - aOffset);
    174    MOZ_ASSERT(toRead > 0);
    175    MOZ_ASSERT(toRead < aCount);
    176    uint32_t read = 0;
    177    nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read);
    178    if (NS_FAILED(rv)) {
    179      ILOG("ReadAt(%" PRIu32 "@%" PRId64
    180           ") uncached read before last block failed -> %s, %" PRIu32,
    181           aCount, aOffset, ResultName(rv).get(), *aBytes);
    182      return rv;
    183    }
    184    if (read == 0) {
    185      ILOG("ReadAt(%" PRIu32 "@%" PRId64
    186           ") uncached read 0 before last block -> OK, %" PRIu32,
    187           aCount, aOffset, *aBytes);
    188      return NS_OK;
    189    }
    190    *aBytes += read;
    191    if (read < toRead) {
    192      // Could not read everything we wanted, we're done.
    193      ILOG("ReadAt(%" PRIu32 "@%" PRId64
    194           ") uncached read before last block, incomplete -> OK, %" PRIu32,
    195           aCount, aOffset, *aBytes);
    196      return NS_OK;
    197    }
    198    ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") read %" PRIu32
    199         " before last block, remaining: %" PRIu32 "@%" PRId64 "...",
    200         aCount, aOffset, read, aCount - read, aOffset + read);
    201    aOffset += read;
    202    aBuffer += read;
    203    aCount -= read;
    204  }
    205 
    206  // We should just have reached the start of the last block.
    207  MOZ_ASSERT(aOffset == lastBlockOffset);
    208  MOZ_ASSERT(aCount <= mCacheBlockSize);
    209  // Make sure to invalidate the cache first.
    210  mCachedBytes = 0;
    211  return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
    212 }
    213 
    214 nsresult MediaResourceIndex::CacheOrReadAt(int64_t aOffset, char* aBuffer,
    215                                           uint32_t aCount, uint32_t* aBytes) {
    216  // We should be here because there is more data to read.
    217  MOZ_ASSERT(aCount > 0);
    218  // We should be in the last block, so we shouldn't try to read past it.
    219  MOZ_ASSERT(IndexInCache(aOffset) + aCount <= mCacheBlockSize);
    220 
    221  const int64_t length = GetLength();
    222  // If length is unknown (-1), look at resource-cached data.
    223  // If length is known and equal or greater than requested, also look at
    224  // resource-cached data.
    225  // Otherwise, if length is known but same, or less than(!?), requested, don't
    226  // attempt to access resource-cached data, as we're not expecting it to ever
    227  // be greater than the length.
    228  if (length < 0 || length >= aOffset + aCount) {
    229    // Is there cached data covering at least the requested range?
    230    const int64_t cachedDataEnd = mResource->GetCachedDataEnd(aOffset);
    231    if (cachedDataEnd >= aOffset + aCount) {
    232      // Try to read as much resource-cached data as can fill our local cache.
    233      // Assume we can read as much as is cached without blocking.
    234      const uint32_t cacheIndex = IndexInCache(aOffset);
    235      const uint32_t toRead = uint32_t(std::min(
    236          cachedDataEnd - aOffset, int64_t(mCacheBlockSize - cacheIndex)));
    237      MOZ_ASSERT(toRead >= aCount);
    238      uint32_t read = 0;
    239      // We would like `toRead` if possible, but ok with at least `aCount`.
    240      nsresult rv = UncachedRangedReadAt(aOffset, &mCachedBlock[cacheIndex],
    241                                         aCount, toRead - aCount, &read);
    242      if (NS_SUCCEEDED(rv)) {
    243        if (read == 0) {
    244          ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
    245               "..%" PRIu32 "@%" PRId64
    246               ") to top-up succeeded but read nothing -> OK anyway",
    247               aCount, aOffset, aCount, toRead, aOffset);
    248          // Couldn't actually read anything, but didn't error out, so count
    249          // that as success.
    250          return NS_OK;
    251        }
    252        if (mCachedOffset + mCachedBytes == aOffset) {
    253          // We were topping-up the cache, just update its size.
    254          ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
    255               "..%" PRIu32 "@%" PRId64 ") to top-up succeeded to read %" PRIu32
    256               "...",
    257               aCount, aOffset, aCount, toRead, aOffset, read);
    258          mCachedBytes += read;
    259        } else {
    260          // We were filling the cache from scratch, save new cache information.
    261          ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
    262               "..%" PRIu32 "@%" PRId64
    263               ") to fill cache succeeded to read %" PRIu32 "...",
    264               aCount, aOffset, aCount, toRead, aOffset, read);
    265          mCachedOffset = aOffset;
    266          mCachedBytes = read;
    267        }
    268        // Copy relevant part into output.
    269        uint32_t toCopy = std::min(aCount, read);
    270        memcpy(aBuffer, &mCachedBlock[cacheIndex], toCopy);
    271        *aBytes += toCopy;
    272        ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - copied %" PRIu32 "@%" PRId64
    273             " -> OK, %" PRIu32,
    274             aCount, aOffset, toCopy, aOffset, *aBytes);
    275        // We may not have read all that was requested, but we got everything
    276        // we could get, so we're done.
    277        return NS_OK;
    278      }
    279      ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
    280           "..%" PRIu32 "@%" PRId64
    281           ") failed: %s, will fallback to blocking read...",
    282           aCount, aOffset, aCount, toRead, aOffset, ResultName(rv).get());
    283      // Failure during reading. Note that this may be due to the cache
    284      // changing between `GetCachedDataEnd` and `ReadAt`, so it's not
    285      // totally unexpected, just hopefully rare; but we do need to handle it.
    286 
    287      // Invalidate part of cache that may have been partially overridden.
    288      if (mCachedOffset + mCachedBytes == aOffset) {
    289        // We were topping-up the cache, just keep the old untouched data.
    290        // (i.e., nothing to do here.)
    291      } else {
    292        // We were filling the cache from scratch, invalidate cache.
    293        mCachedBytes = 0;
    294      }
    295    } else {
    296      ILOG("ReadAt(%" PRIu32 "@%" PRId64
    297           ") - no cached data, will fallback to blocking read...",
    298           aCount, aOffset);
    299    }
    300  } else {
    301    ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - length is %" PRId64
    302         " (%s), will fallback to blocking read as the caller requested...",
    303         aCount, aOffset, length, length < 0 ? "unknown" : "too short!");
    304  }
    305  uint32_t read = 0;
    306  nsresult rv = UncachedReadAt(aOffset, aBuffer, aCount, &read);
    307  if (NS_SUCCEEDED(rv)) {
    308    *aBytes += read;
    309    ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - fallback uncached read got %" PRIu32
    310         " bytes -> %s, %" PRIu32,
    311         aCount, aOffset, read, ResultName(rv).get(), *aBytes);
    312  } else {
    313    ILOG("ReadAt(%" PRIu32 "@%" PRId64
    314         ") - fallback uncached read failed -> %s, %" PRIu32,
    315         aCount, aOffset, ResultName(rv).get(), *aBytes);
    316  }
    317  return rv;
    318 }
    319 
    320 nsresult MediaResourceIndex::UncachedReadAt(int64_t aOffset, char* aBuffer,
    321                                            uint32_t aCount,
    322                                            uint32_t* aBytes) const {
    323  if (aOffset < 0) {
    324    return NS_ERROR_ILLEGAL_VALUE;
    325  }
    326  if (aCount == 0) {
    327    *aBytes = 0;
    328    return NS_OK;
    329  }
    330  return mResource->ReadAt(aOffset, aBuffer, aCount, aBytes);
    331 }
    332 
    333 nsresult MediaResourceIndex::UncachedRangedReadAt(int64_t aOffset,
    334                                                  char* aBuffer,
    335                                                  uint32_t aRequestedCount,
    336                                                  uint32_t aExtraCount,
    337                                                  uint32_t* aBytes) const {
    338  uint32_t count = aRequestedCount + aExtraCount;
    339  if (aOffset < 0 || count < aRequestedCount) {
    340    return NS_ERROR_ILLEGAL_VALUE;
    341  }
    342  if (count == 0) {
    343    *aBytes = 0;
    344    return NS_OK;
    345  }
    346  return mResource->ReadAt(aOffset, aBuffer, count, aBytes);
    347 }
    348 
    349 nsresult MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset) {
    350  switch (aWhence) {
    351    case SEEK_SET:
    352      break;
    353    case SEEK_CUR:
    354      aOffset += mOffset;
    355      break;
    356    case SEEK_END: {
    357      int64_t length = mResource->GetLength();
    358      if (length == -1 || length - aOffset < 0) {
    359        return NS_ERROR_FAILURE;
    360      }
    361      aOffset = mResource->GetLength() - aOffset;
    362    } break;
    363    default:
    364      return NS_ERROR_FAILURE;
    365  }
    366 
    367  if (aOffset < 0) {
    368    return NS_ERROR_ILLEGAL_VALUE;
    369  }
    370  mOffset = aOffset;
    371 
    372  return NS_OK;
    373 }
    374 
    375 already_AddRefed<MediaByteBuffer> MediaResourceIndex::MediaReadAt(
    376    int64_t aOffset, uint32_t aCount) const {
    377  NS_ENSURE_TRUE(aOffset >= 0, nullptr);
    378  RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
    379  bool ok = bytes->SetLength(aCount, fallible);
    380  NS_ENSURE_TRUE(ok, nullptr);
    381 
    382  uint32_t bytesRead = 0;
    383  nsresult rv = mResource->ReadAt(
    384      aOffset, reinterpret_cast<char*>(bytes->Elements()), aCount, &bytesRead);
    385  NS_ENSURE_SUCCESS(rv, nullptr);
    386 
    387  bytes->SetLength(bytesRead);
    388  return bytes.forget();
    389 }
    390 
    391 already_AddRefed<MediaByteBuffer> MediaResourceIndex::CachedMediaReadAt(
    392    int64_t aOffset, uint32_t aCount) const {
    393  RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
    394  bool ok = bytes->SetLength(aCount, fallible);
    395  NS_ENSURE_TRUE(ok, nullptr);
    396  char* curr = reinterpret_cast<char*>(bytes->Elements());
    397  nsresult rv = mResource->ReadFromCache(curr, aOffset, aCount);
    398  NS_ENSURE_SUCCESS(rv, nullptr);
    399  return bytes.forget();
    400 }
    401 
    402 // Get the length of the stream in bytes. Returns -1 if not known.
    403 // This can change over time; after a seek operation, a misbehaving
    404 // server may give us a resource of a different length to what it had
    405 // reported previously --- or it may just lie in its Content-Length
    406 // header and give us more or less data than it reported. We will adjust
    407 // the result of GetLength to reflect the data that's actually arriving.
    408 int64_t MediaResourceIndex::GetLength() const { return mResource->GetLength(); }
    409 
    410 uint32_t MediaResourceIndex::IndexInCache(int64_t aOffsetInFile) const {
    411  const uint32_t index = uint32_t(aOffsetInFile) & (mCacheBlockSize - 1);
    412  MOZ_ASSERT(index == aOffsetInFile % mCacheBlockSize);
    413  return index;
    414 }
    415 
    416 int64_t MediaResourceIndex::CacheOffsetContaining(int64_t aOffsetInFile) const {
    417  const int64_t offset = aOffsetInFile & ~(int64_t(mCacheBlockSize) - 1);
    418  MOZ_ASSERT(offset == aOffsetInFile - IndexInCache(aOffsetInFile));
    419  return offset;
    420 }
    421 
    422 }  // namespace mozilla
    423 
    424 // avoid redefined macro in unified build
    425 #undef ILOG