summaryrefslogtreecommitdiffstats
path: root/media/gmp-clearkey
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /media/gmp-clearkey
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'media/gmp-clearkey')
-rw-r--r--media/gmp-clearkey/0.1/ArrayUtils.h22
-rw-r--r--media/gmp-clearkey/0.1/BigEndian.h59
-rw-r--r--media/gmp-clearkey/0.1/ClearKeyBase64.cpp90
-rw-r--r--media/gmp-clearkey/0.1/ClearKeyBase64.h29
-rw-r--r--media/gmp-clearkey/0.1/ClearKeyCDM.cpp188
-rw-r--r--media/gmp-clearkey/0.1/ClearKeyCDM.h103
-rw-r--r--media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp289
-rw-r--r--media/gmp-clearkey/0.1/ClearKeyDecryptionManager.h111
-rw-r--r--media/gmp-clearkey/0.1/ClearKeyPersistence.cpp153
-rw-r--r--media/gmp-clearkey/0.1/ClearKeyPersistence.h64
-rw-r--r--media/gmp-clearkey/0.1/ClearKeySession.cpp67
-rw-r--r--media/gmp-clearkey/0.1/ClearKeySession.h56
-rw-r--r--media/gmp-clearkey/0.1/ClearKeySessionManager.cpp713
-rw-r--r--media/gmp-clearkey/0.1/ClearKeySessionManager.h138
-rw-r--r--media/gmp-clearkey/0.1/ClearKeyStorage.cpp194
-rw-r--r--media/gmp-clearkey/0.1/ClearKeyStorage.h42
-rw-r--r--media/gmp-clearkey/0.1/ClearKeyUtils.cpp657
-rw-r--r--media/gmp-clearkey/0.1/ClearKeyUtils.h107
-rw-r--r--media/gmp-clearkey/0.1/RefCounted.h85
-rw-r--r--media/gmp-clearkey/0.1/VideoDecoder.cpp324
-rw-r--r--media/gmp-clearkey/0.1/VideoDecoder.h72
-rw-r--r--media/gmp-clearkey/0.1/WMFH264Decoder.cpp331
-rw-r--r--media/gmp-clearkey/0.1/WMFH264Decoder.h70
-rw-r--r--media/gmp-clearkey/0.1/WMFSymbols.h20
-rw-r--r--media/gmp-clearkey/0.1/WMFUtils.cpp223
-rw-r--r--media/gmp-clearkey/0.1/WMFUtils.h243
-rw-r--r--media/gmp-clearkey/0.1/gmp-clearkey.cpp173
-rw-r--r--media/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp81
-rw-r--r--media/gmp-clearkey/0.1/gtest/moz.build15
-rw-r--r--media/gmp-clearkey/0.1/manifest.json.in13
-rw-r--r--media/gmp-clearkey/0.1/moz.build79
31 files changed, 4811 insertions, 0 deletions
diff --git a/media/gmp-clearkey/0.1/ArrayUtils.h b/media/gmp-clearkey/0.1/ArrayUtils.h
new file mode 100644
index 0000000000..ae5f33b68e
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ArrayUtils.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ArrayUtils_h__
+#define __ArrayUtils_h__
+
+#define MOZ_ARRAY_LENGTH(array_) (sizeof(array_) / sizeof(array_[0]))
+
+#endif
diff --git a/media/gmp-clearkey/0.1/BigEndian.h b/media/gmp-clearkey/0.1/BigEndian.h
new file mode 100644
index 0000000000..5ea1b6042f
--- /dev/null
+++ b/media/gmp-clearkey/0.1/BigEndian.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __BigEndian_h__
+#define __BigEndian_h__
+
+#include <stdint.h>
+
+namespace mozilla {
+
+class BigEndian {
+ public:
+ static uint32_t readUint32(const void* aPtr) {
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(aPtr);
+ return uint32_t(p[0]) << 24 | uint32_t(p[1]) << 16 | uint32_t(p[2]) << 8 |
+ uint32_t(p[3]);
+ }
+
+ static uint16_t readUint16(const void* aPtr) {
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(aPtr);
+ return uint32_t(p[0]) << 8 | uint32_t(p[1]);
+ }
+
+ static uint64_t readUint64(const void* aPtr) {
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(aPtr);
+ return uint64_t(p[0]) << 56 | uint64_t(p[1]) << 48 | uint64_t(p[2]) << 40 |
+ uint64_t(p[3]) << 32 | uint64_t(p[4]) << 24 | uint64_t(p[5]) << 16 |
+ uint64_t(p[6]) << 8 | uint64_t(p[7]);
+ }
+
+ static void writeUint64(void* aPtr, uint64_t aValue) {
+ uint8_t* p = reinterpret_cast<uint8_t*>(aPtr);
+ p[0] = uint8_t(aValue >> 56) & 0xff;
+ p[1] = uint8_t(aValue >> 48) & 0xff;
+ p[2] = uint8_t(aValue >> 40) & 0xff;
+ p[3] = uint8_t(aValue >> 32) & 0xff;
+ p[4] = uint8_t(aValue >> 24) & 0xff;
+ p[5] = uint8_t(aValue >> 16) & 0xff;
+ p[6] = uint8_t(aValue >> 8) & 0xff;
+ p[7] = uint8_t(aValue) & 0xff;
+ }
+};
+
+} // namespace mozilla
+
+#endif // __BigEndian_h__
diff --git a/media/gmp-clearkey/0.1/ClearKeyBase64.cpp b/media/gmp-clearkey/0.1/ClearKeyBase64.cpp
new file mode 100644
index 0000000000..bb610afd1b
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyBase64.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ClearKeyBase64.h"
+
+#include <algorithm>
+
+using std::string;
+using std::vector;
+
+/**
+ * Take a base64-encoded string, convert (in-place) each character to its
+ * corresponding value in the [0x00, 0x3f] range, and truncate any padding.
+ */
+static bool Decode6Bit(string& aStr) {
+ for (size_t i = 0; i < aStr.length(); i++) {
+ if (aStr[i] >= 'A' && aStr[i] <= 'Z') {
+ aStr[i] -= 'A';
+ } else if (aStr[i] >= 'a' && aStr[i] <= 'z') {
+ aStr[i] -= 'a' - 26;
+ } else if (aStr[i] >= '0' && aStr[i] <= '9') {
+ aStr[i] -= '0' - 52;
+ } else if (aStr[i] == '-' || aStr[i] == '+') {
+ aStr[i] = 62;
+ } else if (aStr[i] == '_' || aStr[i] == '/') {
+ aStr[i] = 63;
+ } else {
+ // Truncate '=' padding at the end of the aString.
+ if (aStr[i] != '=') {
+ aStr.erase(i, string::npos);
+ return false;
+ }
+ aStr[i] = '\0';
+ aStr.resize(i);
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool DecodeBase64(const string& aEncoded, vector<uint8_t>& aOutDecoded) {
+ if (aEncoded.empty()) {
+ aOutDecoded.clear();
+ return true;
+ }
+ if (aEncoded.size() == 1) {
+ // Invalid Base64 encoding.
+ return false;
+ }
+ string encoded = aEncoded;
+ if (!Decode6Bit(encoded)) {
+ return false;
+ }
+
+ // The number of bytes we haven't yet filled in the current byte, mod 8.
+ int shift = 0;
+
+ aOutDecoded.resize((encoded.size() * 3) / 4);
+ vector<uint8_t>::iterator out = aOutDecoded.begin();
+ for (size_t i = 0; i < encoded.length(); i++) {
+ if (!shift) {
+ *out = encoded[i] << 2;
+ } else {
+ *out |= encoded[i] >> (6 - shift);
+ out++;
+ if (out == aOutDecoded.end()) {
+ // Hit last 6bit octed in encoded, which is padding and can be ignored.
+ break;
+ }
+ *out = encoded[i] << (shift + 2);
+ }
+ shift = (shift + 2) % 8;
+ }
+
+ return true;
+}
diff --git a/media/gmp-clearkey/0.1/ClearKeyBase64.h b/media/gmp-clearkey/0.1/ClearKeyBase64.h
new file mode 100644
index 0000000000..39309c031e
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyBase64.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ClearKeyBase64_h__
+#define __ClearKeyBase64_h__
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+// Decodes a base64 encoded string. Returns true on success.
+bool DecodeBase64(const std::string& aEncoded,
+ std::vector<uint8_t>& aOutDecoded);
+
+#endif
diff --git a/media/gmp-clearkey/0.1/ClearKeyCDM.cpp b/media/gmp-clearkey/0.1/ClearKeyCDM.cpp
new file mode 100644
index 0000000000..8f1f3655ec
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyCDM.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ClearKeyCDM.h"
+
+#include "ClearKeyUtils.h"
+
+using namespace cdm;
+
+ClearKeyCDM::ClearKeyCDM(Host_10* aHost) {
+ mHost = aHost;
+ mSessionManager = new ClearKeySessionManager(mHost);
+}
+
+void ClearKeyCDM::Initialize(bool aAllowDistinctiveIdentifier,
+ bool aAllowPersistentState,
+ bool aUseHardwareSecureCodecs) {
+ mSessionManager->Init(aAllowDistinctiveIdentifier, aAllowPersistentState);
+ // We call mHost->OnInitialized() in the session manager once it has
+ // initialized.
+}
+
+void ClearKeyCDM::GetStatusForPolicy(uint32_t aPromiseId,
+ const Policy& aPolicy) {
+ // MediaKeys::GetStatusForPolicy checks the keysystem and
+ // reject the promise with NS_ERROR_DOM_NOT_SUPPORTED_ERR without calling CDM.
+ // This function should never be called and is not supported.
+ assert(false);
+}
+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_2& aEncryptedBuffer,
+ DecryptedBlock* aDecryptedBuffer) {
+ if (mIsProtectionQueryEnabled) {
+ // Piggyback this check onto Decrypt calls. If Clearkey implements a timer
+ // based approach for firing events, we could instead trigger the check
+ // using that mechanism.
+ mSessionManager->QueryOutputProtectionStatusIfNeeded();
+ }
+ return mSessionManager->Decrypt(aEncryptedBuffer, aDecryptedBuffer);
+}
+
+Status ClearKeyCDM::InitializeAudioDecoder(
+ const AudioDecoderConfig_2& 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_2& aVideoDecoderConfig) {
+#ifdef ENABLE_WMF
+ mVideoDecoder = new VideoDecoder(mHost);
+ return mVideoDecoder->InitDecode(aVideoDecoderConfig);
+#else
+ return Status::kDecodeError;
+#endif
+}
+
+void ClearKeyCDM::DeinitializeDecoder(StreamType aDecoderType) {
+#ifdef ENABLE_WMF
+ if (aDecoderType == StreamType::kStreamTypeVideo) {
+ mVideoDecoder->DecodingComplete();
+ mVideoDecoder = nullptr;
+ }
+#endif
+}
+
+void ClearKeyCDM::ResetDecoder(StreamType aDecoderType) {
+#ifdef ENABLE_WMF
+ if (aDecoderType == StreamType::kStreamTypeVideo) {
+ mVideoDecoder->Reset();
+ }
+#endif
+}
+
+Status ClearKeyCDM::DecryptAndDecodeFrame(const InputBuffer_2& aEncryptedBuffer,
+ VideoFrame* aVideoFrame) {
+#ifdef ENABLE_WMF
+ if (mIsProtectionQueryEnabled) {
+ // Piggyback this check onto Decrypt + Decode. If Clearkey implements a
+ // timer based approach for firing events, we could instead trigger the
+ // check using that mechanism.
+ mSessionManager->QueryOutputProtectionStatusIfNeeded();
+ }
+ return mVideoDecoder->Decode(aEncryptedBuffer, aVideoFrame);
+#else
+ return Status::kDecodeError;
+#endif
+}
+
+Status ClearKeyCDM::DecryptAndDecodeSamples(
+ const InputBuffer_2& 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) {
+ // The higher level GMP machinery should not forward us query information
+ // unless we've requested it (even if mutiple CDMs exist at once and some
+ // others are reqeusting this info). If this assert fires we're violating
+ // that.
+ MOZ_ASSERT(mIsProtectionQueryEnabled,
+ "Should only receive a protection status "
+ "mIsProtectionQueryEnabled is true");
+ // The session manager handles the guts of this for ClearKey.
+ mSessionManager->OnQueryOutputProtectionStatus(aResult, aLinkMask,
+ aOutputProtectionMask);
+}
+
+void ClearKeyCDM::OnStorageId(uint32_t aVersion, const uint8_t* aStorageId,
+ uint32_t aStorageIdSize) {
+ // This function should never be called and is not supported.
+ assert(false);
+}
+
+void ClearKeyCDM::Destroy() {
+ mSessionManager->DecryptingComplete();
+#ifdef ENABLE_WMF
+ // If we have called 'DeinitializeDecoder' mVideoDecoder will be null.
+ if (mVideoDecoder) {
+ mVideoDecoder->DecodingComplete();
+ }
+#endif
+ delete this;
+}
+
+void ClearKeyCDM::EnableProtectionQuery() {
+ MOZ_ASSERT(!mIsProtectionQueryEnabled,
+ "Should not be called more than once per CDM");
+ mIsProtectionQueryEnabled = true;
+}
diff --git a/media/gmp-clearkey/0.1/ClearKeyCDM.h b/media/gmp-clearkey/0.1/ClearKeyCDM.h
new file mode 100644
index 0000000000..6977b0e787
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyCDM.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ClearKeyCDM_h_
+#define ClearKeyCDM_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 "ClearKeySessionManager.h"
+
+#ifdef ENABLE_WMF
+# include "VideoDecoder.h"
+# include "WMFUtils.h"
+#endif
+
+class ClearKeyCDM : public cdm::ContentDecryptionModule_10 {
+ private:
+ RefPtr<ClearKeySessionManager> mSessionManager;
+#ifdef ENABLE_WMF
+ RefPtr<VideoDecoder> mVideoDecoder;
+#endif
+ bool mIsProtectionQueryEnabled = false;
+
+ protected:
+ cdm::Host_10* mHost;
+
+ public:
+ explicit ClearKeyCDM(cdm::Host_10* aHost);
+
+ void Initialize(bool aAllowDistinctiveIdentifier, bool aAllowPersistentState,
+ bool aUseHardwareSecureCodecs) override;
+
+ void GetStatusForPolicy(uint32_t aPromiseId,
+ const cdm::Policy& aPolicy) 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_2& aEncryptedBuffer,
+ cdm::DecryptedBlock* aDecryptedBuffer) override;
+
+ cdm::Status InitializeAudioDecoder(
+ const cdm::AudioDecoderConfig_2& aAudioDecoderConfig) override;
+
+ cdm::Status InitializeVideoDecoder(
+ const cdm::VideoDecoderConfig_2& aVideoDecoderConfig) override;
+
+ void DeinitializeDecoder(cdm::StreamType aDecoderType) override;
+
+ void ResetDecoder(cdm::StreamType aDecoderType) override;
+
+ cdm::Status DecryptAndDecodeFrame(const cdm::InputBuffer_2& aEncryptedBuffer,
+ cdm::VideoFrame* aVideoFrame) override;
+
+ cdm::Status DecryptAndDecodeSamples(
+ const cdm::InputBuffer_2& 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 OnStorageId(uint32_t aVersion, const uint8_t* aStorageId,
+ uint32_t aStorageIdSize) override;
+
+ void Destroy() override;
+
+ void EnableProtectionQuery();
+};
+
+#endif
diff --git a/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp b/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
new file mode 100644
index 0000000000..532ee738ea
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
@@ -0,0 +1,289 @@
+/*
+ * 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 "ClearKeyDecryptionManager.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include <vector>
+#include <algorithm>
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Span.h"
+#include "psshparser/PsshParser.h"
+
+using namespace cdm;
+
+bool AllZero(const std::vector<uint32_t>& aBytes) {
+ return all_of(aBytes.begin(), aBytes.end(),
+ [](uint32_t b) { return b == 0; });
+}
+
+class ClearKeyDecryptor : public RefCounted {
+ public:
+ ClearKeyDecryptor();
+
+ void InitKey(const Key& aKey);
+ bool HasKey() const { return !mKey.empty(); }
+
+ Status Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
+ const CryptoMetaData& aMetadata);
+
+ const Key& DecryptionKey() const { return mKey; }
+
+ private:
+ ~ClearKeyDecryptor();
+
+ Key mKey;
+};
+
+/* static */
+ClearKeyDecryptionManager* ClearKeyDecryptionManager::sInstance = nullptr;
+
+/* static */
+ClearKeyDecryptionManager* ClearKeyDecryptionManager::Get() {
+ if (!sInstance) {
+ sInstance = new ClearKeyDecryptionManager();
+ }
+ return sInstance;
+}
+
+ClearKeyDecryptionManager::ClearKeyDecryptionManager() {
+ CK_LOGD("ClearKeyDecryptionManager::ClearKeyDecryptionManager");
+}
+
+ClearKeyDecryptionManager::~ClearKeyDecryptionManager() {
+ CK_LOGD("ClearKeyDecryptionManager::~ClearKeyDecryptionManager");
+
+ sInstance = nullptr;
+
+ for (auto it = mDecryptors.begin(); it != mDecryptors.end(); it++) {
+ it->second->Release();
+ }
+ mDecryptors.clear();
+}
+
+bool ClearKeyDecryptionManager::HasSeenKeyId(const KeyId& aKeyId) const {
+ CK_LOGD("ClearKeyDecryptionManager::SeenKeyId %s",
+ mDecryptors.find(aKeyId) != mDecryptors.end() ? "t" : "f");
+ return mDecryptors.find(aKeyId) != mDecryptors.end();
+}
+
+bool ClearKeyDecryptionManager::IsExpectingKeyForKeyId(
+ const KeyId& aKeyId) const {
+ CK_LOGARRAY("ClearKeyDecryptionManager::IsExpectingKeyForId ", aKeyId.data(),
+ aKeyId.size());
+ const auto& decryptor = mDecryptors.find(aKeyId);
+ return decryptor != mDecryptors.end() && !decryptor->second->HasKey();
+}
+
+bool ClearKeyDecryptionManager::HasKeyForKeyId(const KeyId& aKeyId) const {
+ CK_LOGD("ClearKeyDecryptionManager::HasKeyForKeyId");
+ const auto& decryptor = mDecryptors.find(aKeyId);
+ return decryptor != mDecryptors.end() && decryptor->second->HasKey();
+}
+
+const Key& ClearKeyDecryptionManager::GetDecryptionKey(const KeyId& aKeyId) {
+ assert(HasKeyForKeyId(aKeyId));
+ return mDecryptors[aKeyId]->DecryptionKey();
+}
+
+void ClearKeyDecryptionManager::InitKey(KeyId aKeyId, Key aKey) {
+ CK_LOGD("ClearKeyDecryptionManager::InitKey ", aKeyId.data(), aKeyId.size());
+ 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());
+ if (!HasSeenKeyId(aKeyId)) {
+ mDecryptors[aKeyId] = new ClearKeyDecryptor();
+ }
+ mDecryptors[aKeyId]->AddRef();
+}
+
+void ClearKeyDecryptionManager::ReleaseKeyId(KeyId aKeyId) {
+ CK_LOGD("ClearKeyDecryptionManager::ReleaseKeyId");
+ assert(HasSeenKeyId(aKeyId));
+
+ ClearKeyDecryptor* decryptor = mDecryptors[aKeyId];
+ if (!decryptor->Release()) {
+ mDecryptors.erase(aKeyId);
+ }
+}
+
+Status ClearKeyDecryptionManager::Decrypt(std::vector<uint8_t>& aBuffer,
+ const CryptoMetaData& aMetadata) {
+ return Decrypt(&aBuffer[0], aBuffer.size(), aMetadata);
+}
+
+Status 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;
+ }
+
+ CK_LOGARRAY("Found decryptor for keyId: ", aMetadata.mKeyId.data(),
+ aMetadata.mKeyId.size());
+ return mDecryptors[aMetadata.mKeyId]->Decrypt(aBuffer, aBufferSize,
+ aMetadata);
+}
+
+ClearKeyDecryptor::ClearKeyDecryptor() { CK_LOGD("ClearKeyDecryptor ctor"); }
+
+ClearKeyDecryptor::~ClearKeyDecryptor() {
+ if (HasKey()) {
+ CK_LOGARRAY("ClearKeyDecryptor dtor; key = ", mKey.data(), mKey.size());
+ } else {
+ CK_LOGD("ClearKeyDecryptor dtor");
+ }
+}
+
+void ClearKeyDecryptor::InitKey(const Key& aKey) { mKey = aKey; }
+
+Status ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
+ const CryptoMetaData& aMetadata) {
+ CK_LOGD("ClearKeyDecryptor::Decrypt");
+ // If the sample is split up into multiple encrypted subsamples, we need to
+ // stitch them into one continuous buffer for decryption.
+ std::vector<uint8_t> tmp(aBufferSize);
+ static_assert(sizeof(uintptr_t) == sizeof(uint8_t*),
+ "We need uintptr_t to be exactly the same size as a pointer");
+
+ // Decrypt CBCS case:
+ if (aMetadata.mEncryptionScheme == EncryptionScheme::kCbcs) {
+ mozilla::CheckedInt<uintptr_t> data = reinterpret_cast<uintptr_t>(aBuffer);
+ if (!data.isValid()) {
+ return Status::kDecryptError;
+ }
+ const uintptr_t endBuffer =
+ reinterpret_cast<uintptr_t>(aBuffer + aBufferSize);
+
+ if (aMetadata.NumSubsamples() == 0) {
+ if (data.value() > endBuffer) {
+ return Status::kDecryptError;
+ }
+ mozilla::Span<uint8_t> encryptedSpan =
+ mozilla::Span(reinterpret_cast<uint8_t*>(data.value()), aBufferSize);
+ if (!ClearKeyUtils::DecryptCbcs(mKey, aMetadata.mIV, encryptedSpan,
+ aMetadata.mCryptByteBlock,
+ aMetadata.mSkipByteBlock)) {
+ return Status::kDecryptError;
+ }
+ return Status::kSuccess;
+ }
+
+ for (size_t i = 0; i < aMetadata.NumSubsamples(); i++) {
+ data += aMetadata.mClearBytes[i];
+ if (!data.isValid() || data.value() > endBuffer) {
+ return Status::kDecryptError;
+ }
+ mozilla::CheckedInt<uintptr_t> dataAfterCipher =
+ data + aMetadata.mCipherBytes[i];
+ if (!dataAfterCipher.isValid() || dataAfterCipher.value() > endBuffer) {
+ // Trying to read past the end of the buffer!
+ return Status::kDecryptError;
+ }
+ mozilla::Span<uint8_t> encryptedSpan = mozilla::Span(
+ reinterpret_cast<uint8_t*>(data.value()), aMetadata.mCipherBytes[i]);
+ if (!ClearKeyUtils::DecryptCbcs(mKey, aMetadata.mIV, encryptedSpan,
+ aMetadata.mCryptByteBlock,
+ aMetadata.mSkipByteBlock)) {
+ return Status::kDecryptError;
+ }
+ data += aMetadata.mCipherBytes[i];
+ if (!data.isValid()) {
+ return Status::kDecryptError;
+ }
+ return Status::kSuccess;
+ }
+ }
+
+ // Decrypt CENC case:
+ if (aMetadata.NumSubsamples()) {
+ // Take all encrypted parts of subsamples and stitch them into one
+ // continuous encrypted buffer.
+ mozilla::CheckedInt<uintptr_t> data = reinterpret_cast<uintptr_t>(aBuffer);
+ const uintptr_t endBuffer =
+ reinterpret_cast<uintptr_t>(aBuffer + aBufferSize);
+ uint8_t* iter = &tmp[0];
+ for (size_t i = 0; i < aMetadata.NumSubsamples(); i++) {
+ data += aMetadata.mClearBytes[i];
+ if (!data.isValid() || data.value() > endBuffer) {
+ // Trying to read past the end of the buffer!
+ return Status::kDecryptError;
+ }
+ const uint32_t& cipherBytes = aMetadata.mCipherBytes[i];
+ mozilla::CheckedInt<uintptr_t> dataAfterCipher = data + cipherBytes;
+ if (!dataAfterCipher.isValid() || dataAfterCipher.value() > endBuffer) {
+ // Trying to read past the end of the buffer!
+ return Status::kDecryptError;
+ }
+
+ memcpy(iter, reinterpret_cast<uint8_t*>(data.value()), cipherBytes);
+
+ data = dataAfterCipher;
+ iter += cipherBytes;
+ }
+
+ tmp.resize((size_t)(iter - &tmp[0]));
+ } else {
+ memcpy(&tmp[0], aBuffer, aBufferSize);
+ }
+
+ // It is possible that we could be passed an unencrypted sample, if all
+ // encrypted sample lengths are zero, and in this case, a zero length
+ // IV is allowed.
+ assert(aMetadata.mIV.size() == 8 || aMetadata.mIV.size() == 16 ||
+ (aMetadata.mIV.empty() && AllZero(aMetadata.mCipherBytes)));
+
+ std::vector<uint8_t> iv(aMetadata.mIV);
+ iv.insert(iv.end(), CENC_KEY_LEN - aMetadata.mIV.size(), 0);
+
+ if (!ClearKeyUtils::DecryptAES(mKey, tmp, iv)) {
+ return Status::kDecryptError;
+ }
+
+ if (aMetadata.NumSubsamples()) {
+ // Take the decrypted buffer, split up into subsamples, and insert those
+ // subsamples back into their original position in the original buffer.
+ uint8_t* data = aBuffer;
+ uint8_t* iter = &tmp[0];
+ for (size_t i = 0; i < aMetadata.NumSubsamples(); i++) {
+ data += aMetadata.mClearBytes[i];
+ uint32_t cipherBytes = aMetadata.mCipherBytes[i];
+
+ memcpy(data, iter, cipherBytes);
+
+ data += cipherBytes;
+ iter += cipherBytes;
+ }
+ } else {
+ memcpy(aBuffer, &tmp[0], aBufferSize);
+ }
+
+ return Status::kSuccess;
+}
diff --git a/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.h b/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.h
new file mode 100644
index 0000000000..c21ff3119a
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ClearKeyDecryptionManager_h__
+#define __ClearKeyDecryptionManager_h__
+
+// This include is required in order for content_decryption_module to work
+// on Unix systems.
+#include <stddef.h>
+
+#include <map>
+
+#include "content_decryption_module.h"
+
+#include "ClearKeyUtils.h"
+#include "RefCounted.h"
+
+class ClearKeyDecryptor;
+
+class CryptoMetaData {
+ public:
+ CryptoMetaData() = default;
+
+ explicit CryptoMetaData(const cdm::InputBuffer_2* aInputBuffer) {
+ Init(aInputBuffer);
+ }
+
+ void Init(const cdm::InputBuffer_2* aInputBuffer) {
+ if (!aInputBuffer) {
+ assert(!IsValid());
+ return;
+ }
+
+ mEncryptionScheme = aInputBuffer->encryption_scheme;
+ Assign(mKeyId, aInputBuffer->key_id, aInputBuffer->key_id_size);
+ Assign(mIV, aInputBuffer->iv, aInputBuffer->iv_size);
+ mCryptByteBlock = aInputBuffer->pattern.crypt_byte_block;
+ mSkipByteBlock = aInputBuffer->pattern.skip_byte_block;
+
+ for (uint32_t i = 0; i < aInputBuffer->num_subsamples; ++i) {
+ const cdm::SubsampleEntry& subsample = aInputBuffer->subsamples[i];
+ mClearBytes.push_back(subsample.clear_bytes);
+ mCipherBytes.push_back(subsample.cipher_bytes);
+ }
+ }
+
+ bool IsValid() const {
+ return !mKeyId.empty() && !mIV.empty() && !mCipherBytes.empty() &&
+ !mClearBytes.empty();
+ }
+
+ size_t NumSubsamples() const {
+ assert(mClearBytes.size() == mCipherBytes.size());
+ return mClearBytes.size();
+ }
+
+ cdm::EncryptionScheme mEncryptionScheme;
+ std::vector<uint8_t> mKeyId;
+ std::vector<uint8_t> mIV;
+ uint32_t mCryptByteBlock;
+ uint32_t mSkipByteBlock;
+ std::vector<uint32_t> mClearBytes;
+ std::vector<uint32_t> mCipherBytes;
+};
+
+class ClearKeyDecryptionManager : public RefCounted {
+ private:
+ ClearKeyDecryptionManager();
+ ~ClearKeyDecryptionManager();
+
+ static ClearKeyDecryptionManager* sInstance;
+
+ public:
+ static ClearKeyDecryptionManager* Get();
+
+ bool HasSeenKeyId(const KeyId& aKeyId) const;
+ bool HasKeyForKeyId(const KeyId& aKeyId) const;
+
+ const Key& GetDecryptionKey(const KeyId& aKeyId);
+
+ // Create a decryptor for the given KeyId if one does not already exist.
+ void InitKey(KeyId aKeyId, Key aKey);
+ void ExpectKeyId(KeyId aKeyId);
+ void ReleaseKeyId(KeyId aKeyId);
+
+ // Decrypts buffer *in place*.
+ cdm::Status Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
+ const CryptoMetaData& aMetadata);
+ cdm::Status Decrypt(std::vector<uint8_t>& aBuffer,
+ const CryptoMetaData& aMetadata);
+
+ private:
+ bool IsExpectingKeyForKeyId(const KeyId& aKeyId) const;
+
+ std::map<KeyId, ClearKeyDecryptor*> mDecryptors;
+};
+
+#endif // __ClearKeyDecryptionManager_h__
diff --git a/media/gmp-clearkey/0.1/ClearKeyPersistence.cpp b/media/gmp-clearkey/0.1/ClearKeyPersistence.cpp
new file mode 100644
index 0000000000..0b81f81399
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyPersistence.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ClearKeyPersistence.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <sstream>
+
+#include "ClearKeySessionManager.h"
+#include "ClearKeyStorage.h"
+#include "ClearKeyUtils.h"
+#include "RefCounted.h"
+
+using namespace cdm;
+
+using std::function;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+void ClearKeyPersistence::ReadAllRecordsFromIndex(
+ function<void()>&& aOnComplete) {
+ // Clear what we think the index file contains, we're about to read it again.
+ mPersistentSessionIds.clear();
+
+ // 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;
+
+ 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()));
+ }
+ }
+
+ 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, std::move(onIndexSuccess),
+ std::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';
+ }
+
+ 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, std::move(onIndexSuccess),
+ std::move(onIndexFail));
+}
+
+ClearKeyPersistence::ClearKeyPersistence(Host_10* aHost) {
+ this->mHost = aHost;
+}
+
+void ClearKeyPersistence::EnsureInitialized(bool aPersistentStateAllowed,
+ function<void()>&& aOnInitialized) {
+ if (aPersistentStateAllowed &&
+ mPersistentKeyState == PersistentKeyState::UNINITIALIZED) {
+ mPersistentKeyState = LOADING;
+ ReadAllRecordsFromIndex(std::move(aOnInitialized));
+ } else {
+ mPersistentKeyState = PersistentKeyState::LOADED;
+ aOnInitialized();
+ }
+}
+
+bool ClearKeyPersistence::IsLoaded() const {
+ return mPersistentKeyState == PersistentKeyState::LOADED;
+}
+
+string ClearKeyPersistence::GetNewSessionId(SessionType aSessionType) {
+ static uint32_t sNextSessionId = 1;
+
+ // Ensure we don't re-use a session id that was persisted.
+ while (Contains(mPersistentSessionIds, sNextSessionId)) {
+ sNextSessionId++;
+ }
+
+ string sessionId;
+ stringstream ss;
+ ss << sNextSessionId;
+ ss >> sessionId;
+
+ if (aSessionType == SessionType::kPersistentLicense) {
+ mPersistentSessionIds.insert(sNextSessionId);
+
+ // Save the updated index file.
+ WriteIndex();
+ }
+
+ sNextSessionId++;
+
+ return sessionId;
+}
+
+bool ClearKeyPersistence::IsPersistentSessionId(const string& aSessionId) {
+ return Contains(mPersistentSessionIds, atoi(aSessionId.c_str()));
+}
+
+void ClearKeyPersistence::PersistentSessionRemoved(string& aSessionId) {
+ mPersistentSessionIds.erase(atoi(aSessionId.c_str()));
+
+ // Update the index file.
+ WriteIndex();
+}
diff --git a/media/gmp-clearkey/0.1/ClearKeyPersistence.h b/media/gmp-clearkey/0.1/ClearKeyPersistence.h
new file mode 100644
index 0000000000..b832db8c11
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyPersistence.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ClearKeyPersistence_h__
+#define __ClearKeyPersistence_h__
+
+// This include is required in order for content_decryption_module to work
+// on Unix systems.
+#include <stddef.h>
+
+#include <functional>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "content_decryption_module.h"
+
+#include "RefCounted.h"
+
+class ClearKeySessionManager;
+
+// Whether we've loaded the persistent session ids yet.
+enum PersistentKeyState { UNINITIALIZED, LOADING, LOADED };
+
+class ClearKeyPersistence : public RefCounted {
+ public:
+ explicit ClearKeyPersistence(cdm::Host_10* aHost);
+
+ void EnsureInitialized(bool aPersistentStateAllowed,
+ std::function<void()>&& aOnInitialized);
+
+ bool IsLoaded() const;
+
+ std::string GetNewSessionId(cdm::SessionType aSessionType);
+
+ bool IsPersistentSessionId(const std::string& aSid);
+
+ void PersistentSessionRemoved(std::string& aSid);
+
+ private:
+ cdm::Host_10* mHost = nullptr;
+
+ PersistentKeyState mPersistentKeyState = PersistentKeyState::UNINITIALIZED;
+
+ std::set<uint32_t> mPersistentSessionIds;
+
+ void ReadAllRecordsFromIndex(std::function<void()>&& aOnComplete);
+ void WriteIndex();
+};
+
+#endif // __ClearKeyPersistence_h__
diff --git a/media/gmp-clearkey/0.1/ClearKeySession.cpp b/media/gmp-clearkey/0.1/ClearKeySession.cpp
new file mode 100644
index 0000000000..b5454d1d0e
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeySession.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#include "BigEndian.h"
+#include "ClearKeyDecryptionManager.h"
+#include "ClearKeySession.h"
+#include "ClearKeyStorage.h"
+#include "ClearKeyUtils.h"
+#include "psshparser/PsshParser.h"
+
+using namespace mozilla;
+using namespace cdm;
+
+ClearKeySession::ClearKeySession(const std::string& aSessionId,
+ SessionType aSessionType)
+ : mSessionId(aSessionId), mSessionType(aSessionType) {
+ CK_LOGD("ClearKeySession ctor %p", this);
+}
+
+ClearKeySession::~ClearKeySession() {
+ CK_LOGD("ClearKeySession dtor %p", this);
+}
+
+bool ClearKeySession::Init(InitDataType aInitDataType, const uint8_t* aInitData,
+ uint32_t aInitDataSize) {
+ CK_LOGD("ClearKeySession::Init");
+
+ if (aInitDataType == InitDataType::kCenc) {
+ ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
+ } else if (aInitDataType == InitDataType::kKeyIds) {
+ ClearKeyUtils::ParseKeyIdsInitData(aInitData, aInitDataSize, mKeyIds);
+ } else if (aInitDataType == InitDataType::kWebM &&
+ aInitDataSize <= kMaxWebmInitDataSize) {
+ // "webm" initData format is simply the raw bytes of the keyId.
+ std::vector<uint8_t> keyId;
+ keyId.assign(aInitData, aInitData + aInitDataSize);
+ mKeyIds.push_back(keyId);
+ }
+
+ if (mKeyIds.empty()) {
+ return false;
+ }
+
+ return true;
+}
+
+SessionType ClearKeySession::Type() const { return mSessionType; }
+
+void ClearKeySession::AddKeyId(const KeyId& aKeyId) {
+ mKeyIds.push_back(aKeyId);
+}
diff --git a/media/gmp-clearkey/0.1/ClearKeySession.h b/media/gmp-clearkey/0.1/ClearKeySession.h
new file mode 100644
index 0000000000..3a130aa45a
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeySession.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ClearKeySession_h__
+#define __ClearKeySession_h__
+
+// This include is required in order for content_decryption_module to work
+// on Unix systems.
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "content_decryption_module.h"
+
+#include "ClearKeyUtils.h"
+
+class ClearKeySession {
+ public:
+ explicit ClearKeySession(const std::string& aSessionId,
+ cdm::SessionType aSessionType);
+
+ ~ClearKeySession();
+
+ const std::vector<KeyId>& GetKeyIds() const { return mKeyIds; }
+
+ bool Init(cdm::InitDataType aInitDataType, const uint8_t* aInitData,
+ uint32_t aInitDataSize);
+
+ cdm::SessionType Type() const;
+
+ void AddKeyId(const KeyId& aKeyId);
+
+ const std::string& Id() const { return mSessionId; }
+
+ private:
+ const std::string mSessionId;
+ std::vector<KeyId> mKeyIds;
+
+ const cdm::SessionType mSessionType;
+};
+
+#endif // __ClearKeySession_h__
diff --git a/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
new file mode 100644
index 0000000000..54a15f4fcf
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
@@ -0,0 +1,713 @@
+/*
+ * 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 "ClearKeySessionManager.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "content_decryption_module.h"
+
+#include "ClearKeyDecryptionManager.h"
+#include "ClearKeyPersistence.h"
+#include "ClearKeyStorage.h"
+#include "ClearKeyUtils.h"
+#include "psshparser/PsshParser.h"
+
+using namespace cdm;
+
+using std::function;
+using std::string;
+using std::vector;
+
+ClearKeySessionManager::ClearKeySessionManager(Host_10* aHost)
+ : mDecryptionManager(ClearKeyDecryptionManager::Get()) {
+ CK_LOGD("ClearKeySessionManager ctor %p", this);
+ AddRef();
+
+ mHost = aHost;
+ mPersistence = new ClearKeyPersistence(mHost);
+}
+
+ClearKeySessionManager::~ClearKeySessionManager() {
+ CK_LOGD("ClearKeySessionManager dtor %p", this);
+}
+
+void ClearKeySessionManager::Init(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();
+ }
+ if (self->mHost) {
+ // The session manager should be the last thing the ClearKey CDM is
+ // waiting on to be initialized.
+ self->mHost->OnInitialized(true);
+ }
+ };
+
+ mPersistence->EnsureInitialized(aPersistentStateAllowed,
+ std::move(onPersistentStateLoaded));
+}
+
+void ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
+ InitDataType aInitDataType,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ SessionType aSessionType) {
+ CK_LOGD("ClearKeySessionManager::CreateSession type:%u", aInitDataType);
+
+ // 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(std::move(deferrer))) {
+ CK_LOGD("Deferring CreateSession");
+ return;
+ }
+
+ 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;
+ }
+
+ // 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, Exception::kExceptionNotSupportedError,
+ 0, message.c_str(), message.size());
+
+ return;
+ }
+
+ string sessionId = mPersistence->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, Exception::kExceptionInvalidStateError,
+ 0, message, strlen(message));
+ delete session;
+
+ return;
+ }
+
+ mSessions[sessionId] = session;
+ mLastSessionId = sessionId;
+
+ const vector<KeyId>& sessionKeys = session->GetKeyIds();
+ vector<KeyId> neededKeys;
+
+ for (auto it = sessionKeys.begin(); it != sessionKeys.end(); it++) {
+ // Need to request this key ID from the client. We always send a key
+ // request, whether or not another session has sent a request with the same
+ // key ID. Otherwise a script can end up waiting for another script to
+ // respond to the request (which may not necessarily happen).
+ neededKeys.push_back(*it);
+ mDecryptionManager->ExpectKeyId(*it);
+ }
+
+ if (neededKeys.empty()) {
+ CK_LOGD("No keys needed from client.");
+ return;
+ }
+
+ // Send a request for needed key data.
+ string request;
+ ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType);
+
+ // 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());
+}
+
+void ClearKeySessionManager::LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) {
+ CK_LOGD("ClearKeySessionManager::LoadSession");
+
+ // 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(std::move(deferrer))) {
+ CK_LOGD("Deferring LoadSession");
+ return;
+ }
+
+ // 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);
+ return;
+ }
+
+ if (!mPersistence->IsPersistentSessionId(sessionId)) {
+ mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
+ 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);
+ };
+
+ function<void()> failure = [self, aPromiseId] {
+ if (!self->mHost) {
+ return;
+ }
+ // As per the API described in ContentDecryptionModule_8
+ self->mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
+ };
+
+ ReadData(mHost, sessionId, std::move(success), std::move(failure));
+}
+
+void ClearKeySessionManager::PersistentSessionDataLoaded(
+ 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) ||
+ (aKeyDataSize % (2 * CENC_KEY_LEN)) != 0) {
+ // As per the instructions in ContentDecryptionModule_8
+ mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
+ return;
+ }
+
+ ClearKeySession* session =
+ new ClearKeySession(aSessionId, SessionType::kPersistentLicense);
+
+ mSessions[aSessionId] = session;
+ mLastSessionId = aSessionId;
+
+ uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN);
+
+ vector<KeyInformation> keyInfos;
+ vector<KeyIdPair> keyPairs;
+ for (uint32_t i = 0; i < numKeys; i++) {
+ const uint8_t* base = aKeyData + 2 * CENC_KEY_LEN * i;
+
+ KeyIdPair keyPair;
+
+ keyPair.mKeyId = KeyId(base, base + CENC_KEY_LEN);
+ assert(keyPair.mKeyId.size() == CENC_KEY_LEN);
+
+ keyPair.mKey = Key(base + CENC_KEY_LEN, base + 2 * CENC_KEY_LEN);
+ assert(keyPair.mKey.size() == CENC_KEY_LEN);
+
+ session->AddKeyId(keyPair.mKeyId);
+
+ mDecryptionManager->ExpectKeyId(keyPair.mKeyId);
+ mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
+ mKeyIds.insert(keyPair.mKey);
+ keyPairs.push_back(keyPair);
+
+ KeyInformation keyInfo = {};
+ keyInfo.key_id = &keyPairs.back().mKeyId[0];
+ keyInfo.key_id_size = keyPair.mKeyId.size();
+ keyInfo.status = KeyStatus::kUsable;
+
+ keyInfos.push_back(keyInfo);
+ }
+
+ mHost->OnSessionKeysChange(&aSessionId[0], aSessionId.size(), true,
+ keyInfos.data(), keyInfos.size());
+
+ mHost->OnResolveNewSessionPromise(aPromiseId, aSessionId.c_str(),
+ aSessionId.size());
+}
+
+void ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) {
+ CK_LOGD("ClearKeySessionManager::UpdateSession");
+
+ // 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(std::move(deferrer))) {
+ CK_LOGD("Deferring LoadSession");
+ return;
+ }
+
+ // Make sure the SessionManager has not been shutdown before we try and
+ // resolve any promises.
+ if (!mHost) {
+ return;
+ }
+
+ CK_LOGD("Updating session: %s", sessionId.c_str());
+
+ 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, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+ ClearKeySession* session = itr->second;
+
+ // Verify the size of session response.
+ if (aResponseSize >= kMaxSessionResponseLength) {
+ CK_LOGW("Session response size is not within a reasonable size.");
+ CK_LOGD("Failed to parse response for session %s", sessionId.c_str());
+
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+
+ // Parse the response for any (key ID, key) pairs.
+ vector<KeyIdPair> keyPairs;
+ if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs,
+ session->Type())) {
+ CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
+
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+
+ vector<KeyInformation> keyInfos;
+ 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 = {};
+ keyInfo.key_id = &keyPair.mKeyId[0];
+ keyInfo.key_id_size = keyPair.mKeyId.size();
+ keyInfo.status = KeyStatus::kUsable;
+
+ keyInfos.push_back(keyInfo);
+ }
+
+ mHost->OnSessionKeysChange(aSessionId, aSessionIdLength, true,
+ keyInfos.data(), keyInfos.size());
+
+ if (session->Type() != SessionType::kPersistentLicense) {
+ mHost->OnResolvePromise(aPromiseId);
+ return;
+ }
+
+ // Store the keys on disk. We store a record whose name is the sessionId,
+ // and simply append each keyId followed by its key.
+ vector<uint8_t> keydata;
+ Serialize(session, keydata);
+
+ 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,
+ Exception::kExceptionInvalidStateError, 0,
+ message, strlen(message));
+ };
+
+ WriteData(mHost, sessionId, keydata, std::move(resolve), std::move(reject));
+}
+
+void ClearKeySessionManager::Serialize(const ClearKeySession* aSession,
+ std::vector<uint8_t>& aOutKeyData) {
+ const std::vector<KeyId>& keyIds = aSession->GetKeyIds();
+ for (size_t i = 0; i < keyIds.size(); i++) {
+ const KeyId& keyId = keyIds[i];
+ if (!mDecryptionManager->HasKeyForKeyId(keyId)) {
+ continue;
+ }
+ assert(keyId.size() == CENC_KEY_LEN);
+ aOutKeyData.insert(aOutKeyData.end(), keyId.begin(), keyId.end());
+ const Key& key = mDecryptionManager->GetDecryptionKey(keyId);
+ assert(key.size() == CENC_KEY_LEN);
+ aOutKeyData.insert(aOutKeyData.end(), key.begin(), key.end());
+ }
+}
+
+void ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) {
+ CK_LOGD("ClearKeySessionManager::CloseSession");
+
+ // 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(std::move(deferrer))) {
+ CK_LOGD("Deferring CloseSession");
+ return;
+ }
+
+ // If DecryptingComplete has been called mHost will be null and we won't
+ // be able to resolve our promise.
+ if (!mHost) {
+ return;
+ }
+
+ auto itr = mSessions.find(sessionId);
+ if (itr == mSessions.end()) {
+ CK_LOGW("ClearKey CDM couldn't close non-existent session.");
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+
+ ClearKeySession* session = itr->second;
+ assert(session);
+
+ ClearInMemorySessionData(session);
+
+ mHost->OnSessionClosed(aSessionId, aSessionIdLength);
+ mHost->OnResolvePromise(aPromiseId);
+}
+
+void ClearKeySessionManager::ClearInMemorySessionData(
+ ClearKeySession* aSession) {
+ mSessions.erase(aSession->Id());
+ delete aSession;
+}
+
+void ClearKeySessionManager::RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) {
+ CK_LOGD("ClearKeySessionManager::RemoveSession");
+
+ // 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(std::move(deferrer))) {
+ CK_LOGD("Deferring RemoveSession");
+ return;
+ }
+
+ // Check that the SessionManager has not been shutdown before we try and
+ // resolve any promises.
+ if (!mHost) {
+ return;
+ }
+
+ auto itr = mSessions.find(sessionId);
+ if (itr == mSessions.end()) {
+ CK_LOGW("ClearKey CDM couldn't remove non-existent session.");
+
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+
+ ClearKeySession* session = itr->second;
+ assert(session);
+ string sid = session->Id();
+ bool isPersistent = session->Type() == SessionType::kPersistentLicense;
+ ClearInMemorySessionData(session);
+
+ if (!isPersistent) {
+ mHost->OnResolvePromise(aPromiseId);
+ return;
+ }
+
+ mPersistence->PersistentSessionRemoved(sid);
+
+ vector<uint8_t> emptyKeydata;
+
+ 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 = "Could not remove session";
+ self->mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ message, strlen(message));
+ };
+
+ WriteData(mHost, sessionId, emptyKeydata, std::move(resolve),
+ std::move(reject));
+}
+
+void ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) {
+ // ClearKey CDM doesn't support this method by spec.
+ CK_LOGD("ClearKeySessionManager::SetServerCertificate");
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionNotSupportedError, 0,
+ nullptr /* message */, 0 /* messageLen */);
+}
+
+Status ClearKeySessionManager::Decrypt(const InputBuffer_2& aBuffer,
+ DecryptedBlock* aDecryptedBlock) {
+ CK_LOGD("ClearKeySessionManager::Decrypt");
+
+ CK_LOGARRAY("Key: ", aBuffer.key_id, aBuffer.key_id_size);
+
+ Buffer* buffer = mHost->Allocate(aBuffer.data_size);
+ assert(buffer != nullptr);
+ assert(buffer->Data() != nullptr);
+ assert(buffer->Capacity() >= aBuffer.data_size);
+
+ memcpy(buffer->Data(), aBuffer.data, aBuffer.data_size);
+
+ Status status = Status::kSuccess;
+ // According to the comment `If |iv_size| = 0, the data is unencrypted.`
+ // Use iv_size to determine if the sample is encrypted.
+ if (aBuffer.iv_size != 0) {
+ status = mDecryptionManager->Decrypt(buffer->Data(), buffer->Size(),
+ CryptoMetaData(&aBuffer));
+ }
+
+ aDecryptedBlock->SetDecryptedBuffer(buffer);
+ aDecryptedBlock->SetTimestamp(aBuffer.timestamp);
+
+ return status;
+}
+
+void ClearKeySessionManager::DecryptingComplete() {
+ CK_LOGD("ClearKeySessionManager::DecryptingComplete %p", this);
+
+ for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
+ delete it->second;
+ }
+ mSessions.clear();
+ mLastSessionId = std::nullopt;
+
+ mDecryptionManager = nullptr;
+ mHost = nullptr;
+
+ Release();
+}
+
+bool ClearKeySessionManager::MaybeDeferTillInitialized(
+ function<void()>&& aMaybeDefer) {
+ if (mPersistence->IsLoaded()) {
+ return false;
+ }
+
+ mDeferredInitialize.emplace(std::move(aMaybeDefer));
+ return true;
+}
+
+void ClearKeySessionManager::OnQueryOutputProtectionStatus(
+ QueryResult aResult, uint32_t aLinkMask, uint32_t aOutputProtectionMask) {
+ MOZ_ASSERT(mHasOutstandingOutputProtectionQuery,
+ "Should only be called if a query is outstanding");
+ CK_LOGD("ClearKeySessionManager::OnQueryOutputProtectionStatus");
+ mHasOutstandingOutputProtectionQuery = false;
+
+ if (aResult == QueryResult::kQueryFailed) {
+ // Indicate the query failed. This can happen if we're in shutdown.
+ NotifyOutputProtectionStatus(KeyStatus::kInternalError);
+ return;
+ }
+
+ if (aLinkMask & OutputLinkTypes::kLinkTypeNetwork) {
+ NotifyOutputProtectionStatus(KeyStatus::kOutputRestricted);
+ return;
+ }
+
+ NotifyOutputProtectionStatus(KeyStatus::kUsable);
+}
+
+void ClearKeySessionManager::QueryOutputProtectionStatusIfNeeded() {
+ MOZ_ASSERT(
+ mHost,
+ "Should not query protection status if we're shutdown (mHost == null)!");
+ CK_LOGD(
+ "ClearKeySessionManager::UpdateOutputProtectionStatusAndQueryIfNeeded");
+ if (mLastOutputProtectionQueryTime.IsNull()) {
+ // We haven't perfomed a check yet, get a query going.
+ MOZ_ASSERT(
+ !mHasOutstandingOutputProtectionQuery,
+ "Shouldn't have an outstanding query if we haven't recorded a time");
+ QueryOutputProtectionStatusFromHost();
+ return;
+ }
+
+ MOZ_ASSERT(!mLastOutputProtectionQueryTime.IsNull(),
+ "Should have already handled the case where we don't yet have a "
+ "previous check time");
+ const mozilla::TimeStamp now = mozilla::TimeStamp::NowLoRes();
+ const mozilla::TimeDuration timeSinceQuery =
+ now - mLastOutputProtectionQueryTime;
+
+ // The time between output protection checks to the host. I.e. if this amount
+ // of time has passed since the last check with the host, another should be
+ // performed (provided the first check has been handled).
+ static const mozilla::TimeDuration kOutputProtectionQueryInterval =
+ mozilla::TimeDuration::FromSeconds(0.2);
+ // The number of kOutputProtectionQueryInterval intervals we can miss before
+ // we decide a check has failed. I.e. if this value is 2, if we have not
+ // received a reply to a check after kOutputProtectionQueryInterval * 2
+ // time, we consider the check failed.
+ constexpr uint32_t kMissedIntervalsBeforeFailure = 2;
+ // The length of time after which we will restrict output until we get a
+ // query response.
+ static const mozilla::TimeDuration kTimeToWaitBeforeFailure =
+ kOutputProtectionQueryInterval * kMissedIntervalsBeforeFailure;
+
+ if ((timeSinceQuery > kOutputProtectionQueryInterval) &&
+ !mHasOutstandingOutputProtectionQuery) {
+ // We don't have an outstanding query and enough time has passed we should
+ // query again.
+ QueryOutputProtectionStatusFromHost();
+ return;
+ }
+
+ if ((timeSinceQuery > kTimeToWaitBeforeFailure) &&
+ mHasOutstandingOutputProtectionQuery) {
+ // A reponse was not received fast enough, notify.
+ NotifyOutputProtectionStatus(KeyStatus::kInternalError);
+ }
+}
+
+void ClearKeySessionManager::QueryOutputProtectionStatusFromHost() {
+ MOZ_ASSERT(
+ mHost,
+ "Should not query protection status if we're shutdown (mHost == null)!");
+ CK_LOGD("ClearKeySessionManager::QueryOutputProtectionStatusFromHost");
+ if (mHost) {
+ mLastOutputProtectionQueryTime = mozilla::TimeStamp::NowLoRes();
+ mHost->QueryOutputProtectionStatus();
+ mHasOutstandingOutputProtectionQuery = true;
+ }
+}
+
+void ClearKeySessionManager::NotifyOutputProtectionStatus(KeyStatus aStatus) {
+ MOZ_ASSERT(aStatus == KeyStatus::kUsable ||
+ aStatus == KeyStatus::kOutputRestricted ||
+ aStatus == KeyStatus::kInternalError,
+ "aStatus should have an expected value");
+ CK_LOGD("ClearKeySessionManager::NotifyOutputProtectionStatus");
+ if (!mLastSessionId.has_value()) {
+ // If we don't have a session id, either because we're too early, or are
+ // shutting down, don't notify.
+ return;
+ }
+
+ string& lastSessionId = mLastSessionId.value();
+
+ // Use 'output-protection' as the key ID. This helps tests disambiguate key
+ // status updates related to this.
+ const uint8_t kKeyId[] = {'o', 'u', 't', 'p', 'u', 't', '-', 'p', 'r',
+ 'o', 't', 'e', 'c', 't', 'i', 'o', 'n'};
+ KeyInformation keyInfo = {};
+ keyInfo.key_id = kKeyId;
+ keyInfo.key_id_size = std::size(kKeyId);
+ keyInfo.status = aStatus;
+
+ vector<KeyInformation> keyInfos;
+ keyInfos.push_back(keyInfo);
+
+ // At time of writing, Gecko's higher level handling doesn't use this arg.
+ // However, we set it to false to mimic Chromium's similar case. Since
+ // Clearkey is used to test the Chromium CDM path, it doesn't hurt to try
+ // and mimic their behaviour.
+ bool hasAdditionalUseableKey = false;
+ mHost->OnSessionKeysChange(lastSessionId.c_str(), lastSessionId.size(),
+ hasAdditionalUseableKey, keyInfos.data(),
+ keyInfos.size());
+}
diff --git a/media/gmp-clearkey/0.1/ClearKeySessionManager.h b/media/gmp-clearkey/0.1/ClearKeySessionManager.h
new file mode 100644
index 0000000000..3a930f666d
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.h
@@ -0,0 +1,138 @@
+/*
+ * 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__
+
+// This include is required in order for content_decryption_module to work
+// on Unix systems.
+#include <stddef.h>
+
+#include <functional>
+#include <map>
+#include <optional>
+#include <queue>
+#include <set>
+#include <string>
+
+#include "content_decryption_module.h"
+
+#include "ClearKeyDecryptionManager.h"
+#include "ClearKeyPersistence.h"
+#include "ClearKeySession.h"
+#include "ClearKeyUtils.h"
+#include "RefCounted.h"
+#include "mozilla/TimeStamp.h"
+
+class ClearKeySessionManager final : public RefCounted {
+ public:
+ explicit ClearKeySessionManager(cdm::Host_10* aHost);
+
+ void Init(bool aDistinctiveIdentifierAllowed, bool aPersistentStateAllowed);
+
+ void CreateSession(uint32_t aPromiseId, cdm::InitDataType aInitDataType,
+ const uint8_t* aInitData, uint32_t aInitDataSize,
+ cdm::SessionType aSessionType);
+
+ void LoadSession(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdLength);
+
+ void UpdateSession(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdLength, const uint8_t* aResponse,
+ uint32_t aResponseSize);
+
+ void CloseSession(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdLength);
+
+ void RemoveSession(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdLength);
+
+ void SetServerCertificate(uint32_t aPromiseId, const uint8_t* aServerCert,
+ uint32_t aServerCertSize);
+
+ cdm::Status Decrypt(const cdm::InputBuffer_2& aBuffer,
+ cdm::DecryptedBlock* aDecryptedBlock);
+
+ void DecryptingComplete();
+
+ void PersistentSessionDataLoaded(uint32_t aPromiseId,
+ const std::string& aSessionId,
+ const uint8_t* aKeyData,
+ uint32_t aKeyDataSize);
+
+ // Receives the result of an output protection query from the user agent.
+ // This may trigger a key status change.
+ // @param aResult indicates if the query succeeded or not. If a query did
+ // not succeed then that other arguments are ignored.
+ // @param aLinkMask is used to indicate if output could be captured by the
+ // user agent. It should be set to `kLinkTypeNetwork` if capture is possible,
+ // otherwise it should be zero.
+ // @param aOutputProtectionMask this argument is unused.
+ void OnQueryOutputProtectionStatus(cdm::QueryResult aResult,
+ uint32_t aLinkMask,
+ uint32_t aOutputProtectionMask);
+
+ // Prompts the session manager to query the output protection status if we
+ // haven't yet, or if enough time has passed since the last check. Will also
+ // notify if a check has not been responded to on time.
+ void QueryOutputProtectionStatusIfNeeded();
+
+ private:
+ ~ClearKeySessionManager();
+
+ void ClearInMemorySessionData(ClearKeySession* aSession);
+ bool MaybeDeferTillInitialized(std::function<void()>&& aMaybeDefer);
+ void Serialize(const ClearKeySession* aSession,
+ std::vector<uint8_t>& aOutKeyData);
+
+ // Signals the host to perform an output protection check.
+ void QueryOutputProtectionStatusFromHost();
+
+ // Called to notify the result of an output protection status call. The
+ // following arguments are expected, along with their intended use:
+ // - KeyStatus::kUsable indicates that the query was responded to and the
+ // response showed output is protected.
+ // - KeyStatus::kOutputRestricted indicates that the query was responded to
+ // and the response showed output is not protected.
+ // - KeyStatus::kInternalError indicates a query was not repsonded to on
+ // time, or that a query was responded to with a failed cdm::QueryResult.
+ // The status passed to this function will be used to update the status of
+ // the keyId "output-protection", which tests an observe.
+ void NotifyOutputProtectionStatus(cdm::KeyStatus aStatus);
+
+ RefPtr<ClearKeyDecryptionManager> mDecryptionManager;
+ RefPtr<ClearKeyPersistence> mPersistence;
+
+ cdm::Host_10* mHost = nullptr;
+
+ std::set<KeyId> mKeyIds;
+ std::map<std::string, ClearKeySession*> mSessions;
+
+ // The session id of the last session created or loaded from persistent
+ // storage. Used to fire test messages at that session.
+ std::optional<std::string> mLastSessionId;
+
+ std::queue<std::function<void()>> mDeferredInitialize;
+
+ // If there is an inflight query to the host to check the output protection
+ // status. Multiple in flight queries should not be allowed, avoid firing
+ // more if this is true.
+ bool mHasOutstandingOutputProtectionQuery = false;
+ // The last time the manager called QueryOutputProtectionStatus on the host.
+ mozilla::TimeStamp mLastOutputProtectionQueryTime;
+};
+
+#endif // __ClearKeyDecryptor_h__
diff --git a/media/gmp-clearkey/0.1/ClearKeyStorage.cpp b/media/gmp-clearkey/0.1/ClearKeyStorage.cpp
new file mode 100644
index 0000000000..1375d33c57
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyStorage.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ClearKeyStorage.h"
+
+#include <assert.h>
+// This include is required in order for content_decryption_module to work
+// on Unix systems.
+#include <stddef.h>
+
+#include <vector>
+
+#include "content_decryption_module.h"
+
+#include "ArrayUtils.h"
+#include "ClearKeyUtils.h"
+
+using namespace cdm;
+
+using std::function;
+using std::string;
+using std::vector;
+
+class WriteRecordClient : public FileIOClient {
+ public:
+ /*
+ * This function will take the memory ownership of the parameters and
+ * delete them when done.
+ */
+ static void Write(Host_10* aHost, string& aRecordName,
+ const vector<uint8_t>& aData, function<void()>&& aOnSuccess,
+ function<void()>&& aOnFailure) {
+ WriteRecordClient* client = new WriteRecordClient(
+ aData, std::move(aOnSuccess), std::move(aOnFailure));
+ client->Do(aRecordName, aHost);
+ }
+
+ 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());
+ }
+ }
+
+ 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);
+ }
+
+ void OnWriteComplete(Status aStatus) override { Done(aStatus); }
+
+ private:
+ explicit WriteRecordClient(const vector<uint8_t>& aData,
+ function<void()>&& aOnSuccess,
+ function<void()>&& aOnFailure)
+ : mFileIO(nullptr),
+ mOnSuccess(std::move(aOnSuccess)),
+ mOnFailure(std::move(aOnFailure)),
+ mData(aData) {}
+
+ void Do(const string& aName, Host_10* aHost) {
+ // Initialize the FileIO.
+ mFileIO = aHost->CreateFileIO(this);
+ mFileIO->Open(aName.c_str(), aName.size());
+ }
+
+ void Done(cdm::FileIOClient::Status aStatus) {
+ // 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 (IO_SUCCEEDED(aStatus)) {
+ mOnSuccess();
+ } else {
+ mOnFailure();
+ }
+
+ delete this;
+ }
+
+ FileIO* mFileIO = nullptr;
+
+ function<void()> mOnSuccess;
+ function<void()> mOnFailure;
+
+ const vector<uint8_t> mData;
+};
+
+void WriteData(Host_10* aHost, string& aRecordName,
+ const vector<uint8_t>& aData, function<void()>&& aOnSuccess,
+ function<void()>&& aOnFailure) {
+ WriteRecordClient::Write(aHost, aRecordName, aData, std::move(aOnSuccess),
+ std::move(aOnFailure));
+}
+
+class ReadRecordClient : public FileIOClient {
+ public:
+ /*
+ * This function will take the memory ownership of the parameters and
+ * delete them when done.
+ */
+ static void Read(Host_10* aHost, string& aRecordName,
+ function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
+ function<void()>&& aOnFailure) {
+ (new ReadRecordClient(std::move(aOnSuccess), std::move(aOnFailure)))
+ ->Do(aRecordName, aHost);
+ }
+
+ void OnOpenComplete(Status aStatus) override {
+ auto err = aStatus;
+ if (aStatus != Status::kSuccess) {
+ Done(err, nullptr, 0);
+ } else {
+ mFileIO->Read();
+ }
+ }
+
+ void OnReadComplete(Status 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);
+ }
+
+ private:
+ explicit ReadRecordClient(
+ function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
+ function<void()>&& aOnFailure)
+ : mFileIO(nullptr),
+ mOnSuccess(std::move(aOnSuccess)),
+ mOnFailure(std::move(aOnFailure)) {}
+
+ void Do(const string& aName, Host_10* aHost) {
+ mFileIO = aHost->CreateFileIO(this);
+ mFileIO->Open(aName.c_str(), aName.size());
+ }
+
+ void Done(cdm::FileIOClient::Status aStatus, 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 (IO_SUCCEEDED(aStatus)) {
+ mOnSuccess(aData, aDataSize);
+ } else {
+ mOnFailure();
+ }
+
+ delete this;
+ }
+
+ FileIO* mFileIO = nullptr;
+
+ function<void(const uint8_t*, uint32_t)> mOnSuccess;
+ function<void()> mOnFailure;
+};
+
+void ReadData(Host_10* aHost, string& aRecordName,
+ function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
+ function<void()>&& aOnFailure) {
+ ReadRecordClient::Read(aHost, aRecordName, std::move(aOnSuccess),
+ std::move(aOnFailure));
+}
diff --git a/media/gmp-clearkey/0.1/ClearKeyStorage.h b/media/gmp-clearkey/0.1/ClearKeyStorage.h
new file mode 100644
index 0000000000..bd8e99b901
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyStorage.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ClearKeyStorage_h__
+#define __ClearKeyStorage_h__
+
+#include <stdint.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "ClearKeySessionManager.h"
+
+#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_10* aHost, std::string& aRecordName,
+ const std::vector<uint8_t>& aData,
+ std::function<void()>&& aOnSuccess,
+ std::function<void()>&& aOnFailure);
+
+// Reads data from a file and fires the appropriate callback when complete.
+void ReadData(cdm::Host_10* aHost, std::string& aRecordName,
+ std::function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
+ std::function<void()>&& aOnFailure);
+
+#endif // __ClearKeyStorage_h__
diff --git a/media/gmp-clearkey/0.1/ClearKeyUtils.cpp b/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
new file mode 100644
index 0000000000..086283fb2e
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
@@ -0,0 +1,657 @@
+/*
+ * 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 "ClearKeyUtils.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <memory.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <cctype>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#include "pk11pub.h"
+#include "prerror.h"
+#include "secmodt.h"
+
+#include "ArrayUtils.h"
+#include "BigEndian.h"
+#include "ClearKeyBase64.h"
+#include "mozilla/Sprintf.h"
+#include "psshparser/PsshParser.h"
+
+using namespace cdm;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+struct DeleteHelper {
+ void operator()(PK11Context* value) { PK11_DestroyContext(value, true); }
+ void operator()(PK11SlotInfo* value) { PK11_FreeSlot(value); }
+ void operator()(PK11SymKey* value) { PK11_FreeSymKey(value); }
+};
+
+template <class T>
+struct MaybeDeleteHelper {
+ void operator()(T* ptr) {
+ if (ptr) {
+ DeleteHelper del;
+ del(ptr);
+ }
+ }
+};
+
+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];
+ VsprintfLiteral(buf, 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 std::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());
+}
+
+/* static */
+bool ClearKeyUtils::DecryptCbcs(const vector<uint8_t>& aKey,
+ const vector<uint8_t>& aIV,
+ mozilla::Span<uint8_t> aSubsample,
+ uint32_t aCryptByteBlock,
+ uint32_t aSkipByteBlock) {
+ assert(aKey.size() == CENC_KEY_LEN);
+ assert(aIV.size() == CENC_KEY_LEN);
+ assert(aCryptByteBlock <= 0xFF);
+ assert(aSkipByteBlock <= 0xFF);
+
+ std::unique_ptr<PK11SlotInfo, MaybeDeleteHelper<PK11SlotInfo>> slot(
+ PK11_GetInternalKeySlot());
+
+ if (!slot.get()) {
+ CK_LOGE("Failed to get internal PK11 slot");
+ return false;
+ }
+
+ SECItem keyItem = {siBuffer, (unsigned char*)&aKey[0], CENC_KEY_LEN};
+ SECItem ivItem = {siBuffer, (unsigned char*)&aIV[0], CENC_KEY_LEN};
+
+ std::unique_ptr<PK11SymKey, MaybeDeleteHelper<PK11SymKey>> key(
+ PK11_ImportSymKey(slot.get(), CKM_AES_CBC, PK11_OriginUnwrap, CKA_DECRYPT,
+ &keyItem, nullptr));
+
+ if (!key.get()) {
+ CK_LOGE("Failed to import sym key");
+ return false;
+ }
+
+ std::unique_ptr<PK11Context, MaybeDeleteHelper<PK11Context>> ctx(
+ PK11_CreateContextBySymKey(CKM_AES_CBC, CKA_DECRYPT, key.get(), &ivItem));
+
+ uint8_t* encryptedSubsample = &aSubsample[0];
+ const uint32_t BLOCK_SIZE = 16;
+ const uint32_t skipBytes = aSkipByteBlock * BLOCK_SIZE;
+ const uint32_t totalBlocks = aSubsample.Length() / BLOCK_SIZE;
+ uint32_t blocksProcessed = 0;
+
+ if (aSkipByteBlock == 0) {
+ // ISO/IEC 23001 - 7 Section 9.6.1
+ // 'When the fields default_crypt_byte_block and default_skip_byte_block in
+ // a version 1 Track Encryption Box('tenc') are non - zero numbers, pattern
+ // encryption SHALL be applied.'
+ // So if both are 0, then everything is encrypted. Similarly, if skip is 0
+ // and crypt is non-0, everything is encrypted.
+ // In this case we can just decrypt all the blocks in one call. This is the
+ // same outcome as decrypting them using smaller steps, as either way the
+ // CBC result should be the same.
+ MOZ_ASSERT(skipBytes == 0);
+ aCryptByteBlock = totalBlocks;
+ }
+
+ while (blocksProcessed < totalBlocks) {
+ uint32_t blocksToDecrypt = aCryptByteBlock <= totalBlocks - blocksProcessed
+ ? aCryptByteBlock
+ : totalBlocks - blocksProcessed;
+ uint32_t bytesToDecrypt = blocksToDecrypt * BLOCK_SIZE;
+ int outLen;
+ SECStatus rv;
+ rv = PK11_CipherOp(ctx.get(), encryptedSubsample, &outLen, bytesToDecrypt,
+ encryptedSubsample, bytesToDecrypt);
+ if (rv != SECSuccess) {
+ CK_LOGE("PK11_CipherOp() failed");
+ return false;
+ }
+
+ encryptedSubsample += skipBytes + bytesToDecrypt;
+ blocksProcessed += aSkipByteBlock + blocksToDecrypt;
+ }
+
+ return true;
+}
+
+/* static */
+bool ClearKeyUtils::DecryptAES(const vector<uint8_t>& aKey,
+ vector<uint8_t>& aData, vector<uint8_t>& aIV) {
+ assert(aIV.size() == CENC_KEY_LEN);
+ assert(aKey.size() == CENC_KEY_LEN);
+
+ PK11SlotInfo* slot = PK11_GetInternalKeySlot();
+ if (!slot) {
+ CK_LOGE("Failed to get internal PK11 slot");
+ return false;
+ }
+
+ SECItem keyItem = {siBuffer, (unsigned char*)&aKey[0], CENC_KEY_LEN};
+ PK11SymKey* key = PK11_ImportSymKey(slot, CKM_AES_CTR, PK11_OriginUnwrap,
+ CKA_ENCRYPT, &keyItem, nullptr);
+ PK11_FreeSlot(slot);
+ if (!key) {
+ CK_LOGE("Failed to import sym key");
+ return false;
+ }
+
+ CK_AES_CTR_PARAMS params;
+ params.ulCounterBits = 32;
+ memcpy(&params.cb, &aIV[0], CENC_KEY_LEN);
+ SECItem paramItem = {siBuffer, (unsigned char*)&params,
+ sizeof(CK_AES_CTR_PARAMS)};
+
+ unsigned int outLen = 0;
+ auto rv = PK11_Decrypt(key, CKM_AES_CTR, &paramItem, &aData[0], &outLen,
+ aData.size(), &aData[0], aData.size());
+
+ aData.resize(outLen);
+ PK11_FreeSymKey(key);
+
+ if (rv != SECSuccess) {
+ CK_LOGE("PK11_Decrypt() failed");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * ClearKey expects all Key IDs to be base64 encoded with non-standard alphabet
+ * and padding.
+ */
+static bool EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded) {
+ const char sAlphabet[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+ const uint8_t sMask = 0x3f;
+
+ aEncoded.resize((aBinary.size() * 8 + 5) / 6);
+
+ // Pad binary data in case there's rubbish past the last byte.
+ aBinary.push_back(0);
+
+ // Number of bytes not consumed in the previous character
+ uint32_t shift = 0;
+
+ auto out = aEncoded.begin();
+ auto data = aBinary.begin();
+ for (string::size_type i = 0; i < aEncoded.length(); i++) {
+ if (shift) {
+ out[i] = (*data << (6 - shift)) & sMask;
+ data++;
+ } else {
+ out[i] = 0;
+ }
+
+ out[i] += (*data >> (shift + 2)) & sMask;
+ shift = (shift + 2) % 8;
+
+ // Cast idx to size_t before using it as an array-index,
+ // to pacify clang 'Wchar-subscripts' warning:
+ size_t idx = static_cast<size_t>(out[i]);
+
+ // out of bounds index for 'sAlphabet'
+ assert(idx < MOZ_ARRAY_LENGTH(sAlphabet));
+ out[i] = sAlphabet[idx];
+ }
+
+ return true;
+}
+
+/* static */
+void ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
+ string& aOutRequest,
+ SessionType aSessionType) {
+ assert(!aKeyIDs.empty() && aOutRequest.empty());
+
+ aOutRequest.append("{\"kids\":[");
+ for (size_t i = 0; i < aKeyIDs.size(); i++) {
+ if (i) {
+ aOutRequest.append(",");
+ }
+ aOutRequest.append("\"");
+
+ string base64key;
+ EncodeBase64Web(aKeyIDs[i], base64key);
+ aOutRequest.append(base64key);
+
+ aOutRequest.append("\"");
+ }
+ aOutRequest.append("],\"type\":");
+
+ aOutRequest.append("\"");
+ aOutRequest.append(SessionTypeToString(aSessionType));
+ aOutRequest.append("\"}");
+}
+
+#define EXPECT_SYMBOL(CTX, X) \
+ do { \
+ if (GetNextSymbol(CTX) != (X)) { \
+ CK_LOGE("Unexpected symbol in JWK parser"); \
+ return false; \
+ } \
+ } while (false)
+
+struct ParserContext {
+ const uint8_t* mIter;
+ const uint8_t* mEnd;
+};
+
+static uint8_t PeekSymbol(ParserContext& aCtx) {
+ for (; aCtx.mIter < aCtx.mEnd; (aCtx.mIter)++) {
+ if (!isspace(*aCtx.mIter)) {
+ return *aCtx.mIter;
+ }
+ }
+
+ return 0;
+}
+
+static uint8_t GetNextSymbol(ParserContext& aCtx) {
+ uint8_t sym = PeekSymbol(aCtx);
+ aCtx.mIter++;
+ return sym;
+}
+
+static bool SkipToken(ParserContext& aCtx);
+
+static bool SkipString(ParserContext& aCtx) {
+ EXPECT_SYMBOL(aCtx, '"');
+ for (uint8_t sym = GetNextSymbol(aCtx); sym; sym = GetNextSymbol(aCtx)) {
+ if (sym == '\\') {
+ sym = GetNextSymbol(aCtx);
+ if (!sym) {
+ return false;
+ }
+ } else if (sym == '"') {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Skip whole object and values it contains.
+ */
+static bool SkipObject(ParserContext& aCtx) {
+ EXPECT_SYMBOL(aCtx, '{');
+
+ if (PeekSymbol(aCtx) == '}') {
+ GetNextSymbol(aCtx);
+ return true;
+ }
+
+ while (true) {
+ if (!SkipString(aCtx)) return false;
+ EXPECT_SYMBOL(aCtx, ':');
+ if (!SkipToken(aCtx)) return false;
+
+ if (PeekSymbol(aCtx) == '}') {
+ GetNextSymbol(aCtx);
+ return true;
+ }
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return false;
+}
+
+/**
+ * Skip array value and the values it contains.
+ */
+static bool SkipArray(ParserContext& aCtx) {
+ EXPECT_SYMBOL(aCtx, '[');
+
+ if (PeekSymbol(aCtx) == ']') {
+ GetNextSymbol(aCtx);
+ return true;
+ }
+
+ while (SkipToken(aCtx)) {
+ if (PeekSymbol(aCtx) == ']') {
+ GetNextSymbol(aCtx);
+ return true;
+ }
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return false;
+}
+
+/**
+ * Skip unquoted literals like numbers, |true|, and |null|.
+ * (XXX and anything else that matches /([:alnum:]|[+-.])+/)
+ */
+static bool SkipLiteral(ParserContext& aCtx) {
+ for (; aCtx.mIter < aCtx.mEnd; aCtx.mIter++) {
+ if (!isalnum(*aCtx.mIter) && *aCtx.mIter != '.' && *aCtx.mIter != '-' &&
+ *aCtx.mIter != '+') {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool SkipToken(ParserContext& aCtx) {
+ uint8_t startSym = PeekSymbol(aCtx);
+ if (startSym == '"') {
+ CK_LOGD("JWK parser skipping string");
+ return SkipString(aCtx);
+ } else if (startSym == '{') {
+ CK_LOGD("JWK parser skipping object");
+ return SkipObject(aCtx);
+ } else if (startSym == '[') {
+ CK_LOGD("JWK parser skipping array");
+ return SkipArray(aCtx);
+ } else {
+ CK_LOGD("JWK parser skipping literal");
+ return SkipLiteral(aCtx);
+ }
+
+ return false;
+}
+
+static bool GetNextLabel(ParserContext& aCtx, string& aOutLabel) {
+ EXPECT_SYMBOL(aCtx, '"');
+
+ const uint8_t* start = aCtx.mIter;
+ for (uint8_t sym = GetNextSymbol(aCtx); sym; sym = GetNextSymbol(aCtx)) {
+ if (sym == '\\') {
+ GetNextSymbol(aCtx);
+ continue;
+ }
+
+ if (sym == '"') {
+ aOutLabel.assign(start, aCtx.mIter - 1);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool ParseKeyObject(ParserContext& aCtx, KeyIdPair& aOutKey) {
+ EXPECT_SYMBOL(aCtx, '{');
+
+ // Reject empty objects as invalid licenses.
+ if (PeekSymbol(aCtx) == '}') {
+ GetNextSymbol(aCtx);
+ return false;
+ }
+
+ string keyId;
+ string key;
+
+ while (true) {
+ string label;
+ string value;
+
+ if (!GetNextLabel(aCtx, label)) {
+ return false;
+ }
+
+ EXPECT_SYMBOL(aCtx, ':');
+ if (label == "kty") {
+ if (!GetNextLabel(aCtx, value)) return false;
+ // By spec, type must be "oct".
+ if (value != "oct") return false;
+ } else if (label == "k" && PeekSymbol(aCtx) == '"') {
+ // if this isn't a string we will fall through to the SkipToken() path.
+ if (!GetNextLabel(aCtx, key)) return false;
+ } else if (label == "kid" && PeekSymbol(aCtx) == '"') {
+ if (!GetNextLabel(aCtx, keyId)) return false;
+ } else {
+ if (!SkipToken(aCtx)) return false;
+ }
+
+ uint8_t sym = PeekSymbol(aCtx);
+ if (!sym || sym == '}') {
+ break;
+ }
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return !key.empty() && !keyId.empty() &&
+ DecodeBase64(keyId, aOutKey.mKeyId) &&
+ DecodeBase64(key, aOutKey.mKey) && GetNextSymbol(aCtx) == '}';
+}
+
+static bool ParseKeys(ParserContext& aCtx, vector<KeyIdPair>& aOutKeys) {
+ // Consume start of array.
+ EXPECT_SYMBOL(aCtx, '[');
+
+ while (true) {
+ KeyIdPair key;
+ if (!ParseKeyObject(aCtx, key)) {
+ CK_LOGE("Failed to parse key object");
+ return false;
+ }
+
+ assert(!key.mKey.empty() && !key.mKeyId.empty());
+ aOutKeys.push_back(key);
+
+ uint8_t sym = PeekSymbol(aCtx);
+ if (!sym || sym == ']') {
+ break;
+ }
+
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return GetNextSymbol(aCtx) == ']';
+}
+
+/* static */
+bool ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
+ vector<KeyIdPair>& aOutKeys,
+ SessionType aSessionType) {
+ ParserContext ctx;
+ ctx.mIter = aKeyData;
+ ctx.mEnd = aKeyData + aKeyDataSize;
+
+ // Consume '{' from start of object.
+ EXPECT_SYMBOL(ctx, '{');
+
+ while (true) {
+ string label;
+ // Consume member key.
+ if (!GetNextLabel(ctx, label)) return false;
+ EXPECT_SYMBOL(ctx, ':');
+
+ if (label == "keys") {
+ // Parse "keys" array.
+ if (!ParseKeys(ctx, aOutKeys)) return false;
+ } else if (label == "type") {
+ // Consume type string.
+ string type;
+ if (!GetNextLabel(ctx, type)) return false;
+ if (type != SessionTypeToString(aSessionType)) {
+ return false;
+ }
+ } else {
+ SkipToken(ctx);
+ }
+
+ // Check for end of object.
+ if (PeekSymbol(ctx) == '}') {
+ break;
+ }
+
+ // Consume ',' between object members.
+ EXPECT_SYMBOL(ctx, ',');
+ }
+
+ // Consume '}' from end of object.
+ EXPECT_SYMBOL(ctx, '}');
+
+ return true;
+}
+
+static bool ParseKeyIds(ParserContext& aCtx, vector<KeyId>& aOutKeyIds) {
+ // Consume start of array.
+ EXPECT_SYMBOL(aCtx, '[');
+
+ while (true) {
+ string label;
+ vector<uint8_t> keyId;
+ if (!GetNextLabel(aCtx, label) || !DecodeBase64(label, keyId)) {
+ return false;
+ }
+ if (!keyId.empty() && keyId.size() <= kMaxKeyIdsLength) {
+ aOutKeyIds.push_back(keyId);
+ }
+
+ uint8_t sym = PeekSymbol(aCtx);
+ if (!sym || sym == ']') {
+ break;
+ }
+
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return GetNextSymbol(aCtx) == ']';
+}
+
+/* static */
+bool ClearKeyUtils::ParseKeyIdsInitData(const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ vector<KeyId>& aOutKeyIds) {
+ ParserContext ctx;
+ ctx.mIter = aInitData;
+ ctx.mEnd = aInitData + aInitDataSize;
+
+ // Consume '{' from start of object.
+ EXPECT_SYMBOL(ctx, '{');
+
+ while (true) {
+ string label;
+ // Consume member kids.
+ if (!GetNextLabel(ctx, label)) return false;
+ EXPECT_SYMBOL(ctx, ':');
+
+ if (label == "kids") {
+ // Parse "kids" array.
+ if (!ParseKeyIds(ctx, aOutKeyIds) || aOutKeyIds.empty()) {
+ return false;
+ }
+ } else {
+ SkipToken(ctx);
+ }
+
+ // Check for end of object.
+ if (PeekSymbol(ctx) == '}') {
+ break;
+ }
+
+ // Consume ',' between object members.
+ EXPECT_SYMBOL(ctx, ',');
+ }
+
+ // Consume '}' from end of object.
+ EXPECT_SYMBOL(ctx, '}');
+
+ return true;
+}
+
+/* static */ const char* ClearKeyUtils::SessionTypeToString(
+ SessionType 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";
+ }
+ }
+}
+
+/* static */
+bool ClearKeyUtils::IsValidSessionId(const char* aBuff, uint32_t aLength) {
+ if (aLength > 10) {
+ // 10 is the max number of characters in UINT32_MAX when
+ // represented as a string; ClearKey session ids are integers.
+ return false;
+ }
+ for (uint32_t i = 0; i < aLength; i++) {
+ if (!isdigit(aBuff[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+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();
+}
diff --git a/media/gmp-clearkey/0.1/ClearKeyUtils.h b/media/gmp-clearkey/0.1/ClearKeyUtils.h
new file mode 100644
index 0000000000..04c227c71d
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ClearKeyUtils_h__
+#define __ClearKeyUtils_h__
+
+#include <assert.h>
+// stdef.h is required for content_decryption_module to work on Unix systems.
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "content_decryption_module.h"
+#include "pk11pub.h"
+
+#include "mozilla/Span.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
+
+typedef std::vector<uint8_t> KeyId;
+typedef std::vector<uint8_t> Key;
+
+// The session response size should be within a reasonable limit.
+// The size 64 KB is referenced from web-platform-test.
+static const uint32_t kMaxSessionResponseLength = 65536;
+
+// Provide limitation for KeyIds length and webm initData size.
+static const uint32_t kMaxWebmInitDataSize = 65536;
+static const uint32_t kMaxKeyIdsLength = 512;
+
+void CK_LogArray(const char* aPrepend, const uint8_t* aData,
+ const uint32_t aDataSize);
+
+struct KeyIdPair {
+ KeyId mKeyId;
+ Key mKey;
+};
+
+class ClearKeyUtils {
+ public:
+ static bool DecryptCbcs(const std::vector<uint8_t>& aKey,
+ const std::vector<uint8_t>& aIV,
+ mozilla::Span<uint8_t> aSubsampleEncryptedRange,
+ uint32_t aCryptByteBlocks, uint32_t aSkipByteBlocks);
+
+ static bool DecryptAES(const std::vector<uint8_t>& aKey,
+ std::vector<uint8_t>& aData,
+ std::vector<uint8_t>& aIV);
+
+ static bool ParseKeyIdsInitData(const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ std::vector<KeyId>& aOutKeyIds);
+
+ static void MakeKeyRequest(const std::vector<KeyId>& aKeyIds,
+ std::string& aOutRequest,
+ cdm::SessionType 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);
+
+ 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>
+inline bool Contains(const Container& aContainer, const Element& aElement) {
+ return aContainer.find(aElement) != aContainer.end();
+}
+
+template <typename T>
+inline void Assign(std::vector<T>& aVec, const T* aData, size_t aLength) {
+ aVec.assign(aData, aData + aLength);
+}
+
+#endif // __ClearKeyUtils_h__
diff --git a/media/gmp-clearkey/0.1/RefCounted.h b/media/gmp-clearkey/0.1/RefCounted.h
new file mode 100644
index 0000000000..25f308164c
--- /dev/null
+++ b/media/gmp-clearkey/0.1/RefCounted.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __RefCount_h__
+#define __RefCount_h__
+
+#include <assert.h>
+#include <stdint.h>
+
+#include <atomic>
+
+#include "ClearKeyUtils.h"
+
+// Note: Thread safe.
+class RefCounted {
+ public:
+ void AddRef() { ++mRefCount; }
+
+ uint32_t Release() {
+ uint32_t newCount = --mRefCount;
+ if (!newCount) {
+ delete this;
+ }
+ return newCount;
+ }
+
+ protected:
+ RefCounted() : mRefCount(0) {}
+ virtual ~RefCounted() { assert(!mRefCount); }
+ std::atomic<uint32_t> mRefCount;
+};
+
+template <class T>
+class RefPtr {
+ public:
+ RefPtr(const RefPtr& src) { Set(src.mPtr); }
+
+ explicit RefPtr(T* aPtr) { Set(aPtr); }
+ RefPtr() { Set(nullptr); }
+
+ ~RefPtr() { Set(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);
+ return *this;
+ }
+
+ private:
+ T* Set(T* aPtr) {
+ if (mPtr == aPtr) {
+ return aPtr;
+ }
+ if (mPtr) {
+ mPtr->Release();
+ }
+ mPtr = aPtr;
+ if (mPtr) {
+ aPtr->AddRef();
+ }
+ return mPtr;
+ }
+
+ T* mPtr = nullptr;
+};
+
+#endif // __RefCount_h__
diff --git a/media/gmp-clearkey/0.1/VideoDecoder.cpp b/media/gmp-clearkey/0.1/VideoDecoder.cpp
new file mode 100644
index 0000000000..570eb99357
--- /dev/null
+++ b/media/gmp-clearkey/0.1/VideoDecoder.cpp
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+
+#include "BigEndian.h"
+#include "ClearKeyUtils.h"
+#include "ClearKeyDecryptionManager.h"
+#include "VideoDecoder.h"
+#include "mozilla/CheckedInt.h"
+
+using namespace wmf;
+
+VideoDecoder::VideoDecoder(cdm::Host_10* aHost)
+ : mHost(aHost), mHasShutdown(false) {
+ CK_LOGD("VideoDecoder created");
+
+ // 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);
+ if (FAILED(hr)) {
+ CK_LOGE("Failed to initialize mDecoder!");
+ }
+}
+
+VideoDecoder::~VideoDecoder() { CK_LOGD("VideoDecoder destroyed"); }
+
+cdm::Status VideoDecoder::InitDecode(const cdm::VideoDecoderConfig_2& aConfig) {
+ CK_LOGD("VideoDecoder::InitDecode");
+
+ if (!mDecoder) {
+ CK_LOGD("VideoDecoder::InitDecode failed to init WMFH264Decoder");
+
+ return cdm::Status::kDecodeError;
+ }
+
+ return cdm::Status::kSuccess;
+}
+
+cdm::Status VideoDecoder::Decode(const cdm::InputBuffer_2& aInputBuffer,
+ cdm::VideoFrame* aVideoFrame) {
+ CK_LOGD("VideoDecoder::Decode");
+ // 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("VideoDecoder::Decode Input buffer null: Draining");
+ return Drain(aVideoFrame);
+ }
+
+ DecodeData* data = new DecodeData();
+ Assign(data->mBuffer, aInputBuffer.data, aInputBuffer.data_size);
+ data->mTimestamp = aInputBuffer.timestamp;
+ data->mCrypto = CryptoMetaData(&aInputBuffer);
+
+ AutoPtr<DecodeData> d(data);
+ HRESULT hr;
+
+ if (!data || !mDecoder) {
+ CK_LOGE("Decode job not set up correctly!");
+ return cdm::Status::kDecodeError;
+ }
+
+ std::vector<uint8_t>& buffer = data->mBuffer;
+
+ if (data->mCrypto.IsValid()) {
+ Status rv =
+ ClearKeyDecryptionManager::Get()->Decrypt(buffer, data->mCrypto);
+
+ if (STATUS_FAILED(rv)) {
+ CK_LOGARRAY("Failed to decrypt video using key ", aInputBuffer.key_id,
+ aInputBuffer.key_id_size);
+ return rv;
+ }
+ }
+
+ hr = mDecoder->Input(buffer.data(), buffer.size(), data->mTimestamp);
+
+ CK_LOGD("VideoDecoder::Decode() Input ret hr=0x%x", hr);
+
+ if (FAILED(hr)) {
+ assert(hr != MF_E_TRANSFORM_NEED_MORE_INPUT);
+
+ CK_LOGE("VideoDecoder::Decode() decode failed ret=0x%x%s", hr,
+ ((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
+ CK_LOGD("Decode failed. The decoder is not accepting input");
+ return cdm::Status::kDecodeError;
+ }
+
+ return OutputFrame(aVideoFrame);
+}
+
+cdm::Status VideoDecoder::OutputFrame(cdm::VideoFrame* aVideoFrame) {
+ CK_LOGD("VideoDecoder::OutputFrame");
+
+ 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) {
+ CComPtr<IMFSample> output;
+ hr = mDecoder->Output(&output);
+
+ if (hr != S_OK) {
+ break;
+ }
+
+ CK_LOGD("VideoDecoder::OutputFrame Decoder output ret=0x%x", hr);
+
+ mOutputQueue.push(output);
+ CK_LOGD("VideoDecoder::OutputFrame: Queue size: %u", mOutputQueue.size());
+ }
+
+ // 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 cdm::Status::kNeedMoreData;
+ }
+
+ // 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", hr);
+ return cdm::Status::kDecodeError;
+ }
+
+ 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) {
+ CK_LOGD("VideoDecoder::OutputFrame Failed! (negative stride)");
+ return cdm::Status::kDecodeError;
+ }
+
+ const IntRect& picture = mDecoder->GetPictureRegion();
+ hr = SampleToVideoFrame(result, picture.width, picture.height,
+ mDecoder->GetStride(), mDecoder->GetFrameHeight(),
+ aVideoFrame);
+ if (FAILED(hr)) {
+ CK_LOGD("VideoDecoder::OutputFrame Failed!");
+ return cdm::Status::kDecodeError;
+ }
+
+ CK_LOGD("VideoDecoder::OutputFrame Succeeded.");
+ return cdm::Status::kSuccess;
+}
+
+HRESULT
+VideoDecoder::SampleToVideoFrame(IMFSample* aSample, int32_t aPictureWidth,
+ int32_t aPictureHeight, int32_t aStride,
+ int32_t aFrameHeight,
+ cdm::VideoFrame* aVideoFrame) {
+ CK_LOGD("[%p] VideoDecoder::SampleToVideoFrame()", this);
+
+ 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);
+
+ // Try and use the IMF2DBuffer interface if available, otherwise fallback
+ // to the IMFMediaBuffer interface. Apparently IMF2DBuffer is more efficient,
+ // but only some systems (Windows 8?) support it.
+ BYTE* data = nullptr;
+ LONG stride = 0;
+ CComPtr<IMF2DBuffer> twoDBuffer;
+ hr = mediaBuffer->QueryInterface(static_cast<IMF2DBuffer**>(&twoDBuffer));
+ if (SUCCEEDED(hr)) {
+ hr = twoDBuffer->Lock2D(&data, &stride);
+ ENSURE(SUCCEEDED(hr), hr);
+ } else {
+ hr = mediaBuffer->Lock(&data, nullptr, nullptr);
+ ENSURE(SUCCEEDED(hr), hr);
+ stride = aStride;
+ }
+
+ // WMF stores the U and V planes 16-row-aligned, so we need to add padding
+ // to the row heights to ensure the source offsets of the Y'CbCr planes are
+ // referenced properly.
+ // YV12, planar format: [YYYY....][UUUU....][VVVV....]
+ // i.e., Y, then U, then V.
+ uint32_t padding = 0;
+ if (aFrameHeight % 16 != 0) {
+ padding = 16 - (aFrameHeight % 16);
+ }
+ uint32_t srcYSize = stride * (aFrameHeight + padding);
+ uint32_t srcUVSize = stride * (aFrameHeight + padding) / 4;
+ uint32_t halfStride = (stride + 1) / 2;
+
+ aVideoFrame->SetStride(cdm::VideoPlane::kYPlane, stride);
+ aVideoFrame->SetStride(cdm::VideoPlane::kUPlane, halfStride);
+ aVideoFrame->SetStride(cdm::VideoPlane::kVPlane, halfStride);
+
+ aVideoFrame->SetSize(cdm::Size{aPictureWidth, aPictureHeight});
+
+ // Note: We allocate the minimal sized buffer required to send the
+ // frame back over to the parent process. This is so that we request the
+ // same sized frame as the buffer allocator expects.
+ using mozilla::CheckedUint32;
+ CheckedUint32 bufferSize = CheckedUint32(stride) * aPictureHeight +
+ ((CheckedUint32(stride) * aPictureHeight) / 4) * 2;
+
+ // If the buffer is bigger than the max for a 32 bit, fail to avoid buffer
+ // overflows.
+ if (!bufferSize.isValid()) {
+ CK_LOGD("VideoDecoder::SampleToFrame Buffersize bigger than UINT32_MAX");
+ return E_FAIL;
+ }
+
+ // Get the buffer from the host.
+ cdm::Buffer* buffer = mHost->Allocate(bufferSize.value());
+ aVideoFrame->SetFrameBuffer(buffer);
+
+ // Make sure the buffer is non-null (allocate guarantees it will be of
+ // sufficient size).
+ if (!buffer) {
+ CK_LOGD("VideoDecoder::SampleToFrame Out of memory");
+ return E_OUTOFMEMORY;
+ }
+
+ uint8_t* outBuffer = buffer->Data();
+
+ aVideoFrame->SetPlaneOffset(cdm::VideoPlane::kYPlane, 0);
+
+ // Offset of U plane is the size of the Y plane, excluding the padding that
+ // WMF adds.
+ uint32_t dstUOffset = stride * aPictureHeight;
+ aVideoFrame->SetPlaneOffset(cdm::VideoPlane::kUPlane, dstUOffset);
+
+ // Offset of the V plane is the size of the Y plane + the size of the U plane,
+ // excluding any padding WMF adds.
+ uint32_t dstVOffset = stride * aPictureHeight + (stride * aPictureHeight) / 4;
+ aVideoFrame->SetPlaneOffset(cdm::VideoPlane::kVPlane, dstVOffset);
+
+ // Copy the pixel data, excluding WMF's padding.
+ memcpy(outBuffer, data, stride * aPictureHeight);
+ memcpy(outBuffer + dstUOffset, data + srcYSize,
+ (stride * aPictureHeight) / 4);
+ memcpy(outBuffer + dstVOffset, data + srcYSize + srcUVSize,
+ (stride * aPictureHeight) / 4);
+
+ if (twoDBuffer) {
+ twoDBuffer->Unlock2D();
+ } else {
+ mediaBuffer->Unlock();
+ }
+
+ LONGLONG hns = 0;
+ hr = aSample->GetSampleTime(&hns);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ aVideoFrame->SetTimestamp(HNsToUsecs(hns));
+
+ return S_OK;
+}
+
+void VideoDecoder::Reset() {
+ CK_LOGD("VideoDecoder::Reset");
+
+ if (mDecoder) {
+ mDecoder->Reset();
+ }
+
+ // Remove all the frames from the output queue.
+ while (!mOutputQueue.empty()) {
+ mOutputQueue.pop();
+ }
+}
+
+cdm::Status VideoDecoder::Drain(cdm::VideoFrame* aVideoFrame) {
+ 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);
+}
+
+void VideoDecoder::DecodingComplete() {
+ CK_LOGD("VideoDecoder::DecodingComplete()");
+
+ mHasShutdown = true;
+
+ // Release the reference we added in the constructor. There may be
+ // WrapRefCounted tasks that also hold references to us, and keep
+ // us alive a little longer.
+ Release();
+}
diff --git a/media/gmp-clearkey/0.1/VideoDecoder.h b/media/gmp-clearkey/0.1/VideoDecoder.h
new file mode 100644
index 0000000000..6a1544ce65
--- /dev/null
+++ b/media/gmp-clearkey/0.1/VideoDecoder.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __VideoDecoder_h__
+#define __VideoDecoder_h__
+
+// This include is required in order for content_decryption_module to work
+// on Unix systems.
+#include <stddef.h>
+
+#include <atomic>
+#include <queue>
+#include <thread>
+
+#include "content_decryption_module.h"
+#include "WMFH264Decoder.h"
+
+class VideoDecoder : public RefCounted {
+ public:
+ explicit VideoDecoder(cdm::Host_10* aHost);
+
+ cdm::Status InitDecode(const cdm::VideoDecoderConfig_2& aConfig);
+
+ cdm::Status Decode(const cdm::InputBuffer_2& aEncryptedBuffer,
+ cdm::VideoFrame* aVideoFrame);
+
+ void Reset();
+
+ void DecodingComplete();
+
+ bool HasShutdown() { return mHasShutdown; }
+
+ private:
+ virtual ~VideoDecoder();
+
+ cdm::Status Drain(cdm::VideoFrame* aVideoFrame);
+
+ struct DecodeData {
+ std::vector<uint8_t> mBuffer;
+ uint64_t mTimestamp = 0;
+ CryptoMetaData mCrypto;
+ };
+
+ cdm::Status OutputFrame(cdm::VideoFrame* aVideoFrame);
+
+ HRESULT SampleToVideoFrame(IMFSample* aSample, int32_t aPictureWidth,
+ int32_t aPictureHeight, int32_t aStride,
+ int32_t aFrameHeight,
+ cdm::VideoFrame* aVideoFrame);
+
+ cdm::Host_10* mHost;
+ wmf::AutoPtr<wmf::WMFH264Decoder> mDecoder;
+
+ std::queue<wmf::CComPtr<IMFSample>> mOutputQueue;
+
+ bool mHasShutdown;
+};
+
+#endif // __VideoDecoder_h__
diff --git a/media/gmp-clearkey/0.1/WMFH264Decoder.cpp b/media/gmp-clearkey/0.1/WMFH264Decoder.cpp
new file mode 100644
index 0000000000..8054b38c64
--- /dev/null
+++ b/media/gmp-clearkey/0.1/WMFH264Decoder.cpp
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "WMFH264Decoder.h"
+
+#include <codecapi.h>
+
+#include <algorithm>
+
+namespace wmf {
+
+WMFH264Decoder::WMFH264Decoder() : mDecoder(nullptr) {
+ memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO));
+ memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO));
+}
+
+WMFH264Decoder::~WMFH264Decoder() {}
+
+HRESULT
+WMFH264Decoder::Init(int32_t aCoreCount) {
+ HRESULT hr;
+
+ hr = CreateMFT(__uuidof(CMSH264DecoderMFT), WMFDecoderDllName(), mDecoder);
+ if (FAILED(hr)) {
+ // Windows 7 Enterprise Server N (which is what Mozilla's mochitests run
+ // on) need a different CLSID to instantiate the H.264 decoder.
+ hr = CreateMFT(CLSID_CMSH264DecMFT, WMFDecoderDllName(), mDecoder);
+ }
+ ENSURE(SUCCEEDED(hr), hr);
+
+ CComPtr<IMFAttributes> attr;
+ hr = mDecoder->GetAttributes(&attr);
+ ENSURE(SUCCEEDED(hr), hr);
+ hr = attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads,
+ GetNumThreads(aCoreCount));
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = SetDecoderInputType();
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = SetDecoderOutputType();
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->GetInputStreamInfo(0, &mInputStreamInfo);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::ConfigureVideoFrameGeometry(IMFMediaType* aMediaType) {
+ ENSURE(aMediaType != nullptr, E_POINTER);
+ HRESULT hr;
+
+ IntRect pictureRegion;
+ hr = wmf::GetPictureRegion(aMediaType, pictureRegion);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ UINT32 width = 0, height = 0;
+ hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height);
+ ENSURE(SUCCEEDED(hr), hr);
+ ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+ ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL);
+
+ UINT32 stride = 0;
+ hr = GetDefaultStride(aMediaType, &stride);
+ ENSURE(SUCCEEDED(hr), hr);
+ ENSURE(stride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+
+ // Success! Save state.
+ mStride = stride;
+ mVideoWidth = width;
+ mVideoHeight = height;
+ mPictureRegion = pictureRegion;
+
+ LOG("WMFH264Decoder frame geometry frame=(%u,%u) stride=%u picture=(%d, %d, "
+ "%d, %d)\n",
+ width, height, mStride, mPictureRegion.x, mPictureRegion.y,
+ mPictureRegion.width, mPictureRegion.height);
+
+ return S_OK;
+}
+
+int32_t WMFH264Decoder::GetFrameHeight() const { return mVideoHeight; }
+
+const IntRect& WMFH264Decoder::GetPictureRegion() const {
+ return mPictureRegion;
+}
+
+int32_t WMFH264Decoder::GetStride() const { return mStride; }
+
+HRESULT
+WMFH264Decoder::SetDecoderInputType() {
+ HRESULT hr;
+
+ CComPtr<IMFMediaType> type;
+ hr = MFCreateMediaType(&type);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = type->SetUINT32(MF_MT_INTERLACE_MODE,
+ MFVideoInterlace_MixedInterlaceOrProgressive);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->SetInputType(0, type, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::SetDecoderOutputType() {
+ HRESULT hr;
+
+ CComPtr<IMFMediaType> type;
+
+ UINT32 typeIndex = 0;
+ while (static_cast<void>(type = nullptr),
+ SUCCEEDED(mDecoder->GetOutputAvailableType(0, typeIndex++, &type))) {
+ GUID subtype;
+ hr = type->GetGUID(MF_MT_SUBTYPE, &subtype);
+ if (FAILED(hr)) {
+ continue;
+ }
+ if (subtype == MFVideoFormat_I420 || subtype == MFVideoFormat_IYUV) {
+ // On Windows 7 Enterprise N the MFT reports it reports IYUV instead
+ // of I420. Other Windows' report I420. The formats are the same, so
+ // support both.
+ hr = mDecoder->SetOutputType(0, type, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = ConfigureVideoFrameGeometry(type);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+ }
+ }
+
+ return E_FAIL;
+}
+
+HRESULT
+WMFH264Decoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData) {
+ ENSURE(mDecoder != nullptr, E_POINTER);
+ HRESULT hr = mDecoder->ProcessMessage(aMsg, aData);
+ ENSURE(SUCCEEDED(hr), hr);
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::CreateInputSample(const uint8_t* aData, uint32_t aDataSize,
+ Microseconds aTimestamp,
+ IMFSample** aOutSample) {
+ HRESULT hr;
+ CComPtr<IMFSample> sample;
+ hr = MFCreateSample(&sample);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ CComPtr<IMFMediaBuffer> buffer;
+ int32_t bufferSize =
+ std::max<uint32_t>(uint32_t(mInputStreamInfo.cbSize), aDataSize);
+ UINT32 alignment =
+ (mInputStreamInfo.cbAlignment > 1) ? mInputStreamInfo.cbAlignment - 1 : 0;
+ hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ DWORD maxLength = 0;
+ DWORD currentLength = 0;
+ BYTE* dst = nullptr;
+ hr = buffer->Lock(&dst, &maxLength, &currentLength);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ // Copy data into sample's buffer.
+ memcpy(dst, aData, aDataSize);
+
+ hr = buffer->Unlock();
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = buffer->SetCurrentLength(aDataSize);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = sample->AddBuffer(buffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = sample->SetSampleTime(UsecsToHNs(aTimestamp));
+ ENSURE(SUCCEEDED(hr), hr);
+
+ *aOutSample = sample.Detach();
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::CreateOutputSample(IMFSample** aOutSample) {
+ HRESULT hr;
+ CComPtr<IMFSample> sample;
+ hr = MFCreateSample(&sample);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ CComPtr<IMFMediaBuffer> buffer;
+ int32_t bufferSize = mOutputStreamInfo.cbSize;
+ UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1)
+ ? mOutputStreamInfo.cbAlignment - 1
+ : 0;
+ hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = sample->AddBuffer(buffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ *aOutSample = sample.Detach();
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::GetOutputSample(IMFSample** aOutSample) {
+ HRESULT hr;
+ // We allocate samples for MFT output.
+ MFT_OUTPUT_DATA_BUFFER output = {0};
+
+ CComPtr<IMFSample> sample;
+ hr = CreateOutputSample(&sample);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ output.pSample = sample;
+
+ DWORD status = 0;
+ hr = mDecoder->ProcessOutput(0, 1, &output, &status);
+ // LOG(L"WMFH264Decoder::GetOutputSample() ProcessOutput returned 0x%x\n",
+ // hr);
+ CComPtr<IMFCollection> events = output.pEvents; // Ensure this is released.
+
+ if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+ // Type change. Probably geometric apperature change.
+ hr = SetDecoderOutputType();
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return GetOutputSample(aOutSample);
+ } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ return MF_E_TRANSFORM_NEED_MORE_INPUT;
+ }
+ // Treat other errors as fatal.
+ ENSURE(SUCCEEDED(hr), hr);
+
+ assert(sample);
+
+ // output.pSample
+ *aOutSample = sample.Detach(); // AddRefs
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::Input(const uint8_t* aData, uint32_t aDataSize,
+ Microseconds aTimestamp) {
+ HRESULT hr;
+ CComPtr<IMFSample> input = nullptr;
+ hr = CreateInputSample(aData, aDataSize, aTimestamp, &input);
+ ENSURE(SUCCEEDED(hr) && input != nullptr, hr);
+
+ hr = mDecoder->ProcessInput(0, input, 0);
+ if (hr == MF_E_NOTACCEPTING) {
+ // MFT *already* has enough data to produce a sample. Retrieve it.
+ LOG("ProcessInput returned MF_E_NOTACCEPTING\n");
+ return MF_E_NOTACCEPTING;
+ }
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::Output(IMFSample** aOutput) {
+ HRESULT hr;
+ CComPtr<IMFSample> outputSample;
+ hr = GetOutputSample(&outputSample);
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ return MF_E_TRANSFORM_NEED_MORE_INPUT;
+ }
+ // Treat other errors as fatal.
+ ENSURE(SUCCEEDED(hr) && outputSample, hr);
+
+ *aOutput = outputSample.Detach();
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::Reset() {
+ HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::Drain() {
+ HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ return S_OK;
+}
+
+} // namespace wmf
diff --git a/media/gmp-clearkey/0.1/WMFH264Decoder.h b/media/gmp-clearkey/0.1/WMFH264Decoder.h
new file mode 100644
index 0000000000..3f74735e23
--- /dev/null
+++ b/media/gmp-clearkey/0.1/WMFH264Decoder.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(WMFH264Decoder_h_)
+# define WMFH264Decoder_h_
+
+# include "WMFUtils.h"
+
+namespace wmf {
+
+class WMFH264Decoder {
+ public:
+ WMFH264Decoder();
+ ~WMFH264Decoder();
+
+ HRESULT Init(int32_t aCoreCount);
+
+ HRESULT Input(const uint8_t* aData, uint32_t aDataSize,
+ Microseconds aTimestamp);
+
+ HRESULT Output(IMFSample** aOutput);
+
+ HRESULT Reset();
+
+ int32_t GetFrameHeight() const;
+ const IntRect& GetPictureRegion() const;
+ int32_t GetStride() const;
+
+ HRESULT Drain();
+
+ private:
+ HRESULT SetDecoderInputType();
+ HRESULT SetDecoderOutputType();
+ HRESULT SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData);
+
+ HRESULT CreateInputSample(const uint8_t* aData, uint32_t aDataSize,
+ Microseconds aTimestamp, IMFSample** aOutSample);
+
+ HRESULT CreateOutputSample(IMFSample** aOutSample);
+
+ HRESULT GetOutputSample(IMFSample** aOutSample);
+ HRESULT ConfigureVideoFrameGeometry(IMFMediaType* aMediaType);
+
+ MFT_INPUT_STREAM_INFO mInputStreamInfo;
+ MFT_OUTPUT_STREAM_INFO mOutputStreamInfo;
+
+ CComPtr<IMFTransform> mDecoder;
+
+ int32_t mVideoWidth;
+ int32_t mVideoHeight;
+ IntRect mPictureRegion;
+ int32_t mStride;
+};
+
+} // namespace wmf
+
+#endif
diff --git a/media/gmp-clearkey/0.1/WMFSymbols.h b/media/gmp-clearkey/0.1/WMFSymbols.h
new file mode 100644
index 0000000000..60a0a6854b
--- /dev/null
+++ b/media/gmp-clearkey/0.1/WMFSymbols.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+MFPLAT_FUNC(MFCreateSample, "mfplat.dll");
+MFPLAT_FUNC(MFCreateAlignedMemoryBuffer, "mfplat.dll");
+MFPLAT_FUNC(MFGetStrideForBitmapInfoHeader, "evr.dll");
+MFPLAT_FUNC(MFCreateMediaType, "mfplat.dll");
diff --git a/media/gmp-clearkey/0.1/WMFUtils.cpp b/media/gmp-clearkey/0.1/WMFUtils.cpp
new file mode 100644
index 0000000000..5aa628f619
--- /dev/null
+++ b/media/gmp-clearkey/0.1/WMFUtils.cpp
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "WMFUtils.h"
+
+#define INITGUID
+#include <guiddef.h>
+#include <stdio.h>
+#include <versionhelpers.h>
+
+#include <algorithm>
+
+#include "ClearKeyUtils.h"
+
+#ifndef __MINGW32__
+# pragma comment(lib, "mfuuid.lib")
+# pragma comment(lib, "wmcodecdspuuid")
+#endif
+
+void LOG(const char* format, ...) {
+#ifdef WMF_DECODER_LOG
+ va_list args;
+ va_start(args, format);
+ vprintf(format, args);
+#endif
+}
+
+DEFINE_GUID(CLSID_CMSH264DecMFT, 0x62CE7E72, 0x4C71, 0x4d20, 0xB1, 0x5D, 0x45,
+ 0x28, 0x31, 0xA8, 0x7D, 0x9D);
+
+namespace wmf {
+
+#define MFPLAT_FUNC(_func, _dllname) decltype(::_func)* _func;
+#include "WMFSymbols.h"
+#undef MFPLAT_FUNC
+
+static bool LinkMfplat() {
+ static bool sInitDone = false;
+ static bool sInitOk = false;
+ if (!sInitDone) {
+ sInitDone = true;
+ HMODULE handle;
+
+#define MFPLAT_FUNC(_func, _dllname) \
+ handle = GetModuleHandleA(_dllname); \
+ if (!(_func = (decltype(_func))(GetProcAddress(handle, #_func)))) { \
+ return false; \
+ }
+
+#include "WMFSymbols.h"
+#undef MFPLAT_FUNC
+ sInitOk = true;
+ }
+ return sInitOk;
+}
+
+bool EnsureLibs() {
+ static bool sInitDone = false;
+ static bool sInitOk = false;
+ if (!sInitDone) {
+ sInitOk = LinkMfplat() && !!GetModuleHandleA(WMFDecoderDllName());
+ sInitDone = true;
+ }
+ return sInitOk;
+}
+
+int32_t MFOffsetToInt32(const MFOffset& aOffset) {
+ return int32_t(aOffset.value + (aOffset.fract / 65536.0f));
+}
+
+// Gets the sub-region of the video frame that should be displayed.
+// See:
+// http://msdn.microsoft.com/en-us/library/windows/desktop/bb530115(v=vs.85).aspx
+HRESULT
+GetPictureRegion(IMFMediaType* aMediaType, IntRect& aOutPictureRegion) {
+ // Determine if "pan and scan" is enabled for this media. If it is, we
+ // only display a region of the video frame, not the entire frame.
+ BOOL panScan =
+ MFGetAttributeUINT32(aMediaType, MF_MT_PAN_SCAN_ENABLED, FALSE);
+
+ // If pan and scan mode is enabled. Try to get the display region.
+ HRESULT hr = E_FAIL;
+ MFVideoArea videoArea;
+ memset(&videoArea, 0, sizeof(MFVideoArea));
+ if (panScan) {
+ hr = aMediaType->GetBlob(MF_MT_PAN_SCAN_APERTURE, (UINT8*)&videoArea,
+ sizeof(MFVideoArea), NULL);
+ }
+
+ // If we're not in pan-and-scan mode, or the pan-and-scan region is not set,
+ // check for a minimimum display aperture.
+ if (!panScan || hr == MF_E_ATTRIBUTENOTFOUND) {
+ hr = aMediaType->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, (UINT8*)&videoArea,
+ sizeof(MFVideoArea), NULL);
+ }
+
+ if (hr == MF_E_ATTRIBUTENOTFOUND) {
+ // Minimum display aperture is not set, for "backward compatibility with
+ // some components", check for a geometric aperture.
+ hr = aMediaType->GetBlob(MF_MT_GEOMETRIC_APERTURE, (UINT8*)&videoArea,
+ sizeof(MFVideoArea), NULL);
+ }
+
+ if (SUCCEEDED(hr)) {
+ // The media specified a picture region, return it.
+ IntRect picture = IntRect(MFOffsetToInt32(videoArea.OffsetX),
+ MFOffsetToInt32(videoArea.OffsetY),
+ videoArea.Area.cx, videoArea.Area.cy);
+ ENSURE(picture.width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+ ENSURE(picture.height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL);
+ aOutPictureRegion = picture;
+ return S_OK;
+ }
+
+ // No picture region defined, fall back to using the entire video area.
+ UINT32 width = 0, height = 0;
+ hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height);
+ ENSURE(SUCCEEDED(hr), hr);
+ ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+ ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL);
+ aOutPictureRegion = IntRect(0, 0, width, height);
+ return S_OK;
+}
+
+HRESULT
+GetDefaultStride(IMFMediaType* aType, uint32_t* aOutStride) {
+ // Try to get the default stride from the media type.
+ UINT32 stride = 0;
+ HRESULT hr = aType->GetUINT32(MF_MT_DEFAULT_STRIDE, &stride);
+ if (SUCCEEDED(hr)) {
+ ENSURE(stride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+ *aOutStride = stride;
+ return S_OK;
+ }
+
+ // Stride attribute not set, calculate it.
+ GUID subtype = GUID_NULL;
+ uint32_t width = 0;
+ uint32_t height = 0;
+
+ hr = aType->GetGUID(MF_MT_SUBTYPE, &subtype);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ hr = MFGetAttributeSize(aType, MF_MT_FRAME_SIZE, &width, &height);
+ ENSURE(SUCCEEDED(hr), hr);
+ ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+ ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL);
+
+ LONG lstride = 0;
+ hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, &lstride);
+ ENSURE(SUCCEEDED(hr), hr);
+ ENSURE(lstride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL);
+ ENSURE(lstride >= 0, E_FAIL);
+ *aOutStride = lstride;
+
+ return hr;
+}
+
+void dump(const uint8_t* data, uint32_t len, const char* filename) {
+ FILE* f = 0;
+ fopen_s(&f, filename, "wb");
+ fwrite(data, len, 1, f);
+ fclose(f);
+}
+
+HRESULT
+CreateMFT(const CLSID& clsid, const char* aDllName,
+ CComPtr<IMFTransform>& aOutMFT) {
+ HMODULE module = ::GetModuleHandleA(aDllName);
+ if (!module) {
+ LOG("Failed to get %S\n", aDllName);
+ return E_FAIL;
+ }
+
+ typedef HRESULT(WINAPI * DllGetClassObjectFnPtr)(
+ const CLSID& clsid, const IID& iid, void** object);
+
+ DllGetClassObjectFnPtr GetClassObjPtr =
+ reinterpret_cast<DllGetClassObjectFnPtr>(
+ GetProcAddress(module, "DllGetClassObject"));
+ if (!GetClassObjPtr) {
+ LOG("Failed to get DllGetClassObject\n");
+ return E_FAIL;
+ }
+
+ CComPtr<IClassFactory> classFactory;
+ HRESULT hr = GetClassObjPtr(
+ clsid, __uuidof(IClassFactory),
+ reinterpret_cast<void**>(static_cast<IClassFactory**>(&classFactory)));
+ if (FAILED(hr)) {
+ LOG("Failed to get H264 IClassFactory\n");
+ return E_FAIL;
+ }
+
+ hr = classFactory->CreateInstance(
+ NULL, __uuidof(IMFTransform),
+ reinterpret_cast<void**>(static_cast<IMFTransform**>(&aOutMFT)));
+ if (FAILED(hr)) {
+ LOG("Failed to get create MFT\n");
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+int32_t GetNumThreads(int32_t aCoreCount) {
+ return aCoreCount > 4 ? -1 : (std::max)(aCoreCount - 1, 1);
+}
+
+} // namespace wmf
diff --git a/media/gmp-clearkey/0.1/WMFUtils.h b/media/gmp-clearkey/0.1/WMFUtils.h
new file mode 100644
index 0000000000..2b4754c7b0
--- /dev/null
+++ b/media/gmp-clearkey/0.1/WMFUtils.h
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __WMFUtils_h__
+#define __WMFUtils_h__
+
+#include <assert.h>
+#include <mfapi.h>
+#include <mferror.h>
+#include <mfobjects.h>
+#include <mftransform.h>
+#include <wmcodecdsp.h>
+
+#include <cstdint>
+#include <string>
+
+#include "VideoLimits.h"
+#include "gmp-platform.h"
+#include "mozilla/Attributes.h"
+
+void LOG(const char* format, ...);
+
+#ifdef LOG_SAMPLE_DECODE
+# define SAMPLE_LOG LOG
+#else
+# define SAMPLE_LOG(...)
+#endif
+
+#ifndef CLSID_CMSAACDecMFT
+# define WMF_MUST_DEFINE_AAC_MFT_CLSID
+extern "C" const CLSID CLSID_CMSAACDecMFT;
+#endif
+
+extern "C" const CLSID CLSID_CMSH264DecMFT;
+
+namespace wmf {
+
+// Reimplementation of CComPtr to reduce dependence on system
+// shared libraries.
+template <class T>
+class CComPtr {
+ public:
+ CComPtr(CComPtr&& aOther) : mPtr(aOther.Detach()) {}
+ CComPtr& operator=(CComPtr&& other) { mPtr = other.Detach(); }
+
+ CComPtr(const CComPtr& aOther) : mPtr(nullptr) { Set(aOther.Get()); }
+ CComPtr() : mPtr(nullptr) {}
+ MOZ_IMPLICIT CComPtr(T* const& aPtr) : mPtr(nullptr) { Set(aPtr); }
+ MOZ_IMPLICIT CComPtr(const std::nullptr_t& aNullPtr) : mPtr(aNullPtr) {}
+ T** operator&() { return &mPtr; }
+ T* operator->() { return mPtr; }
+ operator T*() { return mPtr; }
+ T* operator=(T* const& aPtr) { return Set(aPtr); }
+ T* operator=(const std::nullptr_t& aPtr) { return mPtr = aPtr; }
+
+ T* Get() const { return mPtr; }
+
+ T* Detach() {
+ T* tmp = mPtr;
+ mPtr = nullptr;
+ return tmp;
+ }
+
+ ~CComPtr() {
+ if (mPtr) {
+ mPtr->Release();
+ }
+ mPtr = nullptr;
+ }
+
+ private:
+ T* Set(T* aPtr) {
+ if (mPtr == aPtr) {
+ return aPtr;
+ }
+ if (mPtr) {
+ mPtr->Release();
+ }
+ mPtr = aPtr;
+ if (mPtr) {
+ mPtr->AddRef();
+ }
+ return mPtr;
+ }
+
+ T* mPtr;
+};
+
+class IntRect {
+ public:
+ IntRect(int32_t _x, int32_t _y, int32_t _w, int32_t _h)
+ : x(_x), y(_y), width(_w), height(_h) {}
+ IntRect() : x(0), y(0), width(0), height(0) {}
+ int32_t x;
+ int32_t y;
+ int32_t width;
+ int32_t height;
+};
+
+typedef int64_t Microseconds;
+
+#ifdef ENSURE
+# undef ENSURE
+#endif
+
+#define ENSURE(condition, ret) \
+ { \
+ if (!(condition)) { \
+ LOG("##condition## FAILED %S:%d\n", __FILE__, __LINE__); \
+ return ret; \
+ } \
+ }
+
+#define STATUS_SUCCEEDED(x) ((x) == Status::kSuccess)
+#define STATUS_FAILED(x) ((x) != Status::kSuccess)
+
+#define MFPLAT_FUNC(_func, _dllname) extern decltype(::_func)* _func;
+#include "WMFSymbols.h"
+#undef MFPLAT_FUNC
+
+bool EnsureLibs();
+
+HRESULT
+GetPictureRegion(IMFMediaType* aMediaType, IntRect& aOutPictureRegion);
+
+HRESULT
+GetDefaultStride(IMFMediaType* aType, uint32_t* aOutStride);
+
+// Converts from microseconds to hundreds of nanoseconds.
+// We use microseconds for our timestamps, whereas WMF uses
+// hundreds of nanoseconds.
+inline int64_t UsecsToHNs(int64_t aUsecs) { return aUsecs * 10; }
+
+// Converts from hundreds of nanoseconds to microseconds.
+// We use microseconds for our timestamps, whereas WMF uses
+// hundreds of nanoseconds.
+inline int64_t HNsToUsecs(int64_t hNanoSecs) { return hNanoSecs / 10; }
+
+inline std::string narrow(std::wstring& wide) {
+ std::string ns(wide.begin(), wide.end());
+ return ns;
+}
+
+inline std::wstring widen(std::string& narrow) {
+ std::wstring ws(narrow.begin(), narrow.end());
+ return ws;
+}
+
+#define ARRAY_LENGTH(array_) (sizeof(array_) / sizeof(array_[0]))
+
+template <class Type>
+class AutoPtr {
+ public:
+ AutoPtr() : mPtr(nullptr) {}
+
+ AutoPtr(AutoPtr<Type>& aPtr) : mPtr(aPtr.Forget()) {}
+
+ explicit AutoPtr(Type* aPtr) : mPtr(aPtr) {}
+
+ ~AutoPtr() {
+ if (mPtr) {
+ delete mPtr;
+ }
+ }
+
+ Type* Forget() {
+ Type* rv = mPtr;
+ mPtr = nullptr;
+ return rv;
+ }
+
+ AutoPtr<Type>& operator=(Type* aOther) {
+ Assign(aOther);
+ return *this;
+ }
+
+ AutoPtr<Type>& operator=(AutoPtr<Type>& aOther) {
+ Assign(aOther.Forget());
+ return *this;
+ }
+
+ Type* Get() const { return mPtr; }
+
+ Type* operator->() const {
+ assert(mPtr);
+ return Get();
+ }
+
+ operator Type*() const { return Get(); }
+
+ Type** Receive() { return &mPtr; }
+
+ private:
+ void Assign(Type* aPtr) {
+ if (mPtr) {
+ delete mPtr;
+ }
+ mPtr = aPtr;
+ }
+
+ Type* mPtr;
+};
+
+// Video frame microseconds are (currently) in 90kHz units, as used by RTP.
+// Use this to convert to microseconds...
+inline Microseconds RTPTimeToMicroseconds(int64_t rtptime) {
+ return (rtptime * 1000000) / 90000;
+}
+
+inline uint32_t MicrosecondsToRTPTime(Microseconds us) {
+ return uint32_t(0xffffffff & (us * 90000) / 1000000);
+}
+
+void dump(const uint8_t* data, uint32_t len, const char* filename);
+
+HRESULT
+CreateMFT(const CLSID& clsid, const char* aDllName,
+ CComPtr<IMFTransform>& aOutMFT);
+
+// Returns the name of the DLL that is needed to decode H.264 on
+// the given windows version we're running on.
+inline const char* WMFDecoderDllName() { return "msmpeg2vdec.dll"; }
+
+// Returns the maximum number of threads we want WMF to use for decoding
+// given the number of logical processors available.
+int32_t GetNumThreads(int32_t aCoreCount);
+
+} // namespace wmf
+
+#endif // __WMFUtils_h__
diff --git a/media/gmp-clearkey/0.1/gmp-clearkey.cpp b/media/gmp-clearkey/0.1/gmp-clearkey.cpp
new file mode 100644
index 0000000000..70c27602f7
--- /dev/null
+++ b/media/gmp-clearkey/0.1/gmp-clearkey.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+// This include is required in order for content_decryption_module to work
+// on Unix systems.
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include "content_decryption_module.h"
+#include "content_decryption_module_ext.h"
+#include "nss.h"
+
+#include "ClearKeyCDM.h"
+#include "ClearKeySessionManager.h"
+#include "mozilla/dom/KeySystemNames.h"
+
+#ifndef XP_WIN
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <unistd.h>
+#endif
+
+#ifdef ENABLE_WMF
+# include "WMFUtils.h"
+#endif // ENABLE_WMF
+
+extern "C" {
+
+CDM_API
+void INITIALIZE_CDM_MODULE() {}
+
+static bool sCanReadHostVerificationFiles = false;
+
+CDM_API
+void* CreateCdmInstance(int cdm_interface_version, const char* key_system,
+ uint32_t key_system_size,
+ GetCdmHostFunc get_cdm_host_func, void* user_data) {
+ CK_LOGE("ClearKey CreateCDMInstance");
+
+ if (cdm_interface_version != cdm::ContentDecryptionModule_10::kVersion) {
+ CK_LOGE(
+ "ClearKey CreateCDMInstance failed due to requesting unsupported "
+ "version %d.",
+ cdm_interface_version);
+ return nullptr;
+ }
+#ifdef ENABLE_WMF
+ if (!wmf::EnsureLibs()) {
+ CK_LOGE("Required libraries were not found");
+ return nullptr;
+ }
+#endif
+
+ if (NSS_NoDB_Init(nullptr) == SECFailure) {
+ CK_LOGE("Unable to initialize NSS");
+ return nullptr;
+ }
+
+#ifdef MOZILLA_OFFICIAL
+ // Test that we're able to read the host files.
+ if (!sCanReadHostVerificationFiles) {
+ return nullptr;
+ }
+#endif
+
+ cdm::Host_10* host = static_cast<cdm::Host_10*>(
+ get_cdm_host_func(cdm_interface_version, user_data));
+ ClearKeyCDM* clearKey = new ClearKeyCDM(host);
+
+ CK_LOGE("Created ClearKeyCDM instance!");
+
+ if (strncmp(key_system, mozilla::kClearKeyWithProtectionQueryKeySystemName,
+ key_system_size) == 0) {
+ CK_LOGE("Enabling protection query on ClearKeyCDM instance!");
+ clearKey->EnableProtectionQuery();
+ }
+
+ return clearKey;
+}
+
+const size_t TEST_READ_SIZE = 16 * 1024;
+
+bool CanReadSome(cdm::PlatformFile aFile) {
+ std::vector<uint8_t> data;
+ data.resize(TEST_READ_SIZE);
+#ifdef XP_WIN
+ DWORD bytesRead = 0;
+ return ReadFile(aFile, &data.front(), TEST_READ_SIZE, &bytesRead, nullptr) &&
+ bytesRead > 0;
+#else
+ return read(aFile, &data.front(), TEST_READ_SIZE) > 0;
+#endif
+}
+
+void ClosePlatformFile(cdm::PlatformFile aFile) {
+#ifdef XP_WIN
+ CloseHandle(aFile);
+#else
+ close(aFile);
+#endif
+}
+
+static uint32_t NumExpectedHostFiles(const cdm::HostFile* aHostFiles,
+ uint32_t aNumFiles) {
+#if !defined(XP_WIN)
+ // We expect 4 binaries: clearkey, libxul, plugin-container, and Firefox.
+ return 4;
+#else
+ // Windows running x64 or x86 natively should also have 4 as above.
+ // For Windows on ARM64, we run an x86 plugin-contianer process under
+ // emulation, and so we expect one additional binary; the x86
+ // xul.dll used by plugin-container.exe.
+ bool i686underAArch64 = false;
+ // Assume that we're running under x86 emulation on an aarch64 host if
+ // one of the paths ends with the x86 plugin-container path we'd expect.
+ const std::wstring plugincontainer = L"i686\\plugin-container.exe";
+ for (uint32_t i = 0; i < aNumFiles; i++) {
+ const cdm::HostFile& hostFile = aHostFiles[i];
+ if (hostFile.file != cdm::kInvalidPlatformFile) {
+ std::wstring path = hostFile.file_path;
+ auto offset = path.find(plugincontainer);
+ if (offset != std::string::npos &&
+ offset == path.size() - plugincontainer.size()) {
+ i686underAArch64 = true;
+ break;
+ }
+ }
+ }
+ return i686underAArch64 ? 5 : 4;
+#endif
+}
+
+CDM_API
+bool VerifyCdmHost_0(const cdm::HostFile* aHostFiles, uint32_t aNumFiles) {
+ // Check that we've received the expected number of host files.
+ bool rv = (aNumFiles == NumExpectedHostFiles(aHostFiles, aNumFiles));
+ // Verify that each binary is readable inside the sandbox,
+ // and close the handle.
+ for (uint32_t i = 0; i < aNumFiles; i++) {
+ const cdm::HostFile& hostFile = aHostFiles[i];
+ if (hostFile.file != cdm::kInvalidPlatformFile) {
+ if (!CanReadSome(hostFile.file)) {
+ rv = false;
+ }
+ ClosePlatformFile(hostFile.file);
+ }
+ if (hostFile.sig_file != cdm::kInvalidPlatformFile) {
+ ClosePlatformFile(hostFile.sig_file);
+ }
+ }
+ sCanReadHostVerificationFiles = rv;
+ return rv;
+}
+
+} // extern "C".
diff --git a/media/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp b/media/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp
new file mode 100644
index 0000000000..bff6d0f356
--- /dev/null
+++ b/media/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include <algorithm>
+#include <stdint.h>
+#include <vector>
+
+#include "../ClearKeyBase64.cpp"
+
+using namespace std;
+
+struct B64Test {
+ string b64;
+ vector<uint8_t> raw;
+ bool shouldPass;
+};
+
+const B64Test tests[] = {
+ {"AAAAADk4AU4AAAAAAAAAAA",
+ {0x0, 0x0, 0x0, 0x0, 0x39, 0x38, 0x1, 0x4e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0},
+ true},
+ {"h2mqp1zAJjDIC34YXEXBxA==",
+ {0x87, 0x69, 0xaa, 0xa7, 0x5c, 0xc0, 0x26, 0x30, 0xc8, 0xb, 0x7e, 0x18,
+ 0x5c, 0x45, 0xc1, 0xc4},
+ true},
+ {"flcdA35XHQN-Vx0DflcdAw",
+ {0x7e, 0x57, 0x1d, 0x3, 0x7e, 0x57, 0x1d, 0x3, 0x7e, 0x57, 0x1d, 0x3, 0x7e,
+ 0x57, 0x1d, 0x3},
+ true},
+ {
+ "flczM35XMzN-VzMzflczMw",
+ {0x7e, 0x57, 0x33, 0x33, 0x7e, 0x57, 0x33, 0x33, 0x7e, 0x57, 0x33, 0x33,
+ 0x7e, 0x57, 0x33, 0x33},
+ true,
+ },
+ {"flcdBH5XHQR-Vx0EflcdBA",
+ {0x7e, 0x57, 0x1d, 0x4, 0x7e, 0x57, 0x1d, 0x4, 0x7e, 0x57, 0x1d, 0x4, 0x7e,
+ 0x57, 0x1d, 0x4},
+ true},
+ {"fldERH5XRER-V0REfldERA",
+ {0x7e, 0x57, 0x44, 0x44, 0x7e, 0x57, 0x44, 0x44, 0x7e, 0x57, 0x44, 0x44,
+ 0x7e, 0x57, 0x44, 0x44},
+ true},
+ {"fuzzbiz=", {0x7e, 0xec, 0xf3, 0x6e, 0x2c}, true},
+ {"fuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbiz",
+ {0x7e, 0xec, 0xf3, 0x6e, 0x2c, 0xdf, 0xbb, 0x3c, 0xdb, 0x8b, 0x37,
+ 0xee, 0xcf, 0x36, 0xe2, 0xcd, 0xfb, 0xb3, 0xcd, 0xb8, 0xb3, 0x7e,
+ 0xec, 0xf3, 0x6e, 0x2c, 0xdf, 0xbb, 0x3c, 0xdb, 0x8b, 0x37, 0xee,
+ 0xcf, 0x36, 0xe2, 0xcd, 0xfb, 0xb3, 0xcd, 0xb8, 0xb3},
+ true},
+ {"", {}, true},
+ {"00", {0xd3}, true},
+ {"000", {0xd3, 0x4d}, true},
+
+ {"invalid", {0x8a, 0x7b, 0xda, 0x96, 0x27}, true},
+ {"invalic", {0x8a, 0x7b, 0xda, 0x96, 0x27}, true},
+
+ // Failure tests
+ {"A", {}, false}, // 1 character is too few.
+ {"_", {}, false}, // 1 character is too few.
+};
+
+TEST(ClearKey, DecodeBase64)
+{
+ for (const B64Test& test : tests) {
+ vector<uint8_t> v;
+ bool rv = DecodeBase64(string(test.b64), v);
+ EXPECT_EQ(test.shouldPass, rv);
+ if (test.shouldPass) {
+ EXPECT_EQ(test.raw.size(), v.size());
+ for (size_t k = 0; k < test.raw.size(); k++) {
+ EXPECT_EQ(test.raw[k], v[k]);
+ }
+ }
+ }
+}
diff --git a/media/gmp-clearkey/0.1/gtest/moz.build b/media/gmp-clearkey/0.1/gtest/moz.build
new file mode 100644
index 0000000000..5771c4724c
--- /dev/null
+++ b/media/gmp-clearkey/0.1/gtest/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ "TestClearKeyUtils.cpp",
+]
+
+FINAL_LIBRARY = "xul-gtest"
+
+LOCAL_INCLUDES += [
+ "..",
+]
diff --git a/media/gmp-clearkey/0.1/manifest.json.in b/media/gmp-clearkey/0.1/manifest.json.in
new file mode 100644
index 0000000000..dd36af8556
--- /dev/null
+++ b/media/gmp-clearkey/0.1/manifest.json.in
@@ -0,0 +1,13 @@
+{
+ "name": "clearkey",
+ "description": "ClearKey Gecko Media Plugin",
+ "version": "1",
+ "x-cdm-module-versions": "4",
+ "x-cdm-interface-versions": "10",
+ "x-cdm-host-versions": "10",
+#ifdef ENABLE_WMF
+ "x-cdm-codecs": "avc1"
+#else
+ "x-cdm-codecs": ""
+#endif
+} \ No newline at end of file
diff --git a/media/gmp-clearkey/0.1/moz.build b/media/gmp-clearkey/0.1/moz.build
new file mode 100644
index 0000000000..928abd6cfb
--- /dev/null
+++ b/media/gmp-clearkey/0.1/moz.build
@@ -0,0 +1,79 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Audio/Video: GMP")
+
+GeckoSharedLibrary("clearkey", linkage=None)
+
+FINAL_TARGET = "dist/bin/gmp-clearkey/0.1"
+
+FINAL_TARGET_PP_FILES += ["manifest.json.in"]
+
+UNIFIED_SOURCES += [
+ "ClearKeyBase64.cpp",
+ "ClearKeyCDM.cpp",
+ "ClearKeyDecryptionManager.cpp",
+ "ClearKeyPersistence.cpp",
+ "ClearKeySession.cpp",
+ "ClearKeySessionManager.cpp",
+ "ClearKeyStorage.cpp",
+ "ClearKeyUtils.cpp",
+ "gmp-clearkey.cpp",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "VideoDecoder.cpp",
+ "WMFH264Decoder.cpp",
+ ]
+
+ SOURCES += [
+ "WMFUtils.cpp",
+ ]
+
+ OS_LIBS += [
+ "mfuuid",
+ "uuid",
+ ]
+
+ DEFINES["ENABLE_WMF"] = True
+
+
+DEFINES["CDM_IMPLEMENTATION"] = True
+
+TEST_DIRS += [
+ "gtest",
+]
+
+DisableStlWrapping()
+DEFINES["MOZ_NO_MOZALLOC"] = True
+
+USE_LIBS += [
+ "nss",
+ "psshparser",
+]
+
+# Suppress warnings in third-party code.
+CFLAGS += [
+ "-Wno-pointer-to-int-cast",
+ "-Wno-sign-compare",
+]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CFLAGS += [
+ "-include",
+ "stdio.h", # for sprintf() prototype
+ "-include",
+ "unistd.h", # for getpid() prototype
+ ]
+elif CONFIG["CC_TYPE"] == "clang-cl":
+ CFLAGS += [
+ "-FI",
+ "stdio.h", # for sprintf() prototype
+ ]
+
+REQUIRES_UNIFIED_BUILD = True