Files
android/libwvdrmengine/cdm/core/src/policy_engine.cpp
Alex Dale 6cbd75bb6c Added support for additional HDCP levels.
[ Merge of http://go/wvgerrit/169450 ]

OEMCrypto v17 introduced several new HDCP levels that OEMCrypto may
report; however, the CDM never updated to support them.  The enum
values of the additional levels are no longer sequential with their
level of support (v1.1 is 7, and v2.1 is 3), this requires more
considerations when comparing the required HDCP levels (as specified
by the license) and current HDCP level supported by OEMCrypto.

The following rules were used:
1) HDCP_NONE is the absolute lowest level
2) HDCP_NO_DIGITAL_OUTPUT is the absolute highest level
3) HDCP_V1 is treated as equal to all V1.x levels
4) All other versions are based on their major-minor pairs

Bug: 269671291
Test: license_unittest
Test: policy_engine_constraints_unittest
Test: policy_engine_unittest
Test: GtsMediaTestCases
Change-Id: Ibecfcb981d7e019c68cb8e0c7286222253d18369
2023-03-30 23:33:25 -07:00

429 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_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)
: session_id_(session_id),
event_listener_(event_listener),
license_keys_(new LicenseKeys(crypto_session->GetSecurityLevel())),
policy_timers_(new PolicyTimersV16()),
clock_(new wvutil::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);
}
LOGE("Provided content key is not in license: key_id = %s",
wvutil::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 = HDCP_NONE;
CryptoSession::HdcpCapability ignored = HDCP_NONE;
const 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 (license_state_ != kLicenseStateInitial &&
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 defer_license_state_update) {
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) {
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 CdmResponseType(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 CdmResponseType(NO_ERROR);
}
CdmResponseType PolicyEngine::QueryKeyAllowedUsage(
const KeyId& key_id, CdmKeyAllowedUsage* key_usage) {
if (key_usage == nullptr) {
LOGE("Output parameter |key_usage| not provided");
return CdmResponseType(PARAMETER_NULL);
}
if (license_keys_->GetAllowedUsage(key_id, key_usage)) {
return CdmResponseType(NO_ERROR);
}
return CdmResponseType(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(wvutil::Clock* clock) { clock_.reset(clock); }
void PolicyEngine::SetSecurityLevelForTest(CdmSecurityLevel security_level) {
license_keys_->SetSecurityLevelForTest(security_level);
}
} // namespace wvcdm