From b8e31487a4ad56f579b9464cda6c3add0fad49ff Mon Sep 17 00:00:00 2001 From: "John W. Bruce" Date: Tue, 29 Aug 2017 16:37:49 -0700 Subject: [PATCH] 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 --- .../cdm/core/include/license_key_status.h | 49 ++++--- .../cdm/core/include/policy_engine.h | 15 ++- .../cdm/core/src/license_key_status.cpp | 74 ++++++++--- libwvdrmengine/cdm/core/src/policy_engine.cpp | 50 ++++--- .../cdm/core/test/license_keys_unittest.cpp | 122 ++++++++++++------ .../policy_engine_constraints_unittest.cpp | 69 ++++++++-- 6 files changed, 264 insertions(+), 115 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/license_key_status.h b/libwvdrmengine/cdm/core/include/license_key_status.h index 59689472..e14e3826 100644 --- a/libwvdrmengine/cdm/core/include/license_key_status.h +++ b/libwvdrmengine/cdm/core/include/license_key_status.h @@ -11,6 +11,8 @@ namespace wvcdm { +static const uint32_t kNoResolution = 0; + class LicenseKeyStatus; // Holds all content and operator session keys for a session. @@ -31,11 +33,17 @@ class LicenseKeys { virtual bool GetAllowedUsage(const KeyId& key_id, CdmKeyAllowedUsage* allowed_usage); - // Applies a new status to each content key. - // Returns true if any statuses changed, and sets new_usable_keys to - // true if the status changes resulted in keys becoming usable. - virtual bool ApplyStatusChange(CdmKeyStatus new_status, - bool* new_usable_keys); + // Applies a new status, resolution, and/or HDCP level to each content key, + // updating their usability under their constraints. Returns true if any keys + // have changed usability, and sets new_usable_keys to true if the status + // changes resulted in at least one key becoming usable. + // + // Pass in NULL for any of the const-pointer arguments to leave that state + // unchanged. + virtual bool ApplyStatusChange( + const CdmKeyStatus* new_status, const uint32_t* new_resolution, + const CryptoSession::HdcpCapability* new_hdcp_level, + bool* new_usable_keys); // Populates the CdmKeyStatusMap with the current content keys. virtual void ExtractKeyStatuses(CdmKeyStatusMap* content_keys); @@ -45,11 +53,6 @@ class LicenseKeys { // to the key, returns true. virtual bool MeetsConstraints(const KeyId& key_id); - // Applies a resolution and/or hdcp change to each key, updating their - // useability under their constraints. - virtual void ApplyConstraints(uint32_t new_resolution, - CryptoSession::HdcpCapability new_hdcp_level); - // Extracts the keys from a license and makes them available for // querying usage and constraint settings. virtual void SetFromLicense( @@ -85,21 +88,24 @@ class LicenseKeyStatus { // Returns the current status of the key. virtual CdmKeyStatus GetKeyStatus() const { return key_status_; } - // Applies a new status to this key. - // Returns true if the status changed, and sets new_usable_keys to - // true if the status changes resulted in the key becoming usable. - virtual bool ApplyStatusChange(CdmKeyStatus new_status, - bool* new_usable_keys); + // Applies a new status, resolution, and/or HDCP level, updating the key's + // usability under its constraints. Returns true if this results in a change + // to the key's usability, and sets newly_usable to true if the status + // changes resulted in the key becoming usable. + // + // Pass in NULL for any of the const-pointer arguments to leave that state + // unchanged. + virtual bool ApplyStatusChange( + const CdmKeyStatus* maybe_new_status, + const uint32_t* maybe_new_resolution, + const CryptoSession::HdcpCapability* maybe_new_hdcp_level, + bool* newly_usable); // Returns the current constraint status of this key. The result // may change due to calls to ApplyConstraints(). // Note: this will return true until the first call to ApplyConstraints(). virtual bool MeetsConstraints() const { return meets_constraints_; } - // Applies the given changes in resolution or HDCP settings. - virtual void ApplyConstraints( - uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level); - protected: typedef ::video_widevine::License::KeyContainer KeyContainer; typedef KeyContainer::OperatorSessionKeyPermissions @@ -121,11 +127,14 @@ class LicenseKeyStatus { bool HasConstraints() { return is_content_key_ && constraints_.size() != 0; } - void SetConstraints(const ConstraintList& constraints); + void ApplyConstraints(); bool is_content_key_; CdmKeyStatus key_status_; + uint32_t resolution_; + CryptoSession::HdcpCapability hdcp_level_; + bool can_check_constraints_; bool meets_constraints_; CdmKeyAllowedUsage allowed_usage_; CryptoSession::HdcpCapability default_hdcp_level_; diff --git a/libwvdrmengine/cdm/core/include/policy_engine.h b/libwvdrmengine/cdm/core/include/policy_engine.h index 83214622..f16c0e18 100644 --- a/libwvdrmengine/cdm/core/include/policy_engine.h +++ b/libwvdrmengine/cdm/core/include/policy_engine.h @@ -108,6 +108,10 @@ class PolicyEngine { friend class PolicyEngineConstraintsTest; void InitDevice(CryptoSession* crypto_session); + // Checks the keys against the current state of the hardware in case the HDCP + // level no longer meets their requirements. Will only check once every + // |kHdcpCheckInterval| seconds. Calls NotifyIfKeysChanged() to propagate any + // resultant changes to the OnKeysChange event. void CheckDevice(int64_t current_time); void SetDeviceResolution(uint32_t width, uint32_t height) { @@ -143,9 +147,14 @@ class PolicyEngine { void UpdateRenewalRequest(int64_t current_time); - // Notifies updates in keys information and fire OnKeysChange event if - // key changes. - void NotifyKeysChange(CdmKeyStatus new_status); + + // Updates the keys' status to |new_status|. Calls NotifyIfKeysChanged() to + // propagate any resultant changes to the OnKeysChange event. + void UpdateKeyStatus(CdmKeyStatus new_status); + + // Helper method that correctly fires the OnKeysChange event - if needed - + // depending on the parameters it is given. + void NotifyIfKeysChanged(bool keys_changed, bool new_usable_keys); // Notifies updates in expiry time and fire OnExpirationUpdate event if // expiry time changes. diff --git a/libwvdrmengine/cdm/core/src/license_key_status.cpp b/libwvdrmengine/cdm/core/src/license_key_status.cpp index 0fe19363..6ee38d11 100644 --- a/libwvdrmengine/cdm/core/src/license_key_status.cpp +++ b/libwvdrmengine/cdm/core/src/license_key_status.cpp @@ -86,14 +86,17 @@ bool LicenseKeys::GetAllowedUsage(const KeyId& key_id, } } -bool LicenseKeys::ApplyStatusChange(CdmKeyStatus new_status, - bool* new_usable_keys) { +bool LicenseKeys::ApplyStatusChange( + const CdmKeyStatus* new_status, const uint32_t* new_resolution, + const CryptoSession::HdcpCapability* new_hdcp_level, + bool* new_usable_keys) { bool keys_changed = false; bool newly_usable = false; *new_usable_keys = false; for (LicenseKeyStatusIterator it = keys_.begin(); it != keys_.end(); ++it) { bool usable; - if (it->second->ApplyStatusChange(new_status, &usable)) { + if (it->second->ApplyStatusChange(new_status, new_resolution, + new_hdcp_level, &usable)) { newly_usable |= usable; keys_changed = true; } @@ -122,13 +125,6 @@ bool LicenseKeys::MeetsConstraints(const KeyId& key_id) { } } -void LicenseKeys::ApplyConstraints( - uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level) { - for (LicenseKeyStatusIterator i = keys_.begin(); i != keys_.end(); ++i) { - i->second->ApplyConstraints(new_resolution, new_hdcp_level); - } -} - void LicenseKeys::SetFromLicense( const video_widevine::License& license) { this->Clear(); @@ -145,6 +141,9 @@ void LicenseKeys::SetFromLicense( LicenseKeyStatus::LicenseKeyStatus(const KeyContainer& key) : is_content_key_(false), key_status_(kKeyStatusInternalError), + resolution_(kNoResolution), + hdcp_level_(HDCP_NONE), + can_check_constraints_(false), meets_constraints_(true), default_hdcp_level_(HDCP_NONE) { @@ -221,26 +220,55 @@ bool LicenseKeyStatus::GetAllowedUsage(CdmKeyAllowedUsage* allowed_usage) { return true; } -bool LicenseKeyStatus::ApplyStatusChange(CdmKeyStatus new_status, - bool* new_usable_key) { - *new_usable_key = false; +bool LicenseKeyStatus::ApplyStatusChange( + const CdmKeyStatus* maybe_new_status, const uint32_t* maybe_new_resolution, + const CryptoSession::HdcpCapability* maybe_new_hdcp_level, + bool* newly_usable) { + *newly_usable = false; if (!is_content_key_) { return false; } - CdmKeyStatus updated_status = new_status; - if (updated_status == kKeyStatusUsable) { + + // Most of this function is various work to calculate an updated value for + // the status. We start at the same value as the current status. + CdmKeyStatus updated_status = key_status_; + + // Account for the new status, if provided. + if (maybe_new_status != NULL) { + const CdmKeyStatus& new_status = *maybe_new_status; + can_check_constraints_ = (new_status == kKeyStatusUsable); + updated_status = new_status; + } + + // Account for the new resolution, if provided. + if (maybe_new_resolution != NULL) { + resolution_ = *maybe_new_resolution; + } + + // Account for the new HDCP level, if provided. + if (maybe_new_hdcp_level != NULL) { + hdcp_level_ = *maybe_new_hdcp_level; + } + + // If we can, check the current state against the constraints and update the + // status if needed. + if (can_check_constraints_) { + ApplyConstraints(); if (!MeetsConstraints()) { updated_status = kKeyStatusOutputNotAllowed; } } + + // Check if any of that work changed the key status. if (key_status_ != updated_status) { key_status_ = updated_status; if (updated_status == kKeyStatusUsable) { - *new_usable_key = true; + *newly_usable = true; } return true; + } else { + return false; } - return false; } // If the key has constraints, find the constraint that applies. @@ -250,12 +278,16 @@ bool LicenseKeyStatus::ApplyStatusChange(CdmKeyStatus new_status, // If the key has no constraints, or if the constraint has no HDCP // requirement, use the key's default HDCP setting to check against the // device's current HDCP level. -void LicenseKeyStatus::ApplyConstraints( - uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level) { +void LicenseKeyStatus::ApplyConstraints() { + if (resolution_ == kNoResolution) { + // Until a resolution has been detected, the constraints cannot be checked. + meets_constraints_ = true; + return; + } VideoResolutionConstraint* current_constraint = NULL; if (HasConstraints()) { - current_constraint = GetConstraintForRes(new_resolution, constraints_); + current_constraint = GetConstraintForRes(resolution_, constraints_); if (NULL == current_constraint) { meets_constraints_ = false; return; @@ -270,7 +302,7 @@ void LicenseKeyStatus::ApplyConstraints( desired_hdcp_level = default_hdcp_level_; } - meets_constraints_ = (new_hdcp_level >= desired_hdcp_level); + meets_constraints_ = (hdcp_level_ >= desired_hdcp_level); } void LicenseKeyStatus::SetConstraints(const ConstraintList& constraints) { diff --git a/libwvdrmengine/cdm/core/src/policy_engine.cpp b/libwvdrmengine/cdm/core/src/policy_engine.cpp index 6caae554..c933605a 100644 --- a/libwvdrmengine/cdm/core/src/policy_engine.cpp +++ b/libwvdrmengine/cdm/core/src/policy_engine.cpp @@ -17,7 +17,6 @@ using video_widevine::License; namespace { const int64_t kHdcpCheckInterval = 10; -const uint32_t kNoResolution = 0; } // namespace @@ -71,7 +70,15 @@ void PolicyEngine::CheckDevice(int64_t current_time) { if (!crypto_session_->GetHdcpCapabilities(¤t_hdcp_level, &ignored)) { current_hdcp_level = HDCP_NONE; } - license_keys_->ApplyConstraints(current_resolution_, current_hdcp_level); + + bool new_usable_keys = false; + bool keys_changed = + license_keys_->ApplyStatusChange(NULL, // new_status + ¤t_resolution_, + ¤t_hdcp_level, + &new_usable_keys); + NotifyIfKeysChanged(keys_changed, new_usable_keys); + next_device_check_ = current_time + kHdcpCheckInterval; } } @@ -89,21 +96,19 @@ void PolicyEngine::OnTimerEvent() { if (HasLicenseOrPlaybackDurationExpired(current_time) && license_state_ != kLicenseStateExpired) { license_state_ = kLicenseStateExpired; - NotifyKeysChange(kKeyStatusExpired); + UpdateKeyStatus(kKeyStatusExpired); return; } // Check device conditions that affect playability (HDCP, resolution) CheckDevice(current_time); + // Test to determine if renewal should be attempted. bool renewal_needed = false; - // Test to determine if renewal should be attempted. switch (license_state_) { case kLicenseStateCanPlay: { if (HasRenewalDelayExpired(current_time)) renewal_needed = true; - // HDCP may change, so force a check. - NotifyKeysChange(kKeyStatusUsable); break; } @@ -120,7 +125,7 @@ void PolicyEngine::OnTimerEvent() { case kLicenseStatePending: { if (current_time >= license_start_time_) { license_state_ = kLicenseStateCanPlay; - NotifyKeysChange(kKeyStatusUsable); + UpdateKeyStatus(kKeyStatusUsable); } break; } @@ -132,7 +137,7 @@ void PolicyEngine::OnTimerEvent() { default: { license_state_ = kLicenseStateExpired; - NotifyKeysChange(kKeyStatusInternalError); + UpdateKeyStatus(kKeyStatusInternalError); break; } } @@ -157,7 +162,7 @@ void PolicyEngine::SetLicenseForRelease(const License& license) { policy_.Clear(); // Expire any old keys. - NotifyKeysChange(kKeyStatusExpired); + UpdateKeyStatus(kKeyStatusExpired); UpdateLicense(license); } @@ -190,17 +195,17 @@ void PolicyEngine::UpdateLicense(const License& license) { if (!policy_.can_play() || HasLicenseOrPlaybackDurationExpired(current_time)) { license_state_ = kLicenseStateExpired; - NotifyKeysChange(kKeyStatusExpired); + UpdateKeyStatus(kKeyStatusExpired); return; } // Update state if (current_time >= license_start_time_) { license_state_ = kLicenseStateCanPlay; - NotifyKeysChange(kKeyStatusUsable); + UpdateKeyStatus(kKeyStatusUsable); } else { license_state_ = kLicenseStatePending; - NotifyKeysChange(kKeyStatusPending); + UpdateKeyStatus(kKeyStatusPending); } NotifyExpirationUpdate(current_time); } @@ -240,7 +245,7 @@ void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) { void PolicyEngine::NotifySessionExpiration() { license_state_ = kLicenseStateExpired; - NotifyKeysChange(kKeyStatusExpired); + UpdateKeyStatus(kKeyStatusExpired); } CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) { @@ -429,16 +434,23 @@ bool PolicyEngine::HasRenewalRetryIntervalExpired(int64_t current_time) { next_renewal_time_ <= current_time; } -void PolicyEngine::NotifyKeysChange(CdmKeyStatus new_status) { - bool keys_changed; - bool has_new_usable_key = false; - keys_changed = license_keys_->ApplyStatusChange(new_status, - &has_new_usable_key); +void PolicyEngine::UpdateKeyStatus(CdmKeyStatus new_status) { + bool new_usable_keys = false; + bool keys_changed = + license_keys_->ApplyStatusChange(&new_status, + NULL, // new_resolution + NULL, // new_hdcp_level + &new_usable_keys); + NotifyIfKeysChanged(keys_changed, new_usable_keys); +} + +void PolicyEngine::NotifyIfKeysChanged(bool keys_changed, + bool new_usable_keys) { if (event_listener_ && keys_changed) { CdmKeyStatusMap content_keys; license_keys_->ExtractKeyStatuses(&content_keys); event_listener_->OnSessionKeysChange(session_id_, content_keys, - has_new_usable_key); + new_usable_keys); } } diff --git a/libwvdrmengine/cdm/core/test/license_keys_unittest.cpp b/libwvdrmengine/cdm/core/test/license_keys_unittest.cpp index dc645883..f8557a83 100644 --- a/libwvdrmengine/cdm/core/test/license_keys_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/license_keys_unittest.cpp @@ -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); diff --git a/libwvdrmengine/cdm/core/test/policy_engine_constraints_unittest.cpp b/libwvdrmengine/cdm/core/test/policy_engine_constraints_unittest.cpp index 79657c50..99dd6419 100644 --- a/libwvdrmengine/cdm/core/test/policy_engine_constraints_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/policy_engine_constraints_unittest.cpp @@ -1,5 +1,8 @@ // Copyright 2016 Google Inc. All Rights Reserved. +#include +#include + #include #include #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> { protected: virtual void SetUp() { mock_clock_ = new NiceMock(); 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