[ Merge of http://go/wvgerrit/189590 ] [ Cherry-pick of http://ag/26541307 ] The CDM session shares its CryptoSession instance with a few additional member objects (CdmLicense and PolicyEngine). When the CDM session's crypto session is reset, it must also reset the CdmLicense and PolicyEngine otherwise, a potential stale pointer reference may occur. Test: request_license_test on Oriole Test: run_x86_64_tests Bug: 311239278 Change-Id: Ie175513ae652dcd96e12e5e1def574a8a56d5863
447 lines
14 KiB
C++
447 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 "policy_timers_v18.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
|
|
const uint32_t kMinOemCryptoApiVersionSupportsRenewalDelayBase = 18;
|
|
} // namespace
|
|
|
|
namespace wvcdm {
|
|
|
|
PolicyEngine::PolicyEngine(CdmSessionId session_id,
|
|
WvCdmEventListener* event_listener,
|
|
CryptoSession* crypto_session)
|
|
: session_id_(std::move(session_id)),
|
|
event_listener_(event_listener),
|
|
license_keys_(new LicenseKeys(crypto_session->GetSecurityLevel())),
|
|
clock_(new wvutil::Clock()) {
|
|
uint32_t version = 0;
|
|
if (crypto_session->GetApiVersion(&version)) {
|
|
if (version >= kMinOemCryptoApiVersionSupportsRenewalDelayBase) {
|
|
policy_timers_.reset(new PolicyTimersV18());
|
|
}
|
|
} else {
|
|
LOGW("Failed to get API version: session_id = %s", IdToString(session_id));
|
|
}
|
|
|
|
if (!policy_timers_) {
|
|
// Use V16 policy timers if getting version failed.
|
|
policy_timers_.reset(new PolicyTimersV16());
|
|
}
|
|
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(¤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 (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) &&
|
|
policy_timers_->get_policy().can_renew()) {
|
|
renewal_needed = true;
|
|
}
|
|
// HDCP may change, so force a check.
|
|
NotifyKeysChange(kKeyStatusUsable);
|
|
break;
|
|
}
|
|
|
|
case kLicenseStateNeedRenewal: {
|
|
if (policy_timers_->get_policy().can_renew()) {
|
|
renewal_needed = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case kLicenseStateWaitingLicenseUpdate: {
|
|
if (policy_timers_->HasRenewalRetryIntervalExpired(current_time) &&
|
|
policy_timers_->get_policy().can_renew()) {
|
|
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() &&
|
|
policy_timers_->get_policy().can_renew()) {
|
|
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
|