tor-browser

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

WebMBufferedParser.cpp (24701B)


      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 "WebMBufferedParser.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "MediaDataDemuxer.h"
     12 #include "mozilla/CheckedInt.h"
     13 #include "nsThreadUtils.h"
     14 
     15 #define WEBM_DEBUG(arg, ...)                          \
     16  MOZ_LOG(gMediaDemuxerLog, mozilla::LogLevel::Debug, \
     17          ("WebMBufferedParser(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
     18 
     19 namespace mozilla {
     20 
     21 static uint32_t VIntLength(unsigned char aFirstByte, uint32_t* aMask) {
     22  uint32_t count = 1;
     23  uint32_t mask = 1 << 7;
     24  while (count < 8) {
     25    if ((aFirstByte & mask) != 0) {
     26      break;
     27    }
     28    mask >>= 1;
     29    count += 1;
     30  }
     31  if (aMask) {
     32    *aMask = mask;
     33  }
     34  NS_ASSERTION(count >= 1 && count <= 8, "Insane VInt length.");
     35  return count;
     36 }
     37 
     38 constexpr uint8_t EBML_MAX_ID_LENGTH_DEFAULT = 4;
     39 constexpr uint8_t EBML_MAX_SIZE_LENGTH_DEFAULT = 8;
     40 
     41 WebMBufferedParser::WebMBufferedParser(int64_t aOffset)
     42    : mStartOffset(aOffset),
     43      mCurrentOffset(aOffset),
     44      mInitEndOffset(-1),
     45      mBlockEndOffset(-1),
     46      mState(READ_ELEMENT_ID),
     47      mNextState(READ_ELEMENT_ID),
     48      mVIntRaw(false),
     49      mLastInitStartOffset(-1),
     50      mLastInitSize(0),
     51      mEBMLMaxIdLength(EBML_MAX_ID_LENGTH_DEFAULT),
     52      mEBMLMaxSizeLength(EBML_MAX_SIZE_LENGTH_DEFAULT),
     53      mClusterSyncPos(0),
     54      mVIntLeft(0),
     55      mBlockSize(0),
     56      mClusterTimecode(0),
     57      mClusterOffset(-1),
     58      mClusterEndOffset(-1),
     59      mBlockOffset(0),
     60      mBlockTimecode(0),
     61      mBlockTimecodeLength(0),
     62      mSkipBytes(0),
     63      mTimecodeScale(1000000),
     64      mGotTimecodeScale(false),
     65      mGotClusterTimecode(false) {
     66  if (mStartOffset != 0) {
     67    mState = FIND_CLUSTER_SYNC;
     68  }
     69 }
     70 
     71 void WebMBufferedParser::SetTimecodeScale(uint32_t aTimecodeScale) {
     72  mTimecodeScale = aTimecodeScale;
     73  WEBM_DEBUG("%" PRIu32, mTimecodeScale);
     74  mGotTimecodeScale = true;
     75 }
     76 
     77 MediaResult WebMBufferedParser::Append(const unsigned char* aBuffer,
     78                                       uint32_t aLength,
     79                                       nsTArray<WebMTimeDataOffset>& aMapping) {
     80  static const uint32_t EBML_ID = 0x1a45dfa3;
     81  static const uint32_t SEGMENT_ID = 0x18538067;
     82  static const uint32_t SEGINFO_ID = 0x1549a966;
     83  static const uint32_t TRACKS_ID = 0x1654AE6B;
     84  static const uint32_t CLUSTER_ID = 0x1f43b675;
     85  static const uint32_t TIMECODESCALE_ID = 0x2ad7b1;
     86  static const unsigned char TIMECODE_ID = 0xe7;
     87  static const unsigned char BLOCKGROUP_ID = 0xa0;
     88  static const unsigned char BLOCK_ID = 0xa1;
     89  static const unsigned char SIMPLEBLOCK_ID = 0xa3;
     90  static const uint16_t EBML_MAX_ID_LENGTH_ID = 0x42f2;
     91  static const uint16_t EBML_MAX_SIZE_LENGTH_ID = 0x42f3;
     92  static const uint32_t BLOCK_TIMECODE_LENGTH = 2;
     93 
     94  static const unsigned char CLUSTER_SYNC_ID[] = {0x1f, 0x43, 0xb6, 0x75};
     95 
     96  const unsigned char* p = aBuffer;
     97 
     98  // Parse each byte in aBuffer one-by-one, producing timecodes and updating
     99  // aMapping as we go.  Parser pauses at end of stream (which may be at any
    100  // point within the parse) and resumes parsing the next time Append is
    101  // called with new data.
    102  while (p < aBuffer + aLength) {
    103    switch (mState) {
    104      case READ_ELEMENT_ID:
    105        mVIntRaw = true;
    106        mState = READ_VINT;
    107        mNextState = READ_ELEMENT_SIZE;
    108        break;
    109      case READ_ELEMENT_SIZE:
    110        if (mVInt.mLength > mEBMLMaxIdLength) {
    111          nsPrintfCString detail("Invalid element id of length %" PRIu64,
    112                                 mVInt.mLength);
    113          WEBM_DEBUG("%s", detail.get());
    114          return MediaResult(NS_ERROR_FAILURE, detail);
    115        }
    116        mVIntRaw = false;
    117        mElement.mID = mVInt;
    118        mState = READ_VINT;
    119        mNextState = PARSE_ELEMENT;
    120        break;
    121      case FIND_CLUSTER_SYNC:
    122        if (*p++ == CLUSTER_SYNC_ID[mClusterSyncPos]) {
    123          mClusterSyncPos += 1;
    124        } else {
    125          mClusterSyncPos = 0;
    126        }
    127        if (mClusterSyncPos == sizeof(CLUSTER_SYNC_ID)) {
    128          mVInt.mValue = CLUSTER_ID;
    129          mVInt.mLength = sizeof(CLUSTER_SYNC_ID);
    130          mState = READ_ELEMENT_SIZE;
    131        }
    132        break;
    133      case PARSE_ELEMENT:
    134        if (mVInt.mLength > mEBMLMaxSizeLength) {
    135          nsPrintfCString detail("Invalid element size of length %" PRIu64,
    136                                 mVInt.mLength);
    137          WEBM_DEBUG("%s", detail.get());
    138          return MediaResult(NS_ERROR_FAILURE, detail);
    139        }
    140        mElement.mSize = mVInt;
    141        switch (mElement.mID.mValue) {
    142          case SEGMENT_ID:
    143            mState = READ_ELEMENT_ID;
    144            break;
    145          case SEGINFO_ID:
    146            mGotTimecodeScale = true;
    147            mState = READ_ELEMENT_ID;
    148            break;
    149          case TIMECODE_ID:
    150            mVInt = VInt();
    151            mVIntLeft = mElement.mSize.mValue;
    152            mState = READ_VINT_REST;
    153            mNextState = READ_CLUSTER_TIMECODE;
    154            break;
    155          case TIMECODESCALE_ID:
    156            mVInt = VInt();
    157            mVIntLeft = mElement.mSize.mValue;
    158            mState = READ_VINT_REST;
    159            mNextState = READ_TIMECODESCALE;
    160            break;
    161          case CLUSTER_ID:
    162            mClusterOffset = mCurrentOffset + (p - aBuffer) -
    163                             (mElement.mID.mLength + mElement.mSize.mLength);
    164            // Handle "unknown" length;
    165            if (mElement.mSize.mValue + 1 !=
    166                uint64_t(1) << (mElement.mSize.mLength * 7)) {
    167              mClusterEndOffset = mClusterOffset + mElement.mID.mLength +
    168                                  mElement.mSize.mLength +
    169                                  mElement.mSize.mValue;
    170            } else {
    171              mClusterEndOffset = -1;
    172            }
    173            mGotClusterTimecode = false;
    174            mState = READ_ELEMENT_ID;
    175            break;
    176          case BLOCKGROUP_ID:
    177            mState = READ_ELEMENT_ID;
    178            break;
    179          case SIMPLEBLOCK_ID:
    180            /* FALLTHROUGH */
    181          case BLOCK_ID:
    182            if (!mGotClusterTimecode) {
    183              WEBM_DEBUG(
    184                  "The Timecode element must appear before any Block or "
    185                  "SimpleBlock elements in a Cluster");
    186              return MediaResult(
    187                  NS_ERROR_FAILURE,
    188                  "The Timecode element must appear before any Block or "
    189                  "SimpleBlock elements in a Cluster");
    190            }
    191            mBlockSize = mElement.mSize.mValue;
    192            mBlockTimecode = 0;
    193            mBlockTimecodeLength = BLOCK_TIMECODE_LENGTH;
    194            mBlockOffset = mCurrentOffset + (p - aBuffer) -
    195                           (mElement.mID.mLength + mElement.mSize.mLength);
    196            mState = READ_VINT;
    197            mNextState = READ_BLOCK_TIMECODE;
    198            break;
    199          case TRACKS_ID:
    200            mSkipBytes = mElement.mSize.mValue;
    201            mState = CHECK_INIT_FOUND;
    202            break;
    203          case EBML_MAX_ID_LENGTH_ID:
    204          case EBML_MAX_SIZE_LENGTH_ID:
    205            if (int64_t currentOffset = mCurrentOffset + (p - aBuffer);
    206                currentOffset < mLastInitStartOffset ||
    207                currentOffset >= mLastInitStartOffset + mLastInitSize) {
    208              nsPrintfCString str("Unexpected %s outside init segment",
    209                                  mElement.mID.mValue == EBML_MAX_ID_LENGTH_ID
    210                                      ? "EBMLMaxIdLength"
    211                                      : "EBMLMaxSizeLength");
    212              WEBM_DEBUG("%s", str.get());
    213              return MediaResult(NS_ERROR_FAILURE, str);
    214            }
    215            if (mElement.mSize.mValue > 8) {
    216              // https://www.rfc-editor.org/rfc/rfc8794.html (EBML):
    217              //   An Unsigned Integer Element MUST declare a length from zero
    218              //   to eight octets.
    219              nsPrintfCString str("Bad length of %s size",
    220                                  mElement.mID.mValue == EBML_MAX_ID_LENGTH_ID
    221                                      ? "EBMLMaxIdLength"
    222                                      : "EBMLMaxSizeLength");
    223              WEBM_DEBUG("%s", str.get());
    224              return MediaResult(NS_ERROR_FAILURE, str);
    225            }
    226            mVInt = VInt();
    227            mVIntLeft = mElement.mSize.mValue;
    228            mState = READ_VINT_REST;
    229            mNextState = mElement.mID.mValue == EBML_MAX_ID_LENGTH_ID
    230                             ? READ_EBML_MAX_ID_LENGTH
    231                             : READ_EBML_MAX_SIZE_LENGTH;
    232            break;
    233          case EBML_ID:
    234            mLastInitStartOffset =
    235                mCurrentOffset + (p - aBuffer) -
    236                (mElement.mID.mLength + mElement.mSize.mLength);
    237            mLastInitSize = mElement.mSize.mValue;
    238            mEBMLMaxIdLength = EBML_MAX_ID_LENGTH_DEFAULT;
    239            mEBMLMaxSizeLength = EBML_MAX_SIZE_LENGTH_DEFAULT;
    240            mState = READ_ELEMENT_ID;
    241            break;
    242          default:
    243            mSkipBytes = mElement.mSize.mValue;
    244            mState = SKIP_DATA;
    245            mNextState = READ_ELEMENT_ID;
    246            break;
    247        }
    248        break;
    249      case READ_VINT: {
    250        unsigned char c = *p++;
    251        uint32_t mask;
    252        mVInt.mLength = VIntLength(c, &mask);
    253        mVIntLeft = mVInt.mLength - 1;
    254        mVInt.mValue = mVIntRaw ? c : c & ~mask;
    255        mState = READ_VINT_REST;
    256        break;
    257      }
    258      case READ_VINT_REST:
    259        if (mVIntLeft) {
    260          mVInt.mValue <<= 8;
    261          mVInt.mValue |= *p++;
    262          mVIntLeft -= 1;
    263        } else {
    264          mState = mNextState;
    265        }
    266        break;
    267      case READ_TIMECODESCALE:
    268        if (!mGotTimecodeScale) {
    269          WEBM_DEBUG("Should get the SegmentInfo first");
    270          return MediaResult(NS_ERROR_FAILURE,
    271                             "TimecodeScale appeared before SegmentInfo");
    272        }
    273        mTimecodeScale = mVInt.mValue;
    274        WEBM_DEBUG("READ_TIMECODESCALE %" PRIu32, mTimecodeScale);
    275        mState = READ_ELEMENT_ID;
    276        break;
    277      case READ_CLUSTER_TIMECODE:
    278        mClusterTimecode = mVInt.mValue;
    279        mGotClusterTimecode = true;
    280        mState = READ_ELEMENT_ID;
    281        break;
    282      case READ_BLOCK_TIMECODE:
    283        if (mBlockTimecodeLength) {
    284          mBlockTimecode <<= 8;
    285          mBlockTimecode |= *p++;
    286          mBlockTimecodeLength -= 1;
    287        } else {
    288          // It's possible we've parsed this data before, so avoid inserting
    289          // duplicate WebMTimeDataOffset entries.
    290          {
    291            int64_t endOffset = mBlockOffset + mBlockSize +
    292                                mElement.mID.mLength + mElement.mSize.mLength;
    293            uint32_t idx = aMapping.IndexOfFirstElementGt(endOffset);
    294            if (idx == 0 || aMapping[idx - 1] != endOffset) {
    295              // Don't insert invalid negative timecodes.
    296              if (mBlockTimecode >= 0 ||
    297                  mClusterTimecode >= uint16_t(abs(mBlockTimecode))) {
    298                if (!mGotTimecodeScale) {
    299                  WEBM_DEBUG("Should get the TimecodeScale first");
    300                  return MediaResult(NS_ERROR_FAILURE,
    301                                     "Timecode appeared before SegmentInfo");
    302                }
    303                uint64_t absTimecode = mClusterTimecode + mBlockTimecode;
    304                absTimecode *= mTimecodeScale;
    305                // Avoid creating an entry if the timecode is out of order
    306                // (invalid according to the WebM specification) so that
    307                // ordering invariants of aMapping are not violated.
    308                if (idx == 0 || aMapping[idx - 1].mTimecode <= absTimecode ||
    309                    (idx + 1 < aMapping.Length() &&
    310                     aMapping[idx + 1].mTimecode >= absTimecode)) {
    311                  WebMTimeDataOffset entry(endOffset, absTimecode,
    312                                           mLastInitStartOffset, mClusterOffset,
    313                                           mClusterEndOffset);
    314                  aMapping.InsertElementAt(idx, entry);
    315                } else {
    316                  WEBM_DEBUG("Out of order timecode %" PRIu64
    317                             " in Cluster at %" PRId64 " ignored",
    318                             absTimecode, mClusterOffset);
    319                }
    320              }
    321            }
    322          }
    323 
    324          // Skip rest of block header and the block's payload.
    325          mBlockSize -= mVInt.mLength;
    326          mBlockSize -= BLOCK_TIMECODE_LENGTH;
    327          mSkipBytes = uint32_t(mBlockSize);
    328          mState = SKIP_DATA;
    329          mNextState = READ_ELEMENT_ID;
    330        }
    331        break;
    332      case READ_EBML_MAX_ID_LENGTH:
    333        if (mElement.mSize.mLength == 0) {
    334          // https://www.rfc-editor.org/rfc/rfc8794.html (EBML):
    335          //   If an Empty Element has a default value declared, then the EBML
    336          //   Reader MUST interpret the value of the Empty Element as the
    337          //   default value.
    338          mVInt.mValue = EBML_MAX_ID_LENGTH_DEFAULT;
    339        }
    340        if (mVInt.mValue < 4 || mVInt.mValue > 5) {
    341          // https://www.ietf.org/archive/id/draft-ietf-cellar-matroska-13.html
    342          // (Matroska):
    343          //   The EBMLMaxIDLength of the EBML Header MUST be "4".
    344          //
    345          // Also Matroska:
    346          //   Element IDs are encoded using the VINT mechanism described in
    347          //   Section 4 of [RFC8794] and can be between one and five octets
    348          //   long. Five-octet-long Element IDs are possible only if declared
    349          //   in the EBML header.
    350          nsPrintfCString detail("Invalid EMBLMaxIdLength %" PRIu64,
    351                                 mVInt.mValue);
    352          WEBM_DEBUG("%s", detail.get());
    353          return MediaResult(NS_ERROR_FAILURE, detail);
    354        }
    355        mEBMLMaxIdLength = mVInt.mValue;
    356        mState = READ_ELEMENT_ID;
    357        break;
    358      case READ_EBML_MAX_SIZE_LENGTH:
    359        if (mElement.mSize.mLength == 0) {
    360          // https://www.rfc-editor.org/rfc/rfc8794.html (EBML):
    361          //   If an Empty Element has a default value declared, then the EBML
    362          //   Reader MUST interpret the value of the Empty Element as the
    363          //   default value.
    364          mVInt.mValue = EBML_MAX_SIZE_LENGTH_DEFAULT;
    365        }
    366        if (mVInt.mValue < 1 || mVInt.mValue > 8) {
    367          // https://www.ietf.org/archive/id/draft-ietf-cellar-matroska-13.html
    368          // (Matroska):
    369          //   The EBMLMaxSizeLength of the EBML Header MUST be between "1" and
    370          //   "8" inclusive.
    371          nsPrintfCString detail("Invalid EMBLMaxSizeLength %" PRIu64,
    372                                 mVInt.mValue);
    373          WEBM_DEBUG("%s", detail.get());
    374          return MediaResult(NS_ERROR_FAILURE, detail);
    375        }
    376        mEBMLMaxSizeLength = mVInt.mValue;
    377        mState = READ_ELEMENT_ID;
    378        break;
    379      case SKIP_DATA:
    380        if (mSkipBytes) {
    381          uint32_t left = aLength - (p - aBuffer);
    382          left = std::min(left, mSkipBytes);
    383          p += left;
    384          mSkipBytes -= left;
    385        }
    386        if (!mSkipBytes) {
    387          mBlockEndOffset = mCurrentOffset + (p - aBuffer);
    388          mState = mNextState;
    389        }
    390        break;
    391      case CHECK_INIT_FOUND:
    392        if (mSkipBytes) {
    393          uint32_t left = aLength - (p - aBuffer);
    394          left = std::min(left, mSkipBytes);
    395          p += left;
    396          mSkipBytes -= left;
    397        }
    398        if (!mSkipBytes) {
    399          if (mInitEndOffset < 0) {
    400            mInitEndOffset = mCurrentOffset + (p - aBuffer);
    401            mBlockEndOffset = mCurrentOffset + (p - aBuffer);
    402          }
    403          mState = READ_ELEMENT_ID;
    404        }
    405        break;
    406    }
    407  }
    408 
    409  NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data.");
    410  mCurrentOffset += aLength;
    411 
    412  return NS_OK;
    413 }
    414 
    415 int64_t WebMBufferedParser::EndSegmentOffset(int64_t aOffset) {
    416  if (mLastInitStartOffset > aOffset || mClusterOffset > aOffset) {
    417    return std::min(
    418        mLastInitStartOffset >= 0 ? mLastInitStartOffset : INT64_MAX,
    419        mClusterOffset >= 0 ? mClusterOffset : INT64_MAX);
    420  }
    421  return mBlockEndOffset;
    422 }
    423 
    424 int64_t WebMBufferedParser::GetClusterOffset() const { return mClusterOffset; }
    425 
    426 // SyncOffsetComparator and TimeComparator are slightly confusing, in that
    427 // the nsTArray they're used with (mTimeMapping) is sorted by mEndOffset and
    428 // these comparators are used on the other fields of WebMTimeDataOffset.
    429 // This is only valid because timecodes are required to be monotonically
    430 // increasing within a file (thus establishing an ordering relationship with
    431 // mTimecode), and mEndOffset is derived from mSyncOffset.
    432 struct SyncOffsetComparator {
    433  bool Equals(const WebMTimeDataOffset& a, const int64_t& b) const {
    434    return a.mSyncOffset == b;
    435  }
    436 
    437  bool LessThan(const WebMTimeDataOffset& a, const int64_t& b) const {
    438    return a.mSyncOffset < b;
    439  }
    440 };
    441 
    442 struct TimeComparator {
    443  bool Equals(const WebMTimeDataOffset& a, const uint64_t& b) const {
    444    return a.mTimecode == b;
    445  }
    446 
    447  bool LessThan(const WebMTimeDataOffset& a, const uint64_t& b) const {
    448    return a.mTimecode < b;
    449  }
    450 };
    451 
    452 bool WebMBufferedState::CalculateBufferedForRange(int64_t aStartOffset,
    453                                                  int64_t aEndOffset,
    454                                                  uint64_t* aStartTime,
    455                                                  uint64_t* aEndTime) {
    456  MutexAutoLock lock(mMutex);
    457 
    458  // Find the first WebMTimeDataOffset at or after aStartOffset.
    459  uint32_t start = mTimeMapping.IndexOfFirstElementGt(aStartOffset - 1,
    460                                                      SyncOffsetComparator());
    461  if (start == mTimeMapping.Length()) {
    462    return false;
    463  }
    464 
    465  // Find the first WebMTimeDataOffset at or before aEndOffset.
    466  uint32_t end = mTimeMapping.IndexOfFirstElementGt(aEndOffset);
    467  if (end > 0) {
    468    end -= 1;
    469  }
    470 
    471  // Range is empty.
    472  if (end <= start) {
    473    return false;
    474  }
    475 
    476  NS_ASSERTION(mTimeMapping[start].mSyncOffset >= aStartOffset &&
    477                   mTimeMapping[end].mEndOffset <= aEndOffset,
    478               "Computed time range must lie within data range.");
    479  if (start > 0) {
    480    NS_ASSERTION(mTimeMapping[start - 1].mSyncOffset < aStartOffset,
    481                 "Must have found least WebMTimeDataOffset for start");
    482  }
    483  if (end < mTimeMapping.Length() - 1) {
    484    NS_ASSERTION(mTimeMapping[end + 1].mEndOffset > aEndOffset,
    485                 "Must have found greatest WebMTimeDataOffset for end");
    486  }
    487 
    488  MOZ_ASSERT(mTimeMapping[end].mTimecode >= mTimeMapping[end - 1].mTimecode);
    489  uint64_t frameDuration =
    490      mTimeMapping[end].mTimecode - mTimeMapping[end - 1].mTimecode;
    491  *aStartTime = mTimeMapping[start].mTimecode;
    492  CheckedUint64 endTime{mTimeMapping[end].mTimecode};
    493  endTime += frameDuration;
    494  if (!endTime.isValid()) {
    495    WEBM_DEBUG("End time overflow during CalculateBufferedForRange.");
    496    return false;
    497  }
    498  *aEndTime = endTime.value();
    499  return true;
    500 }
    501 
    502 bool WebMBufferedState::GetOffsetForTime(uint64_t aTime, int64_t* aOffset) {
    503  MutexAutoLock lock(mMutex);
    504 
    505  if (mTimeMapping.IsEmpty()) {
    506    return false;
    507  }
    508 
    509  uint64_t time = aTime;
    510  if (time > 0) {
    511    time = time - 1;
    512  }
    513  uint32_t idx = mTimeMapping.IndexOfFirstElementGt(time, TimeComparator());
    514  if (idx == mTimeMapping.Length()) {
    515    // Clamp to end
    516    *aOffset = mTimeMapping[mTimeMapping.Length() - 1].mSyncOffset;
    517  } else {
    518    // Idx is within array or has been clamped to start
    519    *aOffset = mTimeMapping[idx].mSyncOffset;
    520  }
    521  return true;
    522 }
    523 
    524 void WebMBufferedState::NotifyDataArrived(const unsigned char* aBuffer,
    525                                          uint32_t aLength, int64_t aOffset) {
    526  uint32_t idx = mRangeParsers.IndexOfFirstElementGt(aOffset - 1);
    527  if (idx == 0 || !(mRangeParsers[idx - 1] == aOffset)) {
    528    // If the incoming data overlaps an already parsed range, adjust the
    529    // buffer so that we only reparse the new data.  It's also possible to
    530    // have an overlap where the end of the incoming data is within an
    531    // already parsed range, but we don't bother handling that other than by
    532    // avoiding storing duplicate timecodes when the parser runs.
    533    if (idx != mRangeParsers.Length() &&
    534        mRangeParsers[idx].mStartOffset <= aOffset) {
    535      // Complete overlap, skip parsing.
    536      if (aOffset + aLength <= mRangeParsers[idx].mCurrentOffset) {
    537        return;
    538      }
    539 
    540      // Partial overlap, adjust the buffer to parse only the new data.
    541      int64_t adjust = mRangeParsers[idx].mCurrentOffset - aOffset;
    542      NS_ASSERTION(adjust >= 0, "Overlap detection bug.");
    543      aBuffer += adjust;
    544      aLength -= uint32_t(adjust);
    545    } else {
    546      mRangeParsers.InsertElementAt(idx, WebMBufferedParser(aOffset));
    547      if (idx != 0) {
    548        mRangeParsers[idx].SetTimecodeScale(
    549            mRangeParsers[0].GetTimecodeScale());
    550      }
    551    }
    552  }
    553 
    554  {
    555    MutexAutoLock lock(mMutex);
    556    mRangeParsers[idx].Append(aBuffer, aLength, mTimeMapping);
    557  }
    558 
    559  // Merge parsers with overlapping regions and clean up the remnants.
    560  uint32_t i = 0;
    561  while (i + 1 < mRangeParsers.Length()) {
    562    if (mRangeParsers[i].mCurrentOffset >= mRangeParsers[i + 1].mStartOffset) {
    563      mRangeParsers[i + 1].mStartOffset = mRangeParsers[i].mStartOffset;
    564      mRangeParsers[i + 1].mInitEndOffset = mRangeParsers[i].mInitEndOffset;
    565      mRangeParsers.RemoveElementAt(i);
    566    } else {
    567      i += 1;
    568    }
    569  }
    570 }
    571 
    572 void WebMBufferedState::Reset() {
    573  MutexAutoLock lock(mMutex);
    574  mRangeParsers.Clear();
    575  mTimeMapping.Clear();
    576 }
    577 
    578 void WebMBufferedState::UpdateIndex(const MediaByteRangeSet& aRanges,
    579                                    MediaResource* aResource) {
    580  for (uint32_t index = 0; index < aRanges.Length(); index++) {
    581    const MediaByteRange& range = aRanges[index];
    582    int64_t offset = range.mStart;
    583    uint32_t length = range.mEnd - range.mStart;
    584 
    585    uint32_t idx = mRangeParsers.IndexOfFirstElementGt(offset - 1);
    586    if (!idx || !(mRangeParsers[idx - 1] == offset)) {
    587      // If the incoming data overlaps an already parsed range, adjust the
    588      // buffer so that we only reparse the new data.  It's also possible to
    589      // have an overlap where the end of the incoming data is within an
    590      // already parsed range, but we don't bother handling that other than by
    591      // avoiding storing duplicate timecodes when the parser runs.
    592      if (idx != mRangeParsers.Length() &&
    593          mRangeParsers[idx].mStartOffset <= offset) {
    594        // Complete overlap, skip parsing.
    595        if (offset + length <= mRangeParsers[idx].mCurrentOffset) {
    596          continue;
    597        }
    598 
    599        // Partial overlap, adjust the buffer to parse only the new data.
    600        int64_t adjust = mRangeParsers[idx].mCurrentOffset - offset;
    601        NS_ASSERTION(adjust >= 0, "Overlap detection bug.");
    602        offset += adjust;
    603        length -= uint32_t(adjust);
    604      } else {
    605        mRangeParsers.InsertElementAt(idx, WebMBufferedParser(offset));
    606        if (idx) {
    607          mRangeParsers[idx].SetTimecodeScale(
    608              mRangeParsers[0].GetTimecodeScale());
    609        }
    610      }
    611    }
    612 
    613    MediaResourceIndex res(aResource);
    614    while (length > 0) {
    615      static const uint32_t BLOCK_SIZE = 1048576;
    616      uint32_t block = std::min(length, BLOCK_SIZE);
    617      RefPtr<MediaByteBuffer> bytes = res.CachedMediaReadAt(offset, block);
    618      if (!bytes) {
    619        break;
    620      }
    621      NotifyDataArrived(bytes->Elements(), bytes->Length(), offset);
    622      length -= bytes->Length();
    623      offset += bytes->Length();
    624    }
    625  }
    626 }
    627 
    628 int64_t WebMBufferedState::GetInitEndOffset() {
    629  if (mRangeParsers.IsEmpty()) {
    630    return -1;
    631  }
    632  return mRangeParsers[0].mInitEndOffset;
    633 }
    634 
    635 bool WebMBufferedState::GetStartTime(uint64_t* aTime) {
    636  MutexAutoLock lock(mMutex);
    637 
    638  if (mTimeMapping.IsEmpty()) {
    639    return false;
    640  }
    641 
    642  uint32_t idx = mTimeMapping.IndexOfFirstElementGt(0, SyncOffsetComparator());
    643  if (idx == mTimeMapping.Length()) {
    644    return false;
    645  }
    646 
    647  *aTime = mTimeMapping[idx].mTimecode;
    648  return true;
    649 }
    650 
    651 bool WebMBufferedState::GetNextKeyframeTime(uint64_t aTime,
    652                                            uint64_t* aKeyframeTime) {
    653  MutexAutoLock lock(mMutex);
    654  int64_t offset = 0;
    655  bool rv = GetOffsetForTime(aTime, &offset);
    656  if (!rv) {
    657    return false;
    658  }
    659  uint32_t idx =
    660      mTimeMapping.IndexOfFirstElementGt(offset, SyncOffsetComparator());
    661  if (idx == mTimeMapping.Length()) {
    662    return false;
    663  }
    664  *aKeyframeTime = mTimeMapping[idx].mTimecode;
    665  return true;
    666 }
    667 }  // namespace mozilla
    668 
    669 #undef WEBM_DEBUG