diff options
Diffstat (limited to 'media/wmf-clearkey/WMFClearKeyCDM.cpp')
-rw-r--r-- | media/wmf-clearkey/WMFClearKeyCDM.cpp | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/media/wmf-clearkey/WMFClearKeyCDM.cpp b/media/wmf-clearkey/WMFClearKeyCDM.cpp new file mode 100644 index 0000000000..b782aec5da --- /dev/null +++ b/media/wmf-clearkey/WMFClearKeyCDM.cpp @@ -0,0 +1,371 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WMFClearKeyCDM.h" + +#include <Mferror.h> +#include <mfapi.h> +#include <oleauto.h> +#include <optional> +#include <windows.h> +#include <windows.media.h> + +#include "WMFClearKeyTrustedInput.h" +#include "WMFClearKeySession.h" +#include "WMFDecryptedBlock.h" +#include "WMFPMPServer.h" + +using Microsoft::WRL::ComPtr; +using Microsoft::WRL::MakeAndInitialize; + +static HRESULT AddPropertyToSet( + ABI::Windows::Foundation::Collections::IPropertySet* aPropertySet, + LPCWSTR aName, IInspectable* aInspectable) { + boolean replaced = false; + ComPtr<ABI::Windows::Foundation::Collections::IMap<HSTRING, IInspectable*>> + map; + RETURN_IF_FAILED(aPropertySet->QueryInterface(IID_PPV_ARGS(&map))); + RETURN_IF_FAILED( + map->Insert(Microsoft::WRL::Wrappers::HStringReference(aName).Get(), + aInspectable, &replaced)); + return S_OK; +} + +static HRESULT AddStringToPropertySet( + ABI::Windows::Foundation::Collections::IPropertySet* aPropertySet, + LPCWSTR aName, LPCWSTR aString) { + ComPtr<ABI::Windows::Foundation::IPropertyValue> propertyValue; + ComPtr<ABI::Windows::Foundation::IPropertyValueStatics> propertyValueStatics; + RETURN_IF_FAILED(ABI::Windows::Foundation::GetActivationFactory( + Microsoft::WRL::Wrappers::HStringReference( + RuntimeClass_Windows_Foundation_PropertyValue) + .Get(), + &propertyValueStatics)); + RETURN_IF_FAILED(propertyValueStatics->CreateString( + Microsoft::WRL::Wrappers::HStringReference(aString).Get(), + &propertyValue)); + RETURN_IF_FAILED(AddPropertyToSet(aPropertySet, aName, propertyValue.Get())); + return S_OK; +} + +static HRESULT AddBoolToPropertySet( + ABI::Windows::Foundation::Collections::IPropertySet* aPropertySet, + LPCWSTR aName, BOOL aValue) { + ComPtr<ABI::Windows::Foundation::IPropertyValue> propertyValue; + ComPtr<ABI::Windows::Foundation::IPropertyValueStatics> propertyValueStatics; + RETURN_IF_FAILED(ABI::Windows::Foundation::GetActivationFactory( + Microsoft::WRL::Wrappers::HStringReference( + RuntimeClass_Windows_Foundation_PropertyValue) + .Get(), + &propertyValueStatics)); + RETURN_IF_FAILED( + propertyValueStatics->CreateBoolean(!!aValue, &propertyValue)); + RETURN_IF_FAILED(AddPropertyToSet(aPropertySet, aName, propertyValue.Get())); + return S_OK; +} + +MF_MEDIAKEYSESSION_MESSAGETYPE ToMFMessageType(cdm::MessageType aMessageType) { + switch (aMessageType) { + case cdm::MessageType::kLicenseRequest: + return MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_REQUEST; + case cdm::MessageType::kLicenseRenewal: + return MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_RENEWAL; + case cdm::MessageType::kLicenseRelease: + return MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_RELEASE; + case cdm::MessageType::kIndividualizationRequest: + return MF_MEDIAKEYSESSION_MESSAGETYPE_INDIVIDUALIZATION_REQUEST; + } +} + +namespace mozilla { + +HRESULT WMFClearKeyCDM::RuntimeClassInitialize(IPropertyStore* aProperties) { + ENTRY_LOG(); + // A workaround in order to create an in-process PMP server regardless of the + // system's HWDRM capability. As accoriding to Microsoft, only PlayReady is + // supported for the in-process PMP so we pretend ourselves as a PlayReady + // CDM for the PMP server. + ComPtr<ABI::Windows::Foundation::Collections::IPropertySet> propertyPmp; + RETURN_IF_FAILED(Windows::Foundation::ActivateInstance( + Microsoft::WRL::Wrappers::HStringReference( + RuntimeClass_Windows_Foundation_Collections_PropertySet) + .Get(), + &propertyPmp)); + RETURN_IF_FAILED(AddStringToPropertySet( + propertyPmp.Get(), L"Windows.Media.Protection.MediaProtectionSystemId", + PLAYREADY_GUID_MEDIA_PROTECTION_SYSTEM_ID_STRING)); + RETURN_IF_FAILED(AddBoolToPropertySet( + propertyPmp.Get(), L"Windows.Media.Protection.UseHardwareProtectionLayer", + TRUE)); + RETURN_IF_FAILED((MakeAndInitialize< + WMFPMPServer, + ABI::Windows::Media::Protection::IMediaProtectionPMPServer>( + &mPMPServer, propertyPmp.Get()))); + + mSessionManager = new SessionManagerWrapper(this); + return S_OK; +} + +WMFClearKeyCDM::~WMFClearKeyCDM() { ENTRY_LOG(); } + +STDMETHODIMP WMFClearKeyCDM::SetContentEnabler( + IMFContentEnabler* aContentEnabler, IMFAsyncResult* aResult) { + ENTRY_LOG(); + if (!aContentEnabler || !aResult) { + return E_INVALIDARG; + } + // Invoke the callback immediately but will determine whether the keyid exists + // or not in the decryptor's ProcessOutput(). + RETURN_IF_FAILED(MFInvokeCallback(aResult)); + return S_OK; +} + +STDMETHODIMP WMFClearKeyCDM::SetPMPHostApp(IMFPMPHostApp* aPmpHostApp) { + ENTRY_LOG(); + // Simply return S_OK and ignore IMFPMPHostApp, which is only used for the + // out-of-process PMP. + return S_OK; +} + +STDMETHODIMP WMFClearKeyCDM::CreateSession( + MF_MEDIAKEYSESSION_TYPE aSessionType, + IMFContentDecryptionModuleSessionCallbacks* aCallbacks, + IMFContentDecryptionModuleSession** aSession) { + ENTRY_LOG(); + RETURN_IF_FAILED( + (MakeAndInitialize<WMFClearKeySession, IMFContentDecryptionModuleSession>( + aSession, aSessionType, aCallbacks, mSessionManager))); + return S_OK; +} + +STDMETHODIMP WMFClearKeyCDM::CreateTrustedInput( + const BYTE* aContentInitData, DWORD aContentInitDataSize, + IMFTrustedInput** aTrustedInput) { + ENTRY_LOG(); + ComPtr<IMFTrustedInput> trustedInput; + RETURN_IF_FAILED((MakeAndInitialize<WMFClearKeyTrustedInput, IMFTrustedInput>( + &trustedInput, mSessionManager))); + *aTrustedInput = trustedInput.Detach(); + return S_OK; +} + +STDMETHODIMP WMFClearKeyCDM::GetProtectionSystemIds(GUID** aSystemIds, + DWORD* aCount) { + ENTRY_LOG(); + GUID* systemId = static_cast<GUID*>(CoTaskMemAlloc(sizeof(GUID))); + if (!systemId) { + return E_OUTOFMEMORY; + } + *systemId = CLEARKEY_GUID_CLEARKEY_PROTECTION_SYSTEM_ID; + *aSystemIds = systemId; + *aCount = 1; + return S_OK; +} + +STDMETHODIMP WMFClearKeyCDM::GetService(REFGUID aGuidService, REFIID aRiid, + LPVOID* aPpvObject) { + ENTRY_LOG(); + if (MF_CONTENTDECRYPTIONMODULE_SERVICE != aGuidService) { + ENTRY_LOG_ARGS("unsupported guid!"); + return MF_E_UNSUPPORTED_SERVICE; + } + if (!mPMPServer) { + ENTRY_LOG_ARGS("no PMP server!"); + return MF_INVALID_STATE_ERR; + } + if (aRiid == ABI::Windows::Media::Protection::IID_IMediaProtectionPMPServer) { + RETURN_IF_FAILED(mPMPServer.CopyTo(aRiid, aPpvObject)); + } else { + ComPtr<IMFGetService> getService; + RETURN_IF_FAILED(mPMPServer.As(&getService)); + RETURN_IF_FAILED(getService->GetService(MF_PMP_SERVICE, aRiid, aPpvObject)); + } + return S_OK; +} + +STDMETHODIMP WMFClearKeyCDM::GetSuspendNotify(IMFCdmSuspendNotify** aNotify) { + NOT_IMPLEMENTED(); + return E_NOTIMPL; +} + +STDMETHODIMP WMFClearKeyCDM::SetServerCertificate(const BYTE* aCertificate, + DWORD aCertificateSize) { + NOT_IMPLEMENTED(); + return E_NOTIMPL; +} + +STDMETHODIMP WMFClearKeyCDM::Shutdown() { + ENTRY_LOG(); + mSessionManager->Shutdown(); + return S_OK; +} + +STDMETHODIMP WMFClearKeyCDM::GetShutdownStatus(MFSHUTDOWN_STATUS* aStatus) { + ENTRY_LOG(); + // https://learn.microsoft.com/en-us/windows/win32/api/mfidl/nf-mfidl-imfshutdown-getshutdownstatus#return-value + if (mSessionManager->IsShutdown()) { + return MF_E_INVALIDREQUEST; + } + return S_OK; +} + +// SessionManagerWrapper + +SessionManagerWrapper::SessionManagerWrapper(WMFClearKeyCDM* aCDM) + : mOwnerCDM(aCDM), mSessionManager(new ClearKeySessionManager(this)) { + // For testing, we don't care about these. + mSessionManager->Init(false /* aDistinctiveIdentifierAllowed */, + false /* aPersistentStateAllowed*/); +} + +SessionManagerWrapper::~SessionManagerWrapper() { ENTRY_LOG(); } + +// Callback methods +void SessionManagerWrapper::OnResolveNewSessionPromise( + uint32_t aPromiseId, const char* aSessionId, uint32_t aSessionIdSize) { + if (auto rv = mActiveSyncResultChecker.find(aPromiseId); + rv != mActiveSyncResultChecker.end()) { + LOG("Generated request (promise-id=%u, sessionId=%s)", aPromiseId, + aSessionId); + rv->second->SetResultConstChar(aSessionId); + std::string sessionId{aSessionId}; + MOZ_ASSERT(mSessions.find(sessionId) == mSessions.end()); + mSessions[sessionId] = rv->second->GetKeySession(); + } +} + +void SessionManagerWrapper::OnResolvePromise(uint32_t aPromiseId) { + if (auto rv = mActiveSyncResultChecker.find(aPromiseId); + rv != mActiveSyncResultChecker.end()) { + LOG("Resolved promise (promise-id=%u)", aPromiseId); + rv->second->SetResultBool(true); + } +} + +void SessionManagerWrapper::OnRejectPromise(uint32_t aPromiseId, + cdm::Exception aException, + uint32_t aSystemCode, + const char* aErrorMessage, + uint32_t aErrorMessageSize) { + if (auto rv = mActiveSyncResultChecker.find(aPromiseId); + rv != mActiveSyncResultChecker.end()) { + LOG("Rejected promise (promise-id=%u)", aPromiseId); + rv->second->SetResultBool(false); + } +} + +HRESULT SessionManagerWrapper::GenerateRequest(cdm::InitDataType aInitDataType, + const BYTE* aInitData, + DWORD aInitDataSize, + cdm::SessionType aSessionType, + WMFClearKeySession* aSession, + std::string& aSessionIdOut) { + ENTRY_LOG(); + MOZ_DIAGNOSTIC_ASSERT(aSessionType == cdm::SessionType::kTemporary); + SyncResultChecker checker(*this, aSession); + mSessionManager->CreateSession(checker.GetPromiseId(), aInitDataType, + aInitData, aInitDataSize, aSessionType); + auto* rv = std::get_if<const char*>(&checker.GetResult()); + if (!rv) { + LOG("Failed to generate request, no session Id!"); + return E_FAIL; + } + aSessionIdOut = std::string(*rv); + return S_OK; +} + +HRESULT SessionManagerWrapper::UpdateSession(const std::string& aSessionId, + const BYTE* aResponse, + DWORD aResponseSize) { + ENTRY_LOG(); + SyncResultChecker checker(*this); + mSessionManager->UpdateSession(checker.GetPromiseId(), aSessionId.c_str(), + aSessionId.size(), aResponse, aResponseSize); + auto* rv = std::get_if<bool>(&checker.GetResult()); + return rv && *rv ? S_OK : E_FAIL; +} + +HRESULT SessionManagerWrapper::CloseSession(const std::string& aSessionId) { + ENTRY_LOG(); + SyncResultChecker checker(*this); + mSessionManager->CloseSession(checker.GetPromiseId(), aSessionId.c_str(), + aSessionId.size()); + auto* rv = std::get_if<bool>(&checker.GetResult()); + return rv && *rv ? S_OK : E_FAIL; +} + +HRESULT SessionManagerWrapper::RemoveSession(const std::string& aSessionId) { + ENTRY_LOG(); + SyncResultChecker checker(*this); + MOZ_ASSERT(mSessions.find(aSessionId) != mSessions.end()); + mSessions.erase(aSessionId); + mSessionManager->RemoveSession(checker.GetPromiseId(), aSessionId.c_str(), + aSessionId.size()); + auto* rv = std::get_if<bool>(&checker.GetResult()); + return rv && *rv ? S_OK : E_FAIL; +} + +HRESULT SessionManagerWrapper::Decrypt(const cdm::InputBuffer_2& aBuffer, + cdm::DecryptedBlock* aDecryptedBlock) { + ENTRY_LOG(); + // From MF thread pool. + std::lock_guard<std::mutex> lock(mMutex); + if (mIsShutdown) { + return MF_E_SHUTDOWN; + } + auto rv = mSessionManager->Decrypt(aBuffer, aDecryptedBlock); + return rv == cdm::kSuccess ? S_OK : E_FAIL; +} + +void SessionManagerWrapper::OnSessionMessage(const char* aSessionId, + uint32_t aSessionIdSize, + cdm::MessageType aMessageType, + const char* aMessage, + uint32_t aMessageSize) { + ENTRY_LOG_ARGS("sessionId=%s, sz=%u", aSessionId, aSessionIdSize); + std::string sessionId(aSessionId); + MOZ_ASSERT(mSessions.find(sessionId) != mSessions.end()); + mSessions[sessionId]->OnKeyMessage(ToMFMessageType(aMessageType), + reinterpret_cast<const BYTE*>(aMessage), + aMessageSize); +} + +void SessionManagerWrapper::OnSessionKeysChange( + const char* aSessionId, uint32_t aSessionIdSize, + bool aHasAdditionalUsableKey, const cdm::KeyInformation* aKeysInfo, + uint32_t aKeysInfoCount) { + ENTRY_LOG_ARGS("sessionId=%s, sz=%u", aSessionId, aSessionIdSize); + std::string sessionId(aSessionId); + MOZ_ASSERT(mSessions.find(sessionId) != mSessions.end()); + mSessions[sessionId]->OnKeyStatusChanged(aKeysInfo, aKeysInfoCount); +} + +// `ClearKeySessionManager::Decrypt` will call this method to allocate buffer +// for decryted data. +cdm::Buffer* SessionManagerWrapper::Allocate(uint32_t aCapacity) { + ENTRY_LOG_ARGS("capacity=%u", aCapacity); + return new WMFDecryptedBuffer(aCapacity); +} + +void SessionManagerWrapper::Shutdown() { + ENTRY_LOG(); + std::lock_guard<std::mutex> lock(mMutex); + if (mIsShutdown) { + return; + } + mOwnerCDM = nullptr; + for (const auto& session : mSessions) { + session.second->Shutdown(); + } + mSessions.clear(); + mSessionManager = nullptr; + mIsShutdown = true; +} + +bool SessionManagerWrapper::IsShutdown() { + std::lock_guard<std::mutex> lock(mMutex); + return mIsShutdown; +} + +} // namespace mozilla |