/* 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 "WMFClearKeyDecryptor.h"
#include <mfapi.h>
#include <mferror.h>
#include <vector>
#include "WMFClearKeyCDM.h"
#include "WMFDecryptedBlock.h"
#include "content_decryption_module.h"
namespace mozilla {
using Microsoft::WRL::ComPtr;
WMFClearKeyDecryptor::~WMFClearKeyDecryptor() { ENTRY_LOG(); }
HRESULT WMFClearKeyDecryptor::RuntimeClassInitialize(
SessionManagerWrapper* aSessionManager) {
ENTRY_LOG();
MOZ_ASSERT(aSessionManager);
mSessionManager = aSessionManager;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetStreamLimits(DWORD* aInputMin,
DWORD* aInputMax,
DWORD* aOutputMin,
DWORD* aOutputMax) {
*aInputMin = *aInputMax = *aOutputMin = *aOutputMax =
1;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetStreamCount(DWORD* aInputStreams,
DWORD* aOutputStreams) {
*aInputStreams = *aOutputStreams =
1;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetStreamIDs(DWORD aInputSize,
DWORD* aInputIDs,
DWORD aOutputSize,
DWORD* aOutputIDs) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::GetInputStreamInfo(
DWORD aInputStreamID, MFT_INPUT_STREAM_INFO* aStreamInfo) {
if (aInputStreamID !=
0) {
return MF_E_INVALIDSTREAMNUMBER;
}
aStreamInfo->hnsMaxLatency =
0;
aStreamInfo->dwFlags =
0;
aStreamInfo->cbSize =
0;
aStreamInfo->cbMaxLookahead =
0;
aStreamInfo->cbAlignment =
0;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetOutputStreamInfo(
DWORD aOutputStreamID, MFT_OUTPUT_STREAM_INFO* aStreamInfo) {
if (aOutputStreamID !=
0) {
return MF_E_INVALIDSTREAMNUMBER;
}
aStreamInfo->dwFlags = MFT_OUTPUT_STREAM_PROVIDES_SAMPLES;
aStreamInfo->cbSize =
0;
aStreamInfo->cbAlignment =
0;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetAttributes(IMFAttributes** aAttributes) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::GetInputStreamAttributes(
DWORD aInputStreamID, IMFAttributes** aAttributes) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::GetOutputStreamAttributes(
DWORD aOutputStreamID, IMFAttributes** aAttributes) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::DeleteInputStream(DWORD aStreamID) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::AddInputStreams(DWORD aStreams,
DWORD* aStreamIDs) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::GetInputAvailableType(DWORD aInputStreamID,
DWORD aTypeIndex,
IMFMediaType** aType) {
std::lock_guard<std::mutex> lock(mMutex);
if (aInputStreamID !=
0) {
return MF_E_INVALIDSTREAMNUMBER;
}
if (aTypeIndex !=
0 || !mInputType) {
return MF_E_NO_MORE_TYPES;
}
return mInputType.CopyTo(aType);
}
STDMETHODIMP WMFClearKeyDecryptor::GetOutputAvailableType(
DWORD aOutputStreamID, DWORD aTypeIndex, IMFMediaType** aType) {
std::lock_guard<std::mutex> lock(mMutex);
if (aOutputStreamID !=
0) {
return MF_E_INVALIDSTREAMNUMBER;
}
if (aTypeIndex !=
0 || !mOutputType) {
return MF_E_NO_MORE_TYPES;
}
return mOutputType.CopyTo(aType);
}
STDMETHODIMP WMFClearKeyDecryptor::SetInputType(DWORD aInputStreamID,
IMFMediaType* aType,
DWORD aFlags) {
if (aInputStreamID !=
0) {
return MF_E_INVALIDSTREAMNUMBER;
}
std::lock_guard<std::mutex> lock(mMutex);
if (aFlags & MFT_SET_TYPE_TEST_ONLY) {
return S_OK;
}
// Unwrap the protected media type to get the real underlying media type.
ComPtr<IMFMediaType> unwrapped;
if (aType && SUCCEEDED(MFUnwrapMediaType(aType, &unwrapped))) {
mInputType = unwrapped;
}
else {
mInputType = aType;
}
if (!mOutputType && mInputType) {
// Mirror input type as output type.
RETURN_IF_FAILED(MFCreateMediaType(&mOutputType));
RETURN_IF_FAILED(mInputType->CopyAllItems(mOutputType.Get()));
}
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::SetOutputType(DWORD aOutputStreamID,
IMFMediaType* aType,
DWORD aFlags) {
if (aOutputStreamID !=
0) {
return MF_E_INVALIDSTREAMNUMBER;
}
std::lock_guard<std::mutex> lock(mMutex);
if (aFlags & MFT_SET_TYPE_TEST_ONLY) {
return S_OK;
}
mOutputType = aType;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetInputCurrentType(DWORD aInputStreamID,
IMFMediaType** aType) {
std::lock_guard<std::mutex> lock(mMutex);
if (aInputStreamID !=
0) {
return MF_E_INVALIDSTREAMNUMBER;
}
if (!mInputType) {
return MF_E_TRANSFORM_TYPE_NOT_SET;
}
return mInputType.CopyTo(aType);
}
STDMETHODIMP WMFClearKeyDecryptor::GetOutputCurrentType(DWORD aOutputStreamID,
IMFMediaType** aType) {
std::lock_guard<std::mutex> lock(mMutex);
if (aOutputStreamID !=
0) {
return MF_E_INVALIDSTREAMNUMBER;
}
if (!mOutputType) {
return MF_E_TRANSFORM_TYPE_NOT_SET;
}
return mOutputType.CopyTo(aType);
}
STDMETHODIMP WMFClearKeyDecryptor::GetInputStatus(DWORD aInputStreamID,
DWORD* aFlags) {
std::lock_guard<std::mutex> lock(mMutex);
if (aInputStreamID !=
0) {
return MF_E_INVALIDSTREAMNUMBER;
}
*aFlags = mInputSample ?
0 : MFT_INPUT_STATUS_ACCEPT_DATA;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetOutputStatus(DWORD* aFlags) {
std::lock_guard<std::mutex> lock(mMutex);
*aFlags = mInputSample ? MFT_OUTPUT_STATUS_SAMPLE_READY :
0;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::SetOutputBounds(LONGLONG aLowerBound,
LONGLONG aUpperBound) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::ProcessEvent(DWORD aInputStreamID,
IMFMediaEvent* aEvent) {
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::ProcessMessage(MFT_MESSAGE_TYPE aMessage,
ULONG_PTR aParam) {
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::ProcessInput(DWORD aInputStreamID,
IMFSample* aSample,
DWORD aFlags) {
ENTRY_LOG();
if (aInputStreamID !=
0) {
return MF_E_INVALIDSTREAMNUMBER;
}
if (!aSample) {
return E_INVALIDARG;
}
std::lock_guard<std::mutex> lock(mMutex);
if (mInputSample) {
return MF_E_NOTACCEPTING;
}
mInputSample = aSample;
return S_OK;
}
HRESULT WMFClearKeyDecryptor::DecryptSample(IMFSample* aEncryptedSample,
IMFSample** aDecryptedSample) {
ENTRY_LOG();
// Extract IV (stored as the SampleID blob, 16 bytes).
BYTE* ivData = nullptr;
UINT32 ivSize =
0;
HRESULT hr = aEncryptedSample->GetAllocatedBlob(
MFSampleExtension_Encryption_SampleID, &ivData, &ivSize);
const bool isEncrypted = SUCCEEDED(hr) && ivSize >
0;
LOG(
"isEncrypted=%d, ivSize=%u", isEncrypted, ivSize);
// Copy all buffer data from the sample.
DWORD totalLen =
0;
RETURN_IF_FAILED(aEncryptedSample->GetTotalLength(&totalLen));
std::vector<uint8_t> sampleData(totalLen);
{
ComPtr<IMFMediaBuffer> contiguousBuffer;
RETURN_IF_FAILED(
aEncryptedSample->ConvertToContiguousBuffer(&contiguousBuffer));
BYTE* bufferData = nullptr;
DWORD currentLen =
0;
RETURN_IF_FAILED(contiguousBuffer->Lock(&bufferData, nullptr, ¤tLen));
if (currentLen >
0) {
memcpy(sampleData.data(), bufferData, currentLen);
}
contiguousBuffer->Unlock();
}
WMFDecryptedBlock decryptedBlock;
if (isEncrypted) {
// Extract key ID (stored as a GUID).
GUID keyIdGuid = GUID_NULL;
RETURN_IF_FAILED(
aEncryptedSample->GetGUID(MFSampleExtension_Content_KeyID, &keyIdGuid));
uint8_t keyId[
16];
GuidToKeyId(keyIdGuid, keyId);
// Extract subsample mapping if present.
BYTE* subsampleData = nullptr;
UINT32 subsampleDataSize =
0;
std::vector<cdm::SubsampleEntry> subsamples;
if (SUCCEEDED(aEncryptedSample->GetAllocatedBlob(
MFSampleExtension_Encryption_SubSample_Mapping, &subsampleData,
&subsampleDataSize)) &&
subsampleDataSize >=
8) {
// Each entry is {DWORD clearBytes, DWORD cipherBytes} = 8 bytes.
const DWORD numEntries = subsampleDataSize /
8;
subsamples.resize(numEntries);
for (DWORD i =
0; i < numEntries; i++) {
DWORD clearBytes =
0, cipherBytes =
0;
memcpy(&clearBytes, subsampleData + i *
8,
sizeof(DWORD));
memcpy(&cipherBytes, subsampleData + i *
8 +
sizeof(DWORD),
sizeof(DWORD));
subsamples[i].clear_bytes = clearBytes;
subsamples[i].cipher_bytes = cipherBytes;
}
CoTaskMemFree(subsampleData);
}
else {
if (subsampleData) {
CoTaskMemFree(subsampleData);
}
// No subsample info: treat the whole buffer as cipher bytes.
cdm::SubsampleEntry entry;
entry.clear_bytes =
0;
entry.cipher_bytes = totalLen;
subsamples.push_back(entry);
}
LONGLONG sampleTime =
0;
aEncryptedSample->GetSampleTime(&sampleTime);
cdm::InputBuffer_2 inputBuffer = {};
inputBuffer.data = sampleData.data();
inputBuffer.data_size = static_cast<uint32_t>(sampleData.size());
inputBuffer.encryption_scheme = cdm::EncryptionScheme::kCenc;
inputBuffer.key_id = keyId;
inputBuffer.key_id_size =
16;
inputBuffer.iv = ivData;
inputBuffer.iv_size = ivSize;
inputBuffer.subsamples = subsamples.data();
inputBuffer.num_subsamples = static_cast<uint32_t>(subsamples.size());
inputBuffer.timestamp = sampleTime;
HRESULT decryptHr = mSessionManager->Decrypt(inputBuffer, &decryptedBlock);
LOG(
"Decrypt hr=%lx", static_cast<
long>(decryptHr));
CoTaskMemFree(ivData);
RETURN_IF_FAILED(decryptHr);
}
else {
if (ivData) {
CoTaskMemFree(ivData);
}
LOG(
"Clear sample passthrough, size=%lu", totalLen);
WMFDecryptedBuffer* buffer =
new WMFDecryptedBuffer(static_cast<uint32_t>(sampleData.size()));
memcpy(buffer->Data(), sampleData.data(), sampleData.size());
decryptedBlock.SetDecryptedBuffer(buffer);
}
cdm::Buffer* decryptedBuffer = decryptedBlock.DecryptedBuffer();
if (!decryptedBuffer || !decryptedBuffer->Data()) {
LOG(
"Decrypt produced null or empty buffer");
return E_FAIL;
}
// Wrap decrypted data in a new IMFSample.
ComPtr<IMFSample> outputSample;
RETURN_IF_FAILED(MFCreateSample(&outputSample));
ComPtr<IMFMediaBuffer> outputBuffer;
RETURN_IF_FAILED(MFCreateMemoryBuffer(
static_cast<DWORD>(decryptedBuffer->Size()), &outputBuffer));
BYTE* outputData = nullptr;
RETURN_IF_FAILED(outputBuffer->Lock(&outputData, nullptr, nullptr));
memcpy(outputData, decryptedBuffer->Data(), decryptedBuffer->Size());
outputBuffer->Unlock();
RETURN_IF_FAILED(outputBuffer->SetCurrentLength(
static_cast<DWORD>(decryptedBuffer->Size())));
RETURN_IF_FAILED(outputSample->AddBuffer(outputBuffer.Get()));
LONGLONG sampleTime =
0;
LONGLONG sampleDuration =
0;
if (SUCCEEDED(aEncryptedSample->GetSampleTime(&sampleTime))) {
outputSample->SetSampleTime(sampleTime);
}
if (SUCCEEDED(aEncryptedSample->GetSampleDuration(&sampleDuration))) {
outputSample->SetSampleDuration(sampleDuration);
}
// Propagate the keyframe flag so the downstream H264 decoder knows which
// samples are IDR frames.
UINT32 isCleanPoint =
0;
if (SUCCEEDED(aEncryptedSample->GetUINT32(MFSampleExtension_CleanPoint,
&isCleanPoint)) &&
isCleanPoint) {
outputSample->SetUINT32(MFSampleExtension_CleanPoint,
1);
}
*aDecryptedSample = outputSample.Detach();
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::ProcessOutput(
DWORD aFlags, DWORD aOutputBufferCount,
MFT_OUTPUT_DATA_BUFFER* aOutputSamples, DWORD* aStatus) {
ENTRY_LOG();
*aStatus =
0;
if (aOutputBufferCount !=
1) {
LOG(
"Invalid output buffer count: %lu", aOutputBufferCount);
return E_INVALIDARG;
}
ComPtr<IMFSample> encryptedSample;
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mInputSample) {
LOG(
"No input sample available");
return MF_E_TRANSFORM_NEED_MORE_INPUT;
}
encryptedSample = std::move(mInputSample);
}
// The MFT allocates output samples (MFT_OUTPUT_STREAM_PROVIDES_SAMPLES).
MOZ_ASSERT(!aOutputSamples[
0].pSample);
ComPtr<IMFSample> decryptedSample;
RETURN_IF_FAILED(DecryptSample(encryptedSample.Get(), &decryptedSample));
aOutputSamples[
0].pSample = decryptedSample.Detach();
return S_OK;
}
}
// namespace mozilla