Source code

Revision control

Copy as Markdown

Other Tools

/* 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 "MFCDMParent.h"
#include <mfmediaengine.h>
#include <unknwnbase.h>
#include <wtypes.h>
#define INITGUID // Enable DEFINE_PROPERTYKEY()
#include <propkeydef.h> // For DEFINE_PROPERTYKEY() definition
#include <propvarutil.h> // For InitPropVariantFrom*()
#include "MFCDMProxy.h"
#include "MFMediaEngineUtils.h"
#include "RemoteDecodeUtils.h" // For GetCurrentSandboxingKind()
#include "SpecialSystemDirectory.h" // For temp dir
#include "WMFUtils.h"
#include "mozilla/EMEUtils.h"
#include "mozilla/KeySystemConfig.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/WindowsVersion.h"
#include "mozilla/dom/KeySystemNames.h"
#include "mozilla/dom/MediaKeysBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/ipc/UtilityAudioDecoderChild.h"
#include "mozilla/ipc/UtilityProcessManager.h"
#include "mozilla/ipc/UtilityProcessParent.h"
#include "nsTHashMap.h"
#ifdef MOZ_WMF_CDM_LPAC_SANDBOX
# include "sandboxBroker.h"
#endif
using Microsoft::WRL::ComPtr;
using Microsoft::WRL::MakeAndInitialize;
namespace mozilla {
// See
#ifndef EME_CONTENTDECRYPTIONMODULE_ORIGIN_ID
DEFINE_PROPERTYKEY(EME_CONTENTDECRYPTIONMODULE_ORIGIN_ID, 0x1218a3e2, 0xcfb0,
0x4c98, 0x90, 0xe5, 0x5f, 0x58, 0x18, 0xd4, 0xb6, 0x7e,
PID_FIRST_USABLE);
#endif
#define MFCDM_PARENT_LOG(msg, ...) \
EME_LOG("MFCDMParent[%p, Id=%" PRIu64 "]@%s: " msg, this, this->mId, \
__func__, ##__VA_ARGS__)
#define MFCDM_PARENT_SLOG(msg, ...) \
EME_LOG("MFCDMParent@%s: " msg, __func__, ##__VA_ARGS__)
#define MFCDM_RETURN_IF_FAILED(x) \
do { \
HRESULT rv = x; \
if (MOZ_UNLIKELY(FAILED(rv))) { \
MFCDM_PARENT_SLOG("(" #x ") failed, rv=%lx", rv); \
return rv; \
} \
} while (false)
#define MFCDM_RETURN_BOOL_IF_FAILED(x) \
do { \
HRESULT rv = x; \
if (MOZ_UNLIKELY(FAILED(rv))) { \
MFCDM_PARENT_SLOG("(" #x ") failed, rv=%lx", rv); \
return false; \
} \
} while (false)
#define MFCDM_REJECT_IF(pred, rv) \
do { \
if (MOZ_UNLIKELY(pred)) { \
MFCDM_PARENT_LOG("reject for [" #pred "], rv=%x", uint32_t(rv)); \
aResolver(rv); \
return IPC_OK(); \
} \
} while (false)
#define MFCDM_REJECT_IF_FAILED(op, rv) \
do { \
HRESULT hr = op; \
if (MOZ_UNLIKELY(FAILED(hr))) { \
MFCDM_PARENT_LOG("(" #op ") failed(hr=%lx), rv=%x", hr, uint32_t(rv)); \
aResolver(rv); \
return IPC_OK(); \
} \
} while (false)
StaticMutex sFactoryMutex;
MOZ_RUNINIT static nsTHashMap<nsStringHashKey,
ComPtr<IMFContentDecryptionModuleFactory>>
sFactoryMap;
MOZ_RUNINIT static CopyableTArray<MFCDMCapabilitiesIPDL> sCapabilities;
StaticMutex sCapabilitesMutex;
MOZ_RUNINIT static ComPtr<IUnknown> sMediaEngineClassFactory;
// RAIIized PROPVARIANT. See
// third_party/libwebrtc/modules/audio_device/win/core_audio_utility_win.h
class AutoPropVar {
public:
AutoPropVar() { PropVariantInit(&mVar); }
~AutoPropVar() { Reset(); }
AutoPropVar(const AutoPropVar&) = delete;
AutoPropVar& operator=(const AutoPropVar&) = delete;
bool operator==(const AutoPropVar&) const = delete;
bool operator!=(const AutoPropVar&) const = delete;
// Returns a pointer to the underlying PROPVARIANT for use as an out param
// in a function call.
PROPVARIANT* Receive() {
MOZ_ASSERT(mVar.vt == VT_EMPTY);
return &mVar;
}
// Clears the instance to prepare it for re-use (e.g., via Receive).
void Reset() {
if (mVar.vt != VT_EMPTY) {
HRESULT hr = PropVariantClear(&mVar);
MOZ_ASSERT(SUCCEEDED(hr));
Unused << hr;
}
}
const PROPVARIANT& get() const { return mVar; }
const PROPVARIANT* ptr() const { return &mVar; }
private:
PROPVARIANT mVar;
};
static MF_MEDIAKEYS_REQUIREMENT ToMFRequirement(
const KeySystemConfig::Requirement aRequirement) {
switch (aRequirement) {
case KeySystemConfig::Requirement::NotAllowed:
return MF_MEDIAKEYS_REQUIREMENT_NOT_ALLOWED;
case KeySystemConfig::Requirement::Optional:
return MF_MEDIAKEYS_REQUIREMENT_OPTIONAL;
case KeySystemConfig::Requirement::Required:
return MF_MEDIAKEYS_REQUIREMENT_REQUIRED;
}
};
static inline LPCWSTR InitDataTypeToString(const nsAString& aInitDataType) {
// The strings are defined in https://www.w3.org/TR/eme-initdata-registry/
if (aInitDataType.EqualsLiteral("webm")) {
return L"webm";
} else if (aInitDataType.EqualsLiteral("cenc")) {
return L"cenc";
} else if (aInitDataType.EqualsLiteral("keyids")) {
return L"keyids";
} else {
return L"unknown";
}
}
// The HDCP value follows the feature value in
// - 1 (on without HDCP 2.2 Type 1 restriction)
// - 2 (on with HDCP 2.2 Type 1 restriction)
static nsString GetHdcpPolicy(const dom::HDCPVersion& aMinHdcpVersion) {
if (aMinHdcpVersion == dom::HDCPVersion::_2_2 ||
aMinHdcpVersion == dom::HDCPVersion::_2_3) {
return nsString(u"hdcp=2");
}
return nsString(u"hdcp=1");
}
static bool RequireClearLead(const nsString& aKeySystem) {
return aKeySystem.EqualsLiteral(kWidevineExperiment2KeySystemName) ||
aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName);
}
static void BuildCapabilitiesArray(
const nsTArray<MFCDMMediaCapability>& aCapabilities,
AutoPropVar& capabilitiesPropOut) {
PROPVARIANT* capabilitiesArray = (PROPVARIANT*)CoTaskMemAlloc(
sizeof(PROPVARIANT) * aCapabilities.Length());
for (size_t idx = 0; idx < aCapabilities.Length(); idx++) {
ComPtr<IPropertyStore> capabilitiesProperty;
RETURN_VOID_IF_FAILED(
PSCreateMemoryPropertyStore(IID_PPV_ARGS(&capabilitiesProperty)));
AutoPropVar contentType;
auto* var = contentType.Receive();
var->vt = VT_BSTR;
var->bstrVal = SysAllocString(aCapabilities[idx].contentType().get());
RETURN_VOID_IF_FAILED(
capabilitiesProperty->SetValue(MF_EME_CONTENTTYPE, contentType.get()));
AutoPropVar robustness;
var = robustness.Receive();
var->vt = VT_BSTR;
var->bstrVal = SysAllocString(aCapabilities[idx].robustness().get());
RETURN_VOID_IF_FAILED(
capabilitiesProperty->SetValue(MF_EME_ROBUSTNESS, robustness.get()));
capabilitiesArray[idx].vt = VT_UNKNOWN;
capabilitiesArray[idx].punkVal = capabilitiesProperty.Detach();
}
auto* var = capabilitiesPropOut.Receive();
var->vt = VT_VARIANT | VT_VECTOR;
var->capropvar.cElems = aCapabilities.Length();
var->capropvar.pElems = capabilitiesArray;
}
static HRESULT BuildCDMAccessConfig(const MFCDMInitParamsIPDL& aParams,
ComPtr<IPropertyStore>& aConfig) {
ComPtr<IPropertyStore> mksc; // EME MediaKeySystemConfiguration
MFCDM_RETURN_IF_FAILED(PSCreateMemoryPropertyStore(IID_PPV_ARGS(&mksc)));
// Init type. If we don't set `MF_EME_INITDATATYPES` then we won't be able
// to create CDM module on Windows 10, which is not documented officially.
BSTR* initDataTypeArray =
(BSTR*)CoTaskMemAlloc(sizeof(BSTR) * aParams.initDataTypes().Length());
for (size_t i = 0; i < aParams.initDataTypes().Length(); i++) {
initDataTypeArray[i] =
SysAllocString(InitDataTypeToString(aParams.initDataTypes()[i]));
}
AutoPropVar initDataTypes;
PROPVARIANT* var = initDataTypes.Receive();
var->vt = VT_VECTOR | VT_BSTR;
var->cabstr.cElems = static_cast<ULONG>(aParams.initDataTypes().Length());
var->cabstr.pElems = initDataTypeArray;
MFCDM_RETURN_IF_FAILED(
mksc->SetValue(MF_EME_INITDATATYPES, initDataTypes.get()));
// Audio capabilities
AutoPropVar audioCapabilities;
BuildCapabilitiesArray(aParams.audioCapabilities(), audioCapabilities);
MFCDM_RETURN_IF_FAILED(
mksc->SetValue(MF_EME_AUDIOCAPABILITIES, audioCapabilities.get()));
// Video capabilities
AutoPropVar videoCapabilities;
BuildCapabilitiesArray(aParams.videoCapabilities(), videoCapabilities);
MFCDM_RETURN_IF_FAILED(
mksc->SetValue(MF_EME_VIDEOCAPABILITIES, videoCapabilities.get()));
// Persist state
AutoPropVar persistState;
InitPropVariantFromUInt32(ToMFRequirement(aParams.persistentState()),
persistState.Receive());
MFCDM_RETURN_IF_FAILED(
mksc->SetValue(MF_EME_PERSISTEDSTATE, persistState.get()));
// Distintive Id
AutoPropVar distinctiveID;
InitPropVariantFromUInt32(ToMFRequirement(aParams.distinctiveID()),
distinctiveID.Receive());
MFCDM_RETURN_IF_FAILED(
mksc->SetValue(MF_EME_DISTINCTIVEID, distinctiveID.get()));
aConfig.Swap(mksc);
return S_OK;
}
static HRESULT BuildCDMProperties(const nsString& aOrigin,
ComPtr<IPropertyStore>& aProps) {
MOZ_ASSERT(!aOrigin.IsEmpty());
ComPtr<IPropertyStore> props;
MFCDM_RETURN_IF_FAILED(PSCreateMemoryPropertyStore(IID_PPV_ARGS(&props)));
AutoPropVar origin;
MFCDM_RETURN_IF_FAILED(
InitPropVariantFromString(aOrigin.get(), origin.Receive()));
MFCDM_RETURN_IF_FAILED(
props->SetValue(EME_CONTENTDECRYPTIONMODULE_ORIGIN_ID, origin.get()));
// TODO: support client token?
// TODO: CDM store path per profile?
nsCOMPtr<nsIFile> dir;
if (NS_FAILED(GetSpecialSystemDirectory(OS_TemporaryDirectory,
getter_AddRefs(dir)))) {
return E_ACCESSDENIED;
}
if (NS_FAILED(dir->AppendNative(nsDependentCString("mfcdm")))) {
return E_ACCESSDENIED;
}
nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_FAILED(rv)) {
return E_ACCESSDENIED;
}
nsAutoString cdmStorePath;
if (NS_FAILED(dir->GetPath(cdmStorePath))) {
return E_ACCESSDENIED;
}
AutoPropVar path;
MFCDM_RETURN_IF_FAILED(
InitPropVariantFromString(cdmStorePath.get(), path.Receive()));
MFCDM_RETURN_IF_FAILED(
props->SetValue(MF_CONTENTDECRYPTIONMODULE_STOREPATH, path.get()));
aProps.Swap(props);
return S_OK;
}
static HRESULT CreateContentDecryptionModule(
ComPtr<IMFContentDecryptionModuleFactory> aFactory,
const nsString& aKeySystem, const MFCDMInitParamsIPDL& aParams,
ComPtr<IMFContentDecryptionModule>& aCDMOut) {
// Get access object to CDM.
ComPtr<IPropertyStore> accessConfig;
RETURN_IF_FAILED(BuildCDMAccessConfig(aParams, accessConfig));
AutoTArray<IPropertyStore*, 1> configs = {accessConfig.Get()};
ComPtr<IMFContentDecryptionModuleAccess> cdmAccess;
RETURN_IF_FAILED(aFactory->CreateContentDecryptionModuleAccess(
aKeySystem.get(), configs.Elements(), configs.Length(), &cdmAccess));
// Get CDM.
ComPtr<IPropertyStore> cdmProps;
RETURN_IF_FAILED(BuildCDMProperties(aParams.origin(), cdmProps));
ComPtr<IMFContentDecryptionModule> cdm;
RETURN_IF_FAILED(
cdmAccess->CreateContentDecryptionModule(cdmProps.Get(), &cdm));
aCDMOut.Swap(cdm);
return S_OK;
}
// Wrapper function for IMFContentDecryptionModuleFactory::IsTypeSupported.
static bool IsTypeSupported(
const ComPtr<IMFContentDecryptionModuleFactory>& aFactory,
const nsString& aKeySystem, const nsString* aContentType = nullptr) {
nsString keySystem;
// Widevine's factory only takes original key system string.
if (IsWidevineExperimentKeySystemAndSupported(aKeySystem)) {
keySystem.AppendLiteral(u"com.widevine.alpha");
}
// kPlayReadyHardwareClearLeadKeySystemName is our custom key system name,
// we should use kPlayReadyKeySystemHardware which is the real key system
// name.
else if (aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) {
keySystem.AppendLiteral(kPlayReadyKeySystemHardware);
} else {
keySystem = aKeySystem;
}
return aFactory->IsTypeSupported(
keySystem.get(), aContentType ? aContentType->get() : nullptr);
}
static nsString MapKeySystem(const nsString& aKeySystem) {
// When website requests HW secure robustness for video by original Widevine
// key system name, it would be mapped to this key system which is for HWDRM.
if (IsWidevineKeySystem(aKeySystem)) {
return nsString(u"com.widevine.alpha.experiment");
}
// kPlayReadyHardwareClearLeadKeySystemName is our custom key system name,
// we should use kPlayReadyKeySystemHardware which is the real key system
// name.
if (aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) {
return NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware);
}
return aKeySystem;
}
/* static */
void MFCDMParent::SetWidevineL1Path(const char* aPath) {
nsAutoCString path(aPath);
path.AppendLiteral("\\Google.Widevine.CDM.dll");
sWidevineL1Path = CreateBSTRFromConstChar(path.get());
MFCDM_PARENT_SLOG("Set Widevine L1 dll path=%ls\n", sWidevineL1Path);
}
void MFCDMParent::Register() {
MOZ_ASSERT(!sRegisteredCDMs.Contains(this->mId));
sRegisteredCDMs.InsertOrUpdate(this->mId, this);
MFCDM_PARENT_LOG("Registered!");
}
void MFCDMParent::Unregister() {
MOZ_ASSERT(sRegisteredCDMs.Contains(this->mId));
sRegisteredCDMs.Remove(this->mId);
MFCDM_PARENT_LOG("Unregistered!");
}
MFCDMParent::MFCDMParent(const nsAString& aKeySystem,
RemoteDecoderManagerParent* aManager,
nsISerialEventTarget* aManagerThread)
: mKeySystem(aKeySystem),
mManager(aManager),
mManagerThread(aManagerThread),
mId(sNextId++),
mKeyMessageEvents(aManagerThread),
mKeyChangeEvents(aManagerThread),
mExpirationEvents(aManagerThread) {
MOZ_ASSERT(IsPlayReadyKeySystemAndSupported(aKeySystem) ||
IsWidevineExperimentKeySystemAndSupported(aKeySystem) ||
IsWidevineKeySystem(mKeySystem) ||
IsWMFClearKeySystemAndSupported(aKeySystem));
MOZ_ASSERT(aManager);
MOZ_ASSERT(aManagerThread);
MOZ_ASSERT(XRE_IsUtilityProcess());
MOZ_ASSERT(GetCurrentSandboxingKind() ==
ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM);
MFCDM_PARENT_LOG("MFCDMParent created");
mIPDLSelfRef = this;
Register();
mKeyMessageListener = mKeyMessageEvents.Connect(
mManagerThread, this, &MFCDMParent::SendOnSessionKeyMessage);
mKeyChangeListener = mKeyChangeEvents.Connect(
mManagerThread, this, &MFCDMParent::SendOnSessionKeyStatusesChanged);
mExpirationListener = mExpirationEvents.Connect(
mManagerThread, this, &MFCDMParent::SendOnSessionKeyExpiration);
RETURN_VOID_IF_FAILED(GetOrCreateFactory(mKeySystem, mFactory));
}
void MFCDMParent::ShutdownCDM() {
AssertOnManagerThread();
if (!mCDM) {
return;
}
auto rv = mCDM->SetPMPHostApp(nullptr);
if (FAILED(rv)) {
MFCDM_PARENT_LOG("Failed to clear PMP Host App, rv=%lx", rv);
}
SHUTDOWN_IF_POSSIBLE(mCDM);
mCDM = nullptr;
MFCDM_PARENT_LOG("Shutdown CDM completed");
}
void MFCDMParent::Destroy() {
AssertOnManagerThread();
mKeyMessageEvents.DisconnectAll();
mKeyChangeEvents.DisconnectAll();
mExpirationEvents.DisconnectAll();
mKeyMessageListener.DisconnectIfExists();
mKeyChangeListener.DisconnectIfExists();
mExpirationListener.DisconnectIfExists();
if (mPMPHostWrapper) {
mPMPHostWrapper->Shutdown();
mPMPHostWrapper = nullptr;
}
ShutdownCDM();
mFactory = nullptr;
for (auto& iter : mSessions) {
iter.second->Close();
}
mSessions.clear();
mIPDLSelfRef = nullptr;
}
MFCDMParent::~MFCDMParent() {
MFCDM_PARENT_LOG("MFCDMParent detroyed");
Unregister();
}
/* static */
LPCWSTR MFCDMParent::GetCDMLibraryName(const nsString& aKeySystem) {
if (IsWMFClearKeySystemAndSupported(aKeySystem) ||
StaticPrefs::media_eme_wmf_use_mock_cdm_for_external_cdms()) {
return L"wmfclearkey.dll";
}
// PlayReady is a built-in CDM on Windows, no need to load external library.
if (IsPlayReadyKeySystemAndSupported(aKeySystem)) {
return L"";
}
if (IsWidevineExperimentKeySystemAndSupported(aKeySystem) ||
IsWidevineKeySystem(aKeySystem)) {
return sWidevineL1Path ? sWidevineL1Path : L"L1-not-found";
}
return L"Unknown";
}
/* static */
void MFCDMParent::Shutdown() {
StaticMutexAutoLock lock(sCapabilitesMutex);
sFactoryMap.Clear();
sCapabilities.Clear();
sMediaEngineClassFactory.Reset();
}
/* static */
HRESULT MFCDMParent::GetOrCreateFactory(
const nsString& aKeySystem,
ComPtr<IMFContentDecryptionModuleFactory>& aFactoryOut) {
StaticMutexAutoLock lock(sFactoryMutex);
auto rv = sFactoryMap.MaybeGet(aKeySystem);
if (!rv) {
MFCDM_PARENT_SLOG("No factory %s, creating...",
NS_ConvertUTF16toUTF8(aKeySystem).get());
ComPtr<IMFContentDecryptionModuleFactory> factory;
MFCDM_RETURN_IF_FAILED(LoadFactory(aKeySystem, factory));
sFactoryMap.InsertOrUpdate(aKeySystem, factory);
aFactoryOut.Swap(factory);
} else {
aFactoryOut = *rv;
}
return S_OK;
}
/* static */
HRESULT MFCDMParent::LoadFactory(
const nsString& aKeySystem,
ComPtr<IMFContentDecryptionModuleFactory>& aFactoryOut) {
LPCWSTR libraryName = GetCDMLibraryName(aKeySystem);
const bool loadFromPlatform = wcslen(libraryName) == 0;
MFCDM_PARENT_SLOG("Load factory for %s (libraryName=%ls)",
NS_ConvertUTF16toUTF8(aKeySystem).get(), libraryName);
MFCDM_PARENT_SLOG("Create factory for %s",
NS_ConvertUTF16toUTF8(aKeySystem).get());
ComPtr<IMFContentDecryptionModuleFactory> cdmFactory;
if (loadFromPlatform) {
if (!sMediaEngineClassFactory) {
MFCDM_RETURN_IF_FAILED(CoCreateInstance(
CLSID_MFMediaEngineClassFactory, nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&sMediaEngineClassFactory)));
}
ComPtr<IMFMediaEngineClassFactory4> clsFactory;
MFCDM_RETURN_IF_FAILED(sMediaEngineClassFactory.As(&clsFactory));
MFCDM_RETURN_IF_FAILED(clsFactory->CreateContentDecryptionModuleFactory(
MapKeySystem(aKeySystem).get(), IID_PPV_ARGS(&cdmFactory)));
aFactoryOut.Swap(cdmFactory);
MFCDM_PARENT_SLOG("Created factory for %s from platform!",
NS_ConvertUTF16toUTF8(aKeySystem).get());
return S_OK;
}
HMODULE handle = LoadLibraryW(libraryName);
if (!handle) {
MFCDM_PARENT_SLOG("Failed to load library %ls! (error=%lx)", libraryName,
GetLastError());
return E_FAIL;
}
MFCDM_PARENT_SLOG("Loaded external library '%ls'", libraryName);
using DllGetActivationFactoryFunc =
HRESULT(WINAPI*)(_In_ HSTRING, _COM_Outptr_ IActivationFactory**);
DllGetActivationFactoryFunc pDllGetActivationFactory =
(DllGetActivationFactoryFunc)GetProcAddress(handle,
"DllGetActivationFactory");
if (!pDllGetActivationFactory) {
MFCDM_PARENT_SLOG("Failed to get activation function!");
return E_FAIL;
}
// The follow classID format is what Widevine's DLL expects
// "<key_system>.ContentDecryptionModuleFactory". In addition, when querying
// factory, need to use original Widevine key system name.
nsString stringId;
if (StaticPrefs::media_eme_wmf_use_mock_cdm_for_external_cdms() ||
IsWMFClearKeySystemAndSupported(aKeySystem)) {
stringId.AppendLiteral("org.w3.clearkey");
} else if (IsWidevineExperimentKeySystemAndSupported(aKeySystem) ||
IsWidevineKeySystem(aKeySystem)) {
// Widevine's DLL expects "<key_system>.ContentDecryptionModuleFactory" for
// the class Id.
stringId.AppendLiteral("com.widevine.alpha.ContentDecryptionModuleFactory");
}
MFCDM_PARENT_SLOG("Query factory by classId '%s'",
NS_ConvertUTF16toUTF8(stringId).get());
ScopedHString classId(stringId);
ComPtr<IActivationFactory> pFactory = NULL;
MFCDM_RETURN_IF_FAILED(
pDllGetActivationFactory(classId.Get(), pFactory.GetAddressOf()));
ComPtr<IInspectable> pInspectable;
MFCDM_RETURN_IF_FAILED(pFactory->ActivateInstance(&pInspectable));
MFCDM_RETURN_IF_FAILED(pInspectable.As(&cdmFactory));
aFactoryOut.Swap(cdmFactory);
MFCDM_PARENT_SLOG("Created factory for %s from external library!",
NS_ConvertUTF16toUTF8(aKeySystem).get());
return S_OK;
}
static nsString GetRobustnessStringForKeySystem(const nsString& aKeySystem,
const bool aIsHWSecure,
const bool aIsVideo = true) {
if (IsPlayReadyKeySystemAndSupported(aKeySystem)) {
// Audio doesn't support SL3000.
return aIsHWSecure && aIsVideo ? nsString(u"3000") : nsString(u"2000");
}
if (IsWidevineExperimentKeySystemAndSupported(aKeySystem)) {
return aIsHWSecure ? nsString(u"HW_SECURE_ALL")
: nsString(u"SW_SECURE_DECODE");
}
return nsString(u"");
}
// Use IMFContentDecryptionModuleFactory::IsTypeSupported() to get DRM
// capabilities. The query string is based on following, they are pretty much
// equivalent.
static bool FactorySupports(ComPtr<IMFContentDecryptionModuleFactory>& aFactory,
const nsString& aKeySystem,
const nsCString& aVideoCodec,
const nsCString& aAudioCodec = nsCString(""),
const nsString& aAdditionalFeatures = nsString(u""),
bool aIsHWSecure = false) {
// Create query string, MP4 is the only container supported.
nsString contentType(u"video/mp4;codecs=\"");
MOZ_ASSERT(!aVideoCodec.IsEmpty());
contentType.AppendASCII(aVideoCodec);
if (!aAudioCodec.IsEmpty()) {
contentType.AppendLiteral(u",");
contentType.AppendASCII(aAudioCodec);
}
contentType.AppendLiteral(u"\";features=\"");
if (IsWidevineExperimentKeySystemAndSupported(aKeySystem) ||
IsWidevineKeySystem(aKeySystem)) {
// This decoder subsystem settings are only required by Wivevine.
contentType.AppendLiteral(
u"decode-bpc=8,"
"decode-res-x=1920,decode-res-y=1080,"
"decode-bitrate=10000000,decode-fps=30,");
// `encryption-robustness` is for Widevine only.
if (aIsHWSecure) {
contentType.AppendLiteral(u"encryption-robustness=HW_SECURE_ALL,");
} else {
contentType.AppendLiteral(u"encryption-robustness=SW_SECURE_DECODE,");
}
}
if (!aAdditionalFeatures.IsEmpty()) {
contentType.Append(aAdditionalFeatures);
}
contentType.AppendLiteral(u"\"");
// End of the query string
// PlayReady doesn't implement IsTypeSupported properly, so it requires us to
// use another way to check the capabilities.
if (IsPlayReadyKeySystemAndSupported(aKeySystem) &&
StaticPrefs::media_eme_playready_istypesupportedex()) {
ComPtr<IMFExtendedDRMTypeSupport> spDrmTypeSupport;
MFCDM_RETURN_BOOL_IF_FAILED(sMediaEngineClassFactory.As(&spDrmTypeSupport));
BSTR keySystem = aIsHWSecure
? CreateBSTRFromConstChar(kPlayReadyKeySystemHardware)
: CreateBSTRFromConstChar(kPlayReadyKeySystemName);
MF_MEDIA_ENGINE_CANPLAY canPlay;
spDrmTypeSupport->IsTypeSupportedEx(SysAllocString(contentType.get()),
keySystem, &canPlay);
const bool support =
canPlay !=
MF_MEDIA_ENGINE_CANPLAY::MF_MEDIA_ENGINE_CANPLAY_NOT_SUPPORTED;
MFCDM_PARENT_SLOG("IsTypeSupportedEx=%d (key-system=%ls, content-type=%s)",
support, keySystem,
NS_ConvertUTF16toUTF8(contentType).get());
return support;
}
// Checking capabilies from CDM's IsTypeSupported. Widevine implements this
// method well.
bool support = IsTypeSupported(aFactory, aKeySystem, &contentType);
MFCDM_PARENT_SLOG("IsTypeSupport=%d (key-system=%s, content-type=%s)",
support, NS_ConvertUTF16toUTF8(aKeySystem).get(),
NS_ConvertUTF16toUTF8(contentType).get());
return support;
}
static nsresult IsHDCPVersionSupported(
ComPtr<IMFContentDecryptionModuleFactory>& aFactory,
const nsString& aKeySystem, const dom::HDCPVersion& aMinHdcpVersion) {
nsresult rv = NS_OK;
// Codec doesn't matter when querying the HDCP policy, so use H264.
if (!FactorySupports(aFactory, aKeySystem, nsCString("avc1"),
KeySystemConfig::EMECodecString(""),
GetHdcpPolicy(aMinHdcpVersion))) {
rv = NS_ERROR_DOM_MEDIA_CDM_HDCP_NOT_SUPPORT;
}
return rv;
}
static bool IsKeySystemHWSecure(
const nsAString& aKeySystem,
const nsTArray<MFCDMMediaCapability>& aCapabilities) {
if (IsPlayReadyKeySystemAndSupported(aKeySystem)) {
if (aKeySystem.EqualsLiteral(kPlayReadyKeySystemHardware) ||
aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) {
return true;
}
for (const auto& capabilities : aCapabilities) {
if (capabilities.robustness().EqualsLiteral("3000")) {
return true;
}
}
}
if (IsWidevineExperimentKeySystemAndSupported(aKeySystem) ||
IsWidevineKeySystem(aKeySystem)) {
// We only support Widevine HWDRM.
return true;
}
return false;
}
/* static */
RefPtr<MFCDMParent::CapabilitiesPromise>
MFCDMParent::GetAllKeySystemsCapabilities() {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsISerialEventTarget> backgroundTaskQueue;
if (NS_FAILED(NS_CreateBackgroundTaskQueue(
__func__, getter_AddRefs(backgroundTaskQueue)))) {
MFCDM_PARENT_SLOG(
"Failed to create task queue for all key systems capabilities!");
return CapabilitiesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
__func__);
}
RefPtr<CapabilitiesPromise::Private> p =
new CapabilitiesPromise::Private(__func__);
Unused << backgroundTaskQueue->Dispatch(NS_NewRunnableFunction(__func__, [p] {
MFCDM_PARENT_SLOG("GetAllKeySystemsCapabilities");
enum SecureLevel : bool {
Software = false,
Hardware = true,
};
const nsTArray<std::pair<nsString, SecureLevel>> kKeySystems{
std::pair<nsString, SecureLevel>(
NS_ConvertUTF8toUTF16(kPlayReadyKeySystemName),
SecureLevel::Software),
std::pair<nsString, SecureLevel>(
NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware),
SecureLevel::Hardware),
std::pair<nsString, SecureLevel>(
NS_ConvertUTF8toUTF16(kPlayReadyHardwareClearLeadKeySystemName),
SecureLevel::Hardware),
std::pair<nsString, SecureLevel>(
NS_ConvertUTF8toUTF16(kWidevineExperimentKeySystemName),
SecureLevel::Hardware),
std::pair<nsString, SecureLevel>(
NS_ConvertUTF8toUTF16(kWidevineExperiment2KeySystemName),
SecureLevel::Hardware),
};
CopyableTArray<MFCDMCapabilitiesIPDL> capabilitiesArr;
for (const auto& keySystem : kKeySystems) {
// Only check the capabilites if the relative prefs for the key system
// are ON.
if (IsPlayReadyKeySystemAndSupported(keySystem.first) ||
IsWidevineExperimentKeySystemAndSupported(keySystem.first)) {
MFCDMCapabilitiesIPDL* c = capabilitiesArr.AppendElement();
CapabilitesFlagSet flags;
if (keySystem.second == SecureLevel::Hardware) {
flags += CapabilitesFlag::HarewareDecryption;
}
flags += CapabilitesFlag::NeedHDCPCheck;
if (RequireClearLead(keySystem.first)) {
flags += CapabilitesFlag::NeedClearLeadCheck;
}
GetCapabilities(keySystem.first, flags, nullptr, *c);
}
}
p->Resolve(std::move(capabilitiesArr), __func__);
}));
return p;
}
/* static */
void MFCDMParent::GetCapabilities(const nsString& aKeySystem,
const CapabilitesFlagSet& aFlags,
IMFContentDecryptionModuleFactory* aFactory,
MFCDMCapabilitiesIPDL& aCapabilitiesOut) {
aCapabilitiesOut.keySystem() = aKeySystem;
// WMF CDMs usually require these. See
aCapabilitiesOut.persistentState() = KeySystemConfig::Requirement::Required;
aCapabilitiesOut.distinctiveID() = KeySystemConfig::Requirement::Required;
const bool isHardwareDecryption =
aFlags.contains(CapabilitesFlag::HarewareDecryption);
aCapabilitiesOut.isHardwareDecryption() = isHardwareDecryption;
// Return empty capabilites for SWDRM on Windows 10 because it has the process
// leaking problem.
if (!IsWin11OrLater() && !isHardwareDecryption) {
return;
}
// MFCDM requires persistent storage, and can't use in-memory storage, it
// can't be used in private browsing.
if (aFlags.contains(CapabilitesFlag::IsPrivateBrowsing)) {
return;
}
ComPtr<IMFContentDecryptionModuleFactory> factory = aFactory;
if (!factory) {
RETURN_VOID_IF_FAILED(GetOrCreateFactory(aKeySystem, factory));
}
StaticMutexAutoLock lock(sCapabilitesMutex);
for (auto& capabilities : sCapabilities) {
if (capabilities.keySystem().Equals(aKeySystem) &&
capabilities.isHardwareDecryption() == isHardwareDecryption) {
MFCDM_PARENT_SLOG(
"Return cached capabilities for %s (hardwareDecryption=%d)",
NS_ConvertUTF16toUTF8(aKeySystem).get(), isHardwareDecryption);
if (capabilities.isHDCP22Compatible().isNothing() &&
aFlags.contains(CapabilitesFlag::NeedHDCPCheck)) {
const bool rv = IsHDCPVersionSupported(factory, aKeySystem,
dom::HDCPVersion::_2_2) == NS_OK;
MFCDM_PARENT_SLOG(
"Check HDCP 2.2 compatible (%d) for the cached capabilites", rv);
capabilities.isHDCP22Compatible() = Some(rv);
}
aCapabilitiesOut = capabilities;
return;
}
}
MFCDM_PARENT_SLOG(
"Query capabilities for %s from the factory (hardwareDecryption=%d)",
NS_ConvertUTF16toUTF8(aKeySystem).get(), isHardwareDecryption);
// Widevine requires codec type to be four CC, PlayReady is fine with both.
static auto convertCodecToFourCC =
[](const KeySystemConfig::EMECodecString& aCodec) {
if (aCodec.Equals(KeySystemConfig::EME_CODEC_H264)) {
return "avc1"_ns;
}
if (aCodec.Equals(KeySystemConfig::EME_CODEC_VP8)) {
return "vp80"_ns;
}
if (aCodec.Equals(KeySystemConfig::EME_CODEC_VP9)) {
return "vp09"_ns;
}
if (aCodec.Equals(KeySystemConfig::EME_CODEC_HEVC)) {
return "hev1"_ns;
}
if (aCodec.Equals(KeySystemConfig::EME_CODEC_AV1)) {
return "av01"_ns;
}
if (aCodec.Equals(KeySystemConfig::EME_CODEC_AAC)) {
return "mp4a"_ns;
}
if (aCodec.Equals(KeySystemConfig::EME_CODEC_OPUS)) {
return "Opus"_ns;
}
if (aCodec.Equals(KeySystemConfig::EME_CODEC_VORBIS)) {
return "vrbs"_ns;
}
if (aCodec.Equals(KeySystemConfig::EME_CODEC_FLAC)) {
return "fLaC"_ns;
}
MOZ_ASSERT_UNREACHABLE("Unsupported codec");
return "none"_ns;
};
static nsTArray<KeySystemConfig::EMECodecString> kVideoCodecs(
{KeySystemConfig::EME_CODEC_H264, KeySystemConfig::EME_CODEC_VP8,
KeySystemConfig::EME_CODEC_VP9, KeySystemConfig::EME_CODEC_HEVC,
KeySystemConfig::EME_CODEC_AV1});
// Collect schemes supported by all video codecs.
static nsTArray<CryptoScheme> kSchemes({
CryptoScheme::Cenc,
CryptoScheme::Cbcs,
});
// Remember supported video codecs, which will be used when collecting audio
// codec support.
nsTArray<KeySystemConfig::EMECodecString> supportedVideoCodecs;
if (aFlags.contains(CapabilitesFlag::NeedClearLeadCheck)) {
for (const auto& codec : kVideoCodecs) {
if (codec == KeySystemConfig::EME_CODEC_HEVC &&
!StaticPrefs::media_wmf_hevc_enabled()) {
continue;
}
CryptoSchemeSet supportedScheme;
for (const auto& scheme : kSchemes) {
nsAutoString additionalFeature(u"encryption-type=");
// If we don't specify 'encryption-iv-size', it would use 8 bytes IV as
// default [1]. If it's not supported, then we will try 16 bytes later.
// Since PlayReady 4.0 [2], 8 and 16 bytes IV are both supported. But
// We're not sure if Widevine supports both or not.
// [1]
// [2]
if (scheme == CryptoScheme::Cenc) {
additionalFeature.AppendLiteral(u"cenc-clearlead,");
} else {
additionalFeature.AppendLiteral(u"cbcs-clearlead,");
}
bool rv = FactorySupports(factory, aKeySystem,
convertCodecToFourCC(codec), nsCString(""),
additionalFeature, isHardwareDecryption);
MFCDM_PARENT_SLOG("clearlead %s IV 8 bytes %s %s",
EnumValueToString(scheme), codec.get(),
rv ? "supported" : "not supported");
if (rv) {
supportedScheme += scheme;
break;
}
// Try 16 bytes IV.
additionalFeature.AppendLiteral(u"encryption-iv-size=16,");
rv = FactorySupports(factory, aKeySystem, convertCodecToFourCC(codec),
nsCString(""), additionalFeature,
isHardwareDecryption);
MFCDM_PARENT_SLOG("clearlead %s IV 16 bytes %s %s",
EnumValueToString(scheme), codec.get(),
rv ? "supported" : "not supported");
if (rv) {
supportedScheme += scheme;
break;
}
}
// Add a capability if supported scheme exists
if (!supportedScheme.isEmpty()) {
MFCDMMediaCapability* c =
aCapabilitiesOut.videoCapabilities().AppendElement();
c->contentType() = NS_ConvertUTF8toUTF16(codec);
c->robustness() =
GetRobustnessStringForKeySystem(aKeySystem, isHardwareDecryption);
if (supportedScheme.contains(CryptoScheme::Cenc)) {
c->encryptionSchemes().AppendElement(CryptoScheme::Cenc);
MFCDM_PARENT_SLOG("%s: +video:%s (cenc)", __func__, codec.get());
}
if (supportedScheme.contains(CryptoScheme::Cbcs)) {
c->encryptionSchemes().AppendElement(CryptoScheme::Cbcs);
MFCDM_PARENT_SLOG("%s: +video:%s (cbcs)", __func__, codec.get());
}
supportedVideoCodecs.AppendElement(codec);
}
}
} else {
// Non clearlead situation for video codecs
for (const auto& codec : kVideoCodecs) {
if (codec == KeySystemConfig::EME_CODEC_HEVC &&
!StaticPrefs::media_wmf_hevc_enabled()) {
continue;
}
if (FactorySupports(factory, aKeySystem, convertCodecToFourCC(codec),
KeySystemConfig::EMECodecString(""), nsString(u""),
isHardwareDecryption)) {
MFCDMMediaCapability* c =
aCapabilitiesOut.videoCapabilities().AppendElement();
c->contentType() = NS_ConvertUTF8toUTF16(codec);
c->robustness() =
GetRobustnessStringForKeySystem(aKeySystem, isHardwareDecryption);
// 'If value is unspecified, default value of "cenc" is used.' See
c->encryptionSchemes().AppendElement(CryptoScheme::Cenc);
MFCDM_PARENT_SLOG("%s: +video:%s (cenc)", __func__, codec.get());
// Check cbcs scheme support
if (FactorySupports(
factory, aKeySystem, convertCodecToFourCC(codec),
KeySystemConfig::EMECodecString(""),
nsString(u"encryption-type=cbcs,encryption-iv-size=16,"),
isHardwareDecryption)) {
c->encryptionSchemes().AppendElement(CryptoScheme::Cbcs);
MFCDM_PARENT_SLOG("%s: +video:%s (cbcs)", __func__, codec.get());
}
supportedVideoCodecs.AppendElement(codec);
}
}
}
if (supportedVideoCodecs.IsEmpty()) {
// Return a capabilities with no codec supported.
return;
}
static nsTArray<KeySystemConfig::EMECodecString> kAudioCodecs({
KeySystemConfig::EME_CODEC_AAC,
KeySystemConfig::EME_CODEC_FLAC,
KeySystemConfig::EME_CODEC_OPUS,
KeySystemConfig::EME_CODEC_VORBIS,
});
for (const auto& codec : kAudioCodecs) {
// Hardware decryption is usually only used for video, so we can just check
// the software capabilities for audio in order to save some time. As the
// media foundation would create a new D3D device everytime when we check
// hardware decryption, which takes way longer time.
if (FactorySupports(factory, aKeySystem,
convertCodecToFourCC(supportedVideoCodecs[0]),
convertCodecToFourCC(codec), nsString(u""),
false /* aIsHWSecure */)) {
MFCDMMediaCapability* c =
aCapabilitiesOut.audioCapabilities().AppendElement();
c->contentType() = NS_ConvertUTF8toUTF16(codec);
c->robustness() = GetRobustnessStringForKeySystem(
aKeySystem, false /* aIsHWSecure */, false /* isVideo */);
c->encryptionSchemes().AppendElement(CryptoScheme::Cenc);
MFCDM_PARENT_SLOG("%s: +audio:%s", __func__, codec.get());
}
}
// Only perform HDCP if necessary, "The hdcp query (item 4) has a
// computationally expensive first invocation cost". See
if (aFlags.contains(CapabilitesFlag::NeedHDCPCheck) &&
IsHDCPVersionSupported(factory, aKeySystem, dom::HDCPVersion::_2_2) ==
NS_OK) {
MFCDM_PARENT_SLOG("Capabilites is compatible with HDCP 2.2");
aCapabilitiesOut.isHDCP22Compatible() = Some(true);
}
// TODO: don't hardcode
aCapabilitiesOut.initDataTypes().AppendElement(u"keyids");
aCapabilitiesOut.initDataTypes().AppendElement(u"cenc");
aCapabilitiesOut.sessionTypes().AppendElement(
KeySystemConfig::SessionType::Temporary);
aCapabilitiesOut.sessionTypes().AppendElement(
KeySystemConfig::SessionType::PersistentLicense);
// Cache capabilities for reuse.
sCapabilities.AppendElement(aCapabilitiesOut);
}
mozilla::ipc::IPCResult MFCDMParent::RecvGetCapabilities(
const MFCDMCapabilitiesRequest& aRequest,
GetCapabilitiesResolver&& aResolver) {
MFCDM_REJECT_IF(!mFactory, NS_ERROR_DOM_NOT_SUPPORTED_ERR);
MFCDMCapabilitiesIPDL capabilities;
CapabilitesFlagSet flags;
if (aRequest.isHardwareDecryption()) {
flags += CapabilitesFlag::HarewareDecryption;
}
if (RequireClearLead(aRequest.keySystem())) {
flags += CapabilitesFlag::NeedClearLeadCheck;
}
if (aRequest.isPrivateBrowsing()) {
flags += CapabilitesFlag::IsPrivateBrowsing;
}
GetCapabilities(aRequest.keySystem(), flags, mFactory.Get(), capabilities);
aResolver(std::move(capabilities));
return IPC_OK();
}
mozilla::ipc::IPCResult MFCDMParent::RecvInit(
const MFCDMInitParamsIPDL& aParams, InitResolver&& aResolver) {
static auto RequirementToStr = [](KeySystemConfig::Requirement aRequirement) {
switch (aRequirement) {
case KeySystemConfig::Requirement::Required:
return "Required";
case KeySystemConfig::Requirement::Optional:
return "Optional";
default:
return "NotAllowed";
}
};
MFCDM_PARENT_LOG(
"Creating a CDM (key-system=%s, origin=%s, distinctiveID=%s, "
"persistentState=%s, "
"hwSecure=%d)",
NS_ConvertUTF16toUTF8(mKeySystem).get(),
NS_ConvertUTF16toUTF8(aParams.origin()).get(),
RequirementToStr(aParams.distinctiveID()),
RequirementToStr(aParams.persistentState()),
IsKeySystemHWSecure(mKeySystem, aParams.videoCapabilities()));
MFCDM_REJECT_IF(!mFactory, NS_ERROR_DOM_NOT_SUPPORTED_ERR);
MOZ_ASSERT(IsTypeSupported(mFactory, mKeySystem));
MFCDM_REJECT_IF_FAILED(CreateContentDecryptionModule(
mFactory, MapKeySystem(mKeySystem), aParams, mCDM),
NS_ERROR_FAILURE);
MOZ_ASSERT(mCDM);
MFCDM_PARENT_LOG("Created a CDM!");
// This is only required by PlayReady.
if (IsPlayReadyKeySystemAndSupported(mKeySystem)) {
ComPtr<IMFPMPHost> pmpHost;
ComPtr<IMFGetService> cdmService;
MFCDM_REJECT_IF_FAILED(mCDM.As(&cdmService), NS_ERROR_FAILURE);
MFCDM_REJECT_IF_FAILED(
cdmService->GetService(MF_CONTENTDECRYPTIONMODULE_SERVICE,
IID_PPV_ARGS(&pmpHost)),
NS_ERROR_FAILURE);
MFCDM_REJECT_IF_FAILED(SUCCEEDED(MakeAndInitialize<MFPMPHostWrapper>(
&mPMPHostWrapper, pmpHost)),
NS_ERROR_FAILURE);
MFCDM_REJECT_IF_FAILED(mCDM->SetPMPHostApp(mPMPHostWrapper.Get()),
NS_ERROR_FAILURE);
MFCDM_PARENT_LOG("Set PMPHostWrapper on CDM!");
}
aResolver(MFCDMInitIPDL{mId});
return IPC_OK();
}
mozilla::ipc::IPCResult MFCDMParent::RecvCreateSessionAndGenerateRequest(
const MFCDMCreateSessionParamsIPDL& aParams,
CreateSessionAndGenerateRequestResolver&& aResolver) {
MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call");
static auto SessionTypeToStr = [](KeySystemConfig::SessionType aSessionType) {
switch (aSessionType) {
case KeySystemConfig::SessionType::Temporary:
return "temporary";
case KeySystemConfig::SessionType::PersistentLicense:
return "persistent-license";
default: {
MOZ_ASSERT_UNREACHABLE("Unsupported license type!");
return "invalid";
}
}
};
MFCDM_PARENT_LOG("Creating session for type '%s'",
SessionTypeToStr(aParams.sessionType()));
UniquePtr<MFCDMSession> session{
MFCDMSession::Create(aParams.sessionType(), mCDM.Get(), mManagerThread)};
if (!session) {
MFCDM_PARENT_LOG("Failed to create CDM session");
aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR);
return IPC_OK();
}
MFCDM_REJECT_IF_FAILED(session->GenerateRequest(aParams.initDataType(),
aParams.initData().Elements(),
aParams.initData().Length()),
NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR);
ConnectSessionEvents(session.get());
// TODO : now we assume all session ID is available after session is
// created, but this is not always true. Need to remove this assertion and
// handle cases where session Id is not available yet.
const auto& sessionId = session->SessionID();
MOZ_ASSERT(sessionId);
mSessions.emplace(*sessionId, std::move(session));
MFCDM_PARENT_LOG("Created a CDM session!");
aResolver(*sessionId);
return IPC_OK();
}
mozilla::ipc::IPCResult MFCDMParent::RecvLoadSession(
const KeySystemConfig::SessionType& aSessionType,
const nsString& aSessionId, LoadSessionResolver&& aResolver) {
MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call");
nsresult rv = NS_OK;
auto* session = GetSession(aSessionId);
if (!session) {
aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR);
return IPC_OK();
}
MFCDM_REJECT_IF_FAILED(session->Load(aSessionId),
NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR);
aResolver(rv);
return IPC_OK();
}
mozilla::ipc::IPCResult MFCDMParent::RecvUpdateSession(
const nsString& aSessionId, const CopyableTArray<uint8_t>& aResponse,
UpdateSessionResolver&& aResolver) {
MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call");
nsresult rv = NS_OK;
auto* session = GetSession(aSessionId);
if (!session) {
aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR);
return IPC_OK();
}
MFCDM_REJECT_IF_FAILED(session->Update(aResponse),
NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR);
aResolver(rv);
return IPC_OK();
}
mozilla::ipc::IPCResult MFCDMParent::RecvCloseSession(
const nsString& aSessionId, UpdateSessionResolver&& aResolver) {
MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call");
nsresult rv = NS_OK;
auto* session = GetSession(aSessionId);
if (!session) {
aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR);
return IPC_OK();
}
MFCDM_REJECT_IF_FAILED(session->Close(),
NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR);
aResolver(rv);
return IPC_OK();
}
mozilla::ipc::IPCResult MFCDMParent::RecvRemoveSession(
const nsString& aSessionId, UpdateSessionResolver&& aResolver) {
MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call");
nsresult rv = NS_OK;
auto* session = GetSession(aSessionId);
if (!session) {
aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR);
return IPC_OK();
}
MFCDM_REJECT_IF_FAILED(session->Remove(),
NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR);
aResolver(rv);
return IPC_OK();
}
mozilla::ipc::IPCResult MFCDMParent::RecvSetServerCertificate(
const CopyableTArray<uint8_t>& aCertificate,
UpdateSessionResolver&& aResolver) {
MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call");
nsresult rv = NS_OK;
MFCDM_PARENT_LOG("Set server certificate");
MFCDM_REJECT_IF_FAILED(mCDM->SetServerCertificate(
static_cast<const BYTE*>(aCertificate.Elements()),
aCertificate.Length()),
NS_ERROR_DOM_MEDIA_CDM_ERR);
aResolver(rv);
return IPC_OK();
}
mozilla::ipc::IPCResult MFCDMParent::RecvGetStatusForPolicy(
const dom::HDCPVersion& aMinHdcpVersion,
GetStatusForPolicyResolver&& aResolver) {
MOZ_ASSERT(mCDM, "RecvInit() must be called and waited on before this call");
aResolver(IsHDCPVersionSupported(mFactory, mKeySystem, aMinHdcpVersion));
return IPC_OK();
}
void MFCDMParent::ConnectSessionEvents(MFCDMSession* aSession) {
// TODO : clear session's event source when the session gets removed.
mKeyMessageEvents.Forward(aSession->KeyMessageEvent());
mKeyChangeEvents.Forward(aSession->KeyChangeEvent());
mExpirationEvents.Forward(aSession->ExpirationEvent());
}
MFCDMSession* MFCDMParent::GetSession(const nsString& aSessionId) {
AssertOnManagerThread();
auto iter = mSessions.find(aSessionId);
if (iter == mSessions.end()) {
return nullptr;
}
return iter->second.get();
}
already_AddRefed<MFCDMProxy> MFCDMParent::GetMFCDMProxy() {
if (!mCDM) {
return nullptr;
}
RefPtr<MFCDMProxy> proxy = new MFCDMProxy(mCDM.Get(), mId);
return proxy.forget();
}
/* static */
void MFCDMService::GetAllKeySystemsCapabilities(dom::Promise* aPromise) {
MOZ_ASSERT(XRE_IsParentProcess());
const static auto kSandboxKind = ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM;
LaunchMFCDMProcessIfNeeded(kSandboxKind)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[promise = RefPtr(aPromise)]() {
RefPtr<ipc::UtilityAudioDecoderChild> uadc =
ipc::UtilityAudioDecoderChild::GetSingleton(kSandboxKind);
if (NS_WARN_IF(!uadc)) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
uadc->GetKeySystemCapabilities(promise);
},
[promise = RefPtr(aPromise)](nsresult aError) {
promise->MaybeReject(NS_ERROR_FAILURE);
});
}
/* static */
RefPtr<GenericNonExclusivePromise> MFCDMService::LaunchMFCDMProcessIfNeeded(
ipc::SandboxingKind aSandbox) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(aSandbox == ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM);
RefPtr<ipc::UtilityProcessManager> utilityProc =
ipc::UtilityProcessManager::GetSingleton();
if (NS_WARN_IF(!utilityProc)) {
NS_WARNING("Failed to get UtilityProcessManager");
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
// Check if the MFCDM process exists or not. If not, launch it.
if (utilityProc->Process(aSandbox)) {
return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
}
RefPtr<ipc::UtilityAudioDecoderChild> uadc =
ipc::UtilityAudioDecoderChild::GetSingleton(aSandbox);
if (NS_WARN_IF(!uadc)) {
NS_WARNING("Failed to get UtilityAudioDecoderChild");
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
return utilityProc->StartUtility(uadc, aSandbox)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[uadc, utilityProc, aSandbox]() {
RefPtr<ipc::UtilityProcessParent> parent =
utilityProc->GetProcessParent(aSandbox);
if (!parent) {
NS_WARNING("UtilityAudioDecoderParent lost in the middle");
return GenericNonExclusivePromise::CreateAndReject(
NS_ERROR_FAILURE, __func__);
}
if (!uadc->CanSend()) {
NS_WARNING("UtilityAudioDecoderChild lost in the middle");
return GenericNonExclusivePromise::CreateAndReject(
NS_ERROR_FAILURE, __func__);
}
return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
},
[](ipc::LaunchError const& aError) {
NS_WARNING("Failed to start the MFCDM process!");
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
});
}
/* static */
void MFCDMService::UpdateWidevineL1Path(nsIFile* aFile) {
RefPtr<ipc::UtilityProcessManager> utilityProc =
ipc::UtilityProcessManager::GetSingleton();
if (NS_WARN_IF(!utilityProc)) {
NS_WARNING("Failed to get UtilityProcessManager");
return;
}
// If the MFCDM process hasn't been created yet, then we will set the path
// when creating the process later.
const auto sandboxKind = ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM;
if (!utilityProc->Process(sandboxKind)) {
return;
}
// The MFCDM process has been started, we need to update its L1 path and set
// the permission for LPAC.
nsString widevineL1Path;
MOZ_ASSERT(aFile);
if (NS_WARN_IF(NS_FAILED(aFile->GetTarget(widevineL1Path)))) {
NS_WARNING("MFCDMService::UpdateWidevineL1Path, Failed to get L1 path!");
return;
}
RefPtr<ipc::UtilityAudioDecoderChild> uadc =
ipc::UtilityAudioDecoderChild::GetSingleton(sandboxKind);
if (NS_WARN_IF(!uadc)) {
NS_WARNING("Failed to get UtilityAudioDecoderChild");
return;
}
Unused << uadc->SendUpdateWidevineL1Path(widevineL1Path);
#ifdef MOZ_WMF_CDM_LPAC_SANDBOX
SandboxBroker::EnsureLpacPermsissionsOnDir(widevineL1Path);
#endif
}
#undef MFCDM_REJECT_IF_FAILED
#undef MFCDM_REJECT_IF
#undef MFCDM_RETURN_IF_FAILED
#undef MFCDM_RETURN_BOOL_IF_FAILED
#undef MFCDM_PARENT_SLOG
#undef MFCDM_PARENT_LOG
} // namespace mozilla