tor-browser

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

MFCDMSession.cpp (12086B)


      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 "MFCDMSession.h"
      6 
      7 #include <vcruntime.h>
      8 #include <winerror.h>
      9 
     10 #include <limits>
     11 
     12 #include "GMPUtils.h"  // ToHexString
     13 #include "MFMediaEngineUtils.h"
     14 #include "mozilla/EMEUtils.h"
     15 #include "mozilla/StaticPrefs_media.h"
     16 #include "mozilla/dom/BindingUtils.h"
     17 #include "mozilla/dom/MediaKeyMessageEventBinding.h"
     18 #include "mozilla/dom/MediaKeyStatusMapBinding.h"
     19 #include "nsThreadUtils.h"
     20 
     21 namespace mozilla {
     22 
     23 using Microsoft::WRL::ComPtr;
     24 using Microsoft::WRL::MakeAndInitialize;
     25 
     26 #define LOG(msg, ...) EME_LOG("MFCDMSession=%p, " msg, this, ##__VA_ARGS__)
     27 
     28 static inline MF_MEDIAKEYSESSION_TYPE ConvertSessionType(
     29    KeySystemConfig::SessionType aType) {
     30  switch (aType) {
     31    case KeySystemConfig::SessionType::Temporary:
     32      return MF_MEDIAKEYSESSION_TYPE_TEMPORARY;
     33    case KeySystemConfig::SessionType::PersistentLicense:
     34      return MF_MEDIAKEYSESSION_TYPE_PERSISTENT_LICENSE;
     35  }
     36 }
     37 
     38 static inline LPCWSTR InitDataTypeToString(const nsAString& aInitDataType) {
     39  // The strings are defined in https://www.w3.org/TR/eme-initdata-registry/
     40  if (aInitDataType.EqualsLiteral("webm")) {
     41    return L"webm";
     42  } else if (aInitDataType.EqualsLiteral("cenc")) {
     43    return L"cenc";
     44  } else if (aInitDataType.EqualsLiteral("keyids")) {
     45    return L"keyids";
     46  } else {
     47    return L"unknown";
     48  }
     49 }
     50 
     51 // The callback interface which IMFContentDecryptionModuleSession uses for
     52 // communicating with the session.
     53 class MFCDMSession::SessionCallbacks final
     54    : public Microsoft::WRL::RuntimeClass<
     55          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
     56          IMFContentDecryptionModuleSessionCallbacks> {
     57 public:
     58  SessionCallbacks() { MOZ_COUNT_CTOR(SessionCallbacks); };
     59  SessionCallbacks(const SessionCallbacks&) = delete;
     60  SessionCallbacks& operator=(const SessionCallbacks&) = delete;
     61  ~SessionCallbacks() { MOZ_COUNT_DTOR(SessionCallbacks); }
     62 
     63  HRESULT RuntimeClassInitialize() { return S_OK; }
     64 
     65  // IMFContentDecryptionModuleSessionCallbacks
     66  STDMETHODIMP KeyMessage(MF_MEDIAKEYSESSION_MESSAGETYPE aType,
     67                          const BYTE* aMessage, DWORD aMessageSize,
     68                          LPCWSTR aUrl) final {
     69    CopyableTArray<uint8_t> msg{static_cast<const uint8_t*>(aMessage),
     70                                aMessageSize};
     71    mKeyMessageEvent.Notify(aType, std::move(msg));
     72    return S_OK;
     73  }
     74 
     75  STDMETHODIMP KeyStatusChanged() final {
     76    mKeyChangeEvent.Notify();
     77    return S_OK;
     78  }
     79 
     80  MediaEventSource<MF_MEDIAKEYSESSION_MESSAGETYPE, nsTArray<uint8_t>>&
     81  KeyMessageEvent() {
     82    return mKeyMessageEvent;
     83  }
     84  MediaEventSource<void>& KeyChangeEvent() { return mKeyChangeEvent; }
     85 
     86 private:
     87  MediaEventProducer<MF_MEDIAKEYSESSION_MESSAGETYPE, nsTArray<uint8_t>>
     88      mKeyMessageEvent;
     89  MediaEventProducer<void> mKeyChangeEvent;
     90 };
     91 
     92 /* static*/
     93 MFCDMSession* MFCDMSession::Create(KeySystemConfig::SessionType aSessionType,
     94                                   IMFContentDecryptionModule* aCdm,
     95                                   nsISerialEventTarget* aManagerThread) {
     96  MOZ_ASSERT(aCdm);
     97  MOZ_ASSERT(aManagerThread);
     98  ComPtr<SessionCallbacks> callbacks;
     99  RETURN_PARAM_IF_FAILED(MakeAndInitialize<SessionCallbacks>(&callbacks),
    100                         nullptr);
    101 
    102  ComPtr<IMFContentDecryptionModuleSession> session;
    103  RETURN_PARAM_IF_FAILED(aCdm->CreateSession(ConvertSessionType(aSessionType),
    104                                             callbacks.Get(), &session),
    105                         nullptr);
    106  return new MFCDMSession(session.Get(), callbacks.Get(), aManagerThread);
    107 }
    108 
    109 MFCDMSession::MFCDMSession(IMFContentDecryptionModuleSession* aSession,
    110                           SessionCallbacks* aCallback,
    111                           nsISerialEventTarget* aManagerThread)
    112    : mSession(aSession),
    113      mManagerThread(aManagerThread),
    114      mExpiredTimeMilliSecondsSinceEpoch(
    115          std::numeric_limits<double>::quiet_NaN()) {
    116  MOZ_ASSERT(aSession);
    117  MOZ_ASSERT(aCallback);
    118  MOZ_ASSERT(aManagerThread);
    119  MOZ_COUNT_CTOR(MFCDMSession);
    120  LOG("MFCDMSession created");
    121  mKeyMessageListener = aCallback->KeyMessageEvent().Connect(
    122      mManagerThread, this, &MFCDMSession::OnSessionKeyMessage);
    123  mKeyChangeListener = aCallback->KeyChangeEvent().Connect(
    124      mManagerThread, this, &MFCDMSession::OnSessionKeysChange);
    125 }
    126 
    127 MFCDMSession::~MFCDMSession() {
    128  MOZ_COUNT_DTOR(MFCDMSession);
    129  LOG("MFCDMSession destroyed");
    130  // TODO : maybe disconnect them in `Close()`?
    131  mKeyChangeListener.DisconnectIfExists();
    132  mKeyMessageListener.DisconnectIfExists();
    133 }
    134 
    135 HRESULT MFCDMSession::GenerateRequest(const nsAString& aInitDataType,
    136                                      const uint8_t* aInitData,
    137                                      uint32_t aInitDataSize) {
    138  AssertOnManagerThread();
    139  LOG("GenerateRequest for %s (init sz=%u)",
    140      NS_ConvertUTF16toUTF8(aInitDataType).get(), aInitDataSize);
    141  RETURN_IF_FAILED(mSession->GenerateRequest(
    142      InitDataTypeToString(aInitDataType), aInitData, aInitDataSize));
    143  (void)RetrieveSessionId();
    144  return S_OK;
    145 }
    146 
    147 HRESULT MFCDMSession::Load(const nsAString& aSessionId) {
    148  AssertOnManagerThread();
    149  // TODO : do we need to implement this? Chromium doesn't implement this one.
    150  // Also, how do we know is this given session ID is equal to the session Id
    151  // asked from CDM session or not?
    152  BOOL rv = FALSE;
    153  mSession->Load(char16ptr_t(aSessionId.BeginReading()), &rv);
    154  LOG("Load, id=%s, rv=%s", NS_ConvertUTF16toUTF8(aSessionId).get(),
    155      rv ? "success" : "fail");
    156  return rv ? S_OK : S_FALSE;
    157 }
    158 
    159 HRESULT MFCDMSession::Update(const nsTArray<uint8_t>& aMessage) {
    160  AssertOnManagerThread();
    161  LOG("Update");
    162  RETURN_IF_FAILED(mSession->Update(
    163      static_cast<const BYTE*>(aMessage.Elements()), aMessage.Length()));
    164  RETURN_IF_FAILED(UpdateExpirationIfNeeded());
    165  return S_OK;
    166 }
    167 
    168 HRESULT MFCDMSession::Close(dom::MediaKeySessionClosedReason aReason) {
    169  AssertOnManagerThread();
    170  if (mIsClosed) {
    171    LOG("Close, session is already closed");
    172    return S_OK;
    173  }
    174  LOG("Close");
    175  RETURN_IF_FAILED(mSession->Close());
    176  mIsClosed = true;
    177  mClosedEvent.Notify(MFCDMSessionClosedResult{*mSessionId, aReason});
    178  return S_OK;
    179 }
    180 
    181 HRESULT MFCDMSession::Remove() {
    182  AssertOnManagerThread();
    183  LOG("Remove");
    184  RETURN_IF_FAILED(mSession->Remove());
    185  RETURN_IF_FAILED(UpdateExpirationIfNeeded());
    186  return S_OK;
    187 }
    188 
    189 bool MFCDMSession::RetrieveSessionId() {
    190  AssertOnManagerThread();
    191  if (mSessionId) {
    192    return true;
    193  }
    194  ScopedCoMem<wchar_t> sessionId;
    195  if (FAILED(mSession->GetSessionId(&sessionId)) || !sessionId) {
    196    LOG("Can't get session id or empty session ID!");
    197    return false;
    198  }
    199  LOG("Set session Id %ls", sessionId.Get());
    200  mSessionId = Some(sessionId.Get());
    201  return true;
    202 }
    203 
    204 void MFCDMSession::OnSessionKeysChange() {
    205  AssertOnManagerThread();
    206  LOG("OnSessionKeysChange");
    207 
    208  if (!mSessionId) {
    209    LOG("Unexpected session keys change ignored");
    210    return;
    211  }
    212 
    213  ScopedCoMem<MFMediaKeyStatus> keyStatuses;
    214  UINT count = 0;
    215  RETURN_VOID_IF_FAILED(mSession->GetKeyStatuses(&keyStatuses, &count));
    216 
    217  static auto ToMediaKeyStatus = [](MF_MEDIAKEY_STATUS aStatus) {
    218    // https://learn.microsoft.com/en-us/windows/win32/api/mfidl/ne-mfidl-mf_mediakey_status
    219    switch (aStatus) {
    220      case MF_MEDIAKEY_STATUS_USABLE:
    221        return dom::MediaKeyStatus::Usable;
    222      case MF_MEDIAKEY_STATUS_EXPIRED:
    223        return dom::MediaKeyStatus::Expired;
    224      case MF_MEDIAKEY_STATUS_OUTPUT_DOWNSCALED:
    225        return dom::MediaKeyStatus::Output_downscaled;
    226      // This is for legacy use and should not happen in normal cases. Map it to
    227      // internal error in case it happens.
    228      case MF_MEDIAKEY_STATUS_OUTPUT_NOT_ALLOWED:
    229        return dom::MediaKeyStatus::Internal_error;
    230      case MF_MEDIAKEY_STATUS_STATUS_PENDING:
    231        return dom::MediaKeyStatus::Status_pending;
    232      case MF_MEDIAKEY_STATUS_INTERNAL_ERROR:
    233        return dom::MediaKeyStatus::Internal_error;
    234      case MF_MEDIAKEY_STATUS_RELEASED:
    235        return dom::MediaKeyStatus::Released;
    236      case MF_MEDIAKEY_STATUS_OUTPUT_RESTRICTED:
    237        return dom::MediaKeyStatus::Output_restricted;
    238    }
    239    MOZ_ASSERT_UNREACHABLE("Invalid MF_MEDIAKEY_STATUS enum value");
    240    return dom::MediaKeyStatus::Internal_error;
    241  };
    242 
    243  CopyableTArray<MFCDMKeyInformation> keyInfos;
    244  const bool isInTesting =
    245      StaticPrefs::media_eme_wmf_use_mock_cdm_for_external_cdms();
    246  for (uint32_t idx = 0; idx < count; idx++) {
    247    const MFMediaKeyStatus& keyStatus = keyStatuses[idx];
    248    CopyableTArray<uint8_t> keyId;
    249    if (isInTesting && keyStatus.cbKeyId != sizeof(GUID)) {
    250      // Not a GUID, no need to convert it from GUID.
    251      keyId.AppendElements(keyStatus.pbKeyId, keyStatus.cbKeyId);
    252    } else if (keyStatus.cbKeyId == sizeof(GUID)) {
    253      ByteArrayFromGUID(*reinterpret_cast<const GUID*>(keyStatus.pbKeyId),
    254                        keyId);
    255    } else {
    256      LOG("Key ID with unsupported size ignored");
    257      continue;
    258    }
    259 
    260    nsAutoCString keyIdString(ToHexString(keyId));
    261    LOG("Append keyid-sz=%u, keyid=%s, status=%s", keyStatus.cbKeyId,
    262        keyIdString.get(),
    263        dom::GetEnumString(ToMediaKeyStatus(keyStatus.eMediaKeyStatus)).get());
    264    keyInfos.AppendElement(MFCDMKeyInformation{
    265        std::move(keyId), ToMediaKeyStatus(keyStatus.eMediaKeyStatus)});
    266  }
    267  LOG("Notify 'keychange' for %s", NS_ConvertUTF16toUTF8(*mSessionId).get());
    268  mKeyChangeEvent.Notify(
    269      MFCDMKeyStatusChange{*mSessionId, std::move(keyInfos)});
    270 
    271  // ScopedCoMem<MFMediaKeyStatus> only releases memory for |keyStatuses|. We
    272  // need to manually release memory for |pbKeyId| here.
    273  for (size_t idx = 0; idx < count; idx++) {
    274    if (const auto& keyStatus = keyStatuses[idx]; keyStatus.pbKeyId) {
    275      CoTaskMemFree(keyStatus.pbKeyId);
    276    }
    277  }
    278 }
    279 
    280 HRESULT MFCDMSession::UpdateExpirationIfNeeded() {
    281  AssertOnManagerThread();
    282  MOZ_ASSERT(mSessionId);
    283 
    284  // The msdn document doesn't mention the unit for the expiration time,
    285  // follow chromium's implementation to treat them as millisecond.
    286  double newExpiredEpochTimeMs = 0.0;
    287  RETURN_IF_FAILED(mSession->GetExpiration(&newExpiredEpochTimeMs));
    288 
    289  if (newExpiredEpochTimeMs == mExpiredTimeMilliSecondsSinceEpoch ||
    290      (std::isnan(newExpiredEpochTimeMs) &&
    291       std::isnan(mExpiredTimeMilliSecondsSinceEpoch))) {
    292    return S_OK;
    293  }
    294 
    295  LOG("Session expiration change from %f to %f, notify 'expiration' for %s",
    296      mExpiredTimeMilliSecondsSinceEpoch, newExpiredEpochTimeMs,
    297      NS_ConvertUTF16toUTF8(*mSessionId).get());
    298  mExpiredTimeMilliSecondsSinceEpoch = newExpiredEpochTimeMs;
    299  mExpirationEvent.Notify(
    300      MFCDMKeyExpiration{*mSessionId, mExpiredTimeMilliSecondsSinceEpoch});
    301  return S_OK;
    302 }
    303 
    304 void MFCDMSession::OnSessionKeyMessage(
    305    const MF_MEDIAKEYSESSION_MESSAGETYPE& aType,
    306    const nsTArray<uint8_t>& aMessage) {
    307  AssertOnManagerThread();
    308  // Only send key message after the session Id is ready.
    309  if (!RetrieveSessionId()) {
    310    return;
    311  }
    312  static auto ToMediaKeyMessageType = [](MF_MEDIAKEYSESSION_MESSAGETYPE aType) {
    313    switch (aType) {
    314      case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_REQUEST:
    315        return dom::MediaKeyMessageType::License_request;
    316      case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_RENEWAL:
    317        return dom::MediaKeyMessageType::License_renewal;
    318      case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_RELEASE:
    319        return dom::MediaKeyMessageType::License_release;
    320      case MF_MEDIAKEYSESSION_MESSAGETYPE_INDIVIDUALIZATION_REQUEST:
    321        return dom::MediaKeyMessageType::Individualization_request;
    322      default:
    323        MOZ_CRASH("Unknown session message type");
    324    }
    325  };
    326  LOG("Notify 'keymessage' for %s", NS_ConvertUTF16toUTF8(*mSessionId).get());
    327  mKeyMessageEvent.Notify(MFCDMKeyMessage{
    328      *mSessionId, ToMediaKeyMessageType(aType), std::move(aMessage)});
    329 }
    330 
    331 #undef LOG
    332 
    333 }  // namespace mozilla