Bug 1315850 - Handle sending and receiving key session messages. r=gerald draft
authorChris Pearce <cpearce@mozilla.com>
Wed, 08 Mar 2017 16:41:06 +1300
changeset 504163 0931d65116bf0c499933efc58cae97b275b0ba85
parent 504162 914db1f04e0770776ae25c7b8bdc59e729fe78d0
child 504164 cf303b0d792dfd86f1056529eb4c081d84fc2e82
push id50748
push userbmo:cpearce@mozilla.com
push dateFri, 24 Mar 2017 01:10:17 +0000
reviewersgerald
bugs1315850
milestone55.0a1
Bug 1315850 - Handle sending and receiving key session messages. r=gerald MozReview-Commit-ID: 6hLPFLFRD5I
dom/media/gmp/ChromiumCDMParent.cpp
dom/media/gmp/ChromiumCDMParent.h
dom/media/gmp/ChromiumCDMProxy.cpp
--- a/dom/media/gmp/ChromiumCDMParent.cpp
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -3,16 +3,18 @@
  * 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 "ChromiumCDMParent.h"
 #include "mozilla/gmp/GMPTypes.h"
 #include "GMPContentChild.h"
 #include "mozilla/Unused.h"
 #include "ChromiumCDMProxy.h"
+#include "mozilla/dom/MediaKeyMessageEventBinding.h"
+#include "content_decryption_module.h"
 
 namespace mozilla {
 namespace gmp {
 
 ChromiumCDMParent::ChromiumCDMParent(GMPContentParent* aContentParent,
                                      uint32_t aPluginId)
   : mPluginId(aPluginId)
   , mContentParent(aContentParent)
@@ -23,79 +25,272 @@ bool
 ChromiumCDMParent::Init(ChromiumCDMProxy* aProxy,
                         bool aAllowDistinctiveIdentifier,
                         bool aAllowPersistentState)
 {
   mProxy = aProxy;
   return SendInit(aAllowDistinctiveIdentifier, aAllowPersistentState);
 }
 
+void
+ChromiumCDMParent::SetServerCertificate(uint32_t aPromiseId,
+                                        const nsTArray<uint8_t>& aCert)
+{
+  if (!SendSetServerCertificate(aPromiseId, aCert)) {
+    RejectPromise(
+      aPromiseId,
+      NS_ERROR_DOM_INVALID_STATE_ERR,
+      NS_LITERAL_CSTRING("Failed to send setServerCertificate to CDM process"));
+  }
+}
+
+void
+ChromiumCDMParent::UpdateSession(const nsCString& aSessionId,
+                                 uint32_t aPromiseId,
+                                 const nsTArray<uint8_t>& aResponse)
+{
+  if (!SendUpdateSession(aPromiseId, aSessionId, aResponse)) {
+    RejectPromise(
+      aPromiseId,
+      NS_ERROR_DOM_INVALID_STATE_ERR,
+      NS_LITERAL_CSTRING("Failed to send updateSession to CDM process"));
+  }
+}
+
+void
+ChromiumCDMParent::CloseSession(const nsCString& aSessionId,
+                                uint32_t aPromiseId)
+{
+  if (!SendCloseSession(aPromiseId, aSessionId)) {
+    RejectPromise(
+      aPromiseId,
+      NS_ERROR_DOM_INVALID_STATE_ERR,
+      NS_LITERAL_CSTRING("Failed to send closeSession to CDM process"));
+  }
+}
+
+void
+ChromiumCDMParent::RemoveSession(const nsCString& aSessionId,
+                                 uint32_t aPromiseId)
+{
+  if (!SendRemoveSession(aPromiseId, aSessionId)) {
+    RejectPromise(
+      aPromiseId,
+      NS_ERROR_DOM_INVALID_STATE_ERR,
+      NS_LITERAL_CSTRING("Failed to send removeSession to CDM process"));
+  }
+}
+
 ipc::IPCResult
 ChromiumCDMParent::Recv__delete__()
 {
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnResolveNewSessionPromise(const uint32_t& aPromiseId,
                                                   const nsCString& aSessionId)
 {
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnResolvePromise(const uint32_t& aPromiseId)
 {
+  if (!mProxy) {
+    return IPC_OK();
+  }
+  NS_DispatchToMainThread(NewRunnableMethod<uint32_t>(
+    mProxy, &ChromiumCDMProxy::ResolvePromise, aPromiseId));
   return IPC_OK();
 }
 
+static nsresult
+ToNsresult(uint32_t aError)
+{
+  switch (static_cast<cdm::Error>(aError)) {
+    case cdm::kNotSupportedError:
+      return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+    case cdm::kInvalidStateError:
+      return NS_ERROR_DOM_INVALID_STATE_ERR;
+    case cdm::kInvalidAccessError:
+      // Note: Chrome converts kInvalidAccessError to TypeError, since the
+      // Chromium CDM API doesn't have a type error enum value. The EME spec
+      // requires TypeError in some places, so we do the same conversion.
+      // See bug 1313202.
+      return NS_ERROR_DOM_TYPE_ERR;
+    case cdm::kQuotaExceededError:
+      return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
+    case cdm::kUnknownError:
+      return NS_ERROR_DOM_UNKNOWN_ERR; // Note: Unique placeholder.
+    case cdm::kClientError:
+      return NS_ERROR_DOM_ABORT_ERR; // Note: Unique placeholder.
+    case cdm::kOutputError:
+      return NS_ERROR_DOM_SECURITY_ERR; // Note: Unique placeholder.
+  };
+  MOZ_ASSERT_UNREACHABLE("Invalid cdm::Error enum value.");
+  return NS_ERROR_DOM_TIMEOUT_ERR; // Note: Unique placeholder.
+}
+
+void
+ChromiumCDMParent::RejectPromise(uint32_t aPromiseId,
+                                 nsresult aError,
+                                 const nsCString& aErrorMessage)
+{
+  if (!mProxy) {
+    return;
+  }
+  NS_DispatchToMainThread(NewRunnableMethod<uint32_t, nsresult, nsCString>(
+    mProxy,
+    &ChromiumCDMProxy::RejectPromise,
+    aPromiseId,
+    aError,
+    aErrorMessage));
+}
+
 ipc::IPCResult
 ChromiumCDMParent::RecvOnRejectPromise(const uint32_t& aPromiseId,
                                        const uint32_t& aError,
                                        const uint32_t& aSystemCode,
                                        const nsCString& aErrorMessage)
 {
+  RejectPromise(aPromiseId, ToNsresult(aError), aErrorMessage);
   return IPC_OK();
 }
 
+static dom::MediaKeyMessageType
+ToDOMMessageType(uint32_t aMessageType)
+{
+  switch (static_cast<cdm::MessageType>(aMessageType)) {
+    case cdm::kLicenseRequest:
+      return dom::MediaKeyMessageType::License_request;
+    case cdm::kLicenseRenewal:
+      return dom::MediaKeyMessageType::License_renewal;
+    case cdm::kLicenseRelease:
+      return dom::MediaKeyMessageType::License_release;
+  }
+  MOZ_ASSERT_UNREACHABLE("Invalid cdm::MessageType enum value.");
+  return dom::MediaKeyMessageType::License_request;
+}
+
 ipc::IPCResult
 ChromiumCDMParent::RecvOnSessionMessage(const nsCString& aSessionId,
                                         const uint32_t& aMessageType,
                                         nsTArray<uint8_t>&& aMessage)
 {
+  if (!mProxy) {
+    return IPC_OK();
+  }
+  RefPtr<CDMProxy> proxy = mProxy;
+  nsString sid = NS_ConvertUTF8toUTF16(aSessionId);
+  dom::MediaKeyMessageType messageType = ToDOMMessageType(aMessageType);
+  nsTArray<uint8_t> msg(Move(aMessage));
+  NS_DispatchToMainThread(
+    NS_NewRunnableFunction([proxy, sid, messageType, msg]() mutable {
+      proxy->OnSessionMessage(sid, messageType, msg);
+    }));
   return IPC_OK();
 }
 
+static dom::MediaKeyStatus
+ToDOMMediaKeyStatus(uint32_t aStatus)
+{
+  switch (static_cast<cdm::KeyStatus>(aStatus)) {
+    case cdm::kUsable:
+      return dom::MediaKeyStatus::Usable;
+    case cdm::kInternalError:
+      return dom::MediaKeyStatus::Internal_error;
+    case cdm::kExpired:
+      return dom::MediaKeyStatus::Expired;
+    case cdm::kOutputRestricted:
+      return dom::MediaKeyStatus::Output_restricted;
+    case cdm::kOutputDownscaled:
+      return dom::MediaKeyStatus::Output_downscaled;
+    case cdm::kStatusPending:
+      return dom::MediaKeyStatus::Status_pending;
+    case cdm::kReleased:
+      return dom::MediaKeyStatus::Released;
+  }
+  MOZ_ASSERT_UNREACHABLE("Invalid cdm::KeyStatus enum value.");
+  return dom::MediaKeyStatus::Internal_error;
+}
+
 ipc::IPCResult
 ChromiumCDMParent::RecvOnSessionKeysChange(
   const nsCString& aSessionId,
   nsTArray<CDMKeyInformation>&& aKeysInfo)
 {
+  if (!mProxy) {
+    return IPC_OK();
+  }
+  bool keyStatusesChange = false;
+  {
+    CDMCaps::AutoLock caps(mProxy->Capabilites());
+    for (size_t i = 0; i < aKeysInfo.Length(); i++) {
+      keyStatusesChange |=
+        caps.SetKeyStatus(aKeysInfo[i].mKeyId(),
+                          NS_ConvertUTF8toUTF16(aSessionId),
+                          dom::Optional<dom::MediaKeyStatus>(
+                            ToDOMMediaKeyStatus(aKeysInfo[i].mStatus())));
+    }
+  }
+  if (keyStatusesChange) {
+    NS_DispatchToMainThread(
+      NewRunnableMethod<nsString>(mProxy,
+                                  &ChromiumCDMProxy::OnKeyStatusesChange,
+                                  NS_ConvertUTF8toUTF16(aSessionId)));
+  }
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnExpirationChange(const nsCString& aSessionId,
                                           const double& aSecondsSinceEpoch)
 {
+  if (!mProxy) {
+    return IPC_OK();
+  }
+  NS_DispatchToMainThread(NewRunnableMethod<nsString, UnixTime>(
+    mProxy,
+    &ChromiumCDMProxy::OnExpirationChange,
+    NS_ConvertUTF8toUTF16(aSessionId),
+    GMPTimestamp(aSecondsSinceEpoch * 1000)));
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnSessionClosed(const nsCString& aSessionId)
 {
+  if (!mProxy) {
+    return IPC_OK();
+  }
+  NS_DispatchToMainThread(
+    NewRunnableMethod<nsString>(mProxy,
+                                &ChromiumCDMProxy::OnSessionClosed,
+                                NS_ConvertUTF8toUTF16(aSessionId)));
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnLegacySessionError(const nsCString& aSessionId,
                                             const uint32_t& aError,
                                             const uint32_t& aSystemCode,
                                             const nsCString& aMessage)
 {
+  if (!mProxy) {
+    return IPC_OK();
+  }
+  NS_DispatchToMainThread(
+    NewRunnableMethod<nsString, nsresult, uint32_t, nsString>(
+      mProxy,
+      &ChromiumCDMProxy::OnSessionError,
+      NS_ConvertUTF8toUTF16(aSessionId),
+      ToNsresult(aError),
+      aSystemCode,
+      NS_ConvertUTF8toUTF16(aMessage)));
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvDecrypted(const uint32_t& aStatus,
                                  nsTArray<uint8_t>&& aData)
 {
   return IPC_OK();
--- a/dom/media/gmp/ChromiumCDMParent.h
+++ b/dom/media/gmp/ChromiumCDMParent.h
@@ -30,17 +30,27 @@ public:
   ChromiumCDMParent(GMPContentParent* aContentParent, uint32_t aPluginId);
 
   uint32_t PluginId() const { return mPluginId; }
 
   bool Init(ChromiumCDMProxy* aProxy,
             bool aAllowDistinctiveIdentifier,
             bool aAllowPersistentState);
 
-  // TODO: Add functions for clients to send data to CDM, and
+  void SetServerCertificate(uint32_t aPromiseId,
+                            const nsTArray<uint8_t>& aCert);
+
+  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);
+
   // a Close() function.
 
 protected:
   ~ChromiumCDMParent() {}
 
   ipc::IPCResult Recv__delete__() override;
   ipc::IPCResult RecvOnResolveNewSessionPromise(
     const uint32_t& aPromiseId,
@@ -66,16 +76,20 @@ protected:
                                           const nsCString& aMessage) override;
   ipc::IPCResult RecvDecrypted(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,
+                     const nsCString& aErrorMessage);
+
   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;
 };
 
--- a/dom/media/gmp/ChromiumCDMProxy.cpp
+++ b/dom/media/gmp/ChromiumCDMProxy.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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 "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,
@@ -147,41 +148,71 @@ ChromiumCDMProxy::CreateSession(uint32_t
                                 const nsAString& aInitDataType,
                                 nsTArray<uint8_t>& aInitData)
 {
 }
 
 void
 ChromiumCDMProxy::LoadSession(PromiseId aPromiseId, const nsAString& aSessionId)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RejectPromise(aPromiseId,
+                NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+                NS_LITERAL_CSTRING("loadSession is not supported"));
 }
 
 void
 ChromiumCDMProxy::SetServerCertificate(PromiseId aPromiseId,
                                        nsTArray<uint8_t>& aCert)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mGMPThread->Dispatch(NewRunnableMethod<uint32_t, nsTArray<uint8_t>>(
+    mCDM,
+    &gmp::ChromiumCDMParent::SetServerCertificate,
+    aPromiseId,
+    Move(aCert)));
 }
 
 void
 ChromiumCDMProxy::UpdateSession(const nsAString& aSessionId,
                                 PromiseId aPromiseId,
                                 nsTArray<uint8_t>& aResponse)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mGMPThread->Dispatch(NewRunnableMethod<nsCString, uint32_t, nsTArray<uint8_t>>(
+      mCDM,
+      &gmp::ChromiumCDMParent::UpdateSession,
+      NS_ConvertUTF16toUTF8(aSessionId),
+      aPromiseId,
+      Move(aResponse)));
 }
 
 void
 ChromiumCDMProxy::CloseSession(const nsAString& aSessionId,
                                PromiseId aPromiseId)
 {
+  mGMPThread->Dispatch(NewRunnableMethod<nsCString, uint32_t>(
+    mCDM,
+    &gmp::ChromiumCDMParent::CloseSession,
+    NS_ConvertUTF16toUTF8(aSessionId),
+    aPromiseId));
 }
 
 void
 ChromiumCDMProxy::RemoveSession(const nsAString& aSessionId,
                                 PromiseId aPromiseId)
 {
+  mGMPThread->Dispatch(NewRunnableMethod<nsCString, uint32_t>(
+    mCDM,
+    &gmp::ChromiumCDMParent::RemoveSession,
+    NS_ConvertUTF16toUTF8(aSessionId),
+    aPromiseId));
 }
 
 void
 ChromiumCDMProxy::Shutdown()
 {
 }
 
 void
@@ -237,47 +268,100 @@ ChromiumCDMProxy::OnResolveLoadSessionPr
 {
 }
 
 void
 ChromiumCDMProxy::OnSessionMessage(const nsAString& aSessionId,
                                    dom::MediaKeyMessageType aMessageType,
                                    nsTArray<uint8_t>& aMessage)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mKeys.IsNull()) {
+    return;
+  }
+  RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+  if (session) {
+    session->DispatchKeyMessage(aMessageType, aMessage);
+  }
 }
 
 void
 ChromiumCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mKeys.IsNull()) {
+    return;
+  }
+  RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+  if (session) {
+    session->DispatchKeyStatusesChange();
+  }
 }
 
 void
 ChromiumCDMProxy::OnExpirationChange(const nsAString& aSessionId,
                                      GMPTimestamp aExpiryTime)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mKeys.IsNull()) {
+    return;
+  }
+  RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+  if (session) {
+    // Expiry of 0 is interpreted as "never expire". See bug 1345341.
+    double t = (aExpiryTime == 0) ? std::numeric_limits<double>::quiet_NaN()
+                                  : static_cast<double>(aExpiryTime);
+    session->SetExpiration(t);
+  }
 }
 
 void
 ChromiumCDMProxy::OnSessionClosed(const nsAString& aSessionId)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  bool keyStatusesChange = false;
+  {
+    CDMCaps::AutoLock caps(Capabilites());
+    keyStatusesChange = caps.RemoveKeysForSession(nsString(aSessionId));
+  }
+  if (keyStatusesChange) {
+    OnKeyStatusesChange(aSessionId);
+  }
+  if (mKeys.IsNull()) {
+    return;
+  }
+  RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+  if (session) {
+    session->OnClosed();
+  }
 }
 
 void
 ChromiumCDMProxy::OnDecrypted(uint32_t aId,
                               DecryptStatus aResult,
                               const nsTArray<uint8_t>& aDecryptedData)
 {
 }
 
 void
 ChromiumCDMProxy::OnSessionError(const nsAString& aSessionId,
                                  nsresult aException,
                                  uint32_t aSystemCode,
                                  const nsAString& aMsg)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mKeys.IsNull()) {
+    return;
+  }
+  RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+  if (session) {
+    session->DispatchKeyError(aSystemCode);
+  }
+  LogToConsole(aMsg);
 }
 
 void
 ChromiumCDMProxy::OnRejectPromise(uint32_t aPromiseId,
                                   nsresult aDOMException,
                                   const nsCString& aMsg)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -302,16 +386,21 @@ ChromiumCDMProxy::Decrypt(MediaRawData* 
   return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, nullptr),
                                          __func__);
 }
 
 void
 ChromiumCDMProxy::GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
                                         nsTArray<nsCString>& aSessionIds)
 {
+  CDMCaps::AutoLock caps(Capabilites());
+  caps.GetSessionIdsForKeyId(aKeyId, aSessionIds);
 }
 
 void
 ChromiumCDMProxy::Terminated()
 {
+  if (!mKeys.IsNull()) {
+    mKeys->Terminated();
+  }
 }
 
 } // namespace mozilla