/* -*- 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(mKeySystem); nsCOMPtr task( NewRunnableMethod("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& aInitData) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mOwnerThread); UniquePtr data(new CreateSessionData()); data->mSessionType = aSessionType; data->mCreateSessionToken = aCreateSessionToken; data->mPromiseId = aPromiseId; data->mInitDataType = NS_ConvertUTF16toUTF8(aInitDataType); data->mInitData = std::move(aInitData); nsCOMPtr task(NewRunnableMethod&&>( "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& aCert) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mOwnerThread); mOwnerThread->Dispatch(NewRunnableMethod>( "MediaDrmCDMProxy::md_SetServerCertificate", this, &MediaDrmCDMProxy::md_SetServerCertificate, aPromiseId, std::move(aCert)), NS_DISPATCH_NORMAL); } void MediaDrmCDMProxy::UpdateSession(const nsAString& aSessionId, PromiseId aPromiseId, nsTArray& aResponse) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mOwnerThread); NS_ENSURE_TRUE_VOID(!mKeys.IsNull()); UniquePtr data(new UpdateSessionData()); data->mPromiseId = aPromiseId; data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId); data->mResponse = std::move(aResponse); nsCOMPtr task(NewRunnableMethod&&>( "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 data(new SessionOpData()); data->mPromiseId = aPromiseId; data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId); nsCOMPtr task(NewRunnableMethod&&>( "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 task(NewRunnableMethod( "MediaDrmCDMProxy::md_Shutdown", this, &MediaDrmCDMProxy::md_Shutdown)); mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL); mOwnerThread->Shutdown(); mOwnerThread = nullptr; mKeys.Clear(); } void MediaDrmCDMProxy::Terminated() { // TODO: Implement Terminated. // Should find a way to handle the case when remote side MediaDrm crashed. } void MediaDrmCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken, const nsAString& aSessionId) { MOZ_ASSERT(NS_IsMainThread()); if (mKeys.IsNull()) { return; } RefPtr 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& aMessage) { MOZ_ASSERT(NS_IsMainThread()); if (mKeys.IsNull()) { return; } RefPtr 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 session(mKeys->GetSession(aSessionId)); if (session) { session->SetExpiration(static_cast(aExpiryTime)); } } void MediaDrmCDMProxy::OnSessionClosed(const nsAString& aSessionId) { MOZ_ASSERT(NS_IsMainThread()); if (mKeys.IsNull()) { return; } RefPtr 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 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 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& 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 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 task; task = NewRunnableMethod("MediaDrmCDMProxy::ResolvePromise", this, &MediaDrmCDMProxy::ResolvePromise, aId); mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL); } } template 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 task; task = NewRunnableMethod( "MediaDrmCDMProxy::ResolvePromiseWithResult", this, &MediaDrmCDMProxy::ResolvePromiseWithResult, aId, aResult); mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL); } void MediaDrmCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) { MOZ_ASSERT(NS_IsMainThread()); if (mKeys.IsNull()) { return; } RefPtr 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); UniquePtr callback( new MediaDrmCDMCallbackProxy(this)); mCDM->Init(std::move(callback)); nsCOMPtr task( NewRunnableMethod("MediaDrmCDMProxy::OnCDMCreated", this, &MediaDrmCDMProxy::OnCDMCreated, aPromiseId)); mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL); } void MediaDrmCDMProxy::md_CreateSession(UniquePtr&& 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& 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&& 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&& 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