These are a set of CLs merged from the wv cdm repo to the android repo. * Correct error logging Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/40000 ] In tests, we set the cipher list to avoid using insecure ciphers when connecting to the provisioning/license service. The result of setting the cipher list was being incorrectly validated. Bug: 64847919 * Move mips cache headers to clear_cache_function.h Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/39700 ] Since the clear_cache function has been moved away from the dynamic adapter, we need these conditional includes to be migrated as well for MIPS. * Comment out Level 3 debug call until merge Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/39761 ] This call was introduced in go/wvgerrit/34260/. Since the haystack tool in google3 still needs this merge, this should be commented out so the tool can still build until the merge has finished. * Add logging for MAC keys to mock Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/39740 ] Bug: 70637842 * Move external interfaces into level3.h + refactor Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/39673 ] As part of b/70523618, this CL moves interfaces that partners are responsible for in Level 3 to level3.h so they can be visible as part of the CDM release process. It also cleans up some of the names of the files and adds documentation. * Corrected close session logging level Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/39676 ] Bug: 69460963 * Remove Security Level Path Backward Compatibility Support Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/39505 ] From the android K release onwards certificates were stored in security level specific directories. If upgrading from previous releases persistent information needed to be moved to those directories. Since no device is likely to upgrade from J to Pi, comptibility support can be removed. Bug: 70160032 * Rename privacy_crypto_openssl To privacy_crypto_boringssl Author: John W. Bruce <juce@google.com> [ Merge of http://go/wvgerrit/37122 ] Now that we no longer support OpenSSL in the Shared Source CDM, the name of this file can be updated. Bug: 67907873 Test: build.py x86-64 Test: wv_ce_cdm_unittest Test: jenkins/linux_unit_tests * Remove Conditional Compilation from OpenSSL/BoringSSL Author: John W. Bruce <juce@google.com> [ Merge of http://go/wvgerrit/39460 ] This change removes the usages of conditional compilation to support both BoringSSL and OpenSSL, as well as to support multiple versions of the OpenSSL API. All code is now compiled against one of the two versions of BoringSSL in third_party/. Note that in some cases, the kit/ and legacy_kit/ versions of BoringSSL had different APIs, so when removing the OpenSSL version compatibility conditional compilation, sometimes the older branch was kept and sometimes the newer branch was kept. Bug: 67907873 Test: build.py x86-64 Test: wv_ce_cdm_unittest Test: jenkins/linux_unit_tests * Build CE & Jenkins CDMs With BoringSSL from third_party/ Author: John W. Bruce <juce@google.com> [ Merge of http://go/wvgerrit/37120 ] Up until now, integrators have been responsible for providing a compatible crypto library for use by the CE CDM. (either OpenSSL or BoringSSL) After this change, this decision will no longer be in their hands. The CE CDM build will always use the copy of BoringSSL in third_party/, which will be statically linked with our library with hidden visibility. This allows us to better control what crypto library we use and will prevent continuing problems with trying to support both OpenSSL and BoringSSL. Unfortunately, BoringSSL began using C++11 in mid-2017, and we can't support C++11 right now. Until we can, we need to use a C++11-free version of BoringSSL for libssl. The CDM itself will continue to use a recent BoringSSL, as it only needs libcrypto. But the unit tests that need libssl have to use the legacy version. Bug: 67907873 Test: build.py x86-64 Test: wv_ce_cdm_unittest Test: jenkins/linux_unit_tests * Modified RNG for Level3 to use more entropy Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/39220 ] Bug: 65165076 Modified seed generation to use an xor of clock_gettime and client-implemented code to supply random seeds to the RNG. Modified the RNG as well to use xoroshiro128+ instead of xorshift, since it uses more than one seed/state (which are 64-bit) and has higher "statistical quality". The default implementations for the seed generation use /dev/urandom. * Configure base path for Level3FileSystem Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/39506 ] This is in response to b/70354006. This change makes the Android Level3FileSystem use the existing properties method GetDevicesFilesBasePath for binderization. The same is done for the Linux implementation. * Add legacy_kit/ to BoringSSL Directory Author: John W. Bruce <juce@google.com> [ Merge of http://go/wvgerrit/38861 ] This adds a second copy of BoringSSL to the third_party/boringssl/ directory. This second copy is pinned to the last revision of BoringSSL not to require C++11 and is not updated by the UPDATE_BORINGSSL.sh script. This second copy will be used to provide libssl to the tests on devices that do not support C++11. Once we support C++11 in the CDM again, this weight should be removed and all targets should use the copy of BoringSSL in the kit/ directory. Bug: 67907873 * Use Shared Libraries for Unit Tests Author: John W. Bruce <juce@google.com> [ Merge of http://go/wvgerrit/38860 ] Some unit tests were using a statically-linked CDM instead of a dynamically-linked one. (Or, in one case, trying to link both ways into the same binary.) For now, we need to only link dynamically, so that the unit tests and the CDM can use different versions of BoringSSL. Long-term, we would like to test both kinds of linkage. (See b/69548115 for that.) Some unit tests were also using a dynamicaly-linked CDM that was named such that it appeared to be statically-linked. This patch renames some targets to make the linkage clearer. Bug: 67907873 * Change CDM_Backwards_Compatiblity_Tests to dedicated brances Author: Fred Gylys-Colwell <fredgc@google.com> [ Merge of http://go/wvgerrit/39003 ] The build scripts used by CDM_Backwards_Compatiblity_Tests now pull old versions of oemcrypto from the dedicated branches oemcrypto-v*, which [will eventually] contain old oemcrypto versions, that build with the current build system with a current boringssl version. bug: 67907873 * Fix spacing on level3 header Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/38760 ] * Correct Query status calls Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/38640 ] Bug: 70160032 * Refactoring to allow encryption of client ID Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/37460 ] The code has been restructured to allow encryption of client identification in provisioning requests. This will be enabled when server side changes have been made (b/69427217). * Additional information is included in the Client Identification portion of the provisioning request. * Client identification will be encrypted with a service certificate provided by the app/client. Platform changes to enable passing this to core are needed. If a service certificate is not provided, a default one associated with the production Keysmith will be used. * Switched APIs in CdmEngine to take a service certificate for provisioning rather than licensing. Service certificates for licensing are session based and passed as properties from platform code. Bug: 30737060 * Allow some CDM errors to be reported from multiple locations Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/38360 ] This creates some CdmResponseType errors which may be reused PARAMETER_NULL, NOT_INITIALIZED_ERROR, REINIT_ERROR. I have made changes to a few classes to report these errors. Will work on additional classes in a separate CL. Bug: 69864404 BUG: 71650075 Test: WV Unit/integration tests Change-Id: Icc048770d424ac537d11ff327cda2cb142da802d
504 lines
16 KiB
C++
504 lines
16 KiB
C++
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
|
|
#include "policy_engine.h"
|
|
|
|
#include <limits.h>
|
|
#include <sstream>
|
|
|
|
#include "clock.h"
|
|
#include "log.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
|
|
|
|
} // namespace
|
|
|
|
namespace wvcdm {
|
|
|
|
PolicyEngine::PolicyEngine(CdmSessionId session_id,
|
|
WvCdmEventListener* event_listener,
|
|
CryptoSession* crypto_session)
|
|
: license_state_(kLicenseStateInitial),
|
|
license_start_time_(0),
|
|
playback_start_time_(0),
|
|
last_playback_time_(0),
|
|
last_expiry_time_(0),
|
|
grace_period_end_time_(0),
|
|
last_expiry_time_set_(false),
|
|
was_expired_on_load_(false),
|
|
next_renewal_time_(0),
|
|
last_recorded_current_time_(0),
|
|
session_id_(session_id),
|
|
event_listener_(event_listener),
|
|
license_keys_(new LicenseKeys),
|
|
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("PolicyEngine::CanDecryptContent Key '%s' not in license.",
|
|
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;
|
|
if (!crypto_session_->GetHdcpCapabilities(¤t_hdcp_level, &ignored)) {
|
|
current_hdcp_level = HDCP_NONE;
|
|
}
|
|
license_keys_->ApplyConstraints(current_resolution_, current_hdcp_level);
|
|
}
|
|
}
|
|
|
|
void PolicyEngine::OnTimerEvent() {
|
|
last_recorded_current_time_ += kCdmPolicyTimerDurationSeconds;
|
|
int64_t current_time = GetCurrentTime();
|
|
|
|
// If we have passed the grace period, the expiration will update.
|
|
if (grace_period_end_time_ == 0 && HasPlaybackStarted(current_time)) {
|
|
grace_period_end_time_ = playback_start_time_;
|
|
NotifyExpirationUpdate(current_time);
|
|
}
|
|
|
|
// License expiration trumps all.
|
|
if (HasLicenseOrPlaybackDurationExpired(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 (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 (HasRenewalRetryIntervalExpired(current_time)) {
|
|
renewal_needed = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case kLicenseStatePending: {
|
|
if (current_time >= license_start_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) {
|
|
license_id_.Clear();
|
|
license_id_.CopyFrom(license.id());
|
|
policy_.Clear();
|
|
license_keys_->SetFromLicense(license);
|
|
UpdateLicense(license);
|
|
}
|
|
|
|
void PolicyEngine::UpdateLicenseKeys(const video_widevine::License& license) {
|
|
// Use the current policy and set the new keys.
|
|
video_widevine::License loadable = license;
|
|
loadable.mutable_policy()->CopyFrom(policy_);
|
|
license_keys_->SetFromLicense(loadable);
|
|
NotifyKeysChange(kKeyStatusUsable);
|
|
}
|
|
|
|
void PolicyEngine::SetLicenseForRelease(const License& license) {
|
|
license_id_.Clear();
|
|
license_id_.CopyFrom(license.id());
|
|
policy_.Clear();
|
|
|
|
// Expire any old keys.
|
|
NotifyKeysChange(kKeyStatusExpired);
|
|
UpdateLicense(license);
|
|
}
|
|
|
|
void PolicyEngine::UpdateLicense(const License& license) {
|
|
if (!license.has_policy()) return;
|
|
|
|
if (kLicenseStateExpired == license_state_) {
|
|
LOGD("PolicyEngine::UpdateLicense: updating an expired license");
|
|
}
|
|
|
|
policy_.MergeFrom(license.policy());
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Update time information
|
|
license_start_time_ = license.license_start_time();
|
|
next_renewal_time_ = license_start_time_ + policy_.renewal_delay_seconds();
|
|
|
|
int64_t current_time = GetCurrentTime();
|
|
if (!policy_.can_play() ||
|
|
HasLicenseOrPlaybackDurationExpired(current_time)) {
|
|
license_state_ = kLicenseStateExpired;
|
|
NotifyKeysChange(kKeyStatusExpired);
|
|
return;
|
|
}
|
|
|
|
// Update state
|
|
if (current_time >= license_start_time_) {
|
|
license_state_ = kLicenseStateCanPlay;
|
|
NotifyKeysChange(kKeyStatusUsable);
|
|
} else {
|
|
license_state_ = kLicenseStatePending;
|
|
NotifyKeysChange(kKeyStatusPending);
|
|
}
|
|
NotifyExpirationUpdate(current_time);
|
|
}
|
|
|
|
void PolicyEngine::BeginDecryption() {
|
|
if (playback_start_time_ == 0) {
|
|
switch (license_state_) {
|
|
case kLicenseStateCanPlay:
|
|
case kLicenseStateNeedRenewal:
|
|
case kLicenseStateWaitingLicenseUpdate:
|
|
playback_start_time_ = GetCurrentTime();
|
|
last_playback_time_ = playback_start_time_;
|
|
if (policy_.play_start_grace_period_seconds() == 0)
|
|
grace_period_end_time_ = playback_start_time_;
|
|
|
|
if (policy_.renew_with_usage()) {
|
|
license_state_ = kLicenseStateNeedRenewal;
|
|
}
|
|
NotifyExpirationUpdate(playback_start_time_);
|
|
break;
|
|
case kLicenseStateInitial:
|
|
case kLicenseStatePending:
|
|
case kLicenseStateExpired:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PolicyEngine::DecryptionEvent() {
|
|
last_playback_time_ = 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) {
|
|
std::stringstream ss;
|
|
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_.can_play() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
|
(*query_response)[QUERY_KEY_PERSIST_ALLOWED] =
|
|
policy_.can_persist() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
|
(*query_response)[QUERY_KEY_RENEW_ALLOWED] =
|
|
policy_.can_renew() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
|
ss << GetLicenseOrRentalDurationRemaining(current_time);
|
|
(*query_response)[QUERY_KEY_LICENSE_DURATION_REMAINING] = ss.str();
|
|
ss.str("");
|
|
ss << GetPlaybackDurationRemaining(current_time);
|
|
(*query_response)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str();
|
|
(*query_response)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url();
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType PolicyEngine::QueryKeyAllowedUsage(
|
|
const KeyId& key_id, CdmKeyAllowedUsage* key_usage) {
|
|
if (NULL == key_usage) {
|
|
LOGE("PolicyEngine::QueryKeyAllowedUsage: no key_usage provided");
|
|
return PARAMETER_NULL;
|
|
}
|
|
if (license_keys_->GetAllowedUsage(key_id, key_usage)) {
|
|
return NO_ERROR;
|
|
}
|
|
return KEY_NOT_FOUND_1;
|
|
}
|
|
|
|
bool PolicyEngine::GetSecondsSinceStarted(int64_t* seconds_since_started) {
|
|
if (playback_start_time_ == 0) return false;
|
|
|
|
*seconds_since_started = GetCurrentTime() - playback_start_time_;
|
|
return (*seconds_since_started >= 0) ? true : false;
|
|
}
|
|
|
|
bool PolicyEngine::GetSecondsSinceLastPlayed(
|
|
int64_t* seconds_since_last_played) {
|
|
if (last_playback_time_ == 0) return false;
|
|
|
|
*seconds_since_last_played = GetCurrentTime() - last_playback_time_;
|
|
return (*seconds_since_last_played >= 0) ? true : false;
|
|
}
|
|
|
|
int64_t PolicyEngine::GetLicenseOrPlaybackDurationRemaining() {
|
|
const int64_t current_time = GetCurrentTime();
|
|
const int64_t expiry_time =
|
|
GetExpiryTime(current_time,
|
|
/* ignore_soft_enforce_playback_duration */ false);
|
|
if (expiry_time == NEVER_EXPIRES) return LLONG_MAX;
|
|
if (expiry_time < current_time) return 0;
|
|
return expiry_time - current_time;
|
|
}
|
|
|
|
void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time,
|
|
int64_t last_playback_time,
|
|
int64_t grace_period_end_time) {
|
|
playback_start_time_ = (playback_start_time > 0) ? playback_start_time : 0;
|
|
last_playback_time_ = (last_playback_time > 0) ? last_playback_time : 0;
|
|
grace_period_end_time_ = grace_period_end_time;
|
|
|
|
if (policy_.play_start_grace_period_seconds() != 0) {
|
|
// If we are using grace period, we may need to override some of the values
|
|
// given to us by OEMCrypto. |grace_period_end_time| will be 0 if the grace
|
|
// period has not expired (effectively playback has not begun). Otherwise,
|
|
// |grace_period_end_time| contains the playback start time we should use.
|
|
playback_start_time_ = grace_period_end_time;
|
|
}
|
|
|
|
const int64_t current_time = GetCurrentTime();
|
|
const int64_t expiry_time =
|
|
GetExpiryTime(current_time,
|
|
/* ignore_soft_enforce_playback_duration */ true);
|
|
was_expired_on_load_ =
|
|
expiry_time != NEVER_EXPIRES && expiry_time < current_time;
|
|
|
|
NotifyExpirationUpdate(current_time);
|
|
}
|
|
|
|
void PolicyEngine::UpdateRenewalRequest(int64_t current_time) {
|
|
license_state_ = kLicenseStateWaitingLicenseUpdate;
|
|
next_renewal_time_ = current_time + policy_.renewal_retry_interval_seconds();
|
|
}
|
|
|
|
bool PolicyEngine::HasLicenseOrPlaybackDurationExpired(int64_t current_time) {
|
|
const int64_t expiry_time =
|
|
GetExpiryTime(current_time,
|
|
/* ignore_soft_enforce_playback_duration */ false);
|
|
return expiry_time != NEVER_EXPIRES && expiry_time <= current_time;
|
|
}
|
|
|
|
// For the policy time fields checked in the following methods, a value of 0
|
|
// indicates that there is no limit to the duration. If the fields are zero
|
|
// (including the hard limit) then these methods will return NEVER_EXPIRES.
|
|
|
|
int64_t PolicyEngine::GetHardLicenseExpiryTime() {
|
|
return policy_.license_duration_seconds() > 0
|
|
? license_start_time_ + policy_.license_duration_seconds()
|
|
: NEVER_EXPIRES;
|
|
}
|
|
|
|
int64_t PolicyEngine::GetRentalExpiryTime() {
|
|
const int64_t hard_limit = GetHardLicenseExpiryTime();
|
|
if (policy_.rental_duration_seconds() == 0) return hard_limit;
|
|
const int64_t expiry_time =
|
|
license_start_time_ + policy_.rental_duration_seconds();
|
|
if (hard_limit == NEVER_EXPIRES) return expiry_time;
|
|
return std::min(hard_limit, expiry_time);
|
|
}
|
|
|
|
int64_t PolicyEngine::GetExpiryTime(
|
|
int64_t current_time,
|
|
bool ignore_soft_enforce_playback_duration) {
|
|
if (!HasPlaybackStarted(current_time))
|
|
return GetRentalExpiryTime();
|
|
|
|
const int64_t hard_limit = GetHardLicenseExpiryTime();
|
|
if (policy_.playback_duration_seconds() == 0) return hard_limit;
|
|
if (!ignore_soft_enforce_playback_duration && !was_expired_on_load_ &&
|
|
policy_.soft_enforce_playback_duration()) {
|
|
return hard_limit;
|
|
}
|
|
const int64_t expiry_time =
|
|
playback_start_time_ + policy_.playback_duration_seconds();
|
|
|
|
if (hard_limit == NEVER_EXPIRES)
|
|
return expiry_time;
|
|
return std::min(hard_limit, expiry_time);
|
|
}
|
|
|
|
int64_t PolicyEngine::GetLicenseOrRentalDurationRemaining(
|
|
int64_t current_time) {
|
|
// This is only used in Query. This should return the time remaining on
|
|
// license_duration_seconds for streaming licenses and rental_duration_seconds
|
|
// for offline licenses.
|
|
if (HasLicenseOrPlaybackDurationExpired(current_time)) return 0;
|
|
const int64_t license_expiry_time = GetRentalExpiryTime();
|
|
if (license_expiry_time == NEVER_EXPIRES) return LLONG_MAX;
|
|
if (license_expiry_time < current_time) return 0;
|
|
const int64_t policy_license_duration = policy_.license_duration_seconds();
|
|
if (policy_license_duration == NEVER_EXPIRES)
|
|
return license_expiry_time - current_time;
|
|
return std::min(license_expiry_time - current_time, policy_license_duration);
|
|
}
|
|
|
|
int64_t PolicyEngine::GetPlaybackDurationRemaining(int64_t current_time) {
|
|
// This is only used in Query. This should return playback_duration_seconds,
|
|
// or the time remaining on it if playing.
|
|
const int64_t playback_duration = policy_.playback_duration_seconds();
|
|
if (playback_duration == 0) return LLONG_MAX;
|
|
if (playback_start_time_ == 0) return playback_duration;
|
|
|
|
const int64_t playback_expiry_time = playback_duration + playback_start_time_;
|
|
if (playback_expiry_time < current_time) return 0;
|
|
const int64_t policy_playback_duration = policy_.playback_duration_seconds();
|
|
return std::min(playback_expiry_time - current_time,
|
|
policy_playback_duration);
|
|
}
|
|
|
|
bool PolicyEngine::HasRenewalDelayExpired(int64_t current_time) {
|
|
return policy_.can_renew() && (policy_.renewal_delay_seconds() > 0) &&
|
|
license_start_time_ + policy_.renewal_delay_seconds() <= current_time;
|
|
}
|
|
|
|
bool PolicyEngine::HasRenewalRecoveryDurationExpired(int64_t current_time) {
|
|
// NOTE: Renewal Recovery Duration is currently not used.
|
|
return (policy_.renewal_recovery_duration_seconds() > 0) &&
|
|
license_start_time_ + policy_.renewal_recovery_duration_seconds() <=
|
|
current_time;
|
|
}
|
|
|
|
bool PolicyEngine::HasRenewalRetryIntervalExpired(int64_t current_time) {
|
|
return policy_.can_renew() &&
|
|
(policy_.renewal_retry_interval_seconds() > 0) &&
|
|
next_renewal_time_ <= 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) {
|
|
const int64_t expiry_time =
|
|
GetExpiryTime(current_time,
|
|
/* ignore_soft_enforce_playback_duration */ false);
|
|
if (!last_expiry_time_set_ || expiry_time != last_expiry_time_) {
|
|
last_expiry_time_ = expiry_time;
|
|
if (event_listener_)
|
|
event_listener_->OnExpirationUpdate(session_id_, expiry_time);
|
|
}
|
|
last_expiry_time_set_ = true;
|
|
}
|
|
|
|
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); }
|
|
|
|
} // namespace wvcdm
|