Licenses could be renewed uptil the point of expiry. After that point we expected that the session would have to be closed and a new one opened with a new license loaded. Clank requested that we support renewal of sessions past expiry. In addition, the error returned on decryption, if OEMCrypto determines that the KCB duration has expired, is NEED_KEY rather than KEY_ERROR. Merge of https://widevine-internal-review.googlesource.com/#/c/8240 from the widevine cdm repo. b/11390539 Change-Id: I023320f3f25514cd07b368701a92100429ce1c04
292 lines
8.4 KiB
C++
292 lines
8.4 KiB
C++
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
|
|
#include "policy_engine.h"
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "log.h"
|
|
#include "properties.h"
|
|
#include "string_conversions.h"
|
|
#include "clock.h"
|
|
#include "wv_cdm_constants.h"
|
|
|
|
namespace wvcdm {
|
|
|
|
PolicyEngine::PolicyEngine() {
|
|
Init(new Clock());
|
|
}
|
|
|
|
PolicyEngine::PolicyEngine(Clock* clock) {
|
|
Init(clock);
|
|
}
|
|
|
|
PolicyEngine::~PolicyEngine() {
|
|
if (clock_)
|
|
delete clock_;
|
|
}
|
|
|
|
void PolicyEngine::Init(Clock* clock) {
|
|
license_state_ = kLicenseStateInitial;
|
|
can_decrypt_ = false;
|
|
license_start_time_ = 0;
|
|
license_received_time_ = 0;
|
|
playback_start_time_ = 0;
|
|
next_renewal_time_ = 0;
|
|
policy_max_duration_seconds_ = 0;
|
|
clock_ = clock;
|
|
}
|
|
|
|
void PolicyEngine::OnTimerEvent(bool& event_occured, CdmEventType& event) {
|
|
event_occured = false;
|
|
int64_t current_time = clock_->GetCurrentTime();
|
|
|
|
// License expiration trumps all.
|
|
if ((IsLicenseDurationExpired(current_time) ||
|
|
IsPlaybackDurationExpired(current_time)) &&
|
|
license_state_ != kLicenseStateExpired) {
|
|
license_state_ = kLicenseStateExpired;
|
|
can_decrypt_ = false;
|
|
event = LICENSE_EXPIRED_EVENT;
|
|
event_occured = true;
|
|
return;
|
|
}
|
|
|
|
bool renewal_needed = false;
|
|
|
|
// Test to determine if renewal should be attempted.
|
|
switch (license_state_) {
|
|
case kLicenseStateInitialPendingUsage:
|
|
case kLicenseStateCanPlay: {
|
|
if (IsRenewalDelayExpired(current_time))
|
|
renewal_needed = true;
|
|
break;
|
|
}
|
|
|
|
case kLicenseStateNeedRenewal: {
|
|
renewal_needed = true;
|
|
break;
|
|
}
|
|
|
|
case kLicenseStateWaitingLicenseUpdate: {
|
|
if (IsRenewalRetryIntervalExpired(current_time))
|
|
renewal_needed = true;
|
|
break;
|
|
}
|
|
|
|
case kLicenseStateInitial:
|
|
case kLicenseStateExpired: {
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
license_state_ = kLicenseStateExpired;
|
|
can_decrypt_ = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (renewal_needed) {
|
|
UpdateRenewalRequest(current_time);
|
|
event = LICENSE_RENEWAL_NEEDED_EVENT;
|
|
event_occured = true;
|
|
}
|
|
}
|
|
|
|
void PolicyEngine::SetLicense(
|
|
const video_widevine_server::sdk::License& license) {
|
|
license_id_.Clear();
|
|
license_id_.CopyFrom(license.id());
|
|
policy_.Clear();
|
|
UpdateLicense(license);
|
|
}
|
|
|
|
void PolicyEngine::UpdateLicense(
|
|
const video_widevine_server::sdk::License& license) {
|
|
if (!license.has_policy())
|
|
return;
|
|
|
|
policy_.MergeFrom(license.policy());
|
|
|
|
if (!policy_.can_play()) {
|
|
license_state_ = kLicenseStateExpired;
|
|
return;
|
|
}
|
|
|
|
// some basic license validation
|
|
if (license_state_ == kLicenseStateInitial) {
|
|
// license start time needs to be present in the initial response
|
|
if (!license.has_license_start_time())
|
|
return;
|
|
}
|
|
else {
|
|
// TODO(edwingwong, rfrias): Check back with Thomas and see if
|
|
// we need to enforce that all duration windows are absent if
|
|
// license_start_time is not present. This is a TBD.
|
|
|
|
// if renewal, discard license if version has not been updated
|
|
if (license.id().version() > license_id_.version())
|
|
license_id_.CopyFrom(license.id());
|
|
else
|
|
return;
|
|
}
|
|
|
|
// Update time information
|
|
int64_t current_time = clock_->GetCurrentTime();
|
|
// TODO(edwingwong, rfrias): Check back with Thomas and see if
|
|
// we need to enforce that all duration windows are absent if
|
|
// license_start_time is not present. This is a TBD.
|
|
if (license.has_license_start_time())
|
|
license_start_time_ = license.license_start_time();
|
|
license_received_time_ = current_time;
|
|
next_renewal_time_ = current_time +
|
|
policy_.renewal_delay_seconds();
|
|
|
|
// Calculate policy_max_duration_seconds_. policy_max_duration_seconds_
|
|
// will be set to the minimum of the following policies :
|
|
// rental_duration_seconds and license_duration_seconds.
|
|
// The value is used to determine when the license expires.
|
|
policy_max_duration_seconds_ = 0;
|
|
|
|
if (policy_.has_rental_duration_seconds())
|
|
policy_max_duration_seconds_ = policy_.rental_duration_seconds();
|
|
|
|
if ((policy_.license_duration_seconds() > 0) &&
|
|
((policy_.license_duration_seconds() <
|
|
policy_max_duration_seconds_) ||
|
|
policy_max_duration_seconds_ == 0)) {
|
|
policy_max_duration_seconds_ = policy_.license_duration_seconds();
|
|
}
|
|
|
|
if (Properties::begin_license_usage_when_received())
|
|
playback_start_time_ = current_time;
|
|
|
|
// Update state
|
|
if (Properties::begin_license_usage_when_received()) {
|
|
if (policy_.renew_with_usage()) {
|
|
license_state_ = kLicenseStateNeedRenewal;
|
|
}
|
|
else {
|
|
license_state_ = kLicenseStateCanPlay;
|
|
can_decrypt_ = true;
|
|
}
|
|
}
|
|
else {
|
|
if (license_state_ == kLicenseStateInitial) {
|
|
license_state_ = kLicenseStateInitialPendingUsage;
|
|
}
|
|
else {
|
|
license_state_ = kLicenseStateCanPlay;
|
|
can_decrypt_ = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PolicyEngine::BeginDecryption() {
|
|
if ((playback_start_time_ == 0) &&
|
|
(!Properties::begin_license_usage_when_received())) {
|
|
switch (license_state_) {
|
|
case kLicenseStateInitialPendingUsage:
|
|
case kLicenseStateNeedRenewal:
|
|
case kLicenseStateWaitingLicenseUpdate:
|
|
playback_start_time_ = clock_->GetCurrentTime();
|
|
|
|
if (policy_.renew_with_usage()) {
|
|
license_state_ = kLicenseStateNeedRenewal;
|
|
}
|
|
else {
|
|
license_state_ = kLicenseStateCanPlay;
|
|
can_decrypt_ = true;
|
|
}
|
|
break;
|
|
case kLicenseStateCanPlay:
|
|
case kLicenseStateInitial:
|
|
case kLicenseStateExpired:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
CdmResponseType PolicyEngine::Query(CdmQueryMap* key_info) {
|
|
std::stringstream ss;
|
|
int64_t current_time = clock_->GetCurrentTime();
|
|
|
|
if (license_state_ == kLicenseStateInitial)
|
|
return UNKNOWN_ERROR;
|
|
|
|
(*key_info)[QUERY_KEY_LICENSE_TYPE] =
|
|
license_id_.type() == video_widevine_server::sdk::STREAMING ?
|
|
QUERY_VALUE_STREAMING : QUERY_VALUE_OFFLINE;
|
|
(*key_info)[QUERY_KEY_PLAY_ALLOWED] = policy_.can_play() ?
|
|
QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
|
(*key_info)[QUERY_KEY_PERSIST_ALLOWED] = policy_.can_persist() ?
|
|
QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
|
(*key_info)[QUERY_KEY_RENEW_ALLOWED] = policy_.can_renew() ?
|
|
QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
|
int64_t remaining_time = policy_max_duration_seconds_ +
|
|
license_received_time_ - current_time;
|
|
if (remaining_time < 0)
|
|
remaining_time = 0;
|
|
ss << remaining_time;
|
|
(*key_info)[QUERY_KEY_LICENSE_DURATION_REMAINING] = ss.str();
|
|
remaining_time = policy_.playback_duration_seconds() + playback_start_time_ -
|
|
current_time;
|
|
if (remaining_time < 0)
|
|
remaining_time = 0;
|
|
ss << remaining_time;
|
|
(*key_info)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str();
|
|
(*key_info)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url();
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void PolicyEngine::UpdateRenewalRequest(int64_t current_time) {
|
|
license_state_ = kLicenseStateWaitingLicenseUpdate;
|
|
next_renewal_time_ = current_time + policy_.renewal_retry_interval_seconds();
|
|
}
|
|
|
|
// For the policy time fields checked in the following methods, a value of 0
|
|
// indicates that there is no limit to the duration. These methods
|
|
// will always return false if the value is 0.
|
|
bool PolicyEngine::IsLicenseDurationExpired(int64_t current_time) {
|
|
return policy_max_duration_seconds_ &&
|
|
license_received_time_ + policy_max_duration_seconds_ <=
|
|
current_time;
|
|
}
|
|
|
|
bool PolicyEngine::IsPlaybackDurationExpired(int64_t current_time) {
|
|
return (policy_.playback_duration_seconds() > 0) &&
|
|
playback_start_time_ &&
|
|
playback_start_time_ + policy_.playback_duration_seconds() <=
|
|
current_time;
|
|
}
|
|
|
|
bool PolicyEngine::IsRenewalDelayExpired(int64_t current_time) {
|
|
return policy_.can_renew() &&
|
|
(policy_.renewal_delay_seconds() > 0) &&
|
|
license_received_time_ + policy_.renewal_delay_seconds() <=
|
|
current_time;
|
|
}
|
|
|
|
// TODO(jfore, edwinwong, rfrias): This field is in flux and currently
|
|
// not implemented. Will address after possible updates from Thomas.
|
|
bool PolicyEngine::IsRenewalRecoveryDurationExpired(
|
|
int64_t current_time) {
|
|
return (policy_.renewal_recovery_duration_seconds() > 0) &&
|
|
license_received_time_ + policy_.renewal_recovery_duration_seconds() <=
|
|
current_time;
|
|
}
|
|
|
|
bool PolicyEngine::IsRenewalRetryIntervalExpired(
|
|
int64_t current_time) {
|
|
return policy_.can_renew() &&
|
|
(policy_.renewal_retry_interval_seconds() > 0) &&
|
|
next_renewal_time_ <= current_time;
|
|
}
|
|
|
|
} // wvcdm
|