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:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
165
libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp
Normal file
165
libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user