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