468 lines
19 KiB
C++
468 lines
19 KiB
C++
/* 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 "MFCDMChild.h"
|
|
|
|
#include "mozilla/EMEUtils.h"
|
|
#include "mozilla/KeySystemConfig.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/StaticString.h"
|
|
#include "mozilla/WMFCDMProxyCallback.h"
|
|
#include "nsString.h"
|
|
#include "RemoteDecoderManagerChild.h"
|
|
|
|
namespace mozilla {
|
|
|
|
#define LOG(msg, ...) \
|
|
EME_LOG("MFCDMChild[%p]@%s: " msg, this, __func__, ##__VA_ARGS__)
|
|
#define SLOG(msg, ...) EME_LOG("MFCDMChild@%s: " msg, __func__, ##__VA_ARGS__)
|
|
|
|
#define HANDLE_PENDING_PROMISE(method, callsite, promise, promiseId) \
|
|
do { \
|
|
promise->Then( \
|
|
self->mManagerThread, callsite, \
|
|
[self, promiseId, callsite]( \
|
|
PMFCDMChild::method##Promise::ResolveOrRejectValue&& result) { \
|
|
MutexAutoLock lock(self->mMutex); \
|
|
auto iter = self->mPendingGenericPromises.find(promiseId); \
|
|
if (iter == self->mPendingGenericPromises.end()) { \
|
|
return; \
|
|
} \
|
|
auto& promiseHolder = iter->second; \
|
|
if (result.IsResolve()) { \
|
|
if (NS_SUCCEEDED(result.ResolveValue())) { \
|
|
promiseHolder.ResolveIfExists(true, callsite); \
|
|
} else { \
|
|
promiseHolder.RejectIfExists(result.ResolveValue(), callsite); \
|
|
} \
|
|
} else { \
|
|
/* IPC die */ \
|
|
promiseHolder.RejectIfExists(NS_ERROR_FAILURE, callsite); \
|
|
} \
|
|
self->mPendingGenericPromises.erase(iter); \
|
|
}); \
|
|
} while (0)
|
|
|
|
#define INVOKE_ASYNC(method, promiseId, param1) \
|
|
do { \
|
|
StaticString callsite = __func__; \
|
|
using ParamType = std::remove_reference<decltype(param1)>::type; \
|
|
mManagerThread->Dispatch(NS_NewRunnableFunction( \
|
|
callsite, [self = RefPtr{this}, callsite, promiseId, \
|
|
param_1 = std::forward<ParamType>(param1)] { \
|
|
auto p = self->Send##method(param_1); \
|
|
HANDLE_PENDING_PROMISE(method, callsite, p, promiseId); \
|
|
})); \
|
|
} while (0)
|
|
|
|
#define INVOKE_ASYNC2(method, promiseId, param1, param2) \
|
|
do { \
|
|
StaticString callsite = __func__; \
|
|
using ParamType1 = std::remove_reference<decltype(param1)>::type; \
|
|
using ParamType2 = std::remove_reference<decltype(param2)>::type; \
|
|
mManagerThread->Dispatch(NS_NewRunnableFunction( \
|
|
callsite, [self = RefPtr{this}, callsite, promiseId, \
|
|
param_1 = std::forward<ParamType1>(param1), \
|
|
param_2 = std::forward<ParamType2>(param2)] { \
|
|
auto p = self->Send##method(param_1, param_2); \
|
|
HANDLE_PENDING_PROMISE(method, callsite, p, promiseId); \
|
|
})); \
|
|
} while (0)
|
|
|
|
MFCDMChild::MFCDMChild(const nsAString& aKeySystem)
|
|
: mKeySystem(aKeySystem),
|
|
mManagerThread(RemoteDecoderManagerChild::GetManagerThread()),
|
|
mState(NS_ERROR_NOT_INITIALIZED),
|
|
mShutdown(false) {}
|
|
|
|
MFCDMChild::~MFCDMChild() {}
|
|
|
|
void MFCDMChild::EnsureRemote() {
|
|
if (mRemotePromise) {
|
|
LOG("already created remote promise");
|
|
return;
|
|
}
|
|
|
|
if (!mManagerThread) {
|
|
LOG("no manager thread");
|
|
mState = NS_ERROR_NOT_AVAILABLE;
|
|
mRemotePromise = RemotePromise::CreateAndReject(mState, __func__);
|
|
return;
|
|
}
|
|
|
|
mRemotePromise = mRemotePromiseHolder.Ensure(__func__);
|
|
|
|
RefPtr<MFCDMChild> self = this;
|
|
RemoteDecoderManagerChild::LaunchUtilityProcessIfNeeded(
|
|
RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM)
|
|
->Then(
|
|
mManagerThread, __func__,
|
|
[self, this](bool) {
|
|
mRemoteRequest.Complete();
|
|
RefPtr<RemoteDecoderManagerChild> manager =
|
|
RemoteDecoderManagerChild::GetSingleton(
|
|
RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM);
|
|
if (!manager || !manager->CanSend()) {
|
|
LOG("manager not exists or can't send");
|
|
mState = NS_ERROR_NOT_AVAILABLE;
|
|
mRemotePromiseHolder.RejectIfExists(mState, __func__);
|
|
return;
|
|
}
|
|
|
|
mIPDLSelfRef = this;
|
|
MOZ_ALWAYS_TRUE(manager->SendPMFCDMConstructor(this, mKeySystem));
|
|
mState = NS_OK;
|
|
mRemotePromiseHolder.ResolveIfExists(true, __func__);
|
|
},
|
|
[self, this](nsresult rv) {
|
|
mRemoteRequest.Complete();
|
|
LOG("fail to launch MFCDM process");
|
|
mState = rv;
|
|
mRemotePromiseHolder.RejectIfExists(rv, __func__);
|
|
})
|
|
->Track(mRemoteRequest);
|
|
}
|
|
|
|
void MFCDMChild::Shutdown() {
|
|
MOZ_ASSERT(!mShutdown);
|
|
mShutdown = true;
|
|
if (mState == NS_OK) {
|
|
mManagerThread->Dispatch(
|
|
NS_NewRunnableFunction(__func__, [self = RefPtr{this}, this]() {
|
|
mRemoteRequest.DisconnectIfExists();
|
|
mInitRequest.DisconnectIfExists();
|
|
mProxyCallback = nullptr;
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
for (auto& promise : mPendingSessionPromises) {
|
|
promise.second.RejectIfExists(NS_ERROR_ABORT, __func__);
|
|
}
|
|
mPendingSessionPromises.clear();
|
|
|
|
for (auto& promise : mPendingGenericPromises) {
|
|
promise.second.RejectIfExists(NS_ERROR_ABORT, __func__);
|
|
}
|
|
mPendingGenericPromises.clear();
|
|
}
|
|
|
|
mRemotePromiseHolder.RejectIfExists(NS_ERROR_ABORT, __func__);
|
|
mCapabilitiesPromiseHolder.RejectIfExists(NS_ERROR_ABORT, __func__);
|
|
|
|
Send__delete__(this);
|
|
}));
|
|
}
|
|
}
|
|
|
|
RefPtr<MFCDMChild::CapabilitiesPromise> MFCDMChild::GetCapabilities(
|
|
MFCDMCapabilitiesRequest&& aRequest) {
|
|
MOZ_ASSERT(mManagerThread);
|
|
|
|
if (mShutdown) {
|
|
return CapabilitiesPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
|
|
}
|
|
|
|
if (mState != NS_OK && mState != NS_ERROR_NOT_INITIALIZED) {
|
|
LOG("error=%x", uint32_t(nsresult(mState)));
|
|
return CapabilitiesPromise::CreateAndReject(mState, __func__);
|
|
}
|
|
|
|
auto doSend = [self = RefPtr{this}, request = std::move(aRequest), this]() {
|
|
SendGetCapabilities(request)->Then(
|
|
mManagerThread, __func__,
|
|
[self, this](MFCDMCapabilitiesResult&& aResult) {
|
|
if (aResult.type() == MFCDMCapabilitiesResult::Tnsresult) {
|
|
mCapabilitiesPromiseHolder.RejectIfExists(aResult.get_nsresult(),
|
|
__func__);
|
|
return;
|
|
}
|
|
mCapabilitiesPromiseHolder.ResolveIfExists(
|
|
std::move(aResult.get_MFCDMCapabilitiesIPDL()), __func__);
|
|
},
|
|
[self, this](const mozilla::ipc::ResponseRejectReason& aReason) {
|
|
mCapabilitiesPromiseHolder.RejectIfExists(NS_ERROR_FAILURE, __func__);
|
|
});
|
|
};
|
|
|
|
return InvokeAsync(doSend, __func__, mCapabilitiesPromiseHolder);
|
|
}
|
|
|
|
// Neither error nor shutdown.
|
|
void MFCDMChild::AssertSendable() {
|
|
MOZ_ASSERT((mState == NS_ERROR_NOT_INITIALIZED || mState == NS_OK) &&
|
|
mShutdown == false);
|
|
}
|
|
|
|
template <typename PromiseType>
|
|
already_AddRefed<PromiseType> MFCDMChild::InvokeAsync(
|
|
std::function<void()>&& aCall, StaticString aCallerName,
|
|
MozPromiseHolder<PromiseType>& aPromise) {
|
|
AssertSendable();
|
|
|
|
if (mState == NS_OK) {
|
|
// mRemotePromise is resolved, send on manager thread.
|
|
mManagerThread->Dispatch(
|
|
NS_NewRunnableFunction(aCallerName, std::move(aCall)));
|
|
} else if (mState == NS_ERROR_NOT_INITIALIZED) {
|
|
// mRemotePromise is pending, chain to it.
|
|
mRemotePromise->Then(
|
|
mManagerThread, __func__, std::move(aCall),
|
|
[self = RefPtr{this}, this, &aPromise, aCallerName](nsresult rv) {
|
|
LOG("error=%x", uint32_t(rv));
|
|
mState = rv;
|
|
aPromise.RejectIfExists(rv, aCallerName);
|
|
});
|
|
}
|
|
|
|
return aPromise.Ensure(aCallerName);
|
|
}
|
|
|
|
RefPtr<MFCDMChild::InitPromise> MFCDMChild::Init(
|
|
const nsAString& aOrigin, const CopyableTArray<nsString>& aInitDataTypes,
|
|
const KeySystemConfig::Requirement aPersistentState,
|
|
const KeySystemConfig::Requirement aDistinctiveID,
|
|
const CopyableTArray<MFCDMMediaCapability>& aAudioCapabilities,
|
|
const CopyableTArray<MFCDMMediaCapability>& aVideoCapabilities,
|
|
WMFCDMProxyCallback* aProxyCallback) {
|
|
MOZ_ASSERT(mManagerThread);
|
|
|
|
if (mShutdown) {
|
|
return InitPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
|
|
}
|
|
|
|
if (mState != NS_OK && mState != NS_ERROR_NOT_INITIALIZED) {
|
|
LOG("error=%x", uint32_t(nsresult(mState)));
|
|
return InitPromise::CreateAndReject(mState, __func__);
|
|
}
|
|
|
|
RefPtr<WMFCDMProxyCallback> callback = aProxyCallback;
|
|
MFCDMInitParamsIPDL params{nsString(aOrigin), aInitDataTypes,
|
|
aDistinctiveID, aPersistentState,
|
|
aAudioCapabilities, aVideoCapabilities};
|
|
auto doSend = [self = RefPtr{this}, this, params, callback]() {
|
|
SendInit(params)
|
|
->Then(
|
|
mManagerThread, __func__,
|
|
[self, callback, this](MFCDMInitResult&& aResult) {
|
|
mInitRequest.Complete();
|
|
if (aResult.type() == MFCDMInitResult::Tnsresult) {
|
|
nsresult rv = aResult.get_nsresult();
|
|
mInitPromiseHolder.RejectIfExists(rv, __func__);
|
|
return;
|
|
}
|
|
mProxyCallback = callback;
|
|
mId = aResult.get_MFCDMInitIPDL().id();
|
|
mInitPromiseHolder.ResolveIfExists(aResult.get_MFCDMInitIPDL(),
|
|
__func__);
|
|
},
|
|
[self, this](const mozilla::ipc::ResponseRejectReason& aReason) {
|
|
mInitRequest.Complete();
|
|
mInitPromiseHolder.RejectIfExists(NS_ERROR_FAILURE, __func__);
|
|
})
|
|
->Track(mInitRequest);
|
|
};
|
|
|
|
return InvokeAsync(std::move(doSend), __func__, mInitPromiseHolder);
|
|
}
|
|
|
|
RefPtr<MFCDMChild::SessionPromise> MFCDMChild::CreateSessionAndGenerateRequest(
|
|
uint32_t aPromiseId, KeySystemConfig::SessionType aSessionType,
|
|
const nsAString& aInitDataType, const nsTArray<uint8_t>& aInitData) {
|
|
MOZ_ASSERT(mManagerThread);
|
|
MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it");
|
|
|
|
if (mShutdown) {
|
|
return MFCDMChild::SessionPromise::CreateAndReject(NS_ERROR_ABORT,
|
|
__func__);
|
|
}
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(mPendingSessionPromises.find(aPromiseId) ==
|
|
mPendingSessionPromises.end());
|
|
mPendingSessionPromises.emplace(aPromiseId,
|
|
MozPromiseHolder<SessionPromise>{});
|
|
mManagerThread->Dispatch(NS_NewRunnableFunction(
|
|
__func__, [self = RefPtr{this}, this,
|
|
params =
|
|
MFCDMCreateSessionParamsIPDL{
|
|
aSessionType, nsString{aInitDataType}, aInitData},
|
|
aPromiseId] {
|
|
SendCreateSessionAndGenerateRequest(params)->Then(
|
|
mManagerThread, __func__,
|
|
[self, aPromiseId, this](const MFCDMSessionResult& result) {
|
|
MutexAutoLock lock(mMutex);
|
|
auto iter = mPendingSessionPromises.find(aPromiseId);
|
|
if (iter == mPendingSessionPromises.end()) {
|
|
return;
|
|
}
|
|
auto& promiseHolder = iter->second;
|
|
if (result.type() == MFCDMSessionResult::Tnsresult) {
|
|
promiseHolder.RejectIfExists(result.get_nsresult(), __func__);
|
|
} else {
|
|
LOG("session ID=[%zu]%s", result.get_nsString().Length(),
|
|
NS_ConvertUTF16toUTF8(result.get_nsString()).get());
|
|
promiseHolder.ResolveIfExists(result.get_nsString(), __func__);
|
|
}
|
|
mPendingSessionPromises.erase(iter);
|
|
},
|
|
[self, aPromiseId,
|
|
this](const mozilla::ipc::ResponseRejectReason& aReason) {
|
|
MutexAutoLock lock(mMutex);
|
|
auto iter = mPendingSessionPromises.find(aPromiseId);
|
|
if (iter == mPendingSessionPromises.end()) {
|
|
return;
|
|
}
|
|
auto& promiseHolder = iter->second;
|
|
promiseHolder.RejectIfExists(NS_ERROR_FAILURE, __func__);
|
|
mPendingSessionPromises.erase(iter);
|
|
});
|
|
}));
|
|
return mPendingSessionPromises[aPromiseId].Ensure(__func__);
|
|
}
|
|
|
|
RefPtr<GenericPromise> MFCDMChild::LoadSession(
|
|
uint32_t aPromiseId, const KeySystemConfig::SessionType aSessionType,
|
|
const nsAString& aSessionId) {
|
|
MOZ_ASSERT(mManagerThread);
|
|
MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it");
|
|
|
|
if (mShutdown) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
|
|
}
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(mPendingGenericPromises.find(aPromiseId) ==
|
|
mPendingGenericPromises.end());
|
|
mPendingGenericPromises.emplace(aPromiseId,
|
|
MozPromiseHolder<GenericPromise>{});
|
|
INVOKE_ASYNC2(LoadSession, aPromiseId, aSessionType, nsString{aSessionId});
|
|
return mPendingGenericPromises[aPromiseId].Ensure(__func__);
|
|
}
|
|
|
|
RefPtr<GenericPromise> MFCDMChild::UpdateSession(uint32_t aPromiseId,
|
|
const nsAString& aSessionId,
|
|
nsTArray<uint8_t>& aResponse) {
|
|
MOZ_ASSERT(mManagerThread);
|
|
MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it");
|
|
|
|
if (mShutdown) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
|
|
}
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(mPendingGenericPromises.find(aPromiseId) ==
|
|
mPendingGenericPromises.end());
|
|
mPendingGenericPromises.emplace(aPromiseId,
|
|
MozPromiseHolder<GenericPromise>{});
|
|
INVOKE_ASYNC2(UpdateSession, aPromiseId, nsString{aSessionId},
|
|
std::move(aResponse));
|
|
return mPendingGenericPromises[aPromiseId].Ensure(__func__);
|
|
}
|
|
|
|
RefPtr<GenericPromise> MFCDMChild::CloseSession(uint32_t aPromiseId,
|
|
const nsAString& aSessionId) {
|
|
MOZ_ASSERT(mManagerThread);
|
|
MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it");
|
|
|
|
if (mShutdown) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
|
|
}
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(mPendingGenericPromises.find(aPromiseId) ==
|
|
mPendingGenericPromises.end());
|
|
mPendingGenericPromises.emplace(aPromiseId,
|
|
MozPromiseHolder<GenericPromise>{});
|
|
INVOKE_ASYNC(CloseSession, aPromiseId, nsString{aSessionId});
|
|
return mPendingGenericPromises[aPromiseId].Ensure(__func__);
|
|
}
|
|
|
|
RefPtr<GenericPromise> MFCDMChild::RemoveSession(uint32_t aPromiseId,
|
|
const nsAString& aSessionId) {
|
|
MOZ_ASSERT(mManagerThread);
|
|
MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it");
|
|
|
|
if (mShutdown) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
|
|
}
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(mPendingGenericPromises.find(aPromiseId) ==
|
|
mPendingGenericPromises.end());
|
|
mPendingGenericPromises.emplace(aPromiseId,
|
|
MozPromiseHolder<GenericPromise>{});
|
|
INVOKE_ASYNC(RemoveSession, aPromiseId, nsString{aSessionId});
|
|
return mPendingGenericPromises[aPromiseId].Ensure(__func__);
|
|
}
|
|
|
|
RefPtr<GenericPromise> MFCDMChild::SetServerCertificate(
|
|
uint32_t aPromiseId, nsTArray<uint8_t>& aCert) {
|
|
MOZ_ASSERT(mManagerThread);
|
|
MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it");
|
|
|
|
if (mShutdown) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
|
|
}
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(mPendingGenericPromises.find(aPromiseId) ==
|
|
mPendingGenericPromises.end());
|
|
mPendingGenericPromises.emplace(aPromiseId,
|
|
MozPromiseHolder<GenericPromise>{});
|
|
INVOKE_ASYNC(SetServerCertificate, aPromiseId, std::move(aCert));
|
|
return mPendingGenericPromises[aPromiseId].Ensure(__func__);
|
|
}
|
|
|
|
RefPtr<GenericPromise> MFCDMChild::GetStatusForPolicy(
|
|
uint32_t aPromiseId, const dom::HDCPVersion& aMinHdcpVersion) {
|
|
MOZ_ASSERT(mManagerThread);
|
|
MOZ_ASSERT(mId > 0, "Should call Init() first and wait for it");
|
|
|
|
if (mShutdown) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
|
|
}
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(mPendingGenericPromises.find(aPromiseId) ==
|
|
mPendingGenericPromises.end());
|
|
mPendingGenericPromises.emplace(aPromiseId,
|
|
MozPromiseHolder<GenericPromise>{});
|
|
INVOKE_ASYNC(GetStatusForPolicy, aPromiseId, aMinHdcpVersion);
|
|
return mPendingGenericPromises[aPromiseId].Ensure(__func__);
|
|
}
|
|
|
|
mozilla::ipc::IPCResult MFCDMChild::RecvOnSessionKeyMessage(
|
|
const MFCDMKeyMessage& aMessage) {
|
|
LOG("RecvOnSessionKeyMessage, sessionId=%s",
|
|
NS_ConvertUTF16toUTF8(aMessage.sessionId()).get());
|
|
MOZ_ASSERT(mManagerThread);
|
|
MOZ_ASSERT(mProxyCallback);
|
|
mProxyCallback->OnSessionMessage(aMessage);
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult MFCDMChild::RecvOnSessionKeyStatusesChanged(
|
|
const MFCDMKeyStatusChange& aKeyStatuses) {
|
|
LOG("RecvOnSessionKeyStatusesChanged, sessionId=%s",
|
|
NS_ConvertUTF16toUTF8(aKeyStatuses.sessionId()).get());
|
|
MOZ_ASSERT(mManagerThread);
|
|
MOZ_ASSERT(mProxyCallback);
|
|
mProxyCallback->OnSessionKeyStatusesChange(aKeyStatuses);
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult MFCDMChild::RecvOnSessionKeyExpiration(
|
|
const MFCDMKeyExpiration& aExpiration) {
|
|
LOG("RecvOnSessionKeyExpiration, sessionId=%s",
|
|
NS_ConvertUTF16toUTF8(aExpiration.sessionId()).get());
|
|
MOZ_ASSERT(mManagerThread);
|
|
MOZ_ASSERT(mProxyCallback);
|
|
mProxyCallback->OnSessionKeyExpiration(aExpiration);
|
|
return IPC_OK();
|
|
}
|
|
|
|
#undef SLOG
|
|
#undef LOG
|
|
|
|
} // namespace mozilla
|