From 59941a101a8edc3a7e6ad2274d59e382fe822c9e Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Thu, 10 May 2018 17:19:28 -0700 Subject: [PATCH] Support entitlement license with Widevine key server - Added a new --enable_entitlement_license flag, which sets 'enable_entitlement_license' in Widevine CommonEncryptionRequest; - Support 'boxes' in Widevine CommonEncryptionResponse. b/78171767 Change-Id: Id399fc7fcb2948c571e12c8af7687cfcfcef41fe --- packager/app/packager_main.cc | 1 + packager/app/packager_util.cc | 2 + packager/app/widevine_encryption_flags.cc | 3 + packager/app/widevine_encryption_flags.h | 1 + .../base/protection_system_specific_info.cc | 25 ++- ...rotection_system_specific_info_unittest.cc | 154 +++++++++++------- packager/media/base/widevine_key_source.cc | 40 +++-- packager/media/base/widevine_key_source.h | 10 +- .../base/widevine_key_source_unittest.cc | 59 +++++++ packager/media/public/crypto_params.h | 2 + 10 files changed, 205 insertions(+), 92 deletions(-) diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 8fb1d62600..b601a8ca16 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -323,6 +323,7 @@ base::Optional GetPackagingParams() { widevine.content_id = FLAGS_content_id_bytes; widevine.policy = FLAGS_policy; widevine.group_id = FLAGS_group_id_bytes; + widevine.enable_entitlement_license = FLAGS_enable_entitlement_license; if (!GetWidevineSigner(&widevine.signer)) return base::nullopt; break; diff --git a/packager/app/packager_util.cc b/packager/app/packager_util.cc index b40239b8ce..8fee8f7fdf 100644 --- a/packager/app/packager_util.cc +++ b/packager/app/packager_util.cc @@ -81,6 +81,8 @@ std::unique_ptr CreateEncryptionKeySource( widevine_key_source->set_signer(std::move(request_signer)); } widevine_key_source->set_group_id(widevine.group_id); + widevine_key_source->set_enable_entitlement_license( + widevine.enable_entitlement_license); Status status = widevine_key_source->FetchKeys(widevine.content_id, widevine.policy); diff --git a/packager/app/widevine_encryption_flags.cc b/packager/app/widevine_encryption_flags.cc index 66757c71dc..5f164253c8 100644 --- a/packager/app/widevine_encryption_flags.cc +++ b/packager/app/widevine_encryption_flags.cc @@ -59,6 +59,9 @@ DEFINE_int32(crypto_period_duration, "Crypto period duration in seconds. If it is non-zero, key " "rotation is enabled."); DEFINE_hex_bytes(group_id, "", "Identifier for a group of licenses (hex)."); +DEFINE_bool(enable_entitlement_license, + false, + "Enable entitlement license when using Widevine key server."); namespace shaka { namespace { diff --git a/packager/app/widevine_encryption_flags.h b/packager/app/widevine_encryption_flags.h index d15407b483..89de205abc 100644 --- a/packager/app/widevine_encryption_flags.h +++ b/packager/app/widevine_encryption_flags.h @@ -27,6 +27,7 @@ DECLARE_hex_bytes(aes_signing_iv); DECLARE_string(rsa_signing_key_path); DECLARE_int32(crypto_period_duration); DECLARE_hex_bytes(group_id); +DECLARE_bool(enable_entitlement_license); namespace shaka { diff --git a/packager/media/base/protection_system_specific_info.cc b/packager/media/base/protection_system_specific_info.cc index 69aaf8fe92..2f2ef157a9 100644 --- a/packager/media/base/protection_system_specific_info.cc +++ b/packager/media/base/protection_system_specific_info.cc @@ -6,6 +6,8 @@ #include "packager/media/base/protection_system_specific_info.h" +#include + #include "packager/media/base/buffer_reader.h" #include "packager/media/base/buffer_writer.h" #include "packager/media/base/fourccs.h" @@ -33,7 +35,9 @@ bool ProtectionSystemSpecificInfo::ParseBoxes( const uint8_t* data, size_t data_size, std::vector* pssh_infos) { + std::map, size_t> info_map; pssh_infos->clear(); + BufferReader reader(data, data_size); while (reader.HasBytes(1)) { uint32_t size; @@ -41,10 +45,17 @@ bool ProtectionSystemSpecificInfo::ParseBoxes( RCHECK(reader.SkipBytes(size - 4)); RCHECK(size > kPsshBoxHeaderSize + kSystemIdSize); - pssh_infos->push_back( - {std::vector(data + kPsshBoxHeaderSize, - data + kPsshBoxHeaderSize + kSystemIdSize), - std::vector(data, data + size)}); + const std::vector system_id( + data + kPsshBoxHeaderSize, data + kPsshBoxHeaderSize + kSystemIdSize); + auto iter = info_map.find(system_id); + if (iter != info_map.end()) { + ProtectionSystemSpecificInfo& info = (*pssh_infos)[iter->second]; + info.psshs.insert(info.psshs.end(), data, data + size); + } else { + pssh_infos->push_back( + {system_id, std::vector(data, data + size)}); + info_map[system_id] = pssh_infos->size() - 1; + } data += size; } @@ -63,7 +74,6 @@ std::unique_ptr PsshBoxBuilder::ParseFromBox( uint32_t version_and_flags; RETURN_NULL_IF_FALSE(reader.Read4(&size)); RETURN_NULL_IF_FALSE(reader.Read4(&box_type)); - RETURN_NULL_IF_FALSE(size == data_size); RETURN_NULL_IF_FALSE(box_type == FOURCC_pssh); RETURN_NULL_IF_FALSE(reader.Read4(&version_and_flags)); @@ -90,10 +100,7 @@ std::unique_ptr PsshBoxBuilder::ParseFromBox( RETURN_NULL_IF_FALSE( reader.ReadToVector(&pssh_builder->pssh_data_, pssh_data_size)); - // We should be at the end of the data. The reader should be initialized to - // the data and size according to the size field of the box; therefore it - // is an error if there are bytes remaining. - RETURN_NULL_IF_FALSE(!reader.HasBytes(1)); + // Ignore extra data if there is any. return pssh_builder; } diff --git a/packager/media/base/protection_system_specific_info_unittest.cc b/packager/media/base/protection_system_specific_info_unittest.cc index f8f6d87b8a..3d7a4b2041 100644 --- a/packager/media/base/protection_system_specific_info_unittest.cc +++ b/packager/media/base/protection_system_specific_info_unittest.cc @@ -13,52 +13,86 @@ namespace shaka { namespace media { namespace { -const uint8_t kV0BoxArray[] = { - 0x00, 0x00, 0x00, 0x21, 'p', 's', 's', 'h', // Header - 0x00, 0x00, 0x00, 0x00, // Version = 0, flags = 0 - 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, // System ID - 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0x00, 0x00, 0x00, 0x01, // Data size(1) - 0xFF +const uint8_t kSystemId1V0BoxArray[] = { + 0x00, 0x00, 0x00, 0x21, 'p', 's', 's', 'h', // Header + 0x00, 0x00, 0x00, 0x00, // Version = 0, flags = 0 + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, // System ID + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, + 0x00, 0x00, 0x00, 0x01, // Data size(1) + 0xFF, }; -const uint8_t kV1BoxArray[] = { - 0x00, 0x00, 0x00, 0x35, 'p', 's', 's', 'h', // Header - 0x01, 0x00, 0x00, 0x00, // Version = 1, flags = 0 - 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, // System ID - 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0x00, 0x00, 0x00, 0x01, // KID_count(1) - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // First KID - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, // Data size(1) - 0xFF +const uint8_t kSystemId1V1BoxArray[] = { + 0x00, 0x00, 0x00, 0x35, 'p', 's', 's', 'h', // Header + 0x01, 0x00, 0x00, 0x00, // Version = 1, flags = 0 + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, // System ID + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, + 0x00, 0x00, 0x00, 0x01, // KID_count(1) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // First KID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, // Data size(1) + 0xFF, +}; +const uint8_t kSystemId2V0BoxArray[] = { + 0x00, 0x00, 0x00, 0x21, 'p', 's', 's', 'h', // Header + 0x00, 0x00, 0x00, 0x00, // Version = 0, flags = 0 + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, // System ID + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, + 0x00, 0x00, 0x00, 0x01, // Data size(1) + 0xFF, }; -const uint8_t kTestSystemIdArray[] = { - 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, // System ID - 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, +const uint8_t kTestSystemId1Array[] = { + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, +}; +const uint8_t kTestSystemId2Array[] = { + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, }; const uint8_t kTestKeyIdArray[] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // First KID - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; const uint8_t kTestPsshDataArray[] = {0xFF}; + +std::vector ConcatVectors(const std::vector& a, + const std::vector& b) { + std::vector out; + out.insert(out.end(), a.begin(), a.end()); + out.insert(out.end(), b.begin(), b.end()); + return out; +} + +std::vector ConcatVectors(const std::vector& a, + const std::vector& b, + const std::vector& c) { + return ConcatVectors(ConcatVectors(a, b), c); +} + } // namespace class PsshTest : public ::testing::Test { public: PsshTest() - : v0_box_(kV0BoxArray, kV0BoxArray + arraysize(kV0BoxArray)), - v1_box_(kV1BoxArray, kV1BoxArray + arraysize(kV1BoxArray)), - test_system_id_(kTestSystemIdArray, - kTestSystemIdArray + arraysize(kTestSystemIdArray)), - test_key_id_(kTestKeyIdArray, - kTestKeyIdArray + arraysize(kTestKeyIdArray)), - test_pssh_data_(kTestPsshDataArray, - kTestPsshDataArray + arraysize(kTestPsshDataArray)) {} + : system_id1_v0_box_(std::begin(kSystemId1V0BoxArray), + std::end(kSystemId1V0BoxArray)), + system_id1_v1_box_(std::begin(kSystemId1V1BoxArray), + std::end(kSystemId1V1BoxArray)), + system_id2_v0_box_(std::begin(kSystemId2V0BoxArray), + std::end(kSystemId2V0BoxArray)), + test_system_id1_(std::begin(kTestSystemId1Array), + std::end(kTestSystemId1Array)), + test_system_id2_(std::begin(kTestSystemId2Array), + std::end(kTestSystemId2Array)), + test_key_id_(std::begin(kTestKeyIdArray), std::end(kTestKeyIdArray)), + test_pssh_data_(std::begin(kTestPsshDataArray), + std::end(kTestPsshDataArray)) {} - const std::vector v0_box_; - const std::vector v1_box_; - const std::vector test_system_id_; + const std::vector system_id1_v0_box_; + const std::vector system_id1_v1_box_; + const std::vector system_id2_v0_box_; + const std::vector test_system_id1_; + const std::vector test_system_id2_; const std::vector test_key_id_; const std::vector test_pssh_data_; }; @@ -66,17 +100,17 @@ class PsshTest : public ::testing::Test { TEST_F(PsshTest, ParseBoxes_SupportsV0) { std::vector info; ASSERT_TRUE(ProtectionSystemSpecificInfo::ParseBoxes( - v0_box_.data(), v0_box_.size(), &info)); + system_id1_v0_box_.data(), system_id1_v0_box_.size(), &info)); ASSERT_EQ(1u, info.size()); - EXPECT_EQ(test_system_id_, info[0].system_id); + EXPECT_EQ(test_system_id1_, info[0].system_id); std::unique_ptr pssh_builder = PsshBoxBuilder::ParseFromBox(info[0].psshs.data(), info[0].psshs.size()); ASSERT_TRUE(pssh_builder); ASSERT_EQ(0u, pssh_builder->key_ids().size()); - EXPECT_EQ(test_system_id_, pssh_builder->system_id()); + EXPECT_EQ(test_system_id1_, pssh_builder->system_id()); EXPECT_EQ(test_pssh_data_, pssh_builder->pssh_data()); EXPECT_EQ(0, pssh_builder->pssh_box_version()); } @@ -84,80 +118,74 @@ TEST_F(PsshTest, ParseBoxes_SupportsV0) { TEST_F(PsshTest, ParseBoxes_SupportsV1) { std::vector info; ASSERT_TRUE(ProtectionSystemSpecificInfo::ParseBoxes( - v1_box_.data(), v1_box_.size(), &info)); + system_id1_v1_box_.data(), system_id1_v1_box_.size(), &info)); ASSERT_EQ(1u, info.size()); - EXPECT_EQ(test_system_id_, info[0].system_id); + EXPECT_EQ(test_system_id1_, info[0].system_id); std::unique_ptr pssh_builder = PsshBoxBuilder::ParseFromBox(info[0].psshs.data(), info[0].psshs.size()); ASSERT_TRUE(pssh_builder); ASSERT_EQ(1u, pssh_builder->key_ids().size()); - EXPECT_EQ(test_system_id_, pssh_builder->system_id()); + EXPECT_EQ(test_system_id1_, pssh_builder->system_id()); EXPECT_EQ(test_key_id_, pssh_builder->key_ids()[0]); EXPECT_EQ(test_pssh_data_, pssh_builder->pssh_data()); EXPECT_EQ(1, pssh_builder->pssh_box_version()); } TEST_F(PsshTest, ParseBoxes_SupportsConcatenatedBoxes) { - std::vector data; - data.insert(data.end(), v1_box_.begin(), v1_box_.end()); - data.insert(data.end(), v0_box_.begin(), v0_box_.end()); - data.insert(data.end(), v1_box_.begin(), v1_box_.end()); + std::vector data = + ConcatVectors(system_id1_v0_box_, system_id2_v0_box_, system_id1_v1_box_); std::vector info; ASSERT_TRUE(ProtectionSystemSpecificInfo::ParseBoxes(data.data(), data.size(), &info)); - ASSERT_EQ(3u, info.size()); + // The PSSHs are grouped by system id. Since there are only two system ids, + // there are two ProtectionSystemSpecificInfo. + ASSERT_EQ(2u, info.size()); + EXPECT_EQ(ConcatVectors(system_id1_v0_box_, system_id1_v1_box_), + info[0].psshs); + EXPECT_EQ(system_id2_v0_box_, info[1].psshs); std::unique_ptr pssh_builder = PsshBoxBuilder::ParseFromBox(info[0].psshs.data(), info[0].psshs.size()); ASSERT_TRUE(pssh_builder); - ASSERT_EQ(1u, pssh_builder->key_ids().size()); - EXPECT_EQ(test_system_id_, pssh_builder->system_id()); - EXPECT_EQ(test_key_id_, pssh_builder->key_ids()[0]); + ASSERT_EQ(0u, pssh_builder->key_ids().size()); + EXPECT_EQ(test_system_id1_, pssh_builder->system_id()); EXPECT_EQ(test_pssh_data_, pssh_builder->pssh_data()); - EXPECT_EQ(1, pssh_builder->pssh_box_version()); + EXPECT_EQ(0, pssh_builder->pssh_box_version()); pssh_builder = PsshBoxBuilder::ParseFromBox(info[1].psshs.data(), info[1].psshs.size()); ASSERT_TRUE(pssh_builder); ASSERT_EQ(0u, pssh_builder->key_ids().size()); - EXPECT_EQ(test_system_id_, pssh_builder->system_id()); + EXPECT_EQ(test_system_id2_, pssh_builder->system_id()); EXPECT_EQ(test_pssh_data_, pssh_builder->pssh_data()); EXPECT_EQ(0, pssh_builder->pssh_box_version()); - - pssh_builder = - PsshBoxBuilder::ParseFromBox(info[2].psshs.data(), info[2].psshs.size()); - ASSERT_TRUE(pssh_builder); - - ASSERT_EQ(1u, pssh_builder->key_ids().size()); - EXPECT_EQ(test_system_id_, pssh_builder->system_id()); - EXPECT_EQ(test_key_id_, pssh_builder->key_ids()[0]); - EXPECT_EQ(test_pssh_data_, pssh_builder->pssh_data()); - EXPECT_EQ(1, pssh_builder->pssh_box_version()); } TEST_F(PsshTest, CreateBox_MakesV0Boxes) { PsshBoxBuilder pssh_builder; - pssh_builder.set_system_id(kTestSystemIdArray, arraysize(kTestSystemIdArray)); + pssh_builder.set_system_id(kTestSystemId1Array, + arraysize(kTestSystemId1Array)); pssh_builder.set_pssh_data(test_pssh_data_); pssh_builder.set_pssh_box_version(0); - EXPECT_EQ(v0_box_, pssh_builder.CreateBox()); + EXPECT_EQ(system_id1_v0_box_, pssh_builder.CreateBox()); } TEST_F(PsshTest, CreateBox_MakesV1Boxes) { PsshBoxBuilder pssh_builder; pssh_builder.add_key_id(test_key_id_); - pssh_builder.set_system_id(kTestSystemIdArray, arraysize(kTestSystemIdArray)); + pssh_builder.set_system_id(kTestSystemId1Array, + arraysize(kTestSystemId1Array)); pssh_builder.set_pssh_data(test_pssh_data_); pssh_builder.set_pssh_box_version(1); - EXPECT_EQ(v1_box_, pssh_builder.CreateBox()); + EXPECT_EQ(system_id1_v1_box_, pssh_builder.CreateBox()); } } // namespace media diff --git a/packager/media/base/widevine_key_source.cc b/packager/media/base/widevine_key_source.cc index c1e39de4b6..2df58243b7 100644 --- a/packager/media/base/widevine_key_source.cc +++ b/packager/media/base/widevine_key_source.cc @@ -57,6 +57,24 @@ CommonEncryptionRequest::ProtectionScheme ToCommonEncryptionProtectionScheme( } } +ProtectionSystemSpecificInfo ProtectionSystemInfoFromPsshProto( + const CommonEncryptionResponse::Track::Pssh& pssh_proto) { + PsshBoxBuilder pssh_builder; + pssh_builder.set_system_id(kWidevineSystemId, arraysize(kWidevineSystemId)); + + if (pssh_proto.has_boxes()) { + return {pssh_builder.system_id(), + std::vector(pssh_proto.boxes().begin(), + pssh_proto.boxes().end())}; + } else { + pssh_builder.set_pssh_box_version(0); + const std::vector pssh_data(pssh_proto.data().begin(), + pssh_proto.data().end()); + pssh_builder.set_pssh_data(pssh_data); + return {pssh_builder.system_id(), pssh_builder.CreateBox()}; + } +} + } // namespace WidevineKeySource::WidevineKeySource(const std::string& server_url, @@ -73,7 +91,8 @@ WidevineKeySource::WidevineKeySource(const std::string& server_url, key_production_started_(false), start_key_production_(base::WaitableEvent::ResetPolicy::AUTOMATIC, base::WaitableEvent::InitialState::NOT_SIGNALED), - first_crypto_period_index_(0) { + first_crypto_period_index_(0), + enable_entitlement_license_(false) { key_production_thread_.Start(); } @@ -97,6 +116,8 @@ Status WidevineKeySource::FetchKeys(const std::vector& content_id, common_encryption_request_->set_policy(policy); common_encryption_request_->set_protection_scheme( ToCommonEncryptionProtectionScheme(protection_scheme_)); + if (enable_entitlement_license_) + common_encryption_request_->set_enable_entitlement_license(true); return FetchKeysInternal(!kEnableKeyRotation, 0, false); } @@ -218,10 +239,6 @@ void WidevineKeySource::set_key_fetcher( key_fetcher_ = std::move(key_fetcher); } -void WidevineKeySource::set_group_id(const std::vector& group_id) { - group_id_ = group_id; -} - Status WidevineKeySource::GetKeyInternal(uint32_t crypto_period_index, const std::string& stream_label, EncryptionKey* key) { @@ -430,19 +447,8 @@ bool WidevineKeySource::ExtractEncryptionKey( << track.pssh_size(); return false; } - const auto& pssh_proto = track.pssh(0); - const std::vector pssh_data(pssh_proto.data().begin(), - pssh_proto.data().end()); - - PsshBoxBuilder pssh_builder; - pssh_builder.add_key_id(encryption_key->key_id); - pssh_builder.set_system_id(kWidevineSystemId, - arraysize(kWidevineSystemId)); - pssh_builder.set_pssh_box_version(0); - pssh_builder.set_pssh_data(pssh_data); - encryption_key->key_system_info.push_back( - {pssh_builder.system_id(), pssh_builder.CreateBox()}); + ProtectionSystemInfoFromPsshProto(track.pssh(0))); } encryption_key_map[stream_label] = std::move(encryption_key); } diff --git a/packager/media/base/widevine_key_source.h b/packager/media/base/widevine_key_source.h index 0a3e1afe59..d0221a9bb3 100644 --- a/packager/media/base/widevine_key_source.h +++ b/packager/media/base/widevine_key_source.h @@ -68,9 +68,12 @@ class WidevineKeySource : public KeySource { /// @param key_fetcher points to the @b KeyFetcher object to be injected. void set_key_fetcher(std::unique_ptr key_fetcher); - // Set the group id for the key source - // @param group_id group identifier - void set_group_id(const std::vector& group_id); + void set_group_id(const std::vector& group_id) { + group_id_ = group_id; + } + void set_enable_entitlement_license(bool enable_entitlement_license) { + enable_entitlement_license_ = enable_entitlement_license; + } private: typedef ProducerConsumerQueue> @@ -125,6 +128,7 @@ class WidevineKeySource : public KeySource { base::WaitableEvent start_key_production_; uint32_t first_crypto_period_index_; std::vector group_id_; + bool enable_entitlement_license_; std::unique_ptr key_pool_; EncryptionKeyMap encryption_key_map_; // For non key rotation request. Status common_encryption_request_status_; diff --git a/packager/media/base/widevine_key_source_unittest.cc b/packager/media/base/widevine_key_source_unittest.cc index f061e15dc3..5a3a4f4eec 100644 --- a/packager/media/base/widevine_key_source_unittest.cc +++ b/packager/media/base/widevine_key_source_unittest.cc @@ -56,6 +56,12 @@ const char kExpectedRequestMessageFormat[] = R"("tracks":[{"type":"SD"},{"type":"HD"},{"type":"UHD1"},)" R"({"type":"UHD2"},{"type":"AUDIO"}],)" R"("drm_types":["WIDEVINE"],"protection_scheme":"%s"})"; +const char kExpectedRequestMessageFormatWithEntitlement[] = + R"({"content_id":"%s","policy":"%s",)" + R"("tracks":[{"type":"SD"},{"type":"HD"},{"type":"UHD1"},)" + R"({"type":"UHD2"},{"type":"AUDIO"}],)" + R"("drm_types":["WIDEVINE"],"protection_scheme":"%s",)" + R"("enable_entitlement_license":true})"; const char kExpectedRequestMessageWithAssetIdFormat[] = R"({"tracks":[{"type":"SD"},{"type":"HD"},{"type":"UHD1"},)" R"({"type":"UHD2"},{"type":"AUDIO"}],)" @@ -68,6 +74,9 @@ const char kExpectedSignedMessageFormat[] = R"({"request":"%s","signature":"%s","signer":"%s"})"; const char kTrackFormat[] = R"({"type":"%s","key_id":"%s","key":"%s",)" R"("pssh":[{"drm_type":"WIDEVINE","data":"%s"}]})"; +const char kTrackFormatWithBoxes[] = + R"({"type":"%s","key_id":"%s","key":"%s",)" + R"("pssh":[{"drm_type":"WIDEVINE","data":"%s","boxes":"%s"}]})"; const char kClassicTrackFormat[] = R"({"type":"%s","key":"%s"})"; const char kLicenseResponseFormat[] = R"({"status":"%s","tracks":[%s]})"; const char kHttpResponseFormat[] = R"({"response":"%s"})"; @@ -115,6 +124,21 @@ std::string GetMockPsshData() { return kRequestPsshData; } +std::string GenerateMockLicenseResponseWithBoxes(const std::string& boxes) { + const std::string kTrackTypes[] = {"SD", "HD", "UHD1", "UHD2", "AUDIO"}; + std::string tracks; + for (size_t i = 0; i < 5; ++i) { + if (!tracks.empty()) + tracks += ","; + tracks += base::StringPrintf( + kTrackFormatWithBoxes, kTrackTypes[i].c_str(), + Base64Encode(GetMockKeyId(kTrackTypes[i])).c_str(), + Base64Encode(GetMockKey(kTrackTypes[i])).c_str(), + Base64Encode(GetMockPsshData()).c_str(), boxes.c_str()); + } + return base::StringPrintf(kLicenseResponseFormat, "OK", tracks.c_str()); +} + std::string GenerateMockLicenseResponse() { const std::string kTrackTypes[] = {"SD", "HD", "UHD1", "UHD2", "AUDIO"}; std::string tracks; @@ -323,6 +347,26 @@ TEST_F(WidevineKeySourceTest, NoRetryOnUnknownError) { widevine_key_source_->FetchKeys(content_id_, kPolicy).error_code()); } +TEST_F(WidevineKeySourceTest, BoxesInResponse) { + const char kMockBoxes[] = "mock_pssh_boxes"; + std::string mock_response = base::StringPrintf( + kHttpResponseFormat, Base64Encode(GenerateMockLicenseResponseWithBoxes( + Base64Encode(kMockBoxes))) + .c_str()); + + EXPECT_CALL(*mock_key_fetcher_, FetchKeys(_, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK))); + + CreateWidevineKeySource(); + ASSERT_OK(widevine_key_source_->FetchKeys(content_id_, kPolicy)); + + const char kHdStreamLabel[] = "HD"; + EncryptionKey encryption_key; + ASSERT_OK(widevine_key_source_->GetKey(kHdStreamLabel, &encryption_key)); + ASSERT_EQ(1u, encryption_key.key_system_info.size()); + ASSERT_EQ(kMockBoxes, ToString(encryption_key.key_system_info.front().psshs)); +} + class WidevineKeySourceParameterizedTest : public WidevineKeySourceTest, public WithParamInterface> { @@ -453,6 +497,21 @@ TEST_P(WidevineKeySourceParameterizedTest, LicenseStatusClassicOK) { VerifyKeys(true); } +TEST_P(WidevineKeySourceParameterizedTest, VerifyEntitlementLicenseRequest) { + const std::string expected_message = + base::StringPrintf(kExpectedRequestMessageFormatWithEntitlement, + Base64Encode(kContentId).c_str(), kPolicy, + GetExpectedProtectionScheme().c_str()); + EXPECT_CALL(*mock_request_signer_, + GenerateSignature(StrEq(expected_message), _)) + .WillOnce(Return(false)); + + CreateWidevineKeySource(); + widevine_key_source_->set_enable_entitlement_license(true); + widevine_key_source_->set_signer(std::move(mock_request_signer_)); + ASSERT_NOT_OK(widevine_key_source_->FetchKeys(content_id_, kPolicy)); +} + namespace { const char kCryptoPeriodRequestMessageFormat[] = diff --git a/packager/media/public/crypto_params.h b/packager/media/public/crypto_params.h index 3d6feccef8..9dfb407069 100644 --- a/packager/media/public/crypto_params.h +++ b/packager/media/public/crypto_params.h @@ -59,6 +59,8 @@ struct WidevineEncryptionParams { WidevineSigner signer; /// Group identifier, if present licenses will belong to this group. std::vector group_id; + /// Enables entitlement license when set to true. + bool enable_entitlement_license; }; /// PlayReady encryption parameters.