Files
android/libwvdrmengine/cdm/core/include/policy_engine.h
John W. Bruce b8e31487a4 Unified State-Changing API for LicenseKeyStatus
(This is a merge of http://go/wvgerrit/31040)

Because the Policy Engine was only consulting the result of the Max-Res
Decode check when it was in kLicenseStateCanPlay and not in other states
that imply kKeyStatusUsable, like kLicenseStateWaitingLicenseUpdate, the
Max-Res Decode results would not be honored during the interval between
requesting a renewal and receiving the result. (Or until the key
expired.) This was particularly problematic for keys with renewal delays
less than ten seconds long, which would freeze the Max-Res state before
it had a chance to update for the first time, effectively disabling
Max-Res Decode until renewal was received.

Fixing this required changing how the Policy Engine and the
LicenseKeyStatus objects communicate about the changing usability state
of the LicenseKeyStatus objects. Before, a call to ApplyConstraints()
might calculate a Max-Res failure, but this failure would be pending
until the Policy Engine deigned to call ApplyStatusChange() again.
Without a call to ApplyStatusChange(), it could pend forever. This put a
burden on the PolicyEngine to poll the LicenseKeys with redundant
ApplyStatusChange() calls using the same CdmKeyStatus that the keys were
already in, just in case Max-Res had changed since the last necessary
call to ApplyStatusChange().

If the Policy Engine got the timing of these calls wrong, it would
result in Max-Res results being ignored. (as in the linked bug) If it
ever polled with the wrong CdmKeyStatus, it would update the
LicenseKeys' status when it did not mean to. It would be preferable if
this polling were not needed, so that the Policy Engine couldn't get it
wrong.

This patch changes the API between these classes so that when Max-Res
fails, the state change can be reported immediately instead of pending
until ApplyStatusChange() is called, eliminating the need for polling.
All state changes to the LicenseKeyStatus objects go through a unified
ApplyStatusChange() method that can update the CdmKeyStatus, resolution,
and/or HDCP level and report any resulting usability changes
immediately. This patch updates the unit tests to exercise this new API
instead of the old API.

Previously, the linked bug slipped past our unit tests because we only
test unrenewable, streaming licenses against Max-Res. This patch adds
several more variants to
policy_engine_constraints_unittest so that it tests six kinds of
license to provide better coverage.

Bug: 62393949
Test: build_and_run_all_unit_tests
Change-Id: I0dfdbf6b8ea39abb446089aef5f6ea0502e9b4c6
2017-08-29 16:37:49 -07:00

215 lines
7.9 KiB
C++

// Copyright 2013 Google Inc. All Rights Reserved.
#ifndef WVCDM_CORE_POLICY_ENGINE_H_
#define WVCDM_CORE_POLICY_ENGINE_H_
#include <map>
#include <string>
#include "license_key_status.h"
#include "license_protocol.pb.h"
#include "scoped_ptr.h"
#include "wv_cdm_types.h"
namespace wvcdm {
using video_widevine::LicenseIdentification;
class Clock;
class CryptoSession;
class WvCdmEventListener;
// This acts as an oracle that basically says "Yes(true) you may still decrypt
// or no(false) you may not decrypt this data anymore."
class PolicyEngine {
public:
PolicyEngine(CdmSessionId session_id, WvCdmEventListener* event_listener,
CryptoSession* crypto_session);
virtual ~PolicyEngine();
// The value returned should be taken as a hint rather than an absolute
// status. It is computed during the last call to either SetLicense/
// UpdateLicense/OnTimerEvent/BeginDecryption and may be out of sync
// depending on the amount of time elapsed. The current decryption
// status is not calculated to avoid overhead in the decryption path.
virtual bool CanDecryptContent(const KeyId& key_id);
// OnTimerEvent is called when a timer fires. It notifies the Policy Engine
// that the timer has fired and dispatches the relevant events through
// |event_listener_|.
virtual void OnTimerEvent();
// SetLicense is used in handling the initial license response. It stores
// an exact copy of the policy information stored in the license.
// The license state transitions to kLicenseStateCanPlay if the license
// permits playback.
virtual void SetLicense(const video_widevine::License& license);
// SetLicenseForRelease is used when releasing a license. The keys in this
// license will be ignored, and any old keys will be expired.
virtual void SetLicenseForRelease(
const video_widevine::License& license);
// Call this on first decrypt to set the start of playback.
virtual void BeginDecryption(void);
virtual void DecryptionEvent(void);
// UpdateLicense is used in handling a license response for a renewal request.
// The response may only contain any policy fields that have changed. In this
// case an exact copy is not what we want to happen. We also will receive an
// updated license_start_time from the server. The license will transition to
// kLicenseStateCanPlay if the license permits playback.
virtual void UpdateLicense(
const video_widevine::License& license);
// Used for notifying the Policy Engine of resolution changes
virtual void NotifyResolution(uint32_t width, uint32_t height);
virtual void NotifySessionExpiration();
virtual CdmResponseType Query(CdmQueryMap* query_response);
virtual CdmResponseType QueryKeyAllowedUsage(const KeyId& key_id,
CdmKeyAllowedUsage* key_usage);
virtual const LicenseIdentification& license_id() { return license_id_; }
bool GetSecondsSinceStarted(int64_t* seconds_since_started);
bool GetSecondsSinceLastPlayed(int64_t* seconds_since_started);
// for offline save and restore
int64_t GetPlaybackStartTime() { return playback_start_time_; }
int64_t GetLastPlaybackTime() { return last_playback_time_; }
int64_t GetGracePeriodEndTime() { return grace_period_end_time_; }
void RestorePlaybackTimes(int64_t playback_start_time,
int64_t last_playback_time,
int64_t grace_period_end_time);
bool IsLicenseForFuture() { return license_state_ == kLicenseStatePending; }
bool HasPlaybackStarted(int64_t current_time) {
if (playback_start_time_ == 0)
return false;
const int64_t playback_time = current_time - playback_start_time_;
return playback_time >= policy_.play_start_grace_period_seconds();
}
bool HasLicenseOrPlaybackDurationExpired(int64_t current_time);
int64_t GetLicenseOrPlaybackDurationRemaining();
bool CanRenew() { return policy_.can_renew(); }
bool IsSufficientOutputProtection(const KeyId& key_id) {
return license_keys_->MeetsConstraints(key_id);
}
private:
friend class PolicyEngineTest;
friend class PolicyEngineConstraintsTest;
void InitDevice(CryptoSession* crypto_session);
// Checks the keys against the current state of the hardware in case the HDCP
// level no longer meets their requirements. Will only check once every
// |kHdcpCheckInterval| seconds. Calls NotifyIfKeysChanged() to propagate any
// resultant changes to the OnKeysChange event.
void CheckDevice(int64_t current_time);
void SetDeviceResolution(uint32_t width, uint32_t height) {
current_resolution_ = width * height;
}
typedef enum {
kLicenseStateInitial,
kLicenseStatePending, // if license is issued for sometime in the future
kLicenseStateCanPlay,
kLicenseStateNeedRenewal,
kLicenseStateWaitingLicenseUpdate,
kLicenseStateExpired
} LicenseState;
// Gets the clock time that the license expires. This is the hard limit that
// all license types must obey at all times.
int64_t GetHardLicenseExpiryTime();
// Gets the clock time that the rental duration will expire, using the license
// duration if one is not present.
int64_t GetRentalExpiryTime();
// Gets the clock time that the license expires based on whether we have
// started playing. This takes into account GetHardLicenseExpiryTime.
int64_t GetExpiryTime(int64_t current_time,
bool ignore_soft_enforce_playback_duration);
int64_t GetLicenseOrRentalDurationRemaining(int64_t current_time);
int64_t GetPlaybackDurationRemaining(int64_t current_time);
bool HasRenewalDelayExpired(int64_t current_time);
bool HasRenewalRecoveryDurationExpired(int64_t current_time);
bool HasRenewalRetryIntervalExpired(int64_t current_time);
void UpdateRenewalRequest(int64_t current_time);
// Updates the keys' status to |new_status|. Calls NotifyIfKeysChanged() to
// propagate any resultant changes to the OnKeysChange event.
void UpdateKeyStatus(CdmKeyStatus new_status);
// Helper method that correctly fires the OnKeysChange event - if needed -
// depending on the parameters it is given.
void NotifyIfKeysChanged(bool keys_changed, bool new_usable_keys);
// Notifies updates in expiry time and fire OnExpirationUpdate event if
// expiry time changes.
void NotifyExpirationUpdate(int64_t current_time);
// set_clock() is for testing only. It alters ownership of the
// passed-in pointer.
void set_clock(Clock* clock);
LicenseState license_state_;
// This is the current policy information for this license. This gets updated
// as license renewals occur.
video_widevine::License::Policy policy_;
// This is the license id field from server response. This data gets passed
// back to the server in each renewal request. When we get a renewal response
// from the license server we will get an updated id field.
video_widevine::LicenseIdentification license_id_;
// The server returns the license start time in the license/license renewal
// response based off the request time sent by the client in the
// license request/renewal
int64_t license_start_time_;
int64_t playback_start_time_;
int64_t last_playback_time_;
int64_t last_expiry_time_;
int64_t grace_period_end_time_;
bool last_expiry_time_set_;
bool was_expired_on_load_;
// This is used as a reference point for policy management. This value
// represents an offset from license_start_time_. This is used to
// calculate the time where renewal retries should occur.
int64_t next_renewal_time_;
// Used to dispatch CDM events.
CdmSessionId session_id_;
WvCdmEventListener* event_listener_;
// Keys associated with license - holds allowed usage, usage constraints,
// and current status (CdmKeyStatus)
scoped_ptr<LicenseKeys> license_keys_;
// Device checks
int64_t next_device_check_;
uint32_t current_resolution_;
CryptoSession* crypto_session_;
scoped_ptr<Clock> clock_;
CORE_DISALLOW_COPY_AND_ASSIGN(PolicyEngine);
};
} // wvcdm
#endif // WVCDM_CORE_POLICY_ENGINE_H_