Make PSSH parser more robust.

(This is a merge of http://go/wvgerrit/12700 from the Widevine CDM
repository.)

Adds unit tests which cover several cases, including five which are
fixed in this patch:

* Find a Widevine PSSH after a PSSH with non-zero flags.
  (We have no control over another provider's abuse of the flags field,
   so we should not give up if such a PSSH appears before ours.)
* Find a Widevine PSSH after a v1 PSSH.
  (CENC now specifies a general v1 format.  We don't have to support it
   directly in the CDM, but we do have to skip it gracefully.)
* Find a Widevine PSSH after a non-PSSH box.
  (This would be unusual input, but we can easily recover from it.)
* Parse a PSSH box with a size field of 0, which means "the rest of the
  buffer."
  (This would be unusual input, too, but is technically allowed for any
   MP4 box.)
* Parse a v1 Widevine PSSH box, ignoring the new fields we don't need.

Bug: 19288007
Change-Id: I355df9e34ba4d53cc02e8501de965a0d193ee554
This commit is contained in:
John "Juce" Bruce
2015-03-04 13:57:09 -08:00
parent d5fdd89071
commit 4252a4b790
4 changed files with 259 additions and 47 deletions

View File

@@ -14,12 +14,10 @@ namespace wvcdm {
InitializationData::InitializationData(const std::string& type, InitializationData::InitializationData(const std::string& type,
const CdmInitData& data) const CdmInitData& data)
: type_(type), is_cenc_(false), is_webm_(false) { : type_(type), is_cenc_(false), is_webm_(false) {
if (type == ISO_BMFF_VIDEO_MIME_TYPE || if (type == ISO_BMFF_VIDEO_MIME_TYPE || type == ISO_BMFF_AUDIO_MIME_TYPE ||
type == ISO_BMFF_AUDIO_MIME_TYPE ||
type == CENC_INIT_DATA_FORMAT) { type == CENC_INIT_DATA_FORMAT) {
is_cenc_ = true; is_cenc_ = true;
} else if (type == WEBM_VIDEO_MIME_TYPE || } else if (type == WEBM_VIDEO_MIME_TYPE || type == WEBM_AUDIO_MIME_TYPE ||
type == WEBM_AUDIO_MIME_TYPE ||
type == WEBM_INIT_DATA_FORMAT) { type == WEBM_INIT_DATA_FORMAT) {
is_webm_ = true; is_webm_ = true;
} }
@@ -35,83 +33,127 @@ InitializationData::InitializationData(const std::string& type,
// Parse a blob of multiple concatenated PSSH atoms to extract the first // Parse a blob of multiple concatenated PSSH atoms to extract the first
// Widevine PSSH. // Widevine PSSH.
bool InitializationData::ExtractWidevinePssh( bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
const CdmInitData& init_data, CdmInitData* output) { CdmInitData* output) {
BufferReader reader( BufferReader reader(reinterpret_cast<const uint8_t*>(init_data.data()),
reinterpret_cast<const uint8_t*>(init_data.data()), init_data.length()); init_data.length());
// TODO(kqyang): Extracted from an actual init_data; // Widevine's registered system ID.
// Need to find out where it comes from.
static const uint8_t kWidevineSystemId[] = { static const uint8_t kWidevineSystemId[] = {
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, 0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE,
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED, 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
}; };
// one PSSH blob consists of: // one PSSH box consists of:
// 4 byte size of the PSSH atom, inclusive // 4 byte size of the atom, inclusive. (0 means the rest of the buffer.)
// "pssh" // 4 byte atom type, "pssh".
// 4 byte flags, value 0 // (optional, if size == 1) 8 byte size of the atom, inclusive.
// 16 byte system id // 1 byte version, value 0 or 1. (skip if larger.)
// 4 byte size of PSSH data, exclusive // 3 byte flags, value 0. (ignored.)
// 16 byte system id.
// (optional, if version == 1) 4 byte key ID count. (K)
// (optional, if version == 1) K * 16 byte key ID.
// 4 byte size of PSSH data, exclusive. (N)
// N byte PSSH data.
while (1) { while (1) {
// size of PSSH atom, used for skipping size_t start_pos = reader.pos();
uint32_t size;
if (!reader.Read4(&size)) { // atom size, used for skipping.
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH atom size"); uint64_t size;
if (!reader.Read4Into8(&size)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read atom size.");
return false;
}
std::vector<uint8_t> atom_type;
if (!reader.ReadVec(&atom_type, 4)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read atom type.");
return false; return false;
} }
// "pssh" if (size == 1) {
std::vector<uint8_t> pssh; if (!reader.Read8(&size)) {
if (!reader.ReadVec(&pssh, 4)) { LOGW("CdmEngine::ExtractWidevinePssh: Unable to read 64-bit atom "
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH literal"); "size.");
return false; return false;
} }
if (memcmp(&pssh[0], "pssh", 4)) { } else if (size == 0) {
LOGW("CdmEngine::ExtractWidevinePssh: PSSH literal not present"); size = reader.size() - start_pos;
}
// "pssh"
if (memcmp(&atom_type[0], "pssh", 4)) {
LOGW("CdmEngine::ExtractWidevinePssh: PSSH literal not present.");
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of the "
"atom.");
return false; return false;
} }
continue;
}
// version
uint8_t version;
if (!reader.Read1(&version)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH version.");
return false;
}
if (version > 1) {
// unrecognized version - skip.
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of the "
"atom.");
return false;
}
continue;
}
// flags // flags
uint32_t flags; if (!reader.SkipBytes(3)) {
if (!reader.Read4(&flags)) { LOGW("CdmEngine::ExtractWidevinePssh: Unable to skip the PSSH flags.");
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH flags");
return false;
}
if (flags != 0) {
LOGW("CdmEngine::ExtractWidevinePssh: PSSH flags not zero");
return false; return false;
} }
// system id // system id
std::vector<uint8_t> system_id; std::vector<uint8_t> system_id;
if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) { if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read system ID"); LOGW("CdmEngine::ExtractWidevinePssh: Unable to read system ID.");
return false; return false;
} }
if (memcmp(&system_id[0], kWidevineSystemId, if (memcmp(&system_id[0], kWidevineSystemId, sizeof(kWidevineSystemId))) {
sizeof(kWidevineSystemId))) { // skip non-Widevine PSSH boxes.
// skip the remaining contents of the atom, if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
// after size field, atom name, flags and system id LOGW("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of the "
if (!reader.SkipBytes( "atom.");
size - 4 - 4 - 4 - sizeof(kWidevineSystemId))) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to rest of PSSH atom");
return false; return false;
} }
continue; continue;
} }
// size of PSSH box if (version == 1) {
uint32_t pssh_length; // v1 has additional fields for key IDs. We can skip them.
if (!reader.Read4(&pssh_length)) { uint32_t num_key_ids;
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH box size"); if (!reader.Read4(&num_key_ids)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read num key IDs.");
return false;
}
if (!reader.SkipBytes(num_key_ids * 16)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to skip key IDs.");
return false;
}
}
// size of PSSH data
uint32_t data_length;
if (!reader.Read4(&data_length)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data size.");
return false; return false;
} }
output->clear(); output->clear();
if (!reader.ReadString(output, pssh_length)) { if (!reader.ReadString(output, data_length)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH"); LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data.");
return false; return false;
} }

View File

@@ -0,0 +1,165 @@
// Copyright 2015 Google Inc. All Rights Reserved.
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "initialization_data.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
// References:
// [1] http://dashif.org/identifiers/content-protection/
// [2] http://www.w3.org/TR/encrypted-media/cenc-format.html#common-system
namespace {
const std::string kWidevinePssh = wvcdm::a2bs_hex(
// Widevine PSSH box
"00000042" // atom size
"70737368" // atom type="pssh"
"00000000" // v0, flags=0
"edef8ba979d64acea3c827dcd51d21ed" // system id (Widevine)
"00000022" // data size
// data:
"08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031");
const std::string kWidevinePsshFirst = wvcdm::a2bs_hex(
// first PSSH box, Widevine
"00000042" // atom size
"70737368" // atom type "pssh"
"00000000" // v0, flags=0
"edef8ba979d64acea3c827dcd51d21ed" // system id (Widevine)
"00000022" // data size
// data:
"08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"
// second PSSH box, Playready [1]
"00000028" // atom size
"70737368" // atom type "pssh"
"00000000" // v0, flags=0
"9a04f07998404286ab92e65be0885f95" // system id (PlayReady)
"00000008" // data size
// arbitrary data:
"0102030405060708");
const std::string kWidevinePsshAfterV0Pssh = wvcdm::a2bs_hex(
// first PSSH box, Playready [1]
"00000028" // atom size
"70737368" // atom type "pssh"
"00000000" // v0, flags=0
"9a04f07998404286ab92e65be0885f95" // system id (PlayReady)
"00000008" // data size
// arbitrary data:
"0102030405060708"
// second PSSH box, Widevine
"00000042" // atom size
"70737368" // atom type "pssh"
"00000000" // v0, flags=0
"edef8ba979d64acea3c827dcd51d21ed" // system id (Widevine)
"00000022" // data size
// data:
"08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031");
const std::string kWidevinePsshAfterNonZeroFlags = wvcdm::a2bs_hex(
// first PSSH box, Playready [1]
"00000028" // atom size
"70737368" // atom type "pssh"
"00abcdef" // v0, flags=abcdef
"9a04f07998404286ab92e65be0885f95" // system id (PlayReady)
"00000008" // data size
// arbitrary data:
"0102030405060708"
// second PSSH box, Widevine
"00000042" // atom size
"70737368" // atom type "pssh"
"00000000" // v0, flags=0
"edef8ba979d64acea3c827dcd51d21ed" // system id (Widevine)
"00000022" // data size
// data:
"08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031");
const std::string kWidevinePsshAfterV1Pssh = wvcdm::a2bs_hex(
// first PSSH box, generic CENC [2]
"00000044" // atom size
"70737368" // atom type "pssh"
"01000000" // v1, flags=0
"1077efecc0b24d02ace33c1e52e2fb4b" // system id (generic CENC)
"00000002" // key ID count
"30313233343536373839303132333435" // key ID="0123456789012345"
"38393031323334354142434445464748" // key ID="ABCDEFGHIJKLMNOP"
"00000000" // data size=0
// second PSSH box, Widevine
"00000042" // atom size
"70737368" // atom type "pssh"
"00000000" // v0, flags=0
"edef8ba979d64acea3c827dcd51d21ed" // system id (Widevine)
"00000022" // data size
// data:
"08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031");
const std::string kWidevineV1Pssh = wvcdm::a2bs_hex(
// Widevine PSSH box, v1 format
"00000044" // atom size
"70737368" // atom type "pssh"
"01000000" // v1, flags=0
"edef8ba979d64acea3c827dcd51d21ed" // system id (Widevine)
"00000002" // key ID count
"30313233343536373839303132333435" // key ID="0123456789012345"
"38393031323334354142434445464748" // key ID="ABCDEFGHIJKLMNOP"
"00000022" // data size
// data:
"08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031");
const std::string kOtherBoxFirst = wvcdm::a2bs_hex(
// first box, not a PSSH box
"00000018" // atom size
"77686174" // atom type "what"
"deadbeefdeadbeefdeadbeefdeadbeef" // garbage box data
// second box, a Widevine PSSH box
"00000042" // atom size
"70737368" // atom type "pssh"
"00000000" // v0, flags=0
"edef8ba979d64acea3c827dcd51d21ed" // system id (Widevine)
"00000022" // data size
// data:
"08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031");
const std::string kZeroSizedPsshBox = wvcdm::a2bs_hex(
// Widevine PSSH box
"00000000" // atom size (whole buffer)
"70737368" // atom type="pssh"
"00000000" // v0, flags=0
"edef8ba979d64acea3c827dcd51d21ed" // system id (Widevine)
"00000022" // data size
// data:
"08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031");
class InitializationDataTest
: public ::testing::TestWithParam<std::string> {};
} // namespace
namespace wvcdm {
TEST_P(InitializationDataTest, Parse) {
InitializationData init_data(ISO_BMFF_VIDEO_MIME_TYPE, GetParam());
EXPECT_FALSE(init_data.IsEmpty());
}
INSTANTIATE_TEST_CASE_P(
ParsePssh, InitializationDataTest,
::testing::Values(
kWidevinePssh,
kWidevinePsshFirst,
kWidevinePsshAfterV0Pssh,
kWidevinePsshAfterNonZeroFlags,
kWidevinePsshAfterV1Pssh,
kWidevineV1Pssh,
kOtherBoxFirst,
kZeroSizedPsshBox
));
} // namespace wvcdm

View File

@@ -31,6 +31,10 @@ test_name := http_socket_test
test_src_dir := ../core/test test_src_dir := ../core/test
include $(LOCAL_PATH)/unit-test.mk include $(LOCAL_PATH)/unit-test.mk
test_name := initialization_data_unittest
test_src_dir := ../core/test
include $(LOCAL_PATH)/unit-test.mk
test_name := license_unittest test_name := license_unittest
test_src_dir := ../core/test test_src_dir := ../core/test
include $(LOCAL_PATH)/unit-test.mk include $(LOCAL_PATH)/unit-test.mk

View File

@@ -17,6 +17,7 @@ adb shell /system/bin/libwvdrmdrmplugin_test
adb shell /system/bin/cdm_engine_test adb shell /system/bin/cdm_engine_test
adb shell /system/bin/cdm_session_unittest adb shell /system/bin/cdm_session_unittest
adb shell /system/bin/file_store_unittest adb shell /system/bin/file_store_unittest
adb shell /system/bin/initialization_data_unittest
adb shell /system/bin/device_files_unittest adb shell /system/bin/device_files_unittest
adb shell /system/bin/timer_unittest adb shell /system/bin/timer_unittest
adb shell LD_LIBRARY_PATH=/system/vendor/lib/mediadrm/ /system/bin/libwvdrmengine_test adb shell LD_LIBRARY_PATH=/system/vendor/lib/mediadrm/ /system/bin/libwvdrmengine_test