// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine Master // License Agreement. #include "policy_engine.h" #include #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 } // namespace namespace wvcdm { PolicyEngine::PolicyEngine(CdmSessionId session_id, WvCdmEventListener* event_listener, CryptoSession* crypto_session) : license_state_(kLicenseStateInitial), 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(); // 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) { if (supports_core_messages) policy_timers_.reset(new PolicyTimersV16()); license_id_.CopyFrom(license.id()); license_keys_->SetFromLicense(license); policy_timers_->SetLicense(license); UpdateLicense(license); } void PolicyEngine::SetEntitledLicenseKeys( const std::vector& 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); } void PolicyEngine::UpdateLicense(const License& license) { 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); // Update time information 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_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); NotifyExpirationUpdate(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