/* -*- 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, nsISerialEventTarget* aMainThread) : CDMProxy(aKeys, aKeySystem, aDistinctiveIdentifierRequired, aPersistentStateRequired, aMainThread), 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, NS_LITERAL_CSTRING( "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, NS_LITERAL_CSTRING("Currently Fennec does not support LoadSession")); } 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, NS_LITERAL_CSTRING("Currently Fennec does not support RemoveSession")); } 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; } 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 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); } const nsString& MediaDrmCDMProxy::KeySystem() const { return mKeySystem; } DataMutex& MediaDrmCDMProxy::Capabilites() { return mCapabilites; } 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. NS_NAMED_LITERAL_CSTRING( err, "Currently Fennec does not support GetStatusForPolicy"); 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. NS_NAMED_LITERAL_CSTRING(err, "Null CDM in OnCDMCreated()"); 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 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, NS_LITERAL_CSTRING("Null CDM in md_CreateSession")); 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, NS_LITERAL_CSTRING("Null CDM in md_SetServerCertificate")); return; } if (mCDM->SetServerCertificate(aCert)) { ResolvePromiseWithResult(aPromiseId, true); } else { RejectPromiseWithStateError( aPromiseId, NS_LITERAL_CSTRING( "MediaDrmCDMProxy unable to set server certificate")); } } void MediaDrmCDMProxy::md_UpdateSession(UniquePtr&& aData) { MOZ_ASSERT(IsOnOwnerThread()); if (!mCDM) { RejectPromiseWithStateError( aData->mPromiseId, NS_LITERAL_CSTRING("Null CDM in md_UpdateSession")); return; } mCDM->UpdateSession(aData->mPromiseId, aData->mSessionId, aData->mResponse); } void MediaDrmCDMProxy::md_CloseSession(UniquePtr&& aData) { MOZ_ASSERT(IsOnOwnerThread()); if (!mCDM) { RejectPromiseWithStateError( aData->mPromiseId, NS_LITERAL_CSTRING("Null CDM in md_CloseSession")); 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