diff options
Diffstat (limited to 'dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp')
-rw-r--r-- | dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp | 457 |
1 files changed, 457 insertions, 0 deletions
diff --git a/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp new file mode 100644 index 0000000000..521db07d16 --- /dev/null +++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp @@ -0,0 +1,457 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "mozilla/dom/MediaKeySession.h" +#include "mozilla/MediaDrmCDMProxy.h" +#include "MediaDrmCDMCallbackProxy.h" + +namespace mozilla { + +MediaDrmSessionType ToMediaDrmSessionType( + dom::MediaKeySessionType aSessionType) { + switch (aSessionType) { + case dom::MediaKeySessionType::Temporary: + return kKeyStreaming; + case dom::MediaKeySessionType::Persistent_license: + return kKeyOffline; + default: + return kKeyStreaming; + }; +} + +MediaDrmCDMProxy::MediaDrmCDMProxy(dom::MediaKeys* aKeys, + const nsAString& aKeySystem, + bool aDistinctiveIdentifierRequired, + bool aPersistentStateRequired) + : CDMProxy(aKeys, aKeySystem, aDistinctiveIdentifierRequired, + aPersistentStateRequired), + mCDM(nullptr), + mShutdownCalled(false) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_COUNT_CTOR(MediaDrmCDMProxy); +} + +MediaDrmCDMProxy::~MediaDrmCDMProxy() { MOZ_COUNT_DTOR(MediaDrmCDMProxy); } + +void MediaDrmCDMProxy::Init(PromiseId aPromiseId, const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, + const nsAString& aName) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_TRUE_VOID(!mKeys.IsNull()); + + EME_LOG("MediaDrmCDMProxy::Init (%s, %s) %s", + NS_ConvertUTF16toUTF8(aOrigin).get(), + NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(), + NS_ConvertUTF16toUTF8(aName).get()); + + // Create a thread to work with cdm. + if (!mOwnerThread) { + nsresult rv = + NS_NewNamedThread("MDCDMThread", getter_AddRefs(mOwnerThread)); + if (NS_FAILED(rv)) { + RejectPromiseWithStateError( + aPromiseId, nsLiteralCString( + "Couldn't create CDM thread MediaDrmCDMProxy::Init")); + return; + } + } + + mCDM = mozilla::MakeUnique<MediaDrmProxySupport>(mKeySystem); + nsCOMPtr<nsIRunnable> task( + NewRunnableMethod<uint32_t>("MediaDrmCDMProxy::md_Init", this, + &MediaDrmCDMProxy::md_Init, aPromiseId)); + mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL); +} + +void MediaDrmCDMProxy::CreateSession(uint32_t aCreateSessionToken, + MediaKeySessionType aSessionType, + PromiseId aPromiseId, + const nsAString& aInitDataType, + nsTArray<uint8_t>& aInitData) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mOwnerThread); + + UniquePtr<CreateSessionData> data(new CreateSessionData()); + data->mSessionType = aSessionType; + data->mCreateSessionToken = aCreateSessionToken; + data->mPromiseId = aPromiseId; + data->mInitDataType = NS_ConvertUTF16toUTF8(aInitDataType); + data->mInitData = std::move(aInitData); + + nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<CreateSessionData>&&>( + "MediaDrmCDMProxy::md_CreateSession", this, + &MediaDrmCDMProxy::md_CreateSession, std::move(data))); + mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL); +} + +void MediaDrmCDMProxy::LoadSession(PromiseId aPromiseId, + dom::MediaKeySessionType aSessionType, + const nsAString& aSessionId) { + // TODO: Implement LoadSession. + RejectPromiseWithStateError( + aPromiseId, "Currently Fennec does not support LoadSession"_ns); +} + +void MediaDrmCDMProxy::SetServerCertificate(PromiseId aPromiseId, + nsTArray<uint8_t>& aCert) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mOwnerThread); + + mOwnerThread->Dispatch(NewRunnableMethod<PromiseId, const nsTArray<uint8_t>>( + "MediaDrmCDMProxy::md_SetServerCertificate", this, + &MediaDrmCDMProxy::md_SetServerCertificate, + aPromiseId, std::move(aCert)), + NS_DISPATCH_NORMAL); +} + +void MediaDrmCDMProxy::UpdateSession(const nsAString& aSessionId, + PromiseId aPromiseId, + nsTArray<uint8_t>& aResponse) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mOwnerThread); + NS_ENSURE_TRUE_VOID(!mKeys.IsNull()); + + UniquePtr<UpdateSessionData> data(new UpdateSessionData()); + data->mPromiseId = aPromiseId; + data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId); + data->mResponse = std::move(aResponse); + + nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<UpdateSessionData>&&>( + "MediaDrmCDMProxy::md_UpdateSession", this, + &MediaDrmCDMProxy::md_UpdateSession, std::move(data))); + mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL); +} + +void MediaDrmCDMProxy::CloseSession(const nsAString& aSessionId, + PromiseId aPromiseId) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mOwnerThread); + NS_ENSURE_TRUE_VOID(!mKeys.IsNull()); + + UniquePtr<SessionOpData> data(new SessionOpData()); + data->mPromiseId = aPromiseId; + data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId); + + nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<SessionOpData>&&>( + "MediaDrmCDMProxy::md_CloseSession", this, + &MediaDrmCDMProxy::md_CloseSession, std::move(data))); + mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL); +} + +void MediaDrmCDMProxy::RemoveSession(const nsAString& aSessionId, + PromiseId aPromiseId) { + // TODO: Implement RemoveSession. + RejectPromiseWithStateError( + aPromiseId, "Currently Fennec does not support RemoveSession"_ns); +} + +void MediaDrmCDMProxy::QueryOutputProtectionStatus() { + // TODO(bryce): determine if this is needed for Android and implement as + // needed. See also `NotifyOutputProtectionStatus`. +} + +void MediaDrmCDMProxy::NotifyOutputProtectionStatus( + OutputProtectionCheckStatus aCheckStatus, + OutputProtectionCaptureStatus aCaptureStatus) { + // TODO(bryce): determine if this is needed for Android and implement as + // needed. See also `QueryOutputProtectionStatus`. +} + +void MediaDrmCDMProxy::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mOwnerThread); + nsCOMPtr<nsIRunnable> task(NewRunnableMethod( + "MediaDrmCDMProxy::md_Shutdown", this, &MediaDrmCDMProxy::md_Shutdown)); + + mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL); + mOwnerThread->Shutdown(); + mOwnerThread = nullptr; +} + +void MediaDrmCDMProxy::Terminated() { + // TODO: Implement Terminated. + // Should find a way to handle the case when remote side MediaDrm crashed. +} + +const nsCString& MediaDrmCDMProxy::GetNodeId() const { return mNodeId; } + +void MediaDrmCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken, + const nsAString& aSessionId) { + MOZ_ASSERT(NS_IsMainThread()); + if (mKeys.IsNull()) { + return; + } + + RefPtr<dom::MediaKeySession> session( + mKeys->GetPendingSession(aCreateSessionToken)); + if (session) { + session->SetSessionId(aSessionId); + } +} + +void MediaDrmCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId, + bool aSuccess) { + MOZ_ASSERT(NS_IsMainThread()); + if (mKeys.IsNull()) { + return; + } + mKeys->OnSessionLoaded(aPromiseId, aSuccess); +} + +void MediaDrmCDMProxy::OnSessionMessage(const nsAString& aSessionId, + dom::MediaKeyMessageType aMessageType, + const 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 MediaDrmCDMProxy::OnExpirationChange(const nsAString& aSessionId, + UnixTime aExpiryTime) { + MOZ_ASSERT(NS_IsMainThread()); + if (mKeys.IsNull()) { + return; + } + RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId)); + if (session) { + session->SetExpiration(static_cast<double>(aExpiryTime)); + } +} + +void MediaDrmCDMProxy::OnSessionClosed(const nsAString& aSessionId) { + MOZ_ASSERT(NS_IsMainThread()); + if (mKeys.IsNull()) { + return; + } + RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId)); + if (session) { + session->OnClosed(); + } +} + +void MediaDrmCDMProxy::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); + } +} + +void MediaDrmCDMProxy::OnRejectPromise(uint32_t aPromiseId, + ErrorResult&& aException, + const nsCString& aMsg) { + MOZ_ASSERT(NS_IsMainThread()); + RejectPromise(aPromiseId, std::move(aException), aMsg); +} + +RefPtr<DecryptPromise> MediaDrmCDMProxy::Decrypt(MediaRawData* aSample) { + MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypting individually"); + return nullptr; +} + +void MediaDrmCDMProxy::OnDecrypted(uint32_t aId, DecryptStatus aResult, + const nsTArray<uint8_t>& aDecryptedData) { + MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypted event"); +} + +void MediaDrmCDMProxy::RejectPromise(PromiseId aId, ErrorResult&& aException, + const nsCString& aReason) { + if (NS_IsMainThread()) { + if (!mKeys.IsNull()) { + mKeys->RejectPromise(aId, std::move(aException), aReason); + } + } else { + nsCOMPtr<nsIRunnable> task( + new RejectPromiseTask(this, aId, std::move(aException), aReason)); + mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL); + } +} + +void MediaDrmCDMProxy::RejectPromiseWithStateError(PromiseId aId, + const nsCString& aReason) { + ErrorResult rv; + rv.ThrowInvalidStateError(aReason); + RejectPromise(aId, std::move(rv), aReason); +} + +void MediaDrmCDMProxy::ResolvePromise(PromiseId aId) { + if (NS_IsMainThread()) { + if (!mKeys.IsNull()) { + mKeys->ResolvePromise(aId); + } else { + NS_WARNING("MediaDrmCDMProxy unable to resolve promise!"); + } + } else { + nsCOMPtr<nsIRunnable> task; + task = + NewRunnableMethod<PromiseId>("MediaDrmCDMProxy::ResolvePromise", this, + &MediaDrmCDMProxy::ResolvePromise, aId); + mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL); + } +} + +template <typename T> +void MediaDrmCDMProxy::ResolvePromiseWithResult(PromiseId aId, + const T& aResult) { + if (NS_IsMainThread()) { + if (!mKeys.IsNull()) { + mKeys->ResolvePromiseWithResult(aId, aResult); + } else { + NS_WARNING("MediaDrmCDMProxy unable to resolve promise!"); + } + return; + } + + nsCOMPtr<nsIRunnable> task; + task = NewRunnableMethod<PromiseId, T>( + "MediaDrmCDMProxy::ResolvePromiseWithResult", this, + &MediaDrmCDMProxy::ResolvePromiseWithResult<T>, aId, aResult); + mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL); +} + +const nsString& MediaDrmCDMProxy::KeySystem() const { return mKeySystem; } + +DataMutex<CDMCaps>& MediaDrmCDMProxy::Capabilites() { return mCapabilites; } + +void MediaDrmCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) { + MOZ_ASSERT(NS_IsMainThread()); + if (mKeys.IsNull()) { + return; + } + RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId)); + if (session) { + session->DispatchKeyStatusesChange(); + } +} + +void MediaDrmCDMProxy::GetStatusForPolicy(PromiseId aPromiseId, + const nsAString& aMinHdcpVersion) { + // TODO: Implement GetStatusForPolicy. + constexpr auto err = + "Currently Fennec does not support GetStatusForPolicy"_ns; + + ErrorResult rv; + rv.ThrowNotSupportedError(err); + RejectPromise(aPromiseId, std::move(rv), err); +} + +#ifdef DEBUG +bool MediaDrmCDMProxy::IsOnOwnerThread() { + return NS_GetCurrentThread() == mOwnerThread; +} +#endif + +const nsString& MediaDrmCDMProxy::GetMediaDrmStubId() const { + MOZ_ASSERT(mCDM); + return mCDM->GetMediaDrmStubId(); +} + +void MediaDrmCDMProxy::OnCDMCreated(uint32_t aPromiseId) { + MOZ_ASSERT(NS_IsMainThread()); + if (mKeys.IsNull()) { + return; + } + + if (mCDM) { + mKeys->OnCDMCreated(aPromiseId, 0); + return; + } + + // No CDM? Just reject the promise. + constexpr auto err = "Null CDM in OnCDMCreated()"_ns; + ErrorResult rv; + rv.ThrowInvalidStateError(err); + mKeys->RejectPromise(aPromiseId, std::move(rv), err); +} + +void MediaDrmCDMProxy::md_Init(uint32_t aPromiseId) { + MOZ_ASSERT(IsOnOwnerThread()); + MOZ_ASSERT(mCDM); + + mCallback.reset(new MediaDrmCDMCallbackProxy(this)); + mCDM->Init(mCallback.get()); + nsCOMPtr<nsIRunnable> task( + NewRunnableMethod<uint32_t>("MediaDrmCDMProxy::OnCDMCreated", this, + &MediaDrmCDMProxy::OnCDMCreated, aPromiseId)); + mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL); +} + +void MediaDrmCDMProxy::md_CreateSession(UniquePtr<CreateSessionData>&& aData) { + MOZ_ASSERT(IsOnOwnerThread()); + + if (!mCDM) { + RejectPromiseWithStateError(aData->mPromiseId, + "Null CDM in md_CreateSession"_ns); + return; + } + + mCDM->CreateSession(aData->mCreateSessionToken, aData->mPromiseId, + aData->mInitDataType, aData->mInitData, + ToMediaDrmSessionType(aData->mSessionType)); +} + +void MediaDrmCDMProxy::md_SetServerCertificate(PromiseId aPromiseId, + const nsTArray<uint8_t>& aCert) { + MOZ_ASSERT(IsOnOwnerThread()); + + if (!mCDM) { + RejectPromiseWithStateError(aPromiseId, + "Null CDM in md_SetServerCertificate"_ns); + return; + } + + if (mCDM->SetServerCertificate(aCert)) { + ResolvePromiseWithResult(aPromiseId, true); + } else { + RejectPromiseWithStateError( + aPromiseId, "MediaDrmCDMProxy unable to set server certificate"_ns); + } +} + +void MediaDrmCDMProxy::md_UpdateSession(UniquePtr<UpdateSessionData>&& aData) { + MOZ_ASSERT(IsOnOwnerThread()); + + if (!mCDM) { + RejectPromiseWithStateError(aData->mPromiseId, + "Null CDM in md_UpdateSession"_ns); + return; + } + mCDM->UpdateSession(aData->mPromiseId, aData->mSessionId, aData->mResponse); +} + +void MediaDrmCDMProxy::md_CloseSession(UniquePtr<SessionOpData>&& aData) { + MOZ_ASSERT(IsOnOwnerThread()); + + if (!mCDM) { + RejectPromiseWithStateError(aData->mPromiseId, + "Null CDM in md_CloseSession"_ns); + return; + } + mCDM->CloseSession(aData->mPromiseId, aData->mSessionId); +} + +void MediaDrmCDMProxy::md_Shutdown() { + MOZ_ASSERT(IsOnOwnerThread()); + MOZ_ASSERT(mCDM); + if (mShutdownCalled) { + return; + } + mShutdownCalled = true; + mCDM->Shutdown(); + mCDM = nullptr; +} + +} // namespace mozilla |