Unified State-Changing API for LicenseKeyStatus

(This is a merge of http://go/wvgerrit/31040)

Because the Policy Engine was only consulting the result of the Max-Res
Decode check when it was in kLicenseStateCanPlay and not in other states
that imply kKeyStatusUsable, like kLicenseStateWaitingLicenseUpdate, the
Max-Res Decode results would not be honored during the interval between
requesting a renewal and receiving the result. (Or until the key
expired.) This was particularly problematic for keys with renewal delays
less than ten seconds long, which would freeze the Max-Res state before
it had a chance to update for the first time, effectively disabling
Max-Res Decode until renewal was received.

Fixing this required changing how the Policy Engine and the
LicenseKeyStatus objects communicate about the changing usability state
of the LicenseKeyStatus objects. Before, a call to ApplyConstraints()
might calculate a Max-Res failure, but this failure would be pending
until the Policy Engine deigned to call ApplyStatusChange() again.
Without a call to ApplyStatusChange(), it could pend forever. This put a
burden on the PolicyEngine to poll the LicenseKeys with redundant
ApplyStatusChange() calls using the same CdmKeyStatus that the keys were
already in, just in case Max-Res had changed since the last necessary
call to ApplyStatusChange().

If the Policy Engine got the timing of these calls wrong, it would
result in Max-Res results being ignored. (as in the linked bug) If it
ever polled with the wrong CdmKeyStatus, it would update the
LicenseKeys' status when it did not mean to. It would be preferable if
this polling were not needed, so that the Policy Engine couldn't get it
wrong.

This patch changes the API between these classes so that when Max-Res
fails, the state change can be reported immediately instead of pending
until ApplyStatusChange() is called, eliminating the need for polling.
All state changes to the LicenseKeyStatus objects go through a unified
ApplyStatusChange() method that can update the CdmKeyStatus, resolution,
and/or HDCP level and report any resulting usability changes
immediately. This patch updates the unit tests to exercise this new API
instead of the old API.

Previously, the linked bug slipped past our unit tests because we only
test unrenewable, streaming licenses against Max-Res. This patch adds
several more variants to
policy_engine_constraints_unittest so that it tests six kinds of
license to provide better coverage.

Bug: 62393949
Test: build_and_run_all_unit_tests
Change-Id: I0dfdbf6b8ea39abb446089aef5f6ea0502e9b4c6
This commit is contained in:
John W. Bruce
2017-08-29 16:37:49 -07:00
parent c0133bf3a4
commit b8e31487a4
6 changed files with 264 additions and 115 deletions

View File

@@ -392,14 +392,16 @@ TEST_F(LicenseKeysTest, CanDecrypt) {
EXPECT_FALSE(license_keys_.CanDecryptContent(os_key));
bool new_usable_keys = false;
bool any_change = false;
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
CdmKeyStatus status = kKeyStatusUsable;
any_change = license_keys_.ApplyStatusChange(&status, NULL, NULL,
&new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_TRUE(new_usable_keys);
EXPECT_TRUE(license_keys_.CanDecryptContent(c_key));
EXPECT_FALSE(license_keys_.CanDecryptContent(os_key));
any_change = license_keys_.ApplyStatusChange(kKeyStatusExpired,
status = kKeyStatusExpired;
any_change = license_keys_.ApplyStatusChange(&status, NULL, NULL,
&new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_FALSE(new_usable_keys);
@@ -505,6 +507,7 @@ TEST_F(LicenseKeysTest, ExtractKeyStatuses) {
TEST_F(LicenseKeysTest, KeyStatusChanges) {
bool new_usable_keys = false;
bool any_change = false;
CdmKeyStatus status;
CdmKeyStatusMap key_status_map;
StageOperatorSessionKeys();
StageContentKeys();
@@ -514,7 +517,8 @@ TEST_F(LicenseKeysTest, KeyStatusChanges) {
ExpectKeyStatusesEqual(key_status_map, kKeyStatusInternalError);
// change to pending
any_change = license_keys_.ApplyStatusChange(kKeyStatusPending,
status = kKeyStatusPending;
any_change = license_keys_.ApplyStatusChange(&status, NULL, NULL,
&new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_FALSE(new_usable_keys);
@@ -526,7 +530,8 @@ TEST_F(LicenseKeysTest, KeyStatusChanges) {
ExpectKeyStatusesEqual(key_status_map, kKeyStatusPending);
// change to pending (again)
any_change = license_keys_.ApplyStatusChange(kKeyStatusPending,
status = kKeyStatusPending;
any_change = license_keys_.ApplyStatusChange(&status, NULL, NULL,
&new_usable_keys);
EXPECT_FALSE(any_change);
EXPECT_FALSE(new_usable_keys);
@@ -538,7 +543,8 @@ TEST_F(LicenseKeysTest, KeyStatusChanges) {
ExpectKeyStatusesEqual(key_status_map, kKeyStatusPending);
// change to usable
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
status = kKeyStatusUsable;
any_change = license_keys_.ApplyStatusChange(&status, NULL, NULL,
&new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_TRUE(new_usable_keys);
@@ -550,7 +556,8 @@ TEST_F(LicenseKeysTest, KeyStatusChanges) {
ExpectKeyStatusesEqual(key_status_map, kKeyStatusUsable);
// change to usable (again)
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
status = kKeyStatusUsable;
any_change = license_keys_.ApplyStatusChange(&status, NULL, NULL,
&new_usable_keys);
EXPECT_FALSE(any_change);
EXPECT_FALSE(new_usable_keys);
@@ -562,7 +569,8 @@ TEST_F(LicenseKeysTest, KeyStatusChanges) {
ExpectKeyStatusesEqual(key_status_map, kKeyStatusUsable);
// change to expired
any_change = license_keys_.ApplyStatusChange(kKeyStatusExpired,
status = kKeyStatusExpired;
any_change = license_keys_.ApplyStatusChange(&status, NULL, NULL,
&new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_FALSE(new_usable_keys);
@@ -577,10 +585,14 @@ TEST_F(LicenseKeysTest, KeyStatusChanges) {
TEST_F(LicenseKeysTest, HdcpChanges) {
bool new_usable_keys = false;
bool any_change = false;
CdmKeyStatus status;
uint32_t resolution;
CryptoSession::HdcpCapability hdcp_level;
CdmKeyStatusMap key_status_map;
StageHdcpKeys();
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
status = kKeyStatusUsable;
any_change = license_keys_.ApplyStatusChange(&status, NULL, NULL,
&new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_TRUE(new_usable_keys);
@@ -598,9 +610,11 @@ TEST_F(LicenseKeysTest, HdcpChanges) {
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
license_keys_.ApplyConstraints(100, HDCP_NONE);
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
&new_usable_keys);
status = kKeyStatusUsable;
resolution = 100;
hdcp_level = HDCP_NONE;
any_change = license_keys_.ApplyStatusChange(&status, &resolution,
&hdcp_level, &new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_FALSE(new_usable_keys);
@@ -623,9 +637,11 @@ TEST_F(LicenseKeysTest, HdcpChanges) {
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1,
kKeyStatusOutputNotAllowed);
license_keys_.ApplyConstraints(100, HDCP_V1);
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
&new_usable_keys);
status = kKeyStatusUsable;
resolution = 100;
hdcp_level = HDCP_V1;
any_change = license_keys_.ApplyStatusChange(&status, &resolution,
&hdcp_level, &new_usable_keys);
EXPECT_FALSE(any_change);
EXPECT_FALSE(new_usable_keys);
@@ -648,9 +664,11 @@ TEST_F(LicenseKeysTest, HdcpChanges) {
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1,
kKeyStatusOutputNotAllowed);
license_keys_.ApplyConstraints(100, HDCP_V2_2);
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
&new_usable_keys);
status = kKeyStatusUsable;
resolution = 100;
hdcp_level = HDCP_V2_2;
any_change = license_keys_.ApplyStatusChange(&status, &resolution,
&hdcp_level, &new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_TRUE(new_usable_keys);
@@ -674,9 +692,11 @@ TEST_F(LicenseKeysTest, HdcpChanges) {
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_NO_OUTPUT,
kKeyStatusOutputNotAllowed);
license_keys_.ApplyConstraints(100, HDCP_NO_DIGITAL_OUTPUT);
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
&new_usable_keys);
status = kKeyStatusUsable;
resolution = 100;
hdcp_level = HDCP_NO_DIGITAL_OUTPUT;
any_change = license_keys_.ApplyStatusChange(&status, &resolution,
&hdcp_level, &new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_TRUE(new_usable_keys);
@@ -700,9 +720,11 @@ TEST_F(LicenseKeysTest, HdcpChanges) {
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT,
kKeyStatusUsable);
license_keys_.ApplyConstraints(100, HDCP_NONE);
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
&new_usable_keys);
status = kKeyStatusUsable;
resolution = 100;
hdcp_level = HDCP_NONE;
any_change = license_keys_.ApplyStatusChange(&status, &resolution,
&hdcp_level, &new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_FALSE(new_usable_keys);
@@ -730,11 +752,15 @@ TEST_F(LicenseKeysTest, HdcpChanges) {
TEST_F(LicenseKeysTest, ConstraintChanges) {
bool new_usable_keys = false;
bool any_change = false;
CdmKeyStatus status;
uint32_t resolution;
CryptoSession::HdcpCapability hdcp_level;
CdmKeyStatusMap key_status_map;
StageConstraintKeys();
// No constraints set by device
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
status = kKeyStatusUsable;
any_change = license_keys_.ApplyStatusChange(&status, NULL, NULL,
&new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_TRUE(new_usable_keys);
@@ -749,9 +775,11 @@ TEST_F(LicenseKeysTest, ConstraintChanges) {
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
// Low-res device, no HDCP support
license_keys_.ApplyConstraints(dev_lo_res, HDCP_NONE);
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
&new_usable_keys);
status = kKeyStatusUsable;
resolution = dev_lo_res;
hdcp_level = HDCP_NONE;
any_change = license_keys_.ApplyStatusChange(&status, &resolution,
&hdcp_level, &new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_FALSE(new_usable_keys);
@@ -771,9 +799,11 @@ TEST_F(LicenseKeysTest, ConstraintChanges) {
kKeyStatusOutputNotAllowed);
// Hi-res device, HDCP_V1 support
license_keys_.ApplyConstraints(dev_hi_res, HDCP_V1);
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
&new_usable_keys);
status = kKeyStatusUsable;
resolution = dev_hi_res;
hdcp_level = HDCP_V1;
any_change = license_keys_.ApplyStatusChange(&status, &resolution,
&hdcp_level, &new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_TRUE(new_usable_keys);
@@ -793,9 +823,11 @@ TEST_F(LicenseKeysTest, ConstraintChanges) {
kKeyStatusOutputNotAllowed);
// Lo-res device, HDCP V2.2 support
license_keys_.ApplyConstraints(dev_lo_res, HDCP_V2_2);
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
&new_usable_keys);
status = kKeyStatusUsable;
resolution = dev_lo_res;
hdcp_level = HDCP_V2_2;
any_change = license_keys_.ApplyStatusChange(&status, &resolution,
&hdcp_level, &new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_TRUE(new_usable_keys);
@@ -816,9 +848,11 @@ TEST_F(LicenseKeysTest, ConstraintChanges) {
kKeyStatusOutputNotAllowed);
// Hi-res device, Maximal HDCP support
license_keys_.ApplyConstraints(dev_hi_res, HDCP_NO_DIGITAL_OUTPUT);
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
&new_usable_keys);
status = kKeyStatusUsable;
resolution = dev_hi_res;
hdcp_level = HDCP_NO_DIGITAL_OUTPUT;
any_change = license_keys_.ApplyStatusChange(&status, &resolution,
&hdcp_level, &new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_TRUE(new_usable_keys);
@@ -839,9 +873,11 @@ TEST_F(LicenseKeysTest, ConstraintChanges) {
kKeyStatusUsable);
// Lo-res device, no HDCP support
license_keys_.ApplyConstraints(dev_lo_res, HDCP_NONE);
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
&new_usable_keys);
status = kKeyStatusUsable;
resolution = dev_lo_res;
hdcp_level = HDCP_NONE;
any_change = license_keys_.ApplyStatusChange(&status, &resolution,
&hdcp_level, &new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_TRUE(new_usable_keys);
@@ -862,9 +898,11 @@ TEST_F(LicenseKeysTest, ConstraintChanges) {
kKeyStatusOutputNotAllowed);
// Too-high-res -- all keys rejected
license_keys_.ApplyConstraints(dev_top_res, HDCP_NONE);
any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable,
&new_usable_keys);
status = kKeyStatusUsable;
resolution = dev_top_res;
hdcp_level = HDCP_NONE;
any_change = license_keys_.ApplyStatusChange(&status, &resolution,
&hdcp_level, &new_usable_keys);
EXPECT_TRUE(any_change);
EXPECT_FALSE(new_usable_keys);

View File

@@ -1,5 +1,8 @@
// Copyright 2016 Google Inc. All Rights Reserved.
#include <iostream>
#include <tuple>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "crypto_session.h"
@@ -13,6 +16,8 @@
// protobuf generated classes.
using video_widevine::License;
using video_widevine::License_Policy;
using video_widevine::LicenseType;
using video_widevine::OFFLINE;
using video_widevine::STREAMING;
namespace wvcdm {
@@ -75,14 +80,47 @@ class MockCdmEventListener : public WvCdmEventListener {
int64_t new_expiry_time_seconds));
};
struct LicenseTypeParam {
std::string name;
LicenseType license_type;
bool can_persist;
};
const LicenseTypeParam kStreamingParam{"Streaming", STREAMING, false};
const LicenseTypeParam kOfflineParam{"Offline", OFFLINE, true};
void PrintTo(const LicenseTypeParam& param, std::ostream* os) {
*os << param.name;
}
struct LicenseRenewalParam {
std::string name;
bool can_renew;
int64_t renewal_delay_seconds;
};
const LicenseRenewalParam kNoRenewalParam{"Cannot Renew", false, 0};
const LicenseRenewalParam kShortRenewalParam{"Short Renewal Delay", true, 1};
const LicenseRenewalParam kLongRenewalParam{"Long Renewal Delay", true, 60};
void PrintTo(const LicenseRenewalParam& param, std::ostream* os) {
*os << param.name;
}
} // namespace
class PolicyEngineConstraintsTest : public Test {
class PolicyEngineConstraintsTest
: public TestWithParam<std::tuple<LicenseTypeParam, LicenseRenewalParam>> {
protected:
virtual void SetUp() {
mock_clock_ = new NiceMock<MockClock>();
current_time_ = 0;
// mock_event_listener_ is a StrictMock, but we don't care about renewal
// calls for these tests and want to ignore them.
EXPECT_CALL(mock_event_listener_, OnSessionRenewalNeeded(_))
.Times(AtLeast(0));
policy_engine_.reset(new PolicyEngine(kSessionId, &mock_event_listener_,
&crypto_session_));
InjectMockClock();
@@ -108,19 +146,25 @@ class PolicyEngineConstraintsTest : public Test {
}
void SetupLicense() {
LicenseTypeParam typeParam;
LicenseRenewalParam renewalParam;
std::tie(typeParam, renewalParam) = GetParam();
license_.set_license_start_time(current_time_);
LicenseIdentification* id = license_.mutable_id();
id->set_version(1);
id->set_type(STREAMING);
id->set_type(typeParam.license_type);
License_Policy* policy = license_.mutable_policy();
policy = license_.mutable_policy();
policy->set_can_play(true);
policy->set_can_persist(false);
policy->set_can_persist(typeParam.can_persist);
policy->set_can_renew(renewalParam.can_renew);
policy->set_rental_duration_seconds(kRentalDuration);
policy->set_playback_duration_seconds(kPlaybackDuration);
policy->set_license_duration_seconds(kStreamingLicenseDuration);
policy->set_renewal_delay_seconds(renewalParam.renewal_delay_seconds);
KeyList* keys = license_.mutable_key();
@@ -211,7 +255,7 @@ class PolicyEngineConstraintsTest : public Test {
License license_;
};
TEST_F(PolicyEngineConstraintsTest, IsPermissiveWithoutAResolution) {
TEST_P(PolicyEngineConstraintsTest, IsPermissiveWithoutAResolution) {
EXPECT_CALL(*mock_clock_, GetCurrentTime()).Times(2);
EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(kSessionId, _));
ExpectSessionKeysChange(kKeyStatusUsable, true);
@@ -227,7 +271,7 @@ TEST_F(PolicyEngineConstraintsTest, IsPermissiveWithoutAResolution) {
EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId6));
}
TEST_F(PolicyEngineConstraintsTest, HandlesResolutionsBasedOnConstraints) {
TEST_P(PolicyEngineConstraintsTest, HandlesResolutionsBasedOnConstraints) {
{
Sequence time;
for (int i=0; i<4; ++i) {
@@ -279,7 +323,7 @@ TEST_F(PolicyEngineConstraintsTest, HandlesResolutionsBasedOnConstraints) {
EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId6));
}
TEST_F(PolicyEngineConstraintsTest,
TEST_P(PolicyEngineConstraintsTest,
RequestsHdcpImmediatelyAndOnlyAfterInterval) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.WillOnce(Return(0))
@@ -310,7 +354,7 @@ TEST_F(PolicyEngineConstraintsTest,
policy_engine_->OnTimerEvent();
}
TEST_F(PolicyEngineConstraintsTest, DoesNotRequestHdcpWithoutALicense) {
TEST_P(PolicyEngineConstraintsTest, DoesNotRequestHdcpWithoutALicense) {
EXPECT_CALL(*mock_clock_, GetCurrentTime())
.WillOnce(Return(0));
EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)).Times(0);
@@ -318,7 +362,7 @@ TEST_F(PolicyEngineConstraintsTest, DoesNotRequestHdcpWithoutALicense) {
policy_engine_->OnTimerEvent();
}
TEST_F(PolicyEngineConstraintsTest, HandlesConstraintOverridingHdcp) {
TEST_P(PolicyEngineConstraintsTest, HandlesConstraintOverridingHdcp) {
{
Sequence time;
for (int i=0; i<3; ++i) {
@@ -360,7 +404,7 @@ TEST_F(PolicyEngineConstraintsTest, HandlesConstraintOverridingHdcp) {
EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId6));
}
TEST_F(PolicyEngineConstraintsTest, HandlesNoHdcp) {
TEST_P(PolicyEngineConstraintsTest, HandlesNoHdcp) {
{
Sequence time;
for (int i=0; i<3; ++i) {
@@ -407,7 +451,7 @@ TEST_F(PolicyEngineConstraintsTest, HandlesNoHdcp) {
EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId6));
}
TEST_F(PolicyEngineConstraintsTest, IgnoresHdcpWithoutAResolution) {
TEST_P(PolicyEngineConstraintsTest, IgnoresHdcpWithoutAResolution) {
{
Sequence time;
for (int i=0; i<2; ++i) {
@@ -429,4 +473,9 @@ TEST_F(PolicyEngineConstraintsTest, IgnoresHdcpWithoutAResolution) {
EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId6));
}
INSTANTIATE_TEST_CASE_P(Default, PolicyEngineConstraintsTest,
Combine(
Values(kStreamingParam, kOfflineParam),
Values(kNoRenewalParam, kShortRenewalParam, kLongRenewalParam)));
} // namespace wvcdm