MFCDMParent.cpp (64934B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "MFCDMParent.h" 6 7 #include <mfmediaengine.h> 8 #include <unknwnbase.h> 9 #include <wtypes.h> 10 11 #include "mozilla/Likely.h" 12 #define INITGUID // Enable DEFINE_PROPERTYKEY() 13 #include <prinrval.h> 14 #include <propkeydef.h> // For DEFINE_PROPERTYKEY() definition 15 #include <propvarutil.h> // For InitPropVariantFrom*() 16 17 #include "MFCDMProxy.h" 18 #include "MFMediaEngineUtils.h" 19 #include "RemoteDecodeUtils.h" // For GetCurrentSandboxingKind() 20 #include "SpecialSystemDirectory.h" // For temp dir 21 #include "WMFUtils.h" 22 #include "mozilla/DataMutex.h" 23 #include "mozilla/EMEUtils.h" 24 #include "mozilla/KeySystemConfig.h" 25 #include "mozilla/ProfilerMarkers.h" 26 #include "mozilla/RandomNum.h" 27 #include "mozilla/StaticMutex.h" 28 #include "mozilla/StaticPrefs_media.h" 29 #include "mozilla/WindowsVersion.h" 30 #include "mozilla/dom/KeySystemNames.h" 31 #include "mozilla/dom/MediaKeysBinding.h" 32 #include "mozilla/dom/Promise.h" 33 #include "mozilla/gfx/gfxVars.h" 34 #include "mozilla/ipc/UtilityMediaServiceChild.h" 35 #include "mozilla/ipc/UtilityProcessManager.h" 36 #include "mozilla/ipc/UtilityProcessParent.h" 37 #include "nsTHashMap.h" 38 #include "nsTextFormatter.h" 39 40 #ifdef MOZ_WMF_CDM_LPAC_SANDBOX 41 # include "sandboxBroker.h" 42 #endif 43 44 using Microsoft::WRL::ComPtr; 45 using Microsoft::WRL::MakeAndInitialize; 46 47 namespace mozilla { 48 49 // See 50 // https://source.chromium.org/chromium/chromium/src/+/main:media/cdm/win/media_foundation_cdm_util.cc;l=26-40;drc=503535015a7b373cc6185c69c991e01fda5da571 51 #ifndef EME_CONTENTDECRYPTIONMODULE_ORIGIN_ID 52 DEFINE_PROPERTYKEY(EME_CONTENTDECRYPTIONMODULE_ORIGIN_ID, 0x1218a3e2, 0xcfb0, 53 0x4c98, 0x90, 0xe5, 0x5f, 0x58, 0x18, 0xd4, 0xb6, 0x7e, 54 PID_FIRST_USABLE); 55 #endif 56 57 #define MFCDM_PARENT_LOG(msg, ...) \ 58 EME_LOG("MFCDMParent[%p, Id=%" PRIu64 "]@%s: " msg, this, this->mId, \ 59 __func__, ##__VA_ARGS__) 60 #define MFCDM_PARENT_SLOG(msg, ...) \ 61 EME_LOG("MFCDMParent@%s: " msg, __func__, ##__VA_ARGS__) 62 63 #define MFCDM_RETURN_IF_FAILED(x) \ 64 do { \ 65 HRESULT rv = x; \ 66 if (MOZ_UNLIKELY(FAILED(rv))) { \ 67 MFCDM_PARENT_SLOG("(" #x ") failed, rv=%lx", rv); \ 68 return rv; \ 69 } \ 70 } while (false) 71 72 #define MFCDM_RETURN_BOOL_IF_FAILED(x) \ 73 do { \ 74 HRESULT rv = x; \ 75 if (MOZ_UNLIKELY(FAILED(rv))) { \ 76 MFCDM_PARENT_SLOG("(" #x ") failed, rv=%lx", rv); \ 77 return false; \ 78 } \ 79 } while (false) 80 81 #define MFCDM_REJECT_IF(pred, rv) \ 82 do { \ 83 if (MOZ_UNLIKELY(pred)) { \ 84 MFCDM_PARENT_LOG("reject for [" #pred "], rv=%x", uint32_t(rv)); \ 85 aResolver(rv); \ 86 return IPC_OK(); \ 87 } \ 88 } while (false) 89 90 #define MFCDM_REJECT_IF_FAILED(op, rv) \ 91 do { \ 92 HRESULT hr = op; \ 93 if (MOZ_UNLIKELY(FAILED(hr))) { \ 94 MFCDM_PARENT_LOG("(" #op ") failed(hr=%lx), rv=%x", hr, uint32_t(rv)); \ 95 aResolver(rv); \ 96 return IPC_OK(); \ 97 } \ 98 } while (false) 99 100 MOZ_RUNINIT static StaticDataMutex< 101 nsTHashMap<nsStringHashKey, ComPtr<IMFContentDecryptionModuleFactory>>> 102 sFactoryMap("sFactoryMap"); 103 MOZ_RUNINIT static StaticDataMutex<CopyableTArray<MFCDMCapabilitiesIPDL>> 104 sCapabilities("sCapabilities"); 105 MOZ_RUNINIT static StaticDataMutex<ComPtr<IUnknown>> sMediaEngineClassFactory( 106 "sMediaEngineClassFactory"); 107 108 #define ASSERT_CDM_ACCESS_READ_ONLY_ON_MANAGER_THREAD() \ 109 mCDMAccessLock.Target().AssertOnCurrentThread(); \ 110 mCDMAccessLock.NoteOnTarget(); 111 112 #define ASSERT_CDM_ACCESS_ON_MANAGER_THREAD() \ 113 mCDMAccessLock.Target().AssertOnCurrentThread(); 114 115 // Generate a dummy session ID for resolving the new session promise during 116 // GenerateRequest() when DRM_E_TEE_INVALID_HWDRM_STATE happens. 117 // An example of the generated session ID is DUMMY_9F656F4D76BE30D4. 118 static nsString GenerateDummySessionId() { 119 nsString sessionId; 120 sessionId.AppendLiteral(u"DUMMY_"); 121 char buf[17]; 122 uint64_t randomValue = mozilla::RandomUint64OrDie(); 123 SprintfLiteral(buf, "%016" PRIX64, randomValue); 124 sessionId.AppendASCII(buf); 125 return sessionId; 126 } 127 128 // RAIIized PROPVARIANT. See 129 // third_party/libwebrtc/modules/audio_device/win/core_audio_utility_win.h 130 class AutoPropVar { 131 public: 132 AutoPropVar() { PropVariantInit(&mVar); } 133 134 ~AutoPropVar() { Reset(); } 135 136 AutoPropVar(const AutoPropVar&) = delete; 137 AutoPropVar& operator=(const AutoPropVar&) = delete; 138 bool operator==(const AutoPropVar&) const = delete; 139 bool operator!=(const AutoPropVar&) const = delete; 140 141 // Returns a pointer to the underlying PROPVARIANT for use as an out param 142 // in a function call. 143 PROPVARIANT* Receive() { 144 MOZ_ASSERT(mVar.vt == VT_EMPTY); 145 return &mVar; 146 } 147 148 // Clears the instance to prepare it for re-use (e.g., via Receive). 149 void Reset() { 150 if (mVar.vt != VT_EMPTY) { 151 HRESULT hr = PropVariantClear(&mVar); 152 MOZ_ASSERT(SUCCEEDED(hr)); 153 (void)hr; 154 } 155 } 156 157 const PROPVARIANT& get() const { return mVar; } 158 const PROPVARIANT* ptr() const { return &mVar; } 159 160 private: 161 PROPVARIANT mVar; 162 }; 163 164 static MF_MEDIAKEYS_REQUIREMENT ToMFRequirement( 165 const KeySystemConfig::Requirement aRequirement) { 166 switch (aRequirement) { 167 case KeySystemConfig::Requirement::NotAllowed: 168 return MF_MEDIAKEYS_REQUIREMENT_NOT_ALLOWED; 169 case KeySystemConfig::Requirement::Optional: 170 return MF_MEDIAKEYS_REQUIREMENT_OPTIONAL; 171 case KeySystemConfig::Requirement::Required: 172 return MF_MEDIAKEYS_REQUIREMENT_REQUIRED; 173 } 174 }; 175 176 static inline LPCWSTR InitDataTypeToString(const nsAString& aInitDataType) { 177 // The strings are defined in https://www.w3.org/TR/eme-initdata-registry/ 178 if (aInitDataType.EqualsLiteral("webm")) { 179 return L"webm"; 180 } else if (aInitDataType.EqualsLiteral("cenc")) { 181 return L"cenc"; 182 } else if (aInitDataType.EqualsLiteral("keyids")) { 183 return L"keyids"; 184 } else { 185 return L"unknown"; 186 } 187 } 188 189 static bool RequireClearLead(const nsString& aKeySystem) { 190 return aKeySystem.EqualsLiteral(kWidevineExperiment2KeySystemName) || 191 aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName); 192 } 193 194 static void BuildCapabilitiesArray( 195 const nsTArray<MFCDMMediaCapability>& aCapabilities, 196 AutoPropVar& capabilitiesPropOut) { 197 PROPVARIANT* capabilitiesArray = (PROPVARIANT*)CoTaskMemAlloc( 198 sizeof(PROPVARIANT) * aCapabilities.Length()); 199 for (size_t idx = 0; idx < aCapabilities.Length(); idx++) { 200 ComPtr<IPropertyStore> capabilitiesProperty; 201 RETURN_VOID_IF_FAILED( 202 PSCreateMemoryPropertyStore(IID_PPV_ARGS(&capabilitiesProperty))); 203 204 AutoPropVar contentType; 205 auto* var = contentType.Receive(); 206 var->vt = VT_BSTR; 207 var->bstrVal = SysAllocString(aCapabilities[idx].contentType().get()); 208 RETURN_VOID_IF_FAILED( 209 capabilitiesProperty->SetValue(MF_EME_CONTENTTYPE, contentType.get())); 210 211 AutoPropVar robustness; 212 var = robustness.Receive(); 213 var->vt = VT_BSTR; 214 var->bstrVal = SysAllocString(aCapabilities[idx].robustness().get()); 215 RETURN_VOID_IF_FAILED( 216 capabilitiesProperty->SetValue(MF_EME_ROBUSTNESS, robustness.get())); 217 218 capabilitiesArray[idx].vt = VT_UNKNOWN; 219 capabilitiesArray[idx].punkVal = capabilitiesProperty.Detach(); 220 } 221 auto* var = capabilitiesPropOut.Receive(); 222 var->vt = VT_VARIANT | VT_VECTOR; 223 var->capropvar.cElems = aCapabilities.Length(); 224 var->capropvar.pElems = capabilitiesArray; 225 } 226 227 static HRESULT BuildCDMAccessConfig(const MFCDMInitParamsIPDL& aParams, 228 ComPtr<IPropertyStore>& aConfig) { 229 ComPtr<IPropertyStore> mksc; // EME MediaKeySystemConfiguration 230 MFCDM_RETURN_IF_FAILED(PSCreateMemoryPropertyStore(IID_PPV_ARGS(&mksc))); 231 232 // Init type. If we don't set `MF_EME_INITDATATYPES` then we won't be able 233 // to create CDM module on Windows 10, which is not documented officially. 234 BSTR* initDataTypeArray = 235 (BSTR*)CoTaskMemAlloc(sizeof(BSTR) * aParams.initDataTypes().Length()); 236 for (size_t i = 0; i < aParams.initDataTypes().Length(); i++) { 237 initDataTypeArray[i] = 238 SysAllocString(InitDataTypeToString(aParams.initDataTypes()[i])); 239 } 240 AutoPropVar initDataTypes; 241 PROPVARIANT* var = initDataTypes.Receive(); 242 var->vt = VT_VECTOR | VT_BSTR; 243 var->cabstr.cElems = static_cast<ULONG>(aParams.initDataTypes().Length()); 244 var->cabstr.pElems = initDataTypeArray; 245 MFCDM_RETURN_IF_FAILED( 246 mksc->SetValue(MF_EME_INITDATATYPES, initDataTypes.get())); 247 248 // Audio capabilities 249 AutoPropVar audioCapabilities; 250 BuildCapabilitiesArray(aParams.audioCapabilities(), audioCapabilities); 251 MFCDM_RETURN_IF_FAILED( 252 mksc->SetValue(MF_EME_AUDIOCAPABILITIES, audioCapabilities.get())); 253 254 // Video capabilities 255 AutoPropVar videoCapabilities; 256 BuildCapabilitiesArray(aParams.videoCapabilities(), videoCapabilities); 257 MFCDM_RETURN_IF_FAILED( 258 mksc->SetValue(MF_EME_VIDEOCAPABILITIES, videoCapabilities.get())); 259 260 // Persist state 261 AutoPropVar persistState; 262 InitPropVariantFromUInt32(ToMFRequirement(aParams.persistentState()), 263 persistState.Receive()); 264 MFCDM_RETURN_IF_FAILED( 265 mksc->SetValue(MF_EME_PERSISTEDSTATE, persistState.get())); 266 267 // Distintive Id 268 AutoPropVar distinctiveID; 269 InitPropVariantFromUInt32(ToMFRequirement(aParams.distinctiveID()), 270 distinctiveID.Receive()); 271 MFCDM_RETURN_IF_FAILED( 272 mksc->SetValue(MF_EME_DISTINCTIVEID, distinctiveID.get())); 273 274 aConfig.Swap(mksc); 275 return S_OK; 276 } 277 278 static HRESULT BuildCDMProperties(const nsString& aOrigin, 279 ComPtr<IPropertyStore>& aProps) { 280 MOZ_ASSERT(!aOrigin.IsEmpty()); 281 282 ComPtr<IPropertyStore> props; 283 MFCDM_RETURN_IF_FAILED(PSCreateMemoryPropertyStore(IID_PPV_ARGS(&props))); 284 285 AutoPropVar origin; 286 MFCDM_RETURN_IF_FAILED( 287 InitPropVariantFromString(aOrigin.get(), origin.Receive())); 288 MFCDM_RETURN_IF_FAILED( 289 props->SetValue(EME_CONTENTDECRYPTIONMODULE_ORIGIN_ID, origin.get())); 290 291 // TODO: support client token? 292 293 // TODO: CDM store path per profile? 294 nsCOMPtr<nsIFile> dir; 295 if (NS_FAILED(GetSpecialSystemDirectory(OS_TemporaryDirectory, 296 getter_AddRefs(dir)))) { 297 return E_ACCESSDENIED; 298 } 299 if (NS_FAILED(dir->AppendNative(nsDependentCString("mfcdm")))) { 300 return E_ACCESSDENIED; 301 } 302 nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700); 303 if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_FAILED(rv)) { 304 return E_ACCESSDENIED; 305 } 306 nsAutoString cdmStorePath; 307 if (NS_FAILED(dir->GetPath(cdmStorePath))) { 308 return E_ACCESSDENIED; 309 } 310 311 AutoPropVar path; 312 MFCDM_RETURN_IF_FAILED( 313 InitPropVariantFromString(cdmStorePath.get(), path.Receive())); 314 MFCDM_RETURN_IF_FAILED( 315 props->SetValue(MF_CONTENTDECRYPTIONMODULE_STOREPATH, path.get())); 316 317 aProps.Swap(props); 318 return S_OK; 319 } 320 321 static HRESULT CreateContentDecryptionModule( 322 ComPtr<IMFContentDecryptionModuleFactory> aFactory, 323 const nsString& aKeySystem, const MFCDMInitParamsIPDL& aParams, 324 ComPtr<IMFContentDecryptionModule>& aCDMOut) { 325 // Get access object to CDM. 326 ComPtr<IPropertyStore> accessConfig; 327 RETURN_IF_FAILED(BuildCDMAccessConfig(aParams, accessConfig)); 328 329 AutoTArray<IPropertyStore*, 1> configs = {accessConfig.Get()}; 330 ComPtr<IMFContentDecryptionModuleAccess> cdmAccess; 331 RETURN_IF_FAILED(aFactory->CreateContentDecryptionModuleAccess( 332 aKeySystem.get(), configs.Elements(), configs.Length(), &cdmAccess)); 333 334 // Get CDM. 335 ComPtr<IPropertyStore> cdmProps; 336 RETURN_IF_FAILED(BuildCDMProperties(aParams.origin(), cdmProps)); 337 ComPtr<IMFContentDecryptionModule> cdm; 338 RETURN_IF_FAILED( 339 cdmAccess->CreateContentDecryptionModule(cdmProps.Get(), &cdm)); 340 aCDMOut.Swap(cdm); 341 return S_OK; 342 } 343 344 // Wrapper function for IMFContentDecryptionModuleFactory::IsTypeSupported. 345 static bool IsTypeSupported( 346 const ComPtr<IMFContentDecryptionModuleFactory>& aFactory, 347 const nsString& aKeySystem, const nsString* aContentType = nullptr) { 348 nsString keySystem; 349 // Widevine's factory only takes original key system string. 350 if (IsWidevineExperimentKeySystemAndSupported(aKeySystem)) { 351 keySystem.AppendLiteral(u"com.widevine.alpha"); 352 } 353 // kPlayReadyHardwareClearLeadKeySystemName is our custom key system name, 354 // we should use kPlayReadyKeySystemHardware which is the real key system 355 // name. 356 else if (aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) { 357 keySystem.AppendLiteral(kPlayReadyKeySystemHardware); 358 } else { 359 keySystem = aKeySystem; 360 } 361 return aFactory->IsTypeSupported( 362 keySystem.get(), aContentType ? aContentType->get() : nullptr); 363 } 364 365 static nsString MapKeySystem(const nsString& aKeySystem) { 366 // When website requests HW secure robustness for video by original Widevine 367 // key system name, it would be mapped to this key system which is for HWDRM. 368 if (IsWidevineKeySystem(aKeySystem)) { 369 return nsString(u"com.widevine.alpha.experiment"); 370 } 371 // kPlayReadyHardwareClearLeadKeySystemName is our custom key system name, 372 // we should use kPlayReadyKeySystemHardware which is the real key system 373 // name. 374 if (aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) { 375 return NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware); 376 } 377 return aKeySystem; 378 } 379 380 static bool IsBeingProfiledOrLogEnabled() { 381 return MOZ_LOG_TEST(GetEMELog(), LogLevel::Info) || 382 profiler_thread_is_being_profiled_for_markers(); 383 } 384 385 /* static */ 386 void MFCDMParent::SetWidevineL1Path(const char* aPath) { 387 nsAutoCString path(aPath); 388 path.AppendLiteral("\\Google.Widevine.CDM.dll"); 389 sWidevineL1Path = CreateBSTRFromConstChar(path.get()); 390 MFCDM_PARENT_SLOG("Set Widevine L1 dll path=%ls\n", sWidevineL1Path); 391 } 392 393 void MFCDMParent::Register() { 394 MOZ_ASSERT(!sRegisteredCDMs.Contains(this->mId)); 395 sRegisteredCDMs.InsertOrUpdate(this->mId, this); 396 MFCDM_PARENT_LOG("Registered!"); 397 } 398 399 void MFCDMParent::Unregister() { 400 MOZ_ASSERT(sRegisteredCDMs.Contains(this->mId)); 401 sRegisteredCDMs.Remove(this->mId); 402 MFCDM_PARENT_LOG("Unregistered!"); 403 } 404 405 MFCDMParent::MFCDMParent(const nsAString& aKeySystem, 406 RemoteMediaManagerParent* aManager, 407 nsISerialEventTarget* aManagerThread) 408 : mKeySystem(aKeySystem), 409 mManager(aManager), 410 mManagerThread(aManagerThread), 411 mId(sNextId++), 412 mKeyMessageEvents(aManagerThread), 413 mKeyChangeEvents(aManagerThread), 414 mExpirationEvents(aManagerThread), 415 mClosedEvents(aManagerThread), 416 mCDMAccessLock("MFCDMParent", mManagerThread) { 417 MOZ_ASSERT(IsPlayReadyKeySystemAndSupported(aKeySystem) || 418 IsWidevineExperimentKeySystemAndSupported(aKeySystem) || 419 IsWidevineKeySystem(mKeySystem) || 420 IsWMFClearKeySystemAndSupported(aKeySystem)); 421 MOZ_ASSERT(aManager); 422 MOZ_ASSERT(aManagerThread); 423 MOZ_ASSERT(XRE_IsUtilityProcess()); 424 MOZ_ASSERT(GetCurrentSandboxingKind() == 425 ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM); 426 427 if (IsBeingProfiledOrLogEnabled()) { 428 nsPrintfCString msg("MFCDMParent created for %s", 429 NS_ConvertUTF16toUTF8(aKeySystem).get()); 430 MFCDM_PARENT_LOG("%s", msg.get()); 431 PROFILER_MARKER_TEXT("MFCDMParent::Ctor", MEDIA_PLAYBACK, {}, msg); 432 } 433 mIPDLSelfRef = this; 434 Register(); 435 436 mKeyMessageListener = mKeyMessageEvents.Connect( 437 mManagerThread, this, &MFCDMParent::SendOnSessionKeyMessage); 438 mKeyChangeListener = mKeyChangeEvents.Connect( 439 mManagerThread, this, &MFCDMParent::SendOnSessionKeyStatusesChanged); 440 mExpirationListener = mExpirationEvents.Connect( 441 mManagerThread, this, &MFCDMParent::SendOnSessionKeyExpiration); 442 mClosedListener = mClosedEvents.Connect(mManagerThread, this, 443 &MFCDMParent::SendOnSessionClosed); 444 445 RETURN_VOID_IF_FAILED(GetOrCreateFactory(mKeySystem, mFactory)); 446 } 447 448 void MFCDMParent::ShutdownCDM() { 449 ASSERT_CDM_ACCESS_ON_MANAGER_THREAD(); 450 MutexAutoLock lock(Mutex()); 451 mCDMAccessLock.NoteExclusiveAccess(); 452 if (!mCDM) { 453 return; 454 } 455 auto rv = mCDM->SetPMPHostApp(nullptr); 456 if (FAILED(rv)) { 457 MFCDM_PARENT_LOG("Failed to clear PMP Host App, rv=%lx", rv); 458 } 459 if (mCDMProxy) { 460 mCDMProxy->Shutdown(); 461 mCDMProxy = nullptr; 462 } 463 SHUTDOWN_IF_POSSIBLE(mCDM); 464 mCDM = nullptr; 465 MFCDM_PARENT_LOG("Shutdown CDM completed"); 466 } 467 468 void MFCDMParent::Destroy() { 469 ASSERT_CDM_ACCESS_READ_ONLY_ON_MANAGER_THREAD(); 470 PROFILER_MARKER_UNTYPED("MFCDMParent::Destroy", MEDIA_PLAYBACK); 471 mKeyMessageEvents.DisconnectAll(); 472 mKeyChangeEvents.DisconnectAll(); 473 mExpirationEvents.DisconnectAll(); 474 mClosedEvents.DisconnectAll(); 475 mKeyMessageListener.DisconnectIfExists(); 476 mKeyChangeListener.DisconnectIfExists(); 477 mExpirationListener.DisconnectIfExists(); 478 mClosedListener.DisconnectIfExists(); 479 if (mPMPHostWrapper) { 480 mPMPHostWrapper->Shutdown(); 481 mPMPHostWrapper = nullptr; 482 } 483 ShutdownCDM(); 484 mFactory = nullptr; 485 for (auto& iter : mSessions) { 486 iter.second->Close(dom::MediaKeySessionClosedReason::Closed_by_application); 487 } 488 mSessions.clear(); 489 mIPDLSelfRef = nullptr; 490 } 491 492 MFCDMParent::~MFCDMParent() { 493 MFCDM_PARENT_LOG("MFCDMParent detroyed"); 494 Unregister(); 495 } 496 497 /* static */ 498 LPCWSTR MFCDMParent::GetCDMLibraryName(const nsString& aKeySystem) { 499 if (IsWMFClearKeySystemAndSupported(aKeySystem) || 500 StaticPrefs::media_eme_wmf_use_mock_cdm_for_external_cdms()) { 501 return L"wmfclearkey.dll"; 502 } 503 // PlayReady is a built-in CDM on Windows, no need to load external library. 504 if (IsPlayReadyKeySystemAndSupported(aKeySystem)) { 505 return L""; 506 } 507 if (IsWidevineExperimentKeySystemAndSupported(aKeySystem) || 508 IsWidevineKeySystem(aKeySystem)) { 509 return sWidevineL1Path ? sWidevineL1Path : L"L1-not-found"; 510 } 511 return L"Unknown"; 512 } 513 514 /* static */ 515 void MFCDMParent::Shutdown() { 516 { 517 auto factoryMap = sFactoryMap.Lock(); 518 factoryMap->Clear(); 519 } 520 { 521 auto capabilities = sCapabilities.Lock(); 522 capabilities->Clear(); 523 } 524 { 525 auto mediaEngineClassFactory = sMediaEngineClassFactory.Lock(); 526 mediaEngineClassFactory->Reset(); 527 } 528 } 529 530 /* static */ 531 HRESULT MFCDMParent::GetOrCreateFactory( 532 const nsString& aKeySystem, 533 ComPtr<IMFContentDecryptionModuleFactory>& aFactoryOut) { 534 auto factoryMap = sFactoryMap.Lock(); 535 auto rv = factoryMap->MaybeGet(aKeySystem); 536 if (!rv) { 537 MFCDM_PARENT_SLOG("No factory %s, creating...", 538 NS_ConvertUTF16toUTF8(aKeySystem).get()); 539 ComPtr<IMFContentDecryptionModuleFactory> factory; 540 MFCDM_RETURN_IF_FAILED(LoadFactory(aKeySystem, factory)); 541 factoryMap->InsertOrUpdate(aKeySystem, factory); 542 aFactoryOut.Swap(factory); 543 } else { 544 aFactoryOut = *rv; 545 } 546 return S_OK; 547 } 548 549 /* static */ 550 HRESULT MFCDMParent::LoadFactory( 551 const nsString& aKeySystem, 552 ComPtr<IMFContentDecryptionModuleFactory>& aFactoryOut) { 553 LPCWSTR libraryName = GetCDMLibraryName(aKeySystem); 554 const bool loadFromPlatform = wcslen(libraryName) == 0; 555 MFCDM_PARENT_SLOG("Load factory for %s (libraryName=%ls)", 556 NS_ConvertUTF16toUTF8(aKeySystem).get(), libraryName); 557 558 MFCDM_PARENT_SLOG("Create factory for %s", 559 NS_ConvertUTF16toUTF8(aKeySystem).get()); 560 ComPtr<IMFContentDecryptionModuleFactory> cdmFactory; 561 if (loadFromPlatform) { 562 ComPtr<IMFMediaEngineClassFactory4> clsFactory; 563 { 564 auto mediaEngineClassFactory = sMediaEngineClassFactory.Lock(); 565 if (!*mediaEngineClassFactory) { 566 MFCDM_RETURN_IF_FAILED(CoCreateInstance( 567 CLSID_MFMediaEngineClassFactory, nullptr, CLSCTX_INPROC_SERVER, 568 IID_PPV_ARGS(&*mediaEngineClassFactory))); 569 } 570 MFCDM_RETURN_IF_FAILED((*mediaEngineClassFactory).As(&clsFactory)); 571 } 572 MFCDM_RETURN_IF_FAILED(clsFactory->CreateContentDecryptionModuleFactory( 573 MapKeySystem(aKeySystem).get(), IID_PPV_ARGS(&cdmFactory))); 574 if (MOZ_UNLIKELY(!cdmFactory)) { 575 if (IsBeingProfiledOrLogEnabled()) { 576 nsPrintfCString msg( 577 "CreateContentDecryptionModuleFactory succeeded, but still no " 578 "factory?!"); 579 MFCDM_PARENT_SLOG("%s", msg.get()); 580 PROFILER_MARKER_TEXT("MFCDMParent::LoadFactoryFailed", MEDIA_PLAYBACK, 581 {}, msg); 582 } 583 return E_UNEXPECTED; 584 } 585 aFactoryOut.Swap(cdmFactory); 586 MFCDM_PARENT_SLOG("Created factory for %s from platform!", 587 NS_ConvertUTF16toUTF8(aKeySystem).get()); 588 return S_OK; 589 } 590 591 // The following path is used for testing, loading a clearkey lib. Widevine 592 // support will be soon removed. 593 HMODULE handle = LoadLibraryW(libraryName); 594 if (!handle) { 595 MFCDM_PARENT_SLOG("Failed to load library %ls! (error=%lx)", libraryName, 596 GetLastError()); 597 return E_FAIL; 598 } 599 MFCDM_PARENT_SLOG("Loaded external library '%ls'", libraryName); 600 601 using DllGetActivationFactoryFunc = 602 HRESULT(WINAPI*)(_In_ HSTRING, _COM_Outptr_ IActivationFactory**); 603 DllGetActivationFactoryFunc pDllGetActivationFactory = 604 (DllGetActivationFactoryFunc)GetProcAddress(handle, 605 "DllGetActivationFactory"); 606 if (!pDllGetActivationFactory) { 607 MFCDM_PARENT_SLOG("Failed to get activation function!"); 608 return E_FAIL; 609 } 610 611 // The follow classID format is what Widevine's DLL expects 612 // "<key_system>.ContentDecryptionModuleFactory". In addition, when querying 613 // factory, need to use original Widevine key system name. 614 nsString stringId; 615 if (StaticPrefs::media_eme_wmf_use_mock_cdm_for_external_cdms() || 616 IsWMFClearKeySystemAndSupported(aKeySystem)) { 617 stringId.AppendLiteral("org.w3.clearkey"); 618 } else if (IsWidevineExperimentKeySystemAndSupported(aKeySystem) || 619 IsWidevineKeySystem(aKeySystem)) { 620 // Widevine's DLL expects "<key_system>.ContentDecryptionModuleFactory" for 621 // the class Id. 622 stringId.AppendLiteral("com.widevine.alpha.ContentDecryptionModuleFactory"); 623 } 624 MFCDM_PARENT_SLOG("Query factory by classId '%s'", 625 NS_ConvertUTF16toUTF8(stringId).get()); 626 ScopedHString classId(stringId); 627 ComPtr<IActivationFactory> pFactory = NULL; 628 MFCDM_RETURN_IF_FAILED( 629 pDllGetActivationFactory(classId.Get(), pFactory.GetAddressOf())); 630 631 ComPtr<IInspectable> pInspectable; 632 MFCDM_RETURN_IF_FAILED(pFactory->ActivateInstance(&pInspectable)); 633 MFCDM_RETURN_IF_FAILED(pInspectable.As(&cdmFactory)); 634 { 635 auto mediaEngineClassFactory = sMediaEngineClassFactory.Lock(); 636 if (!*mediaEngineClassFactory) { 637 *mediaEngineClassFactory = pInspectable; 638 } 639 } 640 aFactoryOut.Swap(cdmFactory); 641 MFCDM_PARENT_SLOG("Created factory for %s from external library!", 642 NS_ConvertUTF16toUTF8(aKeySystem).get()); 643 return S_OK; 644 } 645 646 static nsString GetRobustnessStringForKeySystem(const nsString& aKeySystem, 647 const bool aIsHWSecure, 648 const bool aIsVideo = true) { 649 if (IsPlayReadyKeySystemAndSupported(aKeySystem)) { 650 // Audio doesn't support SL3000. 651 return aIsHWSecure && aIsVideo ? nsString(u"3000") : nsString(u"2000"); 652 } 653 if (IsWidevineExperimentKeySystemAndSupported(aKeySystem)) { 654 return aIsHWSecure ? nsString(u"HW_SECURE_ALL") 655 : nsString(u"SW_SECURE_DECODE"); 656 } 657 return nsString(u""); 658 } 659 660 // Use IMFContentDecryptionModuleFactory::IsTypeSupported() to get DRM 661 // capabilities. The query string is based on following, they are pretty much 662 // equivalent. 663 // https://learn.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilities.istypesupported?view=winrt-22621 664 // https://learn.microsoft.com/en-us/windows/win32/api/mfmediaengine/nf-mfmediaengine-imfextendeddrmtypesupport-istypesupportedex 665 static bool FactorySupports( 666 ComPtr<IMFContentDecryptionModuleFactory>& aFactory, 667 ComPtr<IMFExtendedDRMTypeSupport>& aExtendedDrmTypeSupport, 668 const nsString& aKeySystem, const nsCString& aVideoCodec, 669 const nsCString& aAudioCodec = nsCString(""), 670 const nsString& aAdditionalFeatures = nsString(u""), 671 bool aIsHWSecure = false) { 672 // Create query string, MP4 is the only container supported. 673 nsString contentType(u"video/mp4;codecs=\""); 674 MOZ_ASSERT(!aVideoCodec.IsEmpty()); 675 contentType.AppendASCII(aVideoCodec); 676 if (!aAudioCodec.IsEmpty()) { 677 contentType.AppendLiteral(u","); 678 contentType.AppendASCII(aAudioCodec); 679 } 680 contentType.AppendLiteral(u"\";features=\""); 681 if (IsWidevineExperimentKeySystemAndSupported(aKeySystem) || 682 IsWidevineKeySystem(aKeySystem)) { 683 // This decoder subsystem settings are only required by Wivevine. 684 contentType.AppendLiteral( 685 u"decode-bpc=8," 686 "decode-res-x=1920,decode-res-y=1080," 687 "decode-bitrate=10000000,decode-fps=30,"); 688 // `encryption-robustness` is for Widevine only. 689 if (aIsHWSecure) { 690 contentType.AppendLiteral(u"encryption-robustness=HW_SECURE_ALL,"); 691 } else { 692 contentType.AppendLiteral(u"encryption-robustness=SW_SECURE_DECODE,"); 693 } 694 } 695 if (!aAdditionalFeatures.IsEmpty()) { 696 contentType.Append(aAdditionalFeatures); 697 } 698 contentType.AppendLiteral(u"\""); 699 // End of the query string 700 701 // PlayReady doesn't implement IsTypeSupported properly, so it requires us to 702 // use another way to check the capabilities. 703 if (IsPlayReadyKeySystemAndSupported(aKeySystem)) { 704 MOZ_ASSERT(aExtendedDrmTypeSupport); 705 BSTR keySystem = aIsHWSecure 706 ? CreateBSTRFromConstChar(kPlayReadyKeySystemHardware) 707 : CreateBSTRFromConstChar(kPlayReadyKeySystemName); 708 MF_MEDIA_ENGINE_CANPLAY canPlay; 709 aExtendedDrmTypeSupport->IsTypeSupportedEx( 710 SysAllocString(contentType.get()), keySystem, &canPlay); 711 bool support = 712 canPlay != 713 MF_MEDIA_ENGINE_CANPLAY::MF_MEDIA_ENGINE_CANPLAY_NOT_SUPPORTED; 714 MFCDM_PARENT_SLOG( 715 "IsTypeSupportedEx=%d, canPlay=%d (key-system=%ls, content-type=%s)", 716 support, canPlay, keySystem, NS_ConvertUTF16toUTF8(contentType).get()); 717 if (aIsHWSecure && support) { 718 // For HWDRM, `IsTypeSupportedEx` might still return the wrong answer on 719 // certain devices, so we need to create a dummy CDM to see if the HWDRM 720 // is really usable or not. 721 nsTArray<nsString> dummyInitDataType{nsString(u"cenc"), 722 nsString(u"keyids")}; 723 nsString mimeType(u"video/mp4;codecs=\""); 724 mimeType.AppendASCII(aVideoCodec); 725 MFCDMMediaCapability dummyVideoCapability{ 726 mimeType, 727 {CryptoScheme::None}, // No specific scheme 728 nsString(u"3000")}; 729 MFCDMInitParamsIPDL dummyParam{ 730 nsString(u"dummy"), 731 dummyInitDataType, 732 KeySystemConfig::Requirement::Required /* distinctiveID */, 733 KeySystemConfig::Requirement::Required /* persistent */, 734 {} /* audio capabilities */, 735 {dummyVideoCapability} /* video capabilities */, 736 }; 737 ComPtr<IMFContentDecryptionModule> dummyCDM = nullptr; 738 if (FAILED(CreateContentDecryptionModule( 739 aFactory, MapKeySystem(aKeySystem), dummyParam, dummyCDM)) || 740 !dummyCDM) { 741 if (IsBeingProfiledOrLogEnabled()) { 742 nsPrintfCString msg( 743 "HWDRM actually not supported (key-system=%ls, content-type=%s)", 744 keySystem, NS_ConvertUTF16toUTF8(contentType).get()); 745 PROFILER_MARKER_TEXT("MFCDMParent::FailedToUseHWDRM", MEDIA_PLAYBACK, 746 {}, msg); 747 MFCDM_PARENT_SLOG("%s", msg.get()); 748 } 749 support = false; 750 } 751 MFCDM_PARENT_SLOG( 752 "After HWDRM creation check, support=%d (key-system=%ls, " 753 "content-type=%s)", 754 support, keySystem, NS_ConvertUTF16toUTF8(contentType).get()); 755 if (dummyCDM) { 756 SHUTDOWN_IF_POSSIBLE(dummyCDM); 757 } 758 } 759 return support; 760 } 761 762 // Checking capabilies from CDM's IsTypeSupported. Widevine implements this 763 // method well. 764 bool support = IsTypeSupported(aFactory, aKeySystem, &contentType); 765 MFCDM_PARENT_SLOG("IsTypeSupport=%d (key-system=%s, content-type=%s)", 766 support, NS_ConvertUTF16toUTF8(aKeySystem).get(), 767 NS_ConvertUTF16toUTF8(contentType).get()); 768 return support; 769 } 770 771 static MF_MEDIA_ENGINE_CANPLAY RunHDCPSupportCheck( 772 const nsString& aKeySystem, const dom::HDCPVersion& aMinHdcpVersion) { 773 const auto getHDCPPolicyValue = [](const dom::HDCPVersion& aMinHdcpVersion) { 774 // The HDCP value follows the feature value in 775 // https://docs.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilities.istypesupported?view=winrt-19041 776 // - 1 (on without HDCP 2.2 Type 1 restriction) 777 // - 2 (on with HDCP 2.2 Type 1 restriction) 778 return (aMinHdcpVersion == dom::HDCPVersion::_2_2 || 779 aMinHdcpVersion == dom::HDCPVersion::_2_3) 780 ? 2 781 : 1; 782 }; 783 784 // HDCP is indenpendent with codec usage, so we just use H264 in mp4. 785 const char16_t* kFmt = u"video/mp4;codecs=\"avc1\";features=\"hdcp=%d\""; 786 nsAutoString contentType; 787 nsTextFormatter::ssprintf(contentType, kFmt, 788 getHDCPPolicyValue(aMinHdcpVersion)); 789 MOZ_ASSERT(!contentType.IsEmpty()); 790 ComPtr<IMFExtendedDRMTypeSupport> spDrmTypeSupport; 791 auto mediaEngineClassFactory = sMediaEngineClassFactory.Lock(); 792 if (!*mediaEngineClassFactory || 793 FAILED((*mediaEngineClassFactory).As(&spDrmTypeSupport))) { 794 MFCDM_PARENT_SLOG("Failed to get IMFExtendedDRMTypeSupport!"); 795 return MF_MEDIA_ENGINE_CANPLAY_NOT_SUPPORTED; 796 } 797 798 // Remove clearlead postfix if needed. 799 nsCString keySystemWithoutPostfix = 800 NS_ConvertUTF16toUTF8(MapKeySystem(aKeySystem)); 801 BSTR keySystem = CreateBSTRFromConstChar(keySystemWithoutPostfix.get()); 802 MF_MEDIA_ENGINE_CANPLAY canPlay; 803 spDrmTypeSupport->IsTypeSupportedEx(SysAllocString(contentType.get()), 804 keySystem, &canPlay); 805 MFCDM_PARENT_SLOG( 806 "IsTypeSupportedEx for HDCP, canplay=%d (key-system=%ls, " 807 "content-type=%s)", 808 static_cast<int32_t>(canPlay), keySystem, 809 NS_ConvertUTF16toUTF8(contentType).get()); 810 return canPlay; 811 } 812 813 // Only one HDCP check should run at a time; otherwise, the request response 814 // may become invalid. 815 StaticMutex sHDCPMutex; 816 817 static nsresult IsHDCPVersionSupported(const nsString& aKeySystem, 818 const dom::HDCPVersion& aMinHdcpVersion, 819 nsISerialEventTarget* aManagerThread) { 820 MOZ_ASSERT(!aManagerThread->IsOnCurrentThread(), 821 "Should not block the manager thread!"); 822 823 StaticMutexAutoLock lock(sHDCPMutex); 824 // https://learn.microsoft.com/en-us/windows/win32/api/mfmediaengine/nf-mfmediaengine-imfextendeddrmtypesupport-istypesupportedex/ 825 // HDCP query may initially return Maybe. It resolves to Probably or 826 // NotSupported within ~10s, where 10s is the maximum timeout (worst-case) 827 // rather than a common value. Microsoft recommends a minimum retry interval 828 // of 500ms. 829 constexpr auto kPollIntervalMs = 500; 830 constexpr auto kMaxWaitSec = 10; 831 const TimeStamp start = TimeStamp::Now(); 832 while ((TimeStamp::Now() - start) < TimeDuration::FromSeconds(kMaxWaitSec)) { 833 MF_MEDIA_ENGINE_CANPLAY canplay = 834 RunHDCPSupportCheck(aKeySystem, aMinHdcpVersion); 835 if (canplay != MF_MEDIA_ENGINE_CANPLAY_MAYBE) { 836 return canplay == MF_MEDIA_ENGINE_CANPLAY_PROBABLY 837 ? NS_OK 838 : NS_ERROR_DOM_MEDIA_CDM_HDCP_NOT_SUPPORT; 839 } 840 MFCDM_PARENT_SLOG("HDCP support is MAYBE, waiting to check it again..."); 841 PR_Sleep(PR_MillisecondsToInterval(kPollIntervalMs)); 842 } 843 return NS_ERROR_DOM_MEDIA_CDM_HDCP_NOT_SUPPORT; 844 } 845 846 static bool IsKeySystemHWSecure( 847 const nsAString& aKeySystem, 848 const nsTArray<MFCDMMediaCapability>& aCapabilities) { 849 if (IsPlayReadyKeySystemAndSupported(aKeySystem)) { 850 if (aKeySystem.EqualsLiteral(kPlayReadyKeySystemHardware) || 851 aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) { 852 return true; 853 } 854 for (const auto& capabilities : aCapabilities) { 855 if (capabilities.robustness().EqualsLiteral("3000")) { 856 return true; 857 } 858 } 859 } 860 if (IsWidevineExperimentKeySystemAndSupported(aKeySystem) || 861 IsWidevineKeySystem(aKeySystem)) { 862 // We only support Widevine HWDRM. 863 return true; 864 } 865 return false; 866 } 867 868 /* static */ 869 RefPtr<MFCDMParent::CapabilitiesPromise> 870 MFCDMParent::GetAllKeySystemsCapabilities() { 871 MOZ_ASSERT(NS_IsMainThread()); 872 nsCOMPtr<nsISerialEventTarget> backgroundTaskQueue; 873 if (NS_FAILED(NS_CreateBackgroundTaskQueue( 874 __func__, getter_AddRefs(backgroundTaskQueue)))) { 875 MFCDM_PARENT_SLOG( 876 "Failed to create task queue for all key systems capabilities!"); 877 return CapabilitiesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, 878 __func__); 879 } 880 881 RefPtr<CapabilitiesPromise::Private> p = 882 new CapabilitiesPromise::Private(__func__); 883 (void)backgroundTaskQueue->Dispatch(NS_NewRunnableFunction(__func__, [p] { 884 MFCDM_PARENT_SLOG("GetAllKeySystemsCapabilities"); 885 enum SecureLevel : bool { 886 Software = false, 887 Hardware = true, 888 }; 889 const nsTArray<std::pair<nsString, SecureLevel>> kKeySystems{ 890 std::pair<nsString, SecureLevel>( 891 NS_ConvertUTF8toUTF16(kPlayReadyKeySystemName), 892 SecureLevel::Software), 893 std::pair<nsString, SecureLevel>( 894 NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware), 895 SecureLevel::Hardware), 896 std::pair<nsString, SecureLevel>( 897 NS_ConvertUTF8toUTF16(kPlayReadyHardwareClearLeadKeySystemName), 898 SecureLevel::Hardware), 899 std::pair<nsString, SecureLevel>( 900 NS_ConvertUTF8toUTF16(kWidevineExperimentKeySystemName), 901 SecureLevel::Hardware), 902 std::pair<nsString, SecureLevel>( 903 NS_ConvertUTF8toUTF16(kWidevineExperiment2KeySystemName), 904 SecureLevel::Hardware), 905 }; 906 907 CopyableTArray<MFCDMCapabilitiesIPDL> capabilitiesArr; 908 for (const auto& keySystem : kKeySystems) { 909 // Only check the capabilites if the relative prefs for the key system 910 // are ON. 911 if (IsPlayReadyKeySystemAndSupported(keySystem.first) || 912 IsWidevineExperimentKeySystemAndSupported(keySystem.first)) { 913 MFCDMCapabilitiesIPDL* c = capabilitiesArr.AppendElement(); 914 CapabilitesFlagSet flags; 915 if (keySystem.second == SecureLevel::Hardware) { 916 flags += CapabilitesFlag::HarewareDecryption; 917 } 918 if (RequireClearLead(keySystem.first)) { 919 flags += CapabilitesFlag::NeedClearLeadCheck; 920 } 921 GetCapabilities(keySystem.first, flags, nullptr, *c); 922 } 923 } 924 925 p->Resolve(std::move(capabilitiesArr), __func__); 926 })); 927 return p; 928 } 929 930 /* static */ 931 void MFCDMParent::GetCapabilities(const nsString& aKeySystem, 932 const CapabilitesFlagSet& aFlags, 933 IMFContentDecryptionModuleFactory* aFactory, 934 MFCDMCapabilitiesIPDL& aCapabilitiesOut) { 935 aCapabilitiesOut.keySystem() = aKeySystem; 936 // WMF CDMs usually require these. See 937 // https://source.chromium.org/chromium/chromium/src/+/main:media/cdm/win/media_foundation_cdm_factory.cc;l=69-73;drc=b3ca5c09fa0aa07b7f9921501f75e43d80f3ba48 938 aCapabilitiesOut.persistentState() = KeySystemConfig::Requirement::Required; 939 aCapabilitiesOut.distinctiveID() = KeySystemConfig::Requirement::Required; 940 941 const bool isHardwareDecryption = 942 aFlags.contains(CapabilitesFlag::HarewareDecryption); 943 aCapabilitiesOut.isHardwareDecryption() = isHardwareDecryption; 944 // Return empty capabilites for SWDRM on Windows 10 because it has the process 945 // leaking problem. 946 if (!IsWin11OrLater() && !isHardwareDecryption) { 947 return; 948 } 949 950 // HWDRM is blocked by gfx downloadable blocklist. 951 if (isHardwareDecryption && gfx::gfxVars::IsInitialized() && 952 !gfx::gfxVars::UseWMFHWDWM()) { 953 MFCDM_PARENT_SLOG("Block HWDRM for %s", 954 NS_ConvertUTF16toUTF8(aKeySystem).get()); 955 return; 956 } 957 958 // MFCDM requires persistent storage, and can't use in-memory storage, it 959 // can't be used in private browsing. 960 if (aFlags.contains(CapabilitesFlag::IsPrivateBrowsing)) { 961 return; 962 } 963 964 auto capabilitiesUnlocked = sCapabilities.Lock(); 965 for (auto& capabilities : *capabilitiesUnlocked) { 966 if (capabilities.keySystem().Equals(aKeySystem) && 967 capabilities.isHardwareDecryption() == isHardwareDecryption) { 968 MFCDM_PARENT_SLOG( 969 "Return cached capabilities for %s (hardwareDecryption=%d)", 970 NS_ConvertUTF16toUTF8(aKeySystem).get(), isHardwareDecryption); 971 aCapabilitiesOut = capabilities; 972 return; 973 } 974 } 975 976 MFCDM_PARENT_SLOG( 977 "Query capabilities for %s from the factory (hardwareDecryption=%d)", 978 NS_ConvertUTF16toUTF8(aKeySystem).get(), isHardwareDecryption); 979 980 ComPtr<IMFContentDecryptionModuleFactory> factory = aFactory; 981 if (!factory) { 982 RETURN_VOID_IF_FAILED(GetOrCreateFactory(aKeySystem, factory)); 983 } 984 985 ComPtr<IMFExtendedDRMTypeSupport> spDrmTypeSupport; 986 auto mediaEngineClassFactory = sMediaEngineClassFactory.Lock(); 987 if (!*mediaEngineClassFactory || 988 FAILED((*mediaEngineClassFactory).As(&spDrmTypeSupport))) { 989 MFCDM_PARENT_SLOG("Failed to get IMFExtendedDRMTypeSupport!"); 990 return; 991 } 992 993 // Widevine requires codec type to be four CC, PlayReady is fine with both. 994 static auto convertCodecToFourCC = 995 [](const KeySystemConfig::EMECodecString& aCodec) { 996 if (aCodec.Equals(KeySystemConfig::EME_CODEC_H264)) { 997 return "avc1"_ns; 998 } 999 if (aCodec.Equals(KeySystemConfig::EME_CODEC_VP8)) { 1000 return "vp80"_ns; 1001 } 1002 if (aCodec.Equals(KeySystemConfig::EME_CODEC_VP9)) { 1003 return "vp09"_ns; 1004 } 1005 if (aCodec.Equals(KeySystemConfig::EME_CODEC_HEVC)) { 1006 return "hev1"_ns; 1007 } 1008 if (aCodec.Equals(KeySystemConfig::EME_CODEC_AV1)) { 1009 return "av01"_ns; 1010 } 1011 if (aCodec.Equals(KeySystemConfig::EME_CODEC_AAC)) { 1012 return "mp4a"_ns; 1013 } 1014 if (aCodec.Equals(KeySystemConfig::EME_CODEC_OPUS)) { 1015 return "Opus"_ns; 1016 } 1017 if (aCodec.Equals(KeySystemConfig::EME_CODEC_VORBIS)) { 1018 return "vrbs"_ns; 1019 } 1020 if (aCodec.Equals(KeySystemConfig::EME_CODEC_FLAC)) { 1021 return "fLaC"_ns; 1022 } 1023 MOZ_ASSERT_UNREACHABLE("Unsupported codec"); 1024 return "none"_ns; 1025 }; 1026 1027 static nsTArray<KeySystemConfig::EMECodecString> kVideoCodecs( 1028 {KeySystemConfig::EME_CODEC_H264, KeySystemConfig::EME_CODEC_VP8, 1029 KeySystemConfig::EME_CODEC_VP9, KeySystemConfig::EME_CODEC_HEVC, 1030 KeySystemConfig::EME_CODEC_AV1}); 1031 1032 // Collect schemes supported by all video codecs. 1033 static nsTArray<CryptoScheme> kSchemes({ 1034 CryptoScheme::Cenc, 1035 CryptoScheme::Cbcs, 1036 }); 1037 1038 // Remember supported video codecs, which will be used when collecting audio 1039 // codec support. 1040 nsTArray<KeySystemConfig::EMECodecString> supportedVideoCodecs; 1041 1042 if (aFlags.contains(CapabilitesFlag::NeedClearLeadCheck)) { 1043 for (const auto& codec : kVideoCodecs) { 1044 if (codec == KeySystemConfig::EME_CODEC_HEVC && 1045 !StaticPrefs::media_hevc_enabled()) { 1046 continue; 1047 } 1048 CryptoSchemeSet supportedScheme; 1049 for (const auto& scheme : kSchemes) { 1050 nsAutoString additionalFeature(u"encryption-type="); 1051 // If we don't specify 'encryption-iv-size', it would use 8 bytes IV as 1052 // default [1]. If it's not supported, then we will try 16 bytes later. 1053 // Since PlayReady 4.0 [2], 8 and 16 bytes IV are both supported. But 1054 // We're not sure if Widevine supports both or not. 1055 // [1] 1056 // https://learn.microsoft.com/en-us/windows/win32/api/mfmediaengine/nf-mfmediaengine-imfextendeddrmtypesupport-istypesupportedex 1057 // [2] 1058 // https://learn.microsoft.com/en-us/playready/packaging/content-encryption-modes#initialization-vectors-ivs 1059 if (scheme == CryptoScheme::Cenc) { 1060 additionalFeature.AppendLiteral(u"cenc-clearlead,"); 1061 } else { 1062 additionalFeature.AppendLiteral(u"cbcs-clearlead,"); 1063 } 1064 bool rv = FactorySupports(factory, spDrmTypeSupport, aKeySystem, 1065 convertCodecToFourCC(codec), nsCString(""), 1066 additionalFeature, isHardwareDecryption); 1067 MFCDM_PARENT_SLOG("clearlead %s IV 8 bytes %s %s", 1068 EnumValueToString(scheme), codec.get(), 1069 rv ? "supported" : "not supported"); 1070 if (rv) { 1071 supportedScheme += scheme; 1072 continue; 1073 } 1074 // Try 16 bytes IV. 1075 additionalFeature.AppendLiteral(u"encryption-iv-size=16,"); 1076 rv = FactorySupports(factory, spDrmTypeSupport, aKeySystem, 1077 convertCodecToFourCC(codec), nsCString(""), 1078 additionalFeature, isHardwareDecryption); 1079 MFCDM_PARENT_SLOG("clearlead %s IV 16 bytes %s %s", 1080 EnumValueToString(scheme), codec.get(), 1081 rv ? "supported" : "not supported"); 1082 1083 if (rv) { 1084 supportedScheme += scheme; 1085 continue; 1086 } 1087 } 1088 // Add a capability if supported scheme exists 1089 if (!supportedScheme.isEmpty()) { 1090 MFCDMMediaCapability* c = 1091 aCapabilitiesOut.videoCapabilities().AppendElement(); 1092 c->contentType() = NS_ConvertUTF8toUTF16(codec); 1093 c->robustness() = 1094 GetRobustnessStringForKeySystem(aKeySystem, isHardwareDecryption); 1095 if (supportedScheme.contains(CryptoScheme::Cenc)) { 1096 c->encryptionSchemes().AppendElement(CryptoScheme::Cenc); 1097 MFCDM_PARENT_SLOG("%s: +video:%s (cenc)", __func__, codec.get()); 1098 } 1099 if (supportedScheme.contains(CryptoScheme::Cbcs)) { 1100 c->encryptionSchemes().AppendElement(CryptoScheme::Cbcs); 1101 MFCDM_PARENT_SLOG("%s: +video:%s (cbcs)", __func__, codec.get()); 1102 } 1103 supportedVideoCodecs.AppendElement(codec); 1104 } 1105 } 1106 } else { 1107 // Non clearlead situation for video codecs 1108 for (const auto& codec : kVideoCodecs) { 1109 if (codec == KeySystemConfig::EME_CODEC_HEVC && 1110 !StaticPrefs::media_hevc_enabled()) { 1111 continue; 1112 } 1113 if (FactorySupports(factory, spDrmTypeSupport, aKeySystem, 1114 convertCodecToFourCC(codec), 1115 KeySystemConfig::EMECodecString(""), nsString(u""), 1116 isHardwareDecryption)) { 1117 MFCDMMediaCapability* c = 1118 aCapabilitiesOut.videoCapabilities().AppendElement(); 1119 c->contentType() = NS_ConvertUTF8toUTF16(codec); 1120 c->robustness() = 1121 GetRobustnessStringForKeySystem(aKeySystem, isHardwareDecryption); 1122 // 'If value is unspecified, default value of "cenc" is used.' See 1123 // https://learn.microsoft.com/en-us/windows/win32/api/mfmediaengine/nf-mfmediaengine-imfextendeddrmtypesupport-istypesupportedex 1124 c->encryptionSchemes().AppendElement(CryptoScheme::Cenc); 1125 MFCDM_PARENT_SLOG("%s: +video:%s (cenc)", __func__, codec.get()); 1126 // Check cbcs scheme support 1127 if (FactorySupports( 1128 factory, spDrmTypeSupport, aKeySystem, 1129 convertCodecToFourCC(codec), 1130 KeySystemConfig::EMECodecString(""), 1131 nsString(u"encryption-type=cbcs,encryption-iv-size=16,"), 1132 isHardwareDecryption)) { 1133 c->encryptionSchemes().AppendElement(CryptoScheme::Cbcs); 1134 MFCDM_PARENT_SLOG("%s: +video:%s (cbcs)", __func__, codec.get()); 1135 } 1136 supportedVideoCodecs.AppendElement(codec); 1137 } 1138 } 1139 } 1140 1141 if (supportedVideoCodecs.IsEmpty()) { 1142 // Return a capabilities with no codec supported. 1143 return; 1144 } 1145 1146 static nsTArray<KeySystemConfig::EMECodecString> kAudioCodecs({ 1147 KeySystemConfig::EME_CODEC_AAC, 1148 KeySystemConfig::EME_CODEC_FLAC, 1149 KeySystemConfig::EME_CODEC_OPUS, 1150 KeySystemConfig::EME_CODEC_VORBIS, 1151 }); 1152 for (const auto& codec : kAudioCodecs) { 1153 // Hardware decryption is usually only used for video, so we can just check 1154 // the software capabilities for audio in order to save some time. As the 1155 // media foundation would create a new D3D device everytime when we check 1156 // hardware decryption, which takes way longer time. 1157 if (FactorySupports(factory, spDrmTypeSupport, aKeySystem, 1158 convertCodecToFourCC(supportedVideoCodecs[0]), 1159 convertCodecToFourCC(codec), nsString(u""), 1160 false /* aIsHWSecure */)) { 1161 MFCDMMediaCapability* c = 1162 aCapabilitiesOut.audioCapabilities().AppendElement(); 1163 c->contentType() = NS_ConvertUTF8toUTF16(codec); 1164 c->robustness() = GetRobustnessStringForKeySystem( 1165 aKeySystem, false /* aIsHWSecure */, false /* isVideo */); 1166 c->encryptionSchemes().AppendElement(CryptoScheme::Cenc); 1167 MFCDM_PARENT_SLOG("%s: +audio:%s", __func__, codec.get()); 1168 } 1169 } 1170 1171 // TODO: don't hardcode 1172 aCapabilitiesOut.initDataTypes().AppendElement(u"keyids"); 1173 aCapabilitiesOut.initDataTypes().AppendElement(u"cenc"); 1174 aCapabilitiesOut.sessionTypes().AppendElement( 1175 KeySystemConfig::SessionType::Temporary); 1176 aCapabilitiesOut.sessionTypes().AppendElement( 1177 KeySystemConfig::SessionType::PersistentLicense); 1178 1179 // Cache capabilities for reuse. 1180 capabilitiesUnlocked->AppendElement(aCapabilitiesOut); 1181 } 1182 1183 mozilla::ipc::IPCResult MFCDMParent::RecvGetCapabilities( 1184 const MFCDMCapabilitiesRequest& aRequest, 1185 GetCapabilitiesResolver&& aResolver) { 1186 ASSERT_CDM_ACCESS_READ_ONLY_ON_MANAGER_THREAD(); 1187 MFCDM_REJECT_IF(!mFactory, NS_ERROR_DOM_NOT_SUPPORTED_ERR); 1188 MFCDMCapabilitiesIPDL capabilities; 1189 CapabilitesFlagSet flags; 1190 if (aRequest.isHardwareDecryption()) { 1191 flags += CapabilitesFlag::HarewareDecryption; 1192 } 1193 if (RequireClearLead(aRequest.keySystem())) { 1194 flags += CapabilitesFlag::NeedClearLeadCheck; 1195 } 1196 if (aRequest.isPrivateBrowsing()) { 1197 flags += CapabilitesFlag::IsPrivateBrowsing; 1198 } 1199 GetCapabilities(aRequest.keySystem(), flags, mFactory.Get(), capabilities); 1200 aResolver(std::move(capabilities)); 1201 return IPC_OK(); 1202 } 1203 1204 mozilla::ipc::IPCResult MFCDMParent::RecvInit( 1205 const MFCDMInitParamsIPDL& aParams, InitResolver&& aResolver) { 1206 ASSERT_CDM_ACCESS_ON_MANAGER_THREAD(); 1207 static auto RequirementToStr = [](KeySystemConfig::Requirement aRequirement) { 1208 switch (aRequirement) { 1209 case KeySystemConfig::Requirement::Required: 1210 return "Required"; 1211 case KeySystemConfig::Requirement::Optional: 1212 return "Optional"; 1213 default: 1214 return "NotAllowed"; 1215 } 1216 }; 1217 1218 const bool isHWSecure = 1219 IsKeySystemHWSecure(mKeySystem, aParams.videoCapabilities()); 1220 if (IsBeingProfiledOrLogEnabled()) { 1221 nsPrintfCString msg( 1222 "(key-system=%s, origin=%s, distinctiveID=%s, " 1223 "persistentState=%s, " 1224 "hwSecure=%d)", 1225 NS_ConvertUTF16toUTF8(mKeySystem).get(), 1226 NS_ConvertUTF16toUTF8(aParams.origin()).get(), 1227 RequirementToStr(aParams.distinctiveID()), 1228 RequirementToStr(aParams.persistentState()), isHWSecure); 1229 MFCDM_PARENT_LOG("Creating a CDM %s", msg.get()); 1230 PROFILER_MARKER_TEXT("MFCDMParent::RecvInit(creating CDM)", MEDIA_PLAYBACK, 1231 {}, msg); 1232 } 1233 MFCDM_REJECT_IF(!mFactory, NS_ERROR_DOM_NOT_SUPPORTED_ERR); 1234 MOZ_ASSERT_IF(isHWSecure, gfx::gfxVars::UseWMFHWDWM()); 1235 1236 MOZ_ASSERT(IsTypeSupported(mFactory, mKeySystem)); 1237 { 1238 MutexAutoLock lock(Mutex()); 1239 mCDMAccessLock.NoteExclusiveAccess(); 1240 MFCDM_REJECT_IF_FAILED( 1241 CreateContentDecryptionModule(mFactory, MapKeySystem(mKeySystem), 1242 aParams, mCDM), 1243 NS_ERROR_FAILURE); 1244 MFCDM_REJECT_IF(!mCDM, NS_ERROR_FAILURE); 1245 MOZ_ASSERT(!mCDMProxy); 1246 mCDMProxy = new MFCDMProxy(mCDM.Get(), mId); 1247 1248 MFCDM_PARENT_LOG("Created a CDM!"); 1249 PROFILER_MARKER_UNTYPED("MFCDMParent::RecvInit(created CDM)", 1250 MEDIA_PLAYBACK); 1251 // This is only required by PlayReady. 1252 if (IsPlayReadyKeySystemAndSupported(mKeySystem)) { 1253 ComPtr<IMFPMPHost> pmpHost; 1254 ComPtr<IMFGetService> cdmService; 1255 MFCDM_REJECT_IF_FAILED(mCDM.As(&cdmService), NS_ERROR_FAILURE); 1256 MFCDM_REJECT_IF_FAILED( 1257 cdmService->GetService(MF_CONTENTDECRYPTIONMODULE_SERVICE, 1258 IID_PPV_ARGS(&pmpHost)), 1259 NS_ERROR_FAILURE); 1260 MFCDM_REJECT_IF_FAILED(SUCCEEDED(MakeAndInitialize<MFPMPHostWrapper>( 1261 &mPMPHostWrapper, pmpHost)), 1262 NS_ERROR_FAILURE); 1263 MFCDM_REJECT_IF_FAILED(mCDM->SetPMPHostApp(mPMPHostWrapper.Get()), 1264 NS_ERROR_FAILURE); 1265 MFCDM_PARENT_LOG("Set PMPHostWrapper on CDM!"); 1266 } 1267 } 1268 1269 mIsInited = true; 1270 aResolver(MFCDMInitIPDL{mId}); 1271 return IPC_OK(); 1272 } 1273 1274 mozilla::ipc::IPCResult MFCDMParent::RecvCreateSessionAndGenerateRequest( 1275 const MFCDMCreateSessionParamsIPDL& aParams, 1276 CreateSessionAndGenerateRequestResolver&& aResolver) { 1277 ASSERT_CDM_ACCESS_READ_ONLY_ON_MANAGER_THREAD(); 1278 MOZ_ASSERT(mIsInited, "Must finish initialization first"); 1279 1280 static auto SessionTypeToStr = [](KeySystemConfig::SessionType aSessionType) { 1281 switch (aSessionType) { 1282 case KeySystemConfig::SessionType::Temporary: 1283 return "temporary"; 1284 case KeySystemConfig::SessionType::PersistentLicense: 1285 return "persistent-license"; 1286 default: { 1287 MOZ_ASSERT_UNREACHABLE("Unsupported license type!"); 1288 return "invalid"; 1289 } 1290 } 1291 }; 1292 if (IsBeingProfiledOrLogEnabled()) { 1293 nsPrintfCString msg("session for type '%s'", 1294 SessionTypeToStr(aParams.sessionType())); 1295 MFCDM_PARENT_LOG("Creating CDM %s", msg.get()); 1296 PROFILER_MARKER_TEXT( 1297 "MFCDMParent::RecvCreateSessionAndGenerateRequest(creating)", 1298 MEDIA_PLAYBACK, {}, msg); 1299 } 1300 if (!mCDM) { 1301 MFCDM_PARENT_LOG("Cannot create CDM session, already shutdown"); 1302 aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR); 1303 return IPC_OK(); 1304 } 1305 UniquePtr<MFCDMSession> session{ 1306 MFCDMSession::Create(aParams.sessionType(), mCDM.Get(), mManagerThread)}; 1307 if (!session) { 1308 MFCDM_PARENT_LOG("Failed to create CDM session"); 1309 aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR); 1310 return IPC_OK(); 1311 } 1312 ConnectSessionEvents(session.get()); 1313 1314 HRESULT hr = session->GenerateRequest(aParams.initDataType(), 1315 aParams.initData().Elements(), 1316 aParams.initData().Length()); 1317 if (hr == DRM_E_TEE_INVALID_HWDRM_STATE) { 1318 MFCDM_PARENT_LOG( 1319 "Failed to generate request due to DRM_E_TEE_INVALID_HWDRM_STATE"); 1320 mCDMProxy->OnHardwareContextReset(); 1321 session->Close(dom::MediaKeySessionClosedReason::Hardware_context_reset); 1322 aResolver(GenerateDummySessionId()); 1323 return IPC_OK(); 1324 } 1325 1326 if (FAILED(hr)) { 1327 MFCDM_PARENT_LOG("Failed to generate request (hr=%lx)!", hr); 1328 aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR); 1329 // No need to call session's close() because this is not an unrecoverable 1330 // error for CDM. 1331 return IPC_OK(); 1332 } 1333 1334 // TODO : now we assume all session ID is available after session is 1335 // created, but this is not always true. Need to remove this assertion and 1336 // handle cases where session Id is not available yet. 1337 const auto& sessionId = session->SessionID(); 1338 MOZ_ASSERT(sessionId); 1339 mSessions.emplace(*sessionId, std::move(session)); 1340 if (IsBeingProfiledOrLogEnabled()) { 1341 nsPrintfCString msg("session for type '%s', sessionId=%s", 1342 SessionTypeToStr(aParams.sessionType()), 1343 NS_ConvertUTF16toUTF8(*sessionId).get()); 1344 MFCDM_PARENT_LOG("Created CDM %s", msg.get()); 1345 PROFILER_MARKER_TEXT( 1346 "MFCDMParent::RecvCreateSessionAndGenerateRequest(created)", 1347 MEDIA_PLAYBACK, {}, msg); 1348 } 1349 aResolver(*sessionId); 1350 return IPC_OK(); 1351 } 1352 1353 mozilla::ipc::IPCResult MFCDMParent::RecvLoadSession( 1354 const KeySystemConfig::SessionType& aSessionType, 1355 const nsString& aSessionId, LoadSessionResolver&& aResolver) { 1356 ASSERT_CDM_ACCESS_READ_ONLY_ON_MANAGER_THREAD(); 1357 MOZ_ASSERT(mIsInited, "Must finish initialization first"); 1358 1359 nsresult rv = NS_OK; 1360 auto* session = GetSession(aSessionId); 1361 if (!session) { 1362 aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR); 1363 return IPC_OK(); 1364 } 1365 1366 if (IsBeingProfiledOrLogEnabled()) { 1367 nsPrintfCString msg("Load Session %s", 1368 NS_ConvertUTF16toUTF8(aSessionId).get()); 1369 MFCDM_PARENT_LOG("%s", msg.get()); 1370 PROFILER_MARKER_TEXT("MFCDMParent::RecvLoadSession", MEDIA_PLAYBACK, {}, 1371 msg); 1372 } 1373 MFCDM_REJECT_IF_FAILED(session->Load(aSessionId), 1374 NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR); 1375 aResolver(rv); 1376 return IPC_OK(); 1377 } 1378 1379 mozilla::ipc::IPCResult MFCDMParent::RecvUpdateSession( 1380 const nsString& aSessionId, const CopyableTArray<uint8_t>& aResponse, 1381 UpdateSessionResolver&& aResolver) { 1382 ASSERT_CDM_ACCESS_READ_ONLY_ON_MANAGER_THREAD(); 1383 MOZ_ASSERT(mIsInited, "Must finish initialization first"); 1384 nsresult rv = NS_OK; 1385 auto* session = GetSession(aSessionId); 1386 if (!session) { 1387 aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR); 1388 return IPC_OK(); 1389 } 1390 if (IsBeingProfiledOrLogEnabled()) { 1391 nsPrintfCString msg("Update Session %s", 1392 NS_ConvertUTF16toUTF8(aSessionId).get()); 1393 MFCDM_PARENT_LOG("%s", msg.get()); 1394 PROFILER_MARKER_TEXT("MFCDMParent::RecvUpdateSession", MEDIA_PLAYBACK, {}, 1395 msg); 1396 } 1397 MFCDM_REJECT_IF_FAILED(session->Update(aResponse), 1398 NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR); 1399 aResolver(rv); 1400 return IPC_OK(); 1401 } 1402 1403 mozilla::ipc::IPCResult MFCDMParent::RecvCloseSession( 1404 const nsString& aSessionId, UpdateSessionResolver&& aResolver) { 1405 ASSERT_CDM_ACCESS_READ_ONLY_ON_MANAGER_THREAD(); 1406 MOZ_ASSERT(mIsInited, "Must finish initialization first"); 1407 nsresult rv = NS_OK; 1408 auto* session = GetSession(aSessionId); 1409 if (!session) { 1410 aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR); 1411 return IPC_OK(); 1412 } 1413 if (IsBeingProfiledOrLogEnabled()) { 1414 nsPrintfCString msg("Close Session %s", 1415 NS_ConvertUTF16toUTF8(aSessionId).get()); 1416 MFCDM_PARENT_LOG("%s", msg.get()); 1417 PROFILER_MARKER_TEXT("MFCDMParent::RecvCloseSession", MEDIA_PLAYBACK, {}, 1418 msg); 1419 } 1420 MFCDM_REJECT_IF_FAILED( 1421 session->Close(dom::MediaKeySessionClosedReason::Closed_by_application), 1422 NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR); 1423 aResolver(rv); 1424 return IPC_OK(); 1425 } 1426 1427 mozilla::ipc::IPCResult MFCDMParent::RecvRemoveSession( 1428 const nsString& aSessionId, UpdateSessionResolver&& aResolver) { 1429 ASSERT_CDM_ACCESS_READ_ONLY_ON_MANAGER_THREAD(); 1430 MOZ_ASSERT(mIsInited, "Must finish initialization first"); 1431 nsresult rv = NS_OK; 1432 auto* session = GetSession(aSessionId); 1433 if (!session) { 1434 aResolver(NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR); 1435 return IPC_OK(); 1436 } 1437 if (IsBeingProfiledOrLogEnabled()) { 1438 nsPrintfCString msg("Remove Session %s", 1439 NS_ConvertUTF16toUTF8(aSessionId).get()); 1440 MFCDM_PARENT_LOG("%s", msg.get()); 1441 PROFILER_MARKER_TEXT("MFCDMParent::RecvRemoveSession", MEDIA_PLAYBACK, {}, 1442 msg); 1443 } 1444 MFCDM_REJECT_IF_FAILED(session->Remove(), 1445 NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR); 1446 aResolver(rv); 1447 return IPC_OK(); 1448 } 1449 1450 mozilla::ipc::IPCResult MFCDMParent::RecvSetServerCertificate( 1451 const CopyableTArray<uint8_t>& aCertificate, 1452 UpdateSessionResolver&& aResolver) { 1453 ASSERT_CDM_ACCESS_READ_ONLY_ON_MANAGER_THREAD(); 1454 MOZ_ASSERT(mIsInited, "Must finish initialization first"); 1455 nsresult rv = NS_OK; 1456 MFCDM_PARENT_LOG("Set server certificate"); 1457 PROFILER_MARKER_UNTYPED("MFCDMParent::RecvSetServerCertificate", 1458 MEDIA_PLAYBACK); 1459 MFCDM_REJECT_IF(!mCDM, NS_ERROR_DOM_MEDIA_CDM_ERR); 1460 MFCDM_REJECT_IF_FAILED(mCDM->SetServerCertificate( 1461 static_cast<const BYTE*>(aCertificate.Elements()), 1462 aCertificate.Length()), 1463 NS_ERROR_DOM_MEDIA_CDM_ERR); 1464 aResolver(rv); 1465 return IPC_OK(); 1466 } 1467 1468 mozilla::ipc::IPCResult MFCDMParent::RecvGetStatusForPolicy( 1469 const dom::HDCPVersion& aMinHdcpVersion, 1470 GetStatusForPolicyResolver&& aResolver) { 1471 ASSERT_CDM_ACCESS_READ_ONLY_ON_MANAGER_THREAD(); 1472 nsCOMPtr<nsISerialEventTarget> backgroundTaskQueue; 1473 if (NS_FAILED(NS_CreateBackgroundTaskQueue( 1474 __func__, getter_AddRefs(backgroundTaskQueue)))) { 1475 MFCDM_PARENT_LOG("Failed to create a background task queue"); 1476 aResolver(NS_ERROR_FAILURE); 1477 return IPC_OK(); 1478 } 1479 using HDCPPromise = MozPromise<nsresult, nsresult, /* IsExclusive = */ true>; 1480 RefPtr<HDCPPromise::Private> p = new HDCPPromise::Private(__func__); 1481 (void)backgroundTaskQueue->Dispatch(NS_NewRunnableFunction( 1482 __func__, [self = RefPtr<MFCDMParent>(this), this, aMinHdcpVersion, p] { 1483 auto rv = 1484 IsHDCPVersionSupported(mKeySystem, aMinHdcpVersion, mManagerThread); 1485 if (IsBeingProfiledOrLogEnabled()) { 1486 nsPrintfCString msg("HDCP version=%u, support=%s", 1487 static_cast<uint8_t>(aMinHdcpVersion), 1488 rv == NS_OK ? "true" : "false"); 1489 MFCDM_PARENT_LOG("%s", msg.get()); 1490 PROFILER_MARKER_TEXT("MFCDMParent::RecvGetStatusForPolicy", 1491 MEDIA_PLAYBACK, {}, msg); 1492 } 1493 p->Resolve(rv, __func__); 1494 })); 1495 p->Then(mManagerThread, __func__, 1496 [resolver = aResolver](HDCPPromise::ResolveOrRejectValue&& aRv) { 1497 MOZ_ASSERT(aRv.IsResolve()); 1498 resolver(aRv.ResolveValue()); 1499 }); 1500 return IPC_OK(); 1501 } 1502 1503 void MFCDMParent::ConnectSessionEvents(MFCDMSession* aSession) { 1504 // TODO : clear session's event source when the session gets removed. 1505 mKeyMessageEvents.Forward(aSession->KeyMessageEvent()); 1506 mKeyChangeEvents.Forward(aSession->KeyChangeEvent()); 1507 mExpirationEvents.Forward(aSession->ExpirationEvent()); 1508 mClosedEvents.Forward(aSession->ClosedEvent()); 1509 } 1510 1511 MFCDMSession* MFCDMParent::GetSession(const nsString& aSessionId) { 1512 ASSERT_CDM_ACCESS_READ_ONLY_ON_MANAGER_THREAD(); 1513 auto iter = mSessions.find(aSessionId); 1514 if (iter == mSessions.end()) { 1515 return nullptr; 1516 } 1517 return iter->second.get(); 1518 } 1519 1520 MFCDMProxy* MFCDMParent::GetMFCDMProxy() { 1521 MutexAutoLock lock(Mutex()); 1522 mCDMAccessLock.NoteLockHeld(); 1523 return mCDMProxy; 1524 } 1525 1526 /* static */ 1527 void MFCDMService::GetAllKeySystemsCapabilities(dom::Promise* aPromise) { 1528 MOZ_ASSERT(XRE_IsParentProcess()); 1529 const static auto kSandboxKind = ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM; 1530 LaunchMFCDMProcessIfNeeded(kSandboxKind) 1531 ->Then( 1532 GetMainThreadSerialEventTarget(), __func__, 1533 [promise = RefPtr(aPromise)]() { 1534 RefPtr<ipc::UtilityMediaServiceChild> umsc = 1535 ipc::UtilityMediaServiceChild::GetSingleton(kSandboxKind); 1536 if (NS_WARN_IF(!umsc)) { 1537 promise->MaybeReject(NS_ERROR_FAILURE); 1538 return; 1539 } 1540 umsc->GetKeySystemCapabilities(promise); 1541 }, 1542 [promise = RefPtr(aPromise)](nsresult aError) { 1543 promise->MaybeReject(NS_ERROR_FAILURE); 1544 }); 1545 } 1546 1547 /* static */ 1548 RefPtr<GenericNonExclusivePromise> MFCDMService::LaunchMFCDMProcessIfNeeded( 1549 ipc::SandboxingKind aSandbox) { 1550 MOZ_ASSERT(XRE_IsParentProcess()); 1551 MOZ_ASSERT(aSandbox == ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM); 1552 RefPtr<ipc::UtilityProcessManager> utilityProc = 1553 ipc::UtilityProcessManager::GetSingleton(); 1554 if (NS_WARN_IF(!utilityProc)) { 1555 NS_WARNING("Failed to get UtilityProcessManager"); 1556 return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE, 1557 __func__); 1558 } 1559 1560 // Check if the MFCDM process exists or not. If not, launch it. 1561 if (utilityProc->Process(aSandbox)) { 1562 return GenericNonExclusivePromise::CreateAndResolve(true, __func__); 1563 } 1564 1565 RefPtr<ipc::UtilityMediaServiceChild> umsc = 1566 ipc::UtilityMediaServiceChild::GetSingleton(aSandbox); 1567 if (NS_WARN_IF(!umsc)) { 1568 NS_WARNING("Failed to get UtilityMediaServiceChild"); 1569 return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE, 1570 __func__); 1571 } 1572 return utilityProc->StartUtility(umsc, aSandbox) 1573 ->Then( 1574 GetMainThreadSerialEventTarget(), __func__, 1575 [umsc, utilityProc, aSandbox]() { 1576 RefPtr<ipc::UtilityProcessParent> parent = 1577 utilityProc->GetProcessParent(aSandbox); 1578 if (!parent) { 1579 NS_WARNING("UtilityMediaServiceParent lost in the middle"); 1580 return GenericNonExclusivePromise::CreateAndReject( 1581 NS_ERROR_FAILURE, __func__); 1582 } 1583 1584 if (!umsc->CanSend()) { 1585 NS_WARNING("UtilityMediaServiceChild lost in the middle"); 1586 return GenericNonExclusivePromise::CreateAndReject( 1587 NS_ERROR_FAILURE, __func__); 1588 } 1589 return GenericNonExclusivePromise::CreateAndResolve(true, __func__); 1590 }, 1591 [](ipc::LaunchError const& aError) { 1592 NS_WARNING("Failed to start the MFCDM process!"); 1593 return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE, 1594 __func__); 1595 }); 1596 } 1597 1598 /* static */ 1599 void MFCDMService::UpdateWidevineL1Path(nsIFile* aFile) { 1600 RefPtr<ipc::UtilityProcessManager> utilityProc = 1601 ipc::UtilityProcessManager::GetSingleton(); 1602 if (NS_WARN_IF(!utilityProc)) { 1603 NS_WARNING("Failed to get UtilityProcessManager"); 1604 return; 1605 } 1606 1607 // If the MFCDM process hasn't been created yet, then we will set the path 1608 // when creating the process later. 1609 const auto sandboxKind = ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM; 1610 if (!utilityProc->Process(sandboxKind)) { 1611 return; 1612 } 1613 1614 // The MFCDM process has been started, we need to update its L1 path and set 1615 // the permission for LPAC. 1616 nsString widevineL1Path; 1617 MOZ_ASSERT(aFile); 1618 if (NS_WARN_IF(NS_FAILED(aFile->GetTarget(widevineL1Path)))) { 1619 NS_WARNING("MFCDMService::UpdateWidevineL1Path, Failed to get L1 path!"); 1620 return; 1621 } 1622 1623 RefPtr<ipc::UtilityMediaServiceChild> umsc = 1624 ipc::UtilityMediaServiceChild::GetSingleton(sandboxKind); 1625 if (NS_WARN_IF(!umsc)) { 1626 NS_WARNING("Failed to get UtilityMediaServiceChild"); 1627 return; 1628 } 1629 (void)umsc->SendUpdateWidevineL1Path(widevineL1Path); 1630 #ifdef MOZ_WMF_CDM_LPAC_SANDBOX 1631 SandboxBroker::EnsureLpacPermsissionsOnDir(widevineL1Path); 1632 #endif 1633 } 1634 1635 #undef MFCDM_REJECT_IF_FAILED 1636 #undef MFCDM_REJECT_IF 1637 #undef MFCDM_RETURN_IF_FAILED 1638 #undef MFCDM_RETURN_BOOL_IF_FAILED 1639 #undef MFCDM_PARENT_SLOG 1640 #undef MFCDM_PARENT_LOG 1641 #undef ASSERT_CDM_ACCESS_READ_ONLY_ON_MANAGER_THREAD 1642 #undef ASSERT_CDM_ACCESS_ON_MANAGER_THREAD 1643 1644 } // namespace mozilla