Delay license state evaluation for offline licenses
[ Merge of http://go/wvgerrit/106325 and http://go/ag/12644840 ] When offline licenses are restored, licenses and any renewals are processed. License state evaluation occurs and notifications are sent to listeners. If the license is expired, which is likely if a renewal is present, the license state will transition to expired. Transitions out of expired state are not allowed and the renewal has no effect. If we work around this by allowing transitions out of expired state, listeners will get notifications that keys have expired and then that are usable soon after. To avoid delivering erroneous notifications we delay evaluation of license state while the license and renewal are being processed. Evaluation occurs at the last stage of license restoration when playback information from the usage table is being restored. This only need to occur for when licenses are being restored. In other cases when a license or renewal is received, license state evaluation and event listener notification needs to occur immediately. Bug: 166131956 Test: WV unit/integration tests, GtsMediaTestCases tests Change-Id: Ic8ade25316c5e20cc88de9225c43c24b28f21ac4
This commit is contained in:
@@ -58,9 +58,9 @@ class CdmLicense {
|
||||
CdmSession* cdm_session, CdmKeyMessage* signed_request,
|
||||
std::string* server_url);
|
||||
virtual CdmResponseType HandleKeyResponse(
|
||||
const CdmKeyResponse& license_response);
|
||||
bool is_restore, const CdmKeyResponse& license_response);
|
||||
virtual CdmResponseType HandleKeyUpdateResponse(
|
||||
bool is_renewal, const CdmKeyResponse& license_response);
|
||||
bool is_renewal, bool is_restore, const CdmKeyResponse& license_response);
|
||||
virtual CdmResponseType HandleEmbeddedKeyData(
|
||||
const InitializationData& init_data);
|
||||
|
||||
@@ -109,7 +109,7 @@ class CdmLicense {
|
||||
video_widevine::LicenseRequest* license_request);
|
||||
|
||||
CdmResponseType HandleContentKeyResponse(
|
||||
const std::string& msg, const std::string& core_message,
|
||||
bool is_restore, const std::string& msg, const std::string& core_message,
|
||||
const std::string& signature, const std::string& mac_key_iv,
|
||||
const std::string& mac_key, const std::vector<CryptoKey>& key_array,
|
||||
const video_widevine::License& license);
|
||||
@@ -118,7 +118,7 @@ class CdmLicense {
|
||||
// the crypto session. In addition, it also extracts content keys from
|
||||
// |wrapped_keys_| and loads them for use.
|
||||
CdmResponseType HandleEntitlementKeyResponse(
|
||||
const std::string& msg, const std::string& core_message,
|
||||
bool is_restore, const std::string& msg, const std::string& core_message,
|
||||
const std::string& signature, const std::string& mac_key_iv,
|
||||
const std::string& mac_key, const std::vector<CryptoKey>& key_array,
|
||||
const video_widevine::License& license);
|
||||
|
||||
@@ -56,10 +56,13 @@ class PolicyEngine {
|
||||
|
||||
// SetLicense is used in handling the initial license response. It stores
|
||||
// an exact copy of the policy information stored in the license.
|
||||
// The license state transitions to kLicenseStateCanPlay if the license
|
||||
// permits playback.
|
||||
// License state transitions and notifications may occur if
|
||||
// |defer_license_state_update| is not set. If set, the license is likely
|
||||
// being restored and transitions and notifications will be deferred until
|
||||
// stored playback times are restored.
|
||||
virtual void SetLicense(const video_widevine::License& license,
|
||||
bool supports_core_messages);
|
||||
bool supports_core_messages,
|
||||
bool defer_license_state_update);
|
||||
|
||||
// Used to update the currently loaded entitled content keys.
|
||||
virtual void SetEntitledLicenseKeys(
|
||||
@@ -78,9 +81,17 @@ class PolicyEngine {
|
||||
// UpdateLicense is used in handling a license response for a renewal request.
|
||||
// The response may only contain any policy fields that have changed. In this
|
||||
// case an exact copy is not what we want to happen. We also will receive an
|
||||
// updated license_start_time from the server. The license will transition to
|
||||
// kLicenseStateCanPlay if the license permits playback.
|
||||
virtual void UpdateLicense(const video_widevine::License& license);
|
||||
// updated license_start_time from the server.
|
||||
// License state transitions and notifications may occur if
|
||||
// |defer_license_state_update| is not set. If set, the license is likely
|
||||
// being restored and transitions and notifications will be deferred until
|
||||
// stored playback times are restored.
|
||||
virtual void UpdateLicense(const video_widevine::License& license,
|
||||
bool defer_license_state_update);
|
||||
|
||||
// This method updates license state and sends appropriate notifications
|
||||
// to event listeners.
|
||||
virtual void UpdateLicenseState(int64_t current_time);
|
||||
|
||||
// Used for notifying the Policy Engine of resolution changes
|
||||
virtual void NotifyResolution(uint32_t width, uint32_t height);
|
||||
@@ -101,6 +112,9 @@ class PolicyEngine {
|
||||
int64_t GetPlaybackStartTime();
|
||||
int64_t GetLastPlaybackTime();
|
||||
int64_t GetGracePeriodEndTime();
|
||||
|
||||
// This method will also update license state and sends appropriate
|
||||
// notifications to event listeners.
|
||||
void RestorePlaybackTimes(int64_t playback_start_time,
|
||||
int64_t last_playback_time,
|
||||
int64_t grace_period_end_time);
|
||||
@@ -153,6 +167,7 @@ class PolicyEngine {
|
||||
void SetSecurityLevelForTest(CdmSecurityLevel security_level);
|
||||
|
||||
LicenseState license_state_;
|
||||
int64_t license_state_update_deadline_;
|
||||
|
||||
// This is the license id field from server response. This data gets passed
|
||||
// back to the server in each renewal request. When we get a renewal response
|
||||
|
||||
@@ -592,7 +592,8 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) {
|
||||
if (sts != NO_ERROR) return sts;
|
||||
}
|
||||
}
|
||||
sts = license_parser_->HandleKeyResponse(key_response);
|
||||
sts = license_parser_->HandleKeyResponse(/* is restore */ false,
|
||||
key_response);
|
||||
|
||||
// Update the license sdk and service versions.
|
||||
const VersionInfo& version_info = license_parser_->GetServiceVersion();
|
||||
@@ -789,8 +790,9 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
|
||||
LOGE("CDM session not initialized");
|
||||
return NOT_INITIALIZED_ERROR;
|
||||
}
|
||||
CdmResponseType sts =
|
||||
license_parser_->HandleKeyUpdateResponse(true, key_response);
|
||||
CdmResponseType sts = license_parser_->HandleKeyUpdateResponse(
|
||||
/* is renewal */ true,
|
||||
/* is restore */ false, key_response);
|
||||
|
||||
// Record the timing on success.
|
||||
UpdateRequestLatencyTiming(sts);
|
||||
@@ -854,8 +856,9 @@ CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) {
|
||||
LOGE("CDM session not initialized");
|
||||
return NOT_INITIALIZED_ERROR;
|
||||
}
|
||||
CdmResponseType sts =
|
||||
license_parser_->HandleKeyUpdateResponse(false, key_response);
|
||||
CdmResponseType sts = license_parser_->HandleKeyUpdateResponse(
|
||||
/* is renewal */ false,
|
||||
/* is restore */ false, key_response);
|
||||
// Record the timing on success.
|
||||
UpdateRequestLatencyTiming(sts);
|
||||
|
||||
|
||||
@@ -530,7 +530,7 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest(
|
||||
}
|
||||
|
||||
CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
const CdmKeyResponse& license_response) {
|
||||
bool is_restore, const CdmKeyResponse& license_response) {
|
||||
if (!initialized_) {
|
||||
LOGE("CdmLicense not initialized");
|
||||
return LICENSE_PARSER_NOT_INITIALIZED_2;
|
||||
@@ -683,18 +683,19 @@ CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
|
||||
CdmResponseType resp = NO_CONTENT_KEY;
|
||||
if (kLicenseKeyTypeEntitlement == key_type) {
|
||||
resp =
|
||||
HandleEntitlementKeyResponse(signed_message, core_message, signature,
|
||||
mac_key_iv, mac_keys, key_array, license);
|
||||
resp = HandleEntitlementKeyResponse(is_restore, signed_message,
|
||||
core_message, signature, mac_key_iv,
|
||||
mac_keys, key_array, license);
|
||||
} else if (kLicenseKeyTypeContent == key_type) {
|
||||
resp = HandleContentKeyResponse(signed_message, core_message, signature,
|
||||
mac_key_iv, mac_keys, key_array, license);
|
||||
resp = HandleContentKeyResponse(is_restore, signed_message, core_message,
|
||||
signature, mac_key_iv, mac_keys, key_array,
|
||||
license);
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
||||
bool is_renewal, const CdmKeyResponse& license_response) {
|
||||
bool is_renewal, bool is_restore, const CdmKeyResponse& license_response) {
|
||||
if (!initialized_) {
|
||||
LOGE("CdmLicense not initialized");
|
||||
return LICENSE_PARSER_NOT_INITIALIZED_3;
|
||||
@@ -781,7 +782,7 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
||||
}
|
||||
|
||||
if (status == KEY_ADDED) {
|
||||
policy_engine_->UpdateLicense(license);
|
||||
policy_engine_->UpdateLicense(license, is_restore);
|
||||
}
|
||||
|
||||
return status;
|
||||
@@ -836,14 +837,14 @@ CdmResponseType CdmLicense::RestoreOfflineLicense(
|
||||
license_nonce_ = original_license_request.key_control_nonce();
|
||||
}
|
||||
|
||||
CdmResponseType sts = HandleKeyResponse(license_response);
|
||||
CdmResponseType sts = HandleKeyResponse(true, license_response);
|
||||
|
||||
if (sts != KEY_ADDED) return sts;
|
||||
|
||||
if (!license_renewal_response.empty()) {
|
||||
sts = PrepareKeyUpdateReload(cdm_session);
|
||||
if (sts != KEY_MESSAGE) return sts;
|
||||
sts = HandleKeyUpdateResponse(true, license_renewal_response);
|
||||
if (sts != KEY_MESSAGE && sts != NO_ERROR) return sts;
|
||||
sts = HandleKeyUpdateResponse(true, true, license_renewal_response);
|
||||
if (sts != KEY_ADDED) return sts;
|
||||
}
|
||||
|
||||
@@ -968,7 +969,7 @@ CdmResponseType CdmLicense::RestoreLicenseForRelease(
|
||||
}
|
||||
|
||||
if (!license.id().has_provider_session_token()) {
|
||||
CdmResponseType result = HandleKeyResponse(license_response);
|
||||
CdmResponseType result = HandleKeyResponse(false, license_response);
|
||||
return result == KEY_ADDED ? NO_ERROR : result;
|
||||
}
|
||||
|
||||
@@ -1116,7 +1117,7 @@ CdmResponseType CdmLicense::PrepareContentId(
|
||||
}
|
||||
|
||||
CdmResponseType CdmLicense::HandleContentKeyResponse(
|
||||
const std::string& msg, const std::string& core_message,
|
||||
bool is_restore, const std::string& msg, const std::string& core_message,
|
||||
const std::string& signature, const std::string& mac_key_iv,
|
||||
const std::string& mac_key, const std::vector<CryptoKey>& key_array,
|
||||
const video_widevine::License& license) {
|
||||
@@ -1140,13 +1141,13 @@ CdmResponseType CdmLicense::HandleContentKeyResponse(
|
||||
it != key_array.end(); ++it) {
|
||||
loaded_keys_.insert(it->key_id());
|
||||
}
|
||||
policy_engine_->SetLicense(license, supports_core_messages());
|
||||
policy_engine_->SetLicense(license, supports_core_messages(), is_restore);
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
CdmResponseType CdmLicense::HandleEntitlementKeyResponse(
|
||||
const std::string& msg, const std::string& core_message,
|
||||
bool is_restore, const std::string& msg, const std::string& core_message,
|
||||
const std::string& signature, const std::string& mac_key_iv,
|
||||
const std::string& mac_key, const std::vector<CryptoKey>& key_array,
|
||||
const video_widevine::License& license) {
|
||||
@@ -1170,7 +1171,7 @@ CdmResponseType CdmLicense::HandleEntitlementKeyResponse(
|
||||
|
||||
// Save the entitlement keys for future use to handle key changes.
|
||||
entitlement_keys_.CopyFrom(license.key());
|
||||
policy_engine_->SetLicense(license, supports_core_messages());
|
||||
policy_engine_->SetLicense(license, supports_core_messages(), is_restore);
|
||||
|
||||
return HandleNewEntitledKeys(wrapped_keys_);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ using video_widevine::License;
|
||||
namespace {
|
||||
|
||||
const int kCdmPolicyTimerDurationSeconds = 1;
|
||||
const int kClockSkewDelta = 5; // seconds
|
||||
const int kClockSkewDelta = 5; // seconds
|
||||
const int64_t kLicenseStateUpdateDelay = 20; // seconds
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -30,6 +31,7 @@ PolicyEngine::PolicyEngine(CdmSessionId session_id,
|
||||
WvCdmEventListener* event_listener,
|
||||
CryptoSession* crypto_session)
|
||||
: license_state_(kLicenseStateInitial),
|
||||
license_state_update_deadline_(0),
|
||||
last_recorded_current_time_(0),
|
||||
session_id_(session_id),
|
||||
event_listener_(event_listener),
|
||||
@@ -90,6 +92,17 @@ void PolicyEngine::OnTimerEvent() {
|
||||
last_recorded_current_time_ += kCdmPolicyTimerDurationSeconds;
|
||||
const int64_t current_time = GetCurrentTime();
|
||||
|
||||
// This conditional should not succeed but being cautious in case license
|
||||
// state was not updated after the license was processed. Licenses and
|
||||
// renewals from the license service should update state on
|
||||
// |Set/UpdateLicense|. Offline licenses, when restored, should update license
|
||||
// state when |RestorePlaybackTimes| is called.
|
||||
if (license_state_update_deadline_ != 0 &&
|
||||
license_state_update_deadline_ < current_time) {
|
||||
LOGW("License state was not updated after a license was loaded/renewed");
|
||||
UpdateLicenseState(current_time);
|
||||
}
|
||||
|
||||
// If we have passed the grace period, the expiration will update.
|
||||
if (policy_timers_->HasPassedGracePeriod(current_time)) {
|
||||
NotifyExpirationUpdate(current_time);
|
||||
@@ -159,12 +172,13 @@ void PolicyEngine::OnTimerEvent() {
|
||||
}
|
||||
|
||||
void PolicyEngine::SetLicense(const License& license,
|
||||
bool supports_core_messages) {
|
||||
bool supports_core_messages,
|
||||
bool defer_license_state_update) {
|
||||
if (supports_core_messages) policy_timers_.reset(new PolicyTimersV16());
|
||||
license_id_.CopyFrom(license.id());
|
||||
license_keys_->SetFromLicense(license);
|
||||
policy_timers_->SetLicense(license);
|
||||
UpdateLicense(license);
|
||||
UpdateLicense(license, defer_license_state_update);
|
||||
}
|
||||
|
||||
void PolicyEngine::SetEntitledLicenseKeys(
|
||||
@@ -180,10 +194,11 @@ void PolicyEngine::SetLicenseForRelease(const License& license,
|
||||
// Expire any old keys.
|
||||
NotifyKeysChange(kKeyStatusExpired);
|
||||
policy_timers_->SetLicense(license);
|
||||
UpdateLicense(license);
|
||||
UpdateLicense(license, false);
|
||||
}
|
||||
|
||||
void PolicyEngine::UpdateLicense(const License& license) {
|
||||
void PolicyEngine::UpdateLicense(const License& license,
|
||||
bool defer_license_state_update) {
|
||||
if (!license.has_policy()) return;
|
||||
|
||||
if (kLicenseStateExpired == license_state_) {
|
||||
@@ -204,8 +219,15 @@ void PolicyEngine::UpdateLicense(const License& license) {
|
||||
|
||||
const int64_t current_time = GetCurrentTime();
|
||||
policy_timers_->UpdateLicense(current_time, license);
|
||||
if (defer_license_state_update)
|
||||
license_state_update_deadline_ = current_time + kLicenseStateUpdateDelay;
|
||||
else
|
||||
UpdateLicenseState(current_time);
|
||||
}
|
||||
|
||||
void PolicyEngine::UpdateLicenseState(int64_t current_time) {
|
||||
// Update time information
|
||||
license_state_update_deadline_ = 0;
|
||||
if (!policy_timers_->get_policy().can_play() ||
|
||||
policy_timers_->HasLicenseOrRentalOrPlaybackDurationExpired(
|
||||
current_time)) {
|
||||
@@ -349,7 +371,7 @@ void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time,
|
||||
last_playback_time,
|
||||
grace_period_end_time);
|
||||
|
||||
NotifyExpirationUpdate(current_time);
|
||||
UpdateLicenseState(current_time);
|
||||
}
|
||||
|
||||
void PolicyEngine::UpdateRenewalRequest(int64_t current_time) {
|
||||
|
||||
@@ -238,7 +238,7 @@ TEST_F(PolicyEngineConstraintsTest, IsPermissiveWithoutAResolution) {
|
||||
.WillRepeatedly(DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT),
|
||||
Return(GET_HDCP_CAPABILITY_FAILED)));
|
||||
|
||||
policy_engine_->SetLicense(license_, false);
|
||||
policy_engine_->SetLicense(license_, false, false);
|
||||
policy_engine_->OnTimerEvent();
|
||||
|
||||
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId1));
|
||||
@@ -270,7 +270,7 @@ TEST_F(PolicyEngineConstraintsTest, HandlesResolutionsBasedOnConstraints) {
|
||||
.WillRepeatedly(
|
||||
DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), Return(NO_ERROR)));
|
||||
|
||||
policy_engine_->SetLicense(license_, false);
|
||||
policy_engine_->SetLicense(license_, false, false);
|
||||
policy_engine_->NotifyResolution(1, kTargetRes1);
|
||||
policy_engine_->OnTimerEvent();
|
||||
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId1));
|
||||
@@ -334,7 +334,7 @@ TEST_F(PolicyEngineConstraintsTest,
|
||||
}
|
||||
|
||||
policy_engine_->NotifyResolution(1, kTargetRes1);
|
||||
policy_engine_->SetLicense(license_, false);
|
||||
policy_engine_->SetLicense(license_, false, false);
|
||||
policy_engine_->OnTimerEvent();
|
||||
policy_engine_->OnTimerEvent();
|
||||
policy_engine_->OnTimerEvent();
|
||||
@@ -367,7 +367,7 @@ TEST_F(PolicyEngineConstraintsTest, HandlesConstraintOverridingHdcp) {
|
||||
EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _))
|
||||
.WillRepeatedly(DoAll(SetArgPointee<0>(HDCP_V2), Return(NO_ERROR)));
|
||||
|
||||
policy_engine_->SetLicense(license_, false);
|
||||
policy_engine_->SetLicense(license_, false, false);
|
||||
policy_engine_->NotifyResolution(1, kTargetRes1);
|
||||
policy_engine_->OnTimerEvent();
|
||||
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId1));
|
||||
@@ -409,7 +409,7 @@ TEST_F(PolicyEngineConstraintsTest, HandlesNoHdcp) {
|
||||
EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _))
|
||||
.WillRepeatedly(DoAll(SetArgPointee<0>(HDCP_NONE), Return(NO_ERROR)));
|
||||
|
||||
policy_engine_->SetLicense(license_, false);
|
||||
policy_engine_->SetLicense(license_, false, false);
|
||||
|
||||
policy_engine_->NotifyResolution(1, kTargetRes1);
|
||||
policy_engine_->OnTimerEvent();
|
||||
@@ -453,7 +453,7 @@ TEST_F(PolicyEngineConstraintsTest, UsesDefaultHdcpWhenResolutionNotSet) {
|
||||
.WillRepeatedly(DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT),
|
||||
Return(GET_HDCP_CAPABILITY_FAILED)));
|
||||
|
||||
policy_engine_->SetLicense(license_, false);
|
||||
policy_engine_->SetLicense(license_, false, false);
|
||||
policy_engine_->OnTimerEvent();
|
||||
EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId1));
|
||||
EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId2));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user