tor-browser

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

ChromiumCDMChild.cpp (33669B)


      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 "ChromiumCDMChild.h"
      7 
      8 #include <type_traits>
      9 
     10 #include "CDMStorageIdProvider.h"
     11 #include "GMPContentChild.h"
     12 #include "GMPLog.h"
     13 #include "GMPPlatform.h"
     14 #include "GMPUtils.h"
     15 #include "WidevineFileIO.h"
     16 #include "WidevineUtils.h"
     17 #include "WidevineVideoFrame.h"
     18 #include "base/time.h"
     19 #include "mozilla/ScopeExit.h"
     20 #include "nsPrintfCString.h"
     21 #include "nsReadableUtils.h"
     22 
     23 namespace mozilla::gmp {
     24 
     25 ChromiumCDMChild::ChromiumCDMChild(GMPContentChild* aPlugin)
     26    : mPlugin(aPlugin) {
     27  MOZ_ASSERT(IsOnMessageLoopThread());
     28  GMP_LOG_DEBUG("ChromiumCDMChild:: ctor this=%p", this);
     29 }
     30 
     31 void ChromiumCDMChild::Init(cdm::ContentDecryptionModule_11* aCDM,
     32                            const nsACString& aStorageId) {
     33  MOZ_ASSERT(IsOnMessageLoopThread());
     34  mCDM = aCDM;
     35  MOZ_ASSERT(mCDM);
     36  mStorageId = aStorageId;
     37 }
     38 
     39 void ChromiumCDMChild::TimerExpired(void* aContext) {
     40  MOZ_ASSERT(IsOnMessageLoopThread());
     41  GMP_LOG_DEBUG("ChromiumCDMChild::TimerExpired(context=0x%p)", aContext);
     42  if (mCDM) {
     43    mCDM->TimerExpired(aContext);
     44  }
     45 }
     46 
     47 class CDMShmemBuffer : public CDMBuffer {
     48 public:
     49  CDMShmemBuffer(ChromiumCDMChild* aProtocol, ipc::Shmem aShmem)
     50      : mProtocol(aProtocol), mSize(aShmem.Size<uint8_t>()), mShmem(aShmem) {
     51    GMP_LOG_DEBUG("CDMShmemBuffer(size=%" PRIu32 ") created", Size());
     52    // Note: Chrome initializes the size of a buffer to it capacity. We do the
     53    // same.
     54  }
     55 
     56  CDMShmemBuffer(ChromiumCDMChild* aProtocol, ipc::Shmem aShmem,
     57                 WidevineBuffer* aLocalBuffer)
     58      : CDMShmemBuffer(aProtocol, aShmem) {
     59    MOZ_ASSERT(aLocalBuffer->Size() == Size());
     60    memcpy(Data(), aLocalBuffer->Data(),
     61           std::min<uint32_t>(aLocalBuffer->Size(), Size()));
     62  }
     63 
     64  ~CDMShmemBuffer() override {
     65    GMP_LOG_DEBUG("CDMShmemBuffer(size=%" PRIu32 ") destructed writable=%d",
     66                  Size(), mShmem.IsWritable());
     67    if (mShmem.IsWritable()) {
     68      // The shmem wasn't extracted to send its data back up to the parent
     69      // process, so we can reuse the shmem.
     70      mProtocol->GiveBuffer(std::move(mShmem));
     71    }
     72  }
     73 
     74  void Destroy() override {
     75    GMP_LOG_DEBUG("CDMShmemBuffer::Destroy(size=%" PRIu32 ")", Size());
     76    delete this;
     77  }
     78  uint32_t Capacity() const override { return mShmem.Size<uint8_t>(); }
     79 
     80  uint8_t* Data() override { return mShmem.get<uint8_t>(); }
     81 
     82  void SetSize(uint32_t aSize) override {
     83    MOZ_ASSERT(aSize <= Capacity());
     84    // Note: We can't use the shmem's size member after ExtractShmem(),
     85    // has been called, so we track the size exlicitly so that we can use
     86    // Size() in logging after we've called ExtractShmem().
     87    GMP_LOG_DEBUG("CDMShmemBuffer::SetSize(size=%" PRIu32 ")", Size());
     88    mSize = aSize;
     89  }
     90 
     91  uint32_t Size() const override { return mSize; }
     92 
     93  ipc::Shmem ExtractShmem() {
     94    ipc::Shmem shmem = mShmem;
     95    mShmem = ipc::Shmem();
     96    return shmem;
     97  }
     98 
     99  CDMShmemBuffer* AsShmemBuffer() override { return this; }
    100 
    101 private:
    102  RefPtr<ChromiumCDMChild> mProtocol;
    103  uint32_t mSize;
    104  mozilla::ipc::Shmem mShmem;
    105  CDMShmemBuffer(const CDMShmemBuffer&);
    106  void operator=(const CDMShmemBuffer&);
    107 };
    108 
    109 static auto ToString(const nsTArray<ipc::Shmem>& aBuffers) {
    110  return StringJoin(","_ns, aBuffers, [](auto& s, const ipc::Shmem& shmem) {
    111    s.AppendInt(static_cast<uint32_t>(shmem.Size<uint8_t>()));
    112  });
    113 }
    114 
    115 cdm::Buffer* ChromiumCDMChild::Allocate(uint32_t aCapacity) {
    116  GMP_LOG_DEBUG("ChromiumCDMChild::Allocate(capacity=%" PRIu32
    117                ") bufferSizes={%s}",
    118                aCapacity, ToString(mBuffers).get());
    119  MOZ_ASSERT(IsOnMessageLoopThread());
    120 
    121  if (mBuffers.IsEmpty()) {
    122    (void)SendIncreaseShmemPoolSize();
    123  }
    124 
    125  // Find the shmem with the least amount of wasted space if we were to
    126  // select it for this sized allocation. We need to do this because shmems
    127  // for decrypted audio as well as video frames are both stored in this
    128  // list, and we don't want to use the video frame shmems for audio samples.
    129  const size_t invalid = std::numeric_limits<size_t>::max();
    130  size_t best = invalid;
    131  auto wastedSpace = [this, aCapacity](size_t index) {
    132    return mBuffers[index].Size<uint8_t>() - aCapacity;
    133  };
    134  for (size_t i = 0; i < mBuffers.Length(); i++) {
    135    if (mBuffers[i].Size<uint8_t>() >= aCapacity &&
    136        (best == invalid || wastedSpace(i) < wastedSpace(best))) {
    137      best = i;
    138    }
    139  }
    140  if (best == invalid) {
    141    // The parent process should have bestowed upon us a shmem of appropriate
    142    // size, but did not! Do a "dive and catch", and create an non-shared
    143    // memory buffer. The parent will detect this and send us an extra shmem
    144    // so future frames can be in shmems, i.e. returned on the fast path.
    145    return new WidevineBuffer(aCapacity);
    146  }
    147  ipc::Shmem shmem = mBuffers[best];
    148  mBuffers.RemoveElementAt(best);
    149  return new CDMShmemBuffer(this, shmem);
    150 }
    151 
    152 void ChromiumCDMChild::SetTimer(int64_t aDelayMs, void* aContext) {
    153  MOZ_ASSERT(IsOnMessageLoopThread());
    154  GMP_LOG_DEBUG("ChromiumCDMChild::SetTimer(delay=%" PRId64 ", context=0x%p)",
    155                aDelayMs, aContext);
    156  RefPtr<ChromiumCDMChild> self(this);
    157  SetTimerOnMainThread(
    158      NewGMPTask([self, aContext]() { self->TimerExpired(aContext); }),
    159      aDelayMs);
    160 }
    161 
    162 cdm::Time ChromiumCDMChild::GetCurrentWallTime() {
    163  return base::Time::Now().ToDoubleT();
    164 }
    165 
    166 template <typename MethodType, typename... ParamType>
    167 void ChromiumCDMChild::CallMethod(MethodType aMethod, ParamType&&... aParams) {
    168  MOZ_ASSERT(IsOnMessageLoopThread());
    169  // Avoid calling member function after destroy.
    170  if (!mDestroyed) {
    171    (void)(this->*aMethod)(std::forward<ParamType>(aParams)...);
    172  }
    173 }
    174 
    175 template <typename MethodType, typename... ParamType>
    176 void ChromiumCDMChild::CallOnMessageLoopThread(const char* const aName,
    177                                               MethodType aMethod,
    178                                               ParamType&&... aParams) {
    179  if (NS_WARN_IF(!mPlugin)) {
    180    return;
    181  }
    182 
    183  if (IsOnMessageLoopThread()) {
    184    CallMethod(aMethod, std::forward<ParamType>(aParams)...);
    185  } else {
    186    auto m = &ChromiumCDMChild::CallMethod<
    187        decltype(aMethod), const std::remove_reference_t<ParamType>&...>;
    188    RefPtr<mozilla::Runnable> t =
    189        NewRunnableMethod<decltype(aMethod),
    190                          const std::remove_reference_t<ParamType>...>(
    191            aName, this, m, aMethod, std::forward<ParamType>(aParams)...);
    192    mPlugin->GMPMessageLoop()->PostTask(t.forget());
    193  }
    194 }
    195 
    196 void ChromiumCDMChild::OnResolveKeyStatusPromise(uint32_t aPromiseId,
    197                                                 cdm::KeyStatus aKeyStatus) {
    198  GMP_LOG_DEBUG("ChromiumCDMChild::OnResolveKeyStatusPromise(pid=%" PRIu32
    199                "keystatus=%d)",
    200                aPromiseId, aKeyStatus);
    201  CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnResolveKeyStatusPromise",
    202                          &ChromiumCDMChild::SendOnResolvePromiseWithKeyStatus,
    203                          aPromiseId, static_cast<uint32_t>(aKeyStatus));
    204 }
    205 
    206 bool ChromiumCDMChild::OnResolveNewSessionPromiseInternal(
    207    uint32_t aPromiseId, const nsACString& aSessionId) {
    208  MOZ_ASSERT(IsOnMessageLoopThread());
    209  if (mLoadSessionPromiseIds.Contains(aPromiseId)) {
    210    // As laid out in the Chromium CDM API, if the CDM fails to load
    211    // a session it calls OnResolveNewSessionPromise with nullptr as the
    212    // sessionId. We can safely assume this means that we have failed to load a
    213    // session as the other methods specify calling 'OnRejectPromise' when they
    214    // fail.
    215    bool loadSuccessful = !aSessionId.IsEmpty();
    216    GMP_LOG_DEBUG(
    217        "ChromiumCDMChild::OnResolveNewSessionPromise(pid=%u, sid=%s) "
    218        "resolving %s load session ",
    219        aPromiseId, PromiseFlatCString(aSessionId).get(),
    220        (loadSuccessful ? "successful" : "failed"));
    221    mLoadSessionPromiseIds.RemoveElement(aPromiseId);
    222    return SendResolveLoadSessionPromise(aPromiseId, loadSuccessful);
    223  }
    224 
    225  return SendOnResolveNewSessionPromise(aPromiseId, aSessionId);
    226 }
    227 void ChromiumCDMChild::OnResolveNewSessionPromise(uint32_t aPromiseId,
    228                                                  const char* aSessionId,
    229                                                  uint32_t aSessionIdSize) {
    230  GMP_LOG_DEBUG("ChromiumCDMChild::OnResolveNewSessionPromise(pid=%" PRIu32
    231                ", sid=%s)",
    232                aPromiseId, aSessionId);
    233  CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnResolveNewSessionPromise",
    234                          &ChromiumCDMChild::OnResolveNewSessionPromiseInternal,
    235                          aPromiseId, nsCString(aSessionId, aSessionIdSize));
    236 }
    237 
    238 void ChromiumCDMChild::OnResolvePromise(uint32_t aPromiseId) {
    239  GMP_LOG_DEBUG("ChromiumCDMChild::OnResolvePromise(pid=%" PRIu32 ")",
    240                aPromiseId);
    241  CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnResolvePromise",
    242                          &ChromiumCDMChild::SendOnResolvePromise, aPromiseId);
    243 }
    244 
    245 void ChromiumCDMChild::OnRejectPromise(uint32_t aPromiseId,
    246                                       cdm::Exception aException,
    247                                       uint32_t aSystemCode,
    248                                       const char* aErrorMessage,
    249                                       uint32_t aErrorMessageSize) {
    250  GMP_LOG_DEBUG("ChromiumCDMChild::OnRejectPromise(pid=%" PRIu32
    251                ", err=%" PRIu32 " code=%" PRIu32 ", msg='%s')",
    252                aPromiseId, aException, aSystemCode, aErrorMessage);
    253  CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnRejectPromise",
    254                          &ChromiumCDMChild::SendOnRejectPromise, aPromiseId,
    255                          static_cast<uint32_t>(aException), aSystemCode,
    256                          nsCString(aErrorMessage, aErrorMessageSize));
    257 }
    258 
    259 void ChromiumCDMChild::OnSessionMessage(const char* aSessionId,
    260                                        uint32_t aSessionIdSize,
    261                                        cdm::MessageType aMessageType,
    262                                        const char* aMessage,
    263                                        uint32_t aMessageSize) {
    264  GMP_LOG_DEBUG("ChromiumCDMChild::OnSessionMessage(sid=%s, type=%" PRIu32
    265                " size=%" PRIu32 ")",
    266                aSessionId, aMessageType, aMessageSize);
    267  CopyableTArray<uint8_t> message;
    268  message.AppendElements(aMessage, aMessageSize);
    269  CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnSessionMessage",
    270                          &ChromiumCDMChild::SendOnSessionMessage,
    271                          nsCString(aSessionId, aSessionIdSize),
    272                          static_cast<uint32_t>(aMessageType), message);
    273 }
    274 
    275 static auto ToString(const cdm::KeyInformation* aKeysInfo,
    276                     uint32_t aKeysInfoCount) {
    277  return StringJoin(","_ns, Span{aKeysInfo, aKeysInfoCount},
    278                    [](auto& str, const cdm::KeyInformation& key) {
    279                      str.Append(ToHexString(key.key_id, key.key_id_size));
    280                      str.AppendLiteral("=");
    281                      str.AppendInt(key.status);
    282                    });
    283 }
    284 
    285 void ChromiumCDMChild::OnSessionKeysChange(const char* aSessionId,
    286                                           uint32_t aSessionIdSize,
    287                                           bool aHasAdditionalUsableKey,
    288                                           const cdm::KeyInformation* aKeysInfo,
    289                                           uint32_t aKeysInfoCount) {
    290  GMP_LOG_DEBUG("ChromiumCDMChild::OnSessionKeysChange(sid=%s) keys={%s}",
    291                aSessionId, ToString(aKeysInfo, aKeysInfoCount).get());
    292 
    293  CopyableTArray<CDMKeyInformation> keys;
    294  keys.SetCapacity(aKeysInfoCount);
    295  for (uint32_t i = 0; i < aKeysInfoCount; i++) {
    296    const cdm::KeyInformation& key = aKeysInfo[i];
    297    nsTArray<uint8_t> kid;
    298    kid.AppendElements(key.key_id, key.key_id_size);
    299    keys.AppendElement(CDMKeyInformation(kid, key.status, key.system_code));
    300  }
    301  CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnSessionMessage",
    302                          &ChromiumCDMChild::SendOnSessionKeysChange,
    303                          nsCString(aSessionId, aSessionIdSize), keys);
    304 }
    305 
    306 void ChromiumCDMChild::OnExpirationChange(const char* aSessionId,
    307                                          uint32_t aSessionIdSize,
    308                                          cdm::Time aNewExpiryTime) {
    309  GMP_LOG_DEBUG("ChromiumCDMChild::OnExpirationChange(sid=%s, time=%lf)",
    310                aSessionId, aNewExpiryTime);
    311  CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnExpirationChange",
    312                          &ChromiumCDMChild::SendOnExpirationChange,
    313                          nsCString(aSessionId, aSessionIdSize),
    314                          aNewExpiryTime);
    315 }
    316 
    317 void ChromiumCDMChild::OnSessionClosed(const char* aSessionId,
    318                                       uint32_t aSessionIdSize) {
    319  GMP_LOG_DEBUG("ChromiumCDMChild::OnSessionClosed(sid=%s)", aSessionId);
    320  CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnSessionClosed",
    321                          &ChromiumCDMChild::SendOnSessionClosed,
    322                          nsCString(aSessionId, aSessionIdSize));
    323 }
    324 
    325 void ChromiumCDMChild::QueryOutputProtectionStatus() {
    326  GMP_LOG_DEBUG("ChromiumCDMChild::QueryOutputProtectionStatus()");
    327  // We'll handle the response in `CompleteQueryOutputProtectionStatus`.
    328  CallOnMessageLoopThread("gmp::ChromiumCDMChild::QueryOutputProtectionStatus",
    329                          &ChromiumCDMChild::SendOnQueryOutputProtectionStatus);
    330 }
    331 
    332 void ChromiumCDMChild::OnInitialized(bool aSuccess) {
    333  MOZ_ASSERT(!mInitPromise.IsEmpty(),
    334             "mInitPromise should exist during init callback!");
    335  if (!aSuccess) {
    336    mInitPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
    337  }
    338  mInitPromise.ResolveIfExists(true, __func__);
    339 }
    340 
    341 cdm::FileIO* ChromiumCDMChild::CreateFileIO(cdm::FileIOClient* aClient) {
    342  MOZ_ASSERT(IsOnMessageLoopThread());
    343  GMP_LOG_DEBUG("ChromiumCDMChild::CreateFileIO()");
    344  if (!mPersistentStateAllowed) {
    345    return nullptr;
    346  }
    347  return new WidevineFileIO(aClient);
    348 }
    349 
    350 void ChromiumCDMChild::RequestStorageId(uint32_t aVersion) {
    351  MOZ_ASSERT(IsOnMessageLoopThread());
    352  GMP_LOG_DEBUG("ChromiumCDMChild::RequestStorageId() aVersion = %u", aVersion);
    353  // aVersion >= 0x80000000 are reserved.
    354  if (aVersion >= 0x80000000) {
    355    mCDM->OnStorageId(aVersion, nullptr, 0);
    356    return;
    357  }
    358  if (aVersion > CDMStorageIdProvider::kCurrentVersion) {
    359    mCDM->OnStorageId(aVersion, nullptr, 0);
    360    return;
    361  }
    362 
    363  mCDM->OnStorageId(CDMStorageIdProvider::kCurrentVersion,
    364                    !mStorageId.IsEmpty()
    365                        ? reinterpret_cast<const uint8_t*>(mStorageId.get())
    366                        : nullptr,
    367                    mStorageId.Length());
    368 }
    369 
    370 void ChromiumCDMChild::ReportMetrics(cdm::MetricName aMetricName,
    371                                     uint64_t aValue) {
    372  GMP_LOG_DEBUG("ChromiumCDMChild::ReportMetrics() aMetricName=%" PRIu32
    373                ", aValue=%" PRIu64,
    374                aMetricName, aValue);
    375 }
    376 
    377 ChromiumCDMChild::~ChromiumCDMChild() {
    378  GMP_LOG_DEBUG("ChromiumCDMChild:: dtor this=%p", this);
    379 }
    380 
    381 bool ChromiumCDMChild::IsOnMessageLoopThread() {
    382  return mPlugin && mPlugin->GMPMessageLoop() == MessageLoop::current();
    383 }
    384 
    385 void ChromiumCDMChild::ActorDestroy(ActorDestroyReason aReason) {
    386  mPlugin = nullptr;
    387 }
    388 
    389 void ChromiumCDMChild::PurgeShmems() {
    390  for (ipc::Shmem& shmem : mBuffers) {
    391    DeallocShmem(shmem);
    392  }
    393  mBuffers.Clear();
    394 }
    395 
    396 ipc::IPCResult ChromiumCDMChild::RecvPurgeShmems() {
    397  PurgeShmems();
    398  return IPC_OK();
    399 }
    400 
    401 mozilla::ipc::IPCResult ChromiumCDMChild::RecvInit(
    402    const bool& aAllowDistinctiveIdentifier, const bool& aAllowPersistentState,
    403    InitResolver&& aResolver) {
    404  MOZ_ASSERT(IsOnMessageLoopThread());
    405  GMP_LOG_DEBUG(
    406      "ChromiumCDMChild::RecvInit(distinctiveId=%s, persistentState=%s)",
    407      aAllowDistinctiveIdentifier ? "true" : "false",
    408      aAllowPersistentState ? "true" : "false");
    409  mPersistentStateAllowed = aAllowPersistentState;
    410 
    411  RefPtr<ChromiumCDMChild::InitPromise> promise = mInitPromise.Ensure(__func__);
    412  promise->Then(
    413      mPlugin->GMPMessageLoop()->SerialEventTarget(), __func__,
    414      [aResolver](bool /* unused */) { aResolver(true); },
    415      [aResolver](nsresult rv) {
    416        GMP_LOG_DEBUG(
    417            "ChromiumCDMChild::RecvInit() init promise rejected with "
    418            "rv=%" PRIu32,
    419            static_cast<uint32_t>(rv));
    420        aResolver(false);
    421      });
    422 
    423  if (mCDM) {
    424    // Once the CDM is initialized we expect it to resolve mInitPromise via
    425    // ChromiumCDMChild::OnInitialized
    426    mCDM->Initialize(aAllowDistinctiveIdentifier, aAllowPersistentState,
    427                     // We do not yet support hardware secure codecs
    428                     false);
    429  } else {
    430    GMP_LOG_DEBUG(
    431        "ChromiumCDMChild::RecvInit() mCDM not set! Is GMP shutting down?");
    432    mInitPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
    433  }
    434  return IPC_OK();
    435 }
    436 
    437 mozilla::ipc::IPCResult ChromiumCDMChild::RecvSetServerCertificate(
    438    const uint32_t& aPromiseId, nsTArray<uint8_t>&& aServerCert)
    439 
    440 {
    441  MOZ_ASSERT(IsOnMessageLoopThread());
    442  GMP_LOG_DEBUG("ChromiumCDMChild::RecvSetServerCertificate() certlen=%zu",
    443                aServerCert.Length());
    444  if (mCDM) {
    445    mCDM->SetServerCertificate(aPromiseId, aServerCert.Elements(),
    446                               aServerCert.Length());
    447  }
    448  return IPC_OK();
    449 }
    450 
    451 mozilla::ipc::IPCResult ChromiumCDMChild::RecvCreateSessionAndGenerateRequest(
    452    const uint32_t& aPromiseId, const uint32_t& aSessionType,
    453    const uint32_t& aInitDataType, nsTArray<uint8_t>&& aInitData) {
    454  MOZ_ASSERT(IsOnMessageLoopThread());
    455  GMP_LOG_DEBUG(
    456      "ChromiumCDMChild::RecvCreateSessionAndGenerateRequest("
    457      "pid=%" PRIu32 ", sessionType=%" PRIu32 ", initDataType=%" PRIu32
    458      ") initDataLen=%zu",
    459      aPromiseId, aSessionType, aInitDataType, aInitData.Length());
    460  MOZ_ASSERT(aSessionType <= cdm::SessionType::kPersistentLicense);
    461  MOZ_ASSERT(aInitDataType <= cdm::InitDataType::kWebM);
    462  if (mCDM) {
    463    mCDM->CreateSessionAndGenerateRequest(
    464        aPromiseId, static_cast<cdm::SessionType>(aSessionType),
    465        static_cast<cdm::InitDataType>(aInitDataType), aInitData.Elements(),
    466        aInitData.Length());
    467  }
    468  return IPC_OK();
    469 }
    470 
    471 mozilla::ipc::IPCResult ChromiumCDMChild::RecvLoadSession(
    472    const uint32_t& aPromiseId, const uint32_t& aSessionType,
    473    const nsACString& aSessionId) {
    474  MOZ_ASSERT(IsOnMessageLoopThread());
    475  GMP_LOG_DEBUG(
    476      "ChromiumCDMChild::RecvLoadSession(pid=%u, type=%u, sessionId=%s)",
    477      aPromiseId, aSessionType, PromiseFlatCString(aSessionId).get());
    478  if (mCDM) {
    479    mLoadSessionPromiseIds.AppendElement(aPromiseId);
    480    mCDM->LoadSession(aPromiseId, static_cast<cdm::SessionType>(aSessionType),
    481                      aSessionId.BeginReading(), aSessionId.Length());
    482  }
    483  return IPC_OK();
    484 }
    485 
    486 mozilla::ipc::IPCResult ChromiumCDMChild::RecvUpdateSession(
    487    const uint32_t& aPromiseId, const nsACString& aSessionId,
    488    nsTArray<uint8_t>&& aResponse) {
    489  MOZ_ASSERT(IsOnMessageLoopThread());
    490  GMP_LOG_DEBUG("ChromiumCDMChild::RecvUpdateSession(pid=%" PRIu32
    491                ", sid=%s) responseLen=%zu",
    492                aPromiseId, PromiseFlatCString(aSessionId).get(),
    493                aResponse.Length());
    494  if (mCDM) {
    495    mCDM->UpdateSession(aPromiseId, aSessionId.BeginReading(),
    496                        aSessionId.Length(), aResponse.Elements(),
    497                        aResponse.Length());
    498  }
    499  return IPC_OK();
    500 }
    501 
    502 mozilla::ipc::IPCResult ChromiumCDMChild::RecvCloseSession(
    503    const uint32_t& aPromiseId, const nsACString& aSessionId) {
    504  MOZ_ASSERT(IsOnMessageLoopThread());
    505  GMP_LOG_DEBUG("ChromiumCDMChild::RecvCloseSession(pid=%" PRIu32 ", sid=%s)",
    506                aPromiseId, PromiseFlatCString(aSessionId).get());
    507  if (mCDM) {
    508    mCDM->CloseSession(aPromiseId, aSessionId.BeginReading(),
    509                       aSessionId.Length());
    510  }
    511  return IPC_OK();
    512 }
    513 
    514 mozilla::ipc::IPCResult ChromiumCDMChild::RecvRemoveSession(
    515    const uint32_t& aPromiseId, const nsACString& aSessionId) {
    516  MOZ_ASSERT(IsOnMessageLoopThread());
    517  GMP_LOG_DEBUG("ChromiumCDMChild::RecvRemoveSession(pid=%" PRIu32 ", sid=%s)",
    518                aPromiseId, PromiseFlatCString(aSessionId).get());
    519  if (mCDM) {
    520    mCDM->RemoveSession(aPromiseId, aSessionId.BeginReading(),
    521                        aSessionId.Length());
    522  }
    523  return IPC_OK();
    524 }
    525 
    526 mozilla::ipc::IPCResult
    527 ChromiumCDMChild::RecvCompleteQueryOutputProtectionStatus(
    528    const bool& aSuccess, const uint32_t& aLinkMask,
    529    const uint32_t& aProtectionMask) {
    530  MOZ_ASSERT(IsOnMessageLoopThread());
    531  GMP_LOG_DEBUG(
    532      "ChromiumCDMChild::RecvCompleteQueryOutputProtectionStatus(aSuccess=%s, "
    533      "aLinkMask=%" PRIu32 ", aProtectionMask=%" PRIu32 ")",
    534      aSuccess ? "true" : "false", aLinkMask, aProtectionMask);
    535 
    536  if (mCDM) {
    537    cdm::QueryResult queryResult = aSuccess ? cdm::QueryResult::kQuerySucceeded
    538                                            : cdm::QueryResult::kQueryFailed;
    539    mCDM->OnQueryOutputProtectionStatus(queryResult, aLinkMask,
    540                                        aProtectionMask);
    541  }
    542 
    543  return IPC_OK();
    544 }
    545 
    546 mozilla::ipc::IPCResult ChromiumCDMChild::RecvGetStatusForPolicy(
    547    const uint32_t& aPromiseId, const cdm::HdcpVersion& aMinHdcpVersion) {
    548  MOZ_ASSERT(IsOnMessageLoopThread());
    549  GMP_LOG_DEBUG("ChromiumCDMChild::RecvGetStatusForPolicy(pid=%" PRIu32
    550                ", MinHdcpVersion=%" PRIu32 ")",
    551                aPromiseId, static_cast<uint32_t>(aMinHdcpVersion));
    552  if (mCDM) {
    553    cdm::Policy policy;
    554    policy.min_hdcp_version = aMinHdcpVersion;
    555    mCDM->GetStatusForPolicy(aPromiseId, policy);
    556  }
    557  return IPC_OK();
    558 }
    559 
    560 static void InitInputBuffer(const CDMInputBuffer& aBuffer,
    561                            nsTArray<cdm::SubsampleEntry>& aSubSamples,
    562                            cdm::InputBuffer_2& aInputBuffer) {
    563  aInputBuffer.data = aBuffer.mData().get<uint8_t>();
    564  aInputBuffer.data_size = aBuffer.mData().Size<uint8_t>();
    565 
    566  if (aBuffer.mEncryptionScheme() != cdm::EncryptionScheme::kUnencrypted) {
    567    MOZ_ASSERT(aBuffer.mEncryptionScheme() == cdm::EncryptionScheme::kCenc ||
    568               aBuffer.mEncryptionScheme() == cdm::EncryptionScheme::kCbcs);
    569    aInputBuffer.key_id = aBuffer.mKeyId().Elements();
    570    aInputBuffer.key_id_size = aBuffer.mKeyId().Length();
    571 
    572    aInputBuffer.iv = aBuffer.mIV().Elements();
    573    aInputBuffer.iv_size = aBuffer.mIV().Length();
    574 
    575    aSubSamples.SetCapacity(aBuffer.mClearBytes().Length());
    576    for (size_t i = 0; i < aBuffer.mCipherBytes().Length(); i++) {
    577      aSubSamples.AppendElement(cdm::SubsampleEntry{aBuffer.mClearBytes()[i],
    578                                                    aBuffer.mCipherBytes()[i]});
    579    }
    580    aInputBuffer.subsamples = aSubSamples.Elements();
    581    aInputBuffer.num_subsamples = aSubSamples.Length();
    582    aInputBuffer.encryption_scheme = aBuffer.mEncryptionScheme();
    583  }
    584  aInputBuffer.pattern.crypt_byte_block = aBuffer.mCryptByteBlock();
    585  aInputBuffer.pattern.skip_byte_block = aBuffer.mSkipByteBlock();
    586  aInputBuffer.timestamp = aBuffer.mTimestamp();
    587 }
    588 
    589 bool ChromiumCDMChild::HasShmemOfSize(size_t aSize) const {
    590  for (const ipc::Shmem& shmem : mBuffers) {
    591    if (shmem.Size<uint8_t>() == aSize) {
    592      return true;
    593    }
    594  }
    595  return false;
    596 }
    597 
    598 mozilla::ipc::IPCResult ChromiumCDMChild::RecvDecrypt(
    599    const uint32_t& aId, const CDMInputBuffer& aBuffer) {
    600  MOZ_ASSERT(IsOnMessageLoopThread());
    601  GMP_LOG_DEBUG("ChromiumCDMChild::RecvDecrypt()");
    602 
    603  // Parent should have already gifted us a shmem to use as output.
    604  size_t outputShmemSize = aBuffer.mData().Size<uint8_t>();
    605  MOZ_ASSERT(HasShmemOfSize(outputShmemSize));
    606 
    607  // Ensure we deallocate the shmem used to send input.
    608  RefPtr<ChromiumCDMChild> self = this;
    609  auto autoDeallocateInputShmem =
    610      MakeScopeExit([&, self] { self->DeallocShmem(aBuffer.mData()); });
    611 
    612  // On failure, we need to ensure that the shmem that the parent sent
    613  // for the CDM to use to return output back to the parent is deallocated.
    614  // Otherwise, it will leak.
    615  auto autoDeallocateOutputShmem = MakeScopeExit([self, outputShmemSize] {
    616    self->mBuffers.RemoveElementsBy(
    617        [outputShmemSize, self](ipc::Shmem& aShmem) {
    618          if (aShmem.Size<uint8_t>() != outputShmemSize) {
    619            return false;
    620          }
    621          self->DeallocShmem(aShmem);
    622          return true;
    623        });
    624  });
    625 
    626  if (!mCDM) {
    627    GMP_LOG_DEBUG("ChromiumCDMChild::RecvDecrypt() no CDM");
    628    (void)SendDecryptFailed(aId, cdm::kDecryptError);
    629    return IPC_OK();
    630  }
    631  if (aBuffer.mClearBytes().Length() != aBuffer.mCipherBytes().Length()) {
    632    GMP_LOG_DEBUG(
    633        "ChromiumCDMChild::RecvDecrypt() clear/cipher bytes length doesn't "
    634        "match");
    635    (void)SendDecryptFailed(aId, cdm::kDecryptError);
    636    return IPC_OK();
    637  }
    638 
    639  cdm::InputBuffer_2 input = {};
    640  nsTArray<cdm::SubsampleEntry> subsamples;
    641  InitInputBuffer(aBuffer, subsamples, input);
    642 
    643  WidevineDecryptedBlock output;
    644  cdm::Status status = mCDM->Decrypt(input, &output);
    645 
    646  // CDM should have allocated a cdm::Buffer for output.
    647  if (status != cdm::kSuccess || !output.DecryptedBuffer()) {
    648    (void)SendDecryptFailed(aId, status);
    649    return IPC_OK();
    650  }
    651 
    652  auto* buffer = static_cast<CDMBuffer*>(output.DecryptedBuffer());
    653  if (auto* shmemBuffer = buffer->AsShmemBuffer()) {
    654    MOZ_ASSERT(!HasShmemOfSize(outputShmemSize));
    655    ipc::Shmem shmem = shmemBuffer->ExtractShmem();
    656    if (SendDecryptedShmem(aId, cdm::kSuccess, std::move(shmem))) {
    657      // No need to deallocate the output shmem; it should have been returned
    658      // to the content process.
    659      autoDeallocateOutputShmem.release();
    660    }
    661    return IPC_OK();
    662  }
    663 
    664  if (auto* arrayBuffer = buffer->AsArrayBuffer()) {
    665    (void)SendDecryptedData(aId, cdm::kSuccess, arrayBuffer->ExtractBuffer());
    666    return IPC_OK();
    667  }
    668 
    669  MOZ_ASSERT_UNREACHABLE("Unexpected CDMBuffer type!");
    670  GMP_LOG_DEBUG("ChromiumCDMChild::RecvDecrypt() unexpected CDMBuffer type");
    671  (void)SendDecryptFailed(aId, cdm::kDecryptError);
    672  return IPC_OK();
    673 }
    674 
    675 mozilla::ipc::IPCResult ChromiumCDMChild::RecvInitializeVideoDecoder(
    676    const CDMVideoDecoderConfig& aConfig) {
    677  MOZ_ASSERT(IsOnMessageLoopThread());
    678  MOZ_ASSERT(!mDecoderInitialized);
    679  if (!mCDM) {
    680    GMP_LOG_DEBUG("ChromiumCDMChild::RecvInitializeVideoDecoder() no CDM");
    681    (void)SendOnDecoderInitDone(cdm::kInitializationError);
    682    return IPC_OK();
    683  }
    684  cdm::VideoDecoderConfig_2 config = {};
    685  config.codec = static_cast<cdm::VideoCodec>(aConfig.mCodec());
    686  config.profile = static_cast<cdm::VideoCodecProfile>(aConfig.mProfile());
    687  config.format = static_cast<cdm::VideoFormat>(aConfig.mFormat());
    688  config.coded_size =
    689      mCodedSize = {aConfig.mImageWidth(), aConfig.mImageHeight()};
    690  nsTArray<uint8_t> extraData(aConfig.mExtraData().Clone());
    691  config.extra_data = extraData.Elements();
    692  config.extra_data_size = extraData.Length();
    693  config.encryption_scheme = aConfig.mEncryptionScheme();
    694  cdm::Status status = mCDM->InitializeVideoDecoder(config);
    695  GMP_LOG_DEBUG("ChromiumCDMChild::RecvInitializeVideoDecoder() status=%u",
    696                status);
    697  (void)SendOnDecoderInitDone(status);
    698  mDecoderInitialized = status == cdm::kSuccess;
    699  return IPC_OK();
    700 }
    701 
    702 mozilla::ipc::IPCResult ChromiumCDMChild::RecvDeinitializeVideoDecoder() {
    703  MOZ_ASSERT(IsOnMessageLoopThread());
    704  GMP_LOG_DEBUG("ChromiumCDMChild::RecvDeinitializeVideoDecoder()");
    705  MOZ_ASSERT(mDecoderInitialized);
    706  if (mDecoderInitialized && mCDM) {
    707    mCDM->DeinitializeDecoder(cdm::kStreamTypeVideo);
    708  }
    709  mDecoderInitialized = false;
    710  PurgeShmems();
    711  return IPC_OK();
    712 }
    713 
    714 mozilla::ipc::IPCResult ChromiumCDMChild::RecvResetVideoDecoder() {
    715  MOZ_ASSERT(IsOnMessageLoopThread());
    716  GMP_LOG_DEBUG("ChromiumCDMChild::RecvResetVideoDecoder()");
    717  if (mDecoderInitialized && mCDM) {
    718    mCDM->ResetDecoder(cdm::kStreamTypeVideo);
    719  }
    720  (void)SendResetVideoDecoderComplete();
    721  return IPC_OK();
    722 }
    723 
    724 mozilla::ipc::IPCResult ChromiumCDMChild::RecvDecryptAndDecodeFrame(
    725    const CDMInputBuffer& aBuffer) {
    726  MOZ_ASSERT(IsOnMessageLoopThread());
    727  GMP_LOG_DEBUG("ChromiumCDMChild::RecvDecryptAndDecodeFrame() t=%" PRId64 ")",
    728                aBuffer.mTimestamp());
    729  MOZ_ASSERT(mDecoderInitialized);
    730 
    731  if (!mCDM) {
    732    GMP_LOG_DEBUG("ChromiumCDMChild::RecvDecryptAndDecodeFrame() no CDM");
    733    (void)SendDecodeFailed(cdm::kDecodeError);
    734    return IPC_OK();
    735  }
    736 
    737  RefPtr<ChromiumCDMChild> self = this;
    738  auto autoDeallocateShmem =
    739      MakeScopeExit([&, self] { self->DeallocShmem(aBuffer.mData()); });
    740 
    741  // The output frame may not have the same timestamp as the frame we put in.
    742  // We may need to input a number of frames before we receive output. The
    743  // CDM's decoder reorders to ensure frames output are in presentation order.
    744  // So we need to store the durations of the frames input, and retrieve them
    745  // on output.
    746  mFrameDurations.Insert(aBuffer.mTimestamp(), aBuffer.mDuration());
    747 
    748  cdm::InputBuffer_2 input = {};
    749  nsTArray<cdm::SubsampleEntry> subsamples;
    750  InitInputBuffer(aBuffer, subsamples, input);
    751 
    752  WidevineVideoFrame frame;
    753  cdm::Status rv = mCDM->DecryptAndDecodeFrame(input, &frame);
    754  GMP_LOG_DEBUG("ChromiumCDMChild::RecvDecryptAndDecodeFrame() t=%" PRId64
    755                " CDM decoder rv=%d",
    756                aBuffer.mTimestamp(), rv);
    757 
    758  switch (rv) {
    759    case cdm::kNeedMoreData:
    760      (void)SendDecodeFailed(rv);
    761      break;
    762    case cdm::kNoKey:
    763      GMP_LOG_DEBUG("NoKey for sample at time=%" PRId64 "!", input.timestamp);
    764      // Somehow our key became unusable. Typically this would happen when
    765      // a stream requires output protection, and the configuration changed
    766      // such that output protection is no longer available. For example, a
    767      // non-compliant monitor was attached. The JS player should notice the
    768      // key status changing to "output-restricted", and is supposed to switch
    769      // to a stream that doesn't require OP. In order to keep the playback
    770      // pipeline rolling, just output a black frame. See bug 1343140.
    771      if (!frame.InitToBlack(mCodedSize.width, mCodedSize.height,
    772                             input.timestamp)) {
    773        (void)SendDecodeFailed(cdm::kDecodeError);
    774        break;
    775      }
    776      [[fallthrough]];
    777    case cdm::kSuccess:
    778      if (frame.FrameBuffer()) {
    779        ReturnOutput(frame);
    780        break;
    781      }
    782      // CDM didn't set a frame buffer on the sample, report it as an error.
    783      [[fallthrough]];
    784    default:
    785      (void)SendDecodeFailed(rv);
    786      break;
    787  }
    788 
    789  return IPC_OK();
    790 }
    791 
    792 void ChromiumCDMChild::ReturnOutput(WidevineVideoFrame& aFrame) {
    793  MOZ_ASSERT(IsOnMessageLoopThread());
    794  MOZ_ASSERT(aFrame.FrameBuffer());
    795  gmp::CDMVideoFrame output;
    796  output.mFormat() = static_cast<cdm::VideoFormat>(aFrame.Format());
    797  output.mImageWidth() = aFrame.Size().width;
    798  output.mImageHeight() = aFrame.Size().height;
    799  output.mYPlane() = {aFrame.PlaneOffset(cdm::kYPlane),
    800                      aFrame.Stride(cdm::kYPlane)};
    801  output.mUPlane() = {aFrame.PlaneOffset(cdm::kUPlane),
    802                      aFrame.Stride(cdm::kUPlane)};
    803  output.mVPlane() = {aFrame.PlaneOffset(cdm::kVPlane),
    804                      aFrame.Stride(cdm::kVPlane)};
    805  output.mTimestamp() = aFrame.Timestamp();
    806 
    807  uint64_t duration = 0;
    808  if (mFrameDurations.Find(aFrame.Timestamp(), duration)) {
    809    output.mDuration() = duration;
    810  }
    811 
    812  CDMBuffer* base = reinterpret_cast<CDMBuffer*>(aFrame.FrameBuffer());
    813  if (auto* shmemBase = base->AsShmemBuffer()) {
    814    ipc::Shmem shmem = shmemBase->ExtractShmem();
    815    (void)SendDecodedShmem(output, std::move(shmem));
    816    return;
    817  }
    818 
    819  if (auto* arrayBase = base->AsArrayBuffer()) {
    820    (void)SendDecodedData(output, arrayBase->ExtractBuffer());
    821    return;
    822  }
    823 
    824  MOZ_ASSERT_UNREACHABLE("Unexpected CDMBuffer type!");
    825 }
    826 
    827 mozilla::ipc::IPCResult ChromiumCDMChild::RecvDrain() {
    828  MOZ_ASSERT(IsOnMessageLoopThread());
    829  if (!mCDM) {
    830    GMP_LOG_DEBUG("ChromiumCDMChild::RecvDrain() no CDM");
    831    (void)SendDrainComplete();
    832    return IPC_OK();
    833  }
    834  WidevineVideoFrame frame;
    835  cdm::InputBuffer_2 sample = {};
    836  cdm::Status rv = mCDM->DecryptAndDecodeFrame(sample, &frame);
    837  GMP_LOG_DEBUG("ChromiumCDMChild::RecvDrain();  DecryptAndDecodeFrame() rv=%d",
    838                rv);
    839  if (rv == cdm::kSuccess) {
    840    MOZ_ASSERT(frame.Format() != cdm::kUnknownVideoFormat);
    841    ReturnOutput(frame);
    842  } else {
    843    (void)SendDrainComplete();
    844  }
    845  return IPC_OK();
    846 }
    847 
    848 mozilla::ipc::IPCResult ChromiumCDMChild::RecvDestroy() {
    849  MOZ_ASSERT(IsOnMessageLoopThread());
    850  GMP_LOG_DEBUG("ChromiumCDMChild::RecvDestroy()");
    851 
    852  MOZ_ASSERT(!mDecoderInitialized);
    853 
    854  mInitPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
    855 
    856  if (mCDM) {
    857    mCDM->Destroy();
    858    mCDM = nullptr;
    859  }
    860  mDestroyed = true;
    861 
    862  (void)Send__delete__(this);
    863 
    864  return IPC_OK();
    865 }
    866 
    867 mozilla::ipc::IPCResult ChromiumCDMChild::RecvGiveBuffer(ipc::Shmem&& aBuffer) {
    868  MOZ_ASSERT(IsOnMessageLoopThread());
    869 
    870  GiveBuffer(std::move(aBuffer));
    871  return IPC_OK();
    872 }
    873 
    874 void ChromiumCDMChild::GiveBuffer(ipc::Shmem&& aBuffer) {
    875  MOZ_ASSERT(IsOnMessageLoopThread());
    876  size_t sz = aBuffer.Size<uint8_t>();
    877  mBuffers.AppendElement(std::move(aBuffer));
    878  GMP_LOG_DEBUG(
    879      "ChromiumCDMChild::RecvGiveBuffer(capacity=%zu"
    880      ") bufferSizes={%s} mDecoderInitialized=%d",
    881      sz, ToString(mBuffers).get(), mDecoderInitialized);
    882 }
    883 
    884 }  // namespace mozilla::gmp