Skip to content
This repository has been archived by the owner on Apr 3, 2020. It is now read-only.

Commit

Permalink
Merge 317490 "Handle 'cenc' Initialization Data Type in Clear Key."
Browse files Browse the repository at this point in the history
> 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}
(cherry picked from commit 0961c42)

[email protected]

Review URL: https://codereview.chromium.org/958183003

Cr-Commit-Position: refs/branch-heads/2311@{#45}
Cr-Branched-From: 09b7de5-refs/heads/master@{#317474}
  • Loading branch information
jrummell-chromium committed Feb 26, 2015
1 parent 16e8996 commit e078ba0
Show file tree
Hide file tree
Showing 11 changed files with 637 additions and 34 deletions.
3 changes: 3 additions & 0 deletions media/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
31 changes: 25 additions & 6 deletions media/cdm/aes_decryptor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);

Expand Down
10 changes: 5 additions & 5 deletions media/cdm/aes_decryptor_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -430,27 +430,27 @@ 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));
}

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));
}

Expand Down
154 changes: 154 additions & 0 deletions media/cdm/cenc_utils.cc
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions media/cdm/cenc_utils.h
Original file line number Diff line number Diff line change
@@ -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_
Loading

0 comments on commit e078ba0

Please sign in to comment.