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