tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ChromiumCDMParent.cpp (52228B)


      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 "AnnexB.h"
      9 #include "ChromiumCDMCallback.h"
     10 #include "ChromiumCDMCallbackProxy.h"
     11 #include "ChromiumCDMProxy.h"
     12 #include "GMPContentChild.h"
     13 #include "GMPContentParent.h"
     14 #include "GMPLog.h"
     15 #include "GMPService.h"
     16 #include "GMPUtils.h"
     17 #include "H264.h"
     18 #include "VideoUtils.h"
     19 #include "content_decryption_module.h"
     20 #include "mozilla/ScopeExit.h"
     21 #include "mozilla/StaticPrefs_media.h"
     22 #include "mozilla/dom/MediaKeyMessageEventBinding.h"
     23 #include "mozilla/gmp/GMPTypes.h"
     24 
     25 #define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
     26 
     27 namespace mozilla::gmp {
     28 
     29 using namespace eme;
     30 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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  (void)SendCompleteQueryOutputProtectionStatus(aSuccess, aLinkMask,
    275                                                aProtectionMask);
    276 }
    277 
    278 static cdm::HdcpVersion ToCDMHdcpVersion(
    279    const dom::HDCPVersion& aMinHdcpVersion) {
    280  switch (aMinHdcpVersion) {
    281    case dom::HDCPVersion::_1_0:
    282      return cdm::HdcpVersion::kHdcpVersion1_0;
    283    case dom::HDCPVersion::_1_1:
    284      return cdm::HdcpVersion::kHdcpVersion1_1;
    285    case dom::HDCPVersion::_1_2:
    286      return cdm::HdcpVersion::kHdcpVersion1_2;
    287    case dom::HDCPVersion::_1_3:
    288      return cdm::HdcpVersion::kHdcpVersion1_3;
    289    case dom::HDCPVersion::_1_4:
    290      return cdm::HdcpVersion::kHdcpVersion1_4;
    291    case dom::HDCPVersion::_2_0:
    292      return cdm::HdcpVersion::kHdcpVersion2_0;
    293    case dom::HDCPVersion::_2_1:
    294      return cdm::HdcpVersion::kHdcpVersion2_1;
    295    case dom::HDCPVersion::_2_2:
    296      return cdm::HdcpVersion::kHdcpVersion2_2;
    297    case dom::HDCPVersion::_2_3:
    298      return cdm::HdcpVersion::kHdcpVersion2_3;
    299    // When adding another version remember to update GMPMessageUtils so that we
    300    // can serialize it correctly and have correct bounds on the enum!
    301    default:
    302      MOZ_ASSERT_UNREACHABLE("Unexpected HDCP version!");
    303      return cdm::HdcpVersion::kHdcpVersionNone;
    304  }
    305 }
    306 
    307 void ChromiumCDMParent::GetStatusForPolicy(
    308    uint32_t aPromiseId, const dom::HDCPVersion& aMinHdcpVersion) {
    309  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    310  GMP_LOG_DEBUG("ChromiumCDMParent::GetStatusForPolicy(this=%p)", this);
    311  if (mIsShutdown) {
    312    RejectPromiseShutdown(aPromiseId);
    313    return;
    314  }
    315  if (!SendGetStatusForPolicy(aPromiseId, ToCDMHdcpVersion(aMinHdcpVersion))) {
    316    RejectPromiseWithStateError(
    317        aPromiseId, "Failed to send getStatusForPolicy to CDM process"_ns);
    318  }
    319 }
    320 
    321 bool ChromiumCDMParent::InitCDMInputBuffer(gmp::CDMInputBuffer& aBuffer,
    322                                           MediaRawData* aSample) {
    323  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    324  const CryptoSample& crypto = aSample->mCrypto;
    325  if (crypto.mEncryptedSizes.Length() != crypto.mPlainSizes.Length()) {
    326    GMP_LOG_DEBUG("InitCDMInputBuffer clear/cipher subsamples don't match");
    327    return false;
    328  }
    329 
    330  Shmem shmem;
    331  if (!AllocShmem(aSample->Size(), &shmem)) {
    332    return false;
    333  }
    334  memcpy(shmem.get<uint8_t>(), aSample->Data(), aSample->Size());
    335  cdm::EncryptionScheme encryptionScheme = cdm::EncryptionScheme::kUnencrypted;
    336  switch (crypto.mCryptoScheme) {
    337    case CryptoScheme::None:
    338      break;  // Default to none
    339    case CryptoScheme::Cenc:
    340      encryptionScheme = cdm::EncryptionScheme::kCenc;
    341      break;
    342    case CryptoScheme::Cbcs:
    343    case CryptoScheme::Cbcs_1_9:
    344      encryptionScheme = cdm::EncryptionScheme::kCbcs;
    345      break;
    346    default:
    347      GMP_LOG_DEBUG(
    348          "InitCDMInputBuffer got unexpected encryption scheme with "
    349          "value of %" PRIu8 ". Treating as no encryption.",
    350          static_cast<uint8_t>(crypto.mCryptoScheme));
    351      MOZ_ASSERT_UNREACHABLE("Should not have unrecognized encryption type");
    352      break;
    353  }
    354 
    355  const nsTArray<uint8_t>& iv = encryptionScheme != cdm::EncryptionScheme::kCbcs
    356                                    ? crypto.mIV
    357                                    : crypto.mConstantIV;
    358  aBuffer = gmp::CDMInputBuffer(
    359      shmem, crypto.mKeyId, iv, aSample->mTime.ToMicroseconds(),
    360      aSample->mDuration.ToMicroseconds(), crypto.mPlainSizes,
    361      crypto.mEncryptedSizes, crypto.mCryptByteBlock, crypto.mSkipByteBlock,
    362      encryptionScheme);
    363  return true;
    364 }
    365 
    366 bool ChromiumCDMParent::SendBufferToCDM(uint32_t aSizeInBytes) {
    367  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    368  GMP_LOG_DEBUG("ChromiumCDMParent::SendBufferToCDM() size=%" PRIu32,
    369                aSizeInBytes);
    370  Shmem shmem;
    371  if (!AllocShmem(aSizeInBytes, &shmem)) {
    372    return false;
    373  }
    374  if (!SendGiveBuffer(std::move(shmem))) {
    375    DeallocShmem(shmem);
    376    return false;
    377  }
    378  return true;
    379 }
    380 
    381 RefPtr<DecryptPromise> ChromiumCDMParent::Decrypt(MediaRawData* aSample) {
    382  if (mIsShutdown) {
    383    MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    384    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
    385                                           __func__);
    386  }
    387  CDMInputBuffer buffer;
    388  if (!InitCDMInputBuffer(buffer, aSample)) {
    389    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
    390                                           __func__);
    391  }
    392  // Send a buffer to the CDM to store the output. The CDM will either fill
    393  // it with the decrypted sample and return it, or deallocate it on failure.
    394  if (!SendBufferToCDM(aSample->Size())) {
    395    DeallocShmem(buffer.mData());
    396    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
    397                                           __func__);
    398  }
    399 
    400  RefPtr<DecryptJob> job = new DecryptJob(aSample);
    401  if (!SendDecrypt(job->mId, buffer)) {
    402    GMP_LOG_DEBUG(
    403        "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message",
    404        this);
    405    DeallocShmem(buffer.mData());
    406    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
    407                                           __func__);
    408  }
    409  RefPtr<DecryptPromise> promise = job->Ensure();
    410  mDecrypts.AppendElement(job);
    411  return promise;
    412 }
    413 
    414 ipc::IPCResult ChromiumCDMParent::Recv__delete__() {
    415  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    416  MOZ_ASSERT(mIsShutdown);
    417  GMP_LOG_DEBUG("ChromiumCDMParent::Recv__delete__(this=%p)", this);
    418  if (mContentParent) {
    419    mContentParent->ChromiumCDMDestroyed(this);
    420    mContentParent = nullptr;
    421  }
    422  return IPC_OK();
    423 }
    424 
    425 ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromiseWithKeyStatus(
    426    const uint32_t& aPromiseId, const uint32_t& aKeyStatus) {
    427  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    428  GMP_LOG_DEBUG(
    429      "ChromiumCDMParent::RecvOnResolvePromiseWithKeyStatus(this=%p, "
    430      "pid=%" PRIu32 ", keystatus=%" PRIu32 ")",
    431      this, aPromiseId, aKeyStatus);
    432  if (!mCDMCallback || mIsShutdown) {
    433    return IPC_OK();
    434  }
    435 
    436  mCDMCallback->ResolvePromiseWithKeyStatus(aPromiseId, aKeyStatus);
    437 
    438  return IPC_OK();
    439 }
    440 
    441 ipc::IPCResult ChromiumCDMParent::RecvOnResolveNewSessionPromise(
    442    const uint32_t& aPromiseId, const nsCString& aSessionId) {
    443  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    444  GMP_LOG_DEBUG(
    445      "ChromiumCDMParent::RecvOnResolveNewSessionPromise(this=%p, pid=%" PRIu32
    446      ", sid=%s)",
    447      this, aPromiseId, aSessionId.get());
    448  if (!mCDMCallback || mIsShutdown) {
    449    return IPC_OK();
    450  }
    451 
    452  Maybe<uint32_t> token = mPromiseToCreateSessionToken.Extract(aPromiseId);
    453  if (token.isNothing()) {
    454    RejectPromiseWithStateError(aPromiseId,
    455                                "Lost session token for new session."_ns);
    456    return IPC_OK();
    457  }
    458 
    459  mCDMCallback->SetSessionId(token.value(), aSessionId);
    460 
    461  ResolvePromise(aPromiseId);
    462 
    463  return IPC_OK();
    464 }
    465 
    466 ipc::IPCResult ChromiumCDMParent::RecvResolveLoadSessionPromise(
    467    const uint32_t& aPromiseId, const bool& aSuccessful) {
    468  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    469  GMP_LOG_DEBUG(
    470      "ChromiumCDMParent::RecvResolveLoadSessionPromise(this=%p, pid=%" PRIu32
    471      ", successful=%d)",
    472      this, aPromiseId, aSuccessful);
    473  if (!mCDMCallback || mIsShutdown) {
    474    return IPC_OK();
    475  }
    476 
    477  mCDMCallback->ResolveLoadSessionPromise(aPromiseId, aSuccessful);
    478 
    479  return IPC_OK();
    480 }
    481 
    482 void ChromiumCDMParent::ResolvePromise(uint32_t aPromiseId) {
    483  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    484  GMP_LOG_DEBUG("ChromiumCDMParent::ResolvePromise(this=%p, pid=%" PRIu32 ")",
    485                this, aPromiseId);
    486 
    487  // Note: The MediaKeys rejects all pending DOM promises when it
    488  // initiates shutdown.
    489  if (!mCDMCallback || mIsShutdown) {
    490    return;
    491  }
    492 
    493  mCDMCallback->ResolvePromise(aPromiseId);
    494 }
    495 
    496 ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromise(
    497    const uint32_t& aPromiseId) {
    498  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    499  ResolvePromise(aPromiseId);
    500  return IPC_OK();
    501 }
    502 
    503 void ChromiumCDMParent::RejectPromise(uint32_t aPromiseId,
    504                                      ErrorResult&& aException,
    505                                      const nsCString& aErrorMessage) {
    506  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    507  GMP_LOG_DEBUG("ChromiumCDMParent::RejectPromise(this=%p, pid=%" PRIu32 ")",
    508                this, aPromiseId);
    509  // Note: The MediaKeys rejects all pending DOM promises when it
    510  // initiates shutdown.
    511  if (!mCDMCallback || mIsShutdown) {
    512    // Suppress the exception as it will not be explicitly handled due to the
    513    // early return.
    514    aException.SuppressException();
    515    return;
    516  }
    517 
    518  mCDMCallback->RejectPromise(aPromiseId, std::move(aException), aErrorMessage);
    519 }
    520 
    521 void ChromiumCDMParent::RejectPromiseShutdown(uint32_t aPromiseId) {
    522  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    523  RejectPromiseWithStateError(aPromiseId, "CDM is shutdown"_ns);
    524 }
    525 
    526 void ChromiumCDMParent::RejectPromiseWithStateError(
    527    uint32_t aPromiseId, const nsCString& aErrorMessage) {
    528  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    529  ErrorResult rv;
    530  rv.ThrowInvalidStateError(aErrorMessage);
    531  RejectPromise(aPromiseId, std::move(rv), aErrorMessage);
    532 }
    533 
    534 static ErrorResult ToErrorResult(uint32_t aException,
    535                                 const nsCString& aErrorMessage) {
    536  // XXXbz could we have a CopyableErrorResult sent to us with a better error
    537  // message?
    538  ErrorResult rv;
    539  switch (static_cast<cdm::Exception>(aException)) {
    540    case cdm::Exception::kExceptionNotSupportedError:
    541      rv.ThrowNotSupportedError(aErrorMessage);
    542      break;
    543    case cdm::Exception::kExceptionInvalidStateError:
    544      rv.ThrowInvalidStateError(aErrorMessage);
    545      break;
    546    case cdm::Exception::kExceptionTypeError:
    547      rv.ThrowTypeError(aErrorMessage);
    548      break;
    549    case cdm::Exception::kExceptionQuotaExceededError:
    550      rv.ThrowQuotaExceededError(aErrorMessage);
    551      break;
    552    default:
    553      MOZ_ASSERT_UNREACHABLE("Invalid cdm::Exception enum value.");
    554      // Note: Unique placeholder.
    555      rv.ThrowTimeoutError(aErrorMessage);
    556  };
    557  return rv;
    558 }
    559 
    560 ipc::IPCResult ChromiumCDMParent::RecvOnRejectPromise(
    561    const uint32_t& aPromiseId, const uint32_t& aException,
    562    const uint32_t& aSystemCode, const nsCString& aErrorMessage) {
    563  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    564  RejectPromise(aPromiseId, ToErrorResult(aException, aErrorMessage),
    565                aErrorMessage);
    566  return IPC_OK();
    567 }
    568 
    569 ipc::IPCResult ChromiumCDMParent::RecvOnSessionMessage(
    570    const nsCString& aSessionId, const uint32_t& aMessageType,
    571    nsTArray<uint8_t>&& aMessage) {
    572  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    573  GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionMessage(this=%p, sid=%s)",
    574                this, aSessionId.get());
    575  if (!mCDMCallback || mIsShutdown) {
    576    return IPC_OK();
    577  }
    578 
    579  mCDMCallback->SessionMessage(aSessionId, aMessageType, std::move(aMessage));
    580  return IPC_OK();
    581 }
    582 
    583 ipc::IPCResult ChromiumCDMParent::RecvOnSessionKeysChange(
    584    const nsCString& aSessionId, nsTArray<CDMKeyInformation>&& aKeysInfo) {
    585  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    586  GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionKeysChange(this=%p)", this);
    587  if (!mCDMCallback || mIsShutdown) {
    588    return IPC_OK();
    589  }
    590 
    591  mCDMCallback->SessionKeysChange(aSessionId, std::move(aKeysInfo));
    592  return IPC_OK();
    593 }
    594 
    595 ipc::IPCResult ChromiumCDMParent::RecvOnExpirationChange(
    596    const nsCString& aSessionId, const double& aSecondsSinceEpoch) {
    597  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    598  GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnExpirationChange(this=%p) time=%lf",
    599                this, aSecondsSinceEpoch);
    600  if (!mCDMCallback || mIsShutdown) {
    601    return IPC_OK();
    602  }
    603 
    604  mCDMCallback->ExpirationChange(aSessionId, aSecondsSinceEpoch);
    605  return IPC_OK();
    606 }
    607 
    608 ipc::IPCResult ChromiumCDMParent::RecvOnSessionClosed(
    609    const nsCString& aSessionId) {
    610  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    611  GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionClosed(this=%p)", this);
    612  if (!mCDMCallback || mIsShutdown) {
    613    return IPC_OK();
    614  }
    615 
    616  mCDMCallback->SessionClosed(aSessionId);
    617  return IPC_OK();
    618 }
    619 
    620 ipc::IPCResult ChromiumCDMParent::RecvOnQueryOutputProtectionStatus() {
    621  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    622  GMP_LOG_DEBUG(
    623      "ChromiumCDMParent::RecvOnQueryOutputProtectionStatus(this=%p) "
    624      "mIsShutdown=%s mCDMCallback=%s mAwaitingOutputProtectionInformation=%s",
    625      this, mIsShutdown ? "true" : "false", mCDMCallback ? "true" : "false",
    626      mAwaitingOutputProtectionInformation ? "true" : "false");
    627  if (mIsShutdown) {
    628    // We're shutdown, don't try to service the query.
    629    return IPC_OK();
    630  }
    631  if (!mCDMCallback) {
    632    // We don't have a callback. We're not yet outputting anything so can report
    633    // we're safe.
    634    CompleteQueryOutputProtectionStatus(true, uint32_t{}, uint32_t{});
    635    return IPC_OK();
    636  }
    637 
    638  if (mOutputProtectionLinkMask.isSome()) {
    639    MOZ_DIAGNOSTIC_ASSERT(
    640        !mAwaitingOutputProtectionInformation,
    641        "If we have a cached value we should not be awaiting information");
    642    // We have a cached value, use that.
    643    CompleteQueryOutputProtectionStatus(true, mOutputProtectionLinkMask.value(),
    644                                        uint32_t{});
    645    return IPC_OK();
    646  }
    647 
    648  // We need to call up the stack to get the info. The CDM proxy will finish
    649  // the request via `NotifyOutputProtectionStatus`.
    650  mAwaitingOutputProtectionInformation = true;
    651  mCDMCallback->QueryOutputProtectionStatus();
    652  return IPC_OK();
    653 }
    654 
    655 DecryptStatus ToDecryptStatus(uint32_t aStatus) {
    656  switch (static_cast<cdm::Status>(aStatus)) {
    657    case cdm::kSuccess:
    658      return DecryptStatus::Ok;
    659    case cdm::kNoKey:
    660      return DecryptStatus::NoKeyErr;
    661    default:
    662      return DecryptStatus::GenericErr;
    663  }
    664 }
    665 
    666 ipc::IPCResult ChromiumCDMParent::RecvDecryptFailed(const uint32_t& aId,
    667                                                    const uint32_t& aStatus) {
    668  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    669  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecryptFailed(this=%p, id=%" PRIu32
    670                ", status=%" PRIu32 ")",
    671                this, aId, aStatus);
    672 
    673  if (mIsShutdown) {
    674    MOZ_ASSERT(mDecrypts.IsEmpty());
    675    return IPC_OK();
    676  }
    677 
    678  for (size_t i = 0; i < mDecrypts.Length(); i++) {
    679    if (mDecrypts[i]->mId == aId) {
    680      mDecrypts[i]->PostResult(ToDecryptStatus(aStatus));
    681      mDecrypts.RemoveElementAt(i);
    682      break;
    683    }
    684  }
    685  return IPC_OK();
    686 }
    687 
    688 ipc::IPCResult ChromiumCDMParent::RecvDecryptedShmem(const uint32_t& aId,
    689                                                     const uint32_t& aStatus,
    690                                                     ipc::Shmem&& aShmem) {
    691  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    692  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecryptedShmem(this=%p, id=%" PRIu32
    693                ", status=%" PRIu32 ")",
    694                this, aId, aStatus);
    695 
    696  // We must deallocate the shmem once we've copied the result out of it
    697  // in PostResult below.
    698  auto autoDeallocateShmem = MakeScopeExit([&, this] { DeallocShmem(aShmem); });
    699 
    700  if (mIsShutdown) {
    701    MOZ_ASSERT(mDecrypts.IsEmpty());
    702    return IPC_OK();
    703  }
    704  for (size_t i = 0; i < mDecrypts.Length(); i++) {
    705    if (mDecrypts[i]->mId == aId) {
    706      mDecrypts[i]->PostResult(ToDecryptStatus(aStatus),
    707                               aShmem.IsReadable()
    708                                   ? Span<const uint8_t>(aShmem.get<uint8_t>(),
    709                                                         aShmem.Size<uint8_t>())
    710                                   : Span<const uint8_t>());
    711      mDecrypts.RemoveElementAt(i);
    712      break;
    713    }
    714  }
    715  return IPC_OK();
    716 }
    717 
    718 ipc::IPCResult ChromiumCDMParent::RecvDecryptedData(const uint32_t& aId,
    719                                                    const uint32_t& aStatus,
    720                                                    nsTArray<uint8_t>&& aData) {
    721  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    722  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecryptedData(this=%p, id=%" PRIu32
    723                ", status=%" PRIu32 ")",
    724                this, aId, aStatus);
    725 
    726  if (mIsShutdown) {
    727    MOZ_ASSERT(mDecrypts.IsEmpty());
    728    return IPC_OK();
    729  }
    730  for (size_t i = 0; i < mDecrypts.Length(); i++) {
    731    if (mDecrypts[i]->mId == aId) {
    732      mDecrypts[i]->PostResult(ToDecryptStatus(aStatus), aData);
    733      mDecrypts.RemoveElementAt(i);
    734      break;
    735    }
    736  }
    737  return IPC_OK();
    738 }
    739 
    740 ipc::IPCResult ChromiumCDMParent::RecvIncreaseShmemPoolSize() {
    741  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    742  GMP_LOG_DEBUG("%s(this=%p) limit=%" PRIu32 " active=%" PRIu32, __func__, this,
    743                mVideoShmemLimit, mVideoShmemsActive);
    744 
    745  // Put an upper limit on the number of shmems we tolerate the CDM asking
    746  // for, to prevent a memory blow-out. In practice, we expect the CDM to
    747  // need less than 5, but some encodings require more.
    748  // We'd expect CDMs to not have video frames larger than 720p-1080p
    749  // (due to DRM robustness requirements), which is about 1.5MB-3MB per
    750  // frame.
    751  if (mVideoShmemLimit > 50) {
    752    mDecodePromise.RejectIfExists(
    753        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
    754                    RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
    755        __func__);
    756    Shutdown();
    757    return IPC_OK();
    758  }
    759  mVideoShmemLimit++;
    760 
    761  EnsureSufficientShmems(mVideoFrameBufferSize);
    762 
    763  return IPC_OK();
    764 }
    765 
    766 bool ChromiumCDMParent::PurgeShmems() {
    767  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    768  GMP_LOG_DEBUG(
    769      "ChromiumCDMParent::PurgeShmems(this=%p) frame_size=%zu"
    770      " limit=%" PRIu32 " active=%" PRIu32,
    771      this, mVideoFrameBufferSize, mVideoShmemLimit, mVideoShmemsActive);
    772 
    773  if (mVideoShmemsActive == 0) {
    774    // We haven't allocated any shmems, nothing to do here.
    775    return true;
    776  }
    777  if (!SendPurgeShmems()) {
    778    return false;
    779  }
    780  mVideoShmemsActive = 0;
    781  return true;
    782 }
    783 
    784 bool ChromiumCDMParent::EnsureSufficientShmems(size_t aVideoFrameSize) {
    785  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    786  GMP_LOG_DEBUG(
    787      "ChromiumCDMParent::EnsureSufficientShmems(this=%p) "
    788      "size=%zu expected_size=%zu limit=%" PRIu32 " active=%" PRIu32,
    789      this, aVideoFrameSize, mVideoFrameBufferSize, mVideoShmemLimit,
    790      mVideoShmemsActive);
    791 
    792  // The Chromium CDM API requires us to implement a synchronous
    793  // interface to supply buffers to the CDM for it to write decrypted samples
    794  // into. We want our buffers to be backed by shmems, in order to reduce
    795  // the overhead of transferring decoded frames. However due to sandboxing
    796  // restrictions, the CDM process cannot allocate shmems itself.
    797  // We don't want to be doing synchronous IPC to request shmems from the
    798  // content process, nor do we want to have to do intr IPC or make async
    799  // IPC conform to the sync allocation interface. So instead we have the
    800  // content process pre-allocate a set of shmems and give them to the CDM
    801  // process in advance of them being needed.
    802  //
    803  // When the CDM needs to allocate a buffer for storing a decoded video
    804  // frame, the CDM host gives it one of these shmems' buffers. When this
    805  // is sent back to the content process, we upload it to a GPU surface,
    806  // and send the shmem back to the CDM process so it can reuse it.
    807  //
    808  // Normally the CDM won't allocate more than one buffer at once, but
    809  // we've seen cases where it allocates multiple buffers, returns one and
    810  // holds onto the rest. So we need to ensure we have several extra
    811  // shmems pre-allocated for the CDM. This threshold is set by the pref
    812  // media.eme.chromium-api.video-shmems.
    813  //
    814  // We also have a failure recovery mechanism; if the CDM asks for more
    815  // buffers than we have shmem's available, ChromiumCDMChild gives the
    816  // CDM a non-shared memory buffer, and returns the frame to the parent
    817  // in an nsTArray<uint8_t> instead of a shmem. The child then sends a
    818  // message to the parent asking it to increase the number of shmems in
    819  // the pool. Via this mechanism we should recover from incorrectly
    820  // predicting how many shmems to pre-allocate.
    821  //
    822  // At decoder start up, we guess how big the shmems need to be based on
    823  // the video frame dimensions. If we guess wrong, the CDM will follow
    824  // the non-shmem path, and we'll re-create the shmems of the correct size.
    825  // This meanns we can recover from guessing the shmem size wrong.
    826  // We must re-take this path after every decoder de-init/re-init, as the
    827  // frame sizes should change every time we switch video stream.
    828 
    829  if (mVideoFrameBufferSize < aVideoFrameSize) {
    830    if (!PurgeShmems()) {
    831      return false;
    832    }
    833    mVideoFrameBufferSize = aVideoFrameSize;
    834  }
    835 
    836  while (mVideoShmemsActive < mVideoShmemLimit) {
    837    if (!SendBufferToCDM(mVideoFrameBufferSize)) {
    838      return false;
    839    }
    840    mVideoShmemsActive++;
    841  }
    842 
    843  return true;
    844 }
    845 
    846 ipc::IPCResult ChromiumCDMParent::RecvDecodedData(const CDMVideoFrame& aFrame,
    847                                                  nsTArray<uint8_t>&& aData) {
    848  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    849  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodedData(this=%p) time=%" PRId64,
    850                this, aFrame.mTimestamp());
    851 
    852  if (mIsShutdown || mDecodePromise.IsEmpty()) {
    853    return IPC_OK();
    854  }
    855 
    856  if (!EnsureSufficientShmems(aData.Length())) {
    857    mDecodePromise.RejectIfExists(
    858        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
    859                    RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
    860        __func__);
    861    return IPC_OK();
    862  }
    863 
    864  RefPtr<VideoData> v = CreateVideoFrame(aFrame, aData);
    865  if (!v) {
    866    mDecodePromise.RejectIfExists(
    867        MediaResult(NS_ERROR_OUT_OF_MEMORY,
    868                    RESULT_DETAIL("Can't create VideoData")),
    869        __func__);
    870    return IPC_OK();
    871  }
    872 
    873  ReorderAndReturnOutput(std::move(v));
    874 
    875  return IPC_OK();
    876 }
    877 
    878 ipc::IPCResult ChromiumCDMParent::RecvDecodedShmem(const CDMVideoFrame& aFrame,
    879                                                   ipc::Shmem&& aShmem) {
    880  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    881  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodedShmem(this=%p) time=%" PRId64
    882                " duration=%" PRId64,
    883                this, aFrame.mTimestamp(), aFrame.mDuration());
    884 
    885  // On failure we need to deallocate the shmem we're to return to the
    886  // CDM. On success we return it to the CDM to be reused.
    887  auto autoDeallocateShmem =
    888      MakeScopeExit([&, this] { this->DeallocShmem(aShmem); });
    889 
    890  if (mIsShutdown || mDecodePromise.IsEmpty()) {
    891    return IPC_OK();
    892  }
    893 
    894  RefPtr<VideoData> v = CreateVideoFrame(
    895      aFrame, Span<uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
    896  if (!v) {
    897    mDecodePromise.RejectIfExists(
    898        MediaResult(NS_ERROR_OUT_OF_MEMORY,
    899                    RESULT_DETAIL("Can't create VideoData")),
    900        __func__);
    901    return IPC_OK();
    902  }
    903 
    904  // Return the shmem to the CDM so the shmem can be reused to send us
    905  // another frame.
    906  if (!SendGiveBuffer(std::move(aShmem))) {
    907    mDecodePromise.RejectIfExists(
    908        MediaResult(NS_ERROR_OUT_OF_MEMORY,
    909                    RESULT_DETAIL("Can't return shmem to CDM process")),
    910        __func__);
    911    return IPC_OK();
    912  }
    913 
    914  // Don't need to deallocate the shmem since the CDM process is responsible
    915  // for it again.
    916  autoDeallocateShmem.release();
    917 
    918  ReorderAndReturnOutput(std::move(v));
    919 
    920  return IPC_OK();
    921 }
    922 
    923 void ChromiumCDMParent::ReorderAndReturnOutput(RefPtr<VideoData>&& aFrame) {
    924  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    925  if (mMaxRefFrames == 0) {
    926    mDecodePromise.ResolveIfExists(
    927        MediaDataDecoder::DecodedData({std::move(aFrame)}), __func__);
    928    return;
    929  }
    930  mReorderQueue.Push(std::move(aFrame));
    931  MediaDataDecoder::DecodedData results;
    932  while (mReorderQueue.Length() > mMaxRefFrames) {
    933    results.AppendElement(mReorderQueue.Pop());
    934  }
    935  mDecodePromise.Resolve(std::move(results), __func__);
    936 }
    937 
    938 already_AddRefed<VideoData> ChromiumCDMParent::CreateVideoFrame(
    939    const CDMVideoFrame& aFrame, Span<uint8_t> aData) {
    940  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
    941  MOZ_ASSERT(aData.Length() > 0);
    942  GMP_LOG_DEBUG(
    943      "ChromiumCDMParent::CreateVideoFrame(this=%p aFrame.mFormat=%" PRIu32 ")",
    944      this, aFrame.mFormat());
    945 
    946  if (aFrame.mFormat() == cdm::VideoFormat::kUnknownVideoFormat) {
    947    GMP_LOG_DEBUG(
    948        "ChromiumCDMParent::CreateVideoFrame(this=%p) Got kUnknownVideoFormat, "
    949        "bailing.",
    950        this);
    951    return nullptr;
    952  }
    953 
    954  if (aFrame.mFormat() == cdm::VideoFormat::kYUV420P9 ||
    955      aFrame.mFormat() == cdm::VideoFormat::kYUV422P9 ||
    956      aFrame.mFormat() == cdm::VideoFormat::kYUV444P9) {
    957    // If we ever hit this we can reconsider support, but 9 bit formats
    958    // should be so rare as to be non-existent.
    959    GMP_LOG_DEBUG(
    960        "ChromiumCDMParent::CreateVideoFrame(this=%p) Got a 9 bit depth pixel "
    961        "format. We don't support these, bailing.",
    962        this);
    963    return nullptr;
    964  }
    965 
    966  VideoData::YCbCrBuffer b;
    967 
    968  // Determine the dimensions of our chroma planes, color depth and chroma
    969  // subsampling.
    970  uint32_t chromaWidth = (aFrame.mImageWidth() + 1) / 2;
    971  uint32_t chromaHeight = (aFrame.mImageHeight() + 1) / 2;
    972  gfx::ColorDepth colorDepth = gfx::ColorDepth::COLOR_8;
    973  gfx::ChromaSubsampling chromaSubsampling =
    974      gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
    975  switch (aFrame.mFormat()) {
    976    case cdm::VideoFormat::kYv12:
    977    case cdm::VideoFormat::kI420:
    978      break;
    979    case cdm::VideoFormat::kYUV420P10:
    980      colorDepth = gfx::ColorDepth::COLOR_10;
    981      break;
    982    case cdm::VideoFormat::kYUV422P10:
    983      chromaHeight = aFrame.mImageHeight();
    984      colorDepth = gfx::ColorDepth::COLOR_10;
    985      chromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH;
    986      break;
    987    case cdm::VideoFormat::kYUV444P10:
    988      chromaWidth = aFrame.mImageWidth();
    989      chromaHeight = aFrame.mImageHeight();
    990      colorDepth = gfx::ColorDepth::COLOR_10;
    991      chromaSubsampling = gfx::ChromaSubsampling::FULL;
    992      break;
    993    case cdm::VideoFormat::kYUV420P12:
    994      colorDepth = gfx::ColorDepth::COLOR_12;
    995      break;
    996    case cdm::VideoFormat::kYUV422P12:
    997      chromaHeight = aFrame.mImageHeight();
    998      colorDepth = gfx::ColorDepth::COLOR_12;
    999      chromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH;
   1000      break;
   1001    case cdm::VideoFormat::kYUV444P12:
   1002      chromaWidth = aFrame.mImageWidth();
   1003      chromaHeight = aFrame.mImageHeight();
   1004      colorDepth = gfx::ColorDepth::COLOR_12;
   1005      chromaSubsampling = gfx::ChromaSubsampling::FULL;
   1006      break;
   1007    default:
   1008      MOZ_ASSERT_UNREACHABLE("Should handle all formats");
   1009      return nullptr;
   1010  }
   1011 
   1012  // Since we store each plane separately we can just roll the offset
   1013  // into our pointer to that plane and store that.
   1014  b.mPlanes[0].mData = aData.Elements() + aFrame.mYPlane().mPlaneOffset();
   1015  b.mPlanes[0].mWidth = aFrame.mImageWidth();
   1016  b.mPlanes[0].mHeight = aFrame.mImageHeight();
   1017  b.mPlanes[0].mStride = aFrame.mYPlane().mStride();
   1018  b.mPlanes[0].mSkip = 0;
   1019 
   1020  b.mPlanes[1].mData = aData.Elements() + aFrame.mUPlane().mPlaneOffset();
   1021  b.mPlanes[1].mWidth = chromaWidth;
   1022  b.mPlanes[1].mHeight = chromaHeight;
   1023  b.mPlanes[1].mStride = aFrame.mUPlane().mStride();
   1024  b.mPlanes[1].mSkip = 0;
   1025 
   1026  b.mPlanes[2].mData = aData.Elements() + aFrame.mVPlane().mPlaneOffset();
   1027  b.mPlanes[2].mWidth = chromaWidth;
   1028  b.mPlanes[2].mHeight = chromaHeight;
   1029  b.mPlanes[2].mStride = aFrame.mVPlane().mStride();
   1030  b.mPlanes[2].mSkip = 0;
   1031 
   1032  b.mColorDepth = colorDepth;
   1033  b.mChromaSubsampling = chromaSubsampling;
   1034 
   1035  // Ensure that each plane fits within the buffer bounds.
   1036  auto bpp = BytesPerPixel(SurfaceFormatForColorDepth(colorDepth));
   1037  for (const auto& plane : b.mPlanes) {
   1038    auto rowSize = CheckedInt<uint32_t>(plane.mWidth) * bpp;
   1039    if (NS_WARN_IF(!rowSize.isValid() || rowSize.value() > plane.mStride)) {
   1040      GMP_LOG_DEBUG(
   1041          "ChromiumCDMParent::CreateVideoFrame(this=%p) Plane width %u stride "
   1042          "%u bpp %d mismatch, bailing.",
   1043          this, plane.mWidth, plane.mStride, bpp);
   1044      return nullptr;
   1045    }
   1046    auto size = CheckedInt<uint32_t>(plane.mStride) * plane.mHeight;
   1047    if (NS_WARN_IF(!size.isValid())) {
   1048      GMP_LOG_DEBUG(
   1049          "ChromiumCDMParent::CreateVideoFrame(this=%p) Plane height %u stride "
   1050          "%u integer overflow, bailing.",
   1051          this, plane.mHeight, plane.mStride);
   1052      return nullptr;
   1053    }
   1054    auto offset = plane.mData - aData.Elements();
   1055    auto required = size + offset;
   1056    if (NS_WARN_IF(!required.isValid() || required.value() > aData.Length())) {
   1057      GMP_LOG_DEBUG(
   1058          "ChromiumCDMParent::CreateVideoFrame(this=%p) Plane height %u stride "
   1059          "%u offset %u buffer length %zu overflow, bailing.",
   1060          this, plane.mHeight, plane.mStride, static_cast<uint32_t>(offset),
   1061          aData.Length());
   1062      return nullptr;
   1063    }
   1064  }
   1065 
   1066  // We unfortunately can't know which colorspace the video is using at this
   1067  // stage.
   1068  b.mYUVColorSpace =
   1069      DefaultColorSpace({aFrame.mImageWidth(), aFrame.mImageHeight()});
   1070 
   1071  gfx::IntRect pictureRegion(0, 0, aFrame.mImageWidth(), aFrame.mImageHeight());
   1072 
   1073  mozilla::Result<already_AddRefed<VideoData>, MediaResult> r =
   1074      VideoData::CreateAndCopyData(
   1075          mVideoInfo, mImageContainer, mLastStreamOffset,
   1076          media::TimeUnit::FromMicroseconds(
   1077              CheckedInt64(aFrame.mTimestamp()).value()),
   1078          media::TimeUnit::FromMicroseconds(
   1079              CheckedInt64(aFrame.mDuration()).value()),
   1080          b, false, media::TimeUnit::FromMicroseconds(-1), pictureRegion,
   1081          mKnowsCompositor);
   1082  RefPtr<VideoData> v = r.unwrapOr(nullptr);
   1083 
   1084  if (!v || !v->mImage) {
   1085    NS_WARNING("Failed to decode video frame.");
   1086    return v.forget();
   1087  }
   1088 
   1089  // This is a DRM image.
   1090  v->mImage->SetIsDRM(true);
   1091 
   1092  return v.forget();
   1093 }
   1094 
   1095 ipc::IPCResult ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus) {
   1096  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   1097  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodeFailed(this=%p status=%" PRIu32
   1098                ")",
   1099                this, aStatus);
   1100  if (mIsShutdown) {
   1101    MOZ_ASSERT(mDecodePromise.IsEmpty());
   1102    return IPC_OK();
   1103  }
   1104 
   1105  if (aStatus == cdm::kNeedMoreData) {
   1106    mDecodePromise.ResolveIfExists(nsTArray<RefPtr<MediaData>>(), __func__);
   1107    return IPC_OK();
   1108  }
   1109 
   1110  mDecodePromise.RejectIfExists(
   1111      MediaResult(
   1112          NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1113          RESULT_DETAIL(
   1114              "ChromiumCDMParent::RecvDecodeFailed with status %s (%" PRIu32
   1115              ")",
   1116              cdm::EnumValueToString(cdm::Status(aStatus)), aStatus)),
   1117      __func__);
   1118  return IPC_OK();
   1119 }
   1120 
   1121 ipc::IPCResult ChromiumCDMParent::RecvShutdown() {
   1122  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   1123  GMP_LOG_DEBUG("ChromiumCDMParent::RecvShutdown(this=%p)", this);
   1124  Shutdown();
   1125  return IPC_OK();
   1126 }
   1127 
   1128 void ChromiumCDMParent::ActorDestroy(ActorDestroyReason aWhy) {
   1129  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   1130  GMP_LOG_DEBUG("ChromiumCDMParent::ActorDestroy(this=%p, reason=%d)", this,
   1131                aWhy);
   1132  MOZ_ASSERT(!mActorDestroyed);
   1133  mActorDestroyed = true;
   1134  // Shutdown() will clear mCDMCallback, so let's keep a reference for later
   1135  // use.
   1136  auto callback = mCDMCallback;
   1137  if (!mIsShutdown) {
   1138    // Plugin crash.
   1139    MOZ_ASSERT(aWhy == AbnormalShutdown);
   1140    Shutdown();
   1141  }
   1142  MOZ_ASSERT(mIsShutdown);
   1143  RefPtr<ChromiumCDMParent> kungFuDeathGrip(this);
   1144  if (mContentParent) {
   1145    mContentParent->ChromiumCDMDestroyed(this);
   1146    mContentParent = nullptr;
   1147  }
   1148  mAbnormalShutdown = (aWhy == AbnormalShutdown);
   1149  if (mAbnormalShutdown && callback) {
   1150    callback->Terminated();
   1151  }
   1152  MaybeDisconnect(mAbnormalShutdown);
   1153 }
   1154 
   1155 RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMParent::InitializeVideoDecoder(
   1156    const gmp::CDMVideoDecoderConfig& aConfig, const VideoInfo& aInfo,
   1157    RefPtr<layers::ImageContainer> aImageContainer,
   1158    RefPtr<layers::KnowsCompositor> aKnowsCompositor) {
   1159  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   1160  if (mIsShutdown) {
   1161    return MediaDataDecoder::InitPromise::CreateAndReject(
   1162        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1163                    RESULT_DETAIL("ChromiumCDMParent is shutdown")),
   1164        __func__);
   1165  }
   1166 
   1167  // The Widevine CDM version 1.4.8.970 and above contain a video decoder that
   1168  // does not optimally allocate video frames; it requests buffers much larger
   1169  // than required. The exact formula the CDM uses to calculate their frame
   1170  // sizes isn't obvious, but they normally request around or slightly more
   1171  // than 1.5X the optimal amount. So pad the size of buffers we allocate so
   1172  // that we're likely to have buffers big enough to accomodate the CDM's weird
   1173  // frame size calculation.
   1174  const size_t bufferSize =
   1175      1.7 * I420FrameBufferSizePadded(aInfo.mImage.width, aInfo.mImage.height);
   1176  if (bufferSize <= 0) {
   1177    return MediaDataDecoder::InitPromise::CreateAndReject(
   1178        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1179                    RESULT_DETAIL("Video frame buffer size is invalid.")),
   1180        __func__);
   1181  }
   1182 
   1183  if (!EnsureSufficientShmems(bufferSize)) {
   1184    return MediaDataDecoder::InitPromise::CreateAndReject(
   1185        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1186                    RESULT_DETAIL("Failed to init shmems for video decoder")),
   1187        __func__);
   1188  }
   1189 
   1190  if (!SendInitializeVideoDecoder(aConfig)) {
   1191    return MediaDataDecoder::InitPromise::CreateAndReject(
   1192        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1193                    RESULT_DETAIL("Failed to send init video decoder to CDM")),
   1194        __func__);
   1195  }
   1196 
   1197  mMaxRefFrames = (aConfig.mCodec() == cdm::VideoCodec::kCodecH264)
   1198                      ? H264::HasSPS(aInfo.mExtraData)
   1199                            ? H264::ComputeMaxRefFrames(aInfo.mExtraData)
   1200                            : 16
   1201                      : 0;
   1202 
   1203  mVideoDecoderInitialized = true;
   1204  mImageContainer = aImageContainer;
   1205  mKnowsCompositor = aKnowsCompositor;
   1206  mVideoInfo = aInfo;
   1207  mVideoFrameBufferSize = bufferSize;
   1208 
   1209  return mInitVideoDecoderPromise.Ensure(__func__);
   1210 }
   1211 
   1212 ipc::IPCResult ChromiumCDMParent::RecvOnDecoderInitDone(
   1213    const uint32_t& aStatus) {
   1214  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   1215  GMP_LOG_DEBUG(
   1216      "ChromiumCDMParent::RecvOnDecoderInitDone(this=%p, status=%" PRIu32 ")",
   1217      this, aStatus);
   1218  if (mIsShutdown) {
   1219    MOZ_ASSERT(mInitVideoDecoderPromise.IsEmpty());
   1220    return IPC_OK();
   1221  }
   1222  if (aStatus == static_cast<uint32_t>(cdm::kSuccess)) {
   1223    mInitVideoDecoderPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
   1224  } else {
   1225    mVideoDecoderInitialized = false;
   1226    mInitVideoDecoderPromise.RejectIfExists(
   1227        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1228                    RESULT_DETAIL(
   1229                        "CDM init decode failed with status %s (%" PRIu32 ")",
   1230                        cdm::EnumValueToString(cdm::Status(aStatus)), aStatus)),
   1231        __func__);
   1232  }
   1233  return IPC_OK();
   1234 }
   1235 
   1236 RefPtr<MediaDataDecoder::DecodePromise>
   1237 ChromiumCDMParent::DecryptAndDecodeFrame(MediaRawData* aSample) {
   1238  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   1239  if (mIsShutdown) {
   1240    return MediaDataDecoder::DecodePromise::CreateAndReject(
   1241        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1242                    RESULT_DETAIL("ChromiumCDMParent is shutdown")),
   1243        __func__);
   1244  }
   1245 
   1246  GMP_LOG_DEBUG("ChromiumCDMParent::DecryptAndDecodeFrame t=%" PRId64,
   1247                aSample->mTime.ToMicroseconds());
   1248 
   1249  CDMInputBuffer buffer;
   1250 
   1251  if (!InitCDMInputBuffer(buffer, aSample)) {
   1252    return MediaDataDecoder::DecodePromise::CreateAndReject(
   1253        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to init CDM buffer."),
   1254        __func__);
   1255  }
   1256 
   1257  mLastStreamOffset = aSample->mOffset;
   1258 
   1259  if (!SendDecryptAndDecodeFrame(buffer)) {
   1260    GMP_LOG_DEBUG(
   1261        "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message.",
   1262        this);
   1263    DeallocShmem(buffer.mData());
   1264    return MediaDataDecoder::DecodePromise::CreateAndReject(
   1265        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1266                    "Failed to send decrypt to CDM process."),
   1267        __func__);
   1268  }
   1269 
   1270  return mDecodePromise.Ensure(__func__);
   1271 }
   1272 
   1273 RefPtr<MediaDataDecoder::FlushPromise> ChromiumCDMParent::FlushVideoDecoder() {
   1274  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   1275  if (mIsShutdown) {
   1276    MOZ_ASSERT(mReorderQueue.IsEmpty());
   1277    return MediaDataDecoder::FlushPromise::CreateAndReject(
   1278        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1279                    RESULT_DETAIL("ChromiumCDMParent is shutdown")),
   1280        __func__);
   1281  }
   1282 
   1283  mReorderQueue.Clear();
   1284 
   1285  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   1286  if (!SendResetVideoDecoder()) {
   1287    return MediaDataDecoder::FlushPromise::CreateAndReject(
   1288        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1289                    "Failed to send flush to CDM."),
   1290        __func__);
   1291  }
   1292  return mFlushDecoderPromise.Ensure(__func__);
   1293 }
   1294 
   1295 ipc::IPCResult ChromiumCDMParent::RecvResetVideoDecoderComplete() {
   1296  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   1297  MOZ_ASSERT(mReorderQueue.IsEmpty());
   1298  if (mIsShutdown) {
   1299    MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
   1300    return IPC_OK();
   1301  }
   1302  mFlushDecoderPromise.ResolveIfExists(true, __func__);
   1303  return IPC_OK();
   1304 }
   1305 
   1306 RefPtr<MediaDataDecoder::DecodePromise> ChromiumCDMParent::Drain() {
   1307  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   1308  MOZ_ASSERT(mDecodePromise.IsEmpty(), "Must wait for decoding to complete");
   1309  if (mIsShutdown) {
   1310    return MediaDataDecoder::DecodePromise::CreateAndReject(
   1311        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1312                    RESULT_DETAIL("ChromiumCDMParent is shutdown")),
   1313        __func__);
   1314  }
   1315 
   1316  RefPtr<MediaDataDecoder::DecodePromise> p = mDecodePromise.Ensure(__func__);
   1317  if (!SendDrain()) {
   1318    mDecodePromise.Resolve(MediaDataDecoder::DecodedData(), __func__);
   1319  }
   1320  return p;
   1321 }
   1322 
   1323 ipc::IPCResult ChromiumCDMParent::RecvDrainComplete() {
   1324  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   1325  if (mIsShutdown) {
   1326    MOZ_ASSERT(mDecodePromise.IsEmpty());
   1327    return IPC_OK();
   1328  }
   1329 
   1330  MediaDataDecoder::DecodedData samples;
   1331  while (!mReorderQueue.IsEmpty()) {
   1332    samples.AppendElement(mReorderQueue.Pop());
   1333  }
   1334 
   1335  mDecodePromise.ResolveIfExists(std::move(samples), __func__);
   1336  return IPC_OK();
   1337 }
   1338 RefPtr<ShutdownPromise> ChromiumCDMParent::ShutdownVideoDecoder() {
   1339  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   1340  if (mIsShutdown || !mVideoDecoderInitialized) {
   1341    return ShutdownPromise::CreateAndResolve(true, __func__);
   1342  }
   1343  mInitVideoDecoderPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED,
   1344                                          __func__);
   1345  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   1346  MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
   1347  if (!SendDeinitializeVideoDecoder()) {
   1348    return ShutdownPromise::CreateAndResolve(true, __func__);
   1349  }
   1350  mVideoDecoderInitialized = false;
   1351 
   1352  GMP_LOG_DEBUG("ChromiumCDMParent::~ShutdownVideoDecoder(this=%p) ", this);
   1353 
   1354  // The ChromiumCDMChild will purge its shmems, so if the decoder is
   1355  // reinitialized the shmems need to be re-allocated, and they may need
   1356  // to be a different size.
   1357  mVideoShmemsActive = 0;
   1358  mVideoFrameBufferSize = 0;
   1359  return ShutdownPromise::CreateAndResolve(true, __func__);
   1360 }
   1361 
   1362 void ChromiumCDMParent::Shutdown() {
   1363  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   1364  GMP_LOG_DEBUG("ChromiumCDMParent::Shutdown(this=%p)", this);
   1365 
   1366  if (mIsShutdown) {
   1367    return;
   1368  }
   1369  mIsShutdown = true;
   1370 
   1371  // If we're shutting down due to the plugin shutting down due to application
   1372  // shutdown, we should tell the CDM proxy to also shutdown. Otherwise the
   1373  // proxy will shutdown when the owning MediaKeys is destroyed during cycle
   1374  // collection, and that will not shut down cleanly as the GMP thread will be
   1375  // shutdown by then.
   1376  if (mCDMCallback) {
   1377    mCDMCallback->Shutdown();
   1378  }
   1379 
   1380  // We may be called from a task holding the last reference to the CDM
   1381  // callback, so let's clear our local weak pointer to ensure it will not be
   1382  // used afterward (including from an already-queued task, e.g.: ActorDestroy).
   1383  mCDMCallback = nullptr;
   1384 
   1385  mReorderQueue.Clear();
   1386 
   1387  for (RefPtr<DecryptJob>& decrypt : mDecrypts) {
   1388    decrypt->PostResult(eme::AbortedErr);
   1389  }
   1390  mDecrypts.Clear();
   1391 
   1392  if (mVideoDecoderInitialized && !mActorDestroyed) {
   1393    (void)SendDeinitializeVideoDecoder();
   1394    mVideoDecoderInitialized = false;
   1395  }
   1396 
   1397  // Note: MediaKeys rejects all outstanding promises when it initiates
   1398  // shutdown.
   1399  mPromiseToCreateSessionToken.Clear();
   1400 
   1401  mInitPromise.RejectIfExists(
   1402      MediaResult(NS_ERROR_DOM_ABORT_ERR,
   1403                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
   1404      __func__);
   1405 
   1406  mInitVideoDecoderPromise.RejectIfExists(
   1407      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1408                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
   1409      __func__);
   1410  mDecodePromise.RejectIfExists(
   1411      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1412                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
   1413      __func__);
   1414  mFlushDecoderPromise.RejectIfExists(
   1415      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   1416                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
   1417      __func__);
   1418 
   1419  if (!mActorDestroyed) {
   1420    (void)SendDestroy();
   1421  }
   1422 }
   1423 
   1424 }  // namespace mozilla::gmp
   1425 
   1426 #undef NS_DispatchToMainThread