WMFCDMProxy.cpp (17227B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "WMFCDMProxy.h" 8 9 #include "MediaData.h" 10 #include "WMFCDMImpl.h" 11 #include "WMFCDMProxyCallback.h" 12 #include "mozilla/EMEUtils.h" 13 #include "mozilla/WMFCDMProxyCallback.h" 14 #include "mozilla/WindowsVersion.h" 15 #include "mozilla/dom/MediaKeySession.h" 16 #include "mozilla/dom/MediaKeySystemAccessBinding.h" 17 #include "mozilla/dom/MediaKeysBinding.h" 18 19 namespace mozilla { 20 21 #define LOG(msg, ...) \ 22 EME_LOG("WMFCDMProxy[%p]@%s: " msg, this, __func__, ##__VA_ARGS__) 23 24 #define RETURN_IF_SHUTDOWN() \ 25 do { \ 26 MOZ_ASSERT(NS_IsMainThread()); \ 27 if (mIsShutdown) { \ 28 return; \ 29 } \ 30 } while (false) 31 32 #define PERFORM_ON_CDM(operation, promiseId, ...) \ 33 do { \ 34 mCDM->operation(promiseId, __VA_ARGS__) \ 35 ->Then( \ 36 mMainThread, __func__, \ 37 [self = RefPtr{this}, this, promiseId]() { \ 38 RETURN_IF_SHUTDOWN(); \ 39 if (mKeys.IsNull()) { \ 40 EME_LOG("WMFCDMProxy(this=%p, pid=%" PRIu32 \ 41 ") : abort the " #operation " due to empty key", \ 42 this, promiseId); \ 43 return; \ 44 } \ 45 ResolvePromise(promiseId); \ 46 }, \ 47 [self = RefPtr{this}, this, promiseId]() { \ 48 RETURN_IF_SHUTDOWN(); \ 49 RejectPromiseWithStateError( \ 50 promiseId, nsLiteralCString("WMFCDMProxy::" #operation ": " \ 51 "failed to " #operation)); \ 52 }); \ 53 } while (false) 54 55 WMFCDMProxy::WMFCDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem, 56 const dom::MediaKeySystemConfiguration& aConfig) 57 : CDMProxy( 58 aKeys, aKeySystem, 59 aConfig.mDistinctiveIdentifier == dom::MediaKeysRequirement::Required, 60 aConfig.mPersistentState == dom::MediaKeysRequirement::Required), 61 mConfig(aConfig) { 62 MOZ_ASSERT(NS_IsMainThread()); 63 } 64 65 WMFCDMProxy::~WMFCDMProxy() {} 66 67 void WMFCDMProxy::Init(PromiseId aPromiseId, const nsAString& aOrigin, 68 const nsAString& aTopLevelOrigin, 69 const nsAString& aName) { 70 MOZ_ASSERT(NS_IsMainThread()); 71 MOZ_ASSERT(!aOrigin.IsEmpty()); 72 73 MOZ_ASSERT(!mOwnerThread); 74 if (NS_FAILED( 75 NS_NewNamedThread("WMFCDMThread", getter_AddRefs(mOwnerThread)))) { 76 RejectPromiseWithStateError( 77 aPromiseId, 78 nsLiteralCString("WMFCDMProxy::Init: couldn't create CDM thread")); 79 return; 80 } 81 82 mCDM = MakeRefPtr<WMFCDMImpl>(mKeySystem); 83 mProxyCallback = new WMFCDMProxyCallback(this); 84 // SWDRM has a PMP process leakage problem on Windows 10 due to the internal 85 // issue in the media foundation. Microsoft only lands the solution on Windows 86 // 11 and doesn't have plan to port it back to Windows 10. Therefore, for 87 // PlayReady, we need to force to covert SL2000 to SL3000 for our underlying 88 // CDM to avoid that issue. In addition, as we only support L1 for Widevine, 89 // this issue won't happen on it. 90 Maybe<nsString> forcedRobustness = 91 IsPlayReadyKeySystemAndSupported(mKeySystem) && !IsWin11OrLater() 92 ? Some(nsString(u"3000")) 93 : Nothing(); 94 WMFCDMImpl::InitParams params{ 95 nsString(aOrigin), 96 mConfig.mInitDataTypes, 97 mPersistentStateRequired, 98 mDistinctiveIdentifierRequired, 99 mProxyCallback, 100 GenerateMFCDMMediaCapabilities(mConfig.mAudioCapabilities), 101 GenerateMFCDMMediaCapabilities(mConfig.mVideoCapabilities, 102 forcedRobustness)}; 103 mCDM->Init(params)->Then( 104 mMainThread, __func__, 105 [self = RefPtr{this}, this, aPromiseId](const bool) { 106 MOZ_ASSERT(mCDM->Id() > 0); 107 mKeys->OnCDMCreated(aPromiseId, mCDM->Id()); 108 }, 109 [self = RefPtr{this}, this, aPromiseId](const nsresult rv) { 110 RejectPromiseWithStateError( 111 aPromiseId, 112 nsLiteralCString("WMFCDMProxy::Init: WMFCDM init error")); 113 }); 114 } 115 116 CopyableTArray<MFCDMMediaCapability> 117 WMFCDMProxy::GenerateMFCDMMediaCapabilities( 118 const dom::Sequence<dom::MediaKeySystemMediaCapability>& aCapabilities, 119 const Maybe<nsString>& forcedRobustness) { 120 CopyableTArray<MFCDMMediaCapability> outCapabilites; 121 for (const auto& capabilities : aCapabilities) { 122 if (!forcedRobustness) { 123 EME_LOG("WMFCDMProxy::Init %p, robustness=%s", this, 124 NS_ConvertUTF16toUTF8(capabilities.mRobustness).get()); 125 outCapabilites.AppendElement(MFCDMMediaCapability{ 126 capabilities.mContentType, 127 {StringToCryptoScheme(capabilities.mEncryptionScheme)}, 128 capabilities.mRobustness}); 129 } else { 130 EME_LOG("WMFCDMProxy::Init %p, force to robustness=%s", this, 131 NS_ConvertUTF16toUTF8(*forcedRobustness).get()); 132 outCapabilites.AppendElement(MFCDMMediaCapability{ 133 capabilities.mContentType, 134 {StringToCryptoScheme(capabilities.mEncryptionScheme)}, 135 *forcedRobustness}); 136 } 137 } 138 return outCapabilites; 139 } 140 141 void WMFCDMProxy::ResolvePromise(PromiseId aId) { 142 auto resolve = [self = RefPtr{this}, this, aId]() { 143 RETURN_IF_SHUTDOWN(); 144 EME_LOG("WMFCDMProxy::ResolvePromise(this=%p, pid=%" PRIu32 ")", this, aId); 145 if (!mKeys.IsNull()) { 146 mKeys->ResolvePromise(aId); 147 } else { 148 NS_WARNING("WMFCDMProxy unable to resolve promise!"); 149 } 150 }; 151 152 if (NS_IsMainThread()) { 153 resolve(); 154 return; 155 } 156 mMainThread->Dispatch( 157 NS_NewRunnableFunction("WMFCDMProxy::ResolvePromise", resolve)); 158 } 159 160 void WMFCDMProxy::ResolvePromiseWithKeyStatus( 161 const PromiseId& aId, const dom::MediaKeyStatus& aStatus) { 162 auto resolve = [self = RefPtr{this}, this, aId, aStatus]() { 163 RETURN_IF_SHUTDOWN(); 164 EME_LOG("WMFCDMProxy::ResolvePromiseWithKeyStatus(this=%p, pid=%" PRIu32 165 ", status=%s)", 166 this, aId, dom::GetEnumString(aStatus).get()); 167 if (!mKeys.IsNull()) { 168 mKeys->ResolvePromiseWithKeyStatus(aId, aStatus); 169 } else { 170 NS_WARNING("WMFCDMProxy unable to resolve promise!"); 171 } 172 }; 173 174 if (NS_IsMainThread()) { 175 resolve(); 176 return; 177 } 178 mMainThread->Dispatch(NS_NewRunnableFunction( 179 "WMFCDMProxy::ResolvePromiseWithKeyStatus", resolve)); 180 } 181 182 void WMFCDMProxy::RejectPromise(PromiseId aId, ErrorResult&& aException, 183 const nsCString& aReason) { 184 if (!NS_IsMainThread()) { 185 // Use CopyableErrorResult to store our exception in the runnable, 186 // because ErrorResult is not OK to move across threads. 187 mMainThread->Dispatch( 188 NewRunnableMethod<PromiseId, StoreCopyPassByRRef<CopyableErrorResult>, 189 nsCString>("WMFCDMProxy::RejectPromise", this, 190 &WMFCDMProxy::RejectPromiseOnMainThread, 191 aId, std::move(aException), aReason), 192 NS_DISPATCH_NORMAL); 193 return; 194 } 195 EME_LOG("WMFCDMProxy::RejectPromise(this=%p, pid=%" PRIu32 196 ", code=0x%x, " 197 "reason='%s')", 198 this, aId, aException.ErrorCodeAsInt(), aReason.get()); 199 if (!mKeys.IsNull()) { 200 mKeys->RejectPromise(aId, std::move(aException), aReason); 201 } else { 202 // We don't have a MediaKeys object to pass the exception to, so silence 203 // the exception to avoid it asserting due to being unused. 204 aException.SuppressException(); 205 } 206 } 207 208 void WMFCDMProxy::RejectPromiseOnMainThread(PromiseId aId, 209 CopyableErrorResult&& aException, 210 const nsCString& aReason) { 211 RETURN_IF_SHUTDOWN(); 212 // Moving into or out of a non-copyable ErrorResult will assert that both 213 // ErorResults are from our current thread. Avoid the assertion by moving 214 // into a current-thread CopyableErrorResult first. Note that this is safe, 215 // because CopyableErrorResult never holds state that can't move across 216 // threads. 217 CopyableErrorResult rv(std::move(aException)); 218 RejectPromise(aId, std::move(rv), aReason); 219 } 220 221 void WMFCDMProxy::RejectPromiseWithStateError(PromiseId aId, 222 const nsCString& aReason) { 223 ErrorResult rv; 224 rv.ThrowInvalidStateError(aReason); 225 RejectPromise(aId, std::move(rv), aReason); 226 } 227 228 void WMFCDMProxy::CreateSession(uint32_t aCreateSessionToken, 229 dom::MediaKeySessionType aSessionType, 230 PromiseId aPromiseId, 231 const nsAString& aInitDataType, 232 nsTArray<uint8_t>& aInitData) { 233 MOZ_ASSERT(NS_IsMainThread()); 234 RETURN_IF_SHUTDOWN(); 235 const auto sessionType = ConvertToKeySystemConfigSessionType(aSessionType); 236 EME_LOG("WMFCDMProxy::CreateSession(this=%p, pid=%" PRIu32 237 "), sessionType=%s", 238 this, aPromiseId, KeySystemConfig::EnumValueToString(sessionType)); 239 mCDM->CreateSession(aPromiseId, sessionType, aInitDataType, aInitData) 240 ->Then( 241 mMainThread, __func__, 242 [self = RefPtr{this}, this, aCreateSessionToken, 243 aPromiseId](nsString sessionID) { 244 RETURN_IF_SHUTDOWN(); 245 if (mKeys.IsNull()) { 246 EME_LOG("WMFCDMProxy(this=%p, pid=%" PRIu32 247 ") : abort the create session due to " 248 "empty key", 249 this, aPromiseId); 250 return; 251 } 252 if (RefPtr<dom::MediaKeySession> session = 253 mKeys->GetPendingSession(aCreateSessionToken)) { 254 session->SetSessionId(std::move(sessionID)); 255 } 256 ResolvePromise(aPromiseId); 257 }, 258 [self = RefPtr{this}, this, aPromiseId]() { 259 RETURN_IF_SHUTDOWN(); 260 RejectPromiseWithStateError( 261 aPromiseId, 262 nsLiteralCString( 263 "WMFCDMProxy::CreateSession: cannot create session")); 264 }); 265 } 266 267 void WMFCDMProxy::LoadSession(PromiseId aPromiseId, 268 dom::MediaKeySessionType aSessionType, 269 const nsAString& aSessionId) { 270 MOZ_ASSERT(NS_IsMainThread()); 271 RETURN_IF_SHUTDOWN(); 272 const auto sessionType = ConvertToKeySystemConfigSessionType(aSessionType); 273 EME_LOG("WMFCDMProxy::LoadSession(this=%p, pid=%" PRIu32 274 "), sessionType=%s, sessionId=%s", 275 this, aPromiseId, KeySystemConfig::EnumValueToString(sessionType), 276 NS_ConvertUTF16toUTF8(aSessionId).get()); 277 PERFORM_ON_CDM(LoadSession, aPromiseId, sessionType, aSessionId); 278 } 279 280 void WMFCDMProxy::UpdateSession(const nsAString& aSessionId, 281 PromiseId aPromiseId, 282 nsTArray<uint8_t>& aResponse) { 283 MOZ_ASSERT(NS_IsMainThread()); 284 RETURN_IF_SHUTDOWN(); 285 EME_LOG("WMFCDMProxy::UpdateSession(this=%p, pid=%" PRIu32 286 "), sessionId=%s, responseLen=%zu", 287 this, aPromiseId, NS_ConvertUTF16toUTF8(aSessionId).get(), 288 aResponse.Length()); 289 PERFORM_ON_CDM(UpdateSession, aPromiseId, aSessionId, aResponse); 290 } 291 292 void WMFCDMProxy::CloseSession(const nsAString& aSessionId, 293 PromiseId aPromiseId) { 294 MOZ_ASSERT(NS_IsMainThread()); 295 RETURN_IF_SHUTDOWN(); 296 EME_LOG("WMFCDMProxy::CloseSession(this=%p, pid=%" PRIu32 "), sessionId=%s", 297 this, aPromiseId, NS_ConvertUTF16toUTF8(aSessionId).get()); 298 PERFORM_ON_CDM(CloseSession, aPromiseId, aSessionId); 299 } 300 301 void WMFCDMProxy::RemoveSession(const nsAString& aSessionId, 302 PromiseId aPromiseId) { 303 MOZ_ASSERT(NS_IsMainThread()); 304 RETURN_IF_SHUTDOWN(); 305 EME_LOG("WMFCDMProxy::RemoveSession(this=%p, pid=%" PRIu32 "), sessionId=%s", 306 this, aPromiseId, NS_ConvertUTF16toUTF8(aSessionId).get()); 307 PERFORM_ON_CDM(RemoveSession, aPromiseId, aSessionId); 308 } 309 310 void WMFCDMProxy::Shutdown() { 311 MOZ_ASSERT(NS_IsMainThread()); 312 MOZ_ASSERT(!mIsShutdown); 313 if (mProxyCallback) { 314 mProxyCallback->Shutdown(); 315 mProxyCallback = nullptr; 316 } 317 mIsShutdown = true; 318 } 319 320 void WMFCDMProxy::Terminated() { 321 MOZ_ASSERT(NS_IsMainThread()); 322 if (!mKeys.IsNull()) { 323 mKeys->Terminated(); 324 } 325 } 326 327 void WMFCDMProxy::OnSessionMessage(const nsAString& aSessionId, 328 dom::MediaKeyMessageType aMessageType, 329 const nsTArray<uint8_t>& aMessage) { 330 MOZ_ASSERT(NS_IsMainThread()); 331 RETURN_IF_SHUTDOWN(); 332 if (mKeys.IsNull()) { 333 return; 334 } 335 if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) { 336 LOG("Notify key message for session Id=%s", 337 NS_ConvertUTF16toUTF8(aSessionId).get()); 338 session->DispatchKeyMessage(aMessageType, aMessage); 339 } 340 } 341 342 void WMFCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) { 343 MOZ_ASSERT(NS_IsMainThread()); 344 RETURN_IF_SHUTDOWN(); 345 if (mKeys.IsNull()) { 346 return; 347 } 348 if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) { 349 LOG("Notify key statuses for session Id=%s", 350 NS_ConvertUTF16toUTF8(aSessionId).get()); 351 session->DispatchKeyStatusesChange(); 352 } 353 } 354 355 void WMFCDMProxy::OnExpirationChange(const nsAString& aSessionId, 356 UnixTime aExpiryTime) { 357 MOZ_ASSERT(NS_IsMainThread()); 358 RETURN_IF_SHUTDOWN(); 359 if (mKeys.IsNull()) { 360 return; 361 } 362 if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) { 363 LOG("Notify expiration for session Id=%s", 364 NS_ConvertUTF16toUTF8(aSessionId).get()); 365 session->SetExpiration(static_cast<double>(aExpiryTime)); 366 } 367 } 368 369 void WMFCDMProxy::OnSessionClosed(const nsAString& aSessionId, 370 dom::MediaKeySessionClosedReason aReason) { 371 MOZ_ASSERT(NS_IsMainThread()); 372 RETURN_IF_SHUTDOWN(); 373 if (mKeys.IsNull()) { 374 return; 375 } 376 if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) { 377 LOG("Notify closed for session Id=%s", 378 NS_ConvertUTF16toUTF8(aSessionId).get()); 379 session->OnClosed(aReason); 380 } 381 } 382 383 void WMFCDMProxy::SetServerCertificate(PromiseId aPromiseId, 384 nsTArray<uint8_t>& aCert) { 385 MOZ_ASSERT(NS_IsMainThread()); 386 RETURN_IF_SHUTDOWN(); 387 EME_LOG("WMFCDMProxy::SetServerCertificate(this=%p, pid=%" PRIu32 ")", this, 388 aPromiseId); 389 mCDM->SetServerCertificate(aPromiseId, aCert) 390 ->Then( 391 mMainThread, __func__, 392 [self = RefPtr{this}, this, aPromiseId]() { 393 RETURN_IF_SHUTDOWN(); 394 ResolvePromise(aPromiseId); 395 }, 396 [self = RefPtr{this}, this, aPromiseId]() { 397 RETURN_IF_SHUTDOWN(); 398 RejectPromiseWithStateError( 399 aPromiseId, 400 nsLiteralCString("WMFCDMProxy::SetServerCertificate failed!")); 401 }); 402 } 403 404 void WMFCDMProxy::GetStatusForPolicy(PromiseId aPromiseId, 405 const dom::HDCPVersion& aMinHdcpVersion) { 406 MOZ_ASSERT(NS_IsMainThread()); 407 RETURN_IF_SHUTDOWN(); 408 EME_LOG("WMFCDMProxy::GetStatusForPolicy(this=%p, pid=%" PRIu32 409 ", minHDCP=%s)", 410 this, aPromiseId, dom::GetEnumString(aMinHdcpVersion).get()); 411 mCDM->GetStatusForPolicy(aPromiseId, aMinHdcpVersion) 412 ->Then( 413 mMainThread, __func__, 414 [self = RefPtr{this}, this, aPromiseId]() { 415 RETURN_IF_SHUTDOWN(); 416 ResolvePromiseWithKeyStatus(aPromiseId, 417 dom::MediaKeyStatus::Usable); 418 }, 419 [self = RefPtr{this}, this, aPromiseId]() { 420 RETURN_IF_SHUTDOWN(); 421 ResolvePromiseWithKeyStatus(aPromiseId, 422 dom::MediaKeyStatus::Output_restricted); 423 }); 424 } 425 426 bool WMFCDMProxy::IsHardwareDecryptionSupported() const { 427 return mozilla::IsHardwareDecryptionSupported(mConfig); 428 } 429 430 uint64_t WMFCDMProxy::GetCDMProxyId() const { 431 MOZ_DIAGNOSTIC_ASSERT(mCDM); 432 return mCDM->Id(); 433 } 434 435 #undef LOG 436 #undef RETURN_IF_SHUTDOWN 437 #undef PERFORM_ON_CDM 438 439 } // namespace mozilla