tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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