1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "ChromiumCDMParent.h"
7
8 #include "ChromiumCDMCallback.h"
9 #include "ChromiumCDMCallbackProxy.h"
10 #include "ChromiumCDMProxy.h"
11 #include "content_decryption_module.h"
12 #include "GMPContentChild.h"
13 #include "GMPContentParent.h"
14 #include "GMPLog.h"
15 #include "GMPService.h"
16 #include "GMPUtils.h"
17 #include "mozilla/dom/MediaKeyMessageEventBinding.h"
18 #include "mozilla/gmp/GMPTypes.h"
19 #include "mozilla/ScopeExit.h"
20 #include "mozilla/StaticPrefs_media.h"
21 #include "mozilla/Unused.h"
22 #include "AnnexB.h"
23 #include "H264.h"
24
25 #define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
26
27 namespace mozilla::gmp {
28
29 using namespace eme;
30
ChromiumCDMParent(GMPContentParent * aContentParent,uint32_t aPluginId)31 ChromiumCDMParent::ChromiumCDMParent(GMPContentParent* aContentParent,
32 uint32_t aPluginId)
33 : mPluginId(aPluginId),
34 mContentParent(aContentParent),
35 mVideoShmemLimit(StaticPrefs::media_eme_chromium_api_video_shmems())
36 #ifdef DEBUG
37 ,
38 mGMPThread(aContentParent->GMPEventTarget())
39 #endif
40 {
41 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
42 GMP_LOG_DEBUG(
43 "ChromiumCDMParent::ChromiumCDMParent(this=%p, contentParent=%p, "
44 "id=%" PRIu32 ")",
45 this, aContentParent, aPluginId);
46 }
47
Init(ChromiumCDMCallback * aCDMCallback,bool aAllowDistinctiveIdentifier,bool aAllowPersistentState,nsIEventTarget * aMainThread)48 RefPtr<ChromiumCDMParent::InitPromise> ChromiumCDMParent::Init(
49 ChromiumCDMCallback* aCDMCallback, bool aAllowDistinctiveIdentifier,
50 bool aAllowPersistentState, nsIEventTarget* aMainThread) {
51 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
52 GMP_LOG_DEBUG(
53 "ChromiumCDMParent::Init(this=%p) shutdown=%s abormalShutdown=%s "
54 "actorDestroyed=%s",
55 this, mIsShutdown ? "true" : "false",
56 mAbnormalShutdown ? "true" : "false", mActorDestroyed ? "true" : "false");
57 if (!aCDMCallback || !aMainThread) {
58 GMP_LOG_DEBUG(
59 "ChromiumCDMParent::Init(this=%p) failed "
60 "nullCallback=%s nullMainThread=%s",
61 this, !aCDMCallback ? "true" : "false",
62 !aMainThread ? "true" : "false");
63
64 return ChromiumCDMParent::InitPromise::CreateAndReject(
65 MediaResult(NS_ERROR_FAILURE,
66 nsPrintfCString("ChromiumCDMParent::Init() failed "
67 "nullCallback=%s nullMainThread=%s",
68 !aCDMCallback ? "true" : "false",
69 !aMainThread ? "true" : "false")),
70 __func__);
71 }
72
73 RefPtr<ChromiumCDMParent::InitPromise> promise =
74 mInitPromise.Ensure(__func__);
75 RefPtr<ChromiumCDMParent> self = this;
76 SendInit(aAllowDistinctiveIdentifier, aAllowPersistentState)
77 ->Then(
78 GetCurrentSerialEventTarget(), __func__,
79 [self, aCDMCallback](bool aSuccess) {
80 if (!aSuccess) {
81 GMP_LOG_DEBUG(
82 "ChromiumCDMParent::Init() failed with callback from "
83 "child indicating CDM failed init");
84 self->mInitPromise.RejectIfExists(
85 MediaResult(NS_ERROR_FAILURE,
86 "ChromiumCDMParent::Init() failed with callback "
87 "from child indicating CDM failed init"),
88 __func__);
89 return;
90 }
91 GMP_LOG_DEBUG(
92 "ChromiumCDMParent::Init() succeeded with callback from child");
93 self->mCDMCallback = aCDMCallback;
94 self->mInitPromise.ResolveIfExists(true /* unused */, __func__);
95 },
96 [self](ResponseRejectReason&& aReason) {
97 RefPtr<gmp::GeckoMediaPluginService> service =
98 gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
99 bool xpcomWillShutdown =
100 service && service->XPCOMWillShutdownReceived();
101 GMP_LOG_DEBUG(
102 "ChromiumCDMParent::Init(this=%p) failed "
103 "shutdown=%s cdmCrash=%s actorDestroyed=%s "
104 "browserShutdown=%s promiseRejectReason=%d",
105 self.get(), self->mIsShutdown ? "true" : "false",
106 self->mAbnormalShutdown ? "true" : "false",
107 self->mActorDestroyed ? "true" : "false",
108 xpcomWillShutdown ? "true" : "false",
109 static_cast<int>(aReason));
110 self->mInitPromise.RejectIfExists(
111 MediaResult(
112 NS_ERROR_FAILURE,
113 nsPrintfCString("ChromiumCDMParent::Init() failed "
114 "shutdown=%s cdmCrash=%s actorDestroyed=%s "
115 "browserShutdown=%s promiseRejectReason=%d",
116 self->mIsShutdown ? "true" : "false",
117 self->mAbnormalShutdown ? "true" : "false",
118 self->mActorDestroyed ? "true" : "false",
119 xpcomWillShutdown ? "true" : "false",
120 static_cast<int>(aReason))),
121 __func__);
122 });
123 return promise;
124 }
125
CreateSession(uint32_t aCreateSessionToken,uint32_t aSessionType,uint32_t aInitDataType,uint32_t aPromiseId,const nsTArray<uint8_t> & aInitData)126 void ChromiumCDMParent::CreateSession(uint32_t aCreateSessionToken,
127 uint32_t aSessionType,
128 uint32_t aInitDataType,
129 uint32_t aPromiseId,
130 const nsTArray<uint8_t>& aInitData) {
131 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
132 GMP_LOG_DEBUG("ChromiumCDMParent::CreateSession(this=%p)", this);
133 if (mIsShutdown) {
134 RejectPromiseShutdown(aPromiseId);
135 return;
136 }
137 if (!SendCreateSessionAndGenerateRequest(aPromiseId, aSessionType,
138 aInitDataType, aInitData)) {
139 RejectPromiseWithStateError(
140 aPromiseId, "Failed to send generateRequest to CDM process."_ns);
141 return;
142 }
143 mPromiseToCreateSessionToken.InsertOrUpdate(aPromiseId, aCreateSessionToken);
144 }
145
LoadSession(uint32_t aPromiseId,uint32_t aSessionType,nsString aSessionId)146 void ChromiumCDMParent::LoadSession(uint32_t aPromiseId, uint32_t aSessionType,
147 nsString aSessionId) {
148 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
149 GMP_LOG_DEBUG("ChromiumCDMParent::LoadSession(this=%p, pid=%" PRIu32
150 ", type=%" PRIu32 ", sid=%s)",
151 this, aPromiseId, aSessionType,
152 NS_ConvertUTF16toUTF8(aSessionId).get());
153 if (mIsShutdown) {
154 RejectPromiseShutdown(aPromiseId);
155 return;
156 }
157 if (!SendLoadSession(aPromiseId, aSessionType,
158 NS_ConvertUTF16toUTF8(aSessionId))) {
159 RejectPromiseWithStateError(
160 aPromiseId, "Failed to send loadSession to CDM process."_ns);
161 return;
162 }
163 }
164
SetServerCertificate(uint32_t aPromiseId,const nsTArray<uint8_t> & aCert)165 void ChromiumCDMParent::SetServerCertificate(uint32_t aPromiseId,
166 const nsTArray<uint8_t>& aCert) {
167 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
168 GMP_LOG_DEBUG("ChromiumCDMParent::SetServerCertificate(this=%p)", this);
169 if (mIsShutdown) {
170 RejectPromiseShutdown(aPromiseId);
171 return;
172 }
173 if (!SendSetServerCertificate(aPromiseId, aCert)) {
174 RejectPromiseWithStateError(
175 aPromiseId, "Failed to send setServerCertificate to CDM process"_ns);
176 }
177 }
178
UpdateSession(const nsCString & aSessionId,uint32_t aPromiseId,const nsTArray<uint8_t> & aResponse)179 void ChromiumCDMParent::UpdateSession(const nsCString& aSessionId,
180 uint32_t aPromiseId,
181 const nsTArray<uint8_t>& aResponse) {
182 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
183 GMP_LOG_DEBUG("ChromiumCDMParent::UpdateSession(this=%p)", this);
184 if (mIsShutdown) {
185 RejectPromiseShutdown(aPromiseId);
186 return;
187 }
188 if (!SendUpdateSession(aPromiseId, aSessionId, aResponse)) {
189 RejectPromiseWithStateError(
190 aPromiseId, "Failed to send updateSession to CDM process"_ns);
191 }
192 }
193
CloseSession(const nsCString & aSessionId,uint32_t aPromiseId)194 void ChromiumCDMParent::CloseSession(const nsCString& aSessionId,
195 uint32_t aPromiseId) {
196 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
197 GMP_LOG_DEBUG("ChromiumCDMParent::CloseSession(this=%p)", this);
198 if (mIsShutdown) {
199 RejectPromiseShutdown(aPromiseId);
200 return;
201 }
202 if (!SendCloseSession(aPromiseId, aSessionId)) {
203 RejectPromiseWithStateError(
204 aPromiseId, "Failed to send closeSession to CDM process"_ns);
205 }
206 }
207
RemoveSession(const nsCString & aSessionId,uint32_t aPromiseId)208 void ChromiumCDMParent::RemoveSession(const nsCString& aSessionId,
209 uint32_t aPromiseId) {
210 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
211 GMP_LOG_DEBUG("ChromiumCDMParent::RemoveSession(this=%p)", this);
212 if (mIsShutdown) {
213 RejectPromiseShutdown(aPromiseId);
214 return;
215 }
216 if (!SendRemoveSession(aPromiseId, aSessionId)) {
217 RejectPromiseWithStateError(
218 aPromiseId, "Failed to send removeSession to CDM process"_ns);
219 }
220 }
221
NotifyOutputProtectionStatus(bool aSuccess,uint32_t aLinkMask,uint32_t aProtectionMask)222 void ChromiumCDMParent::NotifyOutputProtectionStatus(bool aSuccess,
223 uint32_t aLinkMask,
224 uint32_t aProtectionMask) {
225 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
226 GMP_LOG_DEBUG("ChromiumCDMParent::NotifyOutputProtectionStatus(this=%p)",
227 this);
228 if (mIsShutdown) {
229 return;
230 }
231 const bool haveCachedValue = mOutputProtectionLinkMask.isSome();
232 if (mAwaitingOutputProtectionInformation && !aSuccess) {
233 MOZ_DIAGNOSTIC_ASSERT(
234 !haveCachedValue,
235 "Should not have a cached value if we're still awaiting infomation");
236 // We're awaiting info and don't yet have a cached value, and the check
237 // failed, don't cache the result, just forward the failure.
238 CompleteQueryOutputProtectionStatus(false, aLinkMask, aProtectionMask);
239 return;
240 }
241 if (!mAwaitingOutputProtectionInformation && haveCachedValue && !aSuccess) {
242 // We're not awaiting info, already have a cached value, and the check
243 // failed. Ignore this, we'll update our info from any future successful
244 // checks.
245 return;
246 }
247 MOZ_ASSERT(aSuccess, "Failed checks should be handled by this point");
248 // Update our protection information.
249 mOutputProtectionLinkMask = Some(aLinkMask);
250
251 if (mAwaitingOutputProtectionInformation) {
252 // If we have an outstanding query, service that query with this
253 // information.
254 mAwaitingOutputProtectionInformation = false;
255 MOZ_ASSERT(!haveCachedValue,
256 "If we were waiting on information, we shouldn't have yet "
257 "cached a value");
258 CompleteQueryOutputProtectionStatus(true, mOutputProtectionLinkMask.value(),
259 aProtectionMask);
260 }
261 }
262
CompleteQueryOutputProtectionStatus(bool aSuccess,uint32_t aLinkMask,uint32_t aProtectionMask)263 void ChromiumCDMParent::CompleteQueryOutputProtectionStatus(
264 bool aSuccess, uint32_t aLinkMask, uint32_t aProtectionMask) {
265 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
266 GMP_LOG_DEBUG(
267 "ChromiumCDMParent::CompleteQueryOutputProtectionStatus(this=%p) "
268 "mIsShutdown=%s aSuccess=%s aLinkMask=%" PRIu32,
269 this, mIsShutdown ? "true" : "false", aSuccess ? "true" : "false",
270 aLinkMask);
271 if (mIsShutdown) {
272 return;
273 }
274 Unused << SendCompleteQueryOutputProtectionStatus(aSuccess, aLinkMask,
275 aProtectionMask);
276 }
277
278 // See
279 // https://cs.chromium.org/chromium/src/media/blink/webcontentdecryptionmodule_impl.cc?l=33-66&rcl=d49aa59ac8c2925d5bec229f3f1906537b6b4547
ToCDMHdcpVersion(const nsCString & aMinHdcpVersion)280 static Result<cdm::HdcpVersion, nsresult> ToCDMHdcpVersion(
281 const nsCString& aMinHdcpVersion) {
282 if (aMinHdcpVersion.IsEmpty()) {
283 return cdm::HdcpVersion::kHdcpVersionNone;
284 }
285 if (aMinHdcpVersion.EqualsIgnoreCase("1.0")) {
286 return cdm::HdcpVersion::kHdcpVersion1_0;
287 }
288 if (aMinHdcpVersion.EqualsIgnoreCase("1.1")) {
289 return cdm::HdcpVersion::kHdcpVersion1_1;
290 }
291 if (aMinHdcpVersion.EqualsIgnoreCase("1.2")) {
292 return cdm::HdcpVersion::kHdcpVersion1_2;
293 }
294 if (aMinHdcpVersion.EqualsIgnoreCase("1.3")) {
295 return cdm::HdcpVersion::kHdcpVersion1_3;
296 }
297 if (aMinHdcpVersion.EqualsIgnoreCase("1.4")) {
298 return cdm::HdcpVersion::kHdcpVersion1_4;
299 }
300 if (aMinHdcpVersion.EqualsIgnoreCase("2.0")) {
301 return cdm::HdcpVersion::kHdcpVersion2_0;
302 }
303 if (aMinHdcpVersion.EqualsIgnoreCase("2.1")) {
304 return cdm::HdcpVersion::kHdcpVersion2_1;
305 }
306 if (aMinHdcpVersion.EqualsIgnoreCase("2.2")) {
307 return cdm::HdcpVersion::kHdcpVersion2_2;
308 }
309 // When adding another version remember to update GMPMessageUtils so that we
310 // can serialize it correctly and have correct bounds on the enum!
311
312 // Invalid hdcp version string.
313 return Err(NS_ERROR_INVALID_ARG);
314 }
315
GetStatusForPolicy(uint32_t aPromiseId,const nsCString & aMinHdcpVersion)316 void ChromiumCDMParent::GetStatusForPolicy(uint32_t aPromiseId,
317 const nsCString& aMinHdcpVersion) {
318 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
319 GMP_LOG_DEBUG("ChromiumCDMParent::GetStatusForPolicy(this=%p)", this);
320 if (mIsShutdown) {
321 RejectPromiseShutdown(aPromiseId);
322 return;
323 }
324 auto hdcpVersionResult = ToCDMHdcpVersion(aMinHdcpVersion);
325 if (hdcpVersionResult.isErr()) {
326 ErrorResult rv;
327 // XXXbz there's no spec for this yet, and
328 // <https://github.com/WICG/hdcp-detection/blob/master/explainer.md>
329 // does not define what exceptions get thrown. Let's assume
330 // TypeError for invalid args, as usual.
331 constexpr auto err =
332 "getStatusForPolicy failed due to bad hdcp version argument"_ns;
333 rv.ThrowTypeError(err);
334 RejectPromise(aPromiseId, std::move(rv), err);
335 return;
336 }
337
338 if (!SendGetStatusForPolicy(aPromiseId, hdcpVersionResult.unwrap())) {
339 RejectPromiseWithStateError(
340 aPromiseId, "Failed to send getStatusForPolicy to CDM process"_ns);
341 }
342 }
343
InitCDMInputBuffer(gmp::CDMInputBuffer & aBuffer,MediaRawData * aSample)344 bool ChromiumCDMParent::InitCDMInputBuffer(gmp::CDMInputBuffer& aBuffer,
345 MediaRawData* aSample) {
346 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
347 const CryptoSample& crypto = aSample->mCrypto;
348 if (crypto.mEncryptedSizes.Length() != crypto.mPlainSizes.Length()) {
349 GMP_LOG_DEBUG("InitCDMInputBuffer clear/cipher subsamples don't match");
350 return false;
351 }
352
353 Shmem shmem;
354 if (!AllocShmem(aSample->Size(), Shmem::SharedMemory::TYPE_BASIC, &shmem)) {
355 return false;
356 }
357 memcpy(shmem.get<uint8_t>(), aSample->Data(), aSample->Size());
358 cdm::EncryptionScheme encryptionScheme = cdm::EncryptionScheme::kUnencrypted;
359 switch (crypto.mCryptoScheme) {
360 case CryptoScheme::None:
361 break; // Default to none
362 case CryptoScheme::Cenc:
363 encryptionScheme = cdm::EncryptionScheme::kCenc;
364 break;
365 case CryptoScheme::Cbcs:
366 encryptionScheme = cdm::EncryptionScheme::kCbcs;
367 break;
368 default:
369 GMP_LOG_DEBUG(
370 "InitCDMInputBuffer got unexpected encryption scheme with "
371 "value of %" PRIu8 ". Treating as no encryption.",
372 static_cast<uint8_t>(crypto.mCryptoScheme));
373 MOZ_ASSERT_UNREACHABLE("Should not have unrecognized encryption type");
374 break;
375 }
376
377 const nsTArray<uint8_t>& iv = encryptionScheme != cdm::EncryptionScheme::kCbcs
378 ? crypto.mIV
379 : crypto.mConstantIV;
380 aBuffer = gmp::CDMInputBuffer(
381 std::move(shmem), crypto.mKeyId, iv, aSample->mTime.ToMicroseconds(),
382 aSample->mDuration.ToMicroseconds(), crypto.mPlainSizes,
383 crypto.mEncryptedSizes, crypto.mCryptByteBlock, crypto.mSkipByteBlock,
384 encryptionScheme);
385 return true;
386 }
387
SendBufferToCDM(uint32_t aSizeInBytes)388 bool ChromiumCDMParent::SendBufferToCDM(uint32_t aSizeInBytes) {
389 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
390 GMP_LOG_DEBUG("ChromiumCDMParent::SendBufferToCDM() size=%" PRIu32,
391 aSizeInBytes);
392 Shmem shmem;
393 if (!AllocShmem(aSizeInBytes, Shmem::SharedMemory::TYPE_BASIC, &shmem)) {
394 return false;
395 }
396 if (!SendGiveBuffer(std::move(shmem))) {
397 DeallocShmem(shmem);
398 return false;
399 }
400 return true;
401 }
402
Decrypt(MediaRawData * aSample)403 RefPtr<DecryptPromise> ChromiumCDMParent::Decrypt(MediaRawData* aSample) {
404 if (mIsShutdown) {
405 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
406 return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
407 __func__);
408 }
409 CDMInputBuffer buffer;
410 if (!InitCDMInputBuffer(buffer, aSample)) {
411 return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
412 __func__);
413 }
414 // Send a buffer to the CDM to store the output. The CDM will either fill
415 // it with the decrypted sample and return it, or deallocate it on failure.
416 if (!SendBufferToCDM(aSample->Size())) {
417 DeallocShmem(buffer.mData());
418 return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
419 __func__);
420 }
421
422 RefPtr<DecryptJob> job = new DecryptJob(aSample);
423 if (!SendDecrypt(job->mId, buffer)) {
424 GMP_LOG_DEBUG(
425 "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message",
426 this);
427 DeallocShmem(buffer.mData());
428 return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
429 __func__);
430 }
431 RefPtr<DecryptPromise> promise = job->Ensure();
432 mDecrypts.AppendElement(job);
433 return promise;
434 }
435
Recv__delete__()436 ipc::IPCResult ChromiumCDMParent::Recv__delete__() {
437 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
438 MOZ_ASSERT(mIsShutdown);
439 GMP_LOG_DEBUG("ChromiumCDMParent::Recv__delete__(this=%p)", this);
440 if (mContentParent) {
441 mContentParent->ChromiumCDMDestroyed(this);
442 mContentParent = nullptr;
443 }
444 return IPC_OK();
445 }
446
RecvOnResolvePromiseWithKeyStatus(const uint32_t & aPromiseId,const uint32_t & aKeyStatus)447 ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromiseWithKeyStatus(
448 const uint32_t& aPromiseId, const uint32_t& aKeyStatus) {
449 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
450 GMP_LOG_DEBUG(
451 "ChromiumCDMParent::RecvOnResolvePromiseWithKeyStatus(this=%p, "
452 "pid=%" PRIu32 ", keystatus=%" PRIu32 ")",
453 this, aPromiseId, aKeyStatus);
454 if (!mCDMCallback || mIsShutdown) {
455 return IPC_OK();
456 }
457
458 mCDMCallback->ResolvePromiseWithKeyStatus(aPromiseId, aKeyStatus);
459
460 return IPC_OK();
461 }
462
RecvOnResolveNewSessionPromise(const uint32_t & aPromiseId,const nsCString & aSessionId)463 ipc::IPCResult ChromiumCDMParent::RecvOnResolveNewSessionPromise(
464 const uint32_t& aPromiseId, const nsCString& aSessionId) {
465 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
466 GMP_LOG_DEBUG(
467 "ChromiumCDMParent::RecvOnResolveNewSessionPromise(this=%p, pid=%" PRIu32
468 ", sid=%s)",
469 this, aPromiseId, aSessionId.get());
470 if (!mCDMCallback || mIsShutdown) {
471 return IPC_OK();
472 }
473
474 Maybe<uint32_t> token = mPromiseToCreateSessionToken.Extract(aPromiseId);
475 if (token.isNothing()) {
476 RejectPromiseWithStateError(aPromiseId,
477 "Lost session token for new session."_ns);
478 return IPC_OK();
479 }
480
481 mCDMCallback->SetSessionId(token.value(), aSessionId);
482
483 ResolvePromise(aPromiseId);
484
485 return IPC_OK();
486 }
487
RecvResolveLoadSessionPromise(const uint32_t & aPromiseId,const bool & aSuccessful)488 ipc::IPCResult ChromiumCDMParent::RecvResolveLoadSessionPromise(
489 const uint32_t& aPromiseId, const bool& aSuccessful) {
490 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
491 GMP_LOG_DEBUG(
492 "ChromiumCDMParent::RecvResolveLoadSessionPromise(this=%p, pid=%" PRIu32
493 ", successful=%d)",
494 this, aPromiseId, aSuccessful);
495 if (!mCDMCallback || mIsShutdown) {
496 return IPC_OK();
497 }
498
499 mCDMCallback->ResolveLoadSessionPromise(aPromiseId, aSuccessful);
500
501 return IPC_OK();
502 }
503
ResolvePromise(uint32_t aPromiseId)504 void ChromiumCDMParent::ResolvePromise(uint32_t aPromiseId) {
505 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
506 GMP_LOG_DEBUG("ChromiumCDMParent::ResolvePromise(this=%p, pid=%" PRIu32 ")",
507 this, aPromiseId);
508
509 // Note: The MediaKeys rejects all pending DOM promises when it
510 // initiates shutdown.
511 if (!mCDMCallback || mIsShutdown) {
512 return;
513 }
514
515 mCDMCallback->ResolvePromise(aPromiseId);
516 }
517
RecvOnResolvePromise(const uint32_t & aPromiseId)518 ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromise(
519 const uint32_t& aPromiseId) {
520 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
521 ResolvePromise(aPromiseId);
522 return IPC_OK();
523 }
524
RejectPromise(uint32_t aPromiseId,ErrorResult && aException,const nsCString & aErrorMessage)525 void ChromiumCDMParent::RejectPromise(uint32_t aPromiseId,
526 ErrorResult&& aException,
527 const nsCString& aErrorMessage) {
528 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
529 GMP_LOG_DEBUG("ChromiumCDMParent::RejectPromise(this=%p, pid=%" PRIu32 ")",
530 this, aPromiseId);
531 // Note: The MediaKeys rejects all pending DOM promises when it
532 // initiates shutdown.
533 if (!mCDMCallback || mIsShutdown) {
534 // Suppress the exception as it will not be explicitly handled due to the
535 // early return.
536 aException.SuppressException();
537 return;
538 }
539
540 mCDMCallback->RejectPromise(aPromiseId, std::move(aException), aErrorMessage);
541 }
542
RejectPromiseShutdown(uint32_t aPromiseId)543 void ChromiumCDMParent::RejectPromiseShutdown(uint32_t aPromiseId) {
544 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
545 RejectPromiseWithStateError(aPromiseId, "CDM is shutdown"_ns);
546 }
547
RejectPromiseWithStateError(uint32_t aPromiseId,const nsCString & aErrorMessage)548 void ChromiumCDMParent::RejectPromiseWithStateError(
549 uint32_t aPromiseId, const nsCString& aErrorMessage) {
550 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
551 ErrorResult rv;
552 rv.ThrowInvalidStateError(aErrorMessage);
553 RejectPromise(aPromiseId, std::move(rv), aErrorMessage);
554 }
555
ToErrorResult(uint32_t aException,const nsCString & aErrorMessage)556 static ErrorResult ToErrorResult(uint32_t aException,
557 const nsCString& aErrorMessage) {
558 // XXXbz could we have a CopyableErrorResult sent to us with a better error
559 // message?
560 ErrorResult rv;
561 switch (static_cast<cdm::Exception>(aException)) {
562 case cdm::Exception::kExceptionNotSupportedError:
563 rv.ThrowNotSupportedError(aErrorMessage);
564 break;
565 case cdm::Exception::kExceptionInvalidStateError:
566 rv.ThrowInvalidStateError(aErrorMessage);
567 break;
568 case cdm::Exception::kExceptionTypeError:
569 rv.ThrowTypeError(aErrorMessage);
570 break;
571 case cdm::Exception::kExceptionQuotaExceededError:
572 rv.ThrowQuotaExceededError(aErrorMessage);
573 break;
574 default:
575 MOZ_ASSERT_UNREACHABLE("Invalid cdm::Exception enum value.");
576 // Note: Unique placeholder.
577 rv.ThrowTimeoutError(aErrorMessage);
578 };
579 return rv;
580 }
581
RecvOnRejectPromise(const uint32_t & aPromiseId,const uint32_t & aException,const uint32_t & aSystemCode,const nsCString & aErrorMessage)582 ipc::IPCResult ChromiumCDMParent::RecvOnRejectPromise(
583 const uint32_t& aPromiseId, const uint32_t& aException,
584 const uint32_t& aSystemCode, const nsCString& aErrorMessage) {
585 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
586 RejectPromise(aPromiseId, ToErrorResult(aException, aErrorMessage),
587 aErrorMessage);
588 return IPC_OK();
589 }
590
RecvOnSessionMessage(const nsCString & aSessionId,const uint32_t & aMessageType,nsTArray<uint8_t> && aMessage)591 ipc::IPCResult ChromiumCDMParent::RecvOnSessionMessage(
592 const nsCString& aSessionId, const uint32_t& aMessageType,
593 nsTArray<uint8_t>&& aMessage) {
594 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
595 GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionMessage(this=%p, sid=%s)",
596 this, aSessionId.get());
597 if (!mCDMCallback || mIsShutdown) {
598 return IPC_OK();
599 }
600
601 mCDMCallback->SessionMessage(aSessionId, aMessageType, std::move(aMessage));
602 return IPC_OK();
603 }
604
RecvOnSessionKeysChange(const nsCString & aSessionId,nsTArray<CDMKeyInformation> && aKeysInfo)605 ipc::IPCResult ChromiumCDMParent::RecvOnSessionKeysChange(
606 const nsCString& aSessionId, nsTArray<CDMKeyInformation>&& aKeysInfo) {
607 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
608 GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionKeysChange(this=%p)", this);
609 if (!mCDMCallback || mIsShutdown) {
610 return IPC_OK();
611 }
612
613 mCDMCallback->SessionKeysChange(aSessionId, std::move(aKeysInfo));
614 return IPC_OK();
615 }
616
RecvOnExpirationChange(const nsCString & aSessionId,const double & aSecondsSinceEpoch)617 ipc::IPCResult ChromiumCDMParent::RecvOnExpirationChange(
618 const nsCString& aSessionId, const double& aSecondsSinceEpoch) {
619 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
620 GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnExpirationChange(this=%p) time=%lf",
621 this, aSecondsSinceEpoch);
622 if (!mCDMCallback || mIsShutdown) {
623 return IPC_OK();
624 }
625
626 mCDMCallback->ExpirationChange(aSessionId, aSecondsSinceEpoch);
627 return IPC_OK();
628 }
629
RecvOnSessionClosed(const nsCString & aSessionId)630 ipc::IPCResult ChromiumCDMParent::RecvOnSessionClosed(
631 const nsCString& aSessionId) {
632 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
633 GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionClosed(this=%p)", this);
634 if (!mCDMCallback || mIsShutdown) {
635 return IPC_OK();
636 }
637
638 mCDMCallback->SessionClosed(aSessionId);
639 return IPC_OK();
640 }
641
RecvOnQueryOutputProtectionStatus()642 ipc::IPCResult ChromiumCDMParent::RecvOnQueryOutputProtectionStatus() {
643 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
644 GMP_LOG_DEBUG(
645 "ChromiumCDMParent::RecvOnQueryOutputProtectionStatus(this=%p) "
646 "mIsShutdown=%s mCDMCallback=%s mAwaitingOutputProtectionInformation=%s",
647 this, mIsShutdown ? "true" : "false", mCDMCallback ? "true" : "false",
648 mAwaitingOutputProtectionInformation ? "true" : "false");
649 if (mIsShutdown) {
650 // We're shutdown, don't try to service the query.
651 return IPC_OK();
652 }
653 if (!mCDMCallback) {
654 // We don't have a callback. We're not yet outputting anything so can report
655 // we're safe.
656 CompleteQueryOutputProtectionStatus(true, uint32_t{}, uint32_t{});
657 return IPC_OK();
658 }
659
660 if (mOutputProtectionLinkMask.isSome()) {
661 MOZ_DIAGNOSTIC_ASSERT(
662 !mAwaitingOutputProtectionInformation,
663 "If we have a cached value we should not be awaiting information");
664 // We have a cached value, use that.
665 CompleteQueryOutputProtectionStatus(true, mOutputProtectionLinkMask.value(),
666 uint32_t{});
667 return IPC_OK();
668 }
669
670 // We need to call up the stack to get the info. The CDM proxy will finish
671 // the request via `NotifyOutputProtectionStatus`.
672 mAwaitingOutputProtectionInformation = true;
673 mCDMCallback->QueryOutputProtectionStatus();
674 return IPC_OK();
675 }
676
ToDecryptStatus(uint32_t aStatus)677 DecryptStatus ToDecryptStatus(uint32_t aStatus) {
678 switch (static_cast<cdm::Status>(aStatus)) {
679 case cdm::kSuccess:
680 return DecryptStatus::Ok;
681 case cdm::kNoKey:
682 return DecryptStatus::NoKeyErr;
683 default:
684 return DecryptStatus::GenericErr;
685 }
686 }
687
RecvDecryptFailed(const uint32_t & aId,const uint32_t & aStatus)688 ipc::IPCResult ChromiumCDMParent::RecvDecryptFailed(const uint32_t& aId,
689 const uint32_t& aStatus) {
690 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
691 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecryptFailed(this=%p, id=%" PRIu32
692 ", status=%" PRIu32 ")",
693 this, aId, aStatus);
694
695 if (mIsShutdown) {
696 MOZ_ASSERT(mDecrypts.IsEmpty());
697 return IPC_OK();
698 }
699
700 for (size_t i = 0; i < mDecrypts.Length(); i++) {
701 if (mDecrypts[i]->mId == aId) {
702 mDecrypts[i]->PostResult(ToDecryptStatus(aStatus));
703 mDecrypts.RemoveElementAt(i);
704 break;
705 }
706 }
707 return IPC_OK();
708 }
709
RecvDecrypted(const uint32_t & aId,const uint32_t & aStatus,ipc::Shmem && aShmem)710 ipc::IPCResult ChromiumCDMParent::RecvDecrypted(const uint32_t& aId,
711 const uint32_t& aStatus,
712 ipc::Shmem&& aShmem) {
713 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
714 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecrypted(this=%p, id=%" PRIu32
715 ", status=%" PRIu32 ")",
716 this, aId, aStatus);
717
718 // We must deallocate the shmem once we've copied the result out of it
719 // in PostResult below.
720 auto autoDeallocateShmem = MakeScopeExit([&, this] { DeallocShmem(aShmem); });
721
722 if (mIsShutdown) {
723 MOZ_ASSERT(mDecrypts.IsEmpty());
724 return IPC_OK();
725 }
726 for (size_t i = 0; i < mDecrypts.Length(); i++) {
727 if (mDecrypts[i]->mId == aId) {
728 mDecrypts[i]->PostResult(
729 ToDecryptStatus(aStatus),
730 Span<const uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
731 mDecrypts.RemoveElementAt(i);
732 break;
733 }
734 }
735 return IPC_OK();
736 }
737
RecvIncreaseShmemPoolSize()738 ipc::IPCResult ChromiumCDMParent::RecvIncreaseShmemPoolSize() {
739 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
740 GMP_LOG_DEBUG("%s(this=%p) limit=%" PRIu32 " active=%" PRIu32, __func__, this,
741 mVideoShmemLimit, mVideoShmemsActive);
742
743 // Put an upper limit on the number of shmems we tolerate the CDM asking
744 // for, to prevent a memory blow-out. In practice, we expect the CDM to
745 // need less than 5, but some encodings require more.
746 // We'd expect CDMs to not have video frames larger than 720p-1080p
747 // (due to DRM robustness requirements), which is about 1.5MB-3MB per
748 // frame.
749 if (mVideoShmemLimit > 50) {
750 mDecodePromise.RejectIfExists(
751 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
752 RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
753 __func__);
754 Shutdown();
755 return IPC_OK();
756 }
757 mVideoShmemLimit++;
758
759 EnsureSufficientShmems(mVideoFrameBufferSize);
760
761 return IPC_OK();
762 }
763
PurgeShmems()764 bool ChromiumCDMParent::PurgeShmems() {
765 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
766 GMP_LOG_DEBUG(
767 "ChromiumCDMParent::PurgeShmems(this=%p) frame_size=%zu"
768 " limit=%" PRIu32 " active=%" PRIu32,
769 this, mVideoFrameBufferSize, mVideoShmemLimit, mVideoShmemsActive);
770
771 if (mVideoShmemsActive == 0) {
772 // We haven't allocated any shmems, nothing to do here.
773 return true;
774 }
775 if (!SendPurgeShmems()) {
776 return false;
777 }
778 mVideoShmemsActive = 0;
779 return true;
780 }
781
EnsureSufficientShmems(size_t aVideoFrameSize)782 bool ChromiumCDMParent::EnsureSufficientShmems(size_t aVideoFrameSize) {
783 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
784 GMP_LOG_DEBUG(
785 "ChromiumCDMParent::EnsureSufficientShmems(this=%p) "
786 "size=%zu expected_size=%zu limit=%" PRIu32 " active=%" PRIu32,
787 this, aVideoFrameSize, mVideoFrameBufferSize, mVideoShmemLimit,
788 mVideoShmemsActive);
789
790 // The Chromium CDM API requires us to implement a synchronous
791 // interface to supply buffers to the CDM for it to write decrypted samples
792 // into. We want our buffers to be backed by shmems, in order to reduce
793 // the overhead of transferring decoded frames. However due to sandboxing
794 // restrictions, the CDM process cannot allocate shmems itself.
795 // We don't want to be doing synchronous IPC to request shmems from the
796 // content process, nor do we want to have to do intr IPC or make async
797 // IPC conform to the sync allocation interface. So instead we have the
798 // content process pre-allocate a set of shmems and give them to the CDM
799 // process in advance of them being needed.
800 //
801 // When the CDM needs to allocate a buffer for storing a decoded video
802 // frame, the CDM host gives it one of these shmems' buffers. When this
803 // is sent back to the content process, we upload it to a GPU surface,
804 // and send the shmem back to the CDM process so it can reuse it.
805 //
806 // Normally the CDM won't allocate more than one buffer at once, but
807 // we've seen cases where it allocates multiple buffers, returns one and
808 // holds onto the rest. So we need to ensure we have several extra
809 // shmems pre-allocated for the CDM. This threshold is set by the pref
810 // media.eme.chromium-api.video-shmems.
811 //
812 // We also have a failure recovery mechanism; if the CDM asks for more
813 // buffers than we have shmem's available, ChromiumCDMChild gives the
814 // CDM a non-shared memory buffer, and returns the frame to the parent
815 // in an nsTArray<uint8_t> instead of a shmem. The child then sends a
816 // message to the parent asking it to increase the number of shmems in
817 // the pool. Via this mechanism we should recover from incorrectly
818 // predicting how many shmems to pre-allocate.
819 //
820 // At decoder start up, we guess how big the shmems need to be based on
821 // the video frame dimensions. If we guess wrong, the CDM will follow
822 // the non-shmem path, and we'll re-create the shmems of the correct size.
823 // This meanns we can recover from guessing the shmem size wrong.
824 // We must re-take this path after every decoder de-init/re-init, as the
825 // frame sizes should change every time we switch video stream.
826
827 if (mVideoFrameBufferSize < aVideoFrameSize) {
828 if (!PurgeShmems()) {
829 return false;
830 }
831 mVideoFrameBufferSize = aVideoFrameSize;
832 }
833
834 while (mVideoShmemsActive < mVideoShmemLimit) {
835 if (!SendBufferToCDM(mVideoFrameBufferSize)) {
836 return false;
837 }
838 mVideoShmemsActive++;
839 }
840
841 return true;
842 }
843
RecvDecodedData(const CDMVideoFrame & aFrame,nsTArray<uint8_t> && aData)844 ipc::IPCResult ChromiumCDMParent::RecvDecodedData(const CDMVideoFrame& aFrame,
845 nsTArray<uint8_t>&& aData) {
846 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
847 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodedData(this=%p) time=%" PRId64,
848 this, aFrame.mTimestamp());
849
850 if (mIsShutdown || mDecodePromise.IsEmpty()) {
851 return IPC_OK();
852 }
853
854 if (!EnsureSufficientShmems(aData.Length())) {
855 mDecodePromise.RejectIfExists(
856 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
857 RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
858 __func__);
859 return IPC_OK();
860 }
861
862 RefPtr<VideoData> v = CreateVideoFrame(aFrame, aData);
863 if (!v) {
864 mDecodePromise.RejectIfExists(
865 MediaResult(NS_ERROR_OUT_OF_MEMORY,
866 RESULT_DETAIL("Can't create VideoData")),
867 __func__);
868 return IPC_OK();
869 }
870
871 ReorderAndReturnOutput(std::move(v));
872
873 return IPC_OK();
874 }
875
RecvDecodedShmem(const CDMVideoFrame & aFrame,ipc::Shmem && aShmem)876 ipc::IPCResult ChromiumCDMParent::RecvDecodedShmem(const CDMVideoFrame& aFrame,
877 ipc::Shmem&& aShmem) {
878 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
879 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodedShmem(this=%p) time=%" PRId64
880 " duration=%" PRId64,
881 this, aFrame.mTimestamp(), aFrame.mDuration());
882
883 // On failure we need to deallocate the shmem we're to return to the
884 // CDM. On success we return it to the CDM to be reused.
885 auto autoDeallocateShmem =
886 MakeScopeExit([&, this] { this->DeallocShmem(aShmem); });
887
888 if (mIsShutdown || mDecodePromise.IsEmpty()) {
889 return IPC_OK();
890 }
891
892 RefPtr<VideoData> v = CreateVideoFrame(
893 aFrame, Span<uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
894 if (!v) {
895 mDecodePromise.RejectIfExists(
896 MediaResult(NS_ERROR_OUT_OF_MEMORY,
897 RESULT_DETAIL("Can't create VideoData")),
898 __func__);
899 return IPC_OK();
900 }
901
902 // Return the shmem to the CDM so the shmem can be reused to send us
903 // another frame.
904 if (!SendGiveBuffer(std::move(aShmem))) {
905 mDecodePromise.RejectIfExists(
906 MediaResult(NS_ERROR_OUT_OF_MEMORY,
907 RESULT_DETAIL("Can't return shmem to CDM process")),
908 __func__);
909 return IPC_OK();
910 }
911
912 // Don't need to deallocate the shmem since the CDM process is responsible
913 // for it again.
914 autoDeallocateShmem.release();
915
916 ReorderAndReturnOutput(std::move(v));
917
918 return IPC_OK();
919 }
920
ReorderAndReturnOutput(RefPtr<VideoData> && aFrame)921 void ChromiumCDMParent::ReorderAndReturnOutput(RefPtr<VideoData>&& aFrame) {
922 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
923 if (mMaxRefFrames == 0) {
924 mDecodePromise.ResolveIfExists(
925 MediaDataDecoder::DecodedData({std::move(aFrame)}), __func__);
926 return;
927 }
928 mReorderQueue.Push(std::move(aFrame));
929 MediaDataDecoder::DecodedData results;
930 while (mReorderQueue.Length() > mMaxRefFrames) {
931 results.AppendElement(mReorderQueue.Pop());
932 }
933 mDecodePromise.Resolve(std::move(results), __func__);
934 }
935
CreateVideoFrame(const CDMVideoFrame & aFrame,Span<uint8_t> aData)936 already_AddRefed<VideoData> ChromiumCDMParent::CreateVideoFrame(
937 const CDMVideoFrame& aFrame, Span<uint8_t> aData) {
938 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
939 VideoData::YCbCrBuffer b;
940 MOZ_ASSERT(aData.Length() > 0);
941
942 // Since we store each plane separately we can just roll the offset
943 // into our pointer to that plane and store that.
944 b.mPlanes[0].mData = aData.Elements() + aFrame.mYPlane().mPlaneOffset();
945 b.mPlanes[0].mWidth = aFrame.mImageWidth();
946 b.mPlanes[0].mHeight = aFrame.mImageHeight();
947 b.mPlanes[0].mStride = aFrame.mYPlane().mStride();
948 b.mPlanes[0].mSkip = 0;
949
950 b.mPlanes[1].mData = aData.Elements() + aFrame.mUPlane().mPlaneOffset();
951 b.mPlanes[1].mWidth = (aFrame.mImageWidth() + 1) / 2;
952 b.mPlanes[1].mHeight = (aFrame.mImageHeight() + 1) / 2;
953 b.mPlanes[1].mStride = aFrame.mUPlane().mStride();
954 b.mPlanes[1].mSkip = 0;
955
956 b.mPlanes[2].mData = aData.Elements() + aFrame.mVPlane().mPlaneOffset();
957 b.mPlanes[2].mWidth = (aFrame.mImageWidth() + 1) / 2;
958 b.mPlanes[2].mHeight = (aFrame.mImageHeight() + 1) / 2;
959 b.mPlanes[2].mStride = aFrame.mVPlane().mStride();
960 b.mPlanes[2].mSkip = 0;
961
962 // We unfortunately can't know which colorspace the video is using at this
963 // stage.
964 b.mYUVColorSpace =
965 DefaultColorSpace({aFrame.mImageWidth(), aFrame.mImageHeight()});
966
967 gfx::IntRect pictureRegion(0, 0, aFrame.mImageWidth(), aFrame.mImageHeight());
968 RefPtr<VideoData> v = VideoData::CreateAndCopyData(
969 mVideoInfo, mImageContainer, mLastStreamOffset,
970 media::TimeUnit::FromMicroseconds(aFrame.mTimestamp()),
971 media::TimeUnit::FromMicroseconds(aFrame.mDuration()), b, false,
972 media::TimeUnit::FromMicroseconds(-1), pictureRegion);
973
974 return v.forget();
975 }
976
RecvDecodeFailed(const uint32_t & aStatus)977 ipc::IPCResult ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus) {
978 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
979 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodeFailed(this=%p status=%" PRIu32
980 ")",
981 this, aStatus);
982 if (mIsShutdown) {
983 MOZ_ASSERT(mDecodePromise.IsEmpty());
984 return IPC_OK();
985 }
986
987 if (aStatus == cdm::kNeedMoreData) {
988 mDecodePromise.ResolveIfExists(nsTArray<RefPtr<MediaData>>(), __func__);
989 return IPC_OK();
990 }
991
992 mDecodePromise.RejectIfExists(
993 MediaResult(
994 NS_ERROR_DOM_MEDIA_FATAL_ERR,
995 RESULT_DETAIL(
996 "ChromiumCDMParent::RecvDecodeFailed with status %s (%" PRIu32
997 ")",
998 CdmStatusToString(aStatus), aStatus)),
999 __func__);
1000 return IPC_OK();
1001 }
1002
RecvShutdown()1003 ipc::IPCResult ChromiumCDMParent::RecvShutdown() {
1004 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1005 GMP_LOG_DEBUG("ChromiumCDMParent::RecvShutdown(this=%p)", this);
1006 Shutdown();
1007 return IPC_OK();
1008 }
1009
ActorDestroy(ActorDestroyReason aWhy)1010 void ChromiumCDMParent::ActorDestroy(ActorDestroyReason aWhy) {
1011 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1012 GMP_LOG_DEBUG("ChromiumCDMParent::ActorDestroy(this=%p, reason=%d)", this,
1013 aWhy);
1014 MOZ_ASSERT(!mActorDestroyed);
1015 mActorDestroyed = true;
1016 // Shutdown() will clear mCDMCallback, so let's keep a reference for later
1017 // use.
1018 auto callback = mCDMCallback;
1019 if (!mIsShutdown) {
1020 // Plugin crash.
1021 MOZ_ASSERT(aWhy == AbnormalShutdown);
1022 Shutdown();
1023 }
1024 MOZ_ASSERT(mIsShutdown);
1025 RefPtr<ChromiumCDMParent> kungFuDeathGrip(this);
1026 if (mContentParent) {
1027 mContentParent->ChromiumCDMDestroyed(this);
1028 mContentParent = nullptr;
1029 }
1030 mAbnormalShutdown = (aWhy == AbnormalShutdown);
1031 if (mAbnormalShutdown && callback) {
1032 callback->Terminated();
1033 }
1034 MaybeDisconnect(mAbnormalShutdown);
1035 }
1036
InitializeVideoDecoder(const gmp::CDMVideoDecoderConfig & aConfig,const VideoInfo & aInfo,RefPtr<layers::ImageContainer> aImageContainer)1037 RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMParent::InitializeVideoDecoder(
1038 const gmp::CDMVideoDecoderConfig& aConfig, const VideoInfo& aInfo,
1039 RefPtr<layers::ImageContainer> aImageContainer) {
1040 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1041 if (mIsShutdown) {
1042 return MediaDataDecoder::InitPromise::CreateAndReject(
1043 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1044 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1045 __func__);
1046 }
1047
1048 // The Widevine CDM version 1.4.8.970 and above contain a video decoder that
1049 // does not optimally allocate video frames; it requests buffers much larger
1050 // than required. The exact formula the CDM uses to calculate their frame
1051 // sizes isn't obvious, but they normally request around or slightly more
1052 // than 1.5X the optimal amount. So pad the size of buffers we allocate so
1053 // that we're likely to have buffers big enough to accomodate the CDM's weird
1054 // frame size calculation.
1055 const size_t bufferSize =
1056 1.7 * I420FrameBufferSizePadded(aInfo.mImage.width, aInfo.mImage.height);
1057 if (bufferSize <= 0) {
1058 return MediaDataDecoder::InitPromise::CreateAndReject(
1059 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1060 RESULT_DETAIL("Video frame buffer size is invalid.")),
1061 __func__);
1062 }
1063
1064 if (!EnsureSufficientShmems(bufferSize)) {
1065 return MediaDataDecoder::InitPromise::CreateAndReject(
1066 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1067 RESULT_DETAIL("Failed to init shmems for video decoder")),
1068 __func__);
1069 }
1070
1071 if (!SendInitializeVideoDecoder(aConfig)) {
1072 return MediaDataDecoder::InitPromise::CreateAndReject(
1073 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1074 RESULT_DETAIL("Failed to send init video decoder to CDM")),
1075 __func__);
1076 }
1077
1078 mMaxRefFrames = (aConfig.mCodec() == cdm::VideoCodec::kCodecH264)
1079 ? H264::HasSPS(aInfo.mExtraData)
1080 ? H264::ComputeMaxRefFrames(aInfo.mExtraData)
1081 : 16
1082 : 0;
1083
1084 mVideoDecoderInitialized = true;
1085 mImageContainer = aImageContainer;
1086 mVideoInfo = aInfo;
1087 mVideoFrameBufferSize = bufferSize;
1088
1089 return mInitVideoDecoderPromise.Ensure(__func__);
1090 }
1091
RecvOnDecoderInitDone(const uint32_t & aStatus)1092 ipc::IPCResult ChromiumCDMParent::RecvOnDecoderInitDone(
1093 const uint32_t& aStatus) {
1094 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1095 GMP_LOG_DEBUG(
1096 "ChromiumCDMParent::RecvOnDecoderInitDone(this=%p, status=%" PRIu32 ")",
1097 this, aStatus);
1098 if (mIsShutdown) {
1099 MOZ_ASSERT(mInitVideoDecoderPromise.IsEmpty());
1100 return IPC_OK();
1101 }
1102 if (aStatus == static_cast<uint32_t>(cdm::kSuccess)) {
1103 mInitVideoDecoderPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
1104 } else {
1105 mVideoDecoderInitialized = false;
1106 mInitVideoDecoderPromise.RejectIfExists(
1107 MediaResult(
1108 NS_ERROR_DOM_MEDIA_FATAL_ERR,
1109 RESULT_DETAIL("CDM init decode failed with status %s (%" PRIu32 ")",
1110 CdmStatusToString(aStatus), aStatus)),
1111 __func__);
1112 }
1113 return IPC_OK();
1114 }
1115
1116 RefPtr<MediaDataDecoder::DecodePromise>
DecryptAndDecodeFrame(MediaRawData * aSample)1117 ChromiumCDMParent::DecryptAndDecodeFrame(MediaRawData* aSample) {
1118 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1119 if (mIsShutdown) {
1120 return MediaDataDecoder::DecodePromise::CreateAndReject(
1121 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1122 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1123 __func__);
1124 }
1125
1126 GMP_LOG_DEBUG("ChromiumCDMParent::DecryptAndDecodeFrame t=%" PRId64,
1127 aSample->mTime.ToMicroseconds());
1128
1129 CDMInputBuffer buffer;
1130
1131 if (!InitCDMInputBuffer(buffer, aSample)) {
1132 return MediaDataDecoder::DecodePromise::CreateAndReject(
1133 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to init CDM buffer."),
1134 __func__);
1135 }
1136
1137 mLastStreamOffset = aSample->mOffset;
1138
1139 if (!SendDecryptAndDecodeFrame(buffer)) {
1140 GMP_LOG_DEBUG(
1141 "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message.",
1142 this);
1143 DeallocShmem(buffer.mData());
1144 return MediaDataDecoder::DecodePromise::CreateAndReject(
1145 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1146 "Failed to send decrypt to CDM process."),
1147 __func__);
1148 }
1149
1150 return mDecodePromise.Ensure(__func__);
1151 }
1152
FlushVideoDecoder()1153 RefPtr<MediaDataDecoder::FlushPromise> ChromiumCDMParent::FlushVideoDecoder() {
1154 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1155 if (mIsShutdown) {
1156 MOZ_ASSERT(mReorderQueue.IsEmpty());
1157 return MediaDataDecoder::FlushPromise::CreateAndReject(
1158 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1159 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1160 __func__);
1161 }
1162
1163 mReorderQueue.Clear();
1164
1165 mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
1166 if (!SendResetVideoDecoder()) {
1167 return MediaDataDecoder::FlushPromise::CreateAndReject(
1168 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1169 "Failed to send flush to CDM."),
1170 __func__);
1171 }
1172 return mFlushDecoderPromise.Ensure(__func__);
1173 }
1174
RecvResetVideoDecoderComplete()1175 ipc::IPCResult ChromiumCDMParent::RecvResetVideoDecoderComplete() {
1176 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1177 MOZ_ASSERT(mReorderQueue.IsEmpty());
1178 if (mIsShutdown) {
1179 MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
1180 return IPC_OK();
1181 }
1182 mFlushDecoderPromise.ResolveIfExists(true, __func__);
1183 return IPC_OK();
1184 }
1185
Drain()1186 RefPtr<MediaDataDecoder::DecodePromise> ChromiumCDMParent::Drain() {
1187 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1188 MOZ_ASSERT(mDecodePromise.IsEmpty(), "Must wait for decoding to complete");
1189 if (mIsShutdown) {
1190 return MediaDataDecoder::DecodePromise::CreateAndReject(
1191 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1192 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1193 __func__);
1194 }
1195
1196 RefPtr<MediaDataDecoder::DecodePromise> p = mDecodePromise.Ensure(__func__);
1197 if (!SendDrain()) {
1198 mDecodePromise.Resolve(MediaDataDecoder::DecodedData(), __func__);
1199 }
1200 return p;
1201 }
1202
RecvDrainComplete()1203 ipc::IPCResult ChromiumCDMParent::RecvDrainComplete() {
1204 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1205 if (mIsShutdown) {
1206 MOZ_ASSERT(mDecodePromise.IsEmpty());
1207 return IPC_OK();
1208 }
1209
1210 MediaDataDecoder::DecodedData samples;
1211 while (!mReorderQueue.IsEmpty()) {
1212 samples.AppendElement(mReorderQueue.Pop());
1213 }
1214
1215 mDecodePromise.ResolveIfExists(std::move(samples), __func__);
1216 return IPC_OK();
1217 }
ShutdownVideoDecoder()1218 RefPtr<ShutdownPromise> ChromiumCDMParent::ShutdownVideoDecoder() {
1219 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1220 if (mIsShutdown || !mVideoDecoderInitialized) {
1221 return ShutdownPromise::CreateAndResolve(true, __func__);
1222 }
1223 mInitVideoDecoderPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED,
1224 __func__);
1225 mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
1226 MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
1227 if (!SendDeinitializeVideoDecoder()) {
1228 return ShutdownPromise::CreateAndResolve(true, __func__);
1229 }
1230 mVideoDecoderInitialized = false;
1231
1232 GMP_LOG_DEBUG("ChromiumCDMParent::~ShutdownVideoDecoder(this=%p) ", this);
1233
1234 // The ChromiumCDMChild will purge its shmems, so if the decoder is
1235 // reinitialized the shmems need to be re-allocated, and they may need
1236 // to be a different size.
1237 mVideoShmemsActive = 0;
1238 mVideoFrameBufferSize = 0;
1239 return ShutdownPromise::CreateAndResolve(true, __func__);
1240 }
1241
Shutdown()1242 void ChromiumCDMParent::Shutdown() {
1243 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1244 GMP_LOG_DEBUG("ChromiumCDMParent::Shutdown(this=%p)", this);
1245
1246 if (mIsShutdown) {
1247 return;
1248 }
1249 mIsShutdown = true;
1250
1251 // If we're shutting down due to the plugin shutting down due to application
1252 // shutdown, we should tell the CDM proxy to also shutdown. Otherwise the
1253 // proxy will shutdown when the owning MediaKeys is destroyed during cycle
1254 // collection, and that will not shut down cleanly as the GMP thread will be
1255 // shutdown by then.
1256 if (mCDMCallback) {
1257 mCDMCallback->Shutdown();
1258 }
1259
1260 // We may be called from a task holding the last reference to the CDM
1261 // callback, so let's clear our local weak pointer to ensure it will not be
1262 // used afterward (including from an already-queued task, e.g.: ActorDestroy).
1263 mCDMCallback = nullptr;
1264
1265 mReorderQueue.Clear();
1266
1267 for (RefPtr<DecryptJob>& decrypt : mDecrypts) {
1268 decrypt->PostResult(eme::AbortedErr);
1269 }
1270 mDecrypts.Clear();
1271
1272 if (mVideoDecoderInitialized && !mActorDestroyed) {
1273 Unused << SendDeinitializeVideoDecoder();
1274 mVideoDecoderInitialized = false;
1275 }
1276
1277 // Note: MediaKeys rejects all outstanding promises when it initiates
1278 // shutdown.
1279 mPromiseToCreateSessionToken.Clear();
1280
1281 mInitPromise.RejectIfExists(
1282 MediaResult(NS_ERROR_DOM_ABORT_ERR,
1283 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1284 __func__);
1285
1286 mInitVideoDecoderPromise.RejectIfExists(
1287 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1288 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1289 __func__);
1290 mDecodePromise.RejectIfExists(
1291 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1292 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1293 __func__);
1294 mFlushDecoderPromise.RejectIfExists(
1295 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1296 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1297 __func__);
1298
1299 if (!mActorDestroyed) {
1300 Unused << SendDestroy();
1301 }
1302 }
1303
1304 } // namespace mozilla::gmp
1305
1306 #undef NS_DispatchToMainThread
1307