From 355471c408dd086b16e7e00230705aff35f3738f Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Thu, 7 Jan 2016 13:06:42 -0800 Subject: [PATCH] Modify initialization data to support HLS [ Merge of http://go/wvgerrit/16290 ] HLS uses an EXT-X-KEY tag and attribute list in the media playlist to identify the key and method used to encrypt media segments. This allows for the attributes to be parsed and extracted. b/20630275 Change-Id: I2c4a419022f933b7b34b64dc48930f167abe65c6 --- .../cdm/core/include/initialization_data.h | 28 +- .../cdm/core/include/wv_cdm_constants.h | 12 + .../cdm/core/include/wv_cdm_types.h | 17 + .../cdm/core/src/initialization_data.cpp | 242 ++++++++++++-- .../test/initialization_data_unittest.cpp | 306 +++++++++++++++++- 5 files changed, 571 insertions(+), 34 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/initialization_data.h b/libwvdrmengine/cdm/core/include/initialization_data.h index 80803c7b..b996c0db 100644 --- a/libwvdrmengine/cdm/core/include/initialization_data.h +++ b/libwvdrmengine/cdm/core/include/initialization_data.h @@ -16,23 +16,49 @@ class InitializationData { InitializationData(const std::string& type = std::string(), const CdmInitData& data = CdmInitData()); - bool is_supported() const { return is_cenc_ || is_webm_; } + bool is_supported() const { return is_cenc_ || is_webm_ || is_hls_; } bool is_cenc() const { return is_cenc_; } + bool is_hls() const { return is_hls_; } bool is_webm() const { return is_webm_; } bool IsEmpty() const { return data_.empty(); } const std::string& type() const { return type_; } const CdmInitData& data() const { return data_; } + const CdmHlsData& hls_data() const { return hls_data_; } private: // Parse a blob of multiple concatenated PSSH atoms to extract the first // Widevine PSSH. bool ExtractWidevinePssh(const CdmInitData& init_data, CdmInitData* output); + bool ExtractHlsAttributes(const std::string& attribute_list, + CdmHlsMethod* method, std::vector* iv, + std::string* uri, CdmInitData* init_data); + + static bool ExtractQuotedAttribute(const std::string& attribute_list, + const std::string& key, + std::string* value); + static bool ExtractHexAttribute(const std::string& attribute_list, + const std::string& key, + std::vector* value); + static bool ExtractAttribute(const std::string& attribute_list, + const std::string& key, std::string* value); + +// For testing only: +#if defined(UNIT_TEST) + FRIEND_TEST(HlsAttributeExtractionTest, ExtractAttribute); + FRIEND_TEST(HlsParseTest, Parse); + FRIEND_TEST(HlsTest, ExtractHlsAttributes); + FRIEND_TEST(HlsHexAttributeExtractionTest, ExtractHexAttribute); + FRIEND_TEST(HlsQuotedAttributeExtractionTest, ExtractQuotedAttribute); +#endif + std::string type_; CdmInitData data_; + CdmHlsData hls_data_; bool is_cenc_; + bool is_hls_; bool is_webm_; }; diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h index c576d8a4..2a0429b3 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h @@ -80,8 +80,20 @@ static const std::string ISO_BMFF_AUDIO_MIME_TYPE = "audio/mp4"; static const std::string WEBM_VIDEO_MIME_TYPE = "video/webm"; static const std::string WEBM_AUDIO_MIME_TYPE = "audio/webm"; static const std::string CENC_INIT_DATA_FORMAT = "cenc"; +static const std::string HLS_INIT_DATA_FORMAT = "hls"; static const std::string WEBM_INIT_DATA_FORMAT = "webm"; +static const std::string HLS_INITDATA_ATTRIBUTE = "X-WV-INITDATA"; +static const std::string HLS_KEYFORMAT_ATTRIBUTE = "KEYFORMAT"; +static const std::string HLS_KEYFORMAT_VERSION_ATTRIBUTE = "KEYFORMATVERSION"; +static const std::string HLS_KEYFORMAT_VERSION_VALUE_1 = "1"; +static const std::string HLS_METHOD_ATTRIBUTE = "METHOD"; +static const std::string HLS_METHOD_AES_128 = "AES-128"; +static const std::string HLS_METHOD_NONE = "NONE"; +static const std::string HLS_METHOD_SAMPLE_AES = "SAMPLE-AES"; +static const std::string HLS_IV_ATTRIBUTE = "IV"; +static const std::string HLS_URI_ATTRIBUTE = "URI"; + static const char EMPTY_ORIGIN[] = ""; } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index 40f8b055..0507ef08 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -209,6 +209,10 @@ enum CdmResponseType { EMPTY_PROVISIONING_CERTIFICATE_2, OFFLINE_LICENSE_PROHIBITED, STORAGE_PROHIBITED, + EMPTY_KEYSET_ID_ENG_5, + SESSION_NOT_FOUND_11, + LOAD_USAGE_INFO_FILE_ERROR, + LOAD_USAGE_INFO_MISSING, }; enum CdmKeyStatus { @@ -254,6 +258,19 @@ enum CdmCertificateType { kCertificateX509, }; +enum CdmHlsMethod { + kHlsMethodNone, + kHlsMethodAes128, + kHlsMethodSampleAes, +}; + +struct CdmHlsData { + CdmHlsData() : method(kHlsMethodNone) {} + CdmHlsMethod method; + std::vector iv; + std::string uri; +}; + struct CdmDecryptionParameters { bool is_encrypted; bool is_secure; diff --git a/libwvdrmengine/cdm/core/src/initialization_data.cpp b/libwvdrmengine/cdm/core/src/initialization_data.cpp index a8378586..3f33f4aa 100644 --- a/libwvdrmengine/cdm/core/src/initialization_data.cpp +++ b/libwvdrmengine/cdm/core/src/initialization_data.cpp @@ -7,26 +7,35 @@ #include "buffer_reader.h" #include "log.h" #include "properties.h" +#include "string_conversions.h" #include "wv_cdm_constants.h" namespace wvcdm { InitializationData::InitializationData(const std::string& type, const CdmInitData& data) - : type_(type), is_cenc_(false), is_webm_(false) { + : type_(type), is_cenc_(false), is_hls_(false), is_webm_(false) { 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 || type == WEBM_INIT_DATA_FORMAT) { is_webm_ = true; + } else if (type == HLS_INIT_DATA_FORMAT) { + is_hls_ = true; } if (is_supported()) { if (is_cenc()) { ExtractWidevinePssh(data, &data_); - } else { + } else if (is_webm()) { data_ = data; + } else if (is_hls()) { + CdmInitData init_data; + if (ExtractHlsAttributes(data, &hls_data_.method, &hls_data_.iv, + &hls_data_.uri, &init_data)) { + ExtractWidevinePssh(init_data, &data_); + } } } } @@ -61,19 +70,22 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // atom size, used for skipping. uint64_t size; if (!reader.Read4Into8(&size)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read atom size."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read atom size."); return false; } std::vector atom_type; if (!reader.ReadVec(&atom_type, 4)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read atom type."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read atom type."); return false; } if (size == 1) { if (!reader.Read8(&size)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read 64-bit atom " - "size."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read 64-bit " + "atom size."); return false; } } else if (size == 0) { @@ -82,10 +94,12 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // "pssh" if (memcmp(&atom_type[0], "pssh", 4)) { - LOGV("CdmEngine::ExtractWidevinePssh: PSSH literal not present."); + LOGV( + "InitializationData::ExtractWidevinePssh: PSSH literal not present."); if (!reader.SkipBytes(size - (reader.pos() - start_pos))) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of " - "the atom."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip the rest " + "of the atom."); return false; } continue; @@ -94,15 +108,18 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // version uint8_t version; if (!reader.Read1(&version)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH version."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read PSSH " + "version."); return false; } if (version > 1) { // unrecognized version - skip. if (!reader.SkipBytes(size - (reader.pos() - start_pos))) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of " - "the atom."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip the rest " + "of the atom."); return false; } continue; @@ -110,25 +127,31 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // flags if (!reader.SkipBytes(3)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the PSSH flags."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip the PSSH " + "flags."); return false; } // system id std::vector system_id; if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read system ID."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read system ID."); return false; } if (memcmp(&system_id[0], kWidevineSystemId, sizeof(kWidevineSystemId))) { // skip non-Widevine PSSH boxes. if (!reader.SkipBytes(size - (reader.pos() - start_pos))) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of " - "the atom."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip the rest " + "of the atom."); return false; } - LOGV("CdmEngine::ExtractWidevinePssh: Skipping non-Widevine PSSH."); + LOGV( + "InitializationData::ExtractWidevinePssh: Skipping non-Widevine " + "PSSH."); continue; } @@ -136,11 +159,14 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // v1 has additional fields for key IDs. We can skip them. uint32_t num_key_ids; if (!reader.Read4(&num_key_ids)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read num key IDs."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read num key " + "IDs."); return false; } if (!reader.SkipBytes(num_key_ids * 16)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip key IDs."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to skip key IDs."); return false; } } @@ -148,13 +174,16 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, // size of PSSH data uint32_t data_length; if (!reader.Read4(&data_length)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data size."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read PSSH data " + "size."); return false; } output->clear(); if (!reader.ReadString(output, data_length)) { - LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data."); + LOGV( + "InitializationData::ExtractWidevinePssh: Unable to read PSSH data."); return false; } @@ -165,4 +194,175 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data, return false; } +// Parse an EXT-X-KEY tag attribute list. Verify that Widevine supports it +// by validating KEYFORMAT and KEYFORMATVERSION attributes. Extract out +// method, IV, URI and WV init data. +// +// An example of a widevine supported attribute list from an HLS media playlist +// is, +// "EXT-X-KEY: METHOD=SAMPLE-AES, \" +// "URI=”http://www.youtube.com/license-service”, \" +// "IV=0x6df49213a781e338628d0e9c812d328e, \" +// "KEYFORMAT=”com.widevine.alpha”, \" +// "KEYFORMATVERSION=”1”, \" +// "X-WV-INITDATA=0x9e1a3af3de74ae3606e931fee285e3402e172732aba9a16a0e1441f1e" +bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list, + CdmHlsMethod* method, + std::vector* iv, + std::string* uri, + CdmInitData* init_data) { + std::string value; + if (!ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_ATTRIBUTE, + &value)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS " + "keyformat value"); + return false; + } + + if (value.compare(0, sizeof(KEY_SYSTEM) - 1, KEY_SYSTEM) != 0) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: unrecognized HLS " + "keyformat value: %s", + value.c_str()); + return false; + } + + if (!ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_VERSION_ATTRIBUTE, + &value)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS " + "keyformat version"); + return false; + } + + if (value.compare(HLS_KEYFORMAT_VERSION_VALUE_1)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS keyformat " + "version is not supported: %s", + value.c_str()); + return false; + } + + if (!ExtractAttribute(attribute_list, HLS_METHOD_ATTRIBUTE, &value)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS " + "method"); + return false; + } + + if (value.compare(HLS_METHOD_AES_128) == 0) { + *method = kHlsMethodAes128; + } else if (value.compare(HLS_METHOD_SAMPLE_AES) == 0) { + *method = kHlsMethodSampleAes; + } else if (value.compare(HLS_METHOD_NONE) == 0) { + *method = kHlsMethodNone; + } else { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS method " + "unrecognized: %s", + value.c_str()); + return false; + } + + if (!ExtractHexAttribute(attribute_list, HLS_IV_ATTRIBUTE, iv)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS IV attribute " + "not present"); + return false; + } + + if (!ExtractQuotedAttribute(attribute_list, HLS_URI_ATTRIBUTE, uri)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS URI attribute " + "not present"); + return false; + } + + std::vector init_data_hex; + if (!ExtractHexAttribute(attribute_list, HLS_INITDATA_ATTRIBUTE, + &init_data_hex)) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS initdata " + "attribute not present"); + return false; + } + + init_data->assign(reinterpret_cast(init_data_hex.data()), + init_data_hex.size()); + return true; +} + +bool InitializationData::ExtractQuotedAttribute( + const std::string& attribute_list, const std::string& key, + std::string* value) { + bool result = ExtractAttribute(attribute_list, key, value); + if (value->size() < 2 || value->at(0) != '\"' || + value->at(value->size() - 1) != '\"') + return false; + *value = value->substr(1, value->size() - 2); + if (value->find('\"') != std::string::npos) return false; + return result; +} + +bool InitializationData::ExtractHexAttribute(const std::string& attribute_list, + const std::string& key, + std::vector* value) { + std::string val; + bool result = ExtractAttribute(attribute_list, key, &val); + if (!result || val.size() <= 2 || val.size() % 2 != 0 || val[0] != '0' || + ((val[1] != 'x') && (val[1] != 'X'))) + return false; + for (size_t i = 2; i < val.size(); ++i) { + if (!isxdigit(val[i])) return false; + } + *value = a2b_hex(val.substr(2, val.size() - 2)); + return result; +} + +bool InitializationData::ExtractAttribute(const std::string& attribute_list, + const std::string& key, + std::string* value) { + // validate the key + for (size_t i = 0; i < key.size(); ++i) + if (!isupper(key[i]) && !isdigit(key[i]) && key[i] != '-') return false; + + bool found = false; + size_t pos = 0; + // Find the key followed by '=' + while (!found) { + pos = attribute_list.find(key, pos); + if (pos == std::string::npos) return false; + pos += key.size(); + if (attribute_list[pos] != '=') continue; + found = true; + } + + if (attribute_list.size() <= ++pos) return false; + + size_t end_pos = pos; + found = false; + // Find the next comma outside the quote or end of string + while (!found) { + end_pos = attribute_list.find(',', end_pos); + if (end_pos != std::string::npos && attribute_list[pos] == '\"' && + attribute_list[end_pos - 1] != '\"') + continue; + + if (end_pos == std::string::npos) + end_pos = attribute_list.size() - 1; + else + --end_pos; + found = true; + } + + *value = attribute_list.substr(pos, end_pos - pos + 1); + + // validate the value + for (size_t i = 0; i < value->size(); ++i) + if (!isgraph(value->at(i))) return false; + + return true; +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp index a739803f..8959fc85 100644 --- a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp @@ -139,8 +139,117 @@ const std::string kZeroSizedPsshBox = a2bs_hex( // data: "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); +// HLS test attribute key and values +const std::string kHlsUriValue = "http://www.youtube.com/license-service"; +const std::string kHlsIvHexValue = "6DF49213A781E338628D0E9C812D328E"; +const std::string kHlsIvValue = "0x" + kHlsIvHexValue; +const std::string kHlsKeyFormatValue = "com.widevine.alpha"; +const std::string kHlsKeyFormatValueOther = "com.example"; +const std::string kHlsInitDataHexValue = b2a_hex(kWidevineV1Pssh); +const std::string kHlsInitDataValue = "0x" + kHlsInitDataHexValue; +const std::string kHlsTestKey1 = "TESTKEY1"; +const std::string kHlsTestValue1 = "testvalue1"; +const std::string kHlsTestKey2 = "TESTKEY2"; +const std::string kHlsTestValue2 = "testvalue2"; +const std::string kHlsTestInvalidLowercaseKey = "testkey3"; +const std::string kHlsTestKeyWithDash = "TEST-KEY4"; +const std::string kHlsTestInvalidNonAlphanumKey = "TEST;KEY4"; +const std::string kHlsTestValueWithEmbeddedQuote = "test\"value1"; +const std::string kHlsTestEmptyHexValue = ""; +const std::string kHlsTestNoHexValue = "0x"; +const std::string kHlsTestHexValueWithOddBytes = kHlsIvHexValue + "7"; +const std::string kHlsTestInvalidHexValue = kHlsIvHexValue + "g7"; + +// HLS attribute helper functions +std::string QuoteString(const std::string& value) { + return "\"" + value + "\""; +} + +std::string CreateHlsAttributeList(const std::string& method, + const std::string& uri, + const std::string& iv, + const std::string& key_format, + const std::string& key_format_version, + const std::string& init_data) { + return "EXT-X-KEY: " + HLS_METHOD_ATTRIBUTE + "=" + method + "," + + HLS_URI_ATTRIBUTE + "=" + QuoteString(uri) + "," + HLS_IV_ATTRIBUTE + + "=" + iv + "," + HLS_KEYFORMAT_ATTRIBUTE + "=" + + QuoteString(key_format) + "," + HLS_KEYFORMAT_VERSION_ATTRIBUTE + "=" + + QuoteString(key_format_version) + "," + HLS_INITDATA_ATTRIBUTE + "=" + + init_data; +} + +// HLS attribute list for testing +const std::string kHlsAttributeList = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsUriValue, kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1, kHlsInitDataValue); + +const std::string kHlsAttributeListKeyFormatUnknown = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsUriValue, kHlsIvValue, kHlsKeyFormatValueOther, + HLS_KEYFORMAT_VERSION_VALUE_1, kHlsInitDataValue); + +const std::string kHlsAttributeListKeyFormatVersionUnsupported = + CreateHlsAttributeList(HLS_METHOD_SAMPLE_AES, kHlsUriValue, kHlsIvValue, + kHlsKeyFormatValue, "2", kHlsInitDataValue); + +const std::string kHlsAttributeListMethodAes128 = CreateHlsAttributeList( + HLS_METHOD_AES_128, kHlsUriValue, kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1, kHlsInitDataValue); + +const std::string kHlsAttributeListMethodNone = CreateHlsAttributeList( + HLS_METHOD_NONE, kHlsUriValue, kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1, kHlsInitDataValue); + +const std::string kHlsAttributeListMethodInvalid = CreateHlsAttributeList( + kHlsTestValue1, kHlsUriValue, kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1, kHlsInitDataValue); + +const std::string kHlsAttributeListInvalidUri = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestValueWithEmbeddedQuote, kHlsIvValue, + kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1, kHlsInitDataValue); + +const std::string kHlsAttributeListInvalidIv = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsTestHexValueWithOddBytes, kHlsTestNoHexValue, + kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1, kHlsInitDataValue); + +const std::string kHlsAttributeListInvalidInitData = CreateHlsAttributeList( + HLS_METHOD_SAMPLE_AES, kHlsUriValue, kHlsIvValue, kHlsKeyFormatValue, + HLS_KEYFORMAT_VERSION_VALUE_1, kHlsTestInvalidHexValue); + +std::string InsertHlsAttributeInList(const std::string key, + const std::string& value) { + return kHlsAttributeList + "," + key + "=" + value + "," + kHlsTestKey2 + + "=" + kHlsTestValue2; +} + +struct HlsAttributeVariant { + HlsAttributeVariant(const std::string& attribute_list, const std::string& key, + const std::string& value, bool success) + : attribute_list_(attribute_list), + key_(key), + value_(value), + success_(success) {} + const std::string attribute_list_; + const std::string key_; + const std::string value_; + const bool success_; +}; + class InitializationDataTest : public ::testing::TestWithParam {}; +class HlsAttributeExtractionTest + : public ::testing::TestWithParam {}; + +class HlsHexAttributeExtractionTest + : public ::testing::TestWithParam {}; + +class HlsQuotedAttributeExtractionTest + : public ::testing::TestWithParam {}; + +class HlsParseTest + : public ::testing::TestWithParam {}; + +class HlsTest : public ::testing::Test {}; } // namespace TEST_P(InitializationDataTest, Parse) { @@ -148,17 +257,190 @@ TEST_P(InitializationDataTest, Parse) { EXPECT_FALSE(init_data.IsEmpty()); } -INSTANTIATE_TEST_CASE_P( - ParsePssh, InitializationDataTest, - ::testing::Values( - kWidevinePssh, - kWidevinePsshFirst, - kWidevinePsshAfterV0Pssh, - kWidevinePsshAfterNonZeroFlags, - kWidevinePsshAfterV1Pssh, - kWidevineV1Pssh, - kOtherBoxFirst, - kZeroSizedPsshBox - )); +INSTANTIATE_TEST_CASE_P(ParsePssh, InitializationDataTest, + ::testing::Values(kWidevinePssh, kWidevinePsshFirst, + kWidevinePsshAfterV0Pssh, + kWidevinePsshAfterNonZeroFlags, + kWidevinePsshAfterV1Pssh, + kWidevineV1Pssh, kOtherBoxFirst, + kZeroSizedPsshBox)); +TEST_P(HlsAttributeExtractionTest, ExtractAttribute) { + HlsAttributeVariant param = GetParam(); + std::string value; + if (param.success_) { + EXPECT_TRUE(InitializationData::ExtractAttribute(param.attribute_list_, + param.key_, &value)); + EXPECT_EQ(param.value_, value); + } else { + EXPECT_FALSE(InitializationData::ExtractAttribute(param.attribute_list_, + param.key_, &value)); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsAttributeExtractionTest, + ::testing::Values( + HlsAttributeVariant(kHlsAttributeList, HLS_METHOD_ATTRIBUTE, + HLS_METHOD_SAMPLE_AES, true), + HlsAttributeVariant(kHlsAttributeList, HLS_URI_ATTRIBUTE, + QuoteString(kHlsUriValue), true), + HlsAttributeVariant(kHlsAttributeList, HLS_IV_ATTRIBUTE, kHlsIvValue, + true), + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_ATTRIBUTE, + QuoteString(kHlsKeyFormatValue), true), + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_VERSION_ATTRIBUTE, + QuoteString(HLS_KEYFORMAT_VERSION_VALUE_1), true), + HlsAttributeVariant(kHlsAttributeList, HLS_INITDATA_ATTRIBUTE, + kHlsInitDataValue, true), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestValue1), + kHlsTestKey1, kHlsTestValue1, true), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestValue1), + kHlsTestKey2, kHlsTestValue2, true), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1 + "\t", + kHlsTestValue1), + kHlsTestKey1, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + " " + kHlsTestValue1), + kHlsTestKey1, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestValue1 + " "), + kHlsTestKey1, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1 + "3", + kHlsTestValue1), + kHlsTestKey1, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, ""), + kHlsTestKey1, "", true), + HlsAttributeVariant(InsertHlsAttributeInList( + kHlsTestInvalidLowercaseKey, kHlsTestValue1), + kHlsTestInvalidLowercaseKey, kHlsTestValue1, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKeyWithDash, + kHlsTestValue1), + kHlsTestKeyWithDash, kHlsTestValue1, true), + HlsAttributeVariant(InsertHlsAttributeInList( + kHlsTestInvalidNonAlphanumKey, kHlsTestValue1), + kHlsTestInvalidNonAlphanumKey, kHlsTestValue1, + false), + HlsAttributeVariant( + InsertHlsAttributeInList(kHlsTestKey1, QuoteString(kHlsTestValue1)), + kHlsTestKey1, QuoteString(kHlsTestValue1), true), + HlsAttributeVariant( + InsertHlsAttributeInList( + kHlsTestKey1, QuoteString(kHlsTestValueWithEmbeddedQuote)), + kHlsTestKey1, QuoteString(kHlsTestValueWithEmbeddedQuote), true))); + +TEST_P(HlsHexAttributeExtractionTest, ExtractHexAttribute) { + HlsAttributeVariant param = GetParam(); + std::vector value; + if (param.success_) { + EXPECT_TRUE(InitializationData::ExtractHexAttribute(param.attribute_list_, + param.key_, &value)); + EXPECT_EQ(param.value_, b2a_hex(value)); + } else { + EXPECT_FALSE(InitializationData::ExtractHexAttribute(param.attribute_list_, + param.key_, &value)); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsHexAttributeExtractionTest, + ::testing::Values( + HlsAttributeVariant(kHlsAttributeList, HLS_IV_ATTRIBUTE, kHlsIvHexValue, + true), + HlsAttributeVariant(kHlsAttributeList, HLS_INITDATA_ATTRIBUTE, + kHlsInitDataHexValue, true), + HlsAttributeVariant(kHlsAttributeList, HLS_INITDATA_ATTRIBUTE, + kHlsInitDataHexValue, true), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestEmptyHexValue), + kHlsTestKey1, kHlsTestEmptyHexValue, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestNoHexValue), + kHlsTestKey1, kHlsTestNoHexValue, false), + HlsAttributeVariant(InsertHlsAttributeInList( + kHlsTestKey1, kHlsTestHexValueWithOddBytes), + kHlsTestKey1, kHlsTestHexValueWithOddBytes, false), + HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, + kHlsTestInvalidHexValue), + kHlsTestKey1, kHlsTestInvalidHexValue, false))); + +TEST_P(HlsQuotedAttributeExtractionTest, ExtractQuotedAttribute) { + HlsAttributeVariant param = GetParam(); + std::string value; + if (param.success_) { + EXPECT_TRUE(InitializationData::ExtractQuotedAttribute( + param.attribute_list_, param.key_, &value)); + EXPECT_EQ(param.value_, value); + } else { + EXPECT_FALSE(InitializationData::ExtractQuotedAttribute( + param.attribute_list_, param.key_, &value)); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsQuotedAttributeExtractionTest, + ::testing::Values( + HlsAttributeVariant(kHlsAttributeList, HLS_URI_ATTRIBUTE, kHlsUriValue, + true), + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_ATTRIBUTE, + kHlsKeyFormatValue, true), + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_VERSION_ATTRIBUTE, + HLS_KEYFORMAT_VERSION_VALUE_1, true), + HlsAttributeVariant( + InsertHlsAttributeInList(kHlsTestKey1, QuoteString(kHlsTestValue1)), + kHlsTestKey1, kHlsTestValue1, true), + HlsAttributeVariant( + InsertHlsAttributeInList( + kHlsTestKey1, QuoteString(kHlsTestValueWithEmbeddedQuote)), + kHlsTestKey1, kHlsTestValueWithEmbeddedQuote, false))); + +TEST_P(HlsParseTest, Parse) { + HlsAttributeVariant param = GetParam(); + InitializationData init_data(HLS_INIT_DATA_FORMAT, param.attribute_list_); + if (param.success_) { + EXPECT_TRUE(init_data.is_hls()); + EXPECT_FALSE(init_data.IsEmpty()); + if (param.key_.compare(HLS_METHOD_ATTRIBUTE) == 0) { + if (param.value_.compare(HLS_METHOD_SAMPLE_AES) == 0) { + EXPECT_EQ(kHlsMethodSampleAes, init_data.hls_data().method); + } else if (param.value_.compare(HLS_METHOD_AES_128) == 0) { + EXPECT_EQ(kHlsMethodAes128, init_data.hls_data().method); + } else if (param.value_.compare(HLS_METHOD_NONE) == 0) { + EXPECT_EQ(kHlsMethodNone, init_data.hls_data().method); + } + } else { + EXPECT_EQ(kHlsMethodSampleAes, init_data.hls_data().method); + } + EXPECT_EQ(kHlsIvHexValue, b2a_hex(init_data.hls_data().iv)); + EXPECT_EQ(kHlsUriValue, init_data.hls_data().uri); + } else { + EXPECT_TRUE(init_data.is_hls()); + EXPECT_TRUE(init_data.IsEmpty()); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsParseTest, + ::testing::Values( + HlsAttributeVariant(kHlsAttributeList, "", "", true), + HlsAttributeVariant(kHlsAttributeListKeyFormatUnknown, + HLS_KEYFORMAT_ATTRIBUTE, kHlsKeyFormatValueOther, + false), + HlsAttributeVariant(kHlsAttributeListKeyFormatVersionUnsupported, + HLS_KEYFORMAT_VERSION_ATTRIBUTE, "2", false), + HlsAttributeVariant(kHlsAttributeListMethodAes128, HLS_METHOD_ATTRIBUTE, + HLS_METHOD_AES_128, true), + HlsAttributeVariant(kHlsAttributeListMethodNone, HLS_METHOD_ATTRIBUTE, + HLS_METHOD_NONE, true), + HlsAttributeVariant(kHlsAttributeListMethodInvalid, + HLS_METHOD_ATTRIBUTE, kHlsTestValue1, false), + HlsAttributeVariant(kHlsAttributeListInvalidUri, HLS_URI_ATTRIBUTE, + kHlsTestValueWithEmbeddedQuote, false), + HlsAttributeVariant(kHlsAttributeListInvalidIv, HLS_IV_ATTRIBUTE, + kHlsTestHexValueWithOddBytes, false), + HlsAttributeVariant(kHlsAttributeListInvalidInitData, + HLS_INITDATA_ATTRIBUTE, kHlsTestInvalidHexValue, + false))); } // namespace wvcdm