mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 23:02:20 +00:00
Backed out 3 changesets (bug 1318965) for frequent media test failures a=backout
Backed out changeset 3f756d8ee4cf (bug 1318965) Backed out changeset 4bdf65d60c9e (bug 1318965) Backed out changeset c1e2b6c14a7f (bug 1318965) MozReview-Commit-ID: 6CPk5oS5AOw --HG-- extra : source : babe3f8a0258fb592e17a590450de6ceb09460c3
This commit is contained in:
parent
565fdbe5d8
commit
07fd8cebf7
@ -72,7 +72,6 @@ EXPORTS += [
|
||||
'GMPVideoHost.h',
|
||||
'GMPVideoi420FrameImpl.h',
|
||||
'GMPVideoPlaneImpl.h',
|
||||
'widevine-adapter/content_decryption_module.h',
|
||||
]
|
||||
|
||||
# We link GMPLoader into xul on Android and Linux as its code does not
|
||||
@ -120,7 +119,7 @@ UNIFIED_SOURCES += [
|
||||
'GMPVideoEncoderParent.cpp',
|
||||
'GMPVideoHost.cpp',
|
||||
'GMPVideoi420FrameImpl.cpp',
|
||||
'GMPVideoPlaneImpl.cpp'
|
||||
'GMPVideoPlaneImpl.cpp',
|
||||
]
|
||||
|
||||
DIRS += [
|
||||
|
@ -313,17 +313,6 @@ WidevineDecryptor::OnResolveNewSessionPromise(uint32_t aPromiseId,
|
||||
Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId);
|
||||
return;
|
||||
}
|
||||
|
||||
// This is laid out in the API. If we fail to load a session we should
|
||||
// call OnResolveNewSessionPromise with nullptr as the sessionId.
|
||||
// We can safely assume this means that we have failed to load a session
|
||||
// as the other methods specify calling 'OnRejectPromise' when they fail.
|
||||
if (!aSessionId) {
|
||||
Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d) Failed to load session", aPromiseId);
|
||||
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
|
||||
return;
|
||||
}
|
||||
|
||||
Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d)", aPromiseId);
|
||||
auto iter = mPromiseIdToNewSessionTokens.find(aPromiseId);
|
||||
if (iter == mPromiseIdToNewSessionTokens.end()) {
|
||||
|
79
media/gmp-clearkey/0.1/AnnexB.cpp
Normal file
79
media/gmp-clearkey/0.1/AnnexB.cpp
Normal 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);
|
||||
}
|
32
media/gmp-clearkey/0.1/AnnexB.h
Normal file
32
media/gmp-clearkey/0.1/AnnexB.h
Normal 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__
|
45
media/gmp-clearkey/0.1/ClearKeyAsyncShutdown.cpp
Normal file
45
media/gmp-clearkey/0.1/ClearKeyAsyncShutdown.cpp
Normal 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));
|
||||
}
|
37
media/gmp-clearkey/0.1/ClearKeyAsyncShutdown.h
Normal file
37
media/gmp-clearkey/0.1/ClearKeyAsyncShutdown.h
Normal 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__
|
@ -1,195 +0,0 @@
|
||||
#include "ClearKeyCDM.h"
|
||||
|
||||
#include "ClearKeyUtils.h"
|
||||
|
||||
using namespace cdm;
|
||||
|
||||
ClearKeyCDM::ClearKeyCDM(Host_8* aHost)
|
||||
{
|
||||
mHost = aHost;
|
||||
mSessionManager = new ClearKeySessionManager(mHost);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::Initialize(bool aAllowDistinctiveIdentifier,
|
||||
bool aAllowPersistentState)
|
||||
{
|
||||
mSessionManager->Init(aAllowDistinctiveIdentifier,
|
||||
aAllowPersistentState);
|
||||
|
||||
#ifdef ENABLE_WMF
|
||||
mVideoDecoder = new VideoDecoder(mHost);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::SetServerCertificate(uint32_t aPromiseId,
|
||||
const uint8_t* aServerCertificateData,
|
||||
uint32_t aServerCertificateDataSize)
|
||||
{
|
||||
mSessionManager->SetServerCertificate(aPromiseId,
|
||||
aServerCertificateData,
|
||||
aServerCertificateDataSize);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::CreateSessionAndGenerateRequest(uint32_t aPromiseId,
|
||||
SessionType aSessionType,
|
||||
InitDataType aInitDataType,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize)
|
||||
{
|
||||
mSessionManager->CreateSession(aPromiseId,
|
||||
aInitDataType,
|
||||
aInitData,
|
||||
aInitDataSize,
|
||||
aSessionType);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::LoadSession(uint32_t aPromiseId,
|
||||
SessionType aSessionType,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdSize)
|
||||
{
|
||||
mSessionManager->LoadSession(aPromiseId,
|
||||
aSessionId,
|
||||
aSessionIdSize);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::UpdateSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdSize,
|
||||
const uint8_t* aResponse,
|
||||
uint32_t aResponseSize)
|
||||
{
|
||||
mSessionManager->UpdateSession(aPromiseId,
|
||||
aSessionId,
|
||||
aSessionIdSize,
|
||||
aResponse,
|
||||
aResponseSize);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::CloseSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdSize)
|
||||
{
|
||||
mSessionManager->CloseSession(aPromiseId,
|
||||
aSessionId,
|
||||
aSessionIdSize);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::RemoveSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdSize)
|
||||
{
|
||||
mSessionManager->RemoveSession(aPromiseId,
|
||||
aSessionId,
|
||||
aSessionIdSize);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::TimerExpired(void* aContext)
|
||||
{
|
||||
// Clearkey is not interested in timers, so this method has not been
|
||||
// implemented.
|
||||
assert(false);
|
||||
}
|
||||
|
||||
Status
|
||||
ClearKeyCDM::Decrypt(const InputBuffer& aEncryptedBuffer,
|
||||
DecryptedBlock* aDecryptedBuffer)
|
||||
{
|
||||
return mSessionManager->Decrypt(aEncryptedBuffer, aDecryptedBuffer);
|
||||
}
|
||||
|
||||
Status
|
||||
ClearKeyCDM::InitializeAudioDecoder(
|
||||
const AudioDecoderConfig& aAudioDecoderConfig)
|
||||
{
|
||||
// Audio decoding is not supported by Clearkey because Widevine doesn't
|
||||
// support it and Clearkey's raison d'etre is to provide test coverage
|
||||
// for paths that Widevine will exercise in the wild.
|
||||
return Status::kDecodeError;
|
||||
}
|
||||
|
||||
Status
|
||||
ClearKeyCDM::InitializeVideoDecoder(
|
||||
const VideoDecoderConfig& aVideoDecoderConfig)
|
||||
{
|
||||
#ifdef ENABLE_WMF
|
||||
return mVideoDecoder->InitDecode(aVideoDecoderConfig);
|
||||
#else
|
||||
return Status::kDecodeError;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::DeinitializeDecoder(StreamType aDecoderType)
|
||||
{
|
||||
#ifdef ENABLE_WMF
|
||||
if (aDecoderType == StreamType::kStreamTypeVideo) {
|
||||
mVideoDecoder->Reset();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::ResetDecoder(StreamType aDecoderType)
|
||||
{
|
||||
#ifdef ENABLE_WMF
|
||||
if (aDecoderType == StreamType::kStreamTypeVideo) {
|
||||
mVideoDecoder->Reset();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Status
|
||||
ClearKeyCDM::DecryptAndDecodeFrame(const InputBuffer& aEncryptedBuffer,
|
||||
VideoFrame* aVideoFrame)
|
||||
{
|
||||
#ifdef ENABLE_WMF
|
||||
return mVideoDecoder->Decode(aEncryptedBuffer, aVideoFrame);
|
||||
#else
|
||||
return Status::kDecodeError;
|
||||
#endif
|
||||
}
|
||||
|
||||
Status
|
||||
ClearKeyCDM::DecryptAndDecodeSamples(const InputBuffer& aEncryptedBuffer,
|
||||
AudioFrames* aAudioFrame)
|
||||
{
|
||||
// Audio decoding is not supported by Clearkey because Widevine doesn't
|
||||
// support it and Clearkey's raison d'etre is to provide test coverage
|
||||
// for paths that Widevine will exercise in the wild.
|
||||
return Status::kDecodeError;
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::OnPlatformChallengeResponse(
|
||||
const PlatformChallengeResponse& aResponse)
|
||||
{
|
||||
// This function should never be called and is not supported.
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::OnQueryOutputProtectionStatus(QueryResult aResult,
|
||||
uint32_t aLinkMask,
|
||||
uint32_t aOutputProtectionMask)
|
||||
{
|
||||
// This function should never be called and is not supported.
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyCDM::Destroy()
|
||||
{
|
||||
mSessionManager->DecryptingComplete();
|
||||
#ifdef ENABLE_WMF
|
||||
mVideoDecoder->DecodingComplete();
|
||||
#endif
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
#ifndef ClearKeyCDM_h_
|
||||
#define ClearKeyCDM_h_
|
||||
|
||||
#include "ClearKeySessionManager.h"
|
||||
|
||||
// This include is required in order for content_decryption_module to work
|
||||
// on Unix systems.
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
|
||||
#ifdef ENABLE_WMF
|
||||
#include "WMFUtils.h"
|
||||
#include "VideoDecoder.h"
|
||||
#endif
|
||||
|
||||
class ClearKeyCDM : public cdm::ContentDecryptionModule_8
|
||||
{
|
||||
private:
|
||||
RefPtr<ClearKeySessionManager> mSessionManager;
|
||||
#ifdef ENABLE_WMF
|
||||
RefPtr<VideoDecoder> mVideoDecoder;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
cdm::Host_8* mHost;
|
||||
|
||||
public:
|
||||
explicit ClearKeyCDM(cdm::Host_8* mHost);
|
||||
|
||||
void Initialize(bool aAllowDistinctiveIdentifier,
|
||||
bool aAllowPersistentState) override;
|
||||
|
||||
void SetServerCertificate(uint32_t aPromiseId,
|
||||
const uint8_t* aServerCertificateData,
|
||||
uint32_t aServerCertificateDataSize)
|
||||
override;
|
||||
|
||||
void CreateSessionAndGenerateRequest(uint32_t aPromiseId,
|
||||
cdm::SessionType aSessionType,
|
||||
cdm::InitDataType aInitDataType,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize)
|
||||
override;
|
||||
|
||||
void LoadSession(uint32_t aPromiseId,
|
||||
cdm::SessionType aSessionType,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdSize) override;
|
||||
|
||||
void UpdateSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdSize,
|
||||
const uint8_t* aResponse,
|
||||
uint32_t aResponseSize) override;
|
||||
|
||||
void CloseSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdSize) override;
|
||||
|
||||
void RemoveSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdSize) override;
|
||||
|
||||
void TimerExpired(void* aContext) override;
|
||||
|
||||
cdm::Status Decrypt(const cdm::InputBuffer& aEncryptedBuffer,
|
||||
cdm::DecryptedBlock* aDecryptedBuffer) override;
|
||||
|
||||
cdm::Status InitializeAudioDecoder(
|
||||
const cdm::AudioDecoderConfig& aAudioDecoderConfig) override;
|
||||
|
||||
cdm::Status InitializeVideoDecoder(
|
||||
const cdm::VideoDecoderConfig& aVideoDecoderConfig) override;
|
||||
|
||||
void DeinitializeDecoder(cdm::StreamType aDecoderType) override;
|
||||
|
||||
void ResetDecoder(cdm::StreamType aDecoderType) override;
|
||||
|
||||
cdm::Status DecryptAndDecodeFrame(
|
||||
const cdm::InputBuffer& aEncryptedBuffer,
|
||||
cdm::VideoFrame* aVideoFrame) override;
|
||||
|
||||
cdm::Status DecryptAndDecodeSamples(
|
||||
const cdm::InputBuffer& aEncryptedBuffer,
|
||||
cdm::AudioFrames* aAudioFrame) override;
|
||||
|
||||
void OnPlatformChallengeResponse(
|
||||
const cdm::PlatformChallengeResponse& aResponse) override;
|
||||
|
||||
void
|
||||
OnQueryOutputProtectionStatus(cdm::QueryResult aResult,
|
||||
uint32_t aLinkMask,
|
||||
uint32_t aOutputProtectionMask) override;
|
||||
|
||||
void Destroy() override;
|
||||
};
|
||||
|
||||
#endif
|
@ -14,15 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ClearKeyDecryptionManager.h"
|
||||
|
||||
#include "psshparser/PsshParser.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace cdm;
|
||||
#include "ClearKeyDecryptionManager.h"
|
||||
#include "psshparser/PsshParser.h"
|
||||
#include "gmp-api/gmp-decryption.h"
|
||||
#include <assert.h>
|
||||
|
||||
class ClearKeyDecryptor : public RefCounted
|
||||
{
|
||||
@ -32,7 +30,7 @@ public:
|
||||
void InitKey(const Key& aKey);
|
||||
bool HasKey() const { return !!mKey.size(); }
|
||||
|
||||
Status Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
|
||||
GMPErr Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
|
||||
const CryptoMetaData& aMetadata);
|
||||
|
||||
const Key& DecryptionKey() const { return mKey; }
|
||||
@ -44,8 +42,7 @@ private:
|
||||
};
|
||||
|
||||
|
||||
/* static */ ClearKeyDecryptionManager*
|
||||
ClearKeyDecryptionManager::sInstance = nullptr;
|
||||
/* static */ ClearKeyDecryptionManager* ClearKeyDecryptionManager::sInstance = nullptr;
|
||||
|
||||
/* static */ ClearKeyDecryptionManager*
|
||||
ClearKeyDecryptionManager::Get()
|
||||
@ -76,17 +73,14 @@ ClearKeyDecryptionManager::~ClearKeyDecryptionManager()
|
||||
bool
|
||||
ClearKeyDecryptionManager::HasSeenKeyId(const KeyId& aKeyId) const
|
||||
{
|
||||
CK_LOGD("ClearKeyDecryptionManager::SeenKeyId %s",
|
||||
mDecryptors.find(aKeyId) != mDecryptors.end() ? "t" : "f");
|
||||
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_LOGARRAY("ClearKeyDecryptionManager::IsExpectingKeyForId ",
|
||||
aKeyId.data(),
|
||||
aKeyId.size());
|
||||
CK_LOGD("ClearKeyDecryptionManager::IsExpectingKeyForId %08x...", *(uint32_t*)&aKeyId[0]);
|
||||
const auto& decryptor = mDecryptors.find(aKeyId);
|
||||
return decryptor != mDecryptors.end() && !decryptor->second->HasKey();
|
||||
}
|
||||
@ -109,23 +103,16 @@ ClearKeyDecryptionManager::GetDecryptionKey(const KeyId& aKeyId)
|
||||
void
|
||||
ClearKeyDecryptionManager::InitKey(KeyId aKeyId, Key aKey)
|
||||
{
|
||||
CK_LOGD("ClearKeyDecryptionManager::InitKey ",
|
||||
aKeyId.data(),
|
||||
aKeyId.size());
|
||||
CK_LOGD("ClearKeyDecryptionManager::InitKey %08x...", *(uint32_t*)&aKeyId[0]);
|
||||
if (IsExpectingKeyForKeyId(aKeyId)) {
|
||||
CK_LOGARRAY("Initialized Key ", aKeyId.data(), aKeyId.size());
|
||||
mDecryptors[aKeyId]->InitKey(aKey);
|
||||
} else {
|
||||
CK_LOGARRAY("Failed to initialize key ", aKeyId.data(), aKeyId.size());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyDecryptionManager::ExpectKeyId(KeyId aKeyId)
|
||||
{
|
||||
CK_LOGD("ClearKeyDecryptionManager::ExpectKeyId ",
|
||||
aKeyId.data(),
|
||||
aKeyId.size());
|
||||
CK_LOGD("ClearKeyDecryptionManager::ExpectKeyId %08x...", *(uint32_t*)&aKeyId[0]);
|
||||
if (!HasSeenKeyId(aKeyId)) {
|
||||
mDecryptors[aKeyId] = new ClearKeyDecryptor();
|
||||
}
|
||||
@ -144,31 +131,23 @@ ClearKeyDecryptionManager::ReleaseKeyId(KeyId aKeyId)
|
||||
}
|
||||
}
|
||||
|
||||
Status
|
||||
GMPErr
|
||||
ClearKeyDecryptionManager::Decrypt(std::vector<uint8_t>& aBuffer,
|
||||
const CryptoMetaData& aMetadata)
|
||||
{
|
||||
return Decrypt(&aBuffer[0], aBuffer.size(), aMetadata);
|
||||
}
|
||||
|
||||
Status
|
||||
GMPErr
|
||||
ClearKeyDecryptionManager::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
|
||||
const CryptoMetaData& aMetadata)
|
||||
{
|
||||
CK_LOGD("ClearKeyDecryptionManager::Decrypt");
|
||||
if (!HasKeyForKeyId(aMetadata.mKeyId)) {
|
||||
CK_LOGARRAY("Unable to find decryptor for keyId: ",
|
||||
aMetadata.mKeyId.data(),
|
||||
aMetadata.mKeyId.size());
|
||||
return Status::kNoKey;
|
||||
return GMPNoKeyErr;
|
||||
}
|
||||
|
||||
CK_LOGARRAY("Found decryptor for keyId: ",
|
||||
aMetadata.mKeyId.data(),
|
||||
aMetadata.mKeyId.size());
|
||||
return mDecryptors[aMetadata.mKeyId]->Decrypt(aBuffer,
|
||||
aBufferSize,
|
||||
aMetadata);
|
||||
return mDecryptors[aMetadata.mKeyId]->Decrypt(aBuffer, aBufferSize, aMetadata);
|
||||
}
|
||||
|
||||
ClearKeyDecryptor::ClearKeyDecryptor()
|
||||
@ -179,9 +158,7 @@ ClearKeyDecryptor::ClearKeyDecryptor()
|
||||
ClearKeyDecryptor::~ClearKeyDecryptor()
|
||||
{
|
||||
if (HasKey()) {
|
||||
CK_LOGARRAY("ClearKeyDecryptor dtor; key = ",
|
||||
mKey.data(),
|
||||
mKey.size());
|
||||
CK_LOGD("ClearKeyDecryptor dtor; key = %08x...", *(uint32_t*)&mKey[0]);
|
||||
} else {
|
||||
CK_LOGD("ClearKeyDecryptor dtor");
|
||||
}
|
||||
@ -193,7 +170,7 @@ ClearKeyDecryptor::InitKey(const Key& aKey)
|
||||
mKey = aKey;
|
||||
}
|
||||
|
||||
Status
|
||||
GMPErr
|
||||
ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
|
||||
const CryptoMetaData& aMetadata)
|
||||
{
|
||||
@ -212,7 +189,7 @@ ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
|
||||
uint32_t cipherBytes = aMetadata.mCipherBytes[i];
|
||||
if (data + cipherBytes > aBuffer + aBufferSize) {
|
||||
// Trying to read past the end of the buffer!
|
||||
return Status::kDecryptError;
|
||||
return GMPCryptoErr;
|
||||
}
|
||||
|
||||
memcpy(iter, data, cipherBytes);
|
||||
@ -250,5 +227,5 @@ ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
|
||||
memcpy(aBuffer, &tmp[0], aBufferSize);
|
||||
}
|
||||
|
||||
return Status::kSuccess;
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
@ -17,43 +17,32 @@
|
||||
#ifndef __ClearKeyDecryptionManager_h__
|
||||
#define __ClearKeyDecryptionManager_h__
|
||||
|
||||
#include "ClearKeyUtils.h"
|
||||
// This include is required in order for content_decryption_module to work
|
||||
// on Unix systems.
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "RefCounted.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "ClearKeyUtils.h"
|
||||
#include "RefCounted.h"
|
||||
|
||||
class ClearKeyDecryptor;
|
||||
|
||||
class CryptoMetaData
|
||||
{
|
||||
class CryptoMetaData {
|
||||
public:
|
||||
CryptoMetaData() {}
|
||||
|
||||
explicit CryptoMetaData(const cdm::InputBuffer* aInputBuffer)
|
||||
explicit CryptoMetaData(const GMPEncryptedBufferMetadata* aCrypto)
|
||||
{
|
||||
Init(aInputBuffer);
|
||||
Init(aCrypto);
|
||||
}
|
||||
|
||||
void Init(const cdm::InputBuffer* aInputBuffer)
|
||||
void Init(const GMPEncryptedBufferMetadata* aCrypto)
|
||||
{
|
||||
if (!aInputBuffer) {
|
||||
if (!aCrypto) {
|
||||
assert(!IsValid());
|
||||
return;
|
||||
}
|
||||
|
||||
Assign(mKeyId, aInputBuffer->key_id, aInputBuffer->key_id_size);
|
||||
Assign(mIV, aInputBuffer->iv, aInputBuffer->iv_size);
|
||||
|
||||
for (uint32_t i = 0; i < aInputBuffer->num_subsamples; ++i) {
|
||||
const cdm::SubsampleEntry& subsample = aInputBuffer->subsamples[i];
|
||||
|
||||
mCipherBytes.push_back(subsample.cipher_bytes);
|
||||
mClearBytes.push_back(subsample.clear_bytes);
|
||||
}
|
||||
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 {
|
||||
@ -70,7 +59,7 @@ public:
|
||||
|
||||
std::vector<uint8_t> mKeyId;
|
||||
std::vector<uint8_t> mIV;
|
||||
std::vector<uint32_t> mClearBytes;
|
||||
std::vector<uint16_t> mClearBytes;
|
||||
std::vector<uint32_t> mCipherBytes;
|
||||
};
|
||||
|
||||
@ -96,10 +85,12 @@ public:
|
||||
void ReleaseKeyId(KeyId aKeyId);
|
||||
|
||||
// Decrypts buffer *in place*.
|
||||
cdm::Status Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
|
||||
const CryptoMetaData& aMetadata);
|
||||
cdm::Status Decrypt(std::vector<uint8_t>& aBuffer,
|
||||
const CryptoMetaData& aMetadata);
|
||||
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;
|
||||
|
@ -15,123 +15,81 @@
|
||||
*/
|
||||
|
||||
#include "ClearKeyPersistence.h"
|
||||
|
||||
#include "ClearKeyUtils.h"
|
||||
#include "ClearKeyStorage.h"
|
||||
#include "ClearKeySessionManager.h"
|
||||
#include "RefCounted.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <sstream>
|
||||
#include <string.h>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <assert.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace cdm;
|
||||
|
||||
void
|
||||
ClearKeyPersistence::ReadAllRecordsFromIndex(function<void()>&& aOnComplete) {
|
||||
// Clear what we think the index file contains, we're about to read it again.
|
||||
mPersistentSessionIds.clear();
|
||||
// Whether we've loaded the persistent session ids from GMPStorage yet.
|
||||
enum PersistentKeyState {
|
||||
UNINITIALIZED,
|
||||
LOADING,
|
||||
LOADED
|
||||
};
|
||||
static PersistentKeyState sPersistentKeyState = UNINITIALIZED;
|
||||
|
||||
// Hold a reference to the persistence manager, so it isn't released before
|
||||
// we try and use it.
|
||||
RefPtr<ClearKeyPersistence> self(this);
|
||||
function<void(const uint8_t*, uint32_t)> onIndexSuccess =
|
||||
[self, aOnComplete] (const uint8_t* data, uint32_t size)
|
||||
{
|
||||
CK_LOGD("ClearKeyPersistence: Loaded index file!");
|
||||
const char* charData = (const char*)data;
|
||||
// Set of session Ids of the persistent sessions created or residing in
|
||||
// storage.
|
||||
static set<uint32_t> sPersistentSessionIds;
|
||||
|
||||
stringstream ss(string(charData, charData + size));
|
||||
string name;
|
||||
while (getline(ss, name)) {
|
||||
if (ClearKeyUtils::IsValidSessionId(name.data(), name.size())) {
|
||||
self->mPersistentSessionIds.insert(atoi(name.c_str()));
|
||||
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();
|
||||
}
|
||||
|
||||
self->mPersistentKeyState = PersistentKeyState::LOADED;
|
||||
aOnComplete();
|
||||
};
|
||||
|
||||
function<void()> onIndexFailed =
|
||||
[self, aOnComplete] ()
|
||||
{
|
||||
CK_LOGD("ClearKeyPersistence: Failed to load index file (it might not exist");
|
||||
self->mPersistentKeyState = PersistentKeyState::LOADED;
|
||||
aOnComplete();
|
||||
};
|
||||
|
||||
string filename = "index";
|
||||
ReadData(mHost, filename, move(onIndexSuccess), move(onIndexFailed));
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyPersistence::WriteIndex() {
|
||||
function <void()> onIndexSuccess =
|
||||
[] ()
|
||||
{
|
||||
CK_LOGD("ClearKeyPersistence: Wrote index file");
|
||||
};
|
||||
|
||||
function <void()> onIndexFail =
|
||||
[] ()
|
||||
{
|
||||
CK_LOGD("ClearKeyPersistence: Failed to write index file (this is bad)");
|
||||
};
|
||||
|
||||
stringstream ss;
|
||||
|
||||
for (const uint32_t& sessionId : mPersistentSessionIds) {
|
||||
ss << sessionId;
|
||||
ss << '\n';
|
||||
}
|
||||
sPersistentKeyState = LOADED;
|
||||
aRecordIterator->Close();
|
||||
|
||||
string dataString = ss.str();
|
||||
uint8_t* dataArray = (uint8_t*)dataString.data();
|
||||
vector<uint8_t> data(dataArray, dataArray + dataString.size());
|
||||
|
||||
string filename = "index";
|
||||
WriteData(mHost,
|
||||
filename,
|
||||
data,
|
||||
move(onIndexSuccess),
|
||||
move(onIndexFail));
|
||||
for (size_t i = 0; i < sTasksBlockedOnSessionIdLoad.size(); i++) {
|
||||
sTasksBlockedOnSessionIdLoad[i]->Run();
|
||||
sTasksBlockedOnSessionIdLoad[i]->Destroy();
|
||||
}
|
||||
sTasksBlockedOnSessionIdLoad.clear();
|
||||
}
|
||||
|
||||
|
||||
ClearKeyPersistence::ClearKeyPersistence(Host_8* aHost)
|
||||
/* static */ void
|
||||
ClearKeyPersistence::EnsureInitialized()
|
||||
{
|
||||
this->mHost = aHost;
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyPersistence::EnsureInitialized(bool aPersistentStateAllowed,
|
||||
function<void()>&& aOnInitialized)
|
||||
{
|
||||
if (aPersistentStateAllowed &&
|
||||
mPersistentKeyState == PersistentKeyState::UNINITIALIZED) {
|
||||
mPersistentKeyState = LOADING;
|
||||
ReadAllRecordsFromIndex(move(aOnInitialized));
|
||||
} else {
|
||||
mPersistentKeyState = PersistentKeyState::LOADED;
|
||||
aOnInitialized();
|
||||
if (sPersistentKeyState == UNINITIALIZED) {
|
||||
sPersistentKeyState = LOADING;
|
||||
if (GMP_FAILED(EnumRecordNames(&ReadAllRecordsFromIterator))) {
|
||||
sPersistentKeyState = LOADED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ClearKeyPersistence::IsLoaded() const
|
||||
{
|
||||
return mPersistentKeyState == PersistentKeyState::LOADED;
|
||||
}
|
||||
|
||||
string
|
||||
ClearKeyPersistence::GetNewSessionId(SessionType aSessionType)
|
||||
/* 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(mPersistentSessionIds, sNextSessionId)) {
|
||||
while (Contains(sPersistentSessionIds, sNextSessionId)) {
|
||||
sNextSessionId++;
|
||||
}
|
||||
|
||||
@ -140,11 +98,8 @@ ClearKeyPersistence::GetNewSessionId(SessionType aSessionType)
|
||||
ss << sNextSessionId;
|
||||
ss >> sessionId;
|
||||
|
||||
if (aSessionType == SessionType::kPersistentLicense) {
|
||||
mPersistentSessionIds.insert(sNextSessionId);
|
||||
|
||||
// Save the updated index file.
|
||||
WriteIndex();
|
||||
if (aSessionType == kGMPPersistentSession) {
|
||||
sPersistentSessionIds.insert(sNextSessionId);
|
||||
}
|
||||
|
||||
sNextSessionId++;
|
||||
@ -152,17 +107,154 @@ ClearKeyPersistence::GetNewSessionId(SessionType aSessionType)
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
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(mPersistentSessionIds, atoi(aSessionId.c_str()));
|
||||
return Contains(sPersistentSessionIds, atoi(aSessionId.c_str()));
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyPersistence::PersistentSessionRemoved(string& aSessionId)
|
||||
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)
|
||||
{
|
||||
mPersistentSessionIds.erase(atoi(aSessionId.c_str()));
|
||||
|
||||
// Update the index file.
|
||||
WriteIndex();
|
||||
LoadSessionFromKeysTask* loadTask =
|
||||
new LoadSessionFromKeysTask(aInstance, aSid, aPromiseId);
|
||||
ReadData(aSid, loadTask);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
ClearKeyPersistence::PersistentSessionRemoved(const string& aSessionId)
|
||||
{
|
||||
sPersistentSessionIds.erase(atoi(aSessionId.c_str()));
|
||||
}
|
||||
|
@ -17,51 +17,37 @@
|
||||
#ifndef __ClearKeyPersistence_h__
|
||||
#define __ClearKeyPersistence_h__
|
||||
|
||||
// This include is required in order for content_decryption_module to work
|
||||
// on Unix systems.
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "RefCounted.h"
|
||||
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gmp-api/gmp-decryption.h"
|
||||
|
||||
class ClearKeySessionManager;
|
||||
|
||||
// Whether we've loaded the persistent session ids yet.
|
||||
enum PersistentKeyState {
|
||||
UNINITIALIZED,
|
||||
LOADING,
|
||||
LOADED
|
||||
};
|
||||
|
||||
class ClearKeyPersistence : public RefCounted
|
||||
{
|
||||
class ClearKeyPersistence {
|
||||
public:
|
||||
explicit ClearKeyPersistence(cdm::Host_8* aHost);
|
||||
static void EnsureInitialized();
|
||||
|
||||
void EnsureInitialized(bool aPersistentStateAllowed,
|
||||
std::function<void()>&& aOnInitialized);
|
||||
static std::string GetNewSessionId(GMPSessionType aSessionType);
|
||||
|
||||
bool IsLoaded() const;
|
||||
static bool DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
|
||||
uint32_t aCreateSessionToken,
|
||||
uint32_t aPromiseId,
|
||||
const std::string& aInitDataType,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize,
|
||||
GMPSessionType aSessionType);
|
||||
|
||||
std::string GetNewSessionId(cdm::SessionType aSessionType);
|
||||
static bool DeferLoadSessionIfNotReady(ClearKeySessionManager* aInstance,
|
||||
uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength);
|
||||
|
||||
bool IsPersistentSessionId(const std::string& aSid);
|
||||
static bool IsPersistentSessionId(const std::string& aSid);
|
||||
|
||||
void PersistentSessionRemoved(std::string& aSid);
|
||||
private:
|
||||
cdm::Host_8* mHost = nullptr;
|
||||
static void LoadSessionData(ClearKeySessionManager* aInstance,
|
||||
const std::string& aSid,
|
||||
uint32_t aPromiseId);
|
||||
|
||||
PersistentKeyState mPersistentKeyState = PersistentKeyState::UNINITIALIZED;
|
||||
|
||||
std::set<uint32_t> mPersistentSessionIds;
|
||||
|
||||
void ReadAllRecordsFromIndex(std::function<void()>&& aOnComplete);
|
||||
void WriteIndex();
|
||||
static void PersistentSessionRemoved(const std::string& aSid);
|
||||
};
|
||||
|
||||
#endif // __ClearKeyPersistence_h__
|
||||
|
@ -20,17 +20,18 @@
|
||||
#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;
|
||||
using namespace cdm;
|
||||
using namespace std;
|
||||
|
||||
ClearKeySession::ClearKeySession(const std::string& aSessionId,
|
||||
SessionType aSessionType)
|
||||
GMPDecryptorCallback* aCallback,
|
||||
GMPSessionType aSessionType)
|
||||
: mSessionId(aSessionId)
|
||||
, mCallback(aCallback)
|
||||
, mSessionType(aSessionType)
|
||||
{
|
||||
CK_LOGD("ClearKeySession ctor %p", this);
|
||||
@ -39,21 +40,30 @@ ClearKeySession::ClearKeySession(const std::string& aSessionId,
|
||||
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());
|
||||
}
|
||||
|
||||
bool
|
||||
ClearKeySession::Init(InitDataType aInitDataType,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize)
|
||||
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 == InitDataType::kCenc) {
|
||||
if (aInitDataType == "cenc") {
|
||||
ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
|
||||
} else if (aInitDataType == InitDataType::kKeyIds) {
|
||||
} else if (aInitDataType == "keyids") {
|
||||
ClearKeyUtils::ParseKeyIdsInitData(aInitData, aInitDataSize, mKeyIds);
|
||||
} else if (aInitDataType == InitDataType::kWebM &&
|
||||
aInitDataSize <= kMaxWebmInitDataSize) {
|
||||
} 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);
|
||||
@ -61,13 +71,17 @@ ClearKeySession::Init(InitDataType aInitDataType,
|
||||
}
|
||||
|
||||
if (!mKeyIds.size()) {
|
||||
return false;
|
||||
const char message[] = "Couldn't parse init data";
|
||||
mCallback->RejectPromise(aPromiseId, kGMPTypeError, message, strlen(message));
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
mCallback->SetSessionId(aCreateSessionToken, &mSessionId[0], mSessionId.length());
|
||||
|
||||
mCallback->ResolvePromise(aPromiseId);
|
||||
}
|
||||
|
||||
SessionType
|
||||
GMPSessionType
|
||||
ClearKeySession::Type() const
|
||||
{
|
||||
return mSessionType;
|
||||
|
@ -18,28 +18,30 @@
|
||||
#define __ClearKeySession_h__
|
||||
|
||||
#include "ClearKeyUtils.h"
|
||||
// This include is required in order for content_decryption_module to work
|
||||
// on Unix systems.
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "gmp-api/gmp-decryption.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
class GMPBuffer;
|
||||
class GMPDecryptorCallback;
|
||||
class GMPDecryptorHost;
|
||||
class GMPEncryptedBufferMetadata;
|
||||
|
||||
class ClearKeySession
|
||||
{
|
||||
public:
|
||||
explicit ClearKeySession(const std::string& aSessionId,
|
||||
cdm::SessionType aSessionType);
|
||||
GMPDecryptorCallback* aCallback,
|
||||
GMPSessionType aSessionType);
|
||||
|
||||
~ClearKeySession();
|
||||
|
||||
const std::vector<KeyId>& GetKeyIds() const { return mKeyIds; }
|
||||
|
||||
bool Init(cdm::InitDataType aInitDataType,
|
||||
void Init(uint32_t aCreateSessionToken,
|
||||
uint32_t aPromiseId,
|
||||
const string& aInitDataType,
|
||||
const uint8_t* aInitData, uint32_t aInitDataSize);
|
||||
|
||||
cdm::SessionType Type() const;
|
||||
GMPSessionType Type() const;
|
||||
|
||||
void AddKeyId(const KeyId& aKeyId);
|
||||
|
||||
@ -49,7 +51,8 @@ private:
|
||||
const std::string mSessionId;
|
||||
std::vector<KeyId> mKeyIds;
|
||||
|
||||
const cdm::SessionType mSessionType;
|
||||
GMPDecryptorCallback* mCallback;
|
||||
const GMPSessionType mSessionType;
|
||||
};
|
||||
|
||||
#endif // __ClearKeySession_h__
|
||||
|
@ -14,33 +14,30 @@
|
||||
* 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"
|
||||
// This include is required in order for content_decryption_module to work
|
||||
// on Unix systems.
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "psshparser/PsshParser.h"
|
||||
|
||||
#include "gmp-task-utils.h"
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace cdm;
|
||||
|
||||
ClearKeySessionManager::ClearKeySessionManager(Host_8* aHost)
|
||||
ClearKeySessionManager::ClearKeySessionManager()
|
||||
: mDecryptionManager(ClearKeyDecryptionManager::Get())
|
||||
{
|
||||
CK_LOGD("ClearKeySessionManager ctor %p", this);
|
||||
AddRef();
|
||||
|
||||
mHost = aHost;
|
||||
mPersistence = new ClearKeyPersistence(mHost);
|
||||
if (GetPlatform()->createthread(&mThread) != GMPNoErr) {
|
||||
CK_LOGD("failed to create thread in clearkey cdm");
|
||||
mThread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ClearKeySessionManager::~ClearKeySessionManager()
|
||||
@ -49,106 +46,56 @@ ClearKeySessionManager::~ClearKeySessionManager()
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeySessionManager::Init(bool aDistinctiveIdentifierAllowed,
|
||||
ClearKeySessionManager::Init(GMPDecryptorCallback* aCallback,
|
||||
bool aDistinctiveIdentifierAllowed,
|
||||
bool aPersistentStateAllowed)
|
||||
{
|
||||
CK_LOGD("ClearKeySessionManager::Init");
|
||||
|
||||
RefPtr<ClearKeySessionManager> self(this);
|
||||
function<void()> onPersistentStateLoaded =
|
||||
[self] ()
|
||||
{
|
||||
while (!self->mDeferredInitialize.empty()) {
|
||||
function<void()> func = self->mDeferredInitialize.front();
|
||||
self->mDeferredInitialize.pop();
|
||||
|
||||
func();
|
||||
}
|
||||
};
|
||||
|
||||
mPersistence->EnsureInitialized(aPersistentStateAllowed,
|
||||
move(onPersistentStateLoaded));
|
||||
mCallback = aCallback;
|
||||
ClearKeyPersistence::EnsureInitialized();
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
|
||||
InitDataType aInitDataType,
|
||||
ClearKeySessionManager::CreateSession(uint32_t aCreateSessionToken,
|
||||
uint32_t aPromiseId,
|
||||
const char* aInitDataType,
|
||||
uint32_t aInitDataTypeSize,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize,
|
||||
SessionType aSessionType)
|
||||
GMPSessionType aSessionType)
|
||||
{
|
||||
// Copy the init data so it is correctly captured by the lambda
|
||||
vector<uint8_t> initData(aInitData, aInitData + aInitDataSize);
|
||||
|
||||
RefPtr<ClearKeySessionManager> self(this);
|
||||
function<void()> deferrer =
|
||||
[self, aPromiseId, aInitDataType, initData, aSessionType] ()
|
||||
{
|
||||
self->CreateSession(aPromiseId,
|
||||
aInitDataType,
|
||||
initData.data(),
|
||||
initData.size(),
|
||||
aSessionType);
|
||||
};
|
||||
|
||||
// If we haven't loaded, don't do this yet
|
||||
if (MaybeDeferTillInitialized(deferrer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CK_LOGD("ClearKeySessionManager::CreateSession type:%s", aInitDataType);
|
||||
|
||||
CK_LOGARRAY("ClearKeySessionManager::CreateSession initdata: ",
|
||||
aInitData,
|
||||
aInitDataSize);
|
||||
|
||||
// If 'DecryptingComplete' has been called mHost will be null so we can't
|
||||
// won't be able to resolve our promise
|
||||
if (!mHost) {
|
||||
CK_LOGD("ClearKeySessionManager::CreateSession: mHost is nullptr")
|
||||
return;
|
||||
}
|
||||
|
||||
string initDataType(aInitDataType, aInitDataType + aInitDataTypeSize);
|
||||
// initDataType must be "cenc", "keyids", or "webm".
|
||||
if (aInitDataType != InitDataType::kCenc &&
|
||||
aInitDataType != InitDataType::kKeyIds &&
|
||||
aInitDataType != InitDataType::kWebM) {
|
||||
|
||||
string message = "initDataType is not supported by ClearKey";
|
||||
mHost->OnRejectPromise(aPromiseId,
|
||||
Error::kNotSupportedError,
|
||||
0,
|
||||
message.c_str(),
|
||||
message.size());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
string sessionId = mPersistence->GetNewSessionId(aSessionType);
|
||||
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,
|
||||
aSessionType);
|
||||
|
||||
if (!session->Init(aInitDataType, aInitData, aInitDataSize)) {
|
||||
|
||||
CK_LOGD("Failed to initialize session: %s", sessionId.c_str());
|
||||
|
||||
const static char* message = "Failed to initialize session";
|
||||
mHost->OnRejectPromise(aPromiseId,
|
||||
Error::kUnknownError,
|
||||
0,
|
||||
message,
|
||||
strlen(message));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
@ -166,19 +113,9 @@ ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
|
||||
// Send a request for needed key data.
|
||||
string request;
|
||||
ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType);
|
||||
|
||||
// Resolve the promise with the new session information.
|
||||
mHost->OnResolveNewSessionPromise(aPromiseId,
|
||||
sessionId.c_str(),
|
||||
sessionId.size());
|
||||
|
||||
mHost->OnSessionMessage(sessionId.c_str(),
|
||||
sessionId.size(),
|
||||
MessageType::kLicenseRequest,
|
||||
request.c_str(),
|
||||
request.size(),
|
||||
nullptr,
|
||||
0);
|
||||
mCallback->SessionMessage(&sessionId[0], sessionId.length(),
|
||||
kGMPLicenseRequest,
|
||||
(uint8_t*)&request[0], request.length());
|
||||
}
|
||||
|
||||
void
|
||||
@ -186,90 +123,53 @@ ClearKeySessionManager::LoadSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength)
|
||||
{
|
||||
// Copy the sessionId into a string so the lambda captures it properly.
|
||||
string sessionId(aSessionId, aSessionId + aSessionIdLength);
|
||||
|
||||
// Hold a reference to the SessionManager so that it isn't released before
|
||||
// we try to use it.
|
||||
RefPtr<ClearKeySessionManager> self(this);
|
||||
function<void()> deferrer =
|
||||
[self, aPromiseId, sessionId] ()
|
||||
{
|
||||
self->LoadSession(aPromiseId, sessionId.data(), sessionId.size());
|
||||
};
|
||||
|
||||
if (MaybeDeferTillInitialized(deferrer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CK_LOGD("ClearKeySessionManager::LoadSession");
|
||||
|
||||
// If the SessionManager has been shutdown mHost will be null and we won't
|
||||
// be able to resolve the promise.
|
||||
if (!mHost) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) {
|
||||
mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
|
||||
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mPersistence->IsPersistentSessionId(sessionId)) {
|
||||
mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
|
||||
if (ClearKeyPersistence::DeferLoadSessionIfNotReady(this,
|
||||
aPromiseId,
|
||||
aSessionId,
|
||||
aSessionIdLength)) {
|
||||
return;
|
||||
}
|
||||
|
||||
function<void(const uint8_t*, uint32_t)> success =
|
||||
[self, sessionId, aPromiseId] (const uint8_t* data, uint32_t size)
|
||||
{
|
||||
self->PersistentSessionDataLoaded(aPromiseId,
|
||||
sessionId,
|
||||
data,
|
||||
size);
|
||||
};
|
||||
string sid(aSessionId, aSessionId + aSessionIdLength);
|
||||
if (!ClearKeyPersistence::IsPersistentSessionId(sid)) {
|
||||
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
|
||||
return;
|
||||
}
|
||||
|
||||
function<void()> failure = [self, sessionId, aPromiseId] {
|
||||
if (!self->mHost) {
|
||||
return;
|
||||
}
|
||||
// As per the API described in ContentDecryptionModule_8
|
||||
self->mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
|
||||
};
|
||||
|
||||
ReadData(mHost, sessionId, move(success), move(failure));
|
||||
// Callsback PersistentSessionDataLoaded with results...
|
||||
ClearKeyPersistence::LoadSessionData(this, sid, aPromiseId);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeySessionManager::PersistentSessionDataLoaded(uint32_t aPromiseId,
|
||||
ClearKeySessionManager::PersistentSessionDataLoaded(GMPErr aStatus,
|
||||
uint32_t aPromiseId,
|
||||
const string& aSessionId,
|
||||
const uint8_t* aKeyData,
|
||||
uint32_t aKeyDataSize)
|
||||
{
|
||||
CK_LOGD("ClearKeySessionManager::PersistentSessionDataLoaded");
|
||||
|
||||
// Check that the SessionManager has not been shut down before we try and
|
||||
// resolve any promises.
|
||||
if (!mHost) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Contains(mSessions, aSessionId) ||
|
||||
if (GMP_FAILED(aStatus) ||
|
||||
Contains(mSessions, aSessionId) ||
|
||||
(aKeyDataSize % (2 * CENC_KEY_LEN)) != 0) {
|
||||
|
||||
// As per the instructions in ContentDecryptionModule_8
|
||||
mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
|
||||
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
|
||||
return;
|
||||
}
|
||||
|
||||
ClearKeySession* session = new ClearKeySession(aSessionId,
|
||||
SessionType::kPersistentLicense);
|
||||
|
||||
mCallback,
|
||||
kGMPPersistentSession);
|
||||
mSessions[aSessionId] = session;
|
||||
|
||||
uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN);
|
||||
|
||||
vector<KeyInformation> keyInfos;
|
||||
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;
|
||||
@ -287,25 +187,16 @@ ClearKeySessionManager::PersistentSessionDataLoaded(uint32_t aPromiseId,
|
||||
mDecryptionManager->ExpectKeyId(keyPair.mKeyId);
|
||||
mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
|
||||
mKeyIds.insert(keyPair.mKey);
|
||||
|
||||
keyPairs.push_back(keyPair);
|
||||
|
||||
KeyInformation keyInfo = KeyInformation();
|
||||
keyInfo.key_id = &keyPairs.back().mKeyId[0];
|
||||
keyInfo.key_id_size = keyPair.mKeyId.size();
|
||||
keyInfo.status = KeyStatus::kUsable;
|
||||
|
||||
keyInfos.push_back(keyInfo);
|
||||
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());
|
||||
|
||||
mHost->OnSessionKeysChange(&aSessionId[0],
|
||||
aSessionId.size(),
|
||||
true,
|
||||
keyInfos.data(),
|
||||
keyInfos.size());
|
||||
|
||||
mHost->OnResolveNewSessionPromise(aPromiseId,
|
||||
aSessionId.c_str(),
|
||||
aSessionId.size());
|
||||
mCallback->ResolveLoadSessionPromise(aPromiseId, true);
|
||||
}
|
||||
|
||||
void
|
||||
@ -315,47 +206,13 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
|
||||
const uint8_t* aResponse,
|
||||
uint32_t aResponseSize)
|
||||
{
|
||||
// Copy the method arguments so we can capture them in the lambda
|
||||
string sessionId(aSessionId, aSessionId + aSessionIdLength);
|
||||
vector<uint8_t> response(aResponse, aResponse + aResponseSize);
|
||||
|
||||
// Hold a reference to the SessionManager so it isn't released before we
|
||||
// callback.
|
||||
RefPtr<ClearKeySessionManager> self(this);
|
||||
function<void()> deferrer =
|
||||
[self, aPromiseId, sessionId, response] ()
|
||||
{
|
||||
self->UpdateSession(aPromiseId,
|
||||
sessionId.data(),
|
||||
sessionId.size(),
|
||||
response.data(),
|
||||
response.size());
|
||||
};
|
||||
|
||||
// If we haven't fully loaded, defer calling this method
|
||||
if (MaybeDeferTillInitialized(deferrer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the SessionManager has not been shutdown before we try and
|
||||
// resolve any promises.
|
||||
if (!mHost) {
|
||||
return;
|
||||
}
|
||||
|
||||
CK_LOGD("ClearKeySessionManager::UpdateSession");
|
||||
CK_LOGD("Updating session: %s", sessionId.c_str());
|
||||
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.");
|
||||
CK_LOGD("Unable to find session: %s", sessionId.c_str());
|
||||
mHost->OnRejectPromise(aPromiseId,
|
||||
Error::kInvalidAccessError,
|
||||
0,
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
ClearKeySession* session = itr->second;
|
||||
@ -363,56 +220,32 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
|
||||
// Verify the size of session response.
|
||||
if (aResponseSize >= kMaxSessionResponseLength) {
|
||||
CK_LOGW("Session response size is not within a reasonable size.");
|
||||
CK_LOGD("Failed to parse response for session %s", sessionId.c_str());
|
||||
|
||||
mHost->OnRejectPromise(aPromiseId,
|
||||
Error::kInvalidAccessError,
|
||||
0,
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
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())) {
|
||||
if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) {
|
||||
CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
|
||||
|
||||
mHost->OnRejectPromise(aPromiseId,
|
||||
Error::kInvalidAccessError,
|
||||
0,
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
mCallback->RejectPromise(aPromiseId, kGMPTypeError, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
vector<KeyInformation> keyInfos;
|
||||
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);
|
||||
|
||||
KeyInformation keyInfo = KeyInformation();
|
||||
keyInfo.key_id = &keyPair.mKeyId[0];
|
||||
keyInfo.key_id_size = keyPair.mKeyId.size();
|
||||
keyInfo.status = KeyStatus::kUsable;
|
||||
|
||||
keyInfos.push_back(keyInfo);
|
||||
key_infos.push_back(GMPMediaKeyInfo(&keyPair.mKeyId[0],
|
||||
keyPair.mKeyId.size(),
|
||||
kGMPUsable));
|
||||
}
|
||||
mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdLength,
|
||||
key_infos.data(), key_infos.size());
|
||||
|
||||
mHost->OnSessionKeysChange(aSessionId,
|
||||
aSessionIdLength,
|
||||
true,
|
||||
keyInfos.data(),
|
||||
keyInfos.size());
|
||||
|
||||
if (session->Type() != SessionType::kPersistentLicense) {
|
||||
mHost->OnResolvePromise(aPromiseId);
|
||||
if (session->Type() != kGMPPersistentSession) {
|
||||
mCallback->ResolvePromise(aPromiseId);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -420,30 +253,15 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
|
||||
// and simply append each keyId followed by its key.
|
||||
vector<uint8_t> keydata;
|
||||
Serialize(session, keydata);
|
||||
|
||||
function<void()> resolve = [self, aPromiseId] ()
|
||||
{
|
||||
if (!self->mHost) {
|
||||
return;
|
||||
}
|
||||
self->mHost->OnResolvePromise(aPromiseId);
|
||||
};
|
||||
|
||||
function<void()> reject = [self, aPromiseId] ()
|
||||
{
|
||||
if (!self->mHost) {
|
||||
return;
|
||||
}
|
||||
|
||||
static const char* message = "Couldn't store cenc key init data";
|
||||
self->mHost->OnRejectPromise(aPromiseId,
|
||||
Error::kInvalidStateError,
|
||||
0,
|
||||
message,
|
||||
strlen(message));
|
||||
};
|
||||
|
||||
WriteData(mHost, sessionId, keydata, move(resolve), move(reject));
|
||||
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
|
||||
@ -469,39 +287,13 @@ ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength)
|
||||
{
|
||||
// Copy the sessionId into a string so we capture it properly.
|
||||
string sessionId(aSessionId, aSessionId + aSessionIdLength);
|
||||
// Hold a reference to the session manager, so it doesn't get deleted
|
||||
// before we need to use it.
|
||||
RefPtr<ClearKeySessionManager> self(this);
|
||||
function<void()> deferrer =
|
||||
[self, aPromiseId, sessionId] ()
|
||||
{
|
||||
self->CloseSession(aPromiseId, sessionId.data(), sessionId.size());
|
||||
};
|
||||
|
||||
// If we haven't loaded, call this method later.
|
||||
if (MaybeDeferTillInitialized(deferrer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CK_LOGD("ClearKeySessionManager::CloseSession");
|
||||
|
||||
// If DecryptingComplete has been called mHost will be null and we won't
|
||||
// be able to resolve our promise.
|
||||
if (!mHost) {
|
||||
return;
|
||||
}
|
||||
|
||||
string sessionId(aSessionId, aSessionId + aSessionIdLength);
|
||||
auto itr = mSessions.find(sessionId);
|
||||
if (itr == mSessions.end()) {
|
||||
CK_LOGW("ClearKey CDM couldn't close non-existent session.");
|
||||
mHost->OnRejectPromise(aPromiseId,
|
||||
Error::kInvalidAccessError,
|
||||
0,
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -509,9 +301,8 @@ ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
|
||||
assert(session);
|
||||
|
||||
ClearInMemorySessionData(session);
|
||||
|
||||
mHost->OnSessionClosed(aSessionId, aSessionIdLength);
|
||||
mHost->OnResolvePromise(aPromiseId);
|
||||
mCallback->SessionClosed(aSessionId, aSessionIdLength);
|
||||
mCallback->ResolvePromise(aPromiseId);
|
||||
}
|
||||
|
||||
void
|
||||
@ -526,80 +317,40 @@ ClearKeySessionManager::RemoveSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength)
|
||||
{
|
||||
// Copy the sessionId into a string so it can be captured for the lambda.
|
||||
string sessionId(aSessionId, aSessionId + aSessionIdLength);
|
||||
|
||||
// Hold a reference to the SessionManager, so it isn't released before we
|
||||
// try and use it.
|
||||
RefPtr<ClearKeySessionManager> self(this);
|
||||
function<void()> deferrer =
|
||||
[self, aPromiseId, sessionId] ()
|
||||
{
|
||||
self->RemoveSession(aPromiseId, sessionId.data(), sessionId.size());
|
||||
};
|
||||
|
||||
// If we haven't fully loaded, defer calling this method.
|
||||
if (MaybeDeferTillInitialized(deferrer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the SessionManager has not been shutdown before we try and
|
||||
// resolve any promises.
|
||||
if (!mHost) {
|
||||
return;
|
||||
}
|
||||
|
||||
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.");
|
||||
|
||||
mHost->OnRejectPromise(aPromiseId,
|
||||
Error::kInvalidAccessError,
|
||||
0,
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
ClearKeySession* session = itr->second;
|
||||
assert(session);
|
||||
string sid = session->Id();
|
||||
bool isPersistent = session->Type() == SessionType::kPersistentLicense;
|
||||
bool isPersistent = session->Type() == kGMPPersistentSession;
|
||||
ClearInMemorySessionData(session);
|
||||
|
||||
if (!isPersistent) {
|
||||
mHost->OnResolvePromise(aPromiseId);
|
||||
mCallback->ResolvePromise(aPromiseId);
|
||||
return;
|
||||
}
|
||||
|
||||
mPersistence->PersistentSessionRemoved(sid);
|
||||
ClearKeyPersistence::PersistentSessionRemoved(sid);
|
||||
|
||||
// Overwrite the record storing the sessionId's key data with a zero
|
||||
// length record to delete it.
|
||||
vector<uint8_t> emptyKeydata;
|
||||
|
||||
function<void()> resolve = [self, aPromiseId, sessionId] ()
|
||||
{
|
||||
if (!self->mHost) {
|
||||
return;
|
||||
}
|
||||
self->mHost->OnResolvePromise(aPromiseId);
|
||||
};
|
||||
|
||||
function<void()> reject = [self, aPromiseId, sessionId] ()
|
||||
{
|
||||
if (!self->mHost) {
|
||||
return;
|
||||
}
|
||||
static const char* message = "Could not remove session";
|
||||
self->mHost->OnRejectPromise(aPromiseId,
|
||||
Error::kInvalidAccessError,
|
||||
0,
|
||||
message,
|
||||
strlen(message));
|
||||
};
|
||||
|
||||
WriteData(mHost, sessionId, emptyKeydata, move(resolve), move(reject));
|
||||
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
|
||||
@ -609,36 +360,48 @@ ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId,
|
||||
{
|
||||
// ClearKey CDM doesn't support this method by spec.
|
||||
CK_LOGD("ClearKeySessionManager::SetServerCertificate");
|
||||
mHost->OnRejectPromise(aPromiseId,
|
||||
Error::kNotSupportedError,
|
||||
0,
|
||||
nullptr /* message */,
|
||||
0 /* messageLen */);
|
||||
mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
|
||||
nullptr /* message */, 0 /* messageLen */);
|
||||
}
|
||||
|
||||
Status
|
||||
ClearKeySessionManager::Decrypt(const InputBuffer& aBuffer,
|
||||
DecryptedBlock* aDecryptedBlock)
|
||||
void
|
||||
ClearKeySessionManager::Decrypt(GMPBuffer* aBuffer,
|
||||
GMPEncryptedBufferMetadata* aMetadata)
|
||||
{
|
||||
CK_LOGD("ClearKeySessionManager::Decrypt");
|
||||
|
||||
CK_LOGARRAY("Key: ", aBuffer.key_id, aBuffer.key_id_size);
|
||||
if (!mThread) {
|
||||
CK_LOGW("No decrypt thread");
|
||||
mCallback->Decrypted(aBuffer, GMPGenericErr);
|
||||
return;
|
||||
}
|
||||
|
||||
Buffer* buffer = mHost->Allocate(aBuffer.data_size);
|
||||
assert(buffer != nullptr);
|
||||
assert(buffer->Data() != nullptr);
|
||||
assert(buffer->Capacity() >= aBuffer.data_size);
|
||||
mThread->Post(WrapTaskRefCounted(this,
|
||||
&ClearKeySessionManager::DoDecrypt,
|
||||
aBuffer, aMetadata));
|
||||
}
|
||||
|
||||
memcpy(buffer->Data(), aBuffer.data, aBuffer.data_size);
|
||||
void
|
||||
ClearKeySessionManager::DoDecrypt(GMPBuffer* aBuffer,
|
||||
GMPEncryptedBufferMetadata* aMetadata)
|
||||
{
|
||||
CK_LOGD("ClearKeySessionManager::DoDecrypt");
|
||||
|
||||
Status status = mDecryptionManager->Decrypt(buffer->Data(),
|
||||
buffer->Size(),
|
||||
CryptoMetaData(&aBuffer));
|
||||
GMPErr rv = mDecryptionManager->Decrypt(aBuffer->Data(), aBuffer->Size(),
|
||||
CryptoMetaData(aMetadata));
|
||||
CK_LOGD("DeDecrypt finished with code %x\n", rv);
|
||||
mCallback->Decrypted(aBuffer, rv);
|
||||
}
|
||||
|
||||
aDecryptedBlock->SetDecryptedBuffer(buffer);
|
||||
aDecryptedBlock->SetTimestamp(aBuffer.timestamp);
|
||||
void
|
||||
ClearKeySessionManager::Shutdown()
|
||||
{
|
||||
CK_LOGD("ClearKeySessionManager::Shutdown %p", this);
|
||||
|
||||
return status;
|
||||
for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
|
||||
delete it->second;
|
||||
}
|
||||
mSessions.clear();
|
||||
}
|
||||
|
||||
void
|
||||
@ -646,23 +409,10 @@ ClearKeySessionManager::DecryptingComplete()
|
||||
{
|
||||
CK_LOGD("ClearKeySessionManager::DecryptingComplete %p", this);
|
||||
|
||||
for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
|
||||
delete it->second;
|
||||
}
|
||||
mSessions.clear();
|
||||
GMPThread* thread = mThread;
|
||||
thread->Join();
|
||||
|
||||
Shutdown();
|
||||
mDecryptionManager = nullptr;
|
||||
mHost = nullptr;
|
||||
|
||||
Release();
|
||||
}
|
||||
|
||||
bool ClearKeySessionManager::MaybeDeferTillInitialized(function<void()> aMaybeDefer)
|
||||
{
|
||||
if (mPersistence->IsLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mDeferredInitialize.emplace(move(aMaybeDefer));
|
||||
return true;
|
||||
}
|
||||
|
@ -1,81 +1,80 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
* 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 "ClearKeyDecryptionManager.h"
|
||||
#include "ClearKeyPersistence.h"
|
||||
#include "ClearKeySession.h"
|
||||
#include "ClearKeyUtils.h"
|
||||
// This include is required in order for content_decryption_module to work
|
||||
// on Unix systems.
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "RefCounted.h"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class ClearKeySessionManager final : public RefCounted
|
||||
#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:
|
||||
explicit ClearKeySessionManager(cdm::Host_8* aHost);
|
||||
ClearKeySessionManager();
|
||||
|
||||
void Init(bool aDistinctiveIdentifierAllowed,
|
||||
bool aPersistentStateAllowed);
|
||||
virtual void Init(GMPDecryptorCallback* aCallback,
|
||||
bool aDistinctiveIdentifierAllowed,
|
||||
bool aPersistentStateAllowed) override;
|
||||
|
||||
void CreateSession(uint32_t aPromiseId,
|
||||
cdm::InitDataType aInitDataType,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize,
|
||||
cdm::SessionType aSessionType);
|
||||
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;
|
||||
|
||||
void LoadSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength);
|
||||
virtual void LoadSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength) override;
|
||||
|
||||
void UpdateSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength,
|
||||
const uint8_t* aResponse,
|
||||
uint32_t aResponseSize);
|
||||
virtual void UpdateSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength,
|
||||
const uint8_t* aResponse,
|
||||
uint32_t aResponseSize) override;
|
||||
|
||||
void CloseSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength);
|
||||
virtual void CloseSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength) override;
|
||||
|
||||
void RemoveSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength);
|
||||
virtual void RemoveSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength) override;
|
||||
|
||||
void SetServerCertificate(uint32_t aPromiseId,
|
||||
const uint8_t* aServerCert,
|
||||
uint32_t aServerCertSize);
|
||||
virtual void SetServerCertificate(uint32_t aPromiseId,
|
||||
const uint8_t* aServerCert,
|
||||
uint32_t aServerCertSize) override;
|
||||
|
||||
cdm::Status
|
||||
Decrypt(const cdm::InputBuffer& aBuffer,
|
||||
cdm::DecryptedBlock* aDecryptedBlock);
|
||||
virtual void Decrypt(GMPBuffer* aBuffer,
|
||||
GMPEncryptedBufferMetadata* aMetadata) override;
|
||||
|
||||
void DecryptingComplete();
|
||||
virtual void DecryptingComplete() override;
|
||||
|
||||
void PersistentSessionDataLoaded(uint32_t aPromiseId,
|
||||
void PersistentSessionDataLoaded(GMPErr aStatus,
|
||||
uint32_t aPromiseId,
|
||||
const std::string& aSessionId,
|
||||
const uint8_t* aKeyData,
|
||||
uint32_t aKeyDataSize);
|
||||
@ -83,20 +82,19 @@ public:
|
||||
private:
|
||||
~ClearKeySessionManager();
|
||||
|
||||
void DoDecrypt(GMPBuffer* aBuffer, GMPEncryptedBufferMetadata* aMetadata);
|
||||
void Shutdown();
|
||||
|
||||
void ClearInMemorySessionData(ClearKeySession* aSession);
|
||||
bool MaybeDeferTillInitialized(std::function<void()> aMaybeDefer);
|
||||
void Serialize(const ClearKeySession* aSession,
|
||||
std::vector<uint8_t>& aOutKeyData);
|
||||
void Serialize(const ClearKeySession* aSession, std::vector<uint8_t>& aOutKeyData);
|
||||
|
||||
RefPtr<ClearKeyDecryptionManager> mDecryptionManager;
|
||||
RefPtr<ClearKeyPersistence> mPersistence;
|
||||
|
||||
cdm::Host_8* mHost = nullptr;
|
||||
GMPDecryptorCallback* mCallback;
|
||||
GMPThread* mThread;
|
||||
|
||||
std::set<KeyId> mKeyIds;
|
||||
std::map<std::string, ClearKeySession*> mSessions;
|
||||
|
||||
std::queue<std::function<void()>> mDeferredInitialize;
|
||||
};
|
||||
|
||||
#endif // __ClearKeyDecryptor_h__
|
||||
|
@ -15,212 +15,180 @@
|
||||
*/
|
||||
|
||||
#include "ClearKeyStorage.h"
|
||||
|
||||
#include "ClearKeyUtils.h"
|
||||
|
||||
// This include is required in order for content_decryption_module to work
|
||||
// on Unix systems.
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "gmp-task-utils.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include "ArrayUtils.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace cdm;
|
||||
using namespace std;
|
||||
|
||||
class WriteRecordClient : public FileIOClient
|
||||
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(Host_8* aHost,
|
||||
string& aRecordName,
|
||||
const vector<uint8_t>& aData,
|
||||
function<void()>&& aOnSuccess,
|
||||
function<void()>&& aOnFailure)
|
||||
{
|
||||
WriteRecordClient* client = new WriteRecordClient(aData,
|
||||
move(aOnSuccess),
|
||||
move(aOnFailure));
|
||||
client->Do(aRecordName, aHost);
|
||||
static void Write(const std::string& aRecordName,
|
||||
const std::vector<uint8_t>& aData,
|
||||
GMPTask* aOnSuccess,
|
||||
GMPTask* aOnFailure) {
|
||||
(new WriteRecordClient(aData, aOnSuccess, aOnFailure))->Do(aRecordName);
|
||||
}
|
||||
|
||||
void OnOpenComplete(Status aStatus) override
|
||||
{
|
||||
// If we hit an error, fail.
|
||||
if (aStatus != Status::kSuccess) {
|
||||
Done(aStatus);
|
||||
} else if (mFileIO) { // Otherwise, write our data to the file.
|
||||
mFileIO->Write(&mData[0], mData.size());
|
||||
virtual void OpenComplete(GMPErr aStatus) override {
|
||||
if (GMP_FAILED(aStatus) ||
|
||||
GMP_FAILED(mRecord->Write(&mData.front(), mData.size()))) {
|
||||
Done(mOnFailure, mOnSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
void OnReadComplete(Status aStatus,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize) override
|
||||
{
|
||||
// This function should never be called, we only ever write data with this
|
||||
// client.
|
||||
assert(false);
|
||||
virtual void ReadComplete(GMPErr aStatus,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize) override {
|
||||
assert(false); // Should not reach here.
|
||||
}
|
||||
|
||||
void OnWriteComplete(Status aStatus) override
|
||||
{
|
||||
Done(aStatus);
|
||||
virtual void WriteComplete(GMPErr aStatus) override {
|
||||
if (GMP_FAILED(aStatus)) {
|
||||
Done(mOnFailure, mOnSuccess);
|
||||
} else {
|
||||
Done(mOnSuccess, mOnFailure);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
explicit WriteRecordClient(const vector<uint8_t>& aData,
|
||||
function<void()>&& aOnSuccess,
|
||||
function<void()>&& aOnFailure)
|
||||
: mFileIO(nullptr)
|
||||
, mOnSuccess(move(aOnSuccess))
|
||||
, mOnFailure(move(aOnFailure))
|
||||
WriteRecordClient(const std::vector<uint8_t>& aData,
|
||||
GMPTask* aOnSuccess,
|
||||
GMPTask* aOnFailure)
|
||||
: mRecord(nullptr)
|
||||
, mOnSuccess(aOnSuccess)
|
||||
, mOnFailure(aOnFailure)
|
||||
, mData(aData) {}
|
||||
|
||||
void Do(const string& aName, Host_8* aHost)
|
||||
{
|
||||
// Initialize the FileIO.
|
||||
mFileIO = aHost->CreateFileIO(this);
|
||||
mFileIO->Open(aName.c_str(), aName.size());
|
||||
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(cdm::FileIOClient::Status aStatus)
|
||||
{
|
||||
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 (mFileIO) {
|
||||
mFileIO->Close();
|
||||
if (mRecord) {
|
||||
mRecord->Close();
|
||||
}
|
||||
|
||||
if (IO_SUCCEEDED(aStatus)) {
|
||||
mOnSuccess();
|
||||
} else {
|
||||
mOnFailure();
|
||||
}
|
||||
|
||||
aToDestroy->Destroy();
|
||||
RunOnMainThread(aToRun);
|
||||
delete this;
|
||||
}
|
||||
|
||||
FileIO* mFileIO = nullptr;
|
||||
|
||||
function<void()> mOnSuccess;
|
||||
function<void()> mOnFailure;
|
||||
|
||||
const vector<uint8_t> mData;
|
||||
GMPRecord* mRecord;
|
||||
GMPTask* mOnSuccess;
|
||||
GMPTask* mOnFailure;
|
||||
const std::vector<uint8_t> mData;
|
||||
};
|
||||
|
||||
void
|
||||
WriteData(Host_8* aHost,
|
||||
string& aRecordName,
|
||||
const vector<uint8_t>& aData,
|
||||
function<void()>&& aOnSuccess,
|
||||
function<void()>&& aOnFailure)
|
||||
StoreData(const std::string& aRecordName,
|
||||
const std::vector<uint8_t>& aData,
|
||||
GMPTask* aOnSuccess,
|
||||
GMPTask* aOnFailure)
|
||||
{
|
||||
WriteRecordClient::Write(aHost,
|
||||
aRecordName,
|
||||
aData,
|
||||
move(aOnSuccess),
|
||||
move(aOnFailure));
|
||||
WriteRecordClient::Write(aRecordName, aData, aOnSuccess, aOnFailure);
|
||||
}
|
||||
|
||||
class ReadRecordClient : public FileIOClient
|
||||
{
|
||||
class ReadRecordClient : public GMPRecordClient {
|
||||
public:
|
||||
/*
|
||||
* This function will take the memory ownership of the parameters and
|
||||
* delete them when done.
|
||||
*/
|
||||
static void Read(Host_8* aHost,
|
||||
string& aRecordName,
|
||||
function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
|
||||
function<void()>&& aOnFailure)
|
||||
{
|
||||
|
||||
(new ReadRecordClient(move(aOnSuccess), move(aOnFailure)))->
|
||||
Do(aRecordName, aHost);
|
||||
static void Read(const std::string& aRecordName,
|
||||
ReadContinuation* aContinuation) {
|
||||
assert(aContinuation);
|
||||
(new ReadRecordClient(aContinuation))->Do(aRecordName);
|
||||
}
|
||||
|
||||
void OnOpenComplete(Status aStatus) override
|
||||
{
|
||||
virtual void OpenComplete(GMPErr aStatus) override {
|
||||
auto err = aStatus;
|
||||
if (aStatus != Status::kSuccess) {
|
||||
if (GMP_FAILED(err) ||
|
||||
GMP_FAILED(err = mRecord->Read())) {
|
||||
Done(err, nullptr, 0);
|
||||
} else {
|
||||
mFileIO->Read();
|
||||
}
|
||||
}
|
||||
|
||||
void OnReadComplete(Status aStatus,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize) override
|
||||
{
|
||||
virtual void ReadComplete(GMPErr aStatus,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize) override {
|
||||
Done(aStatus, aData, aDataSize);
|
||||
}
|
||||
|
||||
void OnWriteComplete(Status aStatus) override
|
||||
{
|
||||
// We should never reach here, this client only ever reads data.
|
||||
assert(false);
|
||||
virtual void WriteComplete(GMPErr aStatus) override {
|
||||
assert(false); // Should not reach here.
|
||||
}
|
||||
|
||||
private:
|
||||
explicit ReadRecordClient(function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
|
||||
function<void()>&& aOnFailure)
|
||||
: mFileIO(nullptr)
|
||||
, mOnSuccess(move(aOnSuccess))
|
||||
, mOnFailure(move(aOnFailure))
|
||||
{}
|
||||
explicit ReadRecordClient(ReadContinuation* aContinuation)
|
||||
: mRecord(nullptr)
|
||||
, mContinuation(aContinuation) {}
|
||||
|
||||
void Do(const string& aName, Host_8* aHost)
|
||||
{
|
||||
mFileIO = aHost->CreateFileIO(this);
|
||||
mFileIO->Open(aName.c_str(), aName.size());
|
||||
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(cdm::FileIOClient::Status aStatus,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize)
|
||||
{
|
||||
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 (mFileIO) {
|
||||
mFileIO->Close();
|
||||
if (mRecord) {
|
||||
mRecord->Close();
|
||||
}
|
||||
|
||||
if (IO_SUCCEEDED(aStatus)) {
|
||||
mOnSuccess(aData, aDataSize);
|
||||
} else {
|
||||
mOnFailure();
|
||||
}
|
||||
|
||||
mContinuation->ReadComplete(err, aData, aDataSize);
|
||||
delete mContinuation;
|
||||
delete this;
|
||||
}
|
||||
|
||||
FileIO* mFileIO = nullptr;
|
||||
|
||||
function<void(const uint8_t*, uint32_t)> mOnSuccess;
|
||||
function<void()> mOnFailure;
|
||||
GMPRecord* mRecord;
|
||||
ReadContinuation* mContinuation;
|
||||
};
|
||||
|
||||
void
|
||||
ReadData(Host_8* mHost,
|
||||
string& aRecordName,
|
||||
function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
|
||||
function<void()>&& aOnFailure)
|
||||
ReadData(const std::string& aRecordName,
|
||||
ReadContinuation* aContinuation)
|
||||
{
|
||||
ReadRecordClient::Read(mHost,
|
||||
aRecordName,
|
||||
move(aOnSuccess),
|
||||
move(aOnFailure));
|
||||
ReadRecordClient::Read(aRecordName, aContinuation);
|
||||
}
|
||||
|
||||
GMPErr
|
||||
EnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc)
|
||||
{
|
||||
return GetPlatform()->getrecordenumerator(aRecvIteratorFunc, nullptr);
|
||||
}
|
||||
|
@ -17,27 +17,32 @@
|
||||
#ifndef __ClearKeyStorage_h__
|
||||
#define __ClearKeyStorage_h__
|
||||
|
||||
#include <functional>
|
||||
#include <stdint.h>
|
||||
#include "gmp-api/gmp-errors.h"
|
||||
#include "gmp-api/gmp-platform.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ClearKeySessionManager.h"
|
||||
class GMPTask;
|
||||
|
||||
#define IO_SUCCEEDED(x) ((x) == cdm::FileIOClient::Status::kSuccess)
|
||||
#define IO_FAILED(x) ((x) != cdm::FileIOClient::Status::kSuccess)
|
||||
|
||||
// Writes data to a file and fires the appropriate callback when complete.
|
||||
void WriteData(cdm::Host_8* aHost,
|
||||
std::string& aRecordName,
|
||||
// Responsible for ensuring that both aOnSuccess and aOnFailure are destroyed.
|
||||
void StoreData(const std::string& aRecordName,
|
||||
const std::vector<uint8_t>& aData,
|
||||
std::function<void()>&& aOnSuccess,
|
||||
std::function<void()>&& aOnFailure);
|
||||
GMPTask* aOnSuccess,
|
||||
GMPTask* aOnFailure);
|
||||
|
||||
// Reads data from a file and fires the appropriate callback when complete.
|
||||
void ReadData(cdm::Host_8* aHost,
|
||||
std::string& aRecordName,
|
||||
std::function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
|
||||
std::function<void()>&& 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__
|
||||
|
@ -14,77 +14,33 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ClearKeyUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <cctype>
|
||||
#include <ctype.h>
|
||||
#include <memory.h>
|
||||
#include <sstream>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
#include "ArrayUtils.h"
|
||||
#include "BigEndian.h"
|
||||
#include "ClearKeyUtils.h"
|
||||
#include "ClearKeyBase64.h"
|
||||
// This include is required in order for content_decryption_module to work
|
||||
// on Unix systems.
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "ArrayUtils.h"
|
||||
#include <assert.h>
|
||||
#include <memory.h>
|
||||
#include "BigEndian.h"
|
||||
#include "openaes/oaes_lib.h"
|
||||
#include "psshparser/PsshParser.h"
|
||||
|
||||
using namespace cdm;
|
||||
using namespace std;
|
||||
|
||||
void
|
||||
CK_Log(const char* aFmt, ...)
|
||||
{
|
||||
FILE* out = stdout;
|
||||
|
||||
if (getenv("CLEARKEY_LOG_FILE")) {
|
||||
out = fopen(getenv("CLEARKEY_LOG_FILE"), "a");
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, aFmt);
|
||||
const size_t len = 1024;
|
||||
char buf[len];
|
||||
vsnprintf(buf, len, aFmt, ap);
|
||||
vprintf(aFmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
fprintf(out, "%s\n", buf);
|
||||
fflush(out);
|
||||
|
||||
if (out != stdout) {
|
||||
fclose(out);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
PrintableAsString(const uint8_t* aBytes, uint32_t aLength)
|
||||
{
|
||||
return all_of(aBytes, aBytes + aLength, [] (uint8_t c) {
|
||||
return isprint(c) == 1;
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
CK_LogArray(const char* prepend,
|
||||
const uint8_t* aData,
|
||||
const uint32_t aDataSize)
|
||||
{
|
||||
// If the data is valid ascii, use that. Otherwise print the hex
|
||||
string data = PrintableAsString(aData, aDataSize) ?
|
||||
string(aData, aData + aDataSize) :
|
||||
ClearKeyUtils::ToHexString(aData, aDataSize);
|
||||
|
||||
CK_LOGD("%s%s", prepend, data.c_str());
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -159,9 +115,7 @@ EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded)
|
||||
// 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]);
|
||||
|
||||
// out of bounds index for 'sAlphabet'
|
||||
assert(idx < MOZ_ARRAY_LENGTH(sAlphabet));
|
||||
assert(idx < MOZ_ARRAY_LENGTH(sAlphabet)); // out of bounds index for 'sAlphabet'
|
||||
out[i] = sAlphabet[idx];
|
||||
}
|
||||
|
||||
@ -171,7 +125,7 @@ EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded)
|
||||
/* static */ void
|
||||
ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
|
||||
string& aOutRequest,
|
||||
SessionType aSessionType)
|
||||
GMPSessionType aSessionType)
|
||||
{
|
||||
assert(aKeyIDs.size() && aOutRequest.empty());
|
||||
|
||||
@ -435,7 +389,7 @@ ParseKeys(ParserContext& aCtx, vector<KeyIdPair>& aOutKeys)
|
||||
/* static */ bool
|
||||
ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
|
||||
vector<KeyIdPair>& aOutKeys,
|
||||
SessionType aSessionType)
|
||||
GMPSessionType aSessionType)
|
||||
{
|
||||
ParserContext ctx;
|
||||
ctx.mIter = aKeyData;
|
||||
@ -551,16 +505,15 @@ ClearKeyUtils::ParseKeyIdsInitData(const uint8_t* aInitData,
|
||||
}
|
||||
|
||||
/* static */ const char*
|
||||
ClearKeyUtils::SessionTypeToString(SessionType aSessionType)
|
||||
ClearKeyUtils::SessionTypeToString(GMPSessionType aSessionType)
|
||||
{
|
||||
switch (aSessionType) {
|
||||
case SessionType::kTemporary: return "temporary";
|
||||
case SessionType::kPersistentLicense: return "persistent-license";
|
||||
default: {
|
||||
// We don't support any other license types.
|
||||
assert(false);
|
||||
return "invalid";
|
||||
}
|
||||
case kGMPTemporySession: return "temporary";
|
||||
case kGMPPersistentSession: return "persistent-license";
|
||||
default: {
|
||||
assert(false); // Should not reach here.
|
||||
return "invalid";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -580,15 +533,9 @@ ClearKeyUtils::IsValidSessionId(const char* aBuff, uint32_t aLength)
|
||||
return true;
|
||||
}
|
||||
|
||||
string
|
||||
ClearKeyUtils::ToHexString(const uint8_t * aBytes, uint32_t aLength)
|
||||
{
|
||||
stringstream ss;
|
||||
ss << std::showbase << std::uppercase << std::hex;
|
||||
for (uint32_t i = 0; i < aLength; ++i) {
|
||||
ss << std::hex << static_cast<uint32_t>(aBytes[i]);
|
||||
ss << " ";
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
GMPMutex* GMPCreateMutex() {
|
||||
GMPMutex* mutex;
|
||||
auto err = GetPlatform()->createmutex(&mutex);
|
||||
assert(mutex);
|
||||
return GMP_FAILED(err) ? nullptr : mutex;
|
||||
}
|
||||
|
@ -21,29 +21,22 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
// This include is required in order for content_decryption_module to work
|
||||
// on Unix systems.
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.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__)
|
||||
#define CK_LOGARRAY(APREPEND, ADATA, ADATA_SIZE) CK_LogArray(APREPEND, \
|
||||
ADATA, \
|
||||
ADATA_SIZE)
|
||||
#else
|
||||
// Note: Enabling logging slows things down a LOT, especially when logging to
|
||||
// a file.
|
||||
#define CK_LOGE(...)
|
||||
#define CK_LOGD(...)
|
||||
#define CK_LOGW(...)
|
||||
#define CK_LOGARRAY(APREPEND, ADATA, ADATA_SIZE)
|
||||
#endif
|
||||
|
||||
struct GMPPlatformAPI;
|
||||
extern GMPPlatformAPI* GetPlatform();
|
||||
|
||||
typedef std::vector<uint8_t> KeyId;
|
||||
typedef std::vector<uint8_t> Key;
|
||||
|
||||
@ -55,10 +48,6 @@ static const uint32_t kMaxSessionResponseLength = 65536;
|
||||
static const uint32_t kMaxWebmInitDataSize = 65536;
|
||||
static const uint32_t kMaxKeyIdsLength = 512;
|
||||
|
||||
void CK_LogArray(const char* aPrepend,
|
||||
const uint8_t* aData,
|
||||
const uint32_t aDataSize);
|
||||
|
||||
struct KeyIdPair
|
||||
{
|
||||
KeyId mKeyId;
|
||||
@ -77,16 +66,14 @@ public:
|
||||
|
||||
static void MakeKeyRequest(const std::vector<KeyId>& aKeyIds,
|
||||
std::string& aOutRequest,
|
||||
cdm::SessionType aSessionType);
|
||||
GMPSessionType aSessionType);
|
||||
|
||||
static bool ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
|
||||
std::vector<KeyIdPair>& aOutKeys,
|
||||
cdm::SessionType aSessionType);
|
||||
static const char* SessionTypeToString(cdm::SessionType aSessionType);
|
||||
GMPSessionType aSessionType);
|
||||
static const char* SessionTypeToString(GMPSessionType aSessionType);
|
||||
|
||||
static bool IsValidSessionId(const char* aBuff, uint32_t aLength);
|
||||
|
||||
static std::string ToHexString(const uint8_t * aBytes, uint32_t aLength);
|
||||
};
|
||||
|
||||
template<class Container, class Element>
|
||||
@ -96,6 +83,27 @@ 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)
|
||||
|
@ -21,7 +21,41 @@
|
||||
#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 {
|
||||
@ -47,41 +81,27 @@ protected:
|
||||
{
|
||||
assert(!mRefCount);
|
||||
}
|
||||
std::atomic<uint32_t> mRefCount;
|
||||
AtomicRefCount mRefCount;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class RefPtr {
|
||||
public:
|
||||
RefPtr(const RefPtr& src) {
|
||||
Set(src.mPtr);
|
||||
explicit RefPtr(T* aPtr) : mPtr(nullptr) {
|
||||
Assign(aPtr);
|
||||
}
|
||||
|
||||
explicit RefPtr(T* aPtr) {
|
||||
Set(aPtr);
|
||||
}
|
||||
RefPtr() { Set(nullptr); }
|
||||
|
||||
~RefPtr() {
|
||||
Set(nullptr);
|
||||
Assign(nullptr);
|
||||
}
|
||||
T* operator->() const { return mPtr; }
|
||||
T** operator&() { return &mPtr; }
|
||||
T* operator->() { return mPtr; }
|
||||
operator T*() { return mPtr; }
|
||||
|
||||
T* Get() const { return mPtr; }
|
||||
|
||||
RefPtr& operator=(T* aVal) {
|
||||
Set(aVal);
|
||||
Assign(aVal);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
T* Set(T* aPtr) {
|
||||
if (mPtr == aPtr) {
|
||||
return aPtr;
|
||||
}
|
||||
void Assign(T* aPtr) {
|
||||
if (mPtr) {
|
||||
mPtr->Release();
|
||||
}
|
||||
@ -89,10 +109,8 @@ private:
|
||||
if (mPtr) {
|
||||
aPtr->AddRef();
|
||||
}
|
||||
return mPtr;
|
||||
}
|
||||
|
||||
T* mPtr = nullptr;
|
||||
T* mPtr;
|
||||
};
|
||||
|
||||
#endif // __RefCount_h__
|
||||
|
@ -14,162 +14,247 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#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;
|
||||
using namespace cdm;
|
||||
|
||||
VideoDecoder::VideoDecoder(Host_8 *aHost)
|
||||
: mHost(aHost)
|
||||
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();
|
||||
|
||||
mDecoder = new WMFH264Decoder();
|
||||
|
||||
uint32_t cores = std::max(1u, std::thread::hardware_concurrency());
|
||||
HRESULT hr = mDecoder->Init(cores);
|
||||
}
|
||||
|
||||
VideoDecoder::~VideoDecoder()
|
||||
{
|
||||
|
||||
if (mMutex) {
|
||||
mMutex->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Status
|
||||
VideoDecoder::InitDecode(const VideoDecoderConfig& aConfig)
|
||||
void
|
||||
VideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
|
||||
const uint8_t* aCodecSpecific,
|
||||
uint32_t aCodecSpecificLength,
|
||||
GMPVideoDecoderCallback* aCallback,
|
||||
int32_t aCoreCount)
|
||||
{
|
||||
if (!mDecoder) {
|
||||
mCallback = aCallback;
|
||||
assert(mCallback);
|
||||
mDecoder = new WMFH264Decoder();
|
||||
HRESULT hr = mDecoder->Init(aCoreCount);
|
||||
if (FAILED(hr)) {
|
||||
CK_LOGD("VideoDecoder::InitDecode failed to init WMFH264Decoder");
|
||||
|
||||
return Status::kDecodeError;
|
||||
mCallback->Error(GMPGenericErr);
|
||||
return;
|
||||
}
|
||||
|
||||
return Status::kSuccess;
|
||||
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);
|
||||
}
|
||||
|
||||
Status
|
||||
VideoDecoder::Decode(const InputBuffer& aInputBuffer, VideoFrame* aVideoFrame)
|
||||
void
|
||||
VideoDecoder::EnsureWorker()
|
||||
{
|
||||
// If the input buffer we have been passed has a null buffer, it means we
|
||||
// should drain.
|
||||
if (!aInputBuffer.data) {
|
||||
// This will drain the decoder until there are no frames left to drain,
|
||||
// whereupon it will return 'NeedsMoreData'.
|
||||
CK_LOGD("Input buffer null: Draining");
|
||||
return Drain(aVideoFrame);
|
||||
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;
|
||||
}
|
||||
|
||||
DecodeData* data = new DecodeData();
|
||||
Assign(data->mBuffer, aInputBuffer.data, aInputBuffer.data_size);
|
||||
data->mTimestamp = aInputBuffer.timestamp;
|
||||
data->mCrypto = CryptoMetaData(&aInputBuffer);
|
||||
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(data);
|
||||
AutoPtr<DecodeData> d(aData);
|
||||
HRESULT hr;
|
||||
|
||||
if (!data || !mDecoder) {
|
||||
CK_LOGE("Decode job not set up correctly!");
|
||||
return Status::kDecodeError;
|
||||
{
|
||||
AutoLock lock(mMutex);
|
||||
mNumInputTasks--;
|
||||
assert(mNumInputTasks >= 0);
|
||||
}
|
||||
|
||||
std::vector<uint8_t>& buffer = data->mBuffer;
|
||||
if (mIsFlushing) {
|
||||
CK_LOGD("VideoDecoder::DecodeTask rejecting frame: flushing.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (data->mCrypto.IsValid()) {
|
||||
Status rv =
|
||||
ClearKeyDecryptionManager::Get()->Decrypt(buffer, data->mCrypto);
|
||||
if (!aData || !mHostAPI || !mDecoder) {
|
||||
CK_LOGE("Decode job not set up correctly!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (STATUS_FAILED(rv)) {
|
||||
CK_LOGARRAY("Failed to decrypt video using key ",
|
||||
aInputBuffer.key_id,
|
||||
aInputBuffer.key_id_size);
|
||||
return rv;
|
||||
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(),
|
||||
data->mTimestamp);
|
||||
aData->mTimestamp,
|
||||
aData->mDuration);
|
||||
|
||||
CK_LOGD("VideoDecoder::DecodeTask() Input ret hr=0x%x\n", hr);
|
||||
|
||||
|
||||
if (FAILED(hr)) {
|
||||
assert(hr != MF_E_TRANSFORM_NEED_MORE_INPUT);
|
||||
|
||||
CK_LOGE("VideoDecoder::DecodeTask() decode failed ret=0x%x%s\n",
|
||||
hr,
|
||||
((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
|
||||
CK_LOGD("Decode failed. The decoder is not accepting input");
|
||||
return Status::kDecodeError;
|
||||
hr,
|
||||
((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
|
||||
return;
|
||||
}
|
||||
|
||||
return OutputFrame(aVideoFrame);
|
||||
}
|
||||
|
||||
Status VideoDecoder::OutputFrame(VideoFrame* aVideoFrame) {
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
// Read all the output from the decoder. Ideally, this would be a while loop
|
||||
// where we read the output and check the result as the condition. However,
|
||||
// this produces a memory leak connected to assigning a new CComPtr to the
|
||||
// address of the old one, which avoids the CComPtr cleaning up.
|
||||
while (true) {
|
||||
while (hr == S_OK) {
|
||||
CComPtr<IMFSample> output;
|
||||
hr = mDecoder->Output(&output);
|
||||
|
||||
if (hr != S_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mOutputQueue.push(output);
|
||||
CK_LOGD("Queue size: %u", mOutputQueue.size());
|
||||
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;
|
||||
}
|
||||
|
||||
// If we don't have any inputs, we need more data.
|
||||
if (mOutputQueue.empty()) {
|
||||
CK_LOGD("Decode failed. Not enought data; Requesting more input");
|
||||
return Status::kNeedMoreData;
|
||||
}
|
||||
auto vf = static_cast<GMPVideoi420Frame*>(f);
|
||||
|
||||
// We will get a MF_E_TRANSFORM_NEED_MORE_INPUT every time, as we always
|
||||
// consume everything in the buffer.
|
||||
if (hr != MF_E_TRANSFORM_NEED_MORE_INPUT && FAILED(hr)) {
|
||||
CK_LOGD("Decode failed output ret=0x%x\n", hr);
|
||||
return Status::kDecodeError;
|
||||
}
|
||||
hr = SampleToVideoFrame(aSample, aWidth, aHeight, aStride, vf);
|
||||
ENSURE(SUCCEEDED(hr), /*void*/);
|
||||
|
||||
CComPtr<IMFSample> result = mOutputQueue.front();
|
||||
mOutputQueue.pop();
|
||||
|
||||
// The Chromium CDM API doesn't have support for negative strides, though
|
||||
// they are theoretically possible in real world data.
|
||||
if (mDecoder->GetStride() <= 0) {
|
||||
return Status::kDecodeError;
|
||||
}
|
||||
|
||||
hr = SampleToVideoFrame(result,
|
||||
mDecoder->GetFrameWidth(),
|
||||
mDecoder->GetFrameHeight(),
|
||||
mDecoder->GetStride(),
|
||||
aVideoFrame);
|
||||
if (FAILED(hr)) {
|
||||
return Status::kDecodeError;
|
||||
}
|
||||
|
||||
CK_LOGD("Decode succeeded.");
|
||||
return Status::kSuccess;
|
||||
mCallback->Decoded(vf);
|
||||
}
|
||||
|
||||
HRESULT
|
||||
@ -177,19 +262,14 @@ VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
|
||||
int32_t aWidth,
|
||||
int32_t aHeight,
|
||||
int32_t aStride,
|
||||
VideoFrame* aVideoFrame)
|
||||
GMPVideoi420Frame* aVideoFrame)
|
||||
{
|
||||
CK_LOGD("[%p] VideoDecoder::SampleToVideoFrame()\n", this);
|
||||
assert(aSample);
|
||||
|
||||
ENSURE(aSample != nullptr, E_POINTER);
|
||||
ENSURE(aVideoFrame != nullptr, E_POINTER);
|
||||
|
||||
HRESULT hr;
|
||||
CComPtr<IMFMediaBuffer> mediaBuffer;
|
||||
|
||||
aVideoFrame->SetFormat(kI420);
|
||||
|
||||
// Must convert to contiguous mediaBuffer to use IMD2DBuffer interface.
|
||||
hr = aSample->ConvertToContiguousBuffer(&mediaBuffer);
|
||||
ENSURE(SUCCEEDED(hr), hr);
|
||||
@ -205,60 +285,46 @@ VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
|
||||
hr = twoDBuffer->Lock2D(&data, &stride);
|
||||
ENSURE(SUCCEEDED(hr), hr);
|
||||
} else {
|
||||
hr = mediaBuffer->Lock(&data, nullptr, nullptr);
|
||||
hr = mediaBuffer->Lock(&data, NULL, NULL);
|
||||
ENSURE(SUCCEEDED(hr), hr);
|
||||
stride = aStride;
|
||||
}
|
||||
|
||||
// The U and V planes are stored 16-row-aligned, so we need to add padding
|
||||
// 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....][UUUU....][VVVV....]
|
||||
// i.e., Y, then U, then V.
|
||||
// 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);
|
||||
}
|
||||
uint32_t ySize = stride * (aHeight + padding);
|
||||
uint32_t uSize = stride * (aHeight + padding) / 4;
|
||||
uint32_t halfStride = (stride + 1) / 2;
|
||||
uint32_t halfHeight = (aHeight + 1) / 2;
|
||||
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;
|
||||
|
||||
aVideoFrame->SetStride(VideoFrame::kYPlane, stride);
|
||||
aVideoFrame->SetStride(VideoFrame::kUPlane, halfStride);
|
||||
aVideoFrame->SetStride(VideoFrame::kVPlane, halfStride);
|
||||
auto err = aVideoFrame->CreateEmptyFrame(stride, aHeight, stride, halfStride, halfStride);
|
||||
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
|
||||
|
||||
aVideoFrame->SetSize(Size(aWidth, aHeight));
|
||||
err = aVideoFrame->SetWidth(aWidth);
|
||||
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
|
||||
err = aVideoFrame->SetHeight(aHeight);
|
||||
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
|
||||
|
||||
uint64_t bufferSize = ySize + 2 * uSize;
|
||||
uint8_t* outBuffer = aVideoFrame->Buffer(kGMPYPlane);
|
||||
ENSURE(outBuffer != nullptr, E_FAIL);
|
||||
assert(aVideoFrame->AllocatedSize(kGMPYPlane) >= stride*aHeight);
|
||||
memcpy(outBuffer, data, stride*aHeight);
|
||||
|
||||
// If the buffer is bigger than the max for a 32 bit, fail to avoid buffer
|
||||
// overflows.
|
||||
if (bufferSize > UINT32_MAX) {
|
||||
return Status::kDecodeError;
|
||||
}
|
||||
outBuffer = aVideoFrame->Buffer(kGMPUPlane);
|
||||
ENSURE(outBuffer != nullptr, E_FAIL);
|
||||
assert(aVideoFrame->AllocatedSize(kGMPUPlane) >= halfStride*halfHeight);
|
||||
memcpy(outBuffer, data+y_size, halfStride*halfHeight);
|
||||
|
||||
// Get the buffer from the host.
|
||||
Buffer* buffer = mHost->Allocate(bufferSize);
|
||||
aVideoFrame->SetFrameBuffer(buffer);
|
||||
|
||||
// Make sure the buffer is non-null (allocate guarantees it will be of
|
||||
// sufficient size).
|
||||
if (!buffer) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
uint8_t* outBuffer = buffer->Data();
|
||||
|
||||
aVideoFrame->SetPlaneOffset(VideoFrame::kYPlane, 0);
|
||||
|
||||
// Offset is the size of the copied y data.
|
||||
aVideoFrame->SetPlaneOffset(VideoFrame::kUPlane, ySize);
|
||||
|
||||
// Offset is the size of the copied y data + the size of the copied u data.
|
||||
aVideoFrame->SetPlaneOffset(VideoFrame::kVPlane, ySize + uSize);
|
||||
|
||||
// Copy the data.
|
||||
memcpy(outBuffer, data, ySize + uSize * 2);
|
||||
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();
|
||||
@ -269,46 +335,84 @@ VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
|
||||
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()
|
||||
{
|
||||
CK_LOGD("VideoDecoder::Reset");
|
||||
|
||||
mIsFlushing = true;
|
||||
if (mDecoder) {
|
||||
mDecoder->Reset();
|
||||
}
|
||||
|
||||
// Remove all the frames from the output queue.
|
||||
while (!mOutputQueue.empty()) {
|
||||
mOutputQueue.pop();
|
||||
}
|
||||
// Schedule ResetComplete callback to run after existing frames have been
|
||||
// flushed out of the task queue.
|
||||
EnsureWorker();
|
||||
mWorkerThread->Post(WrapTaskRefCounted(this,
|
||||
&VideoDecoder::ResetCompleteTask));
|
||||
}
|
||||
|
||||
Status
|
||||
VideoDecoder::Drain(VideoFrame* aVideoFrame)
|
||||
void
|
||||
VideoDecoder::DrainTask()
|
||||
{
|
||||
CK_LOGD("VideoDecoder::Drain()");
|
||||
|
||||
if (!mDecoder) {
|
||||
CK_LOGD("Drain failed! Decoder was not initialized");
|
||||
return Status::kDecodeError;
|
||||
}
|
||||
|
||||
mDecoder->Drain();
|
||||
|
||||
// Return any pending output.
|
||||
return OutputFrame(aVideoFrame);
|
||||
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
|
||||
@ -316,3 +420,36 @@ VideoDecoder::DecodingComplete()
|
||||
// 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));
|
||||
}
|
||||
|
@ -18,28 +18,37 @@
|
||||
#define __VideoDecoder_h__
|
||||
|
||||
#include <atomic>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
|
||||
// This include is required in order for content_decryption_module to work
|
||||
// on Unix systems.
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "gmp-task-utils.h"
|
||||
#include "gmp-video-decode.h"
|
||||
#include "gmp-video-host.h"
|
||||
#include "WMFH264Decoder.h"
|
||||
|
||||
class VideoDecoder : public RefCounted
|
||||
#include "mfobjects.h"
|
||||
|
||||
class VideoDecoder : public GMPVideoDecoder
|
||||
, public RefCounted
|
||||
{
|
||||
public:
|
||||
explicit VideoDecoder(cdm::Host_8 *aHost);
|
||||
explicit VideoDecoder(GMPVideoHost *aHostAPI);
|
||||
|
||||
cdm::Status InitDecode(const cdm::VideoDecoderConfig& aConfig);
|
||||
virtual void InitDecode(const GMPVideoCodec& aCodecSettings,
|
||||
const uint8_t* aCodecSpecific,
|
||||
uint32_t aCodecSpecificLength,
|
||||
GMPVideoDecoderCallback* aCallback,
|
||||
int32_t aCoreCount) override;
|
||||
|
||||
cdm::Status Decode(const cdm::InputBuffer& aEncryptedBuffer,
|
||||
cdm::VideoFrame* aVideoFrame);
|
||||
virtual void Decode(GMPVideoEncodedFrame* aInputFrame,
|
||||
bool aMissingFrames,
|
||||
const uint8_t* aCodecSpecific,
|
||||
uint32_t aCodecSpecificLength,
|
||||
int64_t aRenderTimeMs = -1);
|
||||
|
||||
void Reset();
|
||||
virtual void Reset() override;
|
||||
|
||||
void DecodingComplete();
|
||||
virtual void Drain() override;
|
||||
|
||||
virtual void DecodingComplete() override;
|
||||
|
||||
bool HasShutdown() { return mHasShutdown; }
|
||||
|
||||
@ -47,26 +56,53 @@ private:
|
||||
|
||||
virtual ~VideoDecoder();
|
||||
|
||||
cdm::Status Drain(cdm::VideoFrame* aVideoFrame);
|
||||
void EnsureWorker();
|
||||
|
||||
void DrainTask();
|
||||
|
||||
struct DecodeData {
|
||||
DecodeData()
|
||||
: mTimestamp(0)
|
||||
, mDuration(0)
|
||||
, mIsKeyframe(false)
|
||||
{}
|
||||
std::vector<uint8_t> mBuffer;
|
||||
uint64_t mTimestamp = 0;
|
||||
uint64_t mTimestamp;
|
||||
uint64_t mDuration;
|
||||
bool mIsKeyframe;
|
||||
CryptoMetaData mCrypto;
|
||||
};
|
||||
|
||||
cdm::Status OutputFrame(cdm::VideoFrame* aVideoFrame);
|
||||
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,
|
||||
cdm::VideoFrame* aVideoFrame);
|
||||
GMPVideoi420Frame* aVideoFrame);
|
||||
|
||||
cdm::Host_8* mHost;
|
||||
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::queue<wmf::CComPtr<IMFSample>> mOutputQueue;
|
||||
std::vector<uint8_t> mExtraData;
|
||||
std::vector<uint8_t> mAnnexB;
|
||||
|
||||
int32_t mNumInputTasks;
|
||||
bool mSentExtraData;
|
||||
|
||||
std::atomic<bool> mIsFlushing;
|
||||
|
||||
bool mHasShutdown;
|
||||
};
|
||||
|
@ -196,6 +196,7 @@ HRESULT
|
||||
WMFH264Decoder::CreateInputSample(const uint8_t* aData,
|
||||
uint32_t aDataSize,
|
||||
Microseconds aTimestamp,
|
||||
Microseconds aDuration,
|
||||
IMFSample** aOutSample)
|
||||
{
|
||||
HRESULT hr;
|
||||
@ -230,6 +231,8 @@ WMFH264Decoder::CreateInputSample(const uint8_t* aData,
|
||||
hr = sample->SetSampleTime(UsecsToHNs(aTimestamp));
|
||||
ENSURE(SUCCEEDED(hr), hr);
|
||||
|
||||
sample->SetSampleDuration(UsecsToHNs(aDuration));
|
||||
|
||||
*aOutSample = sample.Detach();
|
||||
|
||||
return S_OK;
|
||||
@ -298,11 +301,12 @@ WMFH264Decoder::GetOutputSample(IMFSample** aOutSample)
|
||||
HRESULT
|
||||
WMFH264Decoder::Input(const uint8_t* aData,
|
||||
uint32_t aDataSize,
|
||||
Microseconds aTimestamp)
|
||||
Microseconds aTimestamp,
|
||||
Microseconds aDuration)
|
||||
{
|
||||
HRESULT hr;
|
||||
CComPtr<IMFSample> input = nullptr;
|
||||
hr = CreateInputSample(aData, aDataSize, aTimestamp, &input);
|
||||
hr = CreateInputSample(aData, aDataSize, aTimestamp, aDuration, &input);
|
||||
ENSURE(SUCCEEDED(hr) && input!=nullptr, hr);
|
||||
|
||||
hr = mDecoder->ProcessInput(0, input, 0);
|
||||
|
@ -30,7 +30,8 @@ public:
|
||||
|
||||
HRESULT Input(const uint8_t* aData,
|
||||
uint32_t aDataSize,
|
||||
Microseconds aTimestamp);
|
||||
Microseconds aTimestamp,
|
||||
Microseconds aDuration);
|
||||
|
||||
HRESULT Output(IMFSample** aOutput);
|
||||
|
||||
@ -52,6 +53,7 @@ private:
|
||||
HRESULT CreateInputSample(const uint8_t* aData,
|
||||
uint32_t aDataSize,
|
||||
Microseconds aTimestamp,
|
||||
Microseconds aDuration,
|
||||
IMFSample** aOutSample);
|
||||
|
||||
HRESULT CreateOutputSample(IMFSample** aOutSample);
|
||||
|
@ -119,8 +119,8 @@ typedef int64_t Microseconds;
|
||||
#define ENSURE(condition, ret) \
|
||||
{ if (!(condition)) { LOG("##condition## FAILED %S:%d\n", __FILE__, __LINE__); return ret; } }
|
||||
|
||||
#define STATUS_SUCCEEDED(x) ((x) == Status::kSuccess)
|
||||
#define STATUS_FAILED(x) ((x) != Status::kSuccess)
|
||||
#define GMP_SUCCEEDED(x) ((x) == GMPNoErr)
|
||||
#define GMP_FAILED(x) ((x) != GMPNoErr)
|
||||
|
||||
#define MFPLAT_FUNC(_func, _dllname) \
|
||||
extern decltype(::_func)* _func;
|
||||
|
@ -18,47 +18,68 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ClearKeyCDM.h"
|
||||
#include "ClearKeyAsyncShutdown.h"
|
||||
#include "ClearKeySessionManager.h"
|
||||
// This include is required in order for content_decryption_module to work
|
||||
// on Unix systems.
|
||||
#include "stddef.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "gmp-api/gmp-async-shutdown.h"
|
||||
#include "gmp-api/gmp-decryption.h"
|
||||
#include "gmp-api/gmp-platform.h"
|
||||
|
||||
#ifdef ENABLE_WMF
|
||||
#if defined(ENABLE_WMF)
|
||||
#include "WMFUtils.h"
|
||||
#endif // ENABLE_WMF
|
||||
#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" {
|
||||
|
||||
CDM_EXPORT
|
||||
void INITIALIZE_CDM_MODULE() {
|
||||
|
||||
GMP_EXPORT GMPErr
|
||||
GMPInit(GMPPlatformAPI* aPlatformAPI)
|
||||
{
|
||||
sPlatform = aPlatformAPI;
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
CDM_EXPORT
|
||||
void* CreateCdmInstance(int cdm_interface_version,
|
||||
const char* key_system,
|
||||
uint32_t key_system_size,
|
||||
GetCdmHostFunc get_cdm_host_func,
|
||||
void* user_data)
|
||||
GMP_EXPORT GMPErr
|
||||
GMPGetAPI(const char* aApiName, void* aHostAPI, void** aPluginAPI)
|
||||
{
|
||||
CK_LOGD("ClearKey GMPGetAPI |%s|", aApiName);
|
||||
assert(!*aPluginAPI);
|
||||
|
||||
CK_LOGE("ClearKey CreateCDMInstance");
|
||||
|
||||
#ifdef ENABLE_WMF
|
||||
if (!wmf::EnsureLibs()) {
|
||||
CK_LOGE("Required libraries were not found");
|
||||
return nullptr;
|
||||
if (!strcmp(aApiName, GMP_API_DECRYPTOR)) {
|
||||
*aPluginAPI = new ClearKeySessionManager();
|
||||
}
|
||||
#if defined(ENABLE_WMF)
|
||||
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);
|
||||
}
|
||||
|
||||
cdm::Host_8* host = static_cast<cdm::Host_8*>(
|
||||
get_cdm_host_func(cdm_interface_version, user_data));
|
||||
ClearKeyCDM* clearKey = new ClearKeyCDM(host);
|
||||
|
||||
CK_LOGE("Created ClearKeyCDM instance!");
|
||||
|
||||
return clearKey;
|
||||
return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
|
||||
}
|
||||
|
||||
GMP_EXPORT GMPErr
|
||||
GMPShutdown(void)
|
||||
{
|
||||
CK_LOGD("ClearKey GMPShutdown");
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
}
|
||||
|
1938
media/gmp-clearkey/0.1/gmp-task-utils-generated.h
Normal file
1938
media/gmp-clearkey/0.1/gmp-task-utils-generated.h
Normal file
File diff suppressed because it is too large
Load Diff
47
media/gmp-clearkey/0.1/gmp-task-utils.h
Normal file
47
media/gmp-clearkey/0.1/gmp-task-utils.h
Normal 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_
|
@ -11,8 +11,8 @@ FINAL_TARGET = 'dist/bin/gmp-clearkey/0.1'
|
||||
FINAL_TARGET_PP_FILES += ['manifest.json.in']
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'ClearKeyAsyncShutdown.cpp',
|
||||
'ClearKeyBase64.cpp',
|
||||
'ClearKeyCDM.cpp',
|
||||
'ClearKeyDecryptionManager.cpp',
|
||||
'ClearKeyPersistence.cpp',
|
||||
'ClearKeySession.cpp',
|
||||
@ -28,6 +28,7 @@ SOURCES += [
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
UNIFIED_SOURCES += [
|
||||
'AnnexB.cpp',
|
||||
'VideoDecoder.cpp',
|
||||
'WMFH264Decoder.cpp',
|
||||
]
|
||||
@ -42,13 +43,15 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
|
||||
DEFINES['ENABLE_WMF'] = True
|
||||
|
||||
|
||||
DEFINES['CDM_IMPLEMENTATION'] = True
|
||||
|
||||
TEST_DIRS += [
|
||||
'gtest',
|
||||
]
|
||||
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/media/gmp',
|
||||
]
|
||||
|
||||
DISABLE_STL_WRAPPING = True
|
||||
DEFINES['MOZ_NO_MOZALLOC'] = True
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user