/* -*- 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 "RemoteCDMProxy.h" #include "mozilla/EMEOriginID.h" #include "mozilla/RemoteDecodeUtils.h" #include "mozilla/dom/MediaKeySession.h" #ifdef MOZ_WIDGET_ANDROID # include "mozilla/MediaDrmProvisioningHelper.h" #endif namespace mozilla { #define LOGD(msg, ...) \ MOZ_LOG_FMT(gRemoteDecodeLog, LogLevel::Debug, msg, ##__VA_ARGS__) RemoteCDMProxy::RemoteCDMProxy( nsCOMPtr&& aThread, RefPtr&& aIPDLPromise, RemoteMediaIn aLocation, dom::MediaKeys* aKeys, const nsAString& aKeySystem, bool aDistinctiveIdentifierRequired, bool aPersistentStateRequired) : CDMProxy(aKeys, aKeySystem, aDistinctiveIdentifierRequired, aPersistentStateRequired), mChild(MakeRefPtr()), mThread(std::move(aThread)), mIPDLPromise(std::move(aIPDLPromise)), mLocation(aLocation) { mChild->Initialize(this); } RemoteCDMProxy::~RemoteCDMProxy() { Destroy(); } void RemoteCDMProxy::Destroy() { mThread->Dispatch(NS_NewRunnableFunction( __func__, [child = mChild]() { child->Destroy(); })); } void RemoteCDMProxy::OnProvision( const RemoteCDMProvisionRequestIPDL& aRequest, PRemoteCDMChild::ProvisionResolver&& aResolver) { LOGD("[{}] RemoteCDMProxy::OnProvision", fmt::ptr(this)); #ifdef MOZ_WIDGET_ANDROID auto helper = MakeRefPtr(aRequest, std::move(aResolver)); helper->Provision(); #else aResolver(MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR)); #endif } void RemoteCDMProxy::OnSessionKeyStatus(const RemoteCDMKeyStatusIPDL& aMsg) { LOGD("[{}] RemoteCDMProxy::OnSessionKeyStatus", fmt::ptr(this)); bool changed = false; { auto caps = mCapabilites.Lock(); for (const auto& keyInfo : aMsg.keyInfo()) { changed |= caps->SetKeyStatus(keyInfo.mKeyId, aMsg.sessionId(), keyInfo.mStatus); } } if (!changed) { return; } NS_DispatchToMainThread(NS_NewRunnableFunction( __func__, [self = RefPtr{this}, sessionId = aMsg.sessionId()]() { if (self->mKeys.IsNull()) { return; } if (RefPtr session = self->mKeys->GetSession(sessionId)) { session->DispatchKeyStatusesChange(); } })); } void RemoteCDMProxy::OnSessionKeyExpiration(RemoteCDMKeyExpirationIPDL&& aMsg) { LOGD("[{}] RemoteCDMProxy::OnSessionKeyExpiration", fmt::ptr(this)); NS_DispatchToMainThread(NS_NewRunnableFunction( __func__, [self = RefPtr{this}, msg = std::move(aMsg)]() { if (self->mKeys.IsNull()) { return; } if (RefPtr session = self->mKeys->GetSession(msg.sessionId())) { session->SetExpiration(msg.expiredTimeMilliSecondsSinceEpoch()); } })); } void RemoteCDMProxy::OnSessionKeyMessage(RemoteCDMKeyMessageIPDL&& aMsg) { LOGD("[{}] RemoteCDMProxy::OnSessionKeyMessage", fmt::ptr(this)); NS_DispatchToMainThread(NS_NewRunnableFunction( __func__, [self = RefPtr{this}, msg = std::move(aMsg)]() { if (self->mKeys.IsNull()) { return; } if (RefPtr session = self->mKeys->GetSession(msg.sessionId())) { session->DispatchKeyMessage(msg.type(), msg.message()); } })); } void RemoteCDMProxy::Init(PromiseId aPromiseId, const nsAString& aOrigin, const nsAString& aTopLevelOrigin, const nsAString& aName) { MOZ_ASSERT(NS_IsMainThread()); if (mKeys.IsNull()) { return; } LOGD("[{}] RemoteCDMProxy::Init -- promise {}", fmt::ptr(this), aPromiseId); if (!mIPDLPromise) { RejectPromise(aPromiseId, MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR, "PRemoteCDMChild already initialized"_ns)); return; } RefPtr ipdlPromise = mIPDLPromise; mIPDLPromise = nullptr; GetEMEOriginID(mKeys->GetPrincipal()) ->Then( GetMainThreadSerialEventTarget(), __func__, [self = RefPtr{this}, aPromiseId, ipdlPromise]( const media::PrincipalKeyPromise::ResolveOrRejectValue& aValue) { if (self->mKeys.IsNull()) { return; } nsCString originID; if (aValue.IsResolve()) { originID = aValue.ResolveValue(); } // On rejection, proceed without origin ID. ipdlPromise->Then( self->mThread, __func__, [self, aPromiseId, originID = std::move(originID)]( const GenericNonExclusivePromise::ResolveOrRejectValue& aValue) { LOGD("[{}] RemoteCDMChild::Init -- promise {} resolved {}", fmt::ptr(self.get()), aPromiseId, aValue.IsResolve()); if (aValue.IsReject()) { self->RejectPromise( aPromiseId, MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR, "PRemoteCDMChild ensure process fail"_ns)); return; } self->InitInternal(aPromiseId, originID); }); }); } void RemoteCDMProxy::InitInternal(PromiseId aPromiseId, const nsCString& aOriginID) { LOGD("[{}] RemoteCDMProxy::InitInternal -- promise {}", fmt::ptr(this), aPromiseId); RefPtr manager = RemoteMediaManagerChild::GetSingleton(mLocation); if (!manager) { RejectPromise(aPromiseId, MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR, "PRemoteCDMChild manager is not available"_ns)); return; } LOGD("[{}] RemoteCDMProxy::InitInternal -- send constructor", fmt::ptr(this)); if (!manager->SendPRemoteCDMConstructor(mChild, mKeySystem)) { RejectPromise(aPromiseId, MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR, "PRemoteCDMChild manager is unable to send"_ns)); return; } LOGD("[{}] RemoteCDMProxy::InitInternal -- send init", fmt::ptr(this)); mChild ->SendInit(RemoteCDMInitRequestIPDL(mDistinctiveIdentifierRequired, mPersistentStateRequired, aOriginID)) ->Then( GetMainThreadSerialEventTarget(), __func__, [self = RefPtr{this}, aPromiseId](const PRemoteCDMChild::InitPromise::ResolveOrRejectValue& aValue) { LOGD("[{}] RemoteCDMProxy::InitInternal -- promise {} resolved {}", fmt::ptr(self.get()), aPromiseId, aValue.IsResolve()); if (self->mKeys.IsNull()) { return; } if (aValue.IsReject()) { self->RejectPromise( aPromiseId, MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR, "PRemoteCDMProxy::SendInit IPC fail"_ns)); return; } self->mKeys->OnCDMCreated(aPromiseId, 0); }); } void RemoteCDMProxy::CreateSession(uint32_t aCreateSessionToken, dom::MediaKeySessionType aSessionType, PromiseId aPromiseId, const nsAString& aInitDataType, nsTArray& aInitData) { MOZ_ALWAYS_SUCCEEDS(mThread->Dispatch(NS_NewRunnableFunction( __func__, [self = RefPtr{this}, aCreateSessionToken, aSessionType, aPromiseId, initDataType = nsString(aInitDataType), initData = std::move(aInitData)]() mutable { LOGD("[{}] RemoteCDMProxy::CreateSession -- promise {}", fmt::ptr(self.get()), aPromiseId); self->mChild ->SendCreateSession(RemoteCDMCreateSessionRequestIPDL( aSessionType, std::move(initDataType), std::move(initData))) ->Then( GetMainThreadSerialEventTarget(), __func__, [self, aCreateSessionToken, aPromiseId](const PRemoteCDMChild::CreateSessionPromise:: ResolveOrRejectValue& aValue) { if (self->mKeys.IsNull()) { return; } if (aValue.IsReject()) { self->RejectPromise( aPromiseId, MediaResult( NS_ERROR_DOM_INVALID_STATE_ERR, "PRemoteCDMProxy::SendCreateSession IPC fail"_ns)); return; } const auto& response = aValue.ResolveValue(); if (response.type() == RemoteCDMSessionResponseIPDL::TMediaResult) { self->RejectPromise(aPromiseId, response.get_MediaResult()); return; } const auto& msg = response.get_RemoteCDMKeyMessageIPDL(); const auto& sessionId = msg.sessionId(); if (RefPtr session = self->mKeys->GetPendingSession(aCreateSessionToken)) { session->SetSessionId(sessionId); session->DispatchKeyMessage(msg.type(), msg.message()); } self->ResolvePromise(aPromiseId); }); }))); } void RemoteCDMProxy::LoadSession(PromiseId aPromiseId, dom::MediaKeySessionType aSessionType, const nsAString& aSessionId) { MOZ_ALWAYS_SUCCEEDS(mThread->Dispatch(NS_NewRunnableFunction( __func__, [self = RefPtr{this}, aPromiseId, aSessionType, sessionId = nsString(aSessionId)]() mutable { LOGD("[{}] RemoteCDMProxy::LoadSession -- promise {}", fmt::ptr(self.get()), aPromiseId); self->mChild ->SendLoadSession(RemoteCDMLoadSessionRequestIPDL( aSessionType, std::move(sessionId))) ->Then( GetMainThreadSerialEventTarget(), __func__, [self, aPromiseId](const PRemoteCDMChild::LoadSessionPromise:: ResolveOrRejectValue& aValue) { if (self->mKeys.IsNull()) { return; } self->mKeys->OnSessionLoaded( aPromiseId, aValue.IsResolve() && NS_SUCCEEDED(aValue.ResolveValue())); }); }))); } void RemoteCDMProxy::SetServerCertificate(PromiseId aPromiseId, nsTArray& aCert) { MOZ_ALWAYS_SUCCEEDS(mThread->Dispatch(NS_NewRunnableFunction( __func__, [self = RefPtr{this}, aPromiseId, cert = std::move(aCert)]() mutable { LOGD("[{}] RemoteCDMProxy::SetServerCertificate -- promise {}", fmt::ptr(self.get()), aPromiseId); self->mChild->SendSetServerCertificate(std::move(cert)) ->Then( GetMainThreadSerialEventTarget(), __func__, [self, aPromiseId]( const PRemoteCDMChild::SetServerCertificatePromise:: ResolveOrRejectValue& aValue) { if (self->mKeys.IsNull()) { return; } if (aValue.IsReject()) { self->RejectPromise( aPromiseId, MediaResult( NS_ERROR_DOM_INVALID_STATE_ERR, "PRemoteCDMProxy::SendSetServerCertificate IPC fail"_ns)); return; } self->ResolveOrRejectPromise(aPromiseId, aValue.ResolveValue()); }); }))); } void RemoteCDMProxy::UpdateSession(const nsAString& aSessionId, PromiseId aPromiseId, nsTArray& aResponse) { MOZ_ALWAYS_SUCCEEDS(mThread->Dispatch(NS_NewRunnableFunction( __func__, [self = RefPtr{this}, sessionId = nsString(aSessionId), aPromiseId, response = std::move(aResponse)]() mutable { LOGD("[{}] RemoteCDMProxy::UpdateSession -- promise {}", fmt::ptr(self.get()), aPromiseId); self->mChild ->SendUpdateSession(RemoteCDMUpdateSessionRequestIPDL( std::move(sessionId), std::move(response))) ->Then( GetMainThreadSerialEventTarget(), __func__, [self, aPromiseId](const PRemoteCDMChild::UpdateSessionPromise:: ResolveOrRejectValue& aValue) { if (self->mKeys.IsNull()) { return; } if (aValue.IsReject()) { self->RejectPromise( aPromiseId, MediaResult( NS_ERROR_DOM_INVALID_STATE_ERR, "PRemoteCDMProxy::SendUpdateSession IPC fail"_ns)); return; } self->ResolveOrRejectPromise(aPromiseId, aValue.ResolveValue()); }); }))); } void RemoteCDMProxy::CloseSession(const nsAString& aSessionId, PromiseId aPromiseId) { MOZ_ALWAYS_SUCCEEDS(mThread->Dispatch(NS_NewRunnableFunction( __func__, [self = RefPtr{this}, sessionId = nsString(aSessionId), aPromiseId]() mutable { LOGD("[{}] RemoteCDMProxy::CloseSession -- promise {}", fmt::ptr(self.get()), aPromiseId); self->mChild->SendCloseSession(std::move(sessionId)) ->Then( GetMainThreadSerialEventTarget(), __func__, [self, aPromiseId](const PRemoteCDMChild::CloseSessionPromise:: ResolveOrRejectValue& aValue) { if (self->mKeys.IsNull()) { return; } if (aValue.IsReject()) { self->RejectPromise( aPromiseId, MediaResult( NS_ERROR_DOM_INVALID_STATE_ERR, "PRemoteCDMProxy::SendCloseSession IPC fail"_ns)); return; } self->ResolveOrRejectPromise(aPromiseId, aValue.ResolveValue()); }); }))); } void RemoteCDMProxy::RemoveSession(const nsAString& aSessionId, PromiseId aPromiseId) { MOZ_ALWAYS_SUCCEEDS(mThread->Dispatch(NS_NewRunnableFunction( __func__, [self = RefPtr{this}, sessionId = nsString(aSessionId), aPromiseId]() mutable { LOGD("[{}] RemoteCDMProxy::RemoveSession -- promise {}", fmt::ptr(self.get()), aPromiseId); self->mChild->SendRemoveSession(std::move(sessionId)) ->Then( GetMainThreadSerialEventTarget(), __func__, [self, aPromiseId](const PRemoteCDMChild::RemoveSessionPromise:: ResolveOrRejectValue& aValue) { if (self->mKeys.IsNull()) { return; } if (aValue.IsReject()) { self->RejectPromise( aPromiseId, MediaResult( NS_ERROR_DOM_INVALID_STATE_ERR, "PRemoteCDMProxy::SendRemoveSession IPC fail"_ns)); return; } self->ResolveOrRejectPromise(aPromiseId, aValue.ResolveValue()); }); }))); } void RemoteCDMProxy::QueryOutputProtectionStatus() {} void RemoteCDMProxy::NotifyOutputProtectionStatus( OutputProtectionCheckStatus aCheckStatus, OutputProtectionCaptureStatus aCaptureStatus) {} void RemoteCDMProxy::Shutdown() { LOGD("[{}] RemoteCDMProxy::Shutdown", fmt::ptr(this)); mKeys.Clear(); Destroy(); } void RemoteCDMProxy::Terminated() { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); } void RemoteCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken, const nsAString& aSessionId) { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); } void RemoteCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess) { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); } void RemoteCDMProxy::OnSessionMessage(const nsAString& aSessionId, dom::MediaKeyMessageType aMessageType, const nsTArray& aMessage) { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); } void RemoteCDMProxy::OnExpirationChange(const nsAString& aSessionId, UnixTime aExpiryTime) { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); } void RemoteCDMProxy::OnSessionClosed(const nsAString& aSessionId, dom::MediaKeySessionClosedReason aReason) { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); } void RemoteCDMProxy::OnSessionError(const nsAString& aSessionId, nsresult aException, uint32_t aSystemCode, const nsAString& aMsg) { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); } void RemoteCDMProxy::OnRejectPromise(uint32_t aPromiseId, ErrorResult&& aException, const nsCString& aMsg) { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); } RefPtr RemoteCDMProxy::Decrypt(MediaRawData* aSample) { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); return nullptr; } void RemoteCDMProxy::OnDecrypted(uint32_t aId, DecryptStatus aResult, const nsTArray& aDecryptedData) { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); } void RemoteCDMProxy::RejectPromise(PromiseId aId, ErrorResult&& aException, const nsCString& aReason) { LOGD("[{}] RemoteCDMProxy::RejectPromise -- {}", fmt::ptr(this), aId); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mKeys.IsNull()); mKeys->RejectPromise(aId, std::move(aException), aReason); } void RemoteCDMProxy::ResolvePromise(PromiseId aId) { LOGD("[{}] RemoteCDMProxy::ResolvePromise -- {}", fmt::ptr(this), aId); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mKeys.IsNull()); mKeys->ResolvePromise(aId); } void RemoteCDMProxy::RejectPromise(PromiseId aId, const MediaResult& aResult) { MOZ_ASSERT(NS_FAILED(aResult.Code())); ErrorResult rv; aResult.ThrowTo(rv); RejectPromise(aId, std::move(rv), aResult.Message()); } void RemoteCDMProxy::ResolveOrRejectPromise(PromiseId aId, const MediaResult& aResult) { if (aResult.Code() == NS_OK) { ResolvePromise(aId); return; } RejectPromise(aId, aResult); } void RemoteCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) {} void RemoteCDMProxy::GetStatusForPolicy( PromiseId aPromiseId, const dom::HDCPVersion& aMinHdcpVersion) { RejectPromise( aPromiseId, MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, "Currently Fennec does not support GetStatusForPolicy")); } #ifdef DEBUG bool RemoteCDMProxy::IsOnOwnerThread() { return mThread->IsOnCurrentThread(); } #endif #undef LOGD } // namespace mozilla