summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorjrummell <jrummell@chromium.org>2015-02-20 18:17:30 -0800
committerCommit bot <commit-bot@chromium.org>2015-02-21 02:18:18 +0000
commit0961c42c5a18859d6b4b5202f816c6a1304b3ac5 (patch)
treed9c44042cf1b2ed4a8bcc0e1ab08f535b61dfffb /media
parent01f8886bebfb9eab8dfb370db065a81a613feee1 (diff)
downloadchromium_src-0961c42c5a18859d6b4b5202f816c6a1304b3ac5.zip
chromium_src-0961c42c5a18859d6b4b5202f816c6a1304b3ac5.tar.gz
chromium_src-0961c42c5a18859d6b4b5202f816c6a1304b3ac5.tar.bz2
Handle 'cenc' Initialization Data Type in Clear Key.
Initialization data is provided as concatentated PSSH boxes. Parse the PSSH boxes and extract the keys, if present. Only process the PSSH boxes that contain the Clear Key SystemID, all other PSSH boxes are skipped. This handles both version 0 and version 1 PSSH boxes. BUG=459850 TEST=new tests pass Review URL: https://codereview.chromium.org/936953005 Cr-Commit-Position: refs/heads/master@{#317490}
Diffstat (limited to 'media')
-rw-r--r--media/BUILD.gn3
-rw-r--r--media/cdm/aes_decryptor.cc31
-rw-r--r--media/cdm/aes_decryptor_unittest.cc10
-rw-r--r--media/cdm/cenc_utils.cc154
-rw-r--r--media/cdm/cenc_utils.h30
-rw-r--r--media/cdm/cenc_utils_unittest.cc353
-rw-r--r--media/cdm/json_web_key.cc6
-rw-r--r--media/cdm/json_web_key.h12
-rw-r--r--media/cdm/json_web_key_unittest.cc23
-rw-r--r--media/media.gyp3
-rw-r--r--media/test/pipeline_integration_test.cc46
11 files changed, 637 insertions, 34 deletions
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 6fe18e5..40c7850 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -82,6 +82,8 @@ component("media") {
sources = [
"cdm/aes_decryptor.cc",
"cdm/aes_decryptor.h",
+ "cdm/cenc_utils.cc",
+ "cdm/cenc_utils.h",
"cdm/default_cdm_factory.cc",
"cdm/default_cdm_factory.h",
"cdm/json_web_key.cc",
@@ -529,6 +531,7 @@ if (is_ios) {
test("media_unittests") {
sources = [
"cdm/aes_decryptor_unittest.cc",
+ "cdm/cenc_utils_unittest.cc",
"cdm/json_web_key_unittest.cc",
"filters/audio_clock_unittest.cc",
"filters/audio_decoder_selector_unittest.cc",
diff --git a/media/cdm/aes_decryptor.cc b/media/cdm/aes_decryptor.cc
index 1c9171c..1bf574d 100644
--- a/media/cdm/aes_decryptor.cc
+++ b/media/cdm/aes_decryptor.cc
@@ -19,6 +19,7 @@
#include "media/base/decrypt_config.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_frame.h"
+#include "media/cdm/cenc_utils.h"
#include "media/cdm/json_web_key.h"
namespace media {
@@ -254,13 +255,31 @@ void AesDecryptor::CreateSessionAndGenerateRequest(
std::string session_id(base::UintToString(next_session_id_++));
valid_sessions_.insert(session_id);
- // For now, the AesDecryptor does not care about |init_data_type| or
- // |session_type|; just resolve the promise and then fire a message event
- // using the |init_data| as the key ID in the license request.
- // TODO(jrummell): Validate |init_data_type| and |session_type|.
+ // For now, the AesDecryptor does not care about |session_type|.
+ // TODO(jrummell): Validate |session_type|.
+
std::vector<uint8> message;
- if (init_data && init_data_length)
- CreateLicenseRequest(init_data, init_data_length, session_type, &message);
+ // TODO(jrummell): Since unprefixed will never send NULL, remove this check
+ // when prefixed EME is removed (http://crbug.com/249976).
+ if (init_data && init_data_length) {
+ std::vector<std::vector<uint8>> keys;
+ if (init_data_type == "webm") {
+ // |init_data| is simply the key needed.
+ keys.push_back(
+ std::vector<uint8>(init_data, init_data + init_data_length));
+ } else if (init_data_type == "cenc") {
+ // |init_data| is a set of 0 or more concatenated 'pssh' boxes.
+ if (!GetKeyIdsForCommonSystemId(init_data, init_data_length, &keys)) {
+ promise->reject(NOT_SUPPORTED_ERROR, 0, "No supported PSSH box found.");
+ return;
+ }
+ } else {
+ // TODO(jrummell): Support init_data_type == "keyids".
+ promise->reject(NOT_SUPPORTED_ERROR, 0, "init_data_type not supported.");
+ return;
+ }
+ CreateLicenseRequest(keys, session_type, &message);
+ }
promise->resolve(session_id);
diff --git a/media/cdm/aes_decryptor_unittest.cc b/media/cdm/aes_decryptor_unittest.cc
index 40ddca2..ce06fe2 100644
--- a/media/cdm/aes_decryptor_unittest.cc
+++ b/media/cdm/aes_decryptor_unittest.cc
@@ -285,7 +285,7 @@ class AesDecryptorTest : public testing::Test {
EXPECT_CALL(*this, OnSessionMessage(IsNotEmpty(), _, IsJSONDictionary(),
GURL::EmptyGURL()));
decryptor_.CreateSessionAndGenerateRequest(
- MediaKeys::TEMPORARY_SESSION, std::string(), &key_id[0], key_id.size(),
+ MediaKeys::TEMPORARY_SESSION, "webm", &key_id[0], key_id.size(),
CreateSessionPromise(RESOLVED));
// This expects the promise to be called synchronously, which is the case
// for AesDecryptor.
@@ -430,7 +430,7 @@ TEST_F(AesDecryptorTest, CreateSessionWithNullInitData) {
EXPECT_CALL(*this,
OnSessionMessage(IsNotEmpty(), _, IsEmpty(), GURL::EmptyGURL()));
decryptor_.CreateSessionAndGenerateRequest(MediaKeys::TEMPORARY_SESSION,
- std::string(), NULL, 0,
+ "webm", NULL, 0,
CreateSessionPromise(RESOLVED));
}
@@ -438,19 +438,19 @@ TEST_F(AesDecryptorTest, MultipleCreateSession) {
EXPECT_CALL(*this,
OnSessionMessage(IsNotEmpty(), _, IsEmpty(), GURL::EmptyGURL()));
decryptor_.CreateSessionAndGenerateRequest(MediaKeys::TEMPORARY_SESSION,
- std::string(), NULL, 0,
+ "webm", NULL, 0,
CreateSessionPromise(RESOLVED));
EXPECT_CALL(*this,
OnSessionMessage(IsNotEmpty(), _, IsEmpty(), GURL::EmptyGURL()));
decryptor_.CreateSessionAndGenerateRequest(MediaKeys::TEMPORARY_SESSION,
- std::string(), NULL, 0,
+ "webm", NULL, 0,
CreateSessionPromise(RESOLVED));
EXPECT_CALL(*this,
OnSessionMessage(IsNotEmpty(), _, IsEmpty(), GURL::EmptyGURL()));
decryptor_.CreateSessionAndGenerateRequest(MediaKeys::TEMPORARY_SESSION,
- std::string(), NULL, 0,
+ "webm", NULL, 0,
CreateSessionPromise(RESOLVED));
}
diff --git a/media/cdm/cenc_utils.cc b/media/cdm/cenc_utils.cc
new file mode 100644
index 0000000..2eeafd7
--- /dev/null
+++ b/media/cdm/cenc_utils.cc
@@ -0,0 +1,154 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/cdm/cenc_utils.h"
+
+#include "media/base/bit_reader.h"
+
+namespace media {
+
+// The initialization data for encrypted media files using the ISO Common
+// Encryption ('cenc') protection scheme may contain one or more protection
+// system specific header ('pssh') boxes.
+// ref: https://w3c.github.io/encrypted-media/cenc-format.html
+//
+// The format of a 'pssh' box is as follows:
+// unsigned int(32) size;
+// unsigned int(32) type = "pssh";
+// if (size==1) {
+// unsigned int(64) largesize;
+// } else if (size==0) {
+// -- box extends to end of file
+// }
+// unsigned int(8) version;
+// bit(24) flags;
+// unsigned int(8)[16] SystemID;
+// if (version > 0)
+// {
+// unsigned int(32) KID_count;
+// {
+// unsigned int(8)[16] KID;
+// } [KID_count]
+// }
+// unsigned int(32) DataSize;
+// unsigned int(8)[DataSize] Data;
+
+// Minimum size of a 'pssh' box includes all the required fields (size, type,
+// version, flags, SystemID, DataSize).
+const int kMinimumBoxSizeInBytes = 32;
+
+// SystemID for the Common System.
+// https://w3c.github.io/encrypted-media/cenc-format.html#common-system
+const uint8 kCommonSystemId[] = { 0x10, 0x77, 0xef, 0xec,
+ 0xc0, 0xb2, 0x4d, 0x02,
+ 0xac, 0xe3, 0x3c, 0x1e,
+ 0x52, 0xe2, 0xfb, 0x4b };
+
+#define RCHECK(x) \
+ do { \
+ if (!(x)) \
+ return false; \
+ } while (0)
+
+// Helper function to read up to 32 bits from a bit stream.
+static uint32 ReadBits(BitReader* reader, int num_bits) {
+ DCHECK_GE(reader->bits_available(), num_bits);
+ DCHECK((num_bits > 0) && (num_bits <= 32));
+ uint32 value;
+ reader->ReadBits(num_bits, &value);
+ return value;
+}
+
+// Checks whether the next 16 bytes matches the Common SystemID.
+// Assumes |reader| has enough data.
+static bool IsCommonSystemID(BitReader* reader) {
+ for (uint32 i = 0; i < arraysize(kCommonSystemId); ++i) {
+ if (ReadBits(reader, 8) != kCommonSystemId[i])
+ return false;
+ }
+ return true;
+}
+
+bool GetKeyIdsForCommonSystemId(const uint8* input,
+ int input_length,
+ std::vector<std::vector<uint8>>* key_ids) {
+ int offset = 0;
+ std::vector<std::vector<uint8>> result;
+
+ while (offset < input_length) {
+ BitReader reader(input + offset, input_length - offset);
+
+ // Enough data for a miniumum size 'pssh' box?
+ RCHECK(reader.bits_available() >= kMinimumBoxSizeInBytes * 8);
+
+ uint32 size = ReadBits(&reader, 32);
+
+ // Must be a 'pssh' box or else fail.
+ RCHECK(ReadBits(&reader, 8) == 'p');
+ RCHECK(ReadBits(&reader, 8) == 's');
+ RCHECK(ReadBits(&reader, 8) == 's');
+ RCHECK(ReadBits(&reader, 8) == 'h');
+
+ if (size == 1) {
+ // If largesize > 2**32 it is too big.
+ RCHECK(ReadBits(&reader, 32) == 0);
+ size = ReadBits(&reader, 32);
+ } else if (size == 0) {
+ size = input_length - offset;
+ }
+
+ // Check that the buffer contains at least size bytes.
+ RCHECK(static_cast<uint32>(input_length - offset) >= size);
+
+ // Update offset to point at the next 'pssh' box (may not be one).
+ offset += size;
+
+ // Check the version, as KIDs only available if version > 0.
+ uint8 version = ReadBits(&reader, 8);
+ if (version == 0)
+ continue;
+
+ // flags must be 0. If not, assume incorrect 'pssh' box and move to the
+ // next one.
+ if (ReadBits(&reader, 24) != 0)
+ continue;
+
+ // Validate SystemID
+ RCHECK(static_cast<uint32>(reader.bits_available()) >=
+ arraysize(kCommonSystemId) * 8);
+ if (!IsCommonSystemID(&reader))
+ continue; // Not Common System, so try the next pssh box.
+
+ // Since version > 0, next field is the KID_count.
+ RCHECK(static_cast<uint32>(reader.bits_available()) >= sizeof(uint32) * 8);
+ uint32 count = ReadBits(&reader, 32);
+
+ if (count == 0)
+ continue;
+
+ // Make sure there is enough data for all the KIDs specified, and then
+ // extract them.
+ RCHECK(static_cast<uint32>(reader.bits_available()) > count * 16 * 8);
+ while (count > 0) {
+ std::vector<uint8> key;
+ key.reserve(16);
+ for (int i = 0; i < 16; ++i) {
+ key.push_back(ReadBits(&reader, 8));
+ }
+ result.push_back(key);
+ --count;
+ }
+
+ // Don't bother checking DataSize and Data.
+ }
+
+ key_ids->swap(result);
+
+ // TODO(jrummell): This should return true only if there was at least one
+ // key ID present. However, numerous test files don't contain the 'pssh' box
+ // for Common Format, so no keys are found. http://crbug.com/460308
+ return true;
+}
+
+} // namespace media
diff --git a/media/cdm/cenc_utils.h b/media/cdm/cenc_utils.h
new file mode 100644
index 0000000..c659742
--- /dev/null
+++ b/media/cdm/cenc_utils.h
@@ -0,0 +1,30 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_CDM_CENC_UTILS_H_
+#define MEDIA_CDM_CENC_UTILS_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Gets the Key Ids from a 'pssh' box for the Common SystemID among one or
+// more concatenated 'pssh' boxes. If |input| looks valid, then true is
+// returned and |key_ids| is updated to contain the values found. Otherwise
+// return false.
+// TODO(jrummell): This returns true if no Common SystemID 'pssh' boxes are
+// found, or are included but don't contain any key IDs. This should be
+// fixed once the test files are updated to include correct 'pssh' boxes.
+// http://crbug.com/460308
+MEDIA_EXPORT bool GetKeyIdsForCommonSystemId(
+ const uint8* input,
+ int input_length,
+ std::vector<std::vector<uint8>>* key_ids);
+
+} // namespace media
+
+#endif // MEDIA_CDM_CENC_UTILS_H_
diff --git a/media/cdm/cenc_utils_unittest.cc b/media/cdm/cenc_utils_unittest.cc
new file mode 100644
index 0000000..ae75817
--- /dev/null
+++ b/media/cdm/cenc_utils_unittest.cc
@@ -0,0 +1,353 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/cdm/cenc_utils.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+const uint8 kKey1Data[] = {
+ 0x7E, 0x57, 0x1D, 0x03, 0x7E, 0x57, 0x1D, 0x03,
+ 0x7E, 0x57, 0x1D, 0x03, 0x7E, 0x57, 0x1D, 0x03
+};
+const uint8 kKey2Data[] = {
+ 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x04,
+ 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x04,
+};
+const uint8 kKey3Data[] = {
+ 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x05,
+ 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x05,
+};
+const uint8 kKey4Data[] = {
+ 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x06,
+ 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x06,
+};
+
+class CencUtilsTest : public testing::Test {
+ public:
+ CencUtilsTest()
+ : key1_(kKey1Data, kKey1Data + arraysize(kKey1Data)),
+ key2_(kKey2Data, kKey2Data + arraysize(kKey2Data)),
+ key3_(kKey3Data, kKey3Data + arraysize(kKey3Data)),
+ key4_(kKey4Data, kKey4Data + arraysize(kKey4Data)) {}
+
+ protected:
+ // Initialize the start of the 'pssh' box (up to key_count)
+ void InitializePSSHBox(std::vector<uint8>* box, uint8 size, uint8 version) {
+ DCHECK(box->size() == 0);
+
+ box->reserve(size);
+ // Add size.
+ box->push_back(0);
+ box->push_back(0);
+ box->push_back(0);
+ box->push_back(size);
+ // Add 'pssh'.
+ box->push_back('p');
+ box->push_back('s');
+ box->push_back('s');
+ box->push_back('h');
+ // Add version.
+ box->push_back(version);
+ // Add flags.
+ box->push_back(0);
+ box->push_back(0);
+ box->push_back(0);
+ // Add Clear Key SystemID.
+ box->push_back(0x10);
+ box->push_back(0x77);
+ box->push_back(0xEF);
+ box->push_back(0xEC);
+ box->push_back(0xC0);
+ box->push_back(0xB2);
+ box->push_back(0x4D);
+ box->push_back(0x02);
+ box->push_back(0xAC);
+ box->push_back(0xE3);
+ box->push_back(0x3C);
+ box->push_back(0x1E);
+ box->push_back(0x52);
+ box->push_back(0xE2);
+ box->push_back(0xFB);
+ box->push_back(0x4B);
+ }
+
+ std::vector<uint8> MakePSSHBox(uint8 version) {
+ std::vector<uint8> box;
+ uint8 size = (version == 0) ? 32 : 36;
+ InitializePSSHBox(&box, size, version);
+ if (version > 0) {
+ // Add key_count (= 0).
+ box.push_back(0);
+ box.push_back(0);
+ box.push_back(0);
+ box.push_back(0);
+ }
+ // Add data_size (= 0).
+ box.push_back(0);
+ box.push_back(0);
+ box.push_back(0);
+ box.push_back(0);
+ return box;
+ }
+
+ std::vector<uint8> MakePSSHBox(uint8 version,
+ const std::vector<uint8>& key1) {
+ DCHECK(version > 0);
+ DCHECK(key1.size() == 16);
+
+ std::vector<uint8> box;
+ uint8 size = 52;
+ InitializePSSHBox(&box, size, version);
+
+ // Add key_count (= 1).
+ box.push_back(0);
+ box.push_back(0);
+ box.push_back(0);
+ box.push_back(1);
+
+ // Add key1.
+ for (int i = 0; i < 16; ++i)
+ box.push_back(key1[i]);
+
+ // Add data_size (= 0).
+ box.push_back(0);
+ box.push_back(0);
+ box.push_back(0);
+ box.push_back(0);
+ return box;
+ }
+
+ std::vector<uint8> MakePSSHBox(uint8 version,
+ const std::vector<uint8>& key1,
+ const std::vector<uint8>& key2) {
+ DCHECK(version > 0);
+ DCHECK(key1.size() == 16);
+ DCHECK(key2.size() == 16);
+
+ std::vector<uint8> box;
+ uint8 size = 68;
+ InitializePSSHBox(&box, size, version);
+
+ // Add key_count (= 2).
+ box.push_back(0);
+ box.push_back(0);
+ box.push_back(0);
+ box.push_back(2);
+
+ // Add key1.
+ for (int i = 0; i < 16; ++i)
+ box.push_back(key1[i]);
+
+ // Add key2.
+ for (int i = 0; i < 16; ++i)
+ box.push_back(key2[i]);
+
+ // Add data_size (= 0).
+ box.push_back(0);
+ box.push_back(0);
+ box.push_back(0);
+ box.push_back(0);
+ return box;
+ }
+
+ const std::vector<uint8>& Key1() { return key1_; }
+ const std::vector<uint8>& Key2() { return key2_; }
+ const std::vector<uint8>& Key3() { return key3_; }
+ const std::vector<uint8>& Key4() { return key4_; }
+
+ private:
+ std::vector<uint8> key1_;
+ std::vector<uint8> key2_;
+ std::vector<uint8> key3_;
+ std::vector<uint8> key4_;
+};
+
+TEST_F(CencUtilsTest, EmptyPSSH) {
+ std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(GetKeyIdsForCommonSystemId(nullptr, 0, &key_ids));
+ EXPECT_EQ(0u, key_ids.size());
+}
+
+TEST_F(CencUtilsTest, PSSHVersion0) {
+ std::vector<uint8> box = MakePSSHBox(0);
+ std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box[0], box.size(), &key_ids));
+ EXPECT_EQ(0u, key_ids.size());
+}
+
+TEST_F(CencUtilsTest, PSSHVersion1WithNoKeys) {
+ std::vector<uint8> box = MakePSSHBox(1);
+ std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box[0], box.size(), &key_ids));
+ EXPECT_EQ(0u, key_ids.size());
+}
+
+TEST_F(CencUtilsTest, PSSHVersion1WithOneKey) {
+ std::vector<uint8> box = MakePSSHBox(1, Key1());
+ std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box[0], box.size(), &key_ids));
+ EXPECT_EQ(1u, key_ids.size());
+ EXPECT_EQ(key_ids[0], Key1());
+}
+
+TEST_F(CencUtilsTest, PSSHVersion1WithTwoKeys) {
+ std::vector<uint8> box = MakePSSHBox(1, Key1(), Key2());
+ std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box[0], box.size(), &key_ids));
+ EXPECT_EQ(2u, key_ids.size());
+ EXPECT_EQ(key_ids[0], Key1());
+ EXPECT_EQ(key_ids[1], Key2());
+}
+
+TEST_F(CencUtilsTest, PSSHVersion0Plus1) {
+ std::vector<uint8> box0 = MakePSSHBox(0);
+ std::vector<uint8> box1 = MakePSSHBox(1, Key1());
+
+ // Concatentate box1 into box0.
+ for (const auto& value : box1)
+ box0.push_back(value);
+
+ std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box0[0], box0.size(), &key_ids));
+ EXPECT_EQ(1u, key_ids.size());
+ EXPECT_EQ(key_ids[0], Key1());
+}
+
+TEST_F(CencUtilsTest, PSSHVersion1Plus0) {
+ std::vector<uint8> box0 = MakePSSHBox(0);
+ std::vector<uint8> box1 = MakePSSHBox(1, Key1());
+
+ // Concatentate box0 into box1.
+ for (const auto& value : box0)
+ box1.push_back(value);
+
+ std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box1[0], box1.size(), &key_ids));
+ EXPECT_EQ(1u, key_ids.size());
+ EXPECT_EQ(key_ids[0], Key1());
+}
+
+TEST_F(CencUtilsTest, MultiplePSSHVersion1) {
+ std::vector<uint8> box = MakePSSHBox(1, Key1(), Key2());
+ std::vector<uint8> box1 = MakePSSHBox(1, Key3());
+ std::vector<uint8> box2 = MakePSSHBox(1, Key4());
+
+ // Concatentate box1 into box.
+ for (const auto& value : box1)
+ box.push_back(value);
+ // Concatentate box2 into box.
+ for (const auto& value : box2)
+ box.push_back(value);
+
+ std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box[0], box.size(), &key_ids));
+ EXPECT_EQ(4u, key_ids.size());
+ EXPECT_EQ(key_ids[0], Key1());
+ EXPECT_EQ(key_ids[1], Key2());
+ EXPECT_EQ(key_ids[2], Key3());
+ EXPECT_EQ(key_ids[3], Key4());
+}
+
+TEST_F(CencUtilsTest, InvalidPSSH) {
+ std::vector<uint8> box = MakePSSHBox(1, Key1(), Key2());
+ std::vector<std::vector<uint8>> key_ids;
+ for (uint32 i = 1; i < box.size(); ++i) {
+ // Modify size of data passed to be less than real size.
+ EXPECT_FALSE(GetKeyIdsForCommonSystemId(&box[0], i, &key_ids));
+ // Modify starting point.
+ EXPECT_FALSE(GetKeyIdsForCommonSystemId(&box[i], box.size() - i, &key_ids));
+ }
+}
+
+TEST_F(CencUtilsTest, InvalidSystemID) {
+ std::vector<uint8> box = MakePSSHBox(1, Key1(), Key2());
+
+ // Modify the System ID.
+ ++box[20];
+
+ std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box[0], box.size(), &key_ids));
+ EXPECT_EQ(0u, key_ids.size());
+}
+
+TEST_F(CencUtilsTest, InvalidFlags) {
+ std::vector<uint8> box = MakePSSHBox(1, Key1(), Key2());
+
+ // Modify flags.
+ box[10] = 3;
+
+ std::vector<std::vector<uint8>> key_ids;
+ // TODO(jrummell): This should fail as the 'pssh' box is skipped.
+ EXPECT_TRUE(GetKeyIdsForCommonSystemId(&box[0], box.size(), &key_ids));
+ EXPECT_EQ(0u, key_ids.size());
+}
+
+TEST_F(CencUtilsTest, LongSize) {
+ const uint8 data[] = {
+ 0x00, 0x00, 0x00, 0x01, // size = 1
+ 0x70, 0x73, 0x73, 0x68, // 'pssh'
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, // longsize
+ 0x01, // version
+ 0x00, 0x00, 0x00, // flags
+ 0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, // SystemID
+ 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
+ 0x00, 0x00, 0x00, 0x02, // key count
+ 0x7E, 0x57, 0x1D, 0x03, 0x7E, 0x57, 0x1D, 0x03, // key1
+ 0x7E, 0x57, 0x1D, 0x03, 0x7E, 0x57, 0x1D, 0x03,
+ 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x04, // key2
+ 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x04,
+ 0x00, 0x00, 0x00, 0x00 // datasize
+ };
+
+ std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(GetKeyIdsForCommonSystemId(data, arraysize(data), &key_ids));
+ EXPECT_EQ(2u, key_ids.size());
+}
+
+TEST_F(CencUtilsTest, NoSize) {
+ const uint8 data[] = {
+ 0x00, 0x00, 0x00, 0x00, // size = 0
+ 0x70, 0x73, 0x73, 0x68, // 'pssh'
+ 0x01, // version
+ 0x00, 0x00, 0x00, // flags
+ 0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, // SystemID
+ 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
+ 0x00, 0x00, 0x00, 0x02, // key count
+ 0x7E, 0x57, 0x1D, 0x03, 0x7E, 0x57, 0x1D, 0x03, // key1
+ 0x7E, 0x57, 0x1D, 0x03, 0x7E, 0x57, 0x1D, 0x03,
+ 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x04, // key2
+ 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x04,
+ 0x00, 0x00, 0x00, 0x00 // datasize
+ };
+
+ std::vector<std::vector<uint8>> key_ids;
+ EXPECT_TRUE(GetKeyIdsForCommonSystemId(data, arraysize(data), &key_ids));
+ EXPECT_EQ(2u, key_ids.size());
+}
+
+TEST_F(CencUtilsTest, HugeSize) {
+ const uint8 data[] = {
+ 0x00, 0x00, 0x00, 0x01, // size = 1
+ 0x70, 0x73, 0x73, 0x68, // 'pssh'
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // longsize = big
+ 0x01, // version
+ 0x00, 0x00, 0x00, // flags
+ 0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, // SystemID
+ 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
+ 0x00, 0x00, 0x00, 0x02, // key count
+ 0x7E, 0x57, 0x1D, 0x03, 0x7E, 0x57, 0x1D, 0x03, // key1
+ 0x7E, 0x57, 0x1D, 0x03, 0x7E, 0x57, 0x1D, 0x03,
+ 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x04, // key2
+ 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x04,
+ 0x00, 0x00, 0x00, 0x00 // datasize
+ };
+
+ std::vector<std::vector<uint8>> key_ids;
+ EXPECT_FALSE(GetKeyIdsForCommonSystemId(data, arraysize(data), &key_ids));
+}
+
+} // namespace media
diff --git a/media/cdm/json_web_key.cc b/media/cdm/json_web_key.cc
index 1de3c9e..cd50a8c 100644
--- a/media/cdm/json_web_key.cc
+++ b/media/cdm/json_web_key.cc
@@ -226,14 +226,14 @@ bool ExtractKeysFromJWKSet(const std::string& jwk_set,
return true;
}
-void CreateLicenseRequest(const uint8* key_id,
- int key_id_length,
+void CreateLicenseRequest(const KeyIdList& key_ids,
MediaKeys::SessionType session_type,
std::vector<uint8>* license) {
// Create the license request.
scoped_ptr<base::DictionaryValue> request(new base::DictionaryValue());
scoped_ptr<base::ListValue> list(new base::ListValue());
- list->AppendString(EncodeBase64Url(key_id, key_id_length));
+ for (const auto& key_id : key_ids)
+ list->AppendString(EncodeBase64Url(&key_id[0], key_id.size()));
request->Set(kKeyIdsTag, list.release());
switch (session_type) {
diff --git a/media/cdm/json_web_key.h b/media/cdm/json_web_key.h
index af028f2..2691b43 100644
--- a/media/cdm/json_web_key.h
+++ b/media/cdm/json_web_key.h
@@ -48,6 +48,9 @@ namespace media {
// Ref: http://tools.ietf.org/html/draft-ietf-jose-json-web-key and:
// http://tools.ietf.org/html/draft-jones-jose-json-private-and-symmetric-key
+// Vector of key IDs.
+typedef std::vector<std::vector<uint8>> KeyIdList;
+
// Vector of [key_id, key_value] pairs. Values are raw binary data, stored in
// strings for convenience.
typedef std::pair<std::string, std::string> KeyIdAndKeyPair;
@@ -64,12 +67,9 @@ MEDIA_EXPORT bool ExtractKeysFromJWKSet(const std::string& jwk_set,
KeyIdAndKeyPairs* keys,
MediaKeys::SessionType* session_type);
-// Create a license request message for the |key_id| and |session_type|
-// specified. Currently ClearKey generates a message for each key individually,
-// so no need to take a list of |key_id|'s. |license| is updated to contain the
-// resulting JSON string.
-MEDIA_EXPORT void CreateLicenseRequest(const uint8* key_id,
- int key_id_length,
+// Creates a license request message for the |key_ids| and |session_type|
+// specified. |license| is updated to contain the resulting JSON string.
+MEDIA_EXPORT void CreateLicenseRequest(const KeyIdList& key_ids,
MediaKeys::SessionType session_type,
std::vector<uint8>* license);
diff --git a/media/cdm/json_web_key_unittest.cc b/media/cdm/json_web_key_unittest.cc
index fa5f823..07d28c0 100644
--- a/media/cdm/json_web_key_unittest.cc
+++ b/media/cdm/json_web_key_unittest.cc
@@ -45,7 +45,9 @@ class JSONWebKeyTest : public testing::Test {
MediaKeys::SessionType session_type,
const std::string& expected_result) {
std::vector<uint8> result;
- CreateLicenseRequest(key_id, key_id_length, session_type, &result);
+ KeyIdList key_ids;
+ key_ids.push_back(std::vector<uint8>(key_id, key_id + key_id_length));
+ CreateLicenseRequest(key_ids, session_type, &result);
std::string s(result.begin(), result.end());
EXPECT_EQ(expected_result, s);
}
@@ -498,5 +500,24 @@ TEST_F(JSONWebKeyTest, Base64UrlEncoding) {
arraysize(data1));
}
+TEST_F(JSONWebKeyTest, MultipleKeys) {
+ const uint8 data1[] = { 0x01, 0x02 };
+ const uint8 data2[] = { 0x01, 0x02, 0x03, 0x04 };
+ const uint8 data3[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 };
+
+ std::vector<uint8> result;
+ KeyIdList key_ids;
+ key_ids.push_back(std::vector<uint8>(data1, data1 + arraysize(data1)));
+ key_ids.push_back(std::vector<uint8>(data2, data2 + arraysize(data2)));
+ key_ids.push_back(std::vector<uint8>(data3, data3 + arraysize(data3)));
+ CreateLicenseRequest(key_ids, MediaKeys::TEMPORARY_SESSION, &result);
+ std::string s(result.begin(), result.end());
+ EXPECT_EQ(
+ "{\"kids\":[\"AQI\",\"AQIDBA\",\"AQIDBAUGBwgJCgsMDQ4PEA\"],\"type\":"
+ "\"temporary\"}",
+ s);
+}
+
} // namespace media
diff --git a/media/media.gyp b/media/media.gyp
index a5941db..3e65a43 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -398,6 +398,8 @@
'base/yuv_convert.h',
'cdm/aes_decryptor.cc',
'cdm/aes_decryptor.h',
+ 'cdm/cenc_utils.cc',
+ 'cdm/cenc_utils.h',
'cdm/default_cdm_factory.cc',
'cdm/default_cdm_factory.h',
'cdm/json_web_key.cc',
@@ -1205,6 +1207,7 @@
'base/wall_clock_time_source_unittest.cc',
'base/yuv_convert_unittest.cc',
'cdm/aes_decryptor_unittest.cc',
+ 'cdm/cenc_utils_unittest.cc',
'cdm/json_web_key_unittest.cc',
'ffmpeg/ffmpeg_common_unittest.cc',
'filters/audio_clock_unittest.cc',
diff --git a/media/test/pipeline_integration_test.cc b/media/test/pipeline_integration_test.cc
index d326a6c..9d263c1 100644
--- a/media/test/pipeline_integration_test.cc
+++ b/media/test/pipeline_integration_test.cc
@@ -56,7 +56,6 @@ namespace media {
const char kSourceId[] = "SourceId";
const char kCencInitDataType[] = "cenc";
-const uint8 kInitData[] = { 0x69, 0x6e, 0x69, 0x74 };
const char kWebM[] = "video/webm; codecs=\"vp8,vorbis\"";
const char kWebMVP9[] = "video/webm; codecs=\"vp9\"";
@@ -256,7 +255,7 @@ class KeyProvidingApp : public FakeEncryptedMedia::AppBase {
media::MediaKeys::Exception exception_code,
uint32 system_code,
const std::string& error_message) {
- EXPECT_EQ(expected, REJECTED);
+ EXPECT_EQ(expected, REJECTED) << error_message;
}
scoped_ptr<SimpleCdmPromise> CreatePromise(PromiseResult expected) {
@@ -304,15 +303,26 @@ class KeyProvidingApp : public FakeEncryptedMedia::AppBase {
const std::vector<uint8>& init_data,
AesDecryptor* decryptor) override {
if (current_session_id_.empty()) {
- decryptor->CreateSessionAndGenerateRequest(
- MediaKeys::TEMPORARY_SESSION, init_data_type, kInitData,
- arraysize(kInitData), CreateSessionPromise(RESOLVED));
+ if (init_data_type == kCencInitDataType) {
+ // Since the 'cenc' files are not created with proper 'pssh' boxes,
+ // simply pretend that this is a webm file and pass the expected
+ // key ID as the init_data.
+ // http://crbug.com/460308
+ decryptor->CreateSessionAndGenerateRequest(
+ MediaKeys::TEMPORARY_SESSION, "webm", kKeyId, arraysize(kKeyId),
+ CreateSessionPromise(RESOLVED));
+ } else {
+ decryptor->CreateSessionAndGenerateRequest(
+ MediaKeys::TEMPORARY_SESSION, init_data_type,
+ vector_as_array(&init_data), init_data.size(),
+ CreateSessionPromise(RESOLVED));
+ }
EXPECT_FALSE(current_session_id_.empty());
}
- // Clear Key really needs the key ID in |init_data|. For WebM, they are the
- // same, but this is not the case for ISO CENC. Therefore, provide the
- // correct key ID.
+ // Clear Key really needs the key ID from |init_data|. For WebM, they are
+ // the same, but this is not the case for ISO CENC (key ID embedded in a
+ // 'pssh' box). Therefore, provide the correct key ID.
const uint8* key_id = init_data.empty() ? NULL : &init_data[0];
size_t key_id_length = init_data.size();
if (init_data_type == kCencInitDataType) {
@@ -350,15 +360,25 @@ class RotatingKeyProvidingApp : public KeyProvidingApp {
prev_init_data_ = init_data;
++num_distint_need_key_calls_;
- decryptor->CreateSessionAndGenerateRequest(
- MediaKeys::TEMPORARY_SESSION, init_data_type,
- vector_as_array(&init_data), init_data.size(),
- CreateSessionPromise(RESOLVED));
-
std::vector<uint8> key_id;
std::vector<uint8> key;
EXPECT_TRUE(GetKeyAndKeyId(init_data, &key, &key_id));
+ if (init_data_type == kCencInitDataType) {
+ // Since the 'cenc' files are not created with proper 'pssh' boxes,
+ // simply pretend that this is a webm file and pass the expected
+ // key ID as the init_data.
+ // http://crbug.com/460308
+ decryptor->CreateSessionAndGenerateRequest(
+ MediaKeys::TEMPORARY_SESSION, "webm", vector_as_array(&key_id),
+ key_id.size(), CreateSessionPromise(RESOLVED));
+ } else {
+ decryptor->CreateSessionAndGenerateRequest(
+ MediaKeys::TEMPORARY_SESSION, init_data_type,
+ vector_as_array(&init_data), init_data.size(),
+ CreateSessionPromise(RESOLVED));
+ }
+
// Convert key into a JSON structure and then add it.
std::string jwk = GenerateJWKSet(vector_as_array(&key),
key.size(),