tor-browser

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

OggDemuxer.cpp (78423B)


      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 "OggDemuxer.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "MediaDataDemuxer.h"
     12 #include "OggCodecState.h"
     13 #include "OggRLBox.h"
     14 #include "TimeUnits.h"
     15 #include "XiphExtradata.h"
     16 #include "mozilla/AbstractThread.h"
     17 #include "mozilla/Atomics.h"
     18 #include "mozilla/SchedulerGroup.h"
     19 #include "mozilla/ScopeExit.h"
     20 #include "mozilla/SharedThreadPool.h"
     21 #include "mozilla/TimeStamp.h"
     22 #include "nsAutoRef.h"
     23 #include "nsDebug.h"
     24 #include "nsError.h"
     25 
     26 #define OGG_DEBUG(arg, ...)                                           \
     27  DDMOZ_LOG(gMediaDemuxerLog, mozilla::LogLevel::Debug, "::%s: " arg, \
     28            __func__, ##__VA_ARGS__)
     29 
     30 // Un-comment to enable logging of seek bisections.
     31 // #define SEEK_LOGGING
     32 #ifdef SEEK_LOGGING
     33 #  define SEEK_LOG(type, msg) MOZ_LOG(gMediaDemuxerLog, type, msg)
     34 #else
     35 #  define SEEK_LOG(type, msg)
     36 #endif
     37 
     38 #define CopyAndVerifyOrFail(t, cond, failed) \
     39  (t).copy_and_verify([&](auto val) {        \
     40    if (!(cond)) {                           \
     41      *(failed) = true;                      \
     42    }                                        \
     43    return val;                              \
     44  })
     45 
     46 namespace mozilla {
     47 
     48 using media::TimeInterval;
     49 using media::TimeIntervals;
     50 using media::TimeUnit;
     51 
     52 // The number of microseconds of "fuzz" we use in a bisection search over
     53 // HTTP. When we're seeking with fuzz, we'll stop the search if a bisection
     54 // lands between the seek target and OGG_SEEK_FUZZ_USECS microseconds before the
     55 // seek target.  This is becaue it's usually quicker to just keep downloading
     56 // from an exisiting connection than to do another bisection inside that
     57 // small range, which would open a new HTTP connetion.
     58 static const TimeUnit OGG_SEEK_FUZZ_USECS = TimeUnit::FromMicroseconds(500000);
     59 
     60 // The number of microseconds of "pre-roll" we use for Opus streams.
     61 // The specification recommends 80 ms.
     62 static const TimeUnit OGG_SEEK_OPUS_PREROLL = TimeUnit::FromMicroseconds(80000);
     63 
     64 static Atomic<uint32_t> sStreamSourceID(0u);
     65 
     66 OggDemuxer::nsAutoOggSyncState::nsAutoOggSyncState(rlbox_sandbox_ogg* aSandbox)
     67    : mSandbox(aSandbox) {
     68  if (mSandbox) {
     69    tainted_ogg<ogg_sync_state*> state =
     70        mSandbox->malloc_in_sandbox<ogg_sync_state>();
     71    MOZ_RELEASE_ASSERT(state != nullptr);
     72    mState = state.to_opaque();
     73    sandbox_invoke(*mSandbox, ogg_sync_init, mState);
     74  }
     75 }
     76 OggDemuxer::nsAutoOggSyncState::~nsAutoOggSyncState() {
     77  if (mSandbox) {
     78    sandbox_invoke(*mSandbox, ogg_sync_clear, mState);
     79    mSandbox->free_in_sandbox(rlbox::from_opaque(mState));
     80    tainted_ogg<ogg_sync_state*> null = nullptr;
     81    mState = null.to_opaque();
     82  }
     83 }
     84 
     85 /* static */
     86 rlbox_sandbox_ogg* OggDemuxer::CreateSandbox() {
     87  rlbox_sandbox_ogg* sandbox = new rlbox_sandbox_ogg();
     88 #ifdef MOZ_WASM_SANDBOXING_OGG
     89  bool success = sandbox->create_sandbox(/* shouldAbortOnFailure = */ false,
     90                                         /* custom capacity = */ nullptr,
     91                                         "rlbox_wasm2c_ogg");
     92 #else
     93  bool success = sandbox->create_sandbox();
     94 #endif
     95  if (!success) {
     96    delete sandbox;
     97    sandbox = nullptr;
     98  }
     99  return sandbox;
    100 }
    101 
    102 void OggDemuxer::SandboxDestroy::operator()(rlbox_sandbox_ogg* sandbox) {
    103  if (sandbox) {
    104    sandbox->destroy_sandbox();
    105    delete sandbox;
    106  }
    107 }
    108 
    109 // Return the corresponding category in aKind based on the following specs.
    110 // (https://www.whatwg.org/specs/web-apps/current-
    111 // work/multipage/embedded-content.html#dom-audiotrack-kind) &
    112 // (http://wiki.xiph.org/SkeletonHeaders)
    113 nsString OggDemuxer::GetKind(const nsCString& aRole) {
    114  if (aRole.Find("audio/main") != -1 || aRole.Find("video/main") != -1) {
    115    return u"main"_ns;
    116  }
    117  if (aRole.Find("audio/alternate") != -1 ||
    118      aRole.Find("video/alternate") != -1) {
    119    return u"alternative"_ns;
    120  }
    121  if (aRole.Find("audio/audiodesc") != -1) {
    122    return u"descriptions"_ns;
    123  }
    124  if (aRole.Find("audio/described") != -1) {
    125    return u"main-desc"_ns;
    126  }
    127  if (aRole.Find("audio/dub") != -1) {
    128    return u"translation"_ns;
    129  }
    130  if (aRole.Find("audio/commentary") != -1) {
    131    return u"commentary"_ns;
    132  }
    133  if (aRole.Find("video/sign") != -1) {
    134    return u"sign"_ns;
    135  }
    136  if (aRole.Find("video/captioned") != -1) {
    137    return u"captions"_ns;
    138  }
    139  if (aRole.Find("video/subtitled") != -1) {
    140    return u"subtitles"_ns;
    141  }
    142  return u""_ns;
    143 }
    144 
    145 void OggDemuxer::InitTrack(MessageField* aMsgInfo, TrackInfo* aInfo,
    146                           bool aEnable) {
    147  MOZ_ASSERT(aMsgInfo);
    148  MOZ_ASSERT(aInfo);
    149 
    150  nsCString* sName = aMsgInfo->mValuesStore.Get(eName);
    151  nsCString* sRole = aMsgInfo->mValuesStore.Get(eRole);
    152  nsCString* sTitle = aMsgInfo->mValuesStore.Get(eTitle);
    153  nsCString* sLanguage = aMsgInfo->mValuesStore.Get(eLanguage);
    154  aInfo->Init(sName ? NS_ConvertUTF8toUTF16(*sName) : EmptyString(),
    155              sRole ? GetKind(*sRole) : u""_ns,
    156              sTitle ? NS_ConvertUTF8toUTF16(*sTitle) : EmptyString(),
    157              sLanguage ? NS_ConvertUTF8toUTF16(*sLanguage) : EmptyString(),
    158              aEnable);
    159 }
    160 
    161 OggDemuxer::OggDemuxer(MediaResource* aResource)
    162    : mSandbox(CreateSandbox()),
    163      mVorbisState(nullptr),
    164      mOpusState(nullptr),
    165      mFlacState(nullptr),
    166      mOpusEnabled(MediaDecoder::IsOpusEnabled()),
    167      mSkeletonState(nullptr),
    168      mAudioOggState(aResource, mSandbox.get()),
    169      mIsChained(false),
    170      mTimedMetadataEvent(nullptr),
    171      mOnSeekableEvent(nullptr) {
    172  MOZ_COUNT_CTOR(OggDemuxer);
    173  // aResource is referenced through inner mAudioOffState members.
    174  DDLINKCHILD("resource", aResource);
    175 }
    176 
    177 OggDemuxer::~OggDemuxer() {
    178  MOZ_COUNT_DTOR(OggDemuxer);
    179  Reset(TrackInfo::kAudioTrack);
    180 }
    181 
    182 void OggDemuxer::SetChainingEvents(TimedMetadataEventProducer* aMetadataEvent,
    183                                   MediaEventProducer<void>* aOnSeekableEvent) {
    184  mTimedMetadataEvent = aMetadataEvent;
    185  mOnSeekableEvent = aOnSeekableEvent;
    186 }
    187 
    188 bool OggDemuxer::HasAudio() const {
    189  return mVorbisState || mOpusState || mFlacState;
    190 }
    191 
    192 bool OggDemuxer::HasVideo() const { return false; }
    193 
    194 bool OggDemuxer::HaveStartTime() const { return mStartTime.isSome(); }
    195 
    196 TimeUnit OggDemuxer::StartTime() const {
    197  return mStartTime.refOr(TimeUnit::Zero());
    198 }
    199 
    200 bool OggDemuxer::HaveStartTime(TrackInfo::TrackType aType) {
    201  return OggState(aType).mStartTime.isSome();
    202 }
    203 
    204 TimeUnit OggDemuxer::StartTime(TrackInfo::TrackType aType) {
    205  return OggState(aType).mStartTime.refOr(TimeUnit::Zero());
    206 }
    207 
    208 RefPtr<OggDemuxer::InitPromise> OggDemuxer::Init() {
    209  if (!mSandbox) {
    210    return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
    211  }
    212  const char RLBOX_OGG_RETURN_CODE_SAFE[] =
    213      "Return codes only control whether to early exit. Incorrect return codes "
    214      "will not lead to memory safety issues in the renderer.";
    215 
    216  int ret = sandbox_invoke(*mSandbox, ogg_sync_init,
    217                           OggSyncState(TrackInfo::kAudioTrack))
    218                .unverified_safe_because(RLBOX_OGG_RETURN_CODE_SAFE);
    219  if (ret != 0) {
    220    return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
    221  }
    222  if (ReadMetadata() != NS_OK) {
    223    return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR,
    224                                        __func__);
    225  }
    226 
    227  if (!GetNumberTracks(TrackInfo::kAudioTrack)) {
    228    return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR,
    229                                        __func__);
    230  }
    231 
    232  return InitPromise::CreateAndResolve(NS_OK, __func__);
    233 }
    234 
    235 OggCodecState* OggDemuxer::GetTrackCodecState(
    236    TrackInfo::TrackType aType) const {
    237  switch (aType) {
    238    case TrackInfo::kAudioTrack:
    239      if (mVorbisState) {
    240        return mVorbisState;
    241      } else if (mOpusState) {
    242        return mOpusState;
    243      } else {
    244        return mFlacState;
    245      }
    246    default:
    247      return nullptr;
    248  }
    249 }
    250 
    251 TrackInfo::TrackType OggDemuxer::GetCodecStateType(
    252    OggCodecState* aState) const {
    253  switch (aState->GetType()) {
    254    case OggCodecState::TYPE_OPUS:
    255    case OggCodecState::TYPE_VORBIS:
    256    case OggCodecState::TYPE_FLAC:
    257      return TrackInfo::kAudioTrack;
    258    default:
    259      return TrackInfo::kUndefinedTrack;
    260  }
    261 }
    262 
    263 uint32_t OggDemuxer::GetNumberTracks(TrackInfo::TrackType aType) const {
    264  switch (aType) {
    265    case TrackInfo::kAudioTrack:
    266      return HasAudio() ? 1 : 0;
    267    default:
    268      return 0;
    269  }
    270 }
    271 
    272 UniquePtr<TrackInfo> OggDemuxer::GetTrackInfo(TrackInfo::TrackType aType,
    273                                              size_t aTrackNumber) const {
    274  switch (aType) {
    275    case TrackInfo::kAudioTrack:
    276      return mInfo.mAudio.Clone();
    277    default:
    278      return nullptr;
    279  }
    280 }
    281 
    282 already_AddRefed<MediaTrackDemuxer> OggDemuxer::GetTrackDemuxer(
    283    TrackInfo::TrackType aType, uint32_t aTrackNumber) {
    284  if (GetNumberTracks(aType) <= aTrackNumber) {
    285    return nullptr;
    286  }
    287  RefPtr<OggTrackDemuxer> e = new OggTrackDemuxer(this, aType, aTrackNumber);
    288  DDLINKCHILD("track demuxer", e.get());
    289  mDemuxers.AppendElement(e);
    290 
    291  return e.forget();
    292 }
    293 
    294 nsresult OggDemuxer::Reset(TrackInfo::TrackType aType) {
    295  // Discard any previously buffered packets/pages.
    296  if (mSandbox) {
    297    sandbox_invoke(*mSandbox, ogg_sync_reset, OggSyncState(aType));
    298  }
    299  OggCodecState* trackState = GetTrackCodecState(aType);
    300  if (trackState) {
    301    return trackState->Reset();
    302  }
    303  OggState(aType).mNeedKeyframe = true;
    304  return NS_OK;
    305 }
    306 
    307 bool OggDemuxer::ReadHeaders(TrackInfo::TrackType aType,
    308                             OggCodecState* aState) {
    309  while (!aState->DoneReadingHeaders()) {
    310    DemuxUntilPacketAvailable(aType, aState);
    311    OggPacketPtr packet = aState->PacketOut();
    312    if (!packet) {
    313      OGG_DEBUG("Ran out of header packets early; deactivating stream %" PRIu32,
    314                aState->mSerial);
    315      aState->Deactivate();
    316      return false;
    317    }
    318 
    319    // Local OggCodecState needs to decode headers in order to process
    320    // packet granulepos -> time mappings, etc.
    321    if (!aState->DecodeHeader(std::move(packet))) {
    322      OGG_DEBUG(
    323          "Failed to decode ogg header packet; deactivating stream %" PRIu32,
    324          aState->mSerial);
    325      aState->Deactivate();
    326      return false;
    327    }
    328  }
    329 
    330  return aState->Init();
    331 }
    332 
    333 void OggDemuxer::BuildSerialList(nsTArray<uint32_t>& aTracks) {
    334  // Obtaining seek index information for currently active bitstreams.
    335  if (HasAudio()) {
    336    if (mVorbisState) {
    337      aTracks.AppendElement(mVorbisState->mSerial);
    338    } else if (mOpusState) {
    339      aTracks.AppendElement(mOpusState->mSerial);
    340    }
    341  }
    342 }
    343 
    344 void OggDemuxer::SetupTarget(OggCodecState** aSavedState,
    345                             OggCodecState* aNewState) {
    346  if (*aSavedState) {
    347    (*aSavedState)->Reset();
    348  }
    349 
    350  if (aNewState->GetInfo()->GetAsAudioInfo()) {
    351    mInfo.mAudio = *aNewState->GetInfo()->GetAsAudioInfo();
    352  }
    353  *aSavedState = aNewState;
    354 }
    355 
    356 void OggDemuxer::SetupTargetSkeleton() {
    357  // Setup skeleton related information after mVorbisState & mTheroState
    358  // being set (if they exist).
    359  if (mSkeletonState) {
    360    if (!HasAudio()) {
    361      // We have a skeleton track, but no audio, may as well disable
    362      // the skeleton, we can't do anything useful with this media.
    363      OGG_DEBUG("Deactivating skeleton stream %" PRIu32,
    364                mSkeletonState->mSerial);
    365      mSkeletonState->Deactivate();
    366    } else if (ReadHeaders(TrackInfo::kAudioTrack, mSkeletonState) &&
    367               mSkeletonState->HasIndex()) {
    368      // We don't particularly care about which track we are currently using
    369      // as both MediaResource points to the same content.
    370      // Extract the duration info out of the index, so we don't need to seek to
    371      // the end of resource to get it.
    372      nsTArray<uint32_t> tracks;
    373      BuildSerialList(tracks);
    374      TimeUnit duration = TimeUnit::Zero();
    375      if (NS_SUCCEEDED(mSkeletonState->GetDuration(tracks, duration))) {
    376        OGG_DEBUG("Got duration from Skeleton index %s",
    377                  duration.ToString().get());
    378        mInfo.mMetadataDuration.emplace(duration);
    379      }
    380    }
    381  }
    382 }
    383 
    384 void OggDemuxer::SetupMediaTracksInfo(const nsTArray<uint32_t>& aSerials) {
    385  // For each serial number
    386  // 1. Retrieve a codecState from mCodecStore by this serial number.
    387  // 2. Retrieve a message field from mMsgFieldStore by this serial number.
    388  // 3. For now, skip if the serial number refers to a non-primary bitstream.
    389  // 4. Setup track and other audio related information per different
    390  // types.
    391  for (size_t i = 0; i < aSerials.Length(); i++) {
    392    uint32_t serial = aSerials[i];
    393    OggCodecState* codecState = mCodecStore.Get(serial);
    394 
    395    MessageField* msgInfo = nullptr;
    396    if (mSkeletonState) {
    397      mSkeletonState->mMsgFieldStore.Get(serial, &msgInfo);
    398    }
    399 
    400    OggCodecState* primeState = nullptr;
    401    switch (codecState->GetType()) {
    402      case OggCodecState::TYPE_VORBIS:
    403        primeState = mVorbisState;
    404        break;
    405      case OggCodecState::TYPE_OPUS:
    406        primeState = mOpusState;
    407        break;
    408      case OggCodecState::TYPE_FLAC:
    409        primeState = mFlacState;
    410        break;
    411      default:
    412        break;
    413    }
    414    if (primeState && primeState == codecState) {
    415      if (msgInfo) {
    416        InitTrack(msgInfo, static_cast<TrackInfo*>(&mInfo.mAudio), true);
    417      }
    418      FillTags(static_cast<TrackInfo*>(&mInfo.mAudio), primeState->GetTags());
    419    }
    420  }
    421 }
    422 
    423 void OggDemuxer::FillTags(TrackInfo* aInfo, UniquePtr<MetadataTags>&& aTags) {
    424  if (!aTags) {
    425    return;
    426  }
    427  UniquePtr<MetadataTags> tags(std::move(aTags));
    428  for (const auto& entry : *tags) {
    429    aInfo->mTags.AppendElement(MetadataTag(entry.GetKey(), entry.GetData()));
    430  }
    431 }
    432 
    433 nsresult OggDemuxer::ReadMetadata() {
    434  OGG_DEBUG("OggDemuxer::ReadMetadata called!");
    435 
    436  // We read packets until all bitstreams have read all their header packets.
    437  // We record the offset of the first non-header page so that we know
    438  // what page to seek to when seeking to the media start.
    439 
    440  // @FIXME we have to read all the header packets on all the streams
    441  // and THEN we can run SetupTarget*
    442  // @fixme fixme
    443 
    444  nsTArray<OggCodecState*> bitstreams;
    445  nsTArray<uint32_t> serials;
    446 
    447  tainted_ogg<ogg_page*> page = mSandbox->malloc_in_sandbox<ogg_page>();
    448  if (!page) {
    449    return NS_ERROR_OUT_OF_MEMORY;
    450  }
    451  auto clean_page = MakeScopeExit([&] { mSandbox->free_in_sandbox(page); });
    452 
    453  bool readAllBOS = false;
    454  while (!readAllBOS) {
    455    if (!ReadOggPage(TrackInfo::kAudioTrack, page.to_opaque())) {
    456      // Some kind of error...
    457      OGG_DEBUG("OggDemuxer::ReadOggPage failed? leaving ReadMetadata...");
    458      return NS_ERROR_FAILURE;
    459    }
    460 
    461    uint32_t serial = static_cast<uint32_t>(
    462        sandbox_invoke(*mSandbox, ogg_page_serialno, page)
    463            .unverified_safe_because(RLBOX_OGG_PAGE_SERIAL_REASON));
    464 
    465    if (!sandbox_invoke(*mSandbox, ogg_page_bos, page)
    466             .unverified_safe_because(
    467                 "If this value is incorrect, it would mean not all "
    468                 "bitstreams are read. This does not affect the memory "
    469                 "safety of the renderer.")) {
    470      // We've encountered a non Beginning Of Stream page. No more BOS pages
    471      // can follow in this Ogg segment, so there will be no other bitstreams
    472      // in the Ogg (unless it's invalid).
    473      readAllBOS = true;
    474    } else if (!mCodecStore.Contains(serial)) {
    475      // We've not encountered a stream with this serial number before. Create
    476      // an OggCodecState to demux it, and map that to the OggCodecState
    477      // in mCodecStates.
    478      OggCodecState* const codecState = mCodecStore.Add(
    479          serial,
    480          OggCodecState::Create(mSandbox.get(), page.to_opaque(), serial));
    481      bitstreams.AppendElement(codecState);
    482      serials.AppendElement(serial);
    483    }
    484    if (NS_FAILED(DemuxOggPage(TrackInfo::kAudioTrack, page.to_opaque()))) {
    485      return NS_ERROR_FAILURE;
    486    }
    487  }
    488 
    489  // We've read all BOS pages, so we know the streams contained in the media.
    490  // 1. Find the first encountered Vorbis/Opus bitstream, and configure
    491  //    it as the target A/V bitstream.
    492  // 2. Deactivate the rest of bitstreams for now, until we have MediaInfo
    493  //    support multiple track infos.
    494  for (uint32_t i = 0; i < bitstreams.Length(); ++i) {
    495    OggCodecState* s = bitstreams[i];
    496    if (s) {
    497      if (s->GetType() == OggCodecState::TYPE_VORBIS &&
    498          ReadHeaders(TrackInfo::kAudioTrack, s)) {
    499        if (!mVorbisState) {
    500          SetupTarget(&mVorbisState, s);
    501        } else {
    502          s->Deactivate();
    503        }
    504      } else if (s->GetType() == OggCodecState::TYPE_OPUS &&
    505                 ReadHeaders(TrackInfo::kAudioTrack, s)) {
    506        if (mOpusEnabled) {
    507          if (!mOpusState) {
    508            SetupTarget(&mOpusState, s);
    509          } else {
    510            s->Deactivate();
    511          }
    512        } else {
    513          NS_WARNING(
    514              "Opus decoding disabled."
    515              " See media.opus.enabled in about:config");
    516        }
    517      } else if (s->GetType() == OggCodecState::TYPE_FLAC &&
    518                 ReadHeaders(TrackInfo::kAudioTrack, s)) {
    519        if (!mFlacState) {
    520          SetupTarget(&mFlacState, s);
    521        } else {
    522          s->Deactivate();
    523        }
    524      } else if (s->GetType() == OggCodecState::TYPE_SKELETON &&
    525                 !mSkeletonState) {
    526        mSkeletonState = static_cast<SkeletonState*>(s);
    527      } else {
    528        // Deactivate any non-primary bitstreams.
    529        s->Deactivate();
    530      }
    531    }
    532  }
    533 
    534  SetupTargetSkeleton();
    535  SetupMediaTracksInfo(serials);
    536 
    537  if (HasAudio()) {
    538    TimeUnit startTime = TimeUnit::Invalid();
    539    FindStartTime(startTime);
    540    if (startTime.IsValid()) {
    541      OGG_DEBUG("Detected stream start time %s", startTime.ToString().get());
    542      mStartTime.emplace(startTime);
    543    }
    544 
    545    if (mInfo.mMetadataDuration.isNothing() &&
    546        Resource(TrackInfo::kAudioTrack)->GetLength() >= 0) {
    547      // We didn't get a duration from the index or a Content-Duration header.
    548      // Seek to the end of file to find the end time.
    549      int64_t length = Resource(TrackInfo::kAudioTrack)->GetLength();
    550 
    551      MOZ_ASSERT(length > 0, "Must have a content length to get end time");
    552 
    553      TimeUnit endTime = RangeEndTime(TrackInfo::kAudioTrack, length);
    554 
    555      if (endTime.IsValid() && endTime.IsPositive()) {
    556        mInfo.mUnadjustedMetadataEndTime.emplace(endTime);
    557        TimeUnit computedDuration =
    558            endTime - mStartTime.refOr(TimeUnit::Zero());
    559        if (computedDuration.IsPositive()) {
    560          mInfo.mMetadataDuration.emplace(computedDuration);
    561          OGG_DEBUG("Got Ogg duration from seeking to end %s",
    562                    computedDuration.ToString().get());
    563        } else {
    564          OGG_DEBUG("Ignoring incorect start time in metadata");
    565          mStartTime.reset();
    566        }
    567      }
    568    }
    569    if (mInfo.mMetadataDuration.isNothing()) {
    570      OGG_DEBUG("Couldn't determine OGG file duration.");
    571      mInfo.mMetadataDuration.emplace(TimeUnit::FromInfinity());
    572    }
    573    if (HasAudio()) {
    574      mInfo.mAudio.mDuration = mInfo.mMetadataDuration.ref();
    575    }
    576  } else {
    577    OGG_DEBUG("no audio tracks");
    578    return NS_ERROR_FAILURE;
    579  }
    580 
    581  OGG_DEBUG("success?!");
    582  return NS_OK;
    583 }
    584 
    585 void OggDemuxer::SetChained() {
    586  {
    587    if (mIsChained) {
    588      return;
    589    }
    590    mIsChained = true;
    591  }
    592  if (mOnSeekableEvent) {
    593    mOnSeekableEvent->Notify();
    594  }
    595 }
    596 
    597 bool OggDemuxer::ReadOggChain(const media::TimeUnit& aLastEndTime) {
    598  bool chained = false;
    599  OpusState* newOpusState = nullptr;
    600  VorbisState* newVorbisState = nullptr;
    601  FlacState* newFlacState = nullptr;
    602  UniquePtr<MetadataTags> tags;
    603 
    604  if (HasSkeleton() || !HasAudio()) {
    605    return false;
    606  }
    607 
    608  tainted_ogg<ogg_page*> page = mSandbox->malloc_in_sandbox<ogg_page>();
    609  if (!page) {
    610    return false;
    611  }
    612  auto clean_page = MakeScopeExit([&] { mSandbox->free_in_sandbox(page); });
    613  if (!ReadOggPage(TrackInfo::kAudioTrack, page.to_opaque()) ||
    614      !sandbox_invoke(*mSandbox, ogg_page_bos, page)
    615           .unverified_safe_because(RLBOX_OGG_STATE_ASSERT_REASON)) {
    616    // Chaining is only supported for audio only ogg files.
    617    return false;
    618  }
    619 
    620  uint32_t serial = static_cast<uint32_t>(
    621      sandbox_invoke(*mSandbox, ogg_page_serialno, page)
    622          .unverified_safe_because(
    623              "We are reading a new page with a serial number for the first "
    624              "time and will check if we have seen it before prior to use."));
    625  if (mCodecStore.Contains(serial)) {
    626    return false;
    627  }
    628 
    629  UniquePtr<OggCodecState> codecState(
    630      OggCodecState::Create(mSandbox.get(), page.to_opaque(), serial));
    631  if (!codecState) {
    632    return false;
    633  }
    634 
    635  if (mVorbisState && (codecState->GetType() == OggCodecState::TYPE_VORBIS)) {
    636    newVorbisState = static_cast<VorbisState*>(codecState.get());
    637  } else if (mOpusState &&
    638             (codecState->GetType() == OggCodecState::TYPE_OPUS)) {
    639    newOpusState = static_cast<OpusState*>(codecState.get());
    640  } else if (mFlacState &&
    641             (codecState->GetType() == OggCodecState::TYPE_FLAC)) {
    642    newFlacState = static_cast<FlacState*>(codecState.get());
    643  } else {
    644    return false;
    645  }
    646 
    647  OggCodecState* state;
    648 
    649  mCodecStore.Add(serial, std::move(codecState));
    650  state = mCodecStore.Get(serial);
    651 
    652  NS_ENSURE_TRUE(state != nullptr, false);
    653 
    654  if (NS_FAILED(state->PageIn(page.to_opaque()))) {
    655    return false;
    656  }
    657 
    658  MessageField* msgInfo = nullptr;
    659  if (mSkeletonState) {
    660    mSkeletonState->mMsgFieldStore.Get(serial, &msgInfo);
    661  }
    662 
    663  if ((newVorbisState && ReadHeaders(TrackInfo::kAudioTrack, newVorbisState)) &&
    664      (mVorbisState->GetInfo()->GetAsAudioInfo()->mRate ==
    665       newVorbisState->GetInfo()->GetAsAudioInfo()->mRate) &&
    666      (mVorbisState->GetInfo()->GetAsAudioInfo()->mChannels ==
    667       newVorbisState->GetInfo()->GetAsAudioInfo()->mChannels)) {
    668    SetupTarget(&mVorbisState, newVorbisState);
    669    OGG_DEBUG("New vorbis ogg link, serial=%d\n", mVorbisState->mSerial);
    670 
    671    if (msgInfo) {
    672      InitTrack(msgInfo, &mInfo.mAudio, true);
    673    }
    674 
    675    chained = true;
    676    tags = newVorbisState->GetTags();
    677  }
    678 
    679  if ((newOpusState && ReadHeaders(TrackInfo::kAudioTrack, newOpusState)) &&
    680      (mOpusState->GetInfo()->GetAsAudioInfo()->mRate ==
    681       newOpusState->GetInfo()->GetAsAudioInfo()->mRate) &&
    682      (mOpusState->GetInfo()->GetAsAudioInfo()->mChannels ==
    683       newOpusState->GetInfo()->GetAsAudioInfo()->mChannels)) {
    684    SetupTarget(&mOpusState, newOpusState);
    685 
    686    if (msgInfo) {
    687      InitTrack(msgInfo, &mInfo.mAudio, true);
    688    }
    689 
    690    chained = true;
    691    tags = newOpusState->GetTags();
    692  }
    693 
    694  if ((newFlacState && ReadHeaders(TrackInfo::kAudioTrack, newFlacState)) &&
    695      (mFlacState->GetInfo()->GetAsAudioInfo()->mRate ==
    696       newFlacState->GetInfo()->GetAsAudioInfo()->mRate) &&
    697      (mFlacState->GetInfo()->GetAsAudioInfo()->mChannels ==
    698       newFlacState->GetInfo()->GetAsAudioInfo()->mChannels)) {
    699    SetupTarget(&mFlacState, newFlacState);
    700    OGG_DEBUG("New flac ogg link, serial=%d\n", mFlacState->mSerial);
    701 
    702    if (msgInfo) {
    703      InitTrack(msgInfo, &mInfo.mAudio, true);
    704    }
    705 
    706    chained = true;
    707    tags = newFlacState->GetTags();
    708  }
    709 
    710  if (chained) {
    711    SetChained();
    712    mInfo.mMediaSeekable = false;
    713    mDecodedAudioDuration += aLastEndTime;
    714    if (mTimedMetadataEvent) {
    715      mTimedMetadataEvent->Notify(
    716          TimedMetadata(mDecodedAudioDuration, std::move(tags),
    717                        UniquePtr<MediaInfo>(new MediaInfo(mInfo))));
    718    }
    719    // Setup a new TrackInfo so that the MediaFormatReader will flush the
    720    // current decoder.
    721    mSharedAudioTrackInfo =
    722        new TrackInfoSharedPtr(mInfo.mAudio, ++sStreamSourceID);
    723    return true;
    724  }
    725 
    726  return false;
    727 }
    728 
    729 OggDemuxer::OggStateContext& OggDemuxer::OggState(TrackInfo::TrackType aType) {
    730  MOZ_ASSERT(aType != TrackInfo::kVideoTrack);
    731  return mAudioOggState;
    732 }
    733 
    734 tainted_opaque_ogg<ogg_sync_state*> OggDemuxer::OggSyncState(
    735    TrackInfo::TrackType aType) {
    736  return OggState(aType).mOggState.mState;
    737 }
    738 
    739 MediaResourceIndex* OggDemuxer::Resource(TrackInfo::TrackType aType) {
    740  return &OggState(aType).mResource;
    741 }
    742 
    743 MediaResourceIndex* OggDemuxer::CommonResource() {
    744  return &mAudioOggState.mResource;
    745 }
    746 
    747 bool OggDemuxer::ReadOggPage(TrackInfo::TrackType aType,
    748                             tainted_opaque_ogg<ogg_page*> aPage) {
    749  int ret = 0;
    750  while ((ret = sandbox_invoke(*mSandbox, ogg_sync_pageseek,
    751                               OggSyncState(aType), aPage)
    752                    .unverified_safe_because(RLBOX_OGG_STATE_ASSERT_REASON)) <=
    753         0) {
    754    if (ret < 0) {
    755      // Lost page sync, have to skip up to next page.
    756      continue;
    757    }
    758    // Returns a buffer that can be written too
    759    // with the given size. This buffer is stored
    760    // in the ogg synchronisation structure.
    761    const uint32_t MIN_BUFFER_SIZE = 4096;
    762    tainted_ogg<char*> buffer_tainted = sandbox_invoke(
    763        *mSandbox, ogg_sync_buffer, OggSyncState(aType), MIN_BUFFER_SIZE);
    764    MOZ_ASSERT(buffer_tainted != nullptr, "ogg_sync_buffer failed");
    765 
    766    // Read from the resource into the buffer
    767    uint32_t bytesRead = 0;
    768 
    769    char* buffer = buffer_tainted.copy_and_verify_buffer_address(
    770        [](uintptr_t val) { return reinterpret_cast<char*>(val); },
    771        MIN_BUFFER_SIZE);
    772 
    773    nsresult rv = Resource(aType)->Read(buffer, MIN_BUFFER_SIZE, &bytesRead);
    774    if (NS_FAILED(rv) || !bytesRead) {
    775      // End of file or error.
    776      return false;
    777    }
    778 
    779    // Update the synchronisation layer with the number
    780    // of bytes written to the buffer
    781    ret = sandbox_invoke(*mSandbox, ogg_sync_wrote, OggSyncState(aType),
    782                         bytesRead)
    783              .unverified_safe_because(RLBOX_OGG_STATE_ASSERT_REASON);
    784    NS_ENSURE_TRUE(ret == 0, false);
    785  }
    786 
    787  return true;
    788 }
    789 
    790 nsresult OggDemuxer::DemuxOggPage(TrackInfo::TrackType aType,
    791                                  tainted_opaque_ogg<ogg_page*> aPage) {
    792  tainted_ogg<int> serial = sandbox_invoke(*mSandbox, ogg_page_serialno, aPage);
    793  OggCodecState* codecState = mCodecStore.Get(static_cast<uint32_t>(
    794      serial.unverified_safe_because(RLBOX_OGG_PAGE_SERIAL_REASON)));
    795  if (codecState == nullptr) {
    796    OGG_DEBUG("encountered packet for unrecognized codecState");
    797    return NS_ERROR_FAILURE;
    798  }
    799  if (GetCodecStateType(codecState) != aType &&
    800      codecState->GetType() != OggCodecState::TYPE_SKELETON) {
    801    // Not a page we're interested in.
    802    return NS_OK;
    803  }
    804  if (NS_FAILED(codecState->PageIn(aPage))) {
    805    OGG_DEBUG("codecState->PageIn failed");
    806    return NS_ERROR_FAILURE;
    807  }
    808  return NS_OK;
    809 }
    810 
    811 bool OggDemuxer::IsSeekable() const { return !mIsChained; }
    812 
    813 UniquePtr<EncryptionInfo> OggDemuxer::GetCrypto() { return nullptr; }
    814 
    815 ogg_packet* OggDemuxer::GetNextPacket(TrackInfo::TrackType aType) {
    816  OggCodecState* state = GetTrackCodecState(aType);
    817  ogg_packet* packet = nullptr;
    818  OggStateContext& context = OggState(aType);
    819 
    820  while (true) {
    821    if (packet) {
    822      (void)state->PacketOut();
    823    }
    824    DemuxUntilPacketAvailable(aType, state);
    825 
    826    packet = state->PacketPeek();
    827    if (!packet) {
    828      break;
    829    }
    830    if (state->IsHeader(packet)) {
    831      continue;
    832    }
    833    if (context.mNeedKeyframe && !state->IsKeyframe(packet)) {
    834      continue;
    835    }
    836    context.mNeedKeyframe = false;
    837    break;
    838  }
    839 
    840  return packet;
    841 }
    842 
    843 void OggDemuxer::DemuxUntilPacketAvailable(TrackInfo::TrackType aType,
    844                                           OggCodecState* aState) {
    845  while (!aState->IsPacketReady()) {
    846    OGG_DEBUG("no packet yet, reading some more");
    847    tainted_ogg<ogg_page*> page = mSandbox->malloc_in_sandbox<ogg_page>();
    848    MOZ_RELEASE_ASSERT(page != nullptr);
    849    auto clean_page = MakeScopeExit([&] { mSandbox->free_in_sandbox(page); });
    850    if (!ReadOggPage(aType, page.to_opaque())) {
    851      OGG_DEBUG("no more pages to read in resource?");
    852      return;
    853    }
    854    DemuxOggPage(aType, page.to_opaque());
    855  }
    856 }
    857 
    858 TimeIntervals OggDemuxer::GetBuffered(TrackInfo::TrackType aType) {
    859  if (!HaveStartTime(aType)) {
    860    return TimeIntervals();
    861  }
    862  if (mIsChained) {
    863    return TimeIntervals::Invalid();
    864  }
    865  TimeIntervals buffered;
    866  // HasAudio is not used here as they take a lock and cause
    867  // a deadlock. Accessing mInfo doesn't require a lock - it doesn't change
    868  // after metadata is read.
    869  if (!mInfo.HasValidMedia()) {
    870    // No need to search through the file if there are no audio tracks
    871    return buffered;
    872  }
    873 
    874  AutoPinned<MediaResource> resource(Resource(aType)->GetResource());
    875  MediaByteRangeSet ranges;
    876  nsresult res = resource->GetCachedRanges(ranges);
    877  NS_ENSURE_SUCCESS(res, TimeIntervals::Invalid());
    878 
    879  const char time_interval_reason[] =
    880      "Even if this computation is incorrect due to the reliance on tainted "
    881      "values, only the search for the time interval or the time interval "
    882      "returned will be affected. However this will not result in a memory "
    883      "safety vulnerabilty in the Firefox renderer.";
    884 
    885  // Traverse across the buffered byte ranges, determining the time ranges
    886  // they contain. MediaResource::GetNextCachedData(offset) returns -1 when
    887  // offset is after the end of the media resource, or there's no more cached
    888  // data after the offset. This loop will run until we've checked every
    889  // buffered range in the media, in increasing order of offset.
    890  nsAutoOggSyncState sync(mSandbox.get());
    891  for (uint32_t index = 0; index < ranges.Length(); index++) {
    892    // Ensure the offsets are after the header pages.
    893    int64_t startOffset = ranges[index].mStart;
    894    int64_t endOffset = ranges[index].mEnd;
    895 
    896    // Because the granulepos time is actually the end time of the page,
    897    // we special-case (startOffset == 0) so that the first
    898    // buffered range always appears to be buffered from the media start
    899    // time, rather than from the end-time of the first page.
    900    TimeUnit startTime = (startOffset == 0) ? StartTime() : TimeUnit::Invalid();
    901 
    902    // Find the start time of the range. Read pages until we find one with a
    903    // granulepos which we can convert into a timestamp to use as the time of
    904    // the start of the buffered range.
    905    sandbox_invoke(*mSandbox, ogg_sync_reset, sync.mState);
    906    tainted_ogg<ogg_page*> page = mSandbox->malloc_in_sandbox<ogg_page>();
    907    if (!page) {
    908      return TimeIntervals::Invalid();
    909    }
    910    auto clean_page = MakeScopeExit([&] { mSandbox->free_in_sandbox(page); });
    911 
    912    while (!startTime.IsValid()) {
    913      int32_t discard;
    914      PageSyncResult pageSyncResult =
    915          PageSync(mSandbox.get(), Resource(aType), sync.mState, true,
    916                   startOffset, endOffset, page, discard);
    917      if (pageSyncResult == PAGE_SYNC_ERROR) {
    918        return TimeIntervals::Invalid();
    919      }
    920      if (pageSyncResult == PAGE_SYNC_END_OF_RANGE) {
    921        // Hit the end of range without reading a page, give up trying to
    922        // find a start time for this buffered range, skip onto the next one.
    923        break;
    924      }
    925 
    926      int64_t granulepos = sandbox_invoke(*mSandbox, ogg_page_granulepos, page)
    927                               .unverified_safe_because(time_interval_reason);
    928      if (granulepos == -1) {
    929        // Page doesn't have an end time, advance to the next page
    930        // until we find one.
    931 
    932        bool failedPageLenVerify = false;
    933        // Page length should be under 64Kb according to
    934        // https://xiph.org/ogg/doc/libogg/ogg_page.html
    935        long pageLength =
    936            CopyAndVerifyOrFail(page->header_len + page->body_len,
    937                                val <= 64 * 1024, &failedPageLenVerify);
    938        if (failedPageLenVerify) {
    939          return TimeIntervals::Invalid();
    940        }
    941 
    942        startOffset += pageLength;
    943        continue;
    944      }
    945 
    946      tainted_ogg<uint32_t> serial = rlbox::sandbox_static_cast<uint32_t>(
    947          sandbox_invoke(*mSandbox, ogg_page_serialno, page));
    948      if (aType == TrackInfo::kAudioTrack && mVorbisState &&
    949          (serial == mVorbisState->mSerial)
    950              .unverified_safe_because(time_interval_reason)) {
    951        startTime = mVorbisState->Time(granulepos);
    952        MOZ_ASSERT(startTime.IsPositive(), "Must have positive start time");
    953      } else if (aType == TrackInfo::kAudioTrack && mOpusState &&
    954                 (serial == mOpusState->mSerial)
    955                     .unverified_safe_because(time_interval_reason)) {
    956        startTime = mOpusState->Time(granulepos);
    957        MOZ_ASSERT(startTime.IsPositive(), "Must have positive start time");
    958      } else if (aType == TrackInfo::kAudioTrack && mFlacState &&
    959                 (serial == mFlacState->mSerial)
    960                     .unverified_safe_because(time_interval_reason)) {
    961        startTime = mFlacState->Time(granulepos);
    962        MOZ_ASSERT(startTime.IsPositive(), "Must have positive start time");
    963      } else if (mCodecStore.Contains(
    964                     serial.unverified_safe_because(time_interval_reason))) {
    965        // Stream is not the vorbis stream we're playing,
    966        // but is one that we have header data for.
    967 
    968        bool failedPageLenVerify = false;
    969        // Page length should be under 64Kb according to
    970        // https://xiph.org/ogg/doc/libogg/ogg_page.html
    971        long pageLength =
    972            CopyAndVerifyOrFail(page->header_len + page->body_len,
    973                                val <= 64 * 1024, &failedPageLenVerify);
    974        if (failedPageLenVerify) {
    975          return TimeIntervals::Invalid();
    976        }
    977 
    978        startOffset += pageLength;
    979        continue;
    980      } else {
    981        // Page is for a stream we don't know about (possibly a chained
    982        // ogg), return OK to abort the finding any further ranges. This
    983        // prevents us searching through the rest of the media when we
    984        // may not be able to extract timestamps from it.
    985        SetChained();
    986        return buffered;
    987      }
    988    }
    989 
    990    if (startTime.IsValid()) {
    991      // We were able to find a start time for that range, see if we can
    992      // find an end time.
    993      TimeUnit endTime = RangeEndTime(aType, startOffset, endOffset, true);
    994      if (endTime.IsValid() && endTime > startTime) {
    995        buffered +=
    996            TimeInterval(startTime - StartTime(), endTime - StartTime());
    997      }
    998    }
    999  }
   1000 
   1001  return buffered;
   1002 }
   1003 
   1004 void OggDemuxer::FindStartTime(TimeUnit& aOutStartTime) {
   1005  // Extract the start times of the bitstreams in order to calculate
   1006  // the duration.
   1007  TimeUnit audioStartTime = TimeUnit::FromInfinity();
   1008 
   1009  if (HasAudio()) {
   1010    FindStartTime(TrackInfo::kAudioTrack, audioStartTime);
   1011    if (!audioStartTime.IsPosInf() && audioStartTime.IsValid()) {
   1012      OGG_DEBUG("OggDemuxer::FindStartTime() audio=%s",
   1013                audioStartTime.ToString().get());
   1014      mAudioOggState.mStartTime = Some(audioStartTime);
   1015    }
   1016  }
   1017 
   1018  if (!audioStartTime.IsPosInf()) {
   1019    aOutStartTime = audioStartTime;
   1020  }
   1021 }
   1022 
   1023 void OggDemuxer::FindStartTime(TrackInfo::TrackType aType,
   1024                               TimeUnit& aOutStartTime) {
   1025  TimeUnit startTime = TimeUnit::FromInfinity();
   1026 
   1027  OggCodecState* state = GetTrackCodecState(aType);
   1028  ogg_packet* pkt = GetNextPacket(aType);
   1029  if (pkt) {
   1030    startTime = state->PacketStartTime(pkt);
   1031  }
   1032 
   1033  if (!startTime.IsInfinite()) {
   1034    aOutStartTime = startTime;
   1035  }
   1036 }
   1037 
   1038 nsresult OggDemuxer::SeekInternal(TrackInfo::TrackType aType,
   1039                                  const TimeUnit& aTarget) {
   1040  OGG_DEBUG("About to seek to %s", aTarget.ToString().get());
   1041  nsresult res;
   1042  TimeUnit adjustedTarget = aTarget;
   1043  TimeUnit startTime = StartTime(aType);
   1044  TimeUnit endTime =
   1045      mInfo.mMetadataDuration.valueOr(TimeUnit::Zero()) + startTime;
   1046  if (aType == TrackInfo::kAudioTrack && mOpusState) {
   1047    adjustedTarget = std::max(startTime, aTarget - OGG_SEEK_OPUS_PREROLL);
   1048  }
   1049 
   1050  if (!HaveStartTime(aType) || adjustedTarget == startTime) {
   1051    // We've seeked to the media start or we can't seek.
   1052    // Just seek to the offset of the first content page.
   1053    res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, 0);
   1054    NS_ENSURE_SUCCESS(res, res);
   1055 
   1056    res = Reset(aType);
   1057    NS_ENSURE_SUCCESS(res, res);
   1058  } else {
   1059    // TODO: This may seek back unnecessarily far in the media, but we don't
   1060    // have a way of asking Skeleton to seek to a different target for each
   1061    // stream yet. Using adjustedTarget here is at least correct, if slow.
   1062    IndexedSeekResult sres = SeekToKeyframeUsingIndex(aType, adjustedTarget);
   1063    NS_ENSURE_TRUE(sres != SEEK_FATAL_ERROR, NS_ERROR_FAILURE);
   1064    if (sres == SEEK_INDEX_FAIL) {
   1065      // No index or other non-fatal index-related failure. Try to seek
   1066      // using a bisection search. Determine the already downloaded data
   1067      // in the media cache, so we can try to seek in the cached data first.
   1068      AutoTArray<SeekRange, 16> ranges;
   1069      res = GetSeekRanges(aType, ranges);
   1070      NS_ENSURE_SUCCESS(res, res);
   1071 
   1072      // Figure out if the seek target lies in a buffered range.
   1073      SeekRange r =
   1074          SelectSeekRange(aType, ranges, aTarget, startTime, endTime, true);
   1075 
   1076      if (!r.IsNull()) {
   1077        // We know the buffered range in which the seek target lies, do a
   1078        // bisection search in that buffered range.
   1079        res = SeekInBufferedRange(aType, aTarget, adjustedTarget, startTime,
   1080                                  endTime, ranges, r);
   1081        NS_ENSURE_SUCCESS(res, res);
   1082      } else {
   1083        // The target doesn't lie in a buffered range. Perform a bisection
   1084        // search over the whole media, using the known buffered ranges to
   1085        // reduce the search space.
   1086        res = SeekInUnbuffered(aType, aTarget, startTime, endTime, ranges);
   1087        NS_ENSURE_SUCCESS(res, res);
   1088      }
   1089    }
   1090  }
   1091 
   1092  // Demux forwards until we find the first keyframe prior the target.
   1093  // there may be non-keyframes in the page before the keyframe.
   1094  // Additionally, we may have seeked to the first page referenced by the
   1095  // page index which may be quite far off the target.
   1096  // When doing fastSeek we display the first frame after the seek, so
   1097  // we need to advance the decode to the keyframe otherwise we'll get
   1098  // visual artifacts in the first frame output after the seek.
   1099  OggCodecState* state = GetTrackCodecState(aType);
   1100  OggPacketQueue tempPackets;
   1101  bool foundKeyframe = false;
   1102  while (true) {
   1103    DemuxUntilPacketAvailable(aType, state);
   1104    ogg_packet* packet = state->PacketPeek();
   1105    if (packet == nullptr) {
   1106      OGG_DEBUG("End of stream reached before keyframe found in indexed seek");
   1107      break;
   1108    }
   1109    // Skip any header packet, this can be the case when looping and not parsing
   1110    // the headers again.
   1111    if (state->IsHeader(packet)) {
   1112      OggPacketPtr drop(state->PacketOut());
   1113      continue;
   1114    }
   1115    TimeUnit startTstamp = state->PacketStartTime(packet);
   1116    if (!startTstamp.IsValid()) {
   1117      OGG_DEBUG("Invalid tstamp on packet %p (granulepos: %" PRId64 ")", packet,
   1118                packet->granulepos);
   1119    }
   1120    if (foundKeyframe && startTstamp.IsValid() &&
   1121        startTstamp > adjustedTarget) {
   1122      break;
   1123    }
   1124    if (state->IsKeyframe(packet)) {
   1125      OGG_DEBUG("keyframe found after seeking at %s",
   1126                startTstamp.ToString().get());
   1127      tempPackets.Erase();
   1128      foundKeyframe = true;
   1129    }
   1130    if (foundKeyframe && startTstamp.IsValid() &&
   1131        startTstamp == adjustedTarget) {
   1132      break;
   1133    }
   1134    if (foundKeyframe) {
   1135      tempPackets.Append(state->PacketOut());
   1136    } else {
   1137      // Discard media packets before the first keyframe.
   1138      (void)state->PacketOut();
   1139    }
   1140  }
   1141  // Re-add all packet into the codec state in order.
   1142  state->PushFront(std::move(tempPackets));
   1143 
   1144  return NS_OK;
   1145 }
   1146 
   1147 OggDemuxer::IndexedSeekResult OggDemuxer::RollbackIndexedSeek(
   1148    TrackInfo::TrackType aType, int64_t aOffset) {
   1149  if (mSkeletonState) {
   1150    mSkeletonState->Deactivate();
   1151  }
   1152  nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
   1153  NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
   1154  return SEEK_INDEX_FAIL;
   1155 }
   1156 
   1157 OggDemuxer::IndexedSeekResult OggDemuxer::SeekToKeyframeUsingIndex(
   1158    TrackInfo::TrackType aType, const TimeUnit& aTarget) {
   1159  if (!HasSkeleton() || !mSkeletonState->HasIndex()) {
   1160    return SEEK_INDEX_FAIL;
   1161  }
   1162  // We have an index from the Skeleton track, try to use it to seek.
   1163  AutoTArray<uint32_t, 2> tracks;
   1164  BuildSerialList(tracks);
   1165  SkeletonState::nsSeekTarget keyframe;
   1166  if (NS_FAILED(mSkeletonState->IndexedSeekTarget(aTarget, tracks, keyframe))) {
   1167    // Could not locate a keypoint for the target in the index.
   1168    return SEEK_INDEX_FAIL;
   1169  }
   1170 
   1171  // Remember original resource read cursor position so we can rollback on
   1172  // failure.
   1173  int64_t tell = Resource(aType)->Tell();
   1174 
   1175  // Seek to the keypoint returned by the index.
   1176  if (keyframe.mKeyPoint.mOffset > Resource(aType)->GetLength() ||
   1177      keyframe.mKeyPoint.mOffset < 0) {
   1178    // Index must be invalid.
   1179    return RollbackIndexedSeek(aType, tell);
   1180  }
   1181  OGG_DEBUG("Seeking using index to keyframe at offset %" PRId64 "\n",
   1182            keyframe.mKeyPoint.mOffset);
   1183  nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET,
   1184                                       keyframe.mKeyPoint.mOffset);
   1185  NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
   1186 
   1187  // We've moved the read set, so reset decode.
   1188  res = Reset(aType);
   1189  NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
   1190 
   1191  // Check that the page the index thinks is exactly here is actually exactly
   1192  // here. If not, the index is invalid.
   1193  tainted_ogg<ogg_page*> page = mSandbox->malloc_in_sandbox<ogg_page>();
   1194  if (!page) {
   1195    return SEEK_INDEX_FAIL;
   1196  }
   1197  auto clean_page = MakeScopeExit([&] { mSandbox->free_in_sandbox(page); });
   1198  int skippedBytes = 0;
   1199  PageSyncResult syncres =
   1200      PageSync(mSandbox.get(), Resource(aType), OggSyncState(aType), false,
   1201               keyframe.mKeyPoint.mOffset, Resource(aType)->GetLength(), page,
   1202               skippedBytes);
   1203  NS_ENSURE_TRUE(syncres != PAGE_SYNC_ERROR, SEEK_FATAL_ERROR);
   1204  if (syncres != PAGE_SYNC_OK || skippedBytes != 0) {
   1205    OGG_DEBUG(
   1206        "Indexed-seek failure: Ogg Skeleton Index is invalid "
   1207        "or sync error after seek");
   1208    return RollbackIndexedSeek(aType, tell);
   1209  }
   1210  uint32_t serial = static_cast<uint32_t>(
   1211      sandbox_invoke(*mSandbox, ogg_page_serialno, page)
   1212          .unverified_safe_because(
   1213              "Serial is only used to locate the correct page. If the serial "
   1214              "is incorrect the the renderer would just fail to seek with an "
   1215              "error code. This would not lead to any memory safety bugs."));
   1216  if (serial != keyframe.mSerial) {
   1217    // Serialno of page at offset isn't what the index told us to expect.
   1218    // Assume the index is invalid.
   1219    return RollbackIndexedSeek(aType, tell);
   1220  }
   1221  OggCodecState* codecState = mCodecStore.Get(serial);
   1222  if (codecState && codecState->mActive &&
   1223      sandbox_invoke(*mSandbox, ogg_stream_pagein, codecState->mState, page)
   1224              .unverified_safe_because(RLBOX_OGG_STATE_ASSERT_REASON) != 0) {
   1225    // Couldn't insert page into the ogg resource, or somehow the resource
   1226    // is no longer active.
   1227    return RollbackIndexedSeek(aType, tell);
   1228  }
   1229  return SEEK_OK;
   1230 }
   1231 
   1232 // Reads a page from the media resource.
   1233 OggDemuxer::PageSyncResult OggDemuxer::PageSync(
   1234    rlbox_sandbox_ogg* aSandbox, MediaResourceIndex* aResource,
   1235    tainted_opaque_ogg<ogg_sync_state*> aState, bool aCachedDataOnly,
   1236    int64_t aOffset, int64_t aEndOffset, tainted_ogg<ogg_page*> aPage,
   1237    int& aSkippedBytes) {
   1238  aSkippedBytes = 0;
   1239  // Sync to the next page.
   1240  tainted_ogg<int> ret = 0;
   1241  uint32_t bytesRead = 0;
   1242  int64_t readHead = aOffset;
   1243  while (ret.unverified_safe_because(RLBOX_OGG_STATE_ASSERT_REASON) <= 0) {
   1244    tainted_ogg<long> seek_ret =
   1245        sandbox_invoke(*aSandbox, ogg_sync_pageseek, aState, aPage);
   1246 
   1247    // We aren't really verifying the value of seek_ret below.
   1248    // We are merely ensuring that it won't overflow an integer.
   1249    // However we are assigning the value to ret which is marked tainted, so
   1250    // this is fine.
   1251    bool failedVerify = false;
   1252    CheckedInt<int> checker;
   1253    ret = CopyAndVerifyOrFail(
   1254        seek_ret, (static_cast<void>(checker = val), checker.isValid()),
   1255        &failedVerify);
   1256    if (failedVerify) {
   1257      return PAGE_SYNC_ERROR;
   1258    }
   1259 
   1260    if (ret.unverified_safe_because(RLBOX_OGG_STATE_ASSERT_REASON) == 0) {
   1261      const int page_step_val = PAGE_STEP;
   1262      tainted_ogg<char*> buffer_tainted =
   1263          sandbox_invoke(*aSandbox, ogg_sync_buffer, aState, page_step_val);
   1264      MOZ_ASSERT(buffer_tainted != nullptr, "Must have a buffer");
   1265 
   1266      // Read from the file into the buffer
   1267      int64_t bytesToRead =
   1268          std::min(static_cast<int64_t>(PAGE_STEP), aEndOffset - readHead);
   1269      MOZ_ASSERT(bytesToRead <= UINT32_MAX, "bytesToRead range check");
   1270      if (bytesToRead <= 0) {
   1271        return PAGE_SYNC_END_OF_RANGE;
   1272      }
   1273      char* buffer = buffer_tainted.copy_and_verify_buffer_address(
   1274          [](uintptr_t val) { return reinterpret_cast<char*>(val); },
   1275          static_cast<size_t>(bytesToRead));
   1276 
   1277      nsresult rv = NS_OK;
   1278      if (aCachedDataOnly) {
   1279        rv = aResource->GetResource()->ReadFromCache(
   1280            buffer, readHead, static_cast<uint32_t>(bytesToRead));
   1281        NS_ENSURE_SUCCESS(rv, PAGE_SYNC_ERROR);
   1282        bytesRead = static_cast<uint32_t>(bytesToRead);
   1283      } else {
   1284        rv = aResource->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
   1285        NS_ENSURE_SUCCESS(rv, PAGE_SYNC_ERROR);
   1286        rv = aResource->Read(buffer, static_cast<uint32_t>(bytesToRead),
   1287                             &bytesRead);
   1288        NS_ENSURE_SUCCESS(rv, PAGE_SYNC_ERROR);
   1289      }
   1290      if (bytesRead == 0 && NS_SUCCEEDED(rv)) {
   1291        // End of file.
   1292        return PAGE_SYNC_END_OF_RANGE;
   1293      }
   1294      readHead += bytesRead;
   1295 
   1296      // Update the synchronisation layer with the number
   1297      // of bytes written to the buffer
   1298      ret = sandbox_invoke(*aSandbox, ogg_sync_wrote, aState, bytesRead);
   1299      NS_ENSURE_TRUE(
   1300          ret.unverified_safe_because(RLBOX_OGG_STATE_ASSERT_REASON) == 0,
   1301          PAGE_SYNC_ERROR);
   1302      continue;
   1303    }
   1304 
   1305    if (ret.unverified_safe_because(RLBOX_OGG_STATE_ASSERT_REASON) < 0) {
   1306      MOZ_ASSERT(aSkippedBytes >= 0, "Offset >= 0");
   1307      bool failedSkippedBytesVerify = false;
   1308      ret.copy_and_verify([&](int val) {
   1309        int64_t result = static_cast<int64_t>(aSkippedBytes) - val;
   1310        if (result > std::numeric_limits<int>::max() ||
   1311            result > (aEndOffset - aOffset) || result < 0) {
   1312          failedSkippedBytesVerify = true;
   1313        } else {
   1314          aSkippedBytes = AssertedCast<int>(result);
   1315        }
   1316      });
   1317      if (failedSkippedBytesVerify) {
   1318        return PAGE_SYNC_ERROR;
   1319      }
   1320      continue;
   1321    }
   1322  }
   1323 
   1324  return PAGE_SYNC_OK;
   1325 }
   1326 
   1327 // OggTrackDemuxer
   1328 OggTrackDemuxer::OggTrackDemuxer(OggDemuxer* aParent,
   1329                                 TrackInfo::TrackType aType,
   1330                                 uint32_t aTrackNumber)
   1331    : mParent(aParent), mType(aType) {
   1332  mInfo = mParent->GetTrackInfo(aType, aTrackNumber);
   1333  MOZ_ASSERT(mInfo);
   1334 }
   1335 
   1336 OggTrackDemuxer::~OggTrackDemuxer() = default;
   1337 
   1338 UniquePtr<TrackInfo> OggTrackDemuxer::GetInfo() const { return mInfo->Clone(); }
   1339 
   1340 RefPtr<OggTrackDemuxer::SeekPromise> OggTrackDemuxer::Seek(
   1341    const TimeUnit& aTime) {
   1342  // Seeks to aTime. Upon success, SeekPromise will be resolved with the
   1343  // actual time seeked to. Typically the random access point time
   1344  mQueuedSample = nullptr;
   1345  TimeUnit seekTime = aTime;
   1346  if (mParent->SeekInternal(mType, aTime) == NS_OK) {
   1347    RefPtr<MediaRawData> sample(NextSample());
   1348 
   1349    // Check what time we actually seeked to.
   1350    if (sample != nullptr) {
   1351      seekTime = sample->mTime;
   1352      OGG_DEBUG("%p seeked to time %" PRId64, this, seekTime.ToMicroseconds());
   1353    }
   1354    mQueuedSample = sample;
   1355 
   1356    return SeekPromise::CreateAndResolve(seekTime, __func__);
   1357  }
   1358  return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__);
   1359 }
   1360 
   1361 RefPtr<MediaRawData> OggTrackDemuxer::NextSample() {
   1362  OGG_DEBUG("OggTrackDemuxer::NextSample");
   1363  if (mQueuedSample) {
   1364    RefPtr<MediaRawData> nextSample = mQueuedSample;
   1365    mQueuedSample = nullptr;
   1366    if (mType == TrackInfo::kAudioTrack) {
   1367      nextSample->mTrackInfo = mParent->mSharedAudioTrackInfo;
   1368    }
   1369    OGG_DEBUG("OggTrackDemuxer::NextSample (queued)");
   1370    return nextSample;
   1371  }
   1372  ogg_packet* packet = mParent->GetNextPacket(mType);
   1373  if (!packet) {
   1374    return nullptr;
   1375  }
   1376  // Check the eos state in case we need to look for chained streams.
   1377  bool eos = packet->e_o_s;
   1378  OggCodecState* state = mParent->GetTrackCodecState(mType);
   1379  RefPtr<MediaRawData> data = state->PacketOutAsMediaRawData();
   1380  // ogg allows 'nil' packets, that are EOS and of size 0.
   1381  if (!data || (data->mEOS && data->Size() == 0)) {
   1382    return nullptr;
   1383  }
   1384  if (mType == TrackInfo::kAudioTrack) {
   1385    data->mTrackInfo = mParent->mSharedAudioTrackInfo;
   1386  }
   1387  // mDecodedAudioDuration gets adjusted during ReadOggChain().
   1388  TimeUnit totalDuration = mParent->mDecodedAudioDuration;
   1389  if (eos) {
   1390    // We've encountered an end of bitstream packet; check for a chained
   1391    // bitstream following this one.
   1392    // This will also update mSharedAudioTrackInfo.
   1393    mParent->ReadOggChain(data->GetEndTime());
   1394  }
   1395  data->mOffset = mParent->Resource(mType)->Tell();
   1396  // We adjust the start time of the sample to account for the potential ogg
   1397  // chaining.
   1398  data->mTime += totalDuration;
   1399  if (!data->mTime.IsValid()) {
   1400    return nullptr;
   1401  }
   1402  TimeUnit mediaStartTime = mParent->mStartTime.valueOr(TimeUnit::Zero());
   1403  TimeUnit mediaEndTime =
   1404      mediaStartTime +
   1405      mParent->mInfo.mMetadataDuration.valueOr(TimeUnit::FromInfinity());
   1406  // Trim packets that end after the media duration.
   1407  if (mType == TrackInfo::kAudioTrack) {
   1408    OGG_DEBUG("Check trimming %s > %s", data->GetEndTime().ToString().get(),
   1409              mediaEndTime.ToString().get());
   1410    // Because of a quirk of this demuxer, this needs to be >=. It looks
   1411    // useless, because `toTrim` is going to be 0, but it allows setting
   1412    // `mOriginalPresentationWindow`, so that the trimming logic will later
   1413    // remove extraneous frames.
   1414    // This demuxer sets the end time of a packet to be the end time that
   1415    // should be played, not the end time that corresponds to the number of
   1416    // decoded frames, that we can only have after decoding.
   1417    // >= allows detecting the last packet, and trimming it appropriately,
   1418    // after decoding has happened, with the AudioTrimmer.
   1419    if (data->GetEndTime() >= mediaEndTime) {
   1420      TimeUnit toTrim = data->GetEndTime() - mediaEndTime;
   1421      TimeUnit originalDuration = data->mDuration;
   1422      OGG_DEBUG(
   1423          "Demuxed past media end time, trimming: packet [%s,%s] to [%s,%s]",
   1424          data->mTime.ToString().get(), data->GetEndTime().ToString().get(),
   1425          data->mTime.ToString().get(),
   1426          (data->mTime + originalDuration).ToString().get());
   1427      data->mOriginalPresentationWindow =
   1428          Some(TimeInterval{data->mTime, data->GetEndTime()});
   1429      data->mDuration -= toTrim;
   1430      if (data->mDuration.IsNegative()) {
   1431        data->mDuration = TimeUnit::Zero(data->mTime);
   1432      }
   1433    }
   1434  }
   1435 
   1436  OGG_DEBUG("OGG packet demuxed: [%s,%s] (duration: %s)",
   1437            data->mTime.ToString().get(), data->GetEndTime().ToString().get(),
   1438            data->mDuration.ToString().get());
   1439 
   1440  return data;
   1441 }
   1442 
   1443 RefPtr<OggTrackDemuxer::SamplesPromise> OggTrackDemuxer::GetSamples(
   1444    int32_t aNumSamples) {
   1445  RefPtr<SamplesHolder> samples = new SamplesHolder;
   1446  if (!aNumSamples) {
   1447    return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
   1448                                           __func__);
   1449  }
   1450 
   1451  while (aNumSamples) {
   1452    RefPtr<MediaRawData> sample(NextSample());
   1453    if (!sample) {
   1454      break;
   1455    }
   1456    if (!sample->HasValidTime()) {
   1457      return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
   1458                                             __func__);
   1459    }
   1460    samples->AppendSample(std::move(sample));
   1461    aNumSamples--;
   1462  }
   1463 
   1464  if (samples->GetSamples().IsEmpty()) {
   1465    return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM,
   1466                                           __func__);
   1467  }
   1468  return SamplesPromise::CreateAndResolve(samples, __func__);
   1469 }
   1470 
   1471 void OggTrackDemuxer::Reset() {
   1472  mParent->Reset(mType);
   1473  mQueuedSample = nullptr;
   1474 }
   1475 
   1476 RefPtr<OggTrackDemuxer::SkipAccessPointPromise>
   1477 OggTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold) {
   1478  uint32_t parsed = 0;
   1479  bool found = false;
   1480  RefPtr<MediaRawData> sample;
   1481 
   1482  OGG_DEBUG("TimeThreshold: %f", aTimeThreshold.ToSeconds());
   1483  while (!found && (sample = NextSample())) {
   1484    parsed++;
   1485    if (sample->mKeyframe && sample->mTime >= aTimeThreshold) {
   1486      found = true;
   1487      mQueuedSample = sample;
   1488    }
   1489  }
   1490  if (found) {
   1491    OGG_DEBUG("next sample: %f (parsed: %d)", sample->mTime.ToSeconds(),
   1492              parsed);
   1493    return SkipAccessPointPromise::CreateAndResolve(parsed, __func__);
   1494  }
   1495  SkipFailureHolder failure(NS_ERROR_DOM_MEDIA_END_OF_STREAM, parsed);
   1496  return SkipAccessPointPromise::CreateAndReject(std::move(failure), __func__);
   1497 }
   1498 
   1499 TimeIntervals OggTrackDemuxer::GetBuffered() {
   1500  return mParent->GetBuffered(mType);
   1501 }
   1502 
   1503 void OggTrackDemuxer::BreakCycles() { mParent = nullptr; }
   1504 
   1505 // Returns an ogg page's checksum.
   1506 tainted_opaque_ogg<ogg_uint32_t> OggDemuxer::GetPageChecksum(
   1507    tainted_opaque_ogg<ogg_page*> aPage) {
   1508  tainted_ogg<ogg_page*> page = rlbox::from_opaque(aPage);
   1509 
   1510  const char hint_reason[] =
   1511      "Early bail out of checksum. Even if this is wrong, the renderer's "
   1512      "security is not compromised.";
   1513  if (page == nullptr ||
   1514      (page->header == nullptr).unverified_safe_because(hint_reason) ||
   1515      (page->header_len < 25).unverified_safe_because(hint_reason)) {
   1516    tainted_ogg<ogg_uint32_t> ret = 0;
   1517    return ret.to_opaque();
   1518  }
   1519 
   1520  const int CHECKSUM_BYTES_LENGTH = 4;
   1521  const unsigned char* p =
   1522      (page->header + 22u)
   1523          .copy_and_verify_buffer_address(
   1524              [](uintptr_t val) {
   1525                return reinterpret_cast<const unsigned char*>(val);
   1526              },
   1527              CHECKSUM_BYTES_LENGTH);
   1528  uint32_t c =
   1529      static_cast<uint32_t>(p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24));
   1530  tainted_ogg<uint32_t> ret = c;
   1531  return ret.to_opaque();
   1532 }
   1533 
   1534 TimeUnit OggDemuxer::RangeStartTime(TrackInfo::TrackType aType,
   1535                                    int64_t aOffset) {
   1536  int64_t position = Resource(aType)->Tell();
   1537  nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
   1538  NS_ENSURE_SUCCESS(res, TimeUnit::Zero());
   1539  TimeUnit startTime = TimeUnit::Zero();
   1540  FindStartTime(aType, startTime);
   1541  res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, position);
   1542  NS_ENSURE_SUCCESS(res, TimeUnit::Invalid());
   1543  return startTime;
   1544 }
   1545 
   1546 struct nsDemuxerAutoOggSyncState {
   1547  explicit nsDemuxerAutoOggSyncState(rlbox_sandbox_ogg& aSandbox)
   1548      : mSandbox(aSandbox) {
   1549    mState = mSandbox.malloc_in_sandbox<ogg_sync_state>();
   1550    MOZ_RELEASE_ASSERT(mState != nullptr);
   1551    sandbox_invoke(mSandbox, ogg_sync_init, mState);
   1552  }
   1553  ~nsDemuxerAutoOggSyncState() {
   1554    sandbox_invoke(mSandbox, ogg_sync_clear, mState);
   1555    mSandbox.free_in_sandbox(mState);
   1556  }
   1557  rlbox_sandbox_ogg& mSandbox;
   1558  tainted_ogg<ogg_sync_state*> mState{};
   1559 };
   1560 
   1561 TimeUnit OggDemuxer::RangeEndTime(TrackInfo::TrackType aType,
   1562                                  int64_t aEndOffset) {
   1563  int64_t position = Resource(aType)->Tell();
   1564  TimeUnit endTime = RangeEndTime(aType, 0, aEndOffset, false);
   1565  nsresult res =
   1566      Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, position);
   1567  NS_ENSURE_SUCCESS(res, TimeUnit::Invalid());
   1568  return endTime;
   1569 }
   1570 
   1571 TimeUnit OggDemuxer::RangeEndTime(TrackInfo::TrackType aType,
   1572                                  int64_t aStartOffset, int64_t aEndOffset,
   1573                                  bool aCachedDataOnly) {
   1574  nsDemuxerAutoOggSyncState sync(*mSandbox);
   1575 
   1576  // We need to find the last page which ends before aEndOffset that
   1577  // has a granulepos that we can convert to a timestamp. We do this by
   1578  // backing off from aEndOffset until we encounter a page on which we can
   1579  // interpret the granulepos. If while backing off we encounter a page which
   1580  // we've previously encountered before, we'll either backoff again if we
   1581  // haven't found an end time yet, or return the last end time found.
   1582  const int step = 5000;
   1583  const int maxOggPageSize = 65306;
   1584  int64_t readStartOffset = aEndOffset;
   1585  int64_t readLimitOffset = aEndOffset;
   1586  int64_t readHead = aEndOffset;
   1587  TimeUnit endTime = TimeUnit::Invalid();
   1588  uint32_t checksumAfterSeek = 0;
   1589  uint32_t prevChecksumAfterSeek = 0;
   1590  bool mustBackOff = false;
   1591  tainted_ogg<ogg_page*> page = mSandbox->malloc_in_sandbox<ogg_page>();
   1592  if (!page) {
   1593    return TimeUnit::Invalid();
   1594  }
   1595  auto clean_page = MakeScopeExit([&] { mSandbox->free_in_sandbox(page); });
   1596  while (true) {
   1597    tainted_ogg<long> seek_ret =
   1598        sandbox_invoke(*mSandbox, ogg_sync_pageseek, sync.mState, page);
   1599 
   1600    // We aren't really verifying the value of seek_ret below.
   1601    // We are merely ensuring that it won't overflow an integer.
   1602    // However we are assigning the value to ret which is marked tainted, so
   1603    // this is fine.
   1604    bool failedVerify = false;
   1605    CheckedInt<int> checker;
   1606    tainted_ogg<int> ret = CopyAndVerifyOrFail(
   1607        seek_ret, (static_cast<void>(checker = val), checker.isValid()),
   1608        &failedVerify);
   1609    if (failedVerify) {
   1610      return TimeUnit::Invalid();
   1611    }
   1612 
   1613    if (ret.unverified_safe_because(RLBOX_OGG_STATE_ASSERT_REASON) == 0) {
   1614      // We need more data if we've not encountered a page we've seen before,
   1615      // or we've read to the end of file.
   1616      if (mustBackOff || readHead == aEndOffset || readHead == aStartOffset) {
   1617        if (endTime.IsValid() || readStartOffset == 0) {
   1618          // We have encountered a page before, or we're at the end of file.
   1619          break;
   1620        }
   1621        mustBackOff = false;
   1622        prevChecksumAfterSeek = checksumAfterSeek;
   1623        checksumAfterSeek = 0;
   1624        sandbox_invoke(*mSandbox, ogg_sync_reset, sync.mState);
   1625        readStartOffset =
   1626            std::max(static_cast<int64_t>(0), readStartOffset - step);
   1627        // There's no point reading more than the maximum size of
   1628        // an Ogg page into data we've previously scanned. Any data
   1629        // between readLimitOffset and aEndOffset must be garbage
   1630        // and we can ignore it thereafter.
   1631        readLimitOffset =
   1632            std::min(readLimitOffset, readStartOffset + maxOggPageSize);
   1633        readHead = std::max(aStartOffset, readStartOffset);
   1634      }
   1635 
   1636      int64_t limit =
   1637          std::min(static_cast<int64_t>(UINT32_MAX), aEndOffset - readHead);
   1638      limit = std::max(static_cast<int64_t>(0), limit);
   1639      limit = std::min(limit, static_cast<int64_t>(step));
   1640      uint32_t bytesToRead = static_cast<uint32_t>(limit);
   1641      uint32_t bytesRead = 0;
   1642      tainted_ogg<char*> buffer_tainted =
   1643          sandbox_invoke(*mSandbox, ogg_sync_buffer, sync.mState, bytesToRead);
   1644      char* buffer = buffer_tainted.copy_and_verify_buffer_address(
   1645          [](uintptr_t val) { return reinterpret_cast<char*>(val); },
   1646          bytesToRead);
   1647      MOZ_ASSERT(buffer, "Must have buffer");
   1648      nsresult res;
   1649      if (aCachedDataOnly) {
   1650        res = Resource(aType)->GetResource()->ReadFromCache(buffer, readHead,
   1651                                                            bytesToRead);
   1652        NS_ENSURE_SUCCESS(res, TimeUnit::Invalid());
   1653        bytesRead = bytesToRead;
   1654      } else {
   1655        MOZ_ASSERT(readHead < aEndOffset,
   1656                   "resource pos must be before range end");
   1657        res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
   1658        NS_ENSURE_SUCCESS(res, TimeUnit::Invalid());
   1659        res = Resource(aType)->Read(buffer, bytesToRead, &bytesRead);
   1660        NS_ENSURE_SUCCESS(res, TimeUnit::Invalid());
   1661      }
   1662      readHead += bytesRead;
   1663      if (readHead > readLimitOffset) {
   1664        mustBackOff = true;
   1665      }
   1666 
   1667      // Update the synchronisation layer with the number
   1668      // of bytes written to the buffer
   1669      ret = sandbox_invoke(*mSandbox, ogg_sync_wrote, sync.mState, bytesRead);
   1670      bool failedWroteVerify = false;
   1671      int wrote_success =
   1672          CopyAndVerifyOrFail(ret, val == 0 || val == -1, &failedWroteVerify);
   1673      if (failedWroteVerify) {
   1674        return TimeUnit::Invalid();
   1675      }
   1676 
   1677      if (wrote_success != 0) {
   1678        endTime = TimeUnit::Invalid();
   1679        break;
   1680      }
   1681      continue;
   1682    }
   1683 
   1684    if (ret.unverified_safe_because(RLBOX_OGG_STATE_ASSERT_REASON) < 0 ||
   1685        sandbox_invoke(*mSandbox, ogg_page_granulepos, page)
   1686                .unverified_safe_because(RLBOX_OGG_STATE_ASSERT_REASON) < 0) {
   1687      continue;
   1688    }
   1689 
   1690    tainted_ogg<uint32_t> checksum_tainted =
   1691        rlbox::from_opaque(GetPageChecksum(page.to_opaque()));
   1692    uint32_t checksum = checksum_tainted.unverified_safe_because(
   1693        "checksum is only being used as a hint as part of search for end time. "
   1694        "Incorrect values will not affect the memory safety of the renderer.");
   1695    if (checksumAfterSeek == 0) {
   1696      // This is the first page we've decoded after a backoff/seek. Remember
   1697      // the page checksum. If we backoff further and encounter this page
   1698      // again, we'll know that we won't find a page with an end time after
   1699      // this one, so we'll know to back off again.
   1700      checksumAfterSeek = checksum;
   1701    }
   1702    if (checksum == prevChecksumAfterSeek) {
   1703      // This page has the same checksum as the first page we encountered
   1704      // after the last backoff/seek. Since we've already scanned after this
   1705      // page and failed to find an end time, we may as well backoff again and
   1706      // try to find an end time from an earlier page.
   1707      mustBackOff = true;
   1708      continue;
   1709    }
   1710 
   1711    int64_t granulepos =
   1712        sandbox_invoke(*mSandbox, ogg_page_granulepos, page)
   1713            .unverified_safe_because(
   1714                "If this is incorrect it may lead to incorrect seeking "
   1715                "behavior in the stream, however will not affect the memory "
   1716                "safety of the Firefox renderer.");
   1717    uint32_t serial = static_cast<uint32_t>(
   1718        sandbox_invoke(*mSandbox, ogg_page_serialno, page)
   1719            .unverified_safe_because(RLBOX_OGG_PAGE_SERIAL_REASON));
   1720 
   1721    OggCodecState* codecState = nullptr;
   1722    codecState = mCodecStore.Get(serial);
   1723    if (!codecState) {
   1724      // This page is from a bitstream which we haven't encountered yet.
   1725      // It's probably from a new "link" in a "chained" ogg. Don't
   1726      // bother even trying to find a duration...
   1727      SetChained();
   1728      endTime = TimeUnit::Invalid();
   1729      break;
   1730    }
   1731 
   1732    TimeUnit t = codecState->Time(granulepos);
   1733    if (t.IsValid()) {
   1734      endTime = t;
   1735    }
   1736  }
   1737 
   1738  return endTime;
   1739 }
   1740 
   1741 nsresult OggDemuxer::GetSeekRanges(TrackInfo::TrackType aType,
   1742                                   nsTArray<SeekRange>& aRanges) {
   1743  AutoPinned<MediaResource> resource(Resource(aType)->GetResource());
   1744  MediaByteRangeSet cached;
   1745  nsresult res = resource->GetCachedRanges(cached);
   1746  NS_ENSURE_SUCCESS(res, res);
   1747 
   1748  for (uint32_t index = 0; index < cached.Length(); index++) {
   1749    const auto& range = cached[index];
   1750    TimeUnit startTime = TimeUnit::Invalid();
   1751    TimeUnit endTime = TimeUnit::Invalid();
   1752    if (NS_FAILED(Reset(aType))) {
   1753      return NS_ERROR_FAILURE;
   1754    }
   1755    int64_t startOffset = range.mStart;
   1756    int64_t endOffset = range.mEnd;
   1757    startTime = RangeStartTime(aType, startOffset);
   1758    if (startTime.IsValid() &&
   1759        ((endTime = RangeEndTime(aType, endOffset)).IsValid())) {
   1760      NS_WARNING_ASSERTION(startTime < endTime,
   1761                           "Start time must be before end time");
   1762      aRanges.AppendElement(
   1763          SeekRange(startOffset, endOffset, startTime, endTime));
   1764    }
   1765  }
   1766  if (NS_FAILED(Reset(aType))) {
   1767    return NS_ERROR_FAILURE;
   1768  }
   1769  return NS_OK;
   1770 }
   1771 
   1772 OggDemuxer::SeekRange OggDemuxer::SelectSeekRange(
   1773    TrackInfo::TrackType aType, const nsTArray<SeekRange>& ranges,
   1774    const TimeUnit& aTarget, const TimeUnit& aStartTime,
   1775    const TimeUnit& aEndTime, bool aExact) {
   1776  int64_t so = 0;
   1777  int64_t eo = Resource(aType)->GetLength();
   1778  TimeUnit st = aStartTime;
   1779  TimeUnit et = aEndTime;
   1780  for (uint32_t i = 0; i < ranges.Length(); i++) {
   1781    const SeekRange& r = ranges[i];
   1782    if (r.mTimeStart < aTarget) {
   1783      so = r.mOffsetStart;
   1784      st = r.mTimeStart;
   1785    }
   1786    if (r.mTimeEnd >= aTarget && r.mTimeEnd < et) {
   1787      eo = r.mOffsetEnd;
   1788      et = r.mTimeEnd;
   1789    }
   1790 
   1791    if (r.mTimeStart < aTarget && aTarget <= r.mTimeEnd) {
   1792      // Target lies exactly in this range.
   1793      return ranges[i];
   1794    }
   1795  }
   1796  if (aExact || eo == -1) {
   1797    return SeekRange();
   1798  }
   1799  return SeekRange(so, eo, st, et);
   1800 }
   1801 
   1802 nsresult OggDemuxer::SeekInBufferedRange(TrackInfo::TrackType aType,
   1803                                         const TimeUnit& aTarget,
   1804                                         TimeUnit& aAdjustedTarget,
   1805                                         const TimeUnit& aStartTime,
   1806                                         const TimeUnit& aEndTime,
   1807                                         const nsTArray<SeekRange>& aRanges,
   1808                                         const SeekRange& aRange) {
   1809  OGG_DEBUG("Seeking in buffered data to %s using bisection search",
   1810            aTarget.ToString().get());
   1811  if (aAdjustedTarget >= aTarget) {
   1812    // We know the exact byte range in which the target must lie. It must
   1813    // be buffered in the media cache. Seek there.
   1814    return SeekBisection(aType, aTarget, aRange, TimeUnit::Zero());
   1815  }
   1816  SeekRange k = SelectSeekRange(aType, aRanges, aAdjustedTarget, aStartTime,
   1817                                aEndTime, false);
   1818  return SeekBisection(aType, aAdjustedTarget, k, OGG_SEEK_FUZZ_USECS);
   1819 }
   1820 
   1821 nsresult OggDemuxer::SeekInUnbuffered(TrackInfo::TrackType aType,
   1822                                      const TimeUnit& aTarget,
   1823                                      const TimeUnit& aStartTime,
   1824                                      const TimeUnit& aEndTime,
   1825                                      const nsTArray<SeekRange>& aRanges) {
   1826  OGG_DEBUG("Seeking in unbuffered data to %s using bisection search",
   1827            aTarget.ToString().get());
   1828 
   1829  TimeUnit keyframeOffset = TimeUnit::Zero();
   1830  // Add in the Opus pre-roll if necessary.
   1831  if (aType == TrackInfo::kAudioTrack && mOpusState) {
   1832    keyframeOffset = std::max(keyframeOffset, OGG_SEEK_OPUS_PREROLL);
   1833  }
   1834  TimeUnit seekTarget = std::max(aStartTime, aTarget - keyframeOffset);
   1835  // Minimize the bisection search space using the known timestamps from the
   1836  // buffered ranges.
   1837  SeekRange k =
   1838      SelectSeekRange(aType, aRanges, seekTarget, aStartTime, aEndTime, false);
   1839  return SeekBisection(aType, seekTarget, k, OGG_SEEK_FUZZ_USECS);
   1840 }
   1841 
   1842 nsresult OggDemuxer::SeekBisection(TrackInfo::TrackType aType,
   1843                                   const TimeUnit& aTarget,
   1844                                   const SeekRange& aRange,
   1845                                   const TimeUnit& aFuzz) {
   1846  nsresult res;
   1847 
   1848  if (aTarget <= aRange.mTimeStart) {
   1849    if (NS_FAILED(Reset(aType))) {
   1850      return NS_ERROR_FAILURE;
   1851    }
   1852    res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, 0);
   1853    NS_ENSURE_SUCCESS(res, res);
   1854    return NS_OK;
   1855  }
   1856 
   1857  // Bisection search, find start offset of last page with end time less than
   1858  // the seek target.
   1859  ogg_int64_t startOffset = aRange.mOffsetStart;
   1860  ogg_int64_t startTime = aRange.mTimeStart.ToMicroseconds();
   1861  ogg_int64_t startLength = 0;  // Length of the page at startOffset.
   1862  ogg_int64_t endOffset = aRange.mOffsetEnd;
   1863  ogg_int64_t endTime = aRange.mTimeEnd.ToMicroseconds();
   1864 
   1865  ogg_int64_t seekTarget = aTarget.ToMicroseconds();
   1866  int64_t seekLowerBound =
   1867      std::max(static_cast<int64_t>(0),
   1868               aTarget.ToMicroseconds() - aFuzz.ToMicroseconds());
   1869  int hops = 0;
   1870  DebugOnly<ogg_int64_t> previousGuess = -1;
   1871  int backsteps = 0;
   1872  const int maxBackStep = 10;
   1873  MOZ_ASSERT(
   1874      static_cast<uint64_t>(PAGE_STEP) * pow(2.0, maxBackStep) < INT32_MAX,
   1875      "Backstep calculation must not overflow");
   1876 
   1877  // Seek via bisection search. Loop until we find the offset where the page
   1878  // before the offset is before the seek target, and the page after the offset
   1879  // is after the seek target.
   1880  tainted_ogg<ogg_page*> page = mSandbox->malloc_in_sandbox<ogg_page>();
   1881  if (!page) {
   1882    return NS_ERROR_OUT_OF_MEMORY;
   1883  }
   1884  auto clean_page = MakeScopeExit([&] { mSandbox->free_in_sandbox(page); });
   1885  while (true) {
   1886    ogg_int64_t duration = 0;
   1887    double target = 0;
   1888    ogg_int64_t interval = 0;
   1889    ogg_int64_t guess = 0;
   1890    int skippedBytes = 0;
   1891    ogg_int64_t pageOffset = 0;
   1892    ogg_int64_t pageLength = 0;
   1893    ogg_int64_t granuleTime = -1;
   1894    bool mustBackoff = false;
   1895 
   1896    // Guess where we should bisect to, based on the bit rate and the time
   1897    // remaining in the interval. Loop until we can determine the time at
   1898    // the guess offset.
   1899    while (true) {
   1900      // Discard any previously buffered packets/pages.
   1901      if (NS_FAILED(Reset(aType))) {
   1902        return NS_ERROR_FAILURE;
   1903      }
   1904 
   1905      interval = endOffset - startOffset - startLength;
   1906      if (interval == 0) {
   1907        // Our interval is empty, we've found the optimal seek point, as the
   1908        // page at the start offset is before the seek target, and the page
   1909        // at the end offset is after the seek target.
   1910        SEEK_LOG(LogLevel::Debug,
   1911                 ("Interval narrowed, terminating bisection."));
   1912        break;
   1913      }
   1914 
   1915      // Guess bisection point.
   1916      duration = endTime - startTime;
   1917      target = (double)(seekTarget - startTime) / (double)duration;
   1918      guess = startOffset + startLength +
   1919              static_cast<ogg_int64_t>((double)interval * target);
   1920      guess = std::min(guess, endOffset - PAGE_STEP);
   1921      if (mustBackoff) {
   1922        // We previously failed to determine the time at the guess offset,
   1923        // probably because we ran out of data to decode. This usually happens
   1924        // when we guess very close to the end offset. So reduce the guess
   1925        // offset using an exponential backoff until we determine the time.
   1926        SEEK_LOG(
   1927            LogLevel::Debug,
   1928            ("Backing off %d bytes, backsteps=%d",
   1929             static_cast<int32_t>(PAGE_STEP * pow(2.0, backsteps)), backsteps));
   1930        guess -= PAGE_STEP * static_cast<ogg_int64_t>(pow(2.0, backsteps));
   1931 
   1932        if (guess <= startOffset) {
   1933          // We've tried to backoff to before the start offset of our seek
   1934          // range. This means we couldn't find a seek termination position
   1935          // near the end of the seek range, so just set the seek termination
   1936          // condition, and break out of the bisection loop. We'll begin
   1937          // decoding from the start of the seek range.
   1938          interval = 0;
   1939          break;
   1940        }
   1941        backsteps = std::min(backsteps + 1, maxBackStep);
   1942      } else {
   1943        backsteps = 0;
   1944      }
   1945      guess = std::max(guess, startOffset + startLength);
   1946 
   1947      SEEK_LOG(LogLevel::Debug,
   1948               ("Seek loop start[o=%lld..%lld t=%lld] "
   1949                "end[o=%lld t=%lld] "
   1950                "interval=%lld target=%lf guess=%lld",
   1951                startOffset, (startOffset + startLength), startTime, endOffset,
   1952                endTime, interval, target, guess));
   1953 
   1954      MOZ_ASSERT(guess >= startOffset + startLength,
   1955                 "Guess must be after range start");
   1956      MOZ_ASSERT(guess < endOffset, "Guess must be before range end");
   1957      MOZ_ASSERT(guess != previousGuess,
   1958                 "Guess should be different to previous");
   1959      previousGuess = guess;
   1960 
   1961      hops++;
   1962 
   1963      // Locate the next page after our seek guess, and then figure out the
   1964      // granule time of the audio bitstreams there. We can then
   1965      // make a bisection decision based on our location in the media.
   1966      PageSyncResult pageSyncResult =
   1967          PageSync(mSandbox.get(), Resource(aType), OggSyncState(aType), false,
   1968                   guess, endOffset, page, skippedBytes);
   1969      NS_ENSURE_TRUE(pageSyncResult != PAGE_SYNC_ERROR, NS_ERROR_FAILURE);
   1970 
   1971      if (pageSyncResult == PAGE_SYNC_END_OF_RANGE) {
   1972        // Our guess was too close to the end, we've ended up reading the end
   1973        // page. Backoff exponentially from the end point, in case the last
   1974        // page/frame/sample is huge.
   1975        mustBackoff = true;
   1976        SEEK_LOG(LogLevel::Debug, ("Hit the end of range, backing off"));
   1977        continue;
   1978      }
   1979 
   1980      // We've located a page of length |ret| at |guess + skippedBytes|.
   1981      // Remember where the page is located.
   1982      pageOffset = guess + skippedBytes;
   1983 
   1984      bool failedPageLenVerify = false;
   1985      // Page length should be under 64Kb according to
   1986      // https://xiph.org/ogg/doc/libogg/ogg_page.html
   1987      pageLength = CopyAndVerifyOrFail(page->header_len + page->body_len,
   1988                                       val <= 64 * 1024, &failedPageLenVerify);
   1989      if (failedPageLenVerify) {
   1990        return NS_ERROR_FAILURE;
   1991      }
   1992 
   1993      // Read pages until we can determine the granule time of the audio
   1994      // bitstream.
   1995      ogg_int64_t audioTime = -1;
   1996      do {
   1997        // Add the page to its codec state, determine its granule time.
   1998        uint32_t serial = static_cast<uint32_t>(
   1999            sandbox_invoke(*mSandbox, ogg_page_serialno, page)
   2000                .unverified_safe_because(RLBOX_OGG_PAGE_SERIAL_REASON));
   2001        OggCodecState* codecState = mCodecStore.Get(serial);
   2002        if (codecState && GetCodecStateType(codecState) == aType) {
   2003          if (codecState->mActive) {
   2004            int ret =
   2005                sandbox_invoke(*mSandbox, ogg_stream_pagein, codecState->mState,
   2006                               page)
   2007                    .unverified_safe_because(RLBOX_OGG_STATE_ASSERT_REASON);
   2008            NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
   2009          }
   2010 
   2011          ogg_int64_t granulepos =
   2012              sandbox_invoke(*mSandbox, ogg_page_granulepos, page)
   2013                  .unverified_safe_because(
   2014                      "If this is incorrect it may lead to incorrect seeking "
   2015                      "behavior in the stream, however will not affect the "
   2016                      "memory safety of the Firefox renderer.");
   2017 
   2018          if (aType == TrackInfo::kAudioTrack && granulepos > 0 &&
   2019              audioTime == -1) {
   2020            if (mVorbisState && serial == mVorbisState->mSerial) {
   2021              audioTime = mVorbisState->Time(granulepos).ToMicroseconds();
   2022            } else if (mOpusState && serial == mOpusState->mSerial) {
   2023              audioTime = mOpusState->Time(granulepos).ToMicroseconds();
   2024            } else if (mFlacState && serial == mFlacState->mSerial) {
   2025              audioTime = mFlacState->Time(granulepos).ToMicroseconds();
   2026            }
   2027          }
   2028 
   2029          if (pageOffset + pageLength >= endOffset) {
   2030            // Hit end of readable data.
   2031            break;
   2032          }
   2033        }
   2034        if (!ReadOggPage(aType, page.to_opaque())) {
   2035          break;
   2036        }
   2037 
   2038      } while (aType == TrackInfo::kAudioTrack && audioTime == -1);
   2039 
   2040      if (aType == TrackInfo::kAudioTrack && audioTime == -1) {
   2041        // We don't have timestamps for all active tracks...
   2042        if (pageOffset == startOffset + startLength &&
   2043            pageOffset + pageLength >= endOffset) {
   2044          // We read the entire interval without finding timestamps for all
   2045          // active tracks. We know the interval start offset is before the seek
   2046          // target, and the interval end is after the seek target, and we can't
   2047          // terminate inside the interval, so we terminate the seek at the
   2048          // start of the interval.
   2049          interval = 0;
   2050          break;
   2051        }
   2052 
   2053        // We should backoff; cause the guess to back off from the end, so
   2054        // that we've got more room to capture.
   2055        mustBackoff = true;
   2056        continue;
   2057      }
   2058 
   2059      // We've found appropriate time stamps here. Proceed to bisect
   2060      // the search space.
   2061      granuleTime = audioTime;
   2062      MOZ_ASSERT(granuleTime > 0, "Must get a granuletime");
   2063      break;
   2064    }  // End of "until we determine time at guess offset" loop.
   2065 
   2066    if (interval == 0) {
   2067      // Seek termination condition; we've found the page boundary of the
   2068      // last page before the target, and the first page after the target.
   2069      SEEK_LOG(LogLevel::Debug,
   2070               ("Terminating seek at offset=%lld", startOffset));
   2071      MOZ_ASSERT(startTime < aTarget.ToMicroseconds(),
   2072                 "Start time must always be less than target");
   2073      res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, startOffset);
   2074      NS_ENSURE_SUCCESS(res, res);
   2075      if (NS_FAILED(Reset(aType))) {
   2076        return NS_ERROR_FAILURE;
   2077      }
   2078      break;
   2079    }
   2080 
   2081    SEEK_LOG(LogLevel::Debug,
   2082             ("Time at offset %lld is %lld", guess, granuleTime));
   2083    if (granuleTime < seekTarget && granuleTime > seekLowerBound) {
   2084      // We're within the fuzzy region in which we want to terminate the search.
   2085      res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, pageOffset);
   2086      NS_ENSURE_SUCCESS(res, res);
   2087      if (NS_FAILED(Reset(aType))) {
   2088        return NS_ERROR_FAILURE;
   2089      }
   2090      SEEK_LOG(LogLevel::Debug,
   2091               ("Terminating seek at offset=%lld", pageOffset));
   2092      break;
   2093    }
   2094 
   2095    if (granuleTime >= seekTarget) {
   2096      // We've landed after the seek target.
   2097      MOZ_ASSERT(pageOffset < endOffset, "offset_end must decrease");
   2098      endOffset = pageOffset;
   2099      endTime = granuleTime;
   2100    } else if (granuleTime < seekTarget) {
   2101      // Landed before seek target.
   2102      MOZ_ASSERT(pageOffset >= startOffset + startLength,
   2103                 "Bisection point should be at or after end of first page in "
   2104                 "interval");
   2105      startOffset = pageOffset;
   2106      startLength = pageLength;
   2107      startTime = granuleTime;
   2108    }
   2109    MOZ_ASSERT(startTime <= seekTarget, "Must be before seek target");
   2110    MOZ_ASSERT(endTime >= seekTarget, "End must be after seek target");
   2111  }
   2112 
   2113  (void)hops;
   2114  SEEK_LOG(LogLevel::Debug, ("Seek complete in %d bisections.", hops));
   2115 
   2116  return NS_OK;
   2117 }
   2118 
   2119 #undef OGG_DEBUG
   2120 #undef SEEK_LOG
   2121 #undef CopyAndVerifyOrFail
   2122 }  // namespace mozilla