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