diff --git a/libwvdrmengine/cdm/core/include/initialization_data.h b/libwvdrmengine/cdm/core/include/initialization_data.h index b996c0db..00650e20 100644 --- a/libwvdrmengine/cdm/core/include/initialization_data.h +++ b/libwvdrmengine/cdm/core/include/initialization_data.h @@ -25,7 +25,8 @@ class InitializationData { const std::string& type() const { return type_; } const CdmInitData& data() const { return data_; } - const CdmHlsData& hls_data() const { return hls_data_; } + std::vector hls_iv() const { return hls_iv_; } + CdmHlsMethod hls_method() const { return hls_method_; } private: // Parse a blob of multiple concatenated PSSH atoms to extract the first @@ -34,7 +35,9 @@ class InitializationData { bool ExtractHlsAttributes(const std::string& attribute_list, CdmHlsMethod* method, std::vector* iv, - std::string* uri, CdmInitData* init_data); + std::string* uri); + static bool ConstructWidevineInitData(const std::string& uri, + CdmInitData* output); static bool ExtractQuotedAttribute(const std::string& attribute_list, const std::string& key, @@ -44,22 +47,31 @@ class InitializationData { std::vector* value); static bool ExtractAttribute(const std::string& attribute_list, const std::string& key, std::string* value); + static bool ExtractJsonValue(const std::string& json, + const std::string& key, std::string* value); + + static std::vector ExtractKeyFormatVersions( + const std::string& key_format_versions); // For testing only: #if defined(UNIT_TEST) FRIEND_TEST(HlsAttributeExtractionTest, ExtractAttribute); - FRIEND_TEST(HlsParseTest, Parse); - FRIEND_TEST(HlsTest, ExtractHlsAttributes); + FRIEND_TEST(HlsConstructionTest, InitData); FRIEND_TEST(HlsHexAttributeExtractionTest, ExtractHexAttribute); + FRIEND_TEST(HlsKeyFormatVersionsExtractionTest, ExtractKeyFormatVersions); + FRIEND_TEST(HlsParseTest, Parse); FRIEND_TEST(HlsQuotedAttributeExtractionTest, ExtractQuotedAttribute); + FRIEND_TEST(HlsTest, ExtractHlsAttributes); #endif std::string type_; CdmInitData data_; - CdmHlsData hls_data_; bool is_cenc_; bool is_hls_; bool is_webm_; + + std::vector hls_iv_; + CdmHlsMethod hls_method_; }; } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h index 2a0429b3..ba782ff8 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h @@ -83,9 +83,8 @@ 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_VERSIONS_ATTRIBUTE = "KEYFORMATVERSIONS"; 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"; diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index b638f577..04bf7079 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -266,13 +266,6 @@ enum CdmHlsMethod { kHlsMethodSampleAes, }; -struct CdmHlsData { - CdmHlsData() : method(kHlsMethodNone) {} - CdmHlsMethod method; - std::vector iv; - std::string uri; -}; - enum CdmCipherMode { kCipherModeCtr, kCipherModeCbc, diff --git a/libwvdrmengine/cdm/core/src/initialization_data.cpp b/libwvdrmengine/cdm/core/src/initialization_data.cpp index 3f33f4aa..b4fcd229 100644 --- a/libwvdrmengine/cdm/core/src/initialization_data.cpp +++ b/libwvdrmengine/cdm/core/src/initialization_data.cpp @@ -5,16 +5,40 @@ #include #include "buffer_reader.h" +#include "license_protocol.pb.h" #include "log.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" +namespace { +const char kKeyFormatVersionsSeparator = '/'; +const char kColon = ':'; +const char kDoubleQuote = '\"'; +const char kLeftBracket = '['; +const char kRightBracket = ']'; +const std::string kBase64String = "base64,"; + +// json init data key values +const std::string kProvider = "provider"; +const std::string kContentId = "content_id"; +const std::string kKeyIds = "key_ids"; +} // namespace + namespace wvcdm { +// Protobuf generated classes. +using video_widevine_server::sdk::WidevineCencHeader; +using video_widevine_server::sdk::WidevineCencHeader_Algorithm; +using video_widevine_server::sdk::WidevineCencHeader_Algorithm_AESCTR; + InitializationData::InitializationData(const std::string& type, const CdmInitData& data) - : type_(type), is_cenc_(false), is_hls_(false), is_webm_(false) { + : type_(type), + is_cenc_(false), + is_hls_(false), + is_webm_(false), + hls_method_(kHlsMethodNone) { if (type == ISO_BMFF_VIDEO_MIME_TYPE || type == ISO_BMFF_AUDIO_MIME_TYPE || type == CENC_INIT_DATA_FORMAT) { is_cenc_ = true; @@ -31,10 +55,9 @@ InitializationData::InitializationData(const std::string& type, } 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_); + std::string uri; + if (ExtractHlsAttributes(data, &hls_method_, &hls_iv_, &uri)) { + ConstructWidevineInitData(uri, &data_); } } } @@ -200,17 +223,18 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& 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" +// "EXT-X-KEY: METHOD=SAMPLE-AES, \" +// "URI=”data:text/plain;base64,eyANCiAgICJwcm92aWRlciI6Im1sYmFtaGJvIiwNCiAg" +// "ICJjb250ZW50X2lkIjoiMjAxNV9UZWFycyIsDQogICAia2V5X2lkcyI6DQogICBbDQo" +// "gICAgICAiMzcxZTEzNWUxYTk4NWQ3NWQxOThhN2Y0MTAyMGRjMjMiDQogICBdDQp9DQ" +// "o=", \ +// "IV=0x6df49213a781e338628d0e9c812d328e, \" +// "KEYFORMAT=”com.widevine”, \" +// "KEYFORMATVERSIONS=”1”" bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list, CdmHlsMethod* method, std::vector* iv, - std::string* uri, - CdmInitData* init_data) { + std::string* uri) { std::string value; if (!ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_ATTRIBUTE, &value)) { @@ -228,20 +252,25 @@ bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list, 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; + // KEYFORMATVERSIONS is an optional parameter. If absent its + // value defaults to "1" + if (ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, + &value)) { + std::vector versions = ExtractKeyFormatVersions(value); + bool supported = false; + for (size_t i = 0; i < versions.size(); ++i) { + if (versions[i].compare(HLS_KEYFORMAT_VERSION_VALUE_1) == 0) { + supported = true; + break; + } + } + if (!supported) { + LOGV( + "InitializationData::ExtractHlsInitDataAtttribute: HLS keyformat " + "version is not supported: %s", + value.c_str()); + return false; + } } if (!ExtractAttribute(attribute_list, HLS_METHOD_ATTRIBUTE, &value)) { @@ -279,17 +308,91 @@ bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list, 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 true; +} + +// Extracts a base64 encoded string from URI data. This is then base64 decoded +// and the Json formatted init data is then parsed. The information is used +// to generate a Widevine init data protobuf (WidevineCencHeader). +// +// An example of a widevine supported json formatted init data string is, +// +// { +// "provider":"mlbamhbo", +// "content_id":"2015_Tears", +// "key_ids": +// [ +// "371e135e1a985d75d198a7f41020dc23" +// ] +// } +bool InitializationData::ConstructWidevineInitData( + const std::string& uri, CdmInitData* init_data_proto) { + if (!init_data_proto) { + LOGV("InitializationData::ConstructWidevineInitData: Invalid parameter"); return false; } - init_data->assign(reinterpret_cast(init_data_hex.data()), - init_data_hex.size()); + size_t pos = uri.find(kBase64String); + if (pos == std::string::npos) { + LOGV( + "InitializationData::ConstructWidevineInitData: URI attribute " + "unexpected format: %s", + uri.c_str()); + return false; + } + + std::vector json_init_data = + Base64Decode(uri.substr(pos + kBase64String.size())); + if (json_init_data.size() == 0) { + LOGV( + "InitializationData::ConstructWidevineInitData: Base64 decode of json " + "data failed"); + return false; + } + std::string json_string((const char*)(&json_init_data[0]), + json_init_data.size()); + + // Parse json data + std::string provider; + if (!ExtractJsonValue(json_string, kProvider, &provider)) { + LOGV( + "InitializationData::ConstructWidevineInitData: Unable to extract " + "provider value"); + return false; + } + + std::string content_id; + if (!ExtractJsonValue(json_string, kContentId, &content_id)) { + LOGV( + "InitializationData::ConstructWidevineInitData: Unable to extract " + "content_id value"); + return false; + } + + std::string key_id; + if (!ExtractJsonValue(json_string, kKeyIds, &key_id)) { + LOGV( + "InitializationData::ConstructWidevineInitData: Unable to extract " + "key_id values"); + return false; + } + + key_id = a2bs_hex(key_id); + if (key_id.size() != 16) { + LOGV( + "InitializationData::ConstructWidevineInitData: Invalid key_id size: " + "%ld", + key_id.size()); + return false; + } + + // Now format as Widevine init data protobuf + WidevineCencHeader cenc_header; + cenc_header.set_algorithm(WidevineCencHeader_Algorithm_AESCTR); + cenc_header.add_key_id(key_id); + cenc_header.set_provider(provider); + cenc_header.set_content_id(content_id); + cenc_header.SerializeToString(init_data_proto); return true; } @@ -365,4 +468,63 @@ bool InitializationData::ExtractAttribute(const std::string& attribute_list, return true; } +bool InitializationData::ExtractJsonValue(const std::string& json, + const std::string& key, + std::string* value) { + std::string quoted_key = key; + quoted_key.insert(0, 1, kDoubleQuote); + quoted_key.append(1, kDoubleQuote); + + bool found = false; + size_t pos = 0; + // Find the key followed by ':' + while (!found) { + pos = json.find(quoted_key, pos); + if (pos == std::string::npos) return false; + pos += quoted_key.size(); + while (pos < json.size() && isspace(json[pos])) ++pos; + if (pos >= json.size()) return false; + if (json[pos] != kColon) continue; + found = true; + } + + ++pos; + while (pos < json.size() && isspace(json[pos])) ++pos; + if (pos >= json.size()) return false; + + if (json[pos] == kLeftBracket) { + ++pos; + while (pos < json.size() && isspace(json[pos])) ++pos; + if (pos >= json.size()) return false; + } + + if (json[pos] != kDoubleQuote) return false; + ++pos; + size_t end_pos = json.find(kDoubleQuote, pos); + if (end_pos == std::string::npos) return false; + --end_pos; + *value = json.substr(pos, end_pos - pos + 1); + return true; +} + +// Key format versions are individual values or multiple versions +// separated by '/'. "1" or "1/2/5" +std::vector InitializationData::ExtractKeyFormatVersions( + const std::string& key_format_versions) { + std::vector versions; + size_t pos = 0; + while (pos < key_format_versions.size()) { + size_t next_pos = + key_format_versions.find(kKeyFormatVersionsSeparator, pos); + if (next_pos == std::string::npos) { + versions.push_back(key_format_versions.substr(pos)); + break; + } else { + versions.push_back(key_format_versions.substr(pos, next_pos - pos)); + pos = next_pos + 1; + } + } + return versions; +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/license_protocol.proto b/libwvdrmengine/cdm/core/src/license_protocol.proto index 9079594e..a81885a0 100644 --- a/libwvdrmengine/cdm/core/src/license_protocol.proto +++ b/libwvdrmengine/cdm/core/src/license_protocol.proto @@ -547,3 +547,42 @@ message SignedCertificateStatusList { // key using RSASSA-PSS. Required. optional bytes signature = 2; } + +// ---------------------------------------------------------------------------- +// widevine_header.proto +// ---------------------------------------------------------------------------- +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Description: +// Public protocol buffer definitions for Widevine Cenc Header +// protocol. +message WidevineCencHeader { + enum Algorithm { + UNENCRYPTED = 0; + AESCTR = 1; + }; + optional Algorithm algorithm = 1; + repeated bytes key_id = 2; + + // Content provider name. + optional string provider = 3; + + // A content identifier, specified by content provider. + optional bytes content_id = 4; + + // Track type. Acceptable values are SD, HD and AUDIO. Used to differentiate + // content keys used by an asset. + // No longer adding track_type to the PSSH since the Widevine license server + // will return keys for all allowed track types in a single license. + optional string track_type_deprecated = 5; + + // The name of a registered policy to be used for this asset. + optional string policy = 6; + + // Crypto period index, for media using key rotation. + optional uint32 crypto_period_index = 7; + + // Optional protected context for group content. The grouped_license is a + // serialized SignedMessage. + optional bytes grouped_license = 8; +} diff --git a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp index 8959fc85..df05f0c7 100644 --- a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp @@ -2,6 +2,7 @@ #include #include + #include "initialization_data.h" #include "string_conversions.h" #include "wv_cdm_constants.h" @@ -14,6 +15,21 @@ namespace wvcdm { namespace { +// Constants for JSON formatting +const std::string kLeftBrace = "{"; +const std::string kRightBrace = "}"; +const std::string kLeftBracket = "["; +const std::string kRightBracket = "]"; +const std::string kComma = ","; +const std::string kColon = ":"; +const std::string kDoubleQuote = "\""; +const std::string kNewline = "\n"; +const std::string kFourSpaceIndent = " "; + +const std::string kJsonProvider = "provider"; +const std::string kJsonContentId = "content_id"; +const std::string kJsonKeyIds = "key_ids"; + const std::string kWidevinePssh = a2bs_hex( // Widevine PSSH box "00000042" // atom size @@ -145,8 +161,6 @@ 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"; @@ -159,62 +173,119 @@ const std::string kHlsTestEmptyHexValue = ""; const std::string kHlsTestNoHexValue = "0x"; const std::string kHlsTestHexValueWithOddBytes = kHlsIvHexValue + "7"; const std::string kHlsTestInvalidHexValue = kHlsIvHexValue + "g7"; +char kHlsTestKeyFormatVersionsSeparator = '/'; +const std::string kHlsTestUriDataFormat = "data:text/plain;base64,"; +const std::string kHlsTestProvider = "youtube"; +const std::string kHlsTestContentId = "Tears_2015"; +const std::string kHlsTestKeyId1 = "371e135e1a985d75d198a7f41020dc23"; +const std::string kHlsTestKeyId2 = "e670d9b60ae61583e01bc9253fa19261"; +const std::string kHlsTestKeyId3 = "78094e72165df39721b8a354d6a71390"; +const std::string kHlsTestInvalidKeyId = "b8a354d6a71390"; // HLS attribute helper functions std::string QuoteString(const std::string& value) { return "\"" + value + "\""; } +std::string GenerateJsonInitData(const std::string& provider, + const std::string& content_id, + const std::vector& key_ids) { + std::string json = kLeftBrace + kNewline; + if (provider.size() > 0) { + json += kFourSpaceIndent + kDoubleQuote + kJsonProvider + kDoubleQuote + + kColon + kDoubleQuote + provider + kDoubleQuote + kComma + kNewline; + } + if (content_id.size() > 0) { + json += kFourSpaceIndent + kDoubleQuote + kJsonContentId + kDoubleQuote + + kColon + kDoubleQuote + content_id + kDoubleQuote + kComma + + kNewline; + } + if (key_ids.size() > 0) { + json += kFourSpaceIndent + kDoubleQuote + kJsonKeyIds + kDoubleQuote + + kColon + kNewline; + json += kFourSpaceIndent + kLeftBracket + kNewline; + for (size_t i = 0; i < key_ids.size(); ++i) { + json += kFourSpaceIndent + kFourSpaceIndent + kDoubleQuote + key_ids[i] + + kDoubleQuote; + if (i != key_ids.size() - 1) { + json += kComma; + } + json += kNewline; + } + json += kFourSpaceIndent + kRightBracket + kNewline; + } + json += kRightBrace + kNewline; + return json; +} + +std::string GenerateHlsUriData(const std::string& provider, + const std::string& content_id, + const std::vector& key_ids) { + std::string json = GenerateJsonInitData(content_id, provider, key_ids); + std::vector json_init_data( + reinterpret_cast(json.data()), + reinterpret_cast(json.data() + json.size())); + return kHlsTestUriDataFormat + Base64Encode(json_init_data); +} + 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) { + const std::string& key_format_version) { 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; + QuoteString(key_format) + "," + HLS_KEYFORMAT_VERSIONS_ATTRIBUTE + + "=" + QuoteString(key_format_version); } +// HLS Key Format Version lists for testing +std::vector kHlsTestKeyFormatVersionsSingleVersion = {"1"}; +std::vector kHlsTestKeyFormatVersionsSingleVersionExtendedLength = + {"21"}; +std::vector kHlsTestKeyFormatVersionsTwoVersions = {"1", "2"}; +std::vector kHlsTestKeyFormatVersionsThreeVersions = {"1", "2", + "5"}; +std::vector kHlsTestKeyFormatVersionsFourVersions = {"3", "13", + "19", "27"}; + // 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 kHlsAttributeList = + CreateHlsAttributeList(HLS_METHOD_SAMPLE_AES, kHlsUriValue, kHlsIvValue, + kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1); const std::string kHlsAttributeListKeyFormatUnknown = CreateHlsAttributeList( HLS_METHOD_SAMPLE_AES, kHlsUriValue, kHlsIvValue, kHlsKeyFormatValueOther, - HLS_KEYFORMAT_VERSION_VALUE_1, kHlsInitDataValue); + HLS_KEYFORMAT_VERSION_VALUE_1); const std::string kHlsAttributeListKeyFormatVersionUnsupported = CreateHlsAttributeList(HLS_METHOD_SAMPLE_AES, kHlsUriValue, kHlsIvValue, - kHlsKeyFormatValue, "2", kHlsInitDataValue); + kHlsKeyFormatValue, "2"); -const std::string kHlsAttributeListMethodAes128 = CreateHlsAttributeList( - HLS_METHOD_AES_128, kHlsUriValue, kHlsIvValue, kHlsKeyFormatValue, - HLS_KEYFORMAT_VERSION_VALUE_1, kHlsInitDataValue); +const std::string kHlsAttributeListMethodAes128 = + CreateHlsAttributeList(HLS_METHOD_AES_128, kHlsUriValue, kHlsIvValue, + kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1); -const std::string kHlsAttributeListMethodNone = CreateHlsAttributeList( - HLS_METHOD_NONE, 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); -const std::string kHlsAttributeListMethodInvalid = CreateHlsAttributeList( - kHlsTestValue1, kHlsUriValue, kHlsIvValue, kHlsKeyFormatValue, - HLS_KEYFORMAT_VERSION_VALUE_1, kHlsInitDataValue); +const std::string kHlsAttributeListMethodInvalid = + CreateHlsAttributeList(kHlsTestValue1, kHlsUriValue, kHlsIvValue, + kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1); const std::string kHlsAttributeListInvalidUri = CreateHlsAttributeList( HLS_METHOD_SAMPLE_AES, kHlsTestValueWithEmbeddedQuote, kHlsIvValue, - kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1, kHlsInitDataValue); + kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1); const std::string kHlsAttributeListInvalidIv = CreateHlsAttributeList( HLS_METHOD_SAMPLE_AES, kHlsTestHexValueWithOddBytes, kHlsTestNoHexValue, - kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1, kHlsInitDataValue); + kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1); -const std::string kHlsAttributeListInvalidInitData = CreateHlsAttributeList( - HLS_METHOD_SAMPLE_AES, kHlsUriValue, kHlsIvValue, kHlsKeyFormatValue, - HLS_KEYFORMAT_VERSION_VALUE_1, kHlsTestInvalidHexValue); +const std::string kHlsAttributeListInvalidInitData = + CreateHlsAttributeList(HLS_METHOD_SAMPLE_AES, kHlsUriValue, kHlsIvValue, + kHlsKeyFormatValue, HLS_KEYFORMAT_VERSION_VALUE_1); std::string InsertHlsAttributeInList(const std::string key, const std::string& value) { @@ -222,6 +293,22 @@ std::string InsertHlsAttributeInList(const std::string key, "=" + kHlsTestValue2; } +struct HlsInitDataVariant { + HlsInitDataVariant(const std::string& provider, const std::string& content_id, + const std::string& key_id, bool success) + : provider_(provider), content_id_(content_id), success_(success) { + if (key_id.size() > 0) key_ids_.push_back(key_id); + } + HlsInitDataVariant AddKeyId(const std::string& key_id) { + key_ids_.push_back(key_id); + return *this; + } + const std::string provider_; + const std::string content_id_; + std::vector key_ids_; + const bool success_; +}; + struct HlsAttributeVariant { HlsAttributeVariant(const std::string& attribute_list, const std::string& key, const std::string& value, bool success) @@ -246,8 +333,13 @@ class HlsHexAttributeExtractionTest class HlsQuotedAttributeExtractionTest : public ::testing::TestWithParam {}; -class HlsParseTest - : public ::testing::TestWithParam {}; +class HlsKeyFormatVersionsExtractionTest + : public ::testing::TestWithParam > {}; + +class HlsConstructionTest + : public ::testing::TestWithParam {}; + +class HlsParseTest : public ::testing::TestWithParam {}; class HlsTest : public ::testing::Test {}; } // namespace @@ -265,6 +357,37 @@ INSTANTIATE_TEST_CASE_P(ParsePssh, InitializationDataTest, kWidevineV1Pssh, kOtherBoxFirst, kZeroSizedPsshBox)); +TEST_P(HlsKeyFormatVersionsExtractionTest, ExtractKeyFormatVersions) { + std::vector versions = GetParam(); + std::string key_format_versions; + for (size_t i = 0; i < versions.size(); ++i) { + key_format_versions += versions[i] + kHlsTestKeyFormatVersionsSeparator; + } + key_format_versions.resize(key_format_versions.size() - + sizeof(kHlsTestKeyFormatVersionsSeparator)); + std::vector extracted_versions = + InitializationData::ExtractKeyFormatVersions(key_format_versions); + EXPECT_EQ(versions.size(), extracted_versions.size()); + for (size_t i = 0; i < versions.size(); ++i) { + bool found = false; + for (size_t j = 0; j < extracted_versions.size(); ++j) { + if (versions[i] == extracted_versions[j]) { + found = true; + break; + } + } + EXPECT_TRUE(found); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsKeyFormatVersionsExtractionTest, + ::testing::Values(kHlsTestKeyFormatVersionsSingleVersion, + kHlsTestKeyFormatVersionsSingleVersionExtendedLength, + kHlsTestKeyFormatVersionsTwoVersions, + kHlsTestKeyFormatVersionsThreeVersions, + kHlsTestKeyFormatVersionsFourVersions)); + TEST_P(HlsAttributeExtractionTest, ExtractAttribute) { HlsAttributeVariant param = GetParam(); std::string value; @@ -289,10 +412,8 @@ INSTANTIATE_TEST_CASE_P( true), HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_ATTRIBUTE, QuoteString(kHlsKeyFormatValue), true), - HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_VERSION_ATTRIBUTE, + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, QuoteString(HLS_KEYFORMAT_VERSION_VALUE_1), true), - HlsAttributeVariant(kHlsAttributeList, HLS_INITDATA_ATTRIBUTE, - kHlsInitDataValue, true), HlsAttributeVariant(InsertHlsAttributeInList(kHlsTestKey1, kHlsTestValue1), kHlsTestKey1, kHlsTestValue1, true), @@ -349,10 +470,6 @@ INSTANTIATE_TEST_CASE_P( ::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), @@ -386,7 +503,7 @@ INSTANTIATE_TEST_CASE_P( true), HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_ATTRIBUTE, kHlsKeyFormatValue, true), - HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_VERSION_ATTRIBUTE, + HlsAttributeVariant(kHlsAttributeList, HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, HLS_KEYFORMAT_VERSION_VALUE_1, true), HlsAttributeVariant( InsertHlsAttributeInList(kHlsTestKey1, QuoteString(kHlsTestValue1)), @@ -396,7 +513,41 @@ INSTANTIATE_TEST_CASE_P( kHlsTestKey1, QuoteString(kHlsTestValueWithEmbeddedQuote)), kHlsTestKey1, kHlsTestValueWithEmbeddedQuote, false))); -TEST_P(HlsParseTest, Parse) { +TEST_P(HlsConstructionTest, InitData) { + HlsInitDataVariant param = GetParam(); + + std::string uri = + GenerateHlsUriData(param.provider_, param.content_id_, param.key_ids_); + std::string value; + if (param.success_) { + EXPECT_TRUE(InitializationData::ConstructWidevineInitData(uri, &value)); + // EXPECT_EQ(param.value_, value); + } else { + EXPECT_FALSE(InitializationData::ConstructWidevineInitData(uri, &value)); + } +} + +INSTANTIATE_TEST_CASE_P( + HlsTest, HlsConstructionTest, + ::testing::Values(HlsInitDataVariant(std::string(), kHlsTestContentId, + kHlsTestKeyId1, false), + HlsInitDataVariant(kHlsTestProvider, std::string(), + kHlsTestKeyId1, false), + HlsInitDataVariant(kHlsTestProvider, kHlsTestContentId, + std::string(), false), + HlsInitDataVariant(kHlsTestProvider, kHlsTestContentId, + kHlsTestInvalidKeyId, false), + HlsInitDataVariant(kHlsTestProvider, kHlsTestContentId, + kHlsTestKeyId1, true), + HlsInitDataVariant(kHlsTestProvider, kHlsTestContentId, + kHlsTestKeyId1, true) + .AddKeyId(kHlsTestKeyId2) + .AddKeyId(kHlsTestKeyId3), + HlsInitDataVariant(kHlsTestProvider, kHlsTestContentId, + kHlsTestInvalidKeyId, false) + .AddKeyId(kHlsTestKeyId1))); + +TEST_P(HlsParseTest, DISABLED_Parse) { HlsAttributeVariant param = GetParam(); InitializationData init_data(HLS_INIT_DATA_FORMAT, param.attribute_list_); if (param.success_) { @@ -404,17 +555,16 @@ TEST_P(HlsParseTest, Parse) { 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); + EXPECT_EQ(kHlsMethodSampleAes, init_data.hls_method()); } else if (param.value_.compare(HLS_METHOD_AES_128) == 0) { - EXPECT_EQ(kHlsMethodAes128, init_data.hls_data().method); + EXPECT_EQ(kHlsMethodAes128, init_data.hls_method()); } else if (param.value_.compare(HLS_METHOD_NONE) == 0) { - EXPECT_EQ(kHlsMethodNone, init_data.hls_data().method); + EXPECT_EQ(kHlsMethodNone, init_data.hls_method()); } } else { - EXPECT_EQ(kHlsMethodSampleAes, init_data.hls_data().method); + EXPECT_EQ(kHlsMethodSampleAes, init_data.hls_method()); } - EXPECT_EQ(kHlsIvHexValue, b2a_hex(init_data.hls_data().iv)); - EXPECT_EQ(kHlsUriValue, init_data.hls_data().uri); + EXPECT_EQ(kHlsIvHexValue, b2a_hex(init_data.hls_iv())); } else { EXPECT_TRUE(init_data.is_hls()); EXPECT_TRUE(init_data.IsEmpty()); @@ -429,7 +579,7 @@ INSTANTIATE_TEST_CASE_P( HLS_KEYFORMAT_ATTRIBUTE, kHlsKeyFormatValueOther, false), HlsAttributeVariant(kHlsAttributeListKeyFormatVersionUnsupported, - HLS_KEYFORMAT_VERSION_ATTRIBUTE, "2", false), + HLS_KEYFORMAT_VERSIONS_ATTRIBUTE, "2", false), HlsAttributeVariant(kHlsAttributeListMethodAes128, HLS_METHOD_ATTRIBUTE, HLS_METHOD_AES_128, true), HlsAttributeVariant(kHlsAttributeListMethodNone, HLS_METHOD_ATTRIBUTE, @@ -439,8 +589,6 @@ INSTANTIATE_TEST_CASE_P( HlsAttributeVariant(kHlsAttributeListInvalidUri, HLS_URI_ATTRIBUTE, kHlsTestValueWithEmbeddedQuote, false), HlsAttributeVariant(kHlsAttributeListInvalidIv, HLS_IV_ATTRIBUTE, - kHlsTestHexValueWithOddBytes, false), - HlsAttributeVariant(kHlsAttributeListInvalidInitData, - HLS_INITDATA_ATTRIBUTE, kHlsTestInvalidHexValue, - false))); + kHlsTestHexValueWithOddBytes, false))); + } // namespace wvcdm