import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,79 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AnnexB.h"
#include "BigEndian.h"
#include <cstring>
using mozilla::BigEndian;
static const uint8_t kAnnexBDelimiter[] = { 0, 0, 0, 1 };
/* static */ void
AnnexB::ConvertFrameInPlace(std::vector<uint8_t>& aBuffer)
{
for (size_t i = 0; i < aBuffer.size() - 4 - sizeof(kAnnexBDelimiter) + 1; ) {
uint32_t nalLen = BigEndian::readUint32(&aBuffer[i]);
memcpy(&aBuffer[i], kAnnexBDelimiter, sizeof(kAnnexBDelimiter));
i += nalLen + 4;
}
}
static void
ConvertParamSetToAnnexB(std::vector<uint8_t>::const_iterator& aIter,
size_t aCount,
std::vector<uint8_t>& aOutAnnexB)
{
for (size_t i = 0; i < aCount; i++) {
aOutAnnexB.insert(aOutAnnexB.end(), kAnnexBDelimiter,
kAnnexBDelimiter + sizeof(kAnnexBDelimiter));
uint16_t len = BigEndian::readUint16(&*aIter); aIter += 2;
aOutAnnexB.insert(aOutAnnexB.end(), aIter, aIter + len); aIter += len;
}
}
/* static */ void
AnnexB::ConvertConfig(const std::vector<uint8_t>& aBuffer,
std::vector<uint8_t>& aOutAnnexB)
{
// Skip past irrelevant headers
auto it = aBuffer.begin() + 5;
if (it >= aBuffer.end()) {
return;
}
size_t count = *(it++) & 31;
// Check that we have enough bytes for the Annex B conversion
// and the next size field. Bail if not.
if (it + count * 2 >= aBuffer.end()) {
return;
}
ConvertParamSetToAnnexB(it, count, aOutAnnexB);
// Check that we have enough bytes for the Annex B conversion.
count = *(it++);
if (it + count * 2 > aBuffer.end()) {
aOutAnnexB.clear();
return;
}
ConvertParamSetToAnnexB(it, count, aOutAnnexB);
}

View file

@ -0,0 +1,32 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __AnnexB_h__
#define __AnnexB_h__
#include <cstdint>
#include <vector>
class AnnexB
{
public:
static void ConvertFrameInPlace(std::vector<uint8_t>& aBuffer);
static void ConvertConfig(const std::vector<uint8_t>& aBuffer,
std::vector<uint8_t>& aOutAnnexB);
};
#endif // __AnnexB_h__

View file

@ -0,0 +1,23 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ArrayUtils_h__
#define __ArrayUtils_h__
#define MOZ_ARRAY_LENGTH(array_) \
(sizeof(array_)/sizeof(array_[0]))
#endif

View file

@ -0,0 +1,312 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cstdint>
#include <limits>
#include "AudioDecoder.h"
#include "ClearKeyDecryptionManager.h"
#include "ClearKeyUtils.h"
#include "gmp-task-utils.h"
using namespace wmf;
AudioDecoder::AudioDecoder(GMPAudioHost *aHostAPI)
: mHostAPI(aHostAPI)
, mCallback(nullptr)
, mWorkerThread(nullptr)
, mMutex(nullptr)
, mNumInputTasks(0)
, mHasShutdown(false)
{
// We drop the ref in DecodingComplete().
AddRef();
}
AudioDecoder::~AudioDecoder()
{
if (mMutex) {
mMutex->Destroy();
}
}
void
AudioDecoder::InitDecode(const GMPAudioCodec& aConfig,
GMPAudioDecoderCallback* aCallback)
{
mCallback = aCallback;
assert(mCallback);
mDecoder = new WMFAACDecoder();
HRESULT hr = mDecoder->Init(aConfig.mChannelCount,
aConfig.mSamplesPerSecond,
(BYTE*)aConfig.mExtraData,
aConfig.mExtraDataLen);
LOG("[%p] AudioDecoder::InitializeAudioDecoder() hr=0x%x\n", this, hr);
if (FAILED(hr)) {
mCallback->Error(GMPGenericErr);
return;
}
auto err = GetPlatform()->createmutex(&mMutex);
if (GMP_FAILED(err)) {
mCallback->Error(GMPGenericErr);
return;
}
}
void
AudioDecoder::EnsureWorker()
{
if (!mWorkerThread) {
GetPlatform()->createthread(&mWorkerThread);
if (!mWorkerThread) {
mCallback->Error(GMPAllocErr);
return;
}
}
}
void
AudioDecoder::Decode(GMPAudioSamples* aInput)
{
EnsureWorker();
{
AutoLock lock(mMutex);
mNumInputTasks++;
}
mWorkerThread->Post(WrapTaskRefCounted(this,
&AudioDecoder::DecodeTask,
aInput));
}
void
AudioDecoder::DecodeTask(GMPAudioSamples* aInput)
{
HRESULT hr;
{
AutoLock lock(mMutex);
mNumInputTasks--;
assert(mNumInputTasks >= 0);
}
if (!aInput || !mHostAPI || !mDecoder) {
LOG("Decode job not set up correctly!");
return;
}
const uint8_t* inBuffer = aInput->Buffer();
if (!inBuffer) {
LOG("No buffer for encoded samples!\n");
return;
}
const GMPEncryptedBufferMetadata* crypto = aInput->GetDecryptionData();
std::vector<uint8_t> buffer(inBuffer, inBuffer + aInput->Size());
if (crypto) {
// Plugin host should have set up its decryptor/key sessions
// before trying to decode!
GMPErr rv =
ClearKeyDecryptionManager::Get()->Decrypt(buffer, CryptoMetaData(crypto));
if (GMP_FAILED(rv)) {
CK_LOGE("Failed to decrypt with key id %08x...", *(uint32_t*)crypto->KeyId());
MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::Error, rv));
return;
}
}
hr = mDecoder->Input(&buffer[0],
buffer.size(),
aInput->TimeStamp());
// We must delete the input sample!
GetPlatform()->runonmainthread(WrapTask(aInput, &GMPAudioSamples::Destroy));
SAMPLE_LOG("AudioDecoder::DecodeTask() Input ret hr=0x%x\n", hr);
if (FAILED(hr)) {
LOG("AudioDecoder::DecodeTask() decode failed ret=0x%x%s\n",
hr,
((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
return;
}
while (hr == S_OK) {
CComPtr<IMFSample> output;
hr = mDecoder->Output(&output);
SAMPLE_LOG("AudioDecoder::DecodeTask() output ret=0x%x\n", hr);
if (hr == S_OK) {
ReturnOutput(output);
}
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
AutoLock lock(mMutex);
if (mNumInputTasks == 0) {
// We have run all input tasks. We *must* notify Gecko so that it will
// send us more data.
MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::InputDataExhausted));
}
} else if (FAILED(hr)) {
LOG("AudioDecoder::DecodeTask() output failed hr=0x%x\n", hr);
}
}
}
void
AudioDecoder::ReturnOutput(IMFSample* aSample)
{
SAMPLE_LOG("[%p] AudioDecoder::ReturnOutput()\n", this);
assert(aSample);
HRESULT hr;
GMPAudioSamples* samples = nullptr;
mHostAPI->CreateSamples(kGMPAudioIS16Samples, &samples);
if (!samples) {
LOG("Failed to create i420 frame!\n");
return;
}
hr = MFToGMPSample(aSample, samples);
if (FAILED(hr)) {
samples->Destroy();
LOG("Failed to prepare output sample!");
return;
}
ENSURE(SUCCEEDED(hr), /*void*/);
MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::Decoded, samples));
}
HRESULT
AudioDecoder::MFToGMPSample(IMFSample* aInput,
GMPAudioSamples* aOutput)
{
ENSURE(aInput != nullptr, E_POINTER);
ENSURE(aOutput != nullptr, E_POINTER);
HRESULT hr;
CComPtr<IMFMediaBuffer> mediaBuffer;
hr = aInput->ConvertToContiguousBuffer(&mediaBuffer);
ENSURE(SUCCEEDED(hr), hr);
BYTE* data = nullptr; // Note: *data will be owned by the IMFMediaBuffer, we don't need to free it.
DWORD maxLength = 0, currentLength = 0;
hr = mediaBuffer->Lock(&data, &maxLength, &currentLength);
ENSURE(SUCCEEDED(hr), hr);
auto err = aOutput->SetBufferSize(currentLength);
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
memcpy(aOutput->Buffer(), data, currentLength);
mediaBuffer->Unlock();
LONGLONG hns = 0;
hr = aInput->GetSampleTime(&hns);
ENSURE(SUCCEEDED(hr), hr);
aOutput->SetTimeStamp(HNsToUsecs(hns));
aOutput->SetChannels(mDecoder->Channels());
aOutput->SetRate(mDecoder->Rate());
return S_OK;
}
void
AudioDecoder::Reset()
{
if (mDecoder) {
mDecoder->Reset();
}
if (mCallback) {
mCallback->ResetComplete();
}
}
void
AudioDecoder::DrainTask()
{
mDecoder->Drain();
// Return any pending output.
HRESULT hr = S_OK;
while (hr == S_OK) {
CComPtr<IMFSample> output;
hr = mDecoder->Output(&output);
SAMPLE_LOG("AudioDecoder::DrainTask() output ret=0x%x\n", hr);
if (hr == S_OK) {
ReturnOutput(output);
}
}
MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::DrainComplete));
}
void
AudioDecoder::Drain()
{
if (!mDecoder) {
return;
}
EnsureWorker();
mWorkerThread->Post(WrapTaskRefCounted(this,
&AudioDecoder::DrainTask));
}
void
AudioDecoder::DecodingComplete()
{
if (mWorkerThread) {
mWorkerThread->Join();
}
mHasShutdown = true;
// Release the reference we added in the constructor. There may be
// WrapRefCounted tasks that also hold references to us, and keep
// us alive a little longer.
Release();
}
void
AudioDecoder::MaybeRunOnMainThread(GMPTask* aTask)
{
class MaybeRunTask : public GMPTask
{
public:
MaybeRunTask(AudioDecoder* aDecoder, GMPTask* aTask)
: mDecoder(aDecoder), mTask(aTask)
{ }
virtual void Run(void) {
if (mDecoder->HasShutdown()) {
CK_LOGD("Trying to dispatch to main thread after AudioDecoder has shut down");
return;
}
mTask->Run();
}
virtual void Destroy()
{
mTask->Destroy();
delete this;
}
private:
RefPtr<AudioDecoder> mDecoder;
GMPTask* mTask;
};
GetPlatform()->runonmainthread(new MaybeRunTask(this, aTask));
}

View file

@ -0,0 +1,73 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __AudioDecoder_h__
#define __AudioDecoder_h__
#include "gmp-audio-decode.h"
#include "gmp-audio-host.h"
#include "gmp-task-utils.h"
#include "WMFAACDecoder.h"
#include "RefCounted.h"
#include "mfobjects.h"
class AudioDecoder : public GMPAudioDecoder
, public RefCounted
{
public:
AudioDecoder(GMPAudioHost *aHostAPI);
virtual void InitDecode(const GMPAudioCodec& aCodecSettings,
GMPAudioDecoderCallback* aCallback) override;
virtual void Decode(GMPAudioSamples* aEncodedSamples);
virtual void Reset() override;
virtual void Drain() override;
virtual void DecodingComplete() override;
bool HasShutdown() { return mHasShutdown; }
private:
virtual ~AudioDecoder();
void EnsureWorker();
void DecodeTask(GMPAudioSamples* aEncodedSamples);
void DrainTask();
void ReturnOutput(IMFSample* aSample);
HRESULT MFToGMPSample(IMFSample* aSample,
GMPAudioSamples* aAudioFrame);
void MaybeRunOnMainThread(GMPTask* aTask);
GMPAudioHost *mHostAPI; // host-owned, invalid at DecodingComplete
GMPAudioDecoderCallback* mCallback; // host-owned, invalid at DecodingComplete
GMPThread* mWorkerThread;
GMPMutex* mMutex;
wmf::AutoPtr<wmf::WMFAACDecoder> mDecoder;
int32_t mNumInputTasks;
bool mHasShutdown;
};
#endif // __AudioDecoder_h__

View file

@ -0,0 +1,68 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __BigEndian_h__
#define __BigEndian_h__
#include <stdint.h>
namespace mozilla {
class BigEndian {
public:
static uint32_t readUint32(const void* aPtr) {
const uint8_t* p = reinterpret_cast<const uint8_t*>(aPtr);
return uint32_t(p[0]) << 24 |
uint32_t(p[1]) << 16 |
uint32_t(p[2]) << 8 |
uint32_t(p[3]);
}
static uint16_t readUint16(const void* aPtr) {
const uint8_t* p = reinterpret_cast<const uint8_t*>(aPtr);
return uint32_t(p[0]) << 8 |
uint32_t(p[1]);
}
static uint64_t readUint64(const void* aPtr) {
const uint8_t* p = reinterpret_cast<const uint8_t*>(aPtr);
return uint64_t(p[0]) << 56 |
uint64_t(p[1]) << 48 |
uint64_t(p[2]) << 40 |
uint64_t(p[3]) << 32 |
uint64_t(p[4]) << 24 |
uint64_t(p[5]) << 16 |
uint64_t(p[6]) << 8 |
uint64_t(p[7]);
}
static void writeUint64(void* aPtr, uint64_t aValue) {
uint8_t* p = reinterpret_cast<uint8_t*>(aPtr);
p[0] = uint8_t(aValue >> 56) & 0xff;
p[1] = uint8_t(aValue >> 48) & 0xff;
p[2] = uint8_t(aValue >> 40) & 0xff;
p[3] = uint8_t(aValue >> 32) & 0xff;
p[4] = uint8_t(aValue >> 24) & 0xff;
p[5] = uint8_t(aValue >> 16) & 0xff;
p[6] = uint8_t(aValue >> 8) & 0xff;
p[7] = uint8_t(aValue) & 0xff;
}
};
} // namespace mozilla
#endif // __BigEndian_h__

View file

@ -0,0 +1,45 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ClearKeyAsyncShutdown.h"
#include "gmp-task-utils.h"
ClearKeyAsyncShutdown::ClearKeyAsyncShutdown(GMPAsyncShutdownHost *aHostAPI)
: mHost(aHostAPI)
{
CK_LOGD("ClearKeyAsyncShutdown::ClearKeyAsyncShutdown");
AddRef();
}
ClearKeyAsyncShutdown::~ClearKeyAsyncShutdown()
{
CK_LOGD("ClearKeyAsyncShutdown::~ClearKeyAsyncShutdown");
}
void ShutdownTask(ClearKeyAsyncShutdown* aSelf, GMPAsyncShutdownHost* aHost)
{
// Dumb implementation that just immediately reports completion.
// Real GMPs should ensure they are properly shutdown.
CK_LOGD("ClearKeyAsyncShutdown::BeginShutdown calling ShutdownComplete");
aHost->ShutdownComplete();
aSelf->Release();
}
void ClearKeyAsyncShutdown::BeginShutdown()
{
CK_LOGD("ClearKeyAsyncShutdown::BeginShutdown dispatching asynchronous shutdown task");
GetPlatform()->runonmainthread(WrapTaskNM(ShutdownTask, this, mHost));
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeyAsyncShutdown_h__
#define __ClearKeyAsyncShutdown_h__
#include "gmp-api/gmp-async-shutdown.h"
#include "RefCounted.h"
class ClearKeyAsyncShutdown : public GMPAsyncShutdown
, public RefCounted
{
public:
explicit ClearKeyAsyncShutdown(GMPAsyncShutdownHost *aHostAPI);
void BeginShutdown() override;
private:
virtual ~ClearKeyAsyncShutdown();
GMPAsyncShutdownHost* mHost;
};
#endif // __ClearKeyAsyncShutdown_h__

View file

@ -0,0 +1,99 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ClearKeyBase64.h"
#include <algorithm>
using namespace std;
/**
* Take a base64-encoded string, convert (in-place) each character to its
* corresponding value in the [0x00, 0x3f] range, and truncate any padding.
*/
static bool
Decode6Bit(string& aStr)
{
for (size_t i = 0; i < aStr.length(); i++) {
if (aStr[i] >= 'A' && aStr[i] <= 'Z') {
aStr[i] -= 'A';
}
else if (aStr[i] >= 'a' && aStr[i] <= 'z') {
aStr[i] -= 'a' - 26;
}
else if (aStr[i] >= '0' && aStr[i] <= '9') {
aStr[i] -= '0' - 52;
}
else if (aStr[i] == '-' || aStr[i] == '+') {
aStr[i] = 62;
}
else if (aStr[i] == '_' || aStr[i] == '/') {
aStr[i] = 63;
}
else {
// Truncate '=' padding at the end of the aString.
if (aStr[i] != '=') {
aStr.erase(i, string::npos);
return false;
}
aStr[i] = '\0';
aStr.resize(i);
break;
}
}
return true;
}
bool
DecodeBase64(const string& aEncoded, vector<uint8_t>& aOutDecoded)
{
if (aEncoded.empty()) {
aOutDecoded.clear();
return true;
}
if (aEncoded.size() == 1) {
// Invalid Base64 encoding.
return false;
}
string encoded = aEncoded;
if (!Decode6Bit(encoded)) {
return false;
}
// The number of bytes we haven't yet filled in the current byte, mod 8.
int shift = 0;
aOutDecoded.resize((encoded.size() * 3) / 4);
vector<uint8_t>::iterator out = aOutDecoded.begin();
for (size_t i = 0; i < encoded.length(); i++) {
if (!shift) {
*out = encoded[i] << 2;
}
else {
*out |= encoded[i] >> (6 - shift);
out++;
if (out == aOutDecoded.end()) {
// Hit last 6bit octed in encoded, which is padding and can be ignored.
break;
}
*out = encoded[i] << (shift + 2);
}
shift = (shift + 2) % 8;
}
return true;
}

View file

@ -0,0 +1,28 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeyBase64_h__
#define __ClearKeyBase64_h__
#include <vector>
#include <string>
#include <stdint.h>
// Decodes a base64 encoded string. Returns true on success.
bool
DecodeBase64(const std::string& aEncoded, std::vector<uint8_t>& aOutDecoded);
#endif

View file

@ -0,0 +1,241 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include <vector>
#include "ClearKeyDecryptionManager.h"
#include "psshparser/PsshParser.h"
#include "gmp-api/gmp-decryption.h"
#include "mozilla/CheckedInt.h"
#include <assert.h>
class ClearKeyDecryptor : public RefCounted
{
public:
ClearKeyDecryptor();
void InitKey(const Key& aKey);
bool HasKey() const { return !!mKey.size(); }
GMPErr Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata);
const Key& DecryptionKey() const { return mKey; }
private:
~ClearKeyDecryptor();
Key mKey;
};
/* static */ ClearKeyDecryptionManager* ClearKeyDecryptionManager::sInstance = nullptr;
/* static */ ClearKeyDecryptionManager*
ClearKeyDecryptionManager::Get()
{
if (!sInstance) {
sInstance = new ClearKeyDecryptionManager();
}
return sInstance;
}
ClearKeyDecryptionManager::ClearKeyDecryptionManager()
{
CK_LOGD("ClearKeyDecryptionManager::ClearKeyDecryptionManager");
}
ClearKeyDecryptionManager::~ClearKeyDecryptionManager()
{
CK_LOGD("ClearKeyDecryptionManager::~ClearKeyDecryptionManager");
sInstance = nullptr;
for (auto it = mDecryptors.begin(); it != mDecryptors.end(); it++) {
it->second->Release();
}
mDecryptors.clear();
}
bool
ClearKeyDecryptionManager::HasSeenKeyId(const KeyId& aKeyId) const
{
CK_LOGD("ClearKeyDecryptionManager::SeenKeyId %s", mDecryptors.find(aKeyId) != mDecryptors.end() ? "t" : "f");
return mDecryptors.find(aKeyId) != mDecryptors.end();
}
bool
ClearKeyDecryptionManager::IsExpectingKeyForKeyId(const KeyId& aKeyId) const
{
CK_LOGD("ClearKeyDecryptionManager::IsExpectingKeyForId %08x...", *(uint32_t*)&aKeyId[0]);
const auto& decryptor = mDecryptors.find(aKeyId);
return decryptor != mDecryptors.end() && !decryptor->second->HasKey();
}
bool
ClearKeyDecryptionManager::HasKeyForKeyId(const KeyId& aKeyId) const
{
CK_LOGD("ClearKeyDecryptionManager::HasKeyForKeyId");
const auto& decryptor = mDecryptors.find(aKeyId);
return decryptor != mDecryptors.end() && decryptor->second->HasKey();
}
const Key&
ClearKeyDecryptionManager::GetDecryptionKey(const KeyId& aKeyId)
{
assert(HasKeyForKeyId(aKeyId));
return mDecryptors[aKeyId]->DecryptionKey();
}
void
ClearKeyDecryptionManager::InitKey(KeyId aKeyId, Key aKey)
{
CK_LOGD("ClearKeyDecryptionManager::InitKey %08x...", *(uint32_t*)&aKeyId[0]);
if (IsExpectingKeyForKeyId(aKeyId)) {
mDecryptors[aKeyId]->InitKey(aKey);
}
}
void
ClearKeyDecryptionManager::ExpectKeyId(KeyId aKeyId)
{
CK_LOGD("ClearKeyDecryptionManager::ExpectKeyId %08x...", *(uint32_t*)&aKeyId[0]);
if (!HasSeenKeyId(aKeyId)) {
mDecryptors[aKeyId] = new ClearKeyDecryptor();
}
mDecryptors[aKeyId]->AddRef();
}
void
ClearKeyDecryptionManager::ReleaseKeyId(KeyId aKeyId)
{
CK_LOGD("ClearKeyDecryptionManager::ReleaseKeyId");
assert(HasSeenKeyId(aKeyId));
ClearKeyDecryptor* decryptor = mDecryptors[aKeyId];
if (!decryptor->Release()) {
mDecryptors.erase(aKeyId);
}
}
GMPErr
ClearKeyDecryptionManager::Decrypt(std::vector<uint8_t>& aBuffer,
const CryptoMetaData& aMetadata)
{
return Decrypt(&aBuffer[0], aBuffer.size(), aMetadata);
}
GMPErr
ClearKeyDecryptionManager::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata)
{
CK_LOGD("ClearKeyDecryptionManager::Decrypt");
if (!HasKeyForKeyId(aMetadata.mKeyId)) {
return GMPNoKeyErr;
}
return mDecryptors[aMetadata.mKeyId]->Decrypt(aBuffer, aBufferSize, aMetadata);
}
ClearKeyDecryptor::ClearKeyDecryptor()
{
CK_LOGD("ClearKeyDecryptor ctor");
}
ClearKeyDecryptor::~ClearKeyDecryptor()
{
if (HasKey()) {
CK_LOGD("ClearKeyDecryptor dtor; key = %08x...", *(uint32_t*)&mKey[0]);
} else {
CK_LOGD("ClearKeyDecryptor dtor");
}
}
void
ClearKeyDecryptor::InitKey(const Key& aKey)
{
mKey = aKey;
}
GMPErr
ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata)
{
CK_LOGD("ClearKeyDecryptor::Decrypt");
// If the sample is split up into multiple encrypted subsamples, we need to
// stitch them into one continuous buffer for decryption.
std::vector<uint8_t> tmp(aBufferSize);
if (aMetadata.NumSubsamples()) {
// Take all encrypted parts of subsamples and stitch them into one
// continuous encrypted buffer.
static_assert(sizeof(uintptr_t) == sizeof(uint8_t*),
"We need uintptr_t to be exactly the same size as a pointer");
mozilla::CheckedInt<uintptr_t> data = reinterpret_cast<uintptr_t>(aBuffer);
const uintptr_t endBuffer =
reinterpret_cast<uintptr_t>(aBuffer + aBufferSize);
uint8_t* iter = &tmp[0];
for (size_t i = 0; i < aMetadata.NumSubsamples(); i++) {
data += aMetadata.mClearBytes[i];
if (!data.isValid() || data.value() > endBuffer) {
// Trying to read past the end of the buffer!
return GMPCryptoErr;
}
const uint32_t& cipherBytes = aMetadata.mCipherBytes[i];
mozilla::CheckedInt<uintptr_t> dataAfterCipher = data + cipherBytes;
if (!dataAfterCipher.isValid() || dataAfterCipher.value() > endBuffer) {
// Trying to read past the end of the buffer!
return GMPCryptoErr;
}
memcpy(iter, reinterpret_cast<uint8_t*>(data.value()), cipherBytes);
data = dataAfterCipher;
iter += cipherBytes;
}
tmp.resize((size_t)(iter - &tmp[0]));
} else {
memcpy(&tmp[0], aBuffer, aBufferSize);
}
assert(aMetadata.mIV.size() == 8 || aMetadata.mIV.size() == 16);
std::vector<uint8_t> iv(aMetadata.mIV);
iv.insert(iv.end(), CENC_KEY_LEN - aMetadata.mIV.size(), 0);
ClearKeyUtils::DecryptAES(mKey, tmp, iv);
if (aMetadata.NumSubsamples()) {
// Take the decrypted buffer, split up into subsamples, and insert those
// subsamples back into their original position in the original buffer.
uint8_t* data = aBuffer;
uint8_t* iter = &tmp[0];
for (size_t i = 0; i < aMetadata.NumSubsamples(); i++) {
data += aMetadata.mClearBytes[i];
uint32_t cipherBytes = aMetadata.mCipherBytes[i];
memcpy(data, iter, cipherBytes);
data += cipherBytes;
iter += cipherBytes;
}
} else {
memcpy(aBuffer, &tmp[0], aBufferSize);
}
return GMPNoErr;
}

View file

@ -0,0 +1,101 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeyDecryptionManager_h__
#define __ClearKeyDecryptionManager_h__
#include <map>
#include "ClearKeyUtils.h"
#include "RefCounted.h"
class ClearKeyDecryptor;
class CryptoMetaData {
public:
CryptoMetaData() {}
explicit CryptoMetaData(const GMPEncryptedBufferMetadata* aCrypto)
{
Init(aCrypto);
}
void Init(const GMPEncryptedBufferMetadata* aCrypto)
{
if (!aCrypto) {
assert(!IsValid());
return;
}
Assign(mKeyId, aCrypto->KeyId(), aCrypto->KeyIdSize());
Assign(mIV, aCrypto->IV(), aCrypto->IVSize());
Assign(mClearBytes, aCrypto->ClearBytes(), aCrypto->NumSubsamples());
Assign(mCipherBytes, aCrypto->CipherBytes(), aCrypto->NumSubsamples());
}
bool IsValid() const {
return !mKeyId.empty() &&
!mIV.empty() &&
!mCipherBytes.empty() &&
!mClearBytes.empty();
}
size_t NumSubsamples() const {
assert(mClearBytes.size() == mCipherBytes.size());
return mClearBytes.size();
}
std::vector<uint8_t> mKeyId;
std::vector<uint8_t> mIV;
std::vector<uint16_t> mClearBytes;
std::vector<uint32_t> mCipherBytes;
};
class ClearKeyDecryptionManager : public RefCounted
{
private:
ClearKeyDecryptionManager();
~ClearKeyDecryptionManager();
static ClearKeyDecryptionManager* sInstance;
public:
static ClearKeyDecryptionManager* Get();
bool HasSeenKeyId(const KeyId& aKeyId) const;
bool HasKeyForKeyId(const KeyId& aKeyId) const;
const Key& GetDecryptionKey(const KeyId& aKeyId);
// Create a decryptor for the given KeyId if one does not already exist.
void InitKey(KeyId aKeyId, Key aKey);
void ExpectKeyId(KeyId aKeyId);
void ReleaseKeyId(KeyId aKeyId);
// Decrypts buffer *in place*.
GMPErr Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata);
GMPErr Decrypt(std::vector<uint8_t>& aBuffer,
const CryptoMetaData& aMetadata);
void Shutdown();
private:
bool IsExpectingKeyForKeyId(const KeyId& aKeyId) const;
std::map<KeyId, ClearKeyDecryptor*> mDecryptors;
};
#endif // __ClearKeyDecryptionManager_h__

View file

@ -0,0 +1,260 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ClearKeyPersistence.h"
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "ClearKeySessionManager.h"
#include "RefCounted.h"
#include <stdint.h>
#include <string.h>
#include <set>
#include <vector>
#include <sstream>
#include <assert.h>
using namespace std;
// Whether we've loaded the persistent session ids from GMPStorage yet.
enum PersistentKeyState {
UNINITIALIZED,
LOADING,
LOADED
};
static PersistentKeyState sPersistentKeyState = UNINITIALIZED;
// Set of session Ids of the persistent sessions created or residing in
// storage.
static set<uint32_t> sPersistentSessionIds;
static vector<GMPTask*> sTasksBlockedOnSessionIdLoad;
static void
ReadAllRecordsFromIterator(GMPRecordIterator* aRecordIterator,
void* aUserArg,
GMPErr aStatus)
{
assert(sPersistentKeyState == LOADING);
if (GMP_SUCCEEDED(aStatus)) {
// Extract the record names which are valid uint32_t's; they're
// the persistent session ids.
const char* name = nullptr;
uint32_t len = 0;
while (GMP_SUCCEEDED(aRecordIterator->GetName(&name, &len))) {
if (ClearKeyUtils::IsValidSessionId(name, len)) {
assert(name[len] == 0);
sPersistentSessionIds.insert(atoi(name));
}
aRecordIterator->NextRecord();
}
}
sPersistentKeyState = LOADED;
aRecordIterator->Close();
for (size_t i = 0; i < sTasksBlockedOnSessionIdLoad.size(); i++) {
sTasksBlockedOnSessionIdLoad[i]->Run();
sTasksBlockedOnSessionIdLoad[i]->Destroy();
}
sTasksBlockedOnSessionIdLoad.clear();
}
/* static */ void
ClearKeyPersistence::EnsureInitialized()
{
if (sPersistentKeyState == UNINITIALIZED) {
sPersistentKeyState = LOADING;
if (GMP_FAILED(EnumRecordNames(&ReadAllRecordsFromIterator))) {
sPersistentKeyState = LOADED;
}
}
}
/* static */ string
ClearKeyPersistence::GetNewSessionId(GMPSessionType aSessionType)
{
static uint32_t sNextSessionId = 1;
// Ensure we don't re-use a session id that was persisted.
while (Contains(sPersistentSessionIds, sNextSessionId)) {
sNextSessionId++;
}
string sessionId;
stringstream ss;
ss << sNextSessionId;
ss >> sessionId;
if (aSessionType == kGMPPersistentSession) {
sPersistentSessionIds.insert(sNextSessionId);
}
sNextSessionId++;
return sessionId;
}
class CreateSessionTask : public GMPTask {
public:
CreateSessionTask(ClearKeySessionManager* aTarget,
uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const string& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType)
: mTarget(aTarget)
, mCreateSessionToken(aCreateSessionToken)
, mPromiseId(aPromiseId)
, mInitDataType(aInitDataType)
, mSessionType(aSessionType)
{
mInitData.insert(mInitData.end(),
aInitData,
aInitData + aInitDataSize);
}
virtual void Run() override {
mTarget->CreateSession(mCreateSessionToken,
mPromiseId,
mInitDataType.c_str(),
mInitDataType.size(),
&mInitData.front(),
mInitData.size(),
mSessionType);
}
virtual void Destroy() override {
delete this;
}
private:
RefPtr<ClearKeySessionManager> mTarget;
uint32_t mCreateSessionToken;
uint32_t mPromiseId;
const string mInitDataType;
vector<uint8_t> mInitData;
GMPSessionType mSessionType;
};
/* static */ bool
ClearKeyPersistence::DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const string& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType)
{
if (sPersistentKeyState >= LOADED) {
return false;
}
GMPTask* t = new CreateSessionTask(aInstance,
aCreateSessionToken,
aPromiseId,
aInitDataType,
aInitData,
aInitDataSize,
aSessionType);
sTasksBlockedOnSessionIdLoad.push_back(t);
return true;
}
class LoadSessionTask : public GMPTask {
public:
LoadSessionTask(ClearKeySessionManager* aTarget,
uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
: mTarget(aTarget)
, mPromiseId(aPromiseId)
, mSessionId(aSessionId, aSessionId + aSessionIdLength)
{
}
virtual void Run() override {
mTarget->LoadSession(mPromiseId,
mSessionId.c_str(),
mSessionId.size());
}
virtual void Destroy() override {
delete this;
}
private:
RefPtr<ClearKeySessionManager> mTarget;
uint32_t mPromiseId;
string mSessionId;
};
/* static */ bool
ClearKeyPersistence::DeferLoadSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
{
if (sPersistentKeyState >= LOADED) {
return false;
}
GMPTask* t = new LoadSessionTask(aInstance,
aPromiseId,
aSessionId,
aSessionIdLength);
sTasksBlockedOnSessionIdLoad.push_back(t);
return true;
}
/* static */ bool
ClearKeyPersistence::IsPersistentSessionId(const string& aSessionId)
{
return Contains(sPersistentSessionIds, atoi(aSessionId.c_str()));
}
class LoadSessionFromKeysTask : public ReadContinuation {
public:
LoadSessionFromKeysTask(ClearKeySessionManager* aTarget,
const string& aSessionId,
uint32_t aPromiseId)
: mTarget(aTarget)
, mSessionId(aSessionId)
, mPromiseId(aPromiseId)
{
}
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aLength) override
{
mTarget->PersistentSessionDataLoaded(aStatus, mPromiseId, mSessionId, aData, aLength);
}
private:
RefPtr<ClearKeySessionManager> mTarget;
string mSessionId;
uint32_t mPromiseId;
};
/* static */ void
ClearKeyPersistence::LoadSessionData(ClearKeySessionManager* aInstance,
const string& aSid,
uint32_t aPromiseId)
{
LoadSessionFromKeysTask* loadTask =
new LoadSessionFromKeysTask(aInstance, aSid, aPromiseId);
ReadData(aSid, loadTask);
}
/* static */ void
ClearKeyPersistence::PersistentSessionRemoved(const string& aSessionId)
{
sPersistentSessionIds.erase(atoi(aSessionId.c_str()));
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeyPersistence_h__
#define __ClearKeyPersistence_h__
#include <string>
#include "gmp-api/gmp-decryption.h"
class ClearKeySessionManager;
class ClearKeyPersistence {
public:
static void EnsureInitialized();
static std::string GetNewSessionId(GMPSessionType aSessionType);
static bool DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const std::string& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType);
static bool DeferLoadSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength);
static bool IsPersistentSessionId(const std::string& aSid);
static void LoadSessionData(ClearKeySessionManager* aInstance,
const std::string& aSid,
uint32_t aPromiseId);
static void PersistentSessionRemoved(const std::string& aSid);
};
#endif // __ClearKeyPersistence_h__

View file

@ -0,0 +1,94 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BigEndian.h"
#include "ClearKeyDecryptionManager.h"
#include "ClearKeySession.h"
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "psshparser/PsshParser.h"
#include "gmp-task-utils.h"
#include "gmp-api/gmp-decryption.h"
#include <assert.h>
#include <string.h>
using namespace mozilla;
ClearKeySession::ClearKeySession(const std::string& aSessionId,
GMPDecryptorCallback* aCallback,
GMPSessionType aSessionType)
: mSessionId(aSessionId)
, mCallback(aCallback)
, mSessionType(aSessionType)
{
CK_LOGD("ClearKeySession ctor %p", this);
}
ClearKeySession::~ClearKeySession()
{
CK_LOGD("ClearKeySession dtor %p", this);
std::vector<GMPMediaKeyInfo> key_infos;
for (const KeyId& keyId : mKeyIds) {
assert(ClearKeyDecryptionManager::Get()->HasSeenKeyId(keyId));
ClearKeyDecryptionManager::Get()->ReleaseKeyId(keyId);
key_infos.push_back(GMPMediaKeyInfo(&keyId[0], keyId.size(), kGMPUnknown));
}
mCallback->BatchedKeyStatusChanged(&mSessionId[0], mSessionId.size(),
key_infos.data(), key_infos.size());
}
void
ClearKeySession::Init(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const std::string& aInitDataType,
const uint8_t* aInitData, uint32_t aInitDataSize)
{
CK_LOGD("ClearKeySession::Init");
if (aInitDataType == "cenc") {
ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
} else if (aInitDataType == "keyids") {
ClearKeyUtils::ParseKeyIdsInitData(aInitData, aInitDataSize, mKeyIds);
} else if (aInitDataType == "webm" && aInitDataSize <= kMaxWebmInitDataSize) {
// "webm" initData format is simply the raw bytes of the keyId.
vector<uint8_t> keyId;
keyId.assign(aInitData, aInitData+aInitDataSize);
mKeyIds.push_back(keyId);
}
if (!mKeyIds.size()) {
const char message[] = "Couldn't parse init data";
mCallback->RejectPromise(aPromiseId, kGMPTypeError, message, strlen(message));
return;
}
mCallback->SetSessionId(aCreateSessionToken, &mSessionId[0], mSessionId.length());
mCallback->ResolvePromise(aPromiseId);
}
GMPSessionType
ClearKeySession::Type() const
{
return mSessionType;
}
void
ClearKeySession::AddKeyId(const KeyId& aKeyId)
{
mKeyIds.push_back(aKeyId);
}

View file

@ -0,0 +1,58 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeySession_h__
#define __ClearKeySession_h__
#include "ClearKeyUtils.h"
#include "gmp-api/gmp-decryption.h"
class GMPBuffer;
class GMPDecryptorCallback;
class GMPDecryptorHost;
class GMPEncryptedBufferMetadata;
class ClearKeySession
{
public:
explicit ClearKeySession(const std::string& aSessionId,
GMPDecryptorCallback* aCallback,
GMPSessionType aSessionType);
~ClearKeySession();
const std::vector<KeyId>& GetKeyIds() const { return mKeyIds; }
void Init(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const string& aInitDataType,
const uint8_t* aInitData, uint32_t aInitDataSize);
GMPSessionType Type() const;
void AddKeyId(const KeyId& aKeyId);
const std::string& Id() const { return mSessionId; }
private:
const std::string mSessionId;
std::vector<KeyId> mKeyIds;
GMPDecryptorCallback* mCallback;
const GMPSessionType mSessionType;
};
#endif // __ClearKeySession_h__

View file

@ -0,0 +1,418 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "ClearKeyDecryptionManager.h"
#include "ClearKeySessionManager.h"
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "ClearKeyPersistence.h"
#include "gmp-task-utils.h"
#include <assert.h>
using namespace std;
ClearKeySessionManager::ClearKeySessionManager()
: mDecryptionManager(ClearKeyDecryptionManager::Get())
{
CK_LOGD("ClearKeySessionManager ctor %p", this);
AddRef();
if (GetPlatform()->createthread(&mThread) != GMPNoErr) {
CK_LOGD("failed to create thread in clearkey cdm");
mThread = nullptr;
}
}
ClearKeySessionManager::~ClearKeySessionManager()
{
CK_LOGD("ClearKeySessionManager dtor %p", this);
}
void
ClearKeySessionManager::Init(GMPDecryptorCallback* aCallback,
bool aDistinctiveIdentifierAllowed,
bool aPersistentStateAllowed)
{
CK_LOGD("ClearKeySessionManager::Init");
mCallback = aCallback;
ClearKeyPersistence::EnsureInitialized();
}
void
ClearKeySessionManager::CreateSession(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const char* aInitDataType,
uint32_t aInitDataTypeSize,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType)
{
CK_LOGD("ClearKeySessionManager::CreateSession type:%s", aInitDataType);
string initDataType(aInitDataType, aInitDataType + aInitDataTypeSize);
// initDataType must be "cenc", "keyids", or "webm".
if (initDataType != "cenc" &&
initDataType != "keyids" &&
initDataType != "webm") {
string message = "'" + initDataType + "' is an initDataType unsupported by ClearKey";
mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
message.c_str(), message.size());
return;
}
if (ClearKeyPersistence::DeferCreateSessionIfNotReady(this,
aCreateSessionToken,
aPromiseId,
initDataType,
aInitData,
aInitDataSize,
aSessionType)) {
return;
}
string sessionId = ClearKeyPersistence::GetNewSessionId(aSessionType);
assert(mSessions.find(sessionId) == mSessions.end());
ClearKeySession* session = new ClearKeySession(sessionId, mCallback, aSessionType);
session->Init(aCreateSessionToken, aPromiseId, initDataType, aInitData, aInitDataSize);
mSessions[sessionId] = session;
const vector<KeyId>& sessionKeys = session->GetKeyIds();
vector<KeyId> neededKeys;
for (auto it = sessionKeys.begin(); it != sessionKeys.end(); it++) {
// Need to request this key ID from the client. We always send a key
// request, whether or not another session has sent a request with the same
// key ID. Otherwise a script can end up waiting for another script to
// respond to the request (which may not necessarily happen).
neededKeys.push_back(*it);
mDecryptionManager->ExpectKeyId(*it);
}
if (neededKeys.empty()) {
CK_LOGD("No keys needed from client.");
return;
}
// Send a request for needed key data.
string request;
ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType);
mCallback->SessionMessage(&sessionId[0], sessionId.length(),
kGMPLicenseRequest,
(uint8_t*)&request[0], request.length());
}
void
ClearKeySessionManager::LoadSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
{
CK_LOGD("ClearKeySessionManager::LoadSession");
if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) {
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
return;
}
if (ClearKeyPersistence::DeferLoadSessionIfNotReady(this,
aPromiseId,
aSessionId,
aSessionIdLength)) {
return;
}
string sid(aSessionId, aSessionId + aSessionIdLength);
if (!ClearKeyPersistence::IsPersistentSessionId(sid)) {
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
return;
}
// Callsback PersistentSessionDataLoaded with results...
ClearKeyPersistence::LoadSessionData(this, sid, aPromiseId);
}
void
ClearKeySessionManager::PersistentSessionDataLoaded(GMPErr aStatus,
uint32_t aPromiseId,
const string& aSessionId,
const uint8_t* aKeyData,
uint32_t aKeyDataSize)
{
CK_LOGD("ClearKeySessionManager::PersistentSessionDataLoaded");
if (GMP_FAILED(aStatus) ||
Contains(mSessions, aSessionId) ||
(aKeyDataSize % (2 * CENC_KEY_LEN)) != 0) {
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
return;
}
ClearKeySession* session = new ClearKeySession(aSessionId,
mCallback,
kGMPPersistentSession);
mSessions[aSessionId] = session;
uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN);
vector<GMPMediaKeyInfo> key_infos;
vector<KeyIdPair> keyPairs;
for (uint32_t i = 0; i < numKeys; i ++) {
const uint8_t* base = aKeyData + 2 * CENC_KEY_LEN * i;
KeyIdPair keyPair;
keyPair.mKeyId = KeyId(base, base + CENC_KEY_LEN);
assert(keyPair.mKeyId.size() == CENC_KEY_LEN);
keyPair.mKey = Key(base + CENC_KEY_LEN, base + 2 * CENC_KEY_LEN);
assert(keyPair.mKey.size() == CENC_KEY_LEN);
session->AddKeyId(keyPair.mKeyId);
mDecryptionManager->ExpectKeyId(keyPair.mKeyId);
mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
mKeyIds.insert(keyPair.mKey);
keyPairs.push_back(keyPair);
key_infos.push_back(GMPMediaKeyInfo(&keyPairs[i].mKeyId[0],
keyPairs[i].mKeyId.size(),
kGMPUsable));
}
mCallback->BatchedKeyStatusChanged(&aSessionId[0], aSessionId.size(),
key_infos.data(), key_infos.size());
mCallback->ResolveLoadSessionPromise(aPromiseId, true);
}
void
ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength,
const uint8_t* aResponse,
uint32_t aResponseSize)
{
CK_LOGD("ClearKeySessionManager::UpdateSession");
string sessionId(aSessionId, aSessionId + aSessionIdLength);
auto itr = mSessions.find(sessionId);
if (itr == mSessions.end() || !(itr->second)) {
CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession.");
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
return;
}
ClearKeySession* session = itr->second;
// Verify the size of session response.
if (aResponseSize >= kMaxSessionResponseLength) {
CK_LOGW("Session response size is not within a reasonable size.");
mCallback->RejectPromise(aPromiseId, kGMPTypeError, nullptr, 0);
return;
}
// Parse the response for any (key ID, key) pairs.
vector<KeyIdPair> keyPairs;
if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) {
CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
mCallback->RejectPromise(aPromiseId, kGMPTypeError, nullptr, 0);
return;
}
vector<GMPMediaKeyInfo> key_infos;
for (size_t i = 0; i < keyPairs.size(); i++) {
KeyIdPair& keyPair = keyPairs[i];
mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
mKeyIds.insert(keyPair.mKeyId);
key_infos.push_back(GMPMediaKeyInfo(&keyPair.mKeyId[0],
keyPair.mKeyId.size(),
kGMPUsable));
}
mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdLength,
key_infos.data(), key_infos.size());
if (session->Type() != kGMPPersistentSession) {
mCallback->ResolvePromise(aPromiseId);
return;
}
// Store the keys on disk. We store a record whose name is the sessionId,
// and simply append each keyId followed by its key.
vector<uint8_t> keydata;
Serialize(session, keydata);
GMPTask* resolve = WrapTask(mCallback, &GMPDecryptorCallback::ResolvePromise, aPromiseId);
static const char* message = "Couldn't store cenc key init data";
GMPTask* reject = WrapTask(mCallback,
&GMPDecryptorCallback::RejectPromise,
aPromiseId,
kGMPInvalidStateError,
message,
strlen(message));
StoreData(sessionId, keydata, resolve, reject);
}
void
ClearKeySessionManager::Serialize(const ClearKeySession* aSession,
std::vector<uint8_t>& aOutKeyData)
{
const std::vector<KeyId>& keyIds = aSession->GetKeyIds();
for (size_t i = 0; i < keyIds.size(); i++) {
const KeyId& keyId = keyIds[i];
if (!mDecryptionManager->HasKeyForKeyId(keyId)) {
continue;
}
assert(keyId.size() == CENC_KEY_LEN);
aOutKeyData.insert(aOutKeyData.end(), keyId.begin(), keyId.end());
const Key& key = mDecryptionManager->GetDecryptionKey(keyId);
assert(key.size() == CENC_KEY_LEN);
aOutKeyData.insert(aOutKeyData.end(), key.begin(), key.end());
}
}
void
ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
{
CK_LOGD("ClearKeySessionManager::CloseSession");
string sessionId(aSessionId, aSessionId + aSessionIdLength);
auto itr = mSessions.find(sessionId);
if (itr == mSessions.end()) {
CK_LOGW("ClearKey CDM couldn't close non-existent session.");
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
return;
}
ClearKeySession* session = itr->second;
assert(session);
ClearInMemorySessionData(session);
mCallback->SessionClosed(aSessionId, aSessionIdLength);
mCallback->ResolvePromise(aPromiseId);
}
void
ClearKeySessionManager::ClearInMemorySessionData(ClearKeySession* aSession)
{
mSessions.erase(aSession->Id());
delete aSession;
}
void
ClearKeySessionManager::RemoveSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
{
CK_LOGD("ClearKeySessionManager::RemoveSession");
string sessionId(aSessionId, aSessionId + aSessionIdLength);
auto itr = mSessions.find(sessionId);
if (itr == mSessions.end()) {
CK_LOGW("ClearKey CDM couldn't remove non-existent session.");
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
return;
}
ClearKeySession* session = itr->second;
assert(session);
string sid = session->Id();
bool isPersistent = session->Type() == kGMPPersistentSession;
ClearInMemorySessionData(session);
if (!isPersistent) {
mCallback->ResolvePromise(aPromiseId);
return;
}
ClearKeyPersistence::PersistentSessionRemoved(sid);
// Overwrite the record storing the sessionId's key data with a zero
// length record to delete it.
vector<uint8_t> emptyKeydata;
GMPTask* resolve = WrapTask(mCallback, &GMPDecryptorCallback::ResolvePromise, aPromiseId);
static const char* message = "Could not remove session";
GMPTask* reject = WrapTask(mCallback,
&GMPDecryptorCallback::RejectPromise,
aPromiseId,
kGMPInvalidAccessError,
message,
strlen(message));
StoreData(sessionId, emptyKeydata, resolve, reject);
}
void
ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId,
const uint8_t* aServerCert,
uint32_t aServerCertSize)
{
// ClearKey CDM doesn't support this method by spec.
CK_LOGD("ClearKeySessionManager::SetServerCertificate");
mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
nullptr /* message */, 0 /* messageLen */);
}
void
ClearKeySessionManager::Decrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata)
{
CK_LOGD("ClearKeySessionManager::Decrypt");
if (!mThread) {
CK_LOGW("No decrypt thread");
mCallback->Decrypted(aBuffer, GMPGenericErr);
return;
}
mThread->Post(WrapTaskRefCounted(this,
&ClearKeySessionManager::DoDecrypt,
aBuffer, aMetadata));
}
void
ClearKeySessionManager::DoDecrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata)
{
CK_LOGD("ClearKeySessionManager::DoDecrypt");
GMPErr rv = mDecryptionManager->Decrypt(aBuffer->Data(), aBuffer->Size(),
CryptoMetaData(aMetadata));
CK_LOGD("DeDecrypt finished with code %x\n", rv);
mCallback->Decrypted(aBuffer, rv);
}
void
ClearKeySessionManager::Shutdown()
{
CK_LOGD("ClearKeySessionManager::Shutdown %p", this);
for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
delete it->second;
}
mSessions.clear();
}
void
ClearKeySessionManager::DecryptingComplete()
{
CK_LOGD("ClearKeySessionManager::DecryptingComplete %p", this);
GMPThread* thread = mThread;
thread->Join();
Shutdown();
mDecryptionManager = nullptr;
Release();
}

View file

@ -0,0 +1,100 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeyDecryptor_h__
#define __ClearKeyDecryptor_h__
#include <map>
#include <set>
#include <string>
#include <vector>
#include "ClearKeyDecryptionManager.h"
#include "ClearKeySession.h"
#include "ClearKeyUtils.h"
#include "gmp-api/gmp-decryption.h"
#include "RefCounted.h"
class ClearKeySessionManager final : public GMPDecryptor
, public RefCounted
{
public:
ClearKeySessionManager();
virtual void Init(GMPDecryptorCallback* aCallback,
bool aDistinctiveIdentifierAllowed,
bool aPersistentStateAllowed) override;
virtual void CreateSession(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const char* aInitDataType,
uint32_t aInitDataTypeSize,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType) override;
virtual void LoadSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength) override;
virtual void UpdateSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength,
const uint8_t* aResponse,
uint32_t aResponseSize) override;
virtual void CloseSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength) override;
virtual void RemoveSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength) override;
virtual void SetServerCertificate(uint32_t aPromiseId,
const uint8_t* aServerCert,
uint32_t aServerCertSize) override;
virtual void Decrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata) override;
virtual void DecryptingComplete() override;
void PersistentSessionDataLoaded(GMPErr aStatus,
uint32_t aPromiseId,
const std::string& aSessionId,
const uint8_t* aKeyData,
uint32_t aKeyDataSize);
private:
~ClearKeySessionManager();
void DoDecrypt(GMPBuffer* aBuffer, GMPEncryptedBufferMetadata* aMetadata);
void Shutdown();
void ClearInMemorySessionData(ClearKeySession* aSession);
void Serialize(const ClearKeySession* aSession, std::vector<uint8_t>& aOutKeyData);
RefPtr<ClearKeyDecryptionManager> mDecryptionManager;
GMPDecryptorCallback* mCallback;
GMPThread* mThread;
std::set<KeyId> mKeyIds;
std::map<std::string, ClearKeySession*> mSessions;
};
#endif // __ClearKeyDecryptor_h__

View file

@ -0,0 +1,194 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ClearKeyStorage.h"
#include "ClearKeyUtils.h"
#include "gmp-task-utils.h"
#include <assert.h>
#include "ArrayUtils.h"
#include <vector>
static GMPErr
RunOnMainThread(GMPTask* aTask)
{
return GetPlatform()->runonmainthread(aTask);
}
GMPErr
OpenRecord(const char* aName,
uint32_t aNameLength,
GMPRecord** aOutRecord,
GMPRecordClient* aClient)
{
return GetPlatform()->createrecord(aName, aNameLength, aOutRecord, aClient);
}
class WriteRecordClient : public GMPRecordClient {
public:
/*
* This function will take the memory ownership of the parameters and
* delete them when done.
*/
static void Write(const std::string& aRecordName,
const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure) {
(new WriteRecordClient(aData, aOnSuccess, aOnFailure))->Do(aRecordName);
}
virtual void OpenComplete(GMPErr aStatus) override {
if (GMP_FAILED(aStatus) ||
GMP_FAILED(mRecord->Write(&mData.front(), mData.size()))) {
Done(mOnFailure, mOnSuccess);
}
}
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aDataSize) override {
assert(false); // Should not reach here.
}
virtual void WriteComplete(GMPErr aStatus) override {
if (GMP_FAILED(aStatus)) {
Done(mOnFailure, mOnSuccess);
} else {
Done(mOnSuccess, mOnFailure);
}
}
private:
WriteRecordClient(const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure)
: mRecord(nullptr)
, mOnSuccess(aOnSuccess)
, mOnFailure(aOnFailure)
, mData(aData) {}
void Do(const std::string& aName) {
auto err = OpenRecord(aName.c_str(), aName.size(), &mRecord, this);
if (GMP_FAILED(err) ||
GMP_FAILED(mRecord->Open())) {
Done(mOnFailure, mOnSuccess);
}
}
void Done(GMPTask* aToRun, GMPTask* aToDestroy) {
// Note: Call Close() before running continuation, in case the
// continuation tries to open the same record; if we call Close()
// after running the continuation, the Close() call will arrive
// just after the Open() call succeeds, immediately closing the
// record we just opened.
if (mRecord) {
mRecord->Close();
}
aToDestroy->Destroy();
RunOnMainThread(aToRun);
delete this;
}
GMPRecord* mRecord;
GMPTask* mOnSuccess;
GMPTask* mOnFailure;
const std::vector<uint8_t> mData;
};
void
StoreData(const std::string& aRecordName,
const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure)
{
WriteRecordClient::Write(aRecordName, aData, aOnSuccess, aOnFailure);
}
class ReadRecordClient : public GMPRecordClient {
public:
/*
* This function will take the memory ownership of the parameters and
* delete them when done.
*/
static void Read(const std::string& aRecordName,
ReadContinuation* aContinuation) {
assert(aContinuation);
(new ReadRecordClient(aContinuation))->Do(aRecordName);
}
virtual void OpenComplete(GMPErr aStatus) override {
auto err = aStatus;
if (GMP_FAILED(err) ||
GMP_FAILED(err = mRecord->Read())) {
Done(err, nullptr, 0);
}
}
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aDataSize) override {
Done(aStatus, aData, aDataSize);
}
virtual void WriteComplete(GMPErr aStatus) override {
assert(false); // Should not reach here.
}
private:
explicit ReadRecordClient(ReadContinuation* aContinuation)
: mRecord(nullptr)
, mContinuation(aContinuation) {}
void Do(const std::string& aName) {
auto err = OpenRecord(aName.c_str(), aName.size(), &mRecord, this);
if (GMP_FAILED(err) ||
GMP_FAILED(err = mRecord->Open())) {
Done(err, nullptr, 0);
}
}
void Done(GMPErr err, const uint8_t* aData, uint32_t aDataSize) {
// Note: Call Close() before running continuation, in case the
// continuation tries to open the same record; if we call Close()
// after running the continuation, the Close() call will arrive
// just after the Open() call succeeds, immediately closing the
// record we just opened.
if (mRecord) {
mRecord->Close();
}
mContinuation->ReadComplete(err, aData, aDataSize);
delete mContinuation;
delete this;
}
GMPRecord* mRecord;
ReadContinuation* mContinuation;
};
void
ReadData(const std::string& aRecordName,
ReadContinuation* aContinuation)
{
ReadRecordClient::Read(aRecordName, aContinuation);
}
GMPErr
EnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc)
{
return GetPlatform()->getrecordenumerator(aRecvIteratorFunc, nullptr);
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeyStorage_h__
#define __ClearKeyStorage_h__
#include "gmp-api/gmp-errors.h"
#include "gmp-api/gmp-platform.h"
#include <string>
#include <vector>
#include <stdint.h>
class GMPTask;
// Responsible for ensuring that both aOnSuccess and aOnFailure are destroyed.
void StoreData(const std::string& aRecordName,
const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure);
class ReadContinuation {
public:
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aLength) = 0;
virtual ~ReadContinuation() {}
};
// Deletes aContinuation after running it to report the result.
void ReadData(const std::string& aSessionId,
ReadContinuation* aContinuation);
GMPErr EnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc);
#endif // __ClearKeyStorage_h__

View file

@ -0,0 +1,541 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <ctype.h>
#include <stdarg.h>
#include <stdint.h>
#include <vector>
#include "ClearKeyUtils.h"
#include "ClearKeyBase64.h"
#include "ArrayUtils.h"
#include <assert.h>
#include <memory.h>
#include "BigEndian.h"
#include "openaes/oaes_lib.h"
using namespace std;
void
CK_Log(const char* aFmt, ...)
{
va_list ap;
va_start(ap, aFmt);
vprintf(aFmt, ap);
va_end(ap);
printf("\n");
fflush(stdout);
}
static void
IncrementIV(vector<uint8_t>& aIV) {
using mozilla::BigEndian;
assert(aIV.size() == 16);
BigEndian::writeUint64(&aIV[8], BigEndian::readUint64(&aIV[8]) + 1);
}
/* static */ void
ClearKeyUtils::DecryptAES(const vector<uint8_t>& aKey,
vector<uint8_t>& aData, vector<uint8_t>& aIV)
{
assert(aIV.size() == CENC_KEY_LEN);
assert(aKey.size() == CENC_KEY_LEN);
OAES_CTX* aes = oaes_alloc();
oaes_key_import_data(aes, &aKey[0], aKey.size());
oaes_set_option(aes, OAES_OPTION_ECB, nullptr);
for (size_t i = 0; i < aData.size(); i += CENC_KEY_LEN) {
size_t encLen;
oaes_encrypt(aes, &aIV[0], CENC_KEY_LEN, nullptr, &encLen);
vector<uint8_t> enc(encLen);
oaes_encrypt(aes, &aIV[0], CENC_KEY_LEN, &enc[0], &encLen);
assert(encLen >= 2 * OAES_BLOCK_SIZE + CENC_KEY_LEN);
size_t blockLen = min(aData.size() - i, CENC_KEY_LEN);
for (size_t j = 0; j < blockLen; j++) {
aData[i + j] ^= enc[2 * OAES_BLOCK_SIZE + j];
}
IncrementIV(aIV);
}
oaes_free(&aes);
}
/**
* ClearKey expects all Key IDs to be base64 encoded with non-standard alphabet
* and padding.
*/
static bool
EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded)
{
const char sAlphabet[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
const uint8_t sMask = 0x3f;
aEncoded.resize((aBinary.size() * 8 + 5) / 6);
// Pad binary data in case there's rubbish past the last byte.
aBinary.push_back(0);
// Number of bytes not consumed in the previous character
uint32_t shift = 0;
auto out = aEncoded.begin();
auto data = aBinary.begin();
for (string::size_type i = 0; i < aEncoded.length(); i++) {
if (shift) {
out[i] = (*data << (6 - shift)) & sMask;
data++;
} else {
out[i] = 0;
}
out[i] += (*data >> (shift + 2)) & sMask;
shift = (shift + 2) % 8;
// Cast idx to size_t before using it as an array-index,
// to pacify clang 'Wchar-subscripts' warning:
size_t idx = static_cast<size_t>(out[i]);
assert(idx < MOZ_ARRAY_LENGTH(sAlphabet)); // out of bounds index for 'sAlphabet'
out[i] = sAlphabet[idx];
}
return true;
}
/* static */ void
ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
string& aOutRequest,
GMPSessionType aSessionType)
{
assert(aKeyIDs.size() && aOutRequest.empty());
aOutRequest.append("{\"kids\":[");
for (size_t i = 0; i < aKeyIDs.size(); i++) {
if (i) {
aOutRequest.append(",");
}
aOutRequest.append("\"");
string base64key;
EncodeBase64Web(aKeyIDs[i], base64key);
aOutRequest.append(base64key);
aOutRequest.append("\"");
}
aOutRequest.append("],\"type\":");
aOutRequest.append("\"");
aOutRequest.append(SessionTypeToString(aSessionType));
aOutRequest.append("\"}");
}
#define EXPECT_SYMBOL(CTX, X) do { \
if (GetNextSymbol(CTX) != (X)) { \
CK_LOGE("Unexpected symbol in JWK parser"); \
return false; \
} \
} while (false)
struct ParserContext {
const uint8_t* mIter;
const uint8_t* mEnd;
};
static uint8_t
PeekSymbol(ParserContext& aCtx)
{
for (; aCtx.mIter < aCtx.mEnd; (aCtx.mIter)++) {
if (!isspace(*aCtx.mIter)) {
return *aCtx.mIter;
}
}
return 0;
}
static uint8_t
GetNextSymbol(ParserContext& aCtx)
{
uint8_t sym = PeekSymbol(aCtx);
aCtx.mIter++;
return sym;
}
static bool SkipToken(ParserContext& aCtx);
static bool
SkipString(ParserContext& aCtx)
{
EXPECT_SYMBOL(aCtx, '"');
for (uint8_t sym = GetNextSymbol(aCtx); sym; sym = GetNextSymbol(aCtx)) {
if (sym == '\\') {
sym = GetNextSymbol(aCtx);
} else if (sym == '"') {
return true;
}
}
return false;
}
/**
* Skip whole object and values it contains.
*/
static bool
SkipObject(ParserContext& aCtx)
{
EXPECT_SYMBOL(aCtx, '{');
if (PeekSymbol(aCtx) == '}') {
GetNextSymbol(aCtx);
return true;
}
while (true) {
if (!SkipString(aCtx)) return false;
EXPECT_SYMBOL(aCtx, ':');
if (!SkipToken(aCtx)) return false;
if (PeekSymbol(aCtx) == '}') {
GetNextSymbol(aCtx);
return true;
}
EXPECT_SYMBOL(aCtx, ',');
}
return false;
}
/**
* Skip array value and the values it contains.
*/
static bool
SkipArray(ParserContext& aCtx)
{
EXPECT_SYMBOL(aCtx, '[');
if (PeekSymbol(aCtx) == ']') {
GetNextSymbol(aCtx);
return true;
}
while (SkipToken(aCtx)) {
if (PeekSymbol(aCtx) == ']') {
GetNextSymbol(aCtx);
return true;
}
EXPECT_SYMBOL(aCtx, ',');
}
return false;
}
/**
* Skip unquoted literals like numbers, |true|, and |null|.
* (XXX and anything else that matches /([:alnum:]|[+-.])+/)
*/
static bool
SkipLiteral(ParserContext& aCtx)
{
for (; aCtx.mIter < aCtx.mEnd; aCtx.mIter++) {
if (!isalnum(*aCtx.mIter) &&
*aCtx.mIter != '.' && *aCtx.mIter != '-' && *aCtx.mIter != '+') {
return true;
}
}
return false;
}
static bool
SkipToken(ParserContext& aCtx)
{
uint8_t startSym = PeekSymbol(aCtx);
if (startSym == '"') {
CK_LOGD("JWK parser skipping string");
return SkipString(aCtx);
} else if (startSym == '{') {
CK_LOGD("JWK parser skipping object");
return SkipObject(aCtx);
} else if (startSym == '[') {
CK_LOGD("JWK parser skipping array");
return SkipArray(aCtx);
} else {
CK_LOGD("JWK parser skipping literal");
return SkipLiteral(aCtx);
}
return false;
}
static bool
GetNextLabel(ParserContext& aCtx, string& aOutLabel)
{
EXPECT_SYMBOL(aCtx, '"');
const uint8_t* start = aCtx.mIter;
for (uint8_t sym = GetNextSymbol(aCtx); sym; sym = GetNextSymbol(aCtx)) {
if (sym == '\\') {
GetNextSymbol(aCtx);
continue;
}
if (sym == '"') {
aOutLabel.assign(start, aCtx.mIter - 1);
return true;
}
}
return false;
}
static bool
ParseKeyObject(ParserContext& aCtx, KeyIdPair& aOutKey)
{
EXPECT_SYMBOL(aCtx, '{');
// Reject empty objects as invalid licenses.
if (PeekSymbol(aCtx) == '}') {
GetNextSymbol(aCtx);
return false;
}
string keyId;
string key;
while (true) {
string label;
string value;
if (!GetNextLabel(aCtx, label)) {
return false;
}
EXPECT_SYMBOL(aCtx, ':');
if (label == "kty") {
if (!GetNextLabel(aCtx, value)) return false;
// By spec, type must be "oct".
if (value != "oct") return false;
} else if (label == "k" && PeekSymbol(aCtx) == '"') {
// if this isn't a string we will fall through to the SkipToken() path.
if (!GetNextLabel(aCtx, key)) return false;
} else if (label == "kid" && PeekSymbol(aCtx) == '"') {
if (!GetNextLabel(aCtx, keyId)) return false;
} else {
if (!SkipToken(aCtx)) return false;
}
uint8_t sym = PeekSymbol(aCtx);
if (!sym || sym == '}') {
break;
}
EXPECT_SYMBOL(aCtx, ',');
}
return !key.empty() &&
!keyId.empty() &&
DecodeBase64(keyId, aOutKey.mKeyId) &&
DecodeBase64(key, aOutKey.mKey) &&
GetNextSymbol(aCtx) == '}';
}
static bool
ParseKeys(ParserContext& aCtx, vector<KeyIdPair>& aOutKeys)
{
// Consume start of array.
EXPECT_SYMBOL(aCtx, '[');
while (true) {
KeyIdPair key;
if (!ParseKeyObject(aCtx, key)) {
CK_LOGE("Failed to parse key object");
return false;
}
assert(!key.mKey.empty() && !key.mKeyId.empty());
aOutKeys.push_back(key);
uint8_t sym = PeekSymbol(aCtx);
if (!sym || sym == ']') {
break;
}
EXPECT_SYMBOL(aCtx, ',');
}
return GetNextSymbol(aCtx) == ']';
}
/* static */ bool
ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
vector<KeyIdPair>& aOutKeys,
GMPSessionType aSessionType)
{
ParserContext ctx;
ctx.mIter = aKeyData;
ctx.mEnd = aKeyData + aKeyDataSize;
// Consume '{' from start of object.
EXPECT_SYMBOL(ctx, '{');
while (true) {
string label;
// Consume member key.
if (!GetNextLabel(ctx, label)) return false;
EXPECT_SYMBOL(ctx, ':');
if (label == "keys") {
// Parse "keys" array.
if (!ParseKeys(ctx, aOutKeys)) return false;
} else if (label == "type") {
// Consume type string.
string type;
if (!GetNextLabel(ctx, type)) return false;
if (type != SessionTypeToString(aSessionType)) {
return false;
}
} else {
SkipToken(ctx);
}
// Check for end of object.
if (PeekSymbol(ctx) == '}') {
break;
}
// Consume ',' between object members.
EXPECT_SYMBOL(ctx, ',');
}
// Consume '}' from end of object.
EXPECT_SYMBOL(ctx, '}');
return true;
}
static bool
ParseKeyIds(ParserContext& aCtx, vector<KeyId>& aOutKeyIds)
{
// Consume start of array.
EXPECT_SYMBOL(aCtx, '[');
while (true) {
string label;
vector<uint8_t> keyId;
if (!GetNextLabel(aCtx, label) || !DecodeBase64(label, keyId)) {
return false;
}
if (!keyId.empty() && keyId.size() <= kMaxKeyIdsLength) {
aOutKeyIds.push_back(keyId);
}
uint8_t sym = PeekSymbol(aCtx);
if (!sym || sym == ']') {
break;
}
EXPECT_SYMBOL(aCtx, ',');
}
return GetNextSymbol(aCtx) == ']';
}
/* static */ bool
ClearKeyUtils::ParseKeyIdsInitData(const uint8_t* aInitData,
uint32_t aInitDataSize,
vector<KeyId>& aOutKeyIds)
{
ParserContext ctx;
ctx.mIter = aInitData;
ctx.mEnd = aInitData + aInitDataSize;
// Consume '{' from start of object.
EXPECT_SYMBOL(ctx, '{');
while (true) {
string label;
// Consume member kids.
if (!GetNextLabel(ctx, label)) return false;
EXPECT_SYMBOL(ctx, ':');
if (label == "kids") {
// Parse "kids" array.
if (!ParseKeyIds(ctx, aOutKeyIds) ||
aOutKeyIds.empty()) {
return false;
}
} else {
SkipToken(ctx);
}
// Check for end of object.
if (PeekSymbol(ctx) == '}') {
break;
}
// Consume ',' between object members.
EXPECT_SYMBOL(ctx, ',');
}
// Consume '}' from end of object.
EXPECT_SYMBOL(ctx, '}');
return true;
}
/* static */ const char*
ClearKeyUtils::SessionTypeToString(GMPSessionType aSessionType)
{
switch (aSessionType) {
case kGMPTemporySession: return "temporary";
case kGMPPersistentSession: return "persistent-license";
default: {
assert(false); // Should not reach here.
return "invalid";
}
}
}
/* static */ bool
ClearKeyUtils::IsValidSessionId(const char* aBuff, uint32_t aLength)
{
if (aLength > 10) {
// 10 is the max number of characters in UINT32_MAX when
// represented as a string; ClearKey session ids are integers.
return false;
}
for (uint32_t i = 0; i < aLength; i++) {
if (!isdigit(aBuff[i])) {
return false;
}
}
return true;
}
GMPMutex* GMPCreateMutex() {
GMPMutex* mutex;
auto err = GetPlatform()->createmutex(&mutex);
assert(mutex);
return GMP_FAILED(err) ? nullptr : mutex;
}

View file

@ -0,0 +1,114 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeyUtils_h__
#define __ClearKeyUtils_h__
#include <stdint.h>
#include <string>
#include <vector>
#include <assert.h>
#include "gmp-api/gmp-decryption.h"
#if 0
void CK_Log(const char* aFmt, ...);
#define CK_LOGE(...) CK_Log(__VA_ARGS__)
#define CK_LOGD(...) CK_Log(__VA_ARGS__)
#define CK_LOGW(...) CK_Log(__VA_ARGS__)
#else
#define CK_LOGE(...)
#define CK_LOGD(...)
#define CK_LOGW(...)
#endif
struct GMPPlatformAPI;
extern GMPPlatformAPI* GetPlatform();
typedef std::vector<uint8_t> KeyId;
typedef std::vector<uint8_t> Key;
// The session response size should be within a reasonable limit.
// The size 64 KB is referenced from web-platform-test.
static const uint32_t kMaxSessionResponseLength = 65536;
// Provide limitation for KeyIds length and webm initData size.
static const uint32_t kMaxWebmInitDataSize = 65536;
static const uint32_t kMaxKeyIdsLength = 512;
struct KeyIdPair
{
KeyId mKeyId;
Key mKey;
};
class ClearKeyUtils
{
public:
static void DecryptAES(const std::vector<uint8_t>& aKey,
std::vector<uint8_t>& aData, std::vector<uint8_t>& aIV);
static bool ParseKeyIdsInitData(const uint8_t* aInitData,
uint32_t aInitDataSize,
std::vector<KeyId>& aOutKeyIds);
static void MakeKeyRequest(const std::vector<KeyId>& aKeyIds,
std::string& aOutRequest,
GMPSessionType aSessionType);
static bool ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
std::vector<KeyIdPair>& aOutKeys,
GMPSessionType aSessionType);
static const char* SessionTypeToString(GMPSessionType aSessionType);
static bool IsValidSessionId(const char* aBuff, uint32_t aLength);
};
template<class Container, class Element>
inline bool
Contains(const Container& aContainer, const Element& aElement)
{
return aContainer.find(aElement) != aContainer.end();
}
class AutoLock {
public:
explicit AutoLock(GMPMutex* aMutex)
: mMutex(aMutex)
{
assert(aMutex);
if (mMutex) {
mMutex->Acquire();
}
}
~AutoLock() {
if (mMutex) {
mMutex->Release();
}
}
private:
GMPMutex* mMutex;
};
GMPMutex* GMPCreateMutex();
template<typename T>
inline void
Assign(std::vector<T>& aVec, const T* aData, size_t aLength)
{
aVec.assign(aData, aData + aLength);
}
#endif // __ClearKeyUtils_h__

View file

@ -0,0 +1,116 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __RefCount_h__
#define __RefCount_h__
#include <stdint.h>
#include <assert.h>
#include "ClearKeyUtils.h"
#if defined(_MSC_VER)
#include <atomic>
typedef std::atomic<uint32_t> AtomicRefCount;
#else
class AtomicRefCount {
public:
explicit AtomicRefCount(uint32_t aValue)
: mCount(aValue)
, mMutex(GMPCreateMutex())
{
assert(mMutex);
}
~AtomicRefCount()
{
if (mMutex) {
mMutex->Destroy();
}
}
uint32_t operator--() {
AutoLock lock(mMutex);
return --mCount;
}
uint32_t operator++() {
AutoLock lock(mMutex);
return ++mCount;
}
operator uint32_t() {
AutoLock lock(mMutex);
return mCount;
}
private:
uint32_t mCount;
GMPMutex* mMutex;
};
#endif
// Note: Thread safe.
class RefCounted {
public:
void AddRef() {
++mRefCount;
}
uint32_t Release() {
uint32_t newCount = --mRefCount;
if (!newCount) {
delete this;
}
return newCount;
}
protected:
RefCounted()
: mRefCount(0)
{
}
virtual ~RefCounted()
{
assert(!mRefCount);
}
AtomicRefCount mRefCount;
};
template<class T>
class RefPtr {
public:
explicit RefPtr(T* aPtr) : mPtr(nullptr) {
Assign(aPtr);
}
~RefPtr() {
Assign(nullptr);
}
T* operator->() const { return mPtr; }
RefPtr& operator=(T* aVal) {
Assign(aVal);
return *this;
}
private:
void Assign(T* aPtr) {
if (mPtr) {
mPtr->Release();
}
mPtr = aPtr;
if (mPtr) {
aPtr->AddRef();
}
}
T* mPtr;
};
#endif // __RefCount_h__

View file

@ -0,0 +1,455 @@
/*
* Copyright 2013, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cstdint>
#include <limits>
#include "AnnexB.h"
#include "BigEndian.h"
#include "ClearKeyDecryptionManager.h"
#include "ClearKeyUtils.h"
#include "gmp-task-utils.h"
#include "VideoDecoder.h"
using namespace wmf;
VideoDecoder::VideoDecoder(GMPVideoHost *aHostAPI)
: mHostAPI(aHostAPI)
, mCallback(nullptr)
, mWorkerThread(nullptr)
, mMutex(nullptr)
, mNumInputTasks(0)
, mSentExtraData(false)
, mIsFlushing(false)
, mHasShutdown(false)
{
// We drop the ref in DecodingComplete().
AddRef();
}
VideoDecoder::~VideoDecoder()
{
if (mMutex) {
mMutex->Destroy();
}
}
void
VideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
const uint8_t* aCodecSpecific,
uint32_t aCodecSpecificLength,
GMPVideoDecoderCallback* aCallback,
int32_t aCoreCount)
{
mCallback = aCallback;
assert(mCallback);
mDecoder = new WMFH264Decoder();
HRESULT hr = mDecoder->Init(aCoreCount);
if (FAILED(hr)) {
CK_LOGD("VideoDecoder::InitDecode failed to init WMFH264Decoder");
mCallback->Error(GMPGenericErr);
return;
}
auto err = GetPlatform()->createmutex(&mMutex);
if (GMP_FAILED(err)) {
CK_LOGD("VideoDecoder::InitDecode failed to create GMPMutex");
mCallback->Error(GMPGenericErr);
return;
}
// The first byte is mPacketizationMode, which is only relevant for
// WebRTC/OpenH264 usecase.
const uint8_t* avcc = aCodecSpecific + 1;
const uint8_t* avccEnd = aCodecSpecific + aCodecSpecificLength;
mExtraData.insert(mExtraData.end(), avcc, avccEnd);
AnnexB::ConvertConfig(mExtraData, mAnnexB);
}
void
VideoDecoder::EnsureWorker()
{
if (!mWorkerThread) {
GetPlatform()->createthread(&mWorkerThread);
if (!mWorkerThread) {
mCallback->Error(GMPAllocErr);
return;
}
}
}
void
VideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
bool aMissingFrames,
const uint8_t* aCodecSpecificInfo,
uint32_t aCodecSpecificInfoLength,
int64_t aRenderTimeMs)
{
if (aInputFrame->BufferType() != GMP_BufferLength32) {
// Gecko should only send frames with 4 byte NAL sizes to GMPs.
mCallback->Error(GMPGenericErr);
return;
}
EnsureWorker();
{
AutoLock lock(mMutex);
mNumInputTasks++;
}
// Note: we don't need the codec specific info on a per-frame basis.
// It's mostly useful for WebRTC use cases.
// Make a copy of the data, so we can release aInputFrame ASAP,
// to avoid too many shmem handles being held by the GMP process.
// If the GMP process holds on to too many shmem handles, the Gecko
// side can fail to allocate a shmem to send more input. This is
// particularly a problem in Gecko mochitests, which can open multiple
// actors at once which share the same pool of shmems.
DecodeData* data = new DecodeData();
Assign(data->mBuffer, aInputFrame->Buffer(), aInputFrame->Size());
data->mTimestamp = aInputFrame->TimeStamp();
data->mDuration = aInputFrame->Duration();
data->mIsKeyframe = (aInputFrame->FrameType() == kGMPKeyFrame);
const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
if (crypto) {
data->mCrypto.Init(crypto);
}
aInputFrame->Destroy();
mWorkerThread->Post(WrapTaskRefCounted(this,
&VideoDecoder::DecodeTask,
data));
}
void
VideoDecoder::DecodeTask(DecodeData* aData)
{
CK_LOGD("VideoDecoder::DecodeTask");
AutoPtr<DecodeData> d(aData);
HRESULT hr;
{
AutoLock lock(mMutex);
mNumInputTasks--;
assert(mNumInputTasks >= 0);
}
if (mIsFlushing) {
CK_LOGD("VideoDecoder::DecodeTask rejecting frame: flushing.");
return;
}
if (!aData || !mHostAPI || !mDecoder) {
CK_LOGE("Decode job not set up correctly!");
return;
}
std::vector<uint8_t>& buffer = aData->mBuffer;
if (aData->mCrypto.IsValid()) {
// Plugin host should have set up its decryptor/key sessions
// before trying to decode!
GMPErr rv =
ClearKeyDecryptionManager::Get()->Decrypt(buffer, aData->mCrypto);
if (GMP_FAILED(rv)) {
MaybeRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::Error, rv));
return;
}
}
AnnexB::ConvertFrameInPlace(buffer);
if (aData->mIsKeyframe) {
// We must send the SPS and PPS to Windows Media Foundation's decoder.
// Note: We do this *after* decryption, otherwise the subsample info
// would be incorrect.
buffer.insert(buffer.begin(), mAnnexB.begin(), mAnnexB.end());
}
hr = mDecoder->Input(buffer.data(),
buffer.size(),
aData->mTimestamp,
aData->mDuration);
CK_LOGD("VideoDecoder::DecodeTask() Input ret hr=0x%x\n", hr);
if (FAILED(hr)) {
CK_LOGE("VideoDecoder::DecodeTask() decode failed ret=0x%x%s\n",
hr,
((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
return;
}
while (hr == S_OK) {
CComPtr<IMFSample> output;
hr = mDecoder->Output(&output);
CK_LOGD("VideoDecoder::DecodeTask() output ret=0x%x\n", hr);
if (hr == S_OK) {
MaybeRunOnMainThread(
WrapTaskRefCounted(this,
&VideoDecoder::ReturnOutput,
CComPtr<IMFSample>(output),
mDecoder->GetFrameWidth(),
mDecoder->GetFrameHeight(),
mDecoder->GetStride()));
}
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
AutoLock lock(mMutex);
if (mNumInputTasks == 0) {
// We have run all input tasks. We *must* notify Gecko so that it will
// send us more data.
MaybeRunOnMainThread(
WrapTask(mCallback,
&GMPVideoDecoderCallback::InputDataExhausted));
}
}
if (FAILED(hr)) {
CK_LOGE("VideoDecoder::DecodeTask() output failed hr=0x%x\n", hr);
}
}
}
void
VideoDecoder::ReturnOutput(IMFSample* aSample,
int32_t aWidth,
int32_t aHeight,
int32_t aStride)
{
CK_LOGD("[%p] VideoDecoder::ReturnOutput()\n", this);
assert(aSample);
HRESULT hr;
GMPVideoFrame* f = nullptr;
auto err = mHostAPI->CreateFrame(kGMPI420VideoFrame, &f);
if (GMP_FAILED(err) || !f) {
CK_LOGE("Failed to create i420 frame!\n");
return;
}
if (HasShutdown()) {
// Note: GMPVideoHost::CreateFrame() can process messages before returning,
// including a message that calls VideoDecoder::DecodingComplete(), i.e.
// we can shutdown during the call!
CK_LOGD("Shutdown while waiting on GMPVideoHost::CreateFrame()!\n");
f->Destroy();
return;
}
auto vf = static_cast<GMPVideoi420Frame*>(f);
hr = SampleToVideoFrame(aSample, aWidth, aHeight, aStride, vf);
ENSURE(SUCCEEDED(hr), /*void*/);
mCallback->Decoded(vf);
}
HRESULT
VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
int32_t aWidth,
int32_t aHeight,
int32_t aStride,
GMPVideoi420Frame* aVideoFrame)
{
ENSURE(aSample != nullptr, E_POINTER);
ENSURE(aVideoFrame != nullptr, E_POINTER);
HRESULT hr;
CComPtr<IMFMediaBuffer> mediaBuffer;
// Must convert to contiguous mediaBuffer to use IMD2DBuffer interface.
hr = aSample->ConvertToContiguousBuffer(&mediaBuffer);
ENSURE(SUCCEEDED(hr), hr);
// Try and use the IMF2DBuffer interface if available, otherwise fallback
// to the IMFMediaBuffer interface. Apparently IMF2DBuffer is more efficient,
// but only some systems (Windows 8?) support it.
BYTE* data = nullptr;
LONG stride = 0;
CComPtr<IMF2DBuffer> twoDBuffer;
hr = mediaBuffer->QueryInterface(static_cast<IMF2DBuffer**>(&twoDBuffer));
if (SUCCEEDED(hr)) {
hr = twoDBuffer->Lock2D(&data, &stride);
ENSURE(SUCCEEDED(hr), hr);
} else {
hr = mediaBuffer->Lock(&data, NULL, NULL);
ENSURE(SUCCEEDED(hr), hr);
stride = aStride;
}
// The V and U planes are stored 16-row-aligned, so we need to add padding
// to the row heights to ensure the Y'CbCr planes are referenced properly.
// YV12, planar format: [YYYY....][VVVV....][UUUU....]
// i.e., Y, then V, then U.
uint32_t padding = 0;
if (aHeight % 16 != 0) {
padding = 16 - (aHeight % 16);
}
int32_t y_size = stride * (aHeight + padding);
int32_t v_size = stride * (aHeight + padding) / 4;
int32_t halfStride = (stride + 1) / 2;
int32_t halfHeight = (aHeight + 1) / 2;
auto err = aVideoFrame->CreateEmptyFrame(stride, aHeight, stride, halfStride, halfStride);
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
err = aVideoFrame->SetWidth(aWidth);
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
err = aVideoFrame->SetHeight(aHeight);
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
uint8_t* outBuffer = aVideoFrame->Buffer(kGMPYPlane);
ENSURE(outBuffer != nullptr, E_FAIL);
assert(aVideoFrame->AllocatedSize(kGMPYPlane) >= stride*aHeight);
memcpy(outBuffer, data, stride*aHeight);
outBuffer = aVideoFrame->Buffer(kGMPUPlane);
ENSURE(outBuffer != nullptr, E_FAIL);
assert(aVideoFrame->AllocatedSize(kGMPUPlane) >= halfStride*halfHeight);
memcpy(outBuffer, data+y_size, halfStride*halfHeight);
outBuffer = aVideoFrame->Buffer(kGMPVPlane);
ENSURE(outBuffer != nullptr, E_FAIL);
assert(aVideoFrame->AllocatedSize(kGMPVPlane) >= halfStride*halfHeight);
memcpy(outBuffer, data + y_size + v_size, halfStride*halfHeight);
if (twoDBuffer) {
twoDBuffer->Unlock2D();
} else {
mediaBuffer->Unlock();
}
LONGLONG hns = 0;
hr = aSample->GetSampleTime(&hns);
ENSURE(SUCCEEDED(hr), hr);
aVideoFrame->SetTimestamp(HNsToUsecs(hns));
hr = aSample->GetSampleDuration(&hns);
ENSURE(SUCCEEDED(hr), hr);
aVideoFrame->SetDuration(HNsToUsecs(hns));
return S_OK;
}
void
VideoDecoder::ResetCompleteTask()
{
mIsFlushing = false;
if (mCallback) {
MaybeRunOnMainThread(WrapTask(mCallback,
&GMPVideoDecoderCallback::ResetComplete));
}
}
void
VideoDecoder::Reset()
{
mIsFlushing = true;
if (mDecoder) {
mDecoder->Reset();
}
// Schedule ResetComplete callback to run after existing frames have been
// flushed out of the task queue.
EnsureWorker();
mWorkerThread->Post(WrapTaskRefCounted(this,
&VideoDecoder::ResetCompleteTask));
}
void
VideoDecoder::DrainTask()
{
mDecoder->Drain();
// Return any pending output.
HRESULT hr = S_OK;
while (hr == S_OK) {
CComPtr<IMFSample> output;
hr = mDecoder->Output(&output);
CK_LOGD("VideoDecoder::DrainTask() output ret=0x%x\n", hr);
if (hr == S_OK) {
MaybeRunOnMainThread(
WrapTaskRefCounted(this,
&VideoDecoder::ReturnOutput,
CComPtr<IMFSample>(output),
mDecoder->GetFrameWidth(),
mDecoder->GetFrameHeight(),
mDecoder->GetStride()));
}
}
MaybeRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::DrainComplete));
}
void
VideoDecoder::Drain()
{
if (!mDecoder) {
if (mCallback) {
mCallback->DrainComplete();
}
return;
}
EnsureWorker();
mWorkerThread->Post(WrapTaskRefCounted(this,
&VideoDecoder::DrainTask));
}
void
VideoDecoder::DecodingComplete()
{
if (mWorkerThread) {
mWorkerThread->Join();
}
mHasShutdown = true;
// Release the reference we added in the constructor. There may be
// WrapRefCounted tasks that also hold references to us, and keep
// us alive a little longer.
Release();
}
void
VideoDecoder::MaybeRunOnMainThread(GMPTask* aTask)
{
class MaybeRunTask : public GMPTask
{
public:
MaybeRunTask(VideoDecoder* aDecoder, GMPTask* aTask)
: mDecoder(aDecoder), mTask(aTask)
{ }
virtual void Run(void) {
if (mDecoder->HasShutdown()) {
CK_LOGD("Trying to dispatch to main thread after VideoDecoder has shut down");
return;
}
mTask->Run();
}
virtual void Destroy()
{
mTask->Destroy();
delete this;
}
private:
RefPtr<VideoDecoder> mDecoder;
GMPTask* mTask;
};
GetPlatform()->runonmainthread(new MaybeRunTask(this, aTask));
}

View file

@ -0,0 +1,110 @@
/*
* Copyright 2013, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __VideoDecoder_h__
#define __VideoDecoder_h__
#include <atomic>
#include "gmp-task-utils.h"
#include "gmp-video-decode.h"
#include "gmp-video-host.h"
#include "WMFH264Decoder.h"
#include "mfobjects.h"
class VideoDecoder : public GMPVideoDecoder
, public RefCounted
{
public:
VideoDecoder(GMPVideoHost *aHostAPI);
virtual void InitDecode(const GMPVideoCodec& aCodecSettings,
const uint8_t* aCodecSpecific,
uint32_t aCodecSpecificLength,
GMPVideoDecoderCallback* aCallback,
int32_t aCoreCount) override;
virtual void Decode(GMPVideoEncodedFrame* aInputFrame,
bool aMissingFrames,
const uint8_t* aCodecSpecific,
uint32_t aCodecSpecificLength,
int64_t aRenderTimeMs = -1);
virtual void Reset() override;
virtual void Drain() override;
virtual void DecodingComplete() override;
bool HasShutdown() { return mHasShutdown; }
private:
virtual ~VideoDecoder();
void EnsureWorker();
void DrainTask();
struct DecodeData {
DecodeData()
: mTimestamp(0)
, mDuration(0)
, mIsKeyframe(false)
{}
std::vector<uint8_t> mBuffer;
uint64_t mTimestamp;
uint64_t mDuration;
bool mIsKeyframe;
CryptoMetaData mCrypto;
};
void DecodeTask(DecodeData* aData);
void ResetCompleteTask();
void ReturnOutput(IMFSample* aSample,
int32_t aWidth,
int32_t aHeight,
int32_t aStride);
HRESULT SampleToVideoFrame(IMFSample* aSample,
int32_t aWidth,
int32_t aHeight,
int32_t aStride,
GMPVideoi420Frame* aVideoFrame);
void MaybeRunOnMainThread(GMPTask* aTask);
GMPVideoHost *mHostAPI; // host-owned, invalid at DecodingComplete
GMPVideoDecoderCallback* mCallback; // host-owned, invalid at DecodingComplete
GMPThread* mWorkerThread;
GMPMutex* mMutex;
wmf::AutoPtr<wmf::WMFH264Decoder> mDecoder;
std::vector<uint8_t> mExtraData;
std::vector<uint8_t> mAnnexB;
int32_t mNumInputTasks;
bool mSentExtraData;
std::atomic<bool> mIsFlushing;
bool mHasShutdown;
};
#endif // __VideoDecoder_h__

View file

@ -0,0 +1,366 @@
/*
* Copyright 2013, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "WMFAACDecoder.h"
#include <algorithm>
#include <stdint.h>
#include <vector>
using std::vector;
namespace wmf {
WMFAACDecoder::WMFAACDecoder()
: mDecoder(nullptr)
, mChannels(0)
, mRate(0)
{
memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO));
memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO));
}
WMFAACDecoder::~WMFAACDecoder()
{
Reset();
}
HRESULT
AACAudioSpecificConfigToUserData(BYTE* aAudioSpecConfig, UINT32 aConfigLength,
BYTE** aOutUserData, UINT32* aOutUserDataLength)
{
// For MFAudioFormat_AAC, MF_MT_USER_DATA
// Contains the portion of the HEAACWAVEINFO structure that appears
// after the WAVEFORMATEX structure (that is, after the wfx member).
// This is followed by the AudioSpecificConfig() data, as defined
// by ISO/IEC 14496-3.
// ...
// The length of the AudioSpecificConfig() data is 2 bytes for AAC-LC
// or HE-AAC with implicit signaling of SBR/PS. It is more than 2 bytes
// for HE-AAC with explicit signaling of SBR/PS.
//
// The value of audioObjectType as defined in AudioSpecificConfig()
// must be 2, indicating AAC-LC. The value of extensionAudioObjectType
// must be 5 for SBR or 29 for PS.
//
// See:
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd742784%28v=vs.85%29.aspx
//
// HEAACWAVEINFO structure:
// typedef struct heaacwaveinfo_tag {
// WAVEFORMATEX wfx;
// WORD wPayloadType;
// WORD wAudioProfileLevelIndication;
// WORD wStructType;
// WORD wReserved1;
// DWORD dwReserved2;
// }
const UINT32 heeInfoLen = 4 * sizeof(WORD) + sizeof(DWORD);
BYTE heeInfo[heeInfoLen] = {0};
WORD* w = (WORD*)heeInfo;
w[0] = 0x0; // Payload type raw AAC
w[1] = 0; // Profile level unspecified
const UINT32 len = heeInfoLen + aConfigLength;
BYTE* data = new BYTE[len];
memcpy(data, heeInfo, heeInfoLen);
memcpy(data+heeInfoLen, aAudioSpecConfig, aConfigLength);
*aOutUserData = data;
*aOutUserDataLength = len;
return S_OK;
}
HRESULT
WMFAACDecoder::Init(int32_t aChannelCount,
int32_t aSampleRate,
BYTE* aAACAudioSpecificConfig,
UINT32 aAudioConfigLength)
{
HRESULT hr;
// AAC decoder is in msauddecmft on Win8, and msmpeg2adec in earlier versions.
hr = CreateMFT(CLSID_CMSAACDecMFT,
WMFDecoderDllNameFor(AAC),
mDecoder);
if (FAILED(hr)) {
hr = CreateMFT(CLSID_CMSAACDecMFT,
WMFDecoderDllNameFor(AAC),
mDecoder);
if (FAILED(hr)) {
LOG("Failed to create AAC decoder\n");
return E_FAIL;
}
}
BYTE* userData = nullptr;
UINT32 userDataLength;
hr = AACAudioSpecificConfigToUserData(aAACAudioSpecificConfig,
aAudioConfigLength,
&userData,
&userDataLength);
ENSURE(SUCCEEDED(hr), hr);
hr = SetDecoderInputType(aChannelCount, aSampleRate, userData, userDataLength);
delete userData;
ENSURE(SUCCEEDED(hr), hr);
hr = SetDecoderOutputType();
ENSURE(SUCCEEDED(hr), hr);
hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
ENSURE(SUCCEEDED(hr), hr);
hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
ENSURE(SUCCEEDED(hr), hr);
hr = mDecoder->GetInputStreamInfo(0, &mInputStreamInfo);
ENSURE(SUCCEEDED(hr), hr);
hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo);
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
HRESULT
WMFAACDecoder::SetDecoderInputType(int32_t aChannelCount,
int32_t aSampleRate,
BYTE* aUserData,
UINT32 aUserDataLength)
{
HRESULT hr;
CComPtr<IMFMediaType> type;
hr = MFCreateMediaType(&type);
ENSURE(SUCCEEDED(hr), hr);
hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
ENSURE(SUCCEEDED(hr), hr);
hr = type->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_AAC);
ENSURE(SUCCEEDED(hr), hr);
mRate = aSampleRate;
hr = type->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, mRate);
ENSURE(SUCCEEDED(hr), hr);
mChannels = aChannelCount;
hr = type->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, mChannels);
ENSURE(SUCCEEDED(hr), hr);
hr = type->SetBlob(MF_MT_USER_DATA, aUserData, aUserDataLength);
ENSURE(SUCCEEDED(hr), hr);
hr = mDecoder->SetInputType(0, type, 0);
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
HRESULT
WMFAACDecoder::SetDecoderOutputType()
{
HRESULT hr;
CComPtr<IMFMediaType> type;
UINT32 typeIndex = 0;
while (type = nullptr, SUCCEEDED(mDecoder->GetOutputAvailableType(0, typeIndex++, &type))) {
GUID subtype;
hr = type->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr)) {
continue;
}
if (subtype == MFAudioFormat_PCM) {
hr = mDecoder->SetOutputType(0, type, 0);
ENSURE(SUCCEEDED(hr), hr);
hr = type->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &mChannels);
ENSURE(SUCCEEDED(hr), hr);
hr = type->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &mRate);
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
}
return E_FAIL;
}
HRESULT
WMFAACDecoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData)
{
ENSURE(mDecoder != nullptr, E_POINTER);
HRESULT hr = mDecoder->ProcessMessage(aMsg, aData);
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
HRESULT
WMFAACDecoder::CreateInputSample(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp,
IMFSample** aOutSample)
{
HRESULT hr;
CComPtr<IMFSample> sample = nullptr;
hr = MFCreateSample(&sample);
ENSURE(SUCCEEDED(hr), hr);
CComPtr<IMFMediaBuffer> buffer = nullptr;
int32_t bufferSize = std::max<uint32_t>(uint32_t(mInputStreamInfo.cbSize), aDataSize);
UINT32 alignment = (mInputStreamInfo.cbAlignment > 1) ? mInputStreamInfo.cbAlignment - 1 : 0;
hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
ENSURE(SUCCEEDED(hr), hr);
DWORD maxLength = 0;
DWORD currentLength = 0;
BYTE* dst = nullptr;
hr = buffer->Lock(&dst, &maxLength, &currentLength);
ENSURE(SUCCEEDED(hr), hr);
// Copy data into sample's buffer.
memcpy(dst, aData, aDataSize);
hr = buffer->Unlock();
ENSURE(SUCCEEDED(hr), hr);
hr = buffer->SetCurrentLength(aDataSize);
ENSURE(SUCCEEDED(hr), hr);
hr = sample->AddBuffer(buffer);
ENSURE(SUCCEEDED(hr), hr);
hr = sample->SetSampleTime(UsecsToHNs(aTimestamp));
ENSURE(SUCCEEDED(hr), hr);
*aOutSample = sample.Detach();
return S_OK;
}
HRESULT
WMFAACDecoder::CreateOutputSample(IMFSample** aOutSample)
{
HRESULT hr;
CComPtr<IMFSample> sample = nullptr;
hr = MFCreateSample(&sample);
ENSURE(SUCCEEDED(hr), hr);
CComPtr<IMFMediaBuffer> buffer = nullptr;
int32_t bufferSize = mOutputStreamInfo.cbSize;
UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1) ? mOutputStreamInfo.cbAlignment - 1 : 0;
hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
ENSURE(SUCCEEDED(hr), hr);
hr = sample->AddBuffer(buffer);
ENSURE(SUCCEEDED(hr), hr);
*aOutSample = sample.Detach();
return S_OK;
}
HRESULT
WMFAACDecoder::GetOutputSample(IMFSample** aOutSample)
{
HRESULT hr;
// We allocate samples for MFT output.
MFT_OUTPUT_DATA_BUFFER output = {0};
CComPtr<IMFSample> sample = nullptr;
hr = CreateOutputSample(&sample);
ENSURE(SUCCEEDED(hr), hr);
output.pSample = sample;
DWORD status = 0;
hr = mDecoder->ProcessOutput(0, 1, &output, &status);
CComPtr<IMFCollection> events = output.pEvents; // Ensure this is released.
if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
// Type change. Probably geometric apperature change.
hr = SetDecoderOutputType();
ENSURE(SUCCEEDED(hr), hr);
return GetOutputSample(aOutSample);
} else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT || !sample) {
return MF_E_TRANSFORM_NEED_MORE_INPUT;
}
// Treat other errors as fatal.
ENSURE(SUCCEEDED(hr), hr);
assert(sample);
*aOutSample = sample.Detach();
return S_OK;
}
HRESULT
WMFAACDecoder::Input(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp)
{
CComPtr<IMFSample> input = nullptr;
HRESULT hr = CreateInputSample(aData, aDataSize, aTimestamp, &input);
ENSURE(SUCCEEDED(hr) && input!=nullptr, hr);
hr = mDecoder->ProcessInput(0, input, 0);
if (hr == MF_E_NOTACCEPTING) {
// MFT *already* has enough data to produce a sample. Retrieve it.
LOG("ProcessInput returned MF_E_NOTACCEPTING\n");
return MF_E_NOTACCEPTING;
}
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
HRESULT
WMFAACDecoder::Output(IMFSample** aOutput)
{
CComPtr<IMFSample> outputSample = nullptr;
HRESULT hr = GetOutputSample(&outputSample);
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
return MF_E_TRANSFORM_NEED_MORE_INPUT;
}
// Treat other errors as fatal.
ENSURE(SUCCEEDED(hr) && outputSample, hr);
*aOutput = outputSample.Detach();
return S_OK;
}
HRESULT
WMFAACDecoder::Reset()
{
HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
HRESULT
WMFAACDecoder::Drain()
{
HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright 2013, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !defined(WMFAACDecoder_h_)
#define WMFAACDecoder_h_
#include "WMFUtils.h"
namespace wmf {
class WMFAACDecoder {
public:
WMFAACDecoder();
~WMFAACDecoder();
HRESULT Init(int32_t aChannelCount,
int32_t aSampleRate,
BYTE* aUserData,
UINT32 aUserDataLength);
HRESULT Input(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp);
HRESULT Output(IMFSample** aOutput);
HRESULT Reset();
HRESULT Drain();
UINT32 Channels() const { return mChannels; }
UINT32 Rate() const { return mRate; }
private:
HRESULT GetOutputSample(IMFSample** aOutSample);
HRESULT CreateOutputSample(IMFSample** aOutSample);
HRESULT CreateInputSample(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp,
IMFSample** aOutSample);
HRESULT SetDecoderInputType(int32_t aChannelCount,
int32_t aSampleRate,
BYTE* aUserData,
UINT32 aUserDataLength);
HRESULT SetDecoderOutputType();
HRESULT SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData);
MFT_INPUT_STREAM_INFO mInputStreamInfo;
MFT_OUTPUT_STREAM_INFO mOutputStreamInfo;
CComPtr<IMFTransform> mDecoder;
UINT32 mChannels;
UINT32 mRate;
};
}
#endif

View file

@ -0,0 +1,365 @@
/*
* Copyright 2013, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "WMFH264Decoder.h"
#include <algorithm>
#include <codecapi.h>
namespace wmf {
WMFH264Decoder::WMFH264Decoder()
: mDecoder(nullptr)
{
memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO));
memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO));
}
WMFH264Decoder::~WMFH264Decoder()
{
}
HRESULT
WMFH264Decoder::Init(int32_t aCoreCount)
{
HRESULT hr;
hr = CreateMFT(__uuidof(CMSH264DecoderMFT),
WMFDecoderDllNameFor(H264),
mDecoder);
if (FAILED(hr)) {
// Windows 7 Enterprise Server N (which is what Mozilla's mochitests run
// on) need a different CLSID to instantiate the H.264 decoder.
hr = CreateMFT(CLSID_CMSH264DecMFT,
WMFDecoderDllNameFor(H264),
mDecoder);
}
ENSURE(SUCCEEDED(hr), hr);
CComPtr<IMFAttributes> attr;
hr = mDecoder->GetAttributes(&attr);
ENSURE(SUCCEEDED(hr), hr);
hr = attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads,
GetNumThreads(aCoreCount));
ENSURE(SUCCEEDED(hr), hr);
hr = SetDecoderInputType();
ENSURE(SUCCEEDED(hr), hr);
hr = SetDecoderOutputType();
ENSURE(SUCCEEDED(hr), hr);
hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
ENSURE(SUCCEEDED(hr), hr);
hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
ENSURE(SUCCEEDED(hr), hr);
hr = mDecoder->GetInputStreamInfo(0, &mInputStreamInfo);
ENSURE(SUCCEEDED(hr), hr);
hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo);
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
HRESULT
WMFH264Decoder::ConfigureVideoFrameGeometry(IMFMediaType* aMediaType)
{
ENSURE(aMediaType != nullptr, E_POINTER);
HRESULT hr;
IntRect pictureRegion;
hr = wmf::GetPictureRegion(aMediaType, pictureRegion);
ENSURE(SUCCEEDED(hr), hr);
UINT32 width = 0, height = 0;
hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height);
ENSURE(SUCCEEDED(hr), hr);
ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL);
UINT32 stride = 0;
hr = GetDefaultStride(aMediaType, &stride);
ENSURE(SUCCEEDED(hr), hr);
ENSURE(stride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
// Success! Save state.
mStride = stride;
mVideoWidth = width;
mVideoHeight = height;
mPictureRegion = pictureRegion;
LOG("WMFH264Decoder frame geometry frame=(%u,%u) stride=%u picture=(%d, %d, %d, %d)\n",
width, height,
mStride,
mPictureRegion.x, mPictureRegion.y, mPictureRegion.width, mPictureRegion.height);
return S_OK;
}
int32_t
WMFH264Decoder::GetFrameWidth() const
{
return mVideoWidth;
}
int32_t
WMFH264Decoder::GetFrameHeight() const
{
return mVideoHeight;
}
const IntRect&
WMFH264Decoder::GetPictureRegion() const
{
return mPictureRegion;
}
int32_t
WMFH264Decoder::GetStride() const
{
return mStride;
}
HRESULT
WMFH264Decoder::SetDecoderInputType()
{
HRESULT hr;
CComPtr<IMFMediaType> type;
hr = MFCreateMediaType(&type);
ENSURE(SUCCEEDED(hr), hr);
hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
ENSURE(SUCCEEDED(hr), hr);
hr = type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
ENSURE(SUCCEEDED(hr), hr);
hr = type->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_MixedInterlaceOrProgressive);
ENSURE(SUCCEEDED(hr), hr);
hr = mDecoder->SetInputType(0, type, 0);
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
HRESULT
WMFH264Decoder::SetDecoderOutputType()
{
HRESULT hr;
CComPtr<IMFMediaType> type;
UINT32 typeIndex = 0;
while (type = nullptr, SUCCEEDED(mDecoder->GetOutputAvailableType(0, typeIndex++, &type))) {
GUID subtype;
hr = type->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr)) {
continue;
}
if (subtype == MFVideoFormat_I420 || subtype == MFVideoFormat_IYUV) {
// On Windows 7 Enterprise N the MFT reports it reports IYUV instead
// of I420. Other Windows' report I420. The formats are the same, so
// support both.
hr = mDecoder->SetOutputType(0, type, 0);
ENSURE(SUCCEEDED(hr), hr);
hr = ConfigureVideoFrameGeometry(type);
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
}
return E_FAIL;
}
HRESULT
WMFH264Decoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData)
{
ENSURE(mDecoder != nullptr, E_POINTER);
HRESULT hr = mDecoder->ProcessMessage(aMsg, aData);
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
HRESULT
WMFH264Decoder::CreateInputSample(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp,
Microseconds aDuration,
IMFSample** aOutSample)
{
HRESULT hr;
CComPtr<IMFSample> sample;
hr = MFCreateSample(&sample);
ENSURE(SUCCEEDED(hr), hr);
CComPtr<IMFMediaBuffer> buffer;
int32_t bufferSize = std::max<uint32_t>(uint32_t(mInputStreamInfo.cbSize), aDataSize);
UINT32 alignment = (mInputStreamInfo.cbAlignment > 1) ? mInputStreamInfo.cbAlignment - 1 : 0;
hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
ENSURE(SUCCEEDED(hr), hr);
DWORD maxLength = 0;
DWORD currentLength = 0;
BYTE* dst = nullptr;
hr = buffer->Lock(&dst, &maxLength, &currentLength);
ENSURE(SUCCEEDED(hr), hr);
// Copy data into sample's buffer.
memcpy(dst, aData, aDataSize);
hr = buffer->Unlock();
ENSURE(SUCCEEDED(hr), hr);
hr = buffer->SetCurrentLength(aDataSize);
ENSURE(SUCCEEDED(hr), hr);
hr = sample->AddBuffer(buffer);
ENSURE(SUCCEEDED(hr), hr);
hr = sample->SetSampleTime(UsecsToHNs(aTimestamp));
ENSURE(SUCCEEDED(hr), hr);
sample->SetSampleDuration(UsecsToHNs(aDuration));
*aOutSample = sample.Detach();
return S_OK;
}
HRESULT
WMFH264Decoder::CreateOutputSample(IMFSample** aOutSample)
{
HRESULT hr;
CComPtr<IMFSample> sample;
hr = MFCreateSample(&sample);
ENSURE(SUCCEEDED(hr), hr);
CComPtr<IMFMediaBuffer> buffer;
int32_t bufferSize = mOutputStreamInfo.cbSize;
UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1) ? mOutputStreamInfo.cbAlignment - 1 : 0;
hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
ENSURE(SUCCEEDED(hr), hr);
hr = sample->AddBuffer(buffer);
ENSURE(SUCCEEDED(hr), hr);
*aOutSample = sample.Detach();
return S_OK;
}
HRESULT
WMFH264Decoder::GetOutputSample(IMFSample** aOutSample)
{
HRESULT hr;
// We allocate samples for MFT output.
MFT_OUTPUT_DATA_BUFFER output = {0};
CComPtr<IMFSample> sample;
hr = CreateOutputSample(&sample);
ENSURE(SUCCEEDED(hr), hr);
output.pSample = sample;
DWORD status = 0;
hr = mDecoder->ProcessOutput(0, 1, &output, &status);
//LOG(L"WMFH264Decoder::GetOutputSample() ProcessOutput returned 0x%x\n", hr);
CComPtr<IMFCollection> events = output.pEvents; // Ensure this is released.
if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
// Type change. Probably geometric apperature change.
hr = SetDecoderOutputType();
ENSURE(SUCCEEDED(hr), hr);
return GetOutputSample(aOutSample);
} else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
return MF_E_TRANSFORM_NEED_MORE_INPUT;
}
// Treat other errors as fatal.
ENSURE(SUCCEEDED(hr), hr);
assert(sample);
// output.pSample
*aOutSample = sample.Detach(); // AddRefs
return S_OK;
}
HRESULT
WMFH264Decoder::Input(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp,
Microseconds aDuration)
{
HRESULT hr;
CComPtr<IMFSample> input = nullptr;
hr = CreateInputSample(aData, aDataSize, aTimestamp, aDuration, &input);
ENSURE(SUCCEEDED(hr) && input!=nullptr, hr);
hr = mDecoder->ProcessInput(0, input, 0);
if (hr == MF_E_NOTACCEPTING) {
// MFT *already* has enough data to produce a sample. Retrieve it.
LOG("ProcessInput returned MF_E_NOTACCEPTING\n");
return MF_E_NOTACCEPTING;
}
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
HRESULT
WMFH264Decoder::Output(IMFSample** aOutput)
{
HRESULT hr;
CComPtr<IMFSample> outputSample;
hr = GetOutputSample(&outputSample);
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
return MF_E_TRANSFORM_NEED_MORE_INPUT;
}
// Treat other errors as fatal.
ENSURE(SUCCEEDED(hr) && outputSample, hr);
*aOutput = outputSample.Detach();
return S_OK;
}
HRESULT
WMFH264Decoder::Reset()
{
HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
HRESULT
WMFH264Decoder::Drain()
{
HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
ENSURE(SUCCEEDED(hr), hr);
return S_OK;
}
} // namespace wmf

View file

@ -0,0 +1,78 @@
/*
* Copyright 2013, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !defined(WMFH264Decoder_h_)
#define WMFH264Decoder_h_
#include "WMFUtils.h"
namespace wmf {
class WMFH264Decoder {
public:
WMFH264Decoder();
~WMFH264Decoder();
HRESULT Init(int32_t aCoreCount);
HRESULT Input(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp,
Microseconds aDuration);
HRESULT Output(IMFSample** aOutput);
HRESULT Reset();
int32_t GetFrameWidth() const;
int32_t GetFrameHeight() const;
const IntRect& GetPictureRegion() const;
int32_t GetStride() const;
HRESULT Drain();
private:
HRESULT SetDecoderInputType();
HRESULT SetDecoderOutputType();
HRESULT SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData);
HRESULT CreateInputSample(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp,
Microseconds aDuration,
IMFSample** aOutSample);
HRESULT CreateOutputSample(IMFSample** aOutSample);
HRESULT GetOutputSample(IMFSample** aOutSample);
HRESULT ConfigureVideoFrameGeometry(IMFMediaType* aMediaType);
MFT_INPUT_STREAM_INFO mInputStreamInfo;
MFT_OUTPUT_STREAM_INFO mOutputStreamInfo;
CComPtr<IMFTransform> mDecoder;
int32_t mVideoWidth;
int32_t mVideoHeight;
IntRect mPictureRegion;
int32_t mStride;
};
} // namespace wmf
#endif

View file

@ -0,0 +1,20 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MFPLAT_FUNC(MFCreateSample, "mfplat.dll");
MFPLAT_FUNC(MFCreateAlignedMemoryBuffer, "mfplat.dll");
MFPLAT_FUNC(MFGetStrideForBitmapInfoHeader, "evr.dll");
MFPLAT_FUNC(MFCreateMediaType, "mfplat.dll");

View file

@ -0,0 +1,276 @@
/*
* Copyright 2013, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "WMFUtils.h"
#include "ClearKeyUtils.h"
#include <versionhelpers.h>
#include <algorithm>
#include <stdio.h>
#define INITGUID
#include <guiddef.h>
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "wmcodecdspuuid")
void LOG(const char* format, ...)
{
#ifdef WMF_DECODER_LOG
va_list args;
va_start(args, format);
vprintf(format, args);
#endif
}
#ifdef WMF_MUST_DEFINE_AAC_MFT_CLSID
// Some SDK versions don't define the AAC decoder CLSID.
// {32D186A7-218F-4C75-8876-DD77273A8999}
DEFINE_GUID(CLSID_CMSAACDecMFT, 0x32D186A7, 0x218F, 0x4C75, 0x88, 0x76, 0xDD, 0x77, 0x27, 0x3A, 0x89, 0x99);
#endif
DEFINE_GUID(CLSID_CMSH264DecMFT, 0x62CE7E72, 0x4C71, 0x4d20, 0xB1, 0x5D, 0x45, 0x28, 0x31, 0xA8, 0x7D, 0x9D);
namespace wmf {
#define MFPLAT_FUNC(_func, _dllname) \
decltype(::_func)* _func;
#include "WMFSymbols.h"
#undef MFPLAT_FUNC
static bool
LinkMfplat()
{
static bool sInitDone = false;
static bool sInitOk = false;
if (!sInitDone) {
sInitDone = true;
HMODULE handle;
#define MFPLAT_FUNC(_func, _dllname) \
handle = GetModuleHandleA(_dllname); \
if (!(_func = (decltype(_func))(GetProcAddress(handle, #_func)))) { \
return false; \
}
#include "WMFSymbols.h"
#undef MFPLAT_FUNC
sInitOk = true;
}
return sInitOk;
}
const char*
WMFDecoderDllNameFor(CodecType aCodec)
{
if (aCodec == H264) {
// For H.264 decoding, we need msmpeg2vdec.dll on Win 7 & 8,
// and mfh264dec.dll on Vista.
if (IsWindows7OrGreater()) {
return "msmpeg2vdec.dll";
} else {
return "mfh264dec.dll";
}
} else if (aCodec == AAC) {
// For AAC decoding, we need to use msauddecmft.dll on Win8,
// msmpeg2adec.dll on Win7, and msheaacdec.dll on Vista.
if (IsWindows8OrGreater()) {
return "msauddecmft.dll";
} else if (IsWindows7OrGreater()) {
return "msmpeg2adec.dll";
} else {
return "mfheaacdec.dll";
}
} else {
return "";
}
}
bool
EnsureLibs()
{
static bool sInitDone = false;
static bool sInitOk = false;
if (!sInitDone) {
sInitOk = LinkMfplat() &&
!!GetModuleHandleA(WMFDecoderDllNameFor(AAC)) &&
!!GetModuleHandleA(WMFDecoderDllNameFor(H264));
sInitDone = true;
}
return sInitOk;
}
int32_t
MFOffsetToInt32(const MFOffset& aOffset)
{
return int32_t(aOffset.value + (aOffset.fract / 65536.0f));
}
// Gets the sub-region of the video frame that should be displayed.
// See: http://msdn.microsoft.com/en-us/library/windows/desktop/bb530115(v=vs.85).aspx
HRESULT
GetPictureRegion(IMFMediaType* aMediaType, IntRect& aOutPictureRegion)
{
// Determine if "pan and scan" is enabled for this media. If it is, we
// only display a region of the video frame, not the entire frame.
BOOL panScan = MFGetAttributeUINT32(aMediaType, MF_MT_PAN_SCAN_ENABLED, FALSE);
// If pan and scan mode is enabled. Try to get the display region.
HRESULT hr = E_FAIL;
MFVideoArea videoArea;
memset(&videoArea, 0, sizeof(MFVideoArea));
if (panScan) {
hr = aMediaType->GetBlob(MF_MT_PAN_SCAN_APERTURE,
(UINT8*)&videoArea,
sizeof(MFVideoArea),
NULL);
}
// If we're not in pan-and-scan mode, or the pan-and-scan region is not set,
// check for a minimimum display aperture.
if (!panScan || hr == MF_E_ATTRIBUTENOTFOUND) {
hr = aMediaType->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE,
(UINT8*)&videoArea,
sizeof(MFVideoArea),
NULL);
}
if (hr == MF_E_ATTRIBUTENOTFOUND) {
// Minimum display aperture is not set, for "backward compatibility with
// some components", check for a geometric aperture.
hr = aMediaType->GetBlob(MF_MT_GEOMETRIC_APERTURE,
(UINT8*)&videoArea,
sizeof(MFVideoArea),
NULL);
}
if (SUCCEEDED(hr)) {
// The media specified a picture region, return it.
IntRect picture = IntRect(MFOffsetToInt32(videoArea.OffsetX),
MFOffsetToInt32(videoArea.OffsetY),
videoArea.Area.cx,
videoArea.Area.cy);
ENSURE(picture.width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
ENSURE(picture.height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL);
aOutPictureRegion = picture;
return S_OK;
}
// No picture region defined, fall back to using the entire video area.
UINT32 width = 0, height = 0;
hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height);
ENSURE(SUCCEEDED(hr), hr);
ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL);
aOutPictureRegion = IntRect(0, 0, width, height);
return S_OK;
}
HRESULT
GetDefaultStride(IMFMediaType *aType, uint32_t* aOutStride)
{
// Try to get the default stride from the media type.
UINT32 stride = 0;
HRESULT hr = aType->GetUINT32(MF_MT_DEFAULT_STRIDE, &stride);
if (SUCCEEDED(hr)) {
ENSURE(stride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
*aOutStride = stride;
return S_OK;
}
// Stride attribute not set, calculate it.
GUID subtype = GUID_NULL;
uint32_t width = 0;
uint32_t height = 0;
hr = aType->GetGUID(MF_MT_SUBTYPE, &subtype);
ENSURE(SUCCEEDED(hr), hr);
hr = MFGetAttributeSize(aType, MF_MT_FRAME_SIZE, &width, &height);
ENSURE(SUCCEEDED(hr), hr);
ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL);
LONG lstride = 0;
hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, &lstride);
ENSURE(SUCCEEDED(hr), hr);
ENSURE(lstride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
ENSURE(lstride >= 0, E_FAIL);
*aOutStride = lstride;
return hr;
}
void dump(const uint8_t* data, uint32_t len, const char* filename)
{
FILE* f = 0;
fopen_s(&f, filename, "wb");
fwrite(data, len, 1, f);
fclose(f);
}
HRESULT
CreateMFT(const CLSID& clsid,
const char* aDllName,
CComPtr<IMFTransform>& aOutMFT)
{
HMODULE module = ::GetModuleHandleA(aDllName);
if (!module) {
LOG("Failed to get %S\n", aDllName);
return E_FAIL;
}
typedef HRESULT (WINAPI* DllGetClassObjectFnPtr)(const CLSID& clsid,
const IID& iid,
void** object);
DllGetClassObjectFnPtr GetClassObjPtr =
reinterpret_cast<DllGetClassObjectFnPtr>(GetProcAddress(module, "DllGetClassObject"));
if (!GetClassObjPtr) {
LOG("Failed to get DllGetClassObject\n");
return E_FAIL;
}
CComPtr<IClassFactory> classFactory;
HRESULT hr = GetClassObjPtr(clsid,
__uuidof(IClassFactory),
reinterpret_cast<void**>(static_cast<IClassFactory**>(&classFactory)));
if (FAILED(hr)) {
LOG("Failed to get H264 IClassFactory\n");
return E_FAIL;
}
hr = classFactory->CreateInstance(NULL,
__uuidof(IMFTransform),
reinterpret_cast<void**>(static_cast<IMFTransform**>(&aOutMFT)));
if (FAILED(hr)) {
LOG("Failed to get create MFT\n");
return E_FAIL;
}
return S_OK;
}
int32_t
GetNumThreads(int32_t aCoreCount)
{
return aCoreCount > 4 ? -1 : (std::max)(aCoreCount - 1, 1);
}
} // namespace

View file

@ -0,0 +1,269 @@
/*
* Copyright 2013, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __WMFUtils_h__
#define __WMFUtils_h__
#include <cstdint>
#include <string>
#include <assert.h>
#include <mfapi.h>
#include <mferror.h>
#include <mfobjects.h>
#include <mftransform.h>
#include <wmcodecdsp.h>
#include "VideoLimits.h"
#include "gmp-platform.h"
void LOG(const char* format, ...);
#ifdef LOG_SAMPLE_DECODE
#define SAMPLE_LOG LOG
#else
#define SAMPLE_LOG(...)
#endif
#ifndef CLSID_CMSAACDecMFT
#define WMF_MUST_DEFINE_AAC_MFT_CLSID
extern "C" const CLSID CLSID_CMSAACDecMFT;
#endif
extern "C" const CLSID CLSID_CMSH264DecMFT;
namespace wmf {
// Reimplementation of CComPtr to reduce dependence on system
// shared libraries.
template<class T>
class CComPtr {
public:
CComPtr(CComPtr&& aOther) : mPtr(aOther.Detach()) { }
CComPtr& operator=(CComPtr&& other) { mPtr = other.Detach(); }
CComPtr(const CComPtr& aOther) : mPtr(nullptr) { Set(aOther.Get()); }
CComPtr() : mPtr(nullptr) { }
CComPtr(T* const & aPtr) : mPtr(nullptr) { Set(aPtr); }
CComPtr(const std::nullptr_t& aNullPtr) : mPtr(aNullPtr) { }
T** operator&() { return &mPtr; }
T* operator->(){ return mPtr; }
operator T*() { return mPtr; }
T* operator=(T* const & aPtr) { return Set(aPtr); }
T* operator=(const std::nullptr_t& aPtr) { return mPtr = aPtr; }
T* Get() const { return mPtr; }
T* Detach() {
T* tmp = mPtr;
mPtr = nullptr;
return tmp;
}
~CComPtr() {
if (mPtr) {
mPtr->Release();
}
mPtr = nullptr;
}
private:
T* Set(T* aPtr) {
if (mPtr == aPtr) {
return aPtr;
}
if (mPtr) {
mPtr->Release();
}
mPtr = aPtr;
if (mPtr) {
mPtr->AddRef();
}
return mPtr;
}
T* mPtr;
};
class IntRect {
public:
IntRect(int32_t _x, int32_t _y, int32_t _w, int32_t _h)
: x(_x), y(_y), width(_w), height(_h) {}
IntRect()
: x(0), y(0), width(0), height(0) {}
int32_t x;
int32_t y;
int32_t width;
int32_t height;
};
typedef int64_t Microseconds;
#ifdef ENSURE
#undef ENSURE
#endif
#define ENSURE(condition, ret) \
{ if (!(condition)) { LOG("##condition## FAILED %S:%d\n", __FILE__, __LINE__); return ret; } }
#define GMP_SUCCEEDED(x) ((x) == GMPNoErr)
#define GMP_FAILED(x) ((x) != GMPNoErr)
#define MFPLAT_FUNC(_func, _dllname) \
extern decltype(::_func)* _func;
#include "WMFSymbols.h"
#undef MFPLAT_FUNC
bool
EnsureLibs();
HRESULT
GetPictureRegion(IMFMediaType* aMediaType, IntRect& aOutPictureRegion);
HRESULT
GetDefaultStride(IMFMediaType *aType, uint32_t* aOutStride);
// Converts from microseconds to hundreds of nanoseconds.
// We use microseconds for our timestamps, whereas WMF uses
// hundreds of nanoseconds.
inline int64_t
UsecsToHNs(int64_t aUsecs) {
return aUsecs * 10;
}
// Converts from hundreds of nanoseconds to microseconds.
// We use microseconds for our timestamps, whereas WMF uses
// hundreds of nanoseconds.
inline int64_t
HNsToUsecs(int64_t hNanoSecs) {
return hNanoSecs / 10;
}
inline std::string narrow(std::wstring &wide) {
std::string ns(wide.begin(), wide.end());
return ns;
}
inline std::wstring widen(std::string &narrow) {
std::wstring ws(narrow.begin(), narrow.end());
return ws;
}
#define ARRAY_LENGTH(array_) \
(sizeof(array_)/sizeof(array_[0]))
template<class Type>
class AutoPtr {
public:
AutoPtr()
: mPtr(nullptr)
{
}
AutoPtr(AutoPtr<Type>& aPtr)
: mPtr(aPtr.Forget())
{
}
AutoPtr(Type* aPtr)
: mPtr(aPtr)
{
}
~AutoPtr() {
if (mPtr) {
delete mPtr;
}
}
Type* Forget() {
Type* rv = mPtr;
mPtr = nullptr;
return rv;
}
AutoPtr<Type>& operator=(Type* aOther) {
Assign(aOther);
return *this;
}
AutoPtr<Type>& operator=(AutoPtr<Type>& aOther) {
Assign(aOther.Forget());
return *this;
}
Type* Get() const {
return mPtr;
}
Type* operator->() const {
assert(mPtr);
return Get();
}
operator Type*() const {
return Get();
}
Type** Receive() {
return &mPtr;
}
private:
void Assign(Type* aPtr) {
if (mPtr) {
delete mPtr;
}
mPtr = aPtr;
}
Type* mPtr;
};
// Video frame microseconds are (currently) in 90kHz units, as used by RTP.
// Use this to convert to microseconds...
inline Microseconds RTPTimeToMicroseconds(int64_t rtptime) {
return (rtptime * 1000000) / 90000;
}
inline uint32_t MicrosecondsToRTPTime(Microseconds us) {
return uint32_t(0xffffffff & (us * 90000) / 1000000);
}
void dump(const uint8_t* data, uint32_t len, const char* filename);
HRESULT
CreateMFT(const CLSID& clsid,
const char* aDllName,
CComPtr<IMFTransform>& aOutMFT);
enum CodecType {
H264,
AAC,
};
// Returns the name of the DLL that is needed to decode H.264 or AAC on
// the given windows version we're running on.
const char* WMFDecoderDllNameFor(CodecType aCodec);
// Returns the maximum number of threads we want WMF to use for decoding
// given the number of logical processors available.
int32_t GetNumThreads(int32_t aCoreCount);
} // namespace wmf
#endif // __WMFUtils_h__

View file

@ -0,0 +1,10 @@
Name: clearkey
Description: ClearKey Gecko Media Plugin
Version: 1
#ifdef ENABLE_WMF
APIs: eme-decrypt-v9[org.w3.clearkey], decode-audio[aac:org.w3.clearkey], decode-video[h264:org.w3.clearkey]
Libraries: dxva2.dll, d3d9.dll, msmpeg2vdec.dll, msmpeg2adec.dll, MSAudDecMFT.dll, evr.dll, mfheaacdec.dll, mfh264dec.dll, mfplat.dll
#else
APIs: eme-decrypt-v9[org.w3.clearkey]
Libraries:
#endif

View file

@ -0,0 +1,89 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "ClearKeyAsyncShutdown.h"
#include "ClearKeySessionManager.h"
#include "gmp-api/gmp-async-shutdown.h"
#include "gmp-api/gmp-decryption.h"
#include "gmp-api/gmp-platform.h"
#if defined(ENABLE_WMF)
#include "WMFUtils.h"
#include "AudioDecoder.h"
#include "VideoDecoder.h"
#endif
#if defined(WIN32)
#define GMP_EXPORT __declspec(dllexport)
#else
#define GMP_EXPORT __attribute__((visibility("default")))
#endif
static GMPPlatformAPI* sPlatform = nullptr;
GMPPlatformAPI*
GetPlatform()
{
return sPlatform;
}
extern "C" {
GMP_EXPORT GMPErr
GMPInit(GMPPlatformAPI* aPlatformAPI)
{
sPlatform = aPlatformAPI;
return GMPNoErr;
}
GMP_EXPORT GMPErr
GMPGetAPI(const char* aApiName, void* aHostAPI, void** aPluginAPI)
{
CK_LOGD("ClearKey GMPGetAPI |%s|", aApiName);
assert(!*aPluginAPI);
if (!strcmp(aApiName, GMP_API_DECRYPTOR)) {
*aPluginAPI = new ClearKeySessionManager();
}
#if defined(ENABLE_WMF)
else if (!strcmp(aApiName, GMP_API_AUDIO_DECODER) &&
wmf::EnsureLibs()) {
*aPluginAPI = new AudioDecoder(static_cast<GMPAudioHost*>(aHostAPI));
} else if (!strcmp(aApiName, GMP_API_VIDEO_DECODER) &&
wmf::EnsureLibs()) {
*aPluginAPI = new VideoDecoder(static_cast<GMPVideoHost*>(aHostAPI));
}
#endif
else if (!strcmp(aApiName, GMP_API_ASYNC_SHUTDOWN)) {
*aPluginAPI = new ClearKeyAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI));
} else {
CK_LOGE("GMPGetAPI couldn't resolve API name |%s|\n", aApiName);
}
return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
}
GMP_EXPORT GMPErr
GMPShutdown(void)
{
CK_LOGD("ClearKey GMPShutdown");
return GMPNoErr;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,47 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Original author: ekr@rtfm.com
#ifndef gmp_task_utils_h_
#define gmp_task_utils_h_
#include "gmp-api/gmp-platform.h"
class gmp_task_args_base : public GMPTask {
public:
virtual void Destroy() { delete this; }
virtual void Run() = 0;
};
// The generated file contains four major function templates
// (in variants for arbitrary numbers of arguments up to 10,
// which is why it is machine generated). The four templates
// are:
//
// WrapTask(o, m, ...) -- wraps a member function m of an object ptr o
// WrapTaskRet(o, m, ..., r) -- wraps a member function m of an object ptr o
// the function returns something that can
// be assigned to *r
// WrapTaskNM(f, ...) -- wraps a function f
// WrapTaskNMRet(f, ..., r) -- wraps a function f that returns something
// that can be assigned to *r
//
// All of these template functions return a GMPTask* which can be passed
// to DispatchXX().
#include "gmp-task-utils-generated.h"
#endif // gmp_task_utils_h_

View file

@ -0,0 +1,93 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "gtest/gtest.h"
#include <algorithm>
#include <stdint.h>
#include <vector>
#include "../ClearKeyBase64.cpp"
using namespace std;
struct B64Test {
string b64;
vector<uint8_t> raw;
bool shouldPass;
};
const B64Test tests[] = {
{
"AAAAADk4AU4AAAAAAAAAAA",
{ 0x0, 0x0, 0x0, 0x0, 0x39, 0x38, 0x1, 0x4e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
true
},
{
"h2mqp1zAJjDIC34YXEXBxA==",
{ 0x87, 0x69, 0xaa, 0xa7, 0x5c, 0xc0, 0x26, 0x30, 0xc8, 0xb, 0x7e, 0x18, 0x5c, 0x45, 0xc1, 0xc4 },
true
},
{
"flcdA35XHQN-Vx0DflcdAw",
{ 0x7e, 0x57, 0x1d, 0x3, 0x7e, 0x57, 0x1d, 0x3, 0x7e, 0x57, 0x1d, 0x3, 0x7e, 0x57, 0x1d, 0x3 },
true
},
{
"flczM35XMzN-VzMzflczMw",
{ 0x7e, 0x57, 0x33, 0x33, 0x7e, 0x57, 0x33, 0x33, 0x7e, 0x57, 0x33, 0x33, 0x7e, 0x57, 0x33, 0x33 },
true,
},
{
"flcdBH5XHQR-Vx0EflcdBA",
{ 0x7e, 0x57, 0x1d, 0x4, 0x7e, 0x57, 0x1d, 0x4, 0x7e, 0x57, 0x1d, 0x4, 0x7e, 0x57, 0x1d, 0x4 },
true
},
{
"fldERH5XRER-V0REfldERA",
{ 0x7e, 0x57, 0x44, 0x44, 0x7e, 0x57, 0x44, 0x44, 0x7e, 0x57, 0x44, 0x44, 0x7e, 0x57, 0x44, 0x44 },
true
},
{
"fuzzbiz=",
{ 0x7e, 0xec, 0xf3, 0x6e, 0x2c },
true
},
{
"fuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbiz",
{
0x7e, 0xec, 0xf3, 0x6e, 0x2c, 0xdf, 0xbb, 0x3c, 0xdb, 0x8b,
0x37, 0xee, 0xcf, 0x36, 0xe2, 0xcd, 0xfb, 0xb3, 0xcd, 0xb8,
0xb3, 0x7e, 0xec, 0xf3, 0x6e, 0x2c, 0xdf, 0xbb, 0x3c, 0xdb,
0x8b, 0x37, 0xee, 0xcf, 0x36, 0xe2, 0xcd, 0xfb, 0xb3, 0xcd,
0xb8, 0xb3
},
true
},
{ "", { }, true },
{ "00", { 0xd3 }, true },
{ "000", { 0xd3, 0x4d }, true },
{ "invalid", { 0x8a, 0x7b, 0xda, 0x96, 0x27 }, true },
{ "invalic", { 0x8a, 0x7b, 0xda, 0x96, 0x27 }, true },
// Failure tests
{ "A", { }, false }, // 1 character is too few.
{ "_", { }, false }, // 1 character is too few.
};
TEST(ClearKey, DecodeBase64) {
for (const B64Test& test : tests) {
vector<uint8_t> v;
bool rv = DecodeBase64(string(test.b64), v);
EXPECT_EQ(test.shouldPass, rv);
if (test.shouldPass) {
EXPECT_EQ(test.raw.size(), v.size());
for (size_t k = 0; k < test.raw.size(); k++) {
EXPECT_EQ(test.raw[k], v[k]);
}
}
}
}

View file

@ -0,0 +1,15 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
UNIFIED_SOURCES += [
'TestClearKeyUtils.cpp',
]
FINAL_LIBRARY = 'xul-gtest'
LOCAL_INCLUDES += [
'..',
]

View file

@ -0,0 +1,75 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
SharedLibrary('clearkey')
FINAL_TARGET = 'dist/bin/gmp-clearkey/0.1'
FINAL_TARGET_PP_FILES += ['clearkey.info.in']
UNIFIED_SOURCES += [
'ClearKeyAsyncShutdown.cpp',
'ClearKeyBase64.cpp',
'ClearKeyDecryptionManager.cpp',
'ClearKeyPersistence.cpp',
'ClearKeySession.cpp',
'ClearKeySessionManager.cpp',
'ClearKeyStorage.cpp',
'ClearKeyUtils.cpp',
'gmp-clearkey.cpp',
]
SOURCES += [
'openaes/oaes_lib.c',
]
if CONFIG['OS_ARCH'] == 'WINNT':
UNIFIED_SOURCES += [
'AnnexB.cpp',
'AudioDecoder.cpp',
'VideoDecoder.cpp',
'WMFAACDecoder.cpp',
'WMFH264Decoder.cpp',
]
SOURCES += [
'WMFUtils.cpp',
]
OS_LIBS += [
'mfuuid',
]
DEFINES['ENABLE_WMF'] = True
TEST_DIRS += [
'gtest',
]
LOCAL_INCLUDES += [
'/dom/media/gmp',
]
DISABLE_STL_WRAPPING = True
DEFINES['MOZ_NO_MOZALLOC'] = True
USE_LIBS += ['psshparser']
# Suppress warnings in third-party code.
if CONFIG['GNU_CXX']:
CFLAGS += [
'-Wno-missing-braces',
'-Wno-pointer-to-int-cast',
'-Wno-sign-compare',
'-include', 'stdio.h', # for sprintf() prototype
'-include', 'unistd.h', # for getpid() prototype
]
elif CONFIG['_MSC_VER']:
CFLAGS += [
'-FI', 'stdio.h', # for sprintf() prototype
'-wd4090', # '=' : different 'const' qualifiers
]

View file

@ -0,0 +1,27 @@
---------------------------------------------------------------------------
OpenAES Licence
---------------------------------------------------------------------------
Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------

View file

@ -0,0 +1,77 @@
/*
* ---------------------------------------------------------------------------
* OpenAES License
* ---------------------------------------------------------------------------
* Copyright (c) 2013, Nabil S. Al Ramli, www.nalramli.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* ---------------------------------------------------------------------------
*/
#ifndef _OAES_COMMON_H
#define _OAES_COMMON_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
# ifdef OAES_SHARED
# ifdef oaes_lib_EXPORTS
# define OAES_API __declspec(dllexport)
# else
# define OAES_API __declspec(dllimport)
# endif
# else
# define OAES_API
# endif
#else
# define OAES_API
#endif // WIN32
#define OAES_VERSION "0.9.0"
typedef enum
{
OAES_RET_FIRST = 0,
OAES_RET_SUCCESS = 0,
OAES_RET_ERROR,
OAES_RET_ARG1,
OAES_RET_ARG2,
OAES_RET_ARG3,
OAES_RET_ARG4,
OAES_RET_ARG5,
OAES_RET_NOKEY,
OAES_RET_MEM,
OAES_RET_BUF,
OAES_RET_HEADER,
OAES_RET_COUNT
} OAES_RET;
#ifdef __cplusplus
}
#endif
#endif // _OAES_COMMON_H

View file

@ -0,0 +1,46 @@
/*
* ---------------------------------------------------------------------------
* OpenAES License
* ---------------------------------------------------------------------------
* Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* ---------------------------------------------------------------------------
*/
#ifndef _OAES_CONFIG_H
#define _OAES_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif
#ifndef OAES_DEBUG
#define OAES_DEBUG 0
#endif // OAES_DEBUG
#ifdef __cplusplus
}
#endif
#endif // _OAES_CONFIG_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,168 @@
/*
* ---------------------------------------------------------------------------
* OpenAES License
* ---------------------------------------------------------------------------
* Copyright (c) 2013, Nabil S. Al Ramli, www.nalramli.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* ---------------------------------------------------------------------------
*/
#ifndef _OAES_LIB_H
#define _OAES_LIB_H
#include "oaes_common.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
# ifdef OAES_SHARED
# ifdef oaes_lib_EXPORTS
# define OAES_API __declspec(dllexport)
# else
# define OAES_API __declspec(dllimport)
# endif
# else
# define OAES_API
# endif
#else
# define OAES_API
#endif // WIN32
#define OAES_BLOCK_SIZE 16
typedef void OAES_CTX;
/*
* oaes_set_option() takes one of these values for its [option] parameter
* some options accept either an optional or a required [value] parameter
*/
// no option
#define OAES_OPTION_NONE 0
// enable ECB mode, disable CBC mode
#define OAES_OPTION_ECB 1
// enable CBC mode, disable ECB mode
// value is optional, may pass uint8_t iv[OAES_BLOCK_SIZE] to specify
// the value of the initialization vector, iv
#define OAES_OPTION_CBC 2
#ifdef OAES_DEBUG
typedef int ( * oaes_step_cb ) (
const uint8_t state[OAES_BLOCK_SIZE],
const char * step_name,
int step_count,
void * user_data );
// enable state stepping mode
// value is required, must pass oaes_step_cb to receive the state at each step
#define OAES_OPTION_STEP_ON 4
// disable state stepping mode
#define OAES_OPTION_STEP_OFF 8
#endif // OAES_DEBUG
typedef uint16_t OAES_OPTION;
/*
* // usage:
*
* OAES_CTX * ctx = oaes_alloc();
* .
* .
* .
* {
* oaes_gen_key_xxx( ctx );
* {
* oaes_key_export( ctx, _buf, &_buf_len );
* // or
* oaes_key_export_data( ctx, _buf, &_buf_len );\
* }
* }
* // or
* {
* oaes_key_import( ctx, _buf, _buf_len );
* // or
* oaes_key_import_data( ctx, _buf, _buf_len );
* }
* .
* .
* .
* oaes_encrypt( ctx, m, m_len, c, &c_len );
* .
* .
* .
* oaes_decrypt( ctx, c, c_len, m, &m_len );
* .
* .
* .
* oaes_free( &ctx );
*/
OAES_API OAES_CTX * oaes_alloc();
OAES_API OAES_RET oaes_free( OAES_CTX ** ctx );
OAES_API OAES_RET oaes_set_option( OAES_CTX * ctx,
OAES_OPTION option, const void * value );
OAES_API OAES_RET oaes_key_gen_128( OAES_CTX * ctx );
OAES_API OAES_RET oaes_key_gen_192( OAES_CTX * ctx );
OAES_API OAES_RET oaes_key_gen_256( OAES_CTX * ctx );
// export key with header information
// set data == NULL to get the required data_len
OAES_API OAES_RET oaes_key_export( OAES_CTX * ctx,
uint8_t * data, size_t * data_len );
// directly export the data from key
// set data == NULL to get the required data_len
OAES_API OAES_RET oaes_key_export_data( OAES_CTX * ctx,
uint8_t * data, size_t * data_len );
// import key with header information
OAES_API OAES_RET oaes_key_import( OAES_CTX * ctx,
const uint8_t * data, size_t data_len );
// directly import data into key
OAES_API OAES_RET oaes_key_import_data( OAES_CTX * ctx,
const uint8_t * data, size_t data_len );
// set c == NULL to get the required c_len
OAES_API OAES_RET oaes_encrypt( OAES_CTX * ctx,
const uint8_t * m, size_t m_len, uint8_t * c, size_t * c_len );
// set m == NULL to get the required m_len
OAES_API OAES_RET oaes_decrypt( OAES_CTX * ctx,
const uint8_t * c, size_t c_len, uint8_t * m, size_t * m_len );
// set buf == NULL to get the required buf_len
OAES_API OAES_RET oaes_sprintf(
char * buf, size_t * buf_len, const uint8_t * data, size_t data_len );
#ifdef __cplusplus
}
#endif
#endif // _OAES_LIB_H

View file

@ -0,0 +1,57 @@
/*
------------------------------------------------------------------------------
Standard definitions and types, Bob Jenkins
------------------------------------------------------------------------------
*/
#ifndef STANDARD
# define STANDARD
# ifndef STDIO
# include <stdio.h>
# define STDIO
# endif
# ifndef STDDEF
# include <stddef.h>
# define STDDEF
# endif
typedef unsigned long long ub8;
#define UB8MAXVAL 0xffffffffffffffffLL
#define UB8BITS 64
typedef signed long long sb8;
#define SB8MAXVAL 0x7fffffffffffffffLL
typedef unsigned long int ub4; /* unsigned 4-byte quantities */
#define UB4MAXVAL 0xffffffff
typedef signed long int sb4;
#define UB4BITS 32
#define SB4MAXVAL 0x7fffffff
typedef unsigned short int ub2;
#define UB2MAXVAL 0xffff
#define UB2BITS 16
typedef signed short int sb2;
#define SB2MAXVAL 0x7fff
typedef unsigned char ub1;
#define UB1MAXVAL 0xff
#define UB1BITS 8
typedef signed char sb1; /* signed 1-byte quantities */
#define SB1MAXVAL 0x7f
typedef int word; /* fastest type available */
#define bis(target,mask) ((target) |= (mask))
#define bic(target,mask) ((target) &= ~(mask))
#define bit(target,mask) ((target) & (mask))
// #ifndef min
// # define min(a,b) (((a)<(b)) ? (a) : (b))
// #endif /* min */
// #ifndef max
// # define max(a,b) (((a)<(b)) ? (b) : (a))
// #endif /* max */
#ifndef align
# define align(a) (((ub4)a+(sizeof(void *)-1))&(~(sizeof(void *)-1)))
#endif /* align */
// #ifndef abs
// # define abs(a) (((a)>0) ? (a) : -(a))
// #endif
#define TRUE 1
#define FALSE 0
#define SUCCESS 0 /* 1 on VAX */
#endif /* STANDARD */