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