[ Merge of http://go/wvgerrit/124063 ] LicenseDurationRemaining used to indicate the minimum of rental or license duration till OEMCrypto v16. OEMCrypto v16 onwards it began reporting rental duration alone. This is confusing for app developers and content partners. Keeping LicenseDurationRemaining as apps may depend on it but adding RentalDurationRemaining for clarity. Bug: 186838303 Test: WV unit/integration tests, WvCdmRequestLicenseTest.QueryKeyStatus Change-Id: I6c507150a0945ee36716b4da189f5741b092c0ec
434 lines
14 KiB
C++
434 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 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(¤t_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_RENTAL_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
|