Files
android/libwvdrmengine/cdm/core/src/policy_engine.cpp
Rahul Frias 8543b4c903 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
2020-09-22 15:36:35 -07:00

432 lines
14 KiB
C++

// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "policy_engine.h"
#include <string>
#include "clock.h"
#include "log.h"
#include "policy_timers_v15.h"
#include "policy_timers_v16.h"
#include "properties.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
using video_widevine::License;
namespace {
const int kCdmPolicyTimerDurationSeconds = 1;
const int kClockSkewDelta = 5; // seconds
const int64_t kLicenseStateUpdateDelay = 20; // seconds
} // namespace
namespace wvcdm {
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),
license_keys_(new LicenseKeys(crypto_session->GetSecurityLevel())),
policy_timers_(new PolicyTimersV15),
clock_(new Clock) {
InitDevice(crypto_session);
}
PolicyEngine::~PolicyEngine() {}
bool PolicyEngine::CanDecryptContent(const KeyId& key_id) {
if (license_keys_->IsContentKey(key_id)) {
return license_keys_->CanDecryptContent(key_id);
} else {
LOGE("Provided content key is not in license: key_id = %s",
b2a_hex(key_id).c_str());
return false;
}
}
CdmKeyStatus PolicyEngine::GetKeyStatus(const KeyId& key_id) {
return license_keys_->GetKeyStatus(key_id);
}
void PolicyEngine::InitDevice(CryptoSession* crypto_session) {
current_resolution_ = HDCP_UNSPECIFIED_VIDEO_RESOLUTION;
next_device_check_ = 0;
crypto_session_ = crypto_session;
}
void PolicyEngine::SetDeviceResolution(uint32_t width, uint32_t height) {
current_resolution_ = width * height;
CheckDeviceHdcpStatus();
}
void PolicyEngine::CheckDeviceHdcpStatusOnTimer(int64_t current_time) {
if (current_time >= next_device_check_) {
CheckDeviceHdcpStatus();
next_device_check_ = current_time + HDCP_DEVICE_CHECK_INTERVAL;
}
}
void PolicyEngine::CheckDeviceHdcpStatus() {
if (!license_keys_->Empty()) {
CryptoSession::HdcpCapability current_hdcp_level;
CryptoSession::HdcpCapability ignored;
CdmResponseType status =
crypto_session_->GetHdcpCapabilities(&current_hdcp_level, &ignored);
if (status != NO_ERROR) {
current_hdcp_level = HDCP_NONE;
}
license_keys_->ApplyConstraints(current_resolution_, current_hdcp_level);
}
}
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);
}
// License expiration trumps all.
if (policy_timers_->HasLicenseOrRentalOrPlaybackDurationExpired(
current_time) &&
license_state_ != kLicenseStateExpired) {
license_state_ = kLicenseStateExpired;
NotifyKeysChange(kKeyStatusExpired);
return;
}
// Check device conditions that affect playability (HDCP, resolution)
CheckDeviceHdcpStatusOnTimer(current_time);
bool renewal_needed = false;
// Test to determine if renewal should be attempted.
switch (license_state_) {
case kLicenseStateCanPlay: {
if (policy_timers_->HasRenewalDelayExpired(current_time)) {
renewal_needed = true;
}
// HDCP may change, so force a check.
NotifyKeysChange(kKeyStatusUsable);
break;
}
case kLicenseStateNeedRenewal: {
renewal_needed = true;
break;
}
case kLicenseStateWaitingLicenseUpdate: {
if (policy_timers_->HasRenewalRetryIntervalExpired(current_time)) {
renewal_needed = true;
}
break;
}
case kLicenseStatePending: {
if (!policy_timers_->IsLicenseForFuture(current_time)) {
license_state_ = kLicenseStateCanPlay;
NotifyKeysChange(kKeyStatusUsable);
}
break;
}
case kLicenseStateInitial:
case kLicenseStateExpired: {
break;
}
default: {
license_state_ = kLicenseStateExpired;
NotifyKeysChange(kKeyStatusInternalError);
break;
}
}
if (renewal_needed) {
UpdateRenewalRequest(current_time);
if (event_listener_) event_listener_->OnSessionRenewalNeeded(session_id_);
}
}
void PolicyEngine::SetLicense(const License& license,
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, defer_license_state_update);
}
void PolicyEngine::SetEntitledLicenseKeys(
const std::vector<WidevinePsshData_EntitledKey>& entitled_keys) {
license_keys_->SetEntitledKeys(entitled_keys);
}
void PolicyEngine::SetLicenseForRelease(const License& license,
bool supports_core_messages) {
if (supports_core_messages) policy_timers_.reset(new PolicyTimersV16());
license_id_.CopyFrom(license.id());
// Expire any old keys.
NotifyKeysChange(kKeyStatusExpired);
policy_timers_->SetLicense(license);
UpdateLicense(license, false);
}
void PolicyEngine::UpdateLicense(const License& license,
bool defer_license_state_update) {
if (!license.has_policy()) return;
if (kLicenseStateExpired == license_state_) {
LOGD("Updating an expired license");
}
// some basic license validation
// license start time needs to be specified in the initial response
if (!license.has_license_start_time()) return;
// if renewal, discard license if version has not been updated
if (license_state_ != kLicenseStateInitial) {
if (license.id().version() > license_id_.version())
license_id_.CopyFrom(license.id());
else
return;
}
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)) {
license_state_ = kLicenseStateExpired;
NotifyKeysChange(kKeyStatusExpired);
return;
}
// Update state
if (!policy_timers_->IsLicenseForFuture(current_time)) {
license_state_ = kLicenseStateCanPlay;
NotifyKeysChange(kKeyStatusUsable);
} else {
license_state_ = kLicenseStatePending;
NotifyKeysChange(kKeyStatusUsableInFuture);
}
NotifyExpirationUpdate(current_time);
}
bool PolicyEngine::BeginDecryption() {
const int64_t current_time = GetCurrentTime();
if (!policy_timers_->HasPlaybackStarted(current_time)) {
switch (license_state_) {
case kLicenseStateCanPlay:
case kLicenseStateNeedRenewal:
case kLicenseStateWaitingLicenseUpdate:
policy_timers_->BeginDecryption(current_time);
if (policy_timers_->get_policy().renew_with_usage()) {
license_state_ = kLicenseStateNeedRenewal;
}
NotifyExpirationUpdate(current_time);
return true;
case kLicenseStateInitial:
case kLicenseStatePending:
case kLicenseStateExpired:
default:
return false;
}
} else {
return true;
}
}
void PolicyEngine::DecryptionEvent() {
policy_timers_->DecryptionEvent(GetCurrentTime());
}
void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) {
SetDeviceResolution(width, height);
}
void PolicyEngine::NotifySessionExpiration() {
license_state_ = kLicenseStateExpired;
NotifyKeysChange(kKeyStatusExpired);
}
CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) {
const int64_t current_time = GetCurrentTime();
if (license_state_ == kLicenseStateInitial) {
query_response->clear();
return NO_ERROR;
}
(*query_response)[QUERY_KEY_LICENSE_TYPE] =
license_id_.type() == video_widevine::STREAMING ? QUERY_VALUE_STREAMING
: QUERY_VALUE_OFFLINE;
(*query_response)[QUERY_KEY_PLAY_ALLOWED] =
policy_timers_->get_policy().can_play() ? QUERY_VALUE_TRUE
: QUERY_VALUE_FALSE;
(*query_response)[QUERY_KEY_PERSIST_ALLOWED] =
policy_timers_->get_policy().can_persist() ? QUERY_VALUE_TRUE
: QUERY_VALUE_FALSE;
(*query_response)[QUERY_KEY_RENEW_ALLOWED] =
policy_timers_->get_policy().can_renew() ? QUERY_VALUE_TRUE
: QUERY_VALUE_FALSE;
(*query_response)[QUERY_KEY_LICENSE_DURATION_REMAINING] = std::to_string(
policy_timers_->GetLicenseOrRentalDurationRemaining(current_time));
(*query_response)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = std::to_string(
policy_timers_->GetPlaybackDurationRemaining(current_time));
(*query_response)[QUERY_KEY_RENEWAL_SERVER_URL] =
policy_timers_->get_policy().renewal_server_url();
return NO_ERROR;
}
CdmResponseType PolicyEngine::QueryKeyAllowedUsage(
const KeyId& key_id, CdmKeyAllowedUsage* key_usage) {
if (key_usage == nullptr) {
LOGE("Output parameter |key_usage| not provided");
return PARAMETER_NULL;
}
if (license_keys_->GetAllowedUsage(key_id, key_usage)) {
return NO_ERROR;
}
return KEY_NOT_FOUND_1;
}
bool PolicyEngine::CanUseKeyForSecurityLevel(const KeyId& key_id) {
return license_keys_->MeetsSecurityLevelConstraints(key_id);
}
bool PolicyEngine::GetSecondsSinceStarted(int64_t* seconds_since_started) {
return policy_timers_->GetSecondsSinceStarted(GetCurrentTime(),
seconds_since_started);
}
bool PolicyEngine::GetSecondsSinceLastPlayed(
int64_t* seconds_since_last_played) {
return policy_timers_->GetSecondsSinceLastPlayed(GetCurrentTime(),
seconds_since_last_played);
}
int64_t PolicyEngine::GetLicenseOrRentalOrPlaybackDurationRemaining() {
return policy_timers_->GetLicenseOrRentalOrPlaybackDurationRemaining(
GetCurrentTime());
}
bool PolicyEngine::CanRenew() const {
return policy_timers_->get_policy().can_renew();
}
int64_t PolicyEngine::GetPlaybackStartTime() {
return policy_timers_->GetPlaybackStartTime();
}
int64_t PolicyEngine::GetLastPlaybackTime() {
return policy_timers_->GetLastPlaybackTime();
}
int64_t PolicyEngine::GetGracePeriodEndTime() {
return policy_timers_->GetGracePeriodEndTime();
}
void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time,
int64_t last_playback_time,
int64_t grace_period_end_time) {
const int64_t current_time = GetCurrentTime();
policy_timers_->RestorePlaybackTimes(current_time, playback_start_time,
last_playback_time,
grace_period_end_time);
UpdateLicenseState(current_time);
}
void PolicyEngine::UpdateRenewalRequest(int64_t current_time) {
license_state_ = kLicenseStateWaitingLicenseUpdate;
policy_timers_->UpdateRenewalRequest(current_time);
}
bool PolicyEngine::HasLicenseOrRentalOrPlaybackDurationExpired(
int64_t current_time) {
return policy_timers_->HasLicenseOrRentalOrPlaybackDurationExpired(
current_time);
}
// Apply a key status to the current keys.
// If this represents a new key status, perform a notification callback.
// NOTE: if the new status is kKeyStatusUsable, the HDCP check may result in an
// override to kKeyStatusOutputNotAllowed.
void PolicyEngine::NotifyKeysChange(CdmKeyStatus new_status) {
bool keys_changed;
bool has_new_usable_key = false;
if (new_status == kKeyStatusUsable) {
CheckDeviceHdcpStatus();
}
keys_changed =
license_keys_->ApplyStatusChange(new_status, &has_new_usable_key);
if (event_listener_ && keys_changed) {
CdmKeyStatusMap content_keys;
license_keys_->ExtractKeyStatuses(&content_keys);
event_listener_->OnSessionKeysChange(session_id_, content_keys,
has_new_usable_key);
}
}
void PolicyEngine::NotifyExpirationUpdate(int64_t current_time) {
int64_t expiry_time;
if (policy_timers_->UpdateExpirationTime(current_time, &expiry_time)) {
if (event_listener_)
event_listener_->OnExpirationUpdate(session_id_, expiry_time);
}
}
int64_t PolicyEngine::GetCurrentTime() {
int64_t current_time = clock_->GetCurrentTime();
if (current_time + kClockSkewDelta < last_recorded_current_time_)
current_time = last_recorded_current_time_;
else
last_recorded_current_time_ = current_time;
return current_time;
}
void PolicyEngine::set_clock(Clock* clock) { clock_.reset(clock); }
void PolicyEngine::SetSecurityLevelForTest(CdmSecurityLevel security_level) {
license_keys_->SetSecurityLevelForTest(security_level);
}
} // namespace wvcdm