From 4252a4b790ec95740b8633028c2f5943498ad8e0 Mon Sep 17 00:00:00 2001 From: "John \"Juce\" Bruce" Date: Wed, 4 Mar 2015 13:57:09 -0800 Subject: [PATCH] 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 --- .../cdm/core/src/initialization_data.cpp | 136 ++++++++++----- .../test/initialization_data_unittest.cpp | 165 ++++++++++++++++++ libwvdrmengine/cdm/test/Android.mk | 4 + libwvdrmengine/run_all_unit_tests.sh | 1 + 4 files changed, 259 insertions(+), 47 deletions(-) create mode 100644 libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp diff --git a/libwvdrmengine/cdm/core/src/initialization_data.cpp b/libwvdrmengine/cdm/core/src/initialization_data.cpp index c12fe8b3..379b10d6 100644 --- a/libwvdrmengine/cdm/core/src/initialization_data.cpp +++ b/libwvdrmengine/cdm/core/src/initialization_data.cpp @@ -14,12 +14,10 @@ namespace wvcdm { InitializationData::InitializationData(const std::string& type, const CdmInitData& data) : type_(type), is_cenc_(false), is_webm_(false) { - if (type == ISO_BMFF_VIDEO_MIME_TYPE || - type == ISO_BMFF_AUDIO_MIME_TYPE || + if (type == ISO_BMFF_VIDEO_MIME_TYPE || type == ISO_BMFF_AUDIO_MIME_TYPE || type == CENC_INIT_DATA_FORMAT) { is_cenc_ = true; - } else if (type == WEBM_VIDEO_MIME_TYPE || - type == WEBM_AUDIO_MIME_TYPE || + } else if (type == WEBM_VIDEO_MIME_TYPE || type == WEBM_AUDIO_MIME_TYPE || type == WEBM_INIT_DATA_FORMAT) { 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 // Widevine PSSH. -bool InitializationData::ExtractWidevinePssh( - const CdmInitData& init_data, CdmInitData* output) { - BufferReader reader( - reinterpret_cast(init_data.data()), init_data.length()); +bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, + CdmInitData* output) { + BufferReader reader(reinterpret_cast(init_data.data()), + init_data.length()); - // TODO(kqyang): Extracted from an actual init_data; - // Need to find out where it comes from. + // Widevine's registered system ID. static const uint8_t kWidevineSystemId[] = { 0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED, }; - // one PSSH blob consists of: - // 4 byte size of the PSSH atom, inclusive - // "pssh" - // 4 byte flags, value 0 - // 16 byte system id - // 4 byte size of PSSH data, exclusive + // one PSSH box consists of: + // 4 byte size of the atom, inclusive. (0 means the rest of the buffer.) + // 4 byte atom type, "pssh". + // (optional, if size == 1) 8 byte size of the atom, inclusive. + // 1 byte version, value 0 or 1. (skip if larger.) + // 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) { - // size of PSSH atom, used for skipping - uint32_t size; - if (!reader.Read4(&size)) { - LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH atom size"); + size_t start_pos = reader.pos(); + + // atom size, used for skipping. + uint64_t size; + if (!reader.Read4Into8(&size)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read atom size."); return false; } + std::vector atom_type; + if (!reader.ReadVec(&atom_type, 4)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read atom type."); + return false; + } + + if (size == 1) { + if (!reader.Read8(&size)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read 64-bit atom " + "size."); + return false; + } + } else if (size == 0) { + size = reader.size() - start_pos; + } // "pssh" - std::vector pssh; - if (!reader.ReadVec(&pssh, 4)) { - LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH literal"); - return false; + 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; + } + continue; } - if (memcmp(&pssh[0], "pssh", 4)) { - LOGW("CdmEngine::ExtractWidevinePssh: PSSH literal not present"); + + // version + uint8_t version; + if (!reader.Read1(&version)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH version."); return false; } - // flags - uint32_t flags; - if (!reader.Read4(&flags)) { - LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH flags"); - 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; } - if (flags != 0) { - LOGW("CdmEngine::ExtractWidevinePssh: PSSH flags not zero"); + + // flags + if (!reader.SkipBytes(3)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to skip the PSSH flags."); return false; } // system id std::vector system_id; 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; } - if (memcmp(&system_id[0], kWidevineSystemId, - sizeof(kWidevineSystemId))) { - // skip the remaining contents of the atom, - // after size field, atom name, flags and system id - if (!reader.SkipBytes( - size - 4 - 4 - 4 - sizeof(kWidevineSystemId))) { - LOGW("CdmEngine::ExtractWidevinePssh: Unable to rest of PSSH atom"); + if (memcmp(&system_id[0], kWidevineSystemId, sizeof(kWidevineSystemId))) { + // skip non-Widevine PSSH boxes. + if (!reader.SkipBytes(size - (reader.pos() - start_pos))) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of the " + "atom."); return false; } continue; } - // size of PSSH box - uint32_t pssh_length; - if (!reader.Read4(&pssh_length)) { - LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH box size"); + if (version == 1) { + // v1 has additional fields for key IDs. We can skip them. + uint32_t num_key_ids; + 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; } output->clear(); - if (!reader.ReadString(output, pssh_length)) { - LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH"); + if (!reader.ReadString(output, data_length)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data."); return false; } diff --git a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp new file mode 100644 index 00000000..2fa72a24 --- /dev/null +++ b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp @@ -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 {}; + +} // 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 diff --git a/libwvdrmengine/cdm/test/Android.mk b/libwvdrmengine/cdm/test/Android.mk index 68bc4730..e07088e2 100644 --- a/libwvdrmengine/cdm/test/Android.mk +++ b/libwvdrmengine/cdm/test/Android.mk @@ -31,6 +31,10 @@ test_name := http_socket_test test_src_dir := ../core/test 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_src_dir := ../core/test include $(LOCAL_PATH)/unit-test.mk diff --git a/libwvdrmengine/run_all_unit_tests.sh b/libwvdrmengine/run_all_unit_tests.sh index cb8cd9e9..b28978c3 100755 --- a/libwvdrmengine/run_all_unit_tests.sh +++ b/libwvdrmengine/run_all_unit_tests.sh @@ -17,6 +17,7 @@ adb shell /system/bin/libwvdrmdrmplugin_test adb shell /system/bin/cdm_engine_test adb shell /system/bin/cdm_session_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/timer_unittest adb shell LD_LIBRARY_PATH=/system/vendor/lib/mediadrm/ /system/bin/libwvdrmengine_test