Source code

Revision control

Copy as Markdown

Other Tools

/* 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 "MFCDMSession.h"
#include <limits>
#include <vcruntime.h>
#include <winerror.h>
#include "MFMediaEngineUtils.h"
#include "GMPUtils.h" // ToHexString
#include "mozilla/EMEUtils.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/MediaKeyMessageEventBinding.h"
#include "mozilla/dom/MediaKeyStatusMapBinding.h"
#include "nsThreadUtils.h"
namespace mozilla {
using Microsoft::WRL::ComPtr;
using Microsoft::WRL::MakeAndInitialize;
#define LOG(msg, ...) EME_LOG("MFCDMSession=%p, " msg, this, ##__VA_ARGS__)
static inline MF_MEDIAKEYSESSION_TYPE ConvertSessionType(
KeySystemConfig::SessionType aType) {
switch (aType) {
case KeySystemConfig::SessionType::Temporary:
return MF_MEDIAKEYSESSION_TYPE_TEMPORARY;
case KeySystemConfig::SessionType::PersistentLicense:
return MF_MEDIAKEYSESSION_TYPE_PERSISTENT_LICENSE;
}
}
static inline LPCWSTR InitDataTypeToString(const nsAString& aInitDataType) {
// The strings are defined in https://www.w3.org/TR/eme-initdata-registry/
if (aInitDataType.EqualsLiteral("webm")) {
return L"webm";
} else if (aInitDataType.EqualsLiteral("cenc")) {
return L"cenc";
} else if (aInitDataType.EqualsLiteral("keyids")) {
return L"keyids";
} else {
return L"unknown";
}
}
// The callback interface which IMFContentDecryptionModuleSession uses for
// communicating with the session.
class MFCDMSession::SessionCallbacks final
: public Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
IMFContentDecryptionModuleSessionCallbacks> {
public:
SessionCallbacks() { MOZ_COUNT_CTOR(SessionCallbacks); };
SessionCallbacks(const SessionCallbacks&) = delete;
SessionCallbacks& operator=(const SessionCallbacks&) = delete;
~SessionCallbacks() { MOZ_COUNT_DTOR(SessionCallbacks); }
HRESULT RuntimeClassInitialize() { return S_OK; }
// IMFContentDecryptionModuleSessionCallbacks
STDMETHODIMP KeyMessage(MF_MEDIAKEYSESSION_MESSAGETYPE aType,
const BYTE* aMessage, DWORD aMessageSize,
LPCWSTR aUrl) final {
CopyableTArray<uint8_t> msg{static_cast<const uint8_t*>(aMessage),
aMessageSize};
mKeyMessageEvent.Notify(aType, std::move(msg));
return S_OK;
}
STDMETHODIMP KeyStatusChanged() final {
mKeyChangeEvent.Notify();
return S_OK;
}
MediaEventSource<MF_MEDIAKEYSESSION_MESSAGETYPE, nsTArray<uint8_t>>&
KeyMessageEvent() {
return mKeyMessageEvent;
}
MediaEventSource<void>& KeyChangeEvent() { return mKeyChangeEvent; }
private:
MediaEventProducer<MF_MEDIAKEYSESSION_MESSAGETYPE, nsTArray<uint8_t>>
mKeyMessageEvent;
MediaEventProducer<void> mKeyChangeEvent;
};
/* static*/
MFCDMSession* MFCDMSession::Create(KeySystemConfig::SessionType aSessionType,
IMFContentDecryptionModule* aCdm,
nsISerialEventTarget* aManagerThread) {
MOZ_ASSERT(aCdm);
MOZ_ASSERT(aManagerThread);
ComPtr<SessionCallbacks> callbacks;
RETURN_PARAM_IF_FAILED(MakeAndInitialize<SessionCallbacks>(&callbacks),
nullptr);
ComPtr<IMFContentDecryptionModuleSession> session;
RETURN_PARAM_IF_FAILED(aCdm->CreateSession(ConvertSessionType(aSessionType),
callbacks.Get(), &session),
nullptr);
return new MFCDMSession(session.Get(), callbacks.Get(), aManagerThread);
}
MFCDMSession::MFCDMSession(IMFContentDecryptionModuleSession* aSession,
SessionCallbacks* aCallback,
nsISerialEventTarget* aManagerThread)
: mSession(aSession),
mManagerThread(aManagerThread),
mExpiredTimeMilliSecondsSinceEpoch(
std::numeric_limits<double>::quiet_NaN()) {
MOZ_ASSERT(aSession);
MOZ_ASSERT(aCallback);
MOZ_ASSERT(aManagerThread);
MOZ_COUNT_CTOR(MFCDMSession);
LOG("MFCDMSession created");
mKeyMessageListener = aCallback->KeyMessageEvent().Connect(
mManagerThread, this, &MFCDMSession::OnSessionKeyMessage);
mKeyChangeListener = aCallback->KeyChangeEvent().Connect(
mManagerThread, this, &MFCDMSession::OnSessionKeysChange);
}
MFCDMSession::~MFCDMSession() {
MOZ_COUNT_DTOR(MFCDMSession);
LOG("MFCDMSession destroyed");
// TODO : maybe disconnect them in `Close()`?
mKeyChangeListener.DisconnectIfExists();
mKeyMessageListener.DisconnectIfExists();
}
HRESULT MFCDMSession::GenerateRequest(const nsAString& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize) {
AssertOnManagerThread();
LOG("GenerateRequest for %s (init sz=%u)",
NS_ConvertUTF16toUTF8(aInitDataType).get(), aInitDataSize);
RETURN_IF_FAILED(mSession->GenerateRequest(
InitDataTypeToString(aInitDataType), aInitData, aInitDataSize));
Unused << RetrieveSessionId();
return S_OK;
}
HRESULT MFCDMSession::Load(const nsAString& aSessionId) {
AssertOnManagerThread();
// TODO : do we need to implement this? Chromium doesn't implement this one.
// Also, how do we know is this given session ID is equal to the session Id
// asked from CDM session or not?
BOOL rv = FALSE;
mSession->Load(char16ptr_t(aSessionId.BeginReading()), &rv);
LOG("Load, id=%s, rv=%s", NS_ConvertUTF16toUTF8(aSessionId).get(),
rv ? "success" : "fail");
return rv ? S_OK : S_FALSE;
}
HRESULT MFCDMSession::Update(const nsTArray<uint8_t>& aMessage) {
AssertOnManagerThread();
LOG("Update");
RETURN_IF_FAILED(mSession->Update(
static_cast<const BYTE*>(aMessage.Elements()), aMessage.Length()));
RETURN_IF_FAILED(UpdateExpirationIfNeeded());
return S_OK;
}
HRESULT MFCDMSession::Close() {
AssertOnManagerThread();
LOG("Close");
RETURN_IF_FAILED(mSession->Close());
return S_OK;
}
HRESULT MFCDMSession::Remove() {
AssertOnManagerThread();
LOG("Remove");
RETURN_IF_FAILED(mSession->Remove());
RETURN_IF_FAILED(UpdateExpirationIfNeeded());
return S_OK;
}
bool MFCDMSession::RetrieveSessionId() {
AssertOnManagerThread();
if (mSessionId) {
return true;
}
ScopedCoMem<wchar_t> sessionId;
if (FAILED(mSession->GetSessionId(&sessionId)) || !sessionId) {
LOG("Can't get session id or empty session ID!");
return false;
}
LOG("Set session Id %ls", sessionId.Get());
mSessionId = Some(sessionId.Get());
return true;
}
void MFCDMSession::OnSessionKeysChange() {
AssertOnManagerThread();
LOG("OnSessionKeysChange");
if (!mSessionId) {
LOG("Unexpected session keys change ignored");
return;
}
ScopedCoMem<MFMediaKeyStatus> keyStatuses;
UINT count = 0;
RETURN_VOID_IF_FAILED(mSession->GetKeyStatuses(&keyStatuses, &count));
static auto ToMediaKeyStatus = [](MF_MEDIAKEY_STATUS aStatus) {
switch (aStatus) {
case MF_MEDIAKEY_STATUS_USABLE:
return dom::MediaKeyStatus::Usable;
case MF_MEDIAKEY_STATUS_EXPIRED:
return dom::MediaKeyStatus::Expired;
case MF_MEDIAKEY_STATUS_OUTPUT_DOWNSCALED:
return dom::MediaKeyStatus::Output_downscaled;
// This is for legacy use and should not happen in normal cases. Map it to
// internal error in case it happens.
case MF_MEDIAKEY_STATUS_OUTPUT_NOT_ALLOWED:
return dom::MediaKeyStatus::Internal_error;
case MF_MEDIAKEY_STATUS_STATUS_PENDING:
return dom::MediaKeyStatus::Status_pending;
case MF_MEDIAKEY_STATUS_INTERNAL_ERROR:
return dom::MediaKeyStatus::Internal_error;
case MF_MEDIAKEY_STATUS_RELEASED:
return dom::MediaKeyStatus::Released;
case MF_MEDIAKEY_STATUS_OUTPUT_RESTRICTED:
return dom::MediaKeyStatus::Output_restricted;
}
MOZ_ASSERT_UNREACHABLE("Invalid MF_MEDIAKEY_STATUS enum value");
return dom::MediaKeyStatus::Internal_error;
};
CopyableTArray<MFCDMKeyInformation> keyInfos;
const bool isInTesting =
StaticPrefs::media_eme_wmf_use_mock_cdm_for_external_cdms();
for (uint32_t idx = 0; idx < count; idx++) {
const MFMediaKeyStatus& keyStatus = keyStatuses[idx];
CopyableTArray<uint8_t> keyId;
if (isInTesting && keyStatus.cbKeyId != sizeof(GUID)) {
// Not a GUID, no need to convert it from GUID.
keyId.AppendElements(keyStatus.pbKeyId, keyStatus.cbKeyId);
} else if (keyStatus.cbKeyId == sizeof(GUID)) {
ByteArrayFromGUID(*reinterpret_cast<const GUID*>(keyStatus.pbKeyId),
keyId);
} else {
LOG("Key ID with unsupported size ignored");
continue;
}
nsAutoCString keyIdString(ToHexString(keyId));
LOG("Append keyid-sz=%u, keyid=%s, status=%s", keyStatus.cbKeyId,
keyIdString.get(),
dom::GetEnumString(ToMediaKeyStatus(keyStatus.eMediaKeyStatus)).get());
keyInfos.AppendElement(MFCDMKeyInformation{
std::move(keyId), ToMediaKeyStatus(keyStatus.eMediaKeyStatus)});
}
LOG("Notify 'keychange' for %s", NS_ConvertUTF16toUTF8(*mSessionId).get());
mKeyChangeEvent.Notify(
MFCDMKeyStatusChange{*mSessionId, std::move(keyInfos)});
// ScopedCoMem<MFMediaKeyStatus> only releases memory for |keyStatuses|. We
// need to manually release memory for |pbKeyId| here.
for (size_t idx = 0; idx < count; idx++) {
if (const auto& keyStatus = keyStatuses[idx]; keyStatus.pbKeyId) {
CoTaskMemFree(keyStatus.pbKeyId);
}
}
}
HRESULT MFCDMSession::UpdateExpirationIfNeeded() {
AssertOnManagerThread();
MOZ_ASSERT(mSessionId);
// The msdn document doesn't mention the unit for the expiration time,
// follow chromium's implementation to treat them as millisecond.
double newExpiredEpochTimeMs = 0.0;
RETURN_IF_FAILED(mSession->GetExpiration(&newExpiredEpochTimeMs));
if (newExpiredEpochTimeMs == mExpiredTimeMilliSecondsSinceEpoch ||
(std::isnan(newExpiredEpochTimeMs) &&
std::isnan(mExpiredTimeMilliSecondsSinceEpoch))) {
return S_OK;
}
LOG("Session expiration change from %f to %f, notify 'expiration' for %s",
mExpiredTimeMilliSecondsSinceEpoch, newExpiredEpochTimeMs,
NS_ConvertUTF16toUTF8(*mSessionId).get());
mExpiredTimeMilliSecondsSinceEpoch = newExpiredEpochTimeMs;
mExpirationEvent.Notify(
MFCDMKeyExpiration{*mSessionId, mExpiredTimeMilliSecondsSinceEpoch});
return S_OK;
}
void MFCDMSession::OnSessionKeyMessage(
const MF_MEDIAKEYSESSION_MESSAGETYPE& aType,
const nsTArray<uint8_t>& aMessage) {
AssertOnManagerThread();
// Only send key message after the session Id is ready.
if (!RetrieveSessionId()) {
return;
}
static auto ToMediaKeyMessageType = [](MF_MEDIAKEYSESSION_MESSAGETYPE aType) {
switch (aType) {
case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_REQUEST:
return dom::MediaKeyMessageType::License_request;
case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_RENEWAL:
return dom::MediaKeyMessageType::License_renewal;
case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_RELEASE:
return dom::MediaKeyMessageType::License_release;
case MF_MEDIAKEYSESSION_MESSAGETYPE_INDIVIDUALIZATION_REQUEST:
return dom::MediaKeyMessageType::Individualization_request;
default:
MOZ_CRASH("Unknown session message type");
}
};
LOG("Notify 'keymessage' for %s", NS_ConvertUTF16toUTF8(*mSessionId).get());
mKeyMessageEvent.Notify(MFCDMKeyMessage{
*mSessionId, ToMediaKeyMessageType(aType), std::move(aMessage)});
}
#undef LOG
} // namespace mozilla