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