Bug 1315850 - Send decrypt operations to Chromium CDM. r=gerald draft
authorChris Pearce <cpearce@mozilla.com>
Thu, 09 Mar 2017 11:31:07 +1300
changeset 504166 67575a02290ddb871510dd88f59fdab77658b3ce
parent 504165 b7de793c7676ace43d34a9556ef803e1bd3df239
child 504167 90de89bec9b004859c3c2c09ed8efbd255acc141
push id50748
push userbmo:cpearce@mozilla.com
push dateFri, 24 Mar 2017 01:10:17 +0000
reviewersgerald
bugs1315850
milestone55.0a1
Bug 1315850 - Send decrypt operations to Chromium CDM. r=gerald We still use the same EMEDecryptor MediaDataDecoder as is used by the existing EME decrypting path. MozReview-Commit-ID: 3pXPjChctLb
dom/media/gmp/ChromiumCDMChild.cpp
dom/media/gmp/ChromiumCDMChild.h
dom/media/gmp/ChromiumCDMParent.cpp
dom/media/gmp/ChromiumCDMParent.h
dom/media/gmp/ChromiumCDMProxy.cpp
dom/media/gmp/PChromiumCDM.ipdl
--- a/dom/media/gmp/ChromiumCDMChild.cpp
+++ b/dom/media/gmp/ChromiumCDMChild.cpp
@@ -305,20 +305,88 @@ ChromiumCDMChild::RecvRemoveSession(cons
           aPromiseId,
           aSessionId.get());
   if (mCDM) {
     mCDM->RemoveSession(aPromiseId, aSessionId.get(), aSessionId.Length());
   }
   return IPC_OK();
 }
 
+void
+ChromiumCDMChild::DecryptFailed(uint32_t aId, cdm::Status aStatus)
+{
+  Unused << SendDecrypted(aId, aStatus, nsTArray<uint8_t>());
+}
+
+static void
+InitInputBuffer(const CDMInputBuffer& aBuffer,
+                nsTArray<cdm::SubsampleEntry>& aSubSamples,
+                cdm::InputBuffer& aInputBuffer)
+{
+  aInputBuffer.data = aBuffer.mData().Elements();
+  aInputBuffer.data_size = aBuffer.mData().Length();
+
+  if (aBuffer.mIsEncrypted()) {
+    aInputBuffer.key_id = aBuffer.mKeyId().Elements();
+    aInputBuffer.key_id_size = aBuffer.mKeyId().Length();
+
+    aInputBuffer.iv = aBuffer.mIV().Elements();
+    aInputBuffer.iv_size = aBuffer.mIV().Length();
+
+    aSubSamples.SetCapacity(aBuffer.mClearBytes().Length());
+    for (size_t i = 0; i < aBuffer.mCipherBytes().Length(); i++) {
+      aSubSamples.AppendElement(cdm::SubsampleEntry(aBuffer.mClearBytes()[i],
+                                                    aBuffer.mCipherBytes()[i]));
+    }
+    aInputBuffer.subsamples = aSubSamples.Elements();
+    aInputBuffer.num_subsamples = aSubSamples.Length();
+  }
+  aInputBuffer.timestamp = aBuffer.mTimestamp();
+}
+
 mozilla::ipc::IPCResult
-ChromiumCDMChild::RecvDecrypt(const CDMInputBuffer& aBuffer)
+ChromiumCDMChild::RecvDecrypt(const uint32_t& aId,
+                              const CDMInputBuffer& aBuffer)
 {
   GMP_LOG("ChromiumCDMChild::RecvDecrypt()");
+  if (!mCDM) {
+    GMP_LOG("ChromiumCDMChild::RecvDecrypt() no CDM");
+    DecryptFailed(aId, cdm::kDecryptError);
+    return IPC_OK();
+  }
+  if (aBuffer.mClearBytes().Length() != aBuffer.mCipherBytes().Length()) {
+    GMP_LOG("ChromiumCDMChild::RecvDecrypt() clear/cipher bytes length doesn't "
+            "match");
+    DecryptFailed(aId, cdm::kDecryptError);
+    return IPC_OK();
+  }
+
+  cdm::InputBuffer input;
+  nsTArray<cdm::SubsampleEntry> subsamples;
+  InitInputBuffer(aBuffer, subsamples, input);
+
+  WidevineDecryptedBlock output;
+  cdm::Status status = mCDM->Decrypt(input, &output);
+
+  if (status != cdm::kSuccess) {
+    DecryptFailed(aId, status);
+    return IPC_OK();
+  }
+
+  if (!output.DecryptedBuffer() ||
+      output.DecryptedBuffer()->Size() != aBuffer.mData().Length()) {
+    // The sizes of the input and output should exactly match.
+    DecryptFailed(aId, cdm::kDecryptError);
+    return IPC_OK();
+  }
+
+  nsTArray<uint8_t> buf =
+    static_cast<WidevineBuffer*>(output.DecryptedBuffer())->ExtractBuffer();
+  Unused << SendDecrypted(aId, cdm::kSuccess, buf);
+
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ChromiumCDMChild::RecvInitializeVideoDecoder(
   const CDMVideoDecoderConfig& aConfig)
 {
   GMP_LOG("ChromiumCDMChild::RecvInitializeVideoDecoder()");
--- a/dom/media/gmp/ChromiumCDMChild.h
+++ b/dom/media/gmp/ChromiumCDMChild.h
@@ -86,25 +86,28 @@ protected:
     nsTArray<uint8_t>&& aInitData) override;
   ipc::IPCResult RecvUpdateSession(const uint32_t& aPromiseId,
                                    const nsCString& aSessionId,
                                    nsTArray<uint8_t>&& aResponse) override;
   ipc::IPCResult RecvCloseSession(const uint32_t& aPromiseId,
                                   const nsCString& aSessionId) override;
   ipc::IPCResult RecvRemoveSession(const uint32_t& aPromiseId,
                                    const nsCString& aSessionId) override;
-  ipc::IPCResult RecvDecrypt(const CDMInputBuffer& aBuffer) override;
+  ipc::IPCResult RecvDecrypt(const uint32_t& aId,
+                             const CDMInputBuffer& aBuffer) override;
   ipc::IPCResult RecvInitializeVideoDecoder(
     const CDMVideoDecoderConfig& aConfig) override;
   ipc::IPCResult RecvDeinitializeVideoDecoder() override;
   ipc::IPCResult RecvResetVideoDecoder() override;
   ipc::IPCResult RecvDecryptAndDecodeFrame(
     const CDMInputBuffer& aBuffer) override;
   ipc::IPCResult RecvDestroy() override;
 
+  void DecryptFailed(uint32_t aId, cdm::Status aStatus);
+
   GMPContentChild* mPlugin = nullptr;
   cdm::ContentDecryptionModule_8* mCDM = nullptr;
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // ChromiumCDMChild_h_
--- a/dom/media/gmp/ChromiumCDMParent.cpp
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -104,16 +104,60 @@ ChromiumCDMParent::RemoveSession(const n
   if (!SendRemoveSession(aPromiseId, aSessionId)) {
     RejectPromise(
       aPromiseId,
       NS_ERROR_DOM_INVALID_STATE_ERR,
       NS_LITERAL_CSTRING("Failed to send removeSession to CDM process"));
   }
 }
 
+static bool
+InitCDMInputBuffer(gmp::CDMInputBuffer& aBuffer, MediaRawData* aSample)
+{
+  const CryptoSample& crypto = aSample->mCrypto;
+  if (crypto.mEncryptedSizes.Length() != crypto.mPlainSizes.Length()) {
+    GMP_LOG("InitCDMInputBuffer clear/cipher subsamples don't match");
+    return false;
+  }
+
+  nsTArray<uint8_t> data;
+  data.AppendElements(aSample->Data(), aSample->Size());
+
+  aBuffer = gmp::CDMInputBuffer(data,
+                                crypto.mKeyId,
+                                crypto.mIV,
+                                aSample->mTime,
+                                aSample->mDuration,
+                                crypto.mPlainSizes,
+                                crypto.mEncryptedSizes,
+                                crypto.mValid);
+  return true;
+}
+
+RefPtr<DecryptPromise>
+ChromiumCDMParent::Decrypt(MediaRawData* aSample)
+{
+  CDMInputBuffer buffer;
+  if (!InitCDMInputBuffer(buffer, aSample)) {
+    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
+                                           __func__);
+  }
+  RefPtr<DecryptJob> job = new DecryptJob(aSample);
+  if (!SendDecrypt(job->mId, buffer)) {
+    GMP_LOG(
+      "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message",
+      this);
+    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
+                                           __func__);
+  }
+  RefPtr<DecryptPromise> promise = job->Ensure();
+  mDecrypts.AppendElement(job);
+  return promise;
+}
+
 ipc::IPCResult
 ChromiumCDMParent::Recv__delete__()
 {
   GMP_LOG("ChromiumCDMParent::Recv__delete__(this=%p)", this);
   return IPC_OK();
 }
 
 ipc::IPCResult
@@ -361,20 +405,45 @@ ChromiumCDMParent::RecvOnLegacySessionEr
       &ChromiumCDMProxy::OnSessionError,
       NS_ConvertUTF8toUTF16(aSessionId),
       ToNsresult(aError),
       aSystemCode,
       NS_ConvertUTF8toUTF16(aMessage)));
   return IPC_OK();
 }
 
+DecryptStatus
+ToDecryptStatus(uint32_t aError)
+{
+  switch (static_cast<cdm::Status>(aError)) {
+    case cdm::kSuccess:
+      return DecryptStatus::Ok;
+    case cdm::kNoKey:
+      return DecryptStatus::NoKeyErr;
+    default:
+      return DecryptStatus::GenericErr;
+  }
+}
+
 ipc::IPCResult
-ChromiumCDMParent::RecvDecrypted(const uint32_t& aStatus,
+ChromiumCDMParent::RecvDecrypted(const uint32_t& aId,
+                                 const uint32_t& aStatus,
                                  nsTArray<uint8_t>&& aData)
 {
+  GMP_LOG("ChromiumCDMParent::RecvDecrypted(this=%p, id=%u, status=%u)",
+          this,
+          aId,
+          aStatus);
+  for (size_t i = 0; i < mDecrypts.Length(); i++) {
+    if (mDecrypts[i]->mId == aId) {
+      mDecrypts[i]->PostResult(ToDecryptStatus(aStatus), aData);
+      mDecrypts.RemoveElementAt(i);
+      break;
+    }
+  }
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvDecoded(const CDMVideoFrame& aFrame)
 {
   return IPC_OK();
 }
--- a/dom/media/gmp/ChromiumCDMParent.h
+++ b/dom/media/gmp/ChromiumCDMParent.h
@@ -7,19 +7,21 @@
 #define ChromiumCDMParent_h_
 
 #include "GMPCrashHelper.h"
 #include "GMPCrashHelperHolder.h"
 #include "GMPMessageUtils.h"
 #include "mozilla/gmp/PChromiumCDMParent.h"
 #include "mozilla/RefPtr.h"
 #include "nsDataHashtable.h"
+#include "DecryptJob.h"
 
 namespace mozilla {
 
+class MediaRawData;
 class ChromiumCDMProxy;
 
 namespace gmp {
 
 class GMPContentParent;
 
 class ChromiumCDMParent final
   : public PChromiumCDMParent
@@ -48,16 +50,18 @@ public:
   void UpdateSession(const nsCString& aSessionId,
                      uint32_t aPromiseId,
                      const nsTArray<uint8_t>& aResponse);
 
   void CloseSession(const nsCString& aSessionId, uint32_t aPromiseId);
 
   void RemoveSession(const nsCString& aSessionId, uint32_t aPromiseId);
 
+  RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample);
+
   // TODO: Add functions for clients to send data to CDM, and
   // a Close() function.
 
 protected:
   ~ChromiumCDMParent() {}
 
   ipc::IPCResult Recv__delete__() override;
   ipc::IPCResult RecvOnResolveNewSessionPromise(
@@ -77,17 +81,18 @@ protected:
   ipc::IPCResult RecvOnExpirationChange(
     const nsCString& aSessionId,
     const double& aSecondsSinceEpoch) override;
   ipc::IPCResult RecvOnSessionClosed(const nsCString& aSessionId) override;
   ipc::IPCResult RecvOnLegacySessionError(const nsCString& aSessionId,
                                           const uint32_t& aError,
                                           const uint32_t& aSystemCode,
                                           const nsCString& aMessage) override;
-  ipc::IPCResult RecvDecrypted(const uint32_t& aStatus,
+  ipc::IPCResult RecvDecrypted(const uint32_t& aId,
+                               const uint32_t& aStatus,
                                nsTArray<uint8_t>&& aData) override;
   ipc::IPCResult RecvDecoded(const CDMVideoFrame& aFrame) override;
   ipc::IPCResult RecvDecodeFailed(const uint32_t& aStatus) override;
   ipc::IPCResult RecvShutdown() override;
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void RejectPromise(uint32_t aPromiseId,
                      nsresult aError,
@@ -97,14 +102,15 @@ protected:
 
   const uint32_t mPluginId;
   GMPContentParent* mContentParent;
   // Note: this pointer is a weak reference because otherwise it would cause
   // a cycle, as ChromiumCDMProxy has a strong reference to the
   // ChromiumCDMParent.
   ChromiumCDMProxy* mProxy = nullptr;
   nsDataHashtable<nsUint32HashKey, uint32_t> mPromiseToCreateSessionToken;
+  nsTArray<RefPtr<DecryptJob>> mDecrypts;
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // ChromiumCDMParent_h_
--- a/dom/media/gmp/ChromiumCDMProxy.cpp
+++ b/dom/media/gmp/ChromiumCDMProxy.cpp
@@ -4,17 +4,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ChromiumCDMProxy.h"
 #include "mozilla/dom/MediaKeySession.h"
 #include "GMPUtils.h"
 #include "nsPrintfCString.h"
 #include "GMPService.h"
-#include "mozilla/dom/MediaKeySession.h"
 
 namespace mozilla {
 
 ChromiumCDMProxy::ChromiumCDMProxy(dom::MediaKeys* aKeys,
                                    const nsAString& aKeySystem,
                                    GMPCrashHelper* aCrashHelper,
                                    bool aDistinctiveIdentifierRequired,
                                    bool aPersistentStateRequired,
@@ -469,18 +468,20 @@ CDMCaps&
 ChromiumCDMProxy::Capabilites()
 {
   return mCapabilites;
 }
 
 RefPtr<DecryptPromise>
 ChromiumCDMProxy::Decrypt(MediaRawData* aSample)
 {
-  return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, nullptr),
-                                         __func__);
+  RefPtr<gmp::ChromiumCDMParent> cdm = mCDM;
+  RefPtr<MediaRawData> sample = aSample;
+  return InvokeAsync(
+    mGMPThread, __func__, [cdm, sample]() { return cdm->Decrypt(sample); });
 }
 
 void
 ChromiumCDMProxy::GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
                                         nsTArray<nsCString>& aSessionIds)
 {
   CDMCaps::AutoLock caps(Capabilites());
   caps.GetSessionIdsForKeyId(aKeyId, aSessionIds);
--- a/dom/media/gmp/PChromiumCDM.ipdl
+++ b/dom/media/gmp/PChromiumCDM.ipdl
@@ -31,17 +31,17 @@ child:
                       uint8_t[] aResponse);
 
   async CloseSession(uint32_t aPromiseId,
                      nsCString aSessionId);
 
   async RemoveSession(uint32_t aPromiseId,
                       nsCString aSessionId);
 
-  async Decrypt(CDMInputBuffer aBuffer);
+  async Decrypt(uint32_t aId, CDMInputBuffer aBuffer);
 
   async InitializeVideoDecoder(CDMVideoDecoderConfig aConfig);
 
   async DeinitializeVideoDecoder();
 
   async ResetVideoDecoder();
 
   async DecryptAndDecodeFrame(CDMInputBuffer aBuffer);
@@ -74,17 +74,17 @@ parent:
   async OnSessionClosed(nsCString aSessionId);
 
   async OnLegacySessionError(nsCString aSessionId,
                              uint32_t aError,
                              uint32_t aSystemCode,
                              nsCString aMessage);
 
   // Return values of cdm::ContentDecryptionModule8::Decrypt
-  async Decrypted(uint32_t aStatus, uint8_t[] aData);
+  async Decrypted(uint32_t aId, uint32_t aStatus, uint8_t[] aData);
 
   // Return values of cdm::ContentDecryptionModule8::DecryptAndDecodeFrame
   async Decoded(CDMVideoFrame aFrame);
   async DecodeFailed(uint32_t aStatus);
 
   async Shutdown();
 };