diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index 593eb191..42c6aec5 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -128,7 +128,6 @@ class CdmSession { // instance variables bool initialized_; CdmSessionId session_id_; - WvCdmEventListener* event_listener_; scoped_ptr license_parser_; scoped_ptr crypto_session_; scoped_ptr policy_engine_; diff --git a/libwvdrmengine/cdm/core/include/policy_engine.h b/libwvdrmengine/cdm/core/include/policy_engine.h index 21aa21bf..2c2adc07 100644 --- a/libwvdrmengine/cdm/core/include/policy_engine.h +++ b/libwvdrmengine/cdm/core/include/policy_engine.h @@ -3,6 +3,7 @@ #ifndef WVCDM_CORE_POLICY_ENGINE_H_ #define WVCDM_CORE_POLICY_ENGINE_H_ +#include #include #include "license_protocol.pb.h" @@ -59,6 +60,8 @@ class PolicyEngine { // 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* key_info); virtual const LicenseIdentification& license_id() { return license_id_; } @@ -105,15 +108,19 @@ class PolicyEngine { void UpdateRenewalRequest(int64_t current_time); + // Notifies updates in keys information and fire OnKeysChange event if + // key changes. + void NotifyKeysChange(CdmKeyStatus new_status); + // Notifies updates in expiry time and fire OnExpirationUpdate event if // expiry time changes. void NotifyExpirationUpdate(); // These setters are for testing only. Takes ownership of the pointers. void set_clock(Clock* clock); + void set_max_res_engine(MaxResEngine* max_res_engine); LicenseState license_state_; - bool can_decrypt_; // This is the current policy information for this license. This gets updated // as license renewals occur. @@ -142,7 +149,10 @@ class PolicyEngine { CdmSessionId session_id_; WvCdmEventListener* event_listener_; - MaxResEngine max_res_engine_; + scoped_ptr max_res_engine_; + + std::map keys_status_; + scoped_ptr clock_; CORE_DISALLOW_COPY_AND_ASSIGN(PolicyEngine); diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_event_listener.h b/libwvdrmengine/cdm/core/include/wv_cdm_event_listener.h index 92988dfb..1d1f1ef5 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_event_listener.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_event_listener.h @@ -15,6 +15,10 @@ class WvCdmEventListener { virtual void OnSessionRenewalNeeded(const CdmSessionId& session_id) = 0; virtual void OnSessionExpiration(const CdmSessionId& session_id) = 0; + virtual void OnSessionKeysChange( + const CdmSessionId& session_id, + const std::vector& cdm_keys_info, + bool has_new_usable_key) = 0; virtual void OnExpirationUpdate(const CdmSessionId& session_id, int64_t new_expiry_time) = 0; diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index 09569e55..063a18a1 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -49,6 +49,15 @@ enum CdmResponseType { INSUFFICIENT_CRYPTO_RESOURCES, }; +enum CdmKeyStatus { + kKeyStatusUsable, + kKeyStatusInternalError, + kKeyStatusExpired, + kKeyStatusOutputNotAllowed, + kKeyStatusOutputDownscaled, + kKeyStatusPending, +}; + #define CORE_DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&); \ void operator=(const TypeName&) @@ -116,6 +125,14 @@ struct CdmDecryptionParameters { is_video(true) {} }; +struct CdmKeyInformation { + CdmKeyInformation(const KeyId& id, CdmKeyStatus status) + : key_id(id), key_status(status) {} + + KeyId key_id; + CdmKeyStatus key_status; +}; + // forward class references class KeyMessage; class Request; diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index e5f625db..c3734674 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -27,11 +27,10 @@ CdmSession::CdmSession(const CdmClientPropertySet* cdm_client_property_set, WvCdmEventListener* event_listener) : initialized_(false), session_id_(GenerateSessionId()), - event_listener_(event_listener), license_parser_(new CdmLicense), crypto_session_(new CryptoSession), - policy_engine_(new PolicyEngine(session_id_, event_listener_, - crypto_session_.get())), + policy_engine_( + new PolicyEngine(session_id_, event_listener, crypto_session_.get())), file_handle_(new DeviceFiles), license_received_(false), is_offline_(false), @@ -532,7 +531,7 @@ void CdmSession::OnTimerEvent(bool update_usage) { void CdmSession::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) { if (key_set_id_ == key_set_id) { - if (event_listener_) event_listener_->OnSessionExpiration(session_id_); + policy_engine_->NotifySessionExpiration(); } } diff --git a/libwvdrmengine/cdm/core/src/policy_engine.cpp b/libwvdrmengine/cdm/core/src/policy_engine.cpp index 77ba8ba9..3592abd0 100644 --- a/libwvdrmengine/cdm/core/src/policy_engine.cpp +++ b/libwvdrmengine/cdm/core/src/policy_engine.cpp @@ -15,13 +15,14 @@ #include "wv_cdm_constants.h" #include "wv_cdm_event_listener.h" +using video_widevine_server::sdk::License; + namespace wvcdm { PolicyEngine::PolicyEngine(CdmSessionId session_id, WvCdmEventListener* event_listener, CryptoSession* crypto_session) : license_state_(kLicenseStateInitial), - can_decrypt_(false), license_start_time_(0), playback_start_time_(0), last_playback_time_(0), @@ -30,13 +31,18 @@ PolicyEngine::PolicyEngine(CdmSessionId session_id, policy_max_duration_seconds_(0), session_id_(session_id), event_listener_(event_listener), - max_res_engine_(crypto_session), + max_res_engine_(new MaxResEngine(crypto_session)), clock_(new Clock) {} PolicyEngine::~PolicyEngine() {} bool PolicyEngine::CanDecrypt(const KeyId& key_id) { - return can_decrypt_ && max_res_engine_.CanDecrypt(key_id); + if (keys_status_.find(key_id) == keys_status_.end()) { + LOGE("PolicyEngine::CanDecrypt Key '%s' not in license.", + b2a_hex(key_id).c_str()); + return false; + } + return keys_status_[key_id] == kKeyStatusUsable; } void PolicyEngine::OnTimerEvent() { @@ -47,17 +53,21 @@ void PolicyEngine::OnTimerEvent() { IsPlaybackDurationExpired(current_time)) && license_state_ != kLicenseStateExpired) { license_state_ = kLicenseStateExpired; - can_decrypt_ = false; + NotifyKeysChange(kKeyStatusExpired); if (event_listener_) event_listener_->OnSessionExpiration(session_id_); return; } + max_res_engine_->OnTimerEvent(); + bool renewal_needed = false; // Test to determine if renewal should be attempted. switch (license_state_) { case kLicenseStateCanPlay: { if (IsRenewalDelayExpired(current_time)) renewal_needed = true; + // HDCP may change, so force a check. + NotifyKeysChange(kKeyStatusUsable); break; } @@ -74,7 +84,7 @@ void PolicyEngine::OnTimerEvent() { case kLicenseStatePending: { if (current_time >= license_start_time_) { license_state_ = kLicenseStateCanPlay; - can_decrypt_ = true; + NotifyKeysChange(kKeyStatusUsable); } break; } @@ -86,7 +96,7 @@ void PolicyEngine::OnTimerEvent() { default: { license_state_ = kLicenseStateExpired; - can_decrypt_ = false; + NotifyKeysChange(kKeyStatusInternalError); break; } } @@ -95,21 +105,26 @@ void PolicyEngine::OnTimerEvent() { UpdateRenewalRequest(current_time); if (event_listener_) event_listener_->OnSessionRenewalNeeded(session_id_); } - - max_res_engine_.OnTimerEvent(); } -void PolicyEngine::SetLicense( - const video_widevine_server::sdk::License& license) { +void PolicyEngine::SetLicense(const License& license) { license_id_.Clear(); license_id_.CopyFrom(license.id()); policy_.Clear(); + + // Extract content key ids. + keys_status_.clear(); + for (int key_index = 0; key_index < license.key_size(); ++key_index) { + const License::KeyContainer& key = license.key(key_index); + if (key.type() == License::KeyContainer::CONTENT && key.has_id()) + keys_status_[key.id()] = kKeyStatusInternalError; + } + UpdateLicense(license); - max_res_engine_.SetLicense(license); + max_res_engine_->SetLicense(license); } -void PolicyEngine::UpdateLicense( - const video_widevine_server::sdk::License& license) { +void PolicyEngine::UpdateLicense(const License& license) { if (!license.has_policy()) return; if (kLicenseStateExpired == license_state_) { @@ -153,16 +168,17 @@ void PolicyEngine::UpdateLicense( if (!policy_.can_play() || IsLicenseDurationExpired(current_time) || IsPlaybackDurationExpired(current_time)) { license_state_ = kLicenseStateExpired; + NotifyKeysChange(kKeyStatusExpired); return; } // Update state if (current_time >= license_start_time_) { license_state_ = kLicenseStateCanPlay; - can_decrypt_ = true; + NotifyKeysChange(kKeyStatusUsable); } else { license_state_ = kLicenseStatePending; - can_decrypt_ = false; + NotifyKeysChange(kKeyStatusPending); } NotifyExpirationUpdate(); } @@ -195,7 +211,13 @@ void PolicyEngine::DecryptionEvent() { } void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) { - max_res_engine_.SetResolution(width, height); + max_res_engine_->SetResolution(width, height); +} + +void PolicyEngine::NotifySessionExpiration() { + license_state_ = kLicenseStateExpired; + NotifyKeysChange(kKeyStatusExpired); + if (event_listener_) event_listener_->OnSessionExpiration(session_id_); } CdmResponseType PolicyEngine::Query(CdmQueryMap* key_info) { @@ -303,6 +325,35 @@ bool PolicyEngine::IsRenewalRetryIntervalExpired(int64_t current_time) { next_renewal_time_ <= current_time; } +void PolicyEngine::NotifyKeysChange(CdmKeyStatus new_status) { + bool keys_changed = false; + bool has_new_usable_key = false; + for (std::map::iterator it = keys_status_.begin(); + it != keys_status_.end(); ++it) { + const KeyId key_id = it->first; + CdmKeyStatus& key_status = it->second; + CdmKeyStatus updated_status = new_status; + if (updated_status == kKeyStatusUsable) { + if (!max_res_engine_->CanDecrypt(key_id)) + updated_status = kKeyStatusOutputNotAllowed; + } + if (key_status != updated_status) { + key_status = updated_status; + if (updated_status == kKeyStatusUsable) has_new_usable_key = true; + keys_changed = true; + } + } + if (keys_changed && event_listener_) { + std::vector keys_info; + for (std::map::iterator it = keys_status_.begin(); + it != keys_status_.end(); ++it) { + keys_info.push_back(CdmKeyInformation(it->first, it->second)); + } + event_listener_->OnSessionKeysChange(session_id_, keys_info, + has_new_usable_key); + } +} + void PolicyEngine::NotifyExpirationUpdate() { int64_t expiry_time = std::min(GetLicenseExpiryTime(), GetPlaybackExpiryTime()); @@ -315,4 +366,8 @@ void PolicyEngine::NotifyExpirationUpdate() { void PolicyEngine::set_clock(Clock* clock) { clock_.reset(clock); } +void PolicyEngine::set_max_res_engine(MaxResEngine* max_res_engine) { + max_res_engine_.reset(max_res_engine); +} + } // wvcdm diff --git a/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp b/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp index 13c674a4..2c1c3144 100644 --- a/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp @@ -38,6 +38,8 @@ const int64_t kHighDuration = const char* kRenewalServerUrl = "https://test.google.com/license/GetCencLicense"; const wvcdm::KeyId kKeyId = "357adc89f1673433c36c621f1b5c41ee"; +const wvcdm::KeyId kAnotherKeyId = "another_key_id"; +const wvcdm::KeyId kSomeRandomKeyId = "some_random_key_id"; const wvcdm::CdmSessionId kSessionId = "mock_session_id"; int64_t GetLicenseRenewalDelay(int64_t license_duration) { @@ -58,20 +60,34 @@ using video_widevine_server::sdk::OFFLINE; // gmock methods using ::testing::_; +using ::testing::AllOf; using ::testing::AtLeast; +using ::testing::Field; using ::testing::InSequence; using ::testing::MockFunction; using ::testing::Return; using ::testing::StrictMock; +using ::testing::UnorderedElementsAre; class MockCdmEventListener : public WvCdmEventListener { public: MOCK_METHOD1(OnSessionRenewalNeeded, void(const CdmSessionId& session_id)); MOCK_METHOD1(OnSessionExpiration, void(const CdmSessionId& session_id)); + MOCK_METHOD3(OnSessionKeysChange, + void(const CdmSessionId& session_id, + const std::vector& cdm_keys_info, + bool has_new_usable_key)); MOCK_METHOD2(OnExpirationUpdate, void(const CdmSessionId& session_id, int64_t new_expiry_time)); }; +class MockMaxResEngine : public MaxResEngine { + public: + MockMaxResEngine() : MaxResEngine(NULL) {} + + MOCK_METHOD1(CanDecrypt, bool(const KeyId& key_id)); +}; + class PolicyEngineTest : public ::testing::Test { protected: virtual void SetUp() { @@ -85,6 +101,10 @@ class PolicyEngineTest : public ::testing::Test { id->set_version(1); id->set_type(STREAMING); + License::KeyContainer* key = license_.add_key(); + key->set_type(License::KeyContainer::CONTENT); + key->set_id(kKeyId); + License_Policy* policy = license_.mutable_policy(); policy = license_.mutable_policy(); policy->set_can_play(true); @@ -120,8 +140,42 @@ class PolicyEngineTest : public ::testing::Test { policy_engine_->set_clock(mock_clock_); } + void InjectMockMaxResEngine() { + mock_max_res_engine_ = new MockMaxResEngine(); + policy_engine_->set_max_res_engine(mock_max_res_engine_); + } + + void ExpectSessionKeysChange(CdmKeyStatus expected_key_status, + bool expected_has_new_usable_key) { + EXPECT_CALL(mock_event_listener_, + OnSessionKeysChange( + kSessionId, + UnorderedElementsAre( + AllOf(Field(&CdmKeyInformation::key_id, kKeyId), + Field(&CdmKeyInformation::key_status, + expected_key_status))), + expected_has_new_usable_key)); + } + + void ExpectSessionKeysChange(CdmKeyStatus expected_key1_status, + CdmKeyStatus expected_key2_status, + bool expected_has_new_usable_key) { + EXPECT_CALL(mock_event_listener_, + OnSessionKeysChange( + kSessionId, + UnorderedElementsAre( + AllOf(Field(&CdmKeyInformation::key_id, kKeyId), + Field(&CdmKeyInformation::key_status, + expected_key1_status)), + AllOf(Field(&CdmKeyInformation::key_id, kAnotherKeyId), + Field(&CdmKeyInformation::key_status, + expected_key2_status))), + expected_has_new_usable_key)); + } + StrictMock mock_event_listener_; MockClock* mock_clock_; + MockMaxResEngine* mock_max_res_engine_; scoped_ptr policy_engine_; License license_; MockFunction check_; @@ -137,6 +191,7 @@ TEST_F(PolicyEngineTest, PlaybackSuccess) { .WillOnce(Return(kLicenseStartTime + 5)) .WillOnce(Return(kLicenseStartTime + 10)); + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); @@ -146,6 +201,7 @@ TEST_F(PolicyEngineTest, PlaybackSuccess) { policy_engine_->BeginDecryption(); EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kSomeRandomKeyId)); } TEST_F(PolicyEngineTest, PlaybackFailed_CanPlayFalse) { @@ -156,6 +212,8 @@ TEST_F(PolicyEngineTest, PlaybackFailed_CanPlayFalse) { .WillOnce(Return(kLicenseStartTime + 1)) .WillOnce(Return(kLicenseStartTime + 5)); + ExpectSessionKeysChange(kKeyStatusExpired, false); + policy_engine_->SetLicense(license_); EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId)); @@ -179,9 +237,11 @@ TEST_F(PolicyEngineTest, PlaybackFails_RentalDurationExpired) { .WillOnce(Return(kLicenseStartTime + min_duration)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); + ExpectSessionKeysChange(kKeyStatusExpired, false); EXPECT_CALL(mock_event_listener_, OnSessionExpiration(kSessionId)); EXPECT_CALL(check_, Call(2)); @@ -211,11 +271,13 @@ TEST_F(PolicyEngineTest, PlaybackFails_PlaybackDurationExpired) { .WillOnce(Return(playback_start_time + kPlaybackDuration + 2)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kHighDuration)); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, playback_start_time + kPlaybackDuration)); EXPECT_CALL(check_, Call(1)); + ExpectSessionKeysChange(kKeyStatusExpired, false); EXPECT_CALL(mock_event_listener_, OnSessionExpiration(_)); EXPECT_CALL(check_, Call(2)); @@ -244,9 +306,11 @@ TEST_F(PolicyEngineTest, PlaybackFails_LicenseDurationExpired) { .WillOnce(Return(kLicenseStartTime + min_duration)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); + ExpectSessionKeysChange(kKeyStatusExpired, false); EXPECT_CALL(mock_event_listener_, OnSessionExpiration(_)); EXPECT_CALL(check_, Call(2)); @@ -275,9 +339,11 @@ TEST_F(PolicyEngineTest, PlaybackFails_ExpiryBeforeRenewalDelay) { .WillOnce(Return(kLicenseStartTime + min_duration)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); + ExpectSessionKeysChange(kKeyStatusExpired, false); EXPECT_CALL(mock_event_listener_, OnSessionExpiration(_)); EXPECT_CALL(check_, Call(2)); @@ -309,6 +375,7 @@ TEST_F(PolicyEngineTest, PlaybackOk_RentalDuration0) { .WillOnce(Return(kLicenseStartTime + kStreamingLicenseDuration)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); @@ -316,6 +383,7 @@ TEST_F(PolicyEngineTest, PlaybackOk_RentalDuration0) { EXPECT_CALL(check_, Call(2)); EXPECT_CALL(mock_event_listener_, OnSessionRenewalNeeded(_)); EXPECT_CALL(check_, Call(3)); + ExpectSessionKeysChange(kKeyStatusExpired, false); EXPECT_CALL(mock_event_listener_, OnSessionExpiration(_)); EXPECT_CALL(check_, Call(4)); @@ -348,6 +416,7 @@ TEST_F(PolicyEngineTest, PlaybackOk_PlaybackDuration0) { .WillOnce(Return(kLicenseStartTime + kHighDuration + 2)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kHighDuration)); EXPECT_CALL(check_, Call(1)); @@ -355,6 +424,7 @@ TEST_F(PolicyEngineTest, PlaybackOk_PlaybackDuration0) { EXPECT_CALL(check_, Call(2)); EXPECT_CALL(mock_event_listener_, OnSessionRenewalNeeded(_)); EXPECT_CALL(check_, Call(3)); + ExpectSessionKeysChange(kKeyStatusExpired, false); EXPECT_CALL(mock_event_listener_, OnSessionExpiration(_)); EXPECT_CALL(check_, Call(4)); @@ -385,9 +455,11 @@ TEST_F(PolicyEngineTest, PlaybackOk_LicenseDuration0) { .WillOnce(Return(kLicenseStartTime + min_duration)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); + ExpectSessionKeysChange(kKeyStatusExpired, false); EXPECT_CALL(mock_event_listener_, OnSessionExpiration(_)); EXPECT_CALL(check_, Call(2)); @@ -417,6 +489,7 @@ TEST_F(PolicyEngineTest, PlaybackOk_Durations0) { .WillOnce(Return(kLicenseStartTime + kHighDuration - 1)) .WillOnce(Return(kLicenseStartTime + kHighDuration)); + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kNoExpiration)); policy_engine_->SetLicense(license_); @@ -437,8 +510,11 @@ TEST_F(PolicyEngineTest, PlaybackOk_LicenseWithFutureStartTime) { .WillOnce(Return(kLicenseStartTime)) .WillOnce(Return(kLicenseStartTime + 10)); + InSequence s; + ExpectSessionKeysChange(kKeyStatusPending, false); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); + ExpectSessionKeysChange(kKeyStatusUsable, true); policy_engine_->SetLicense(license_); @@ -466,10 +542,12 @@ TEST_F(PolicyEngineTest, PlaybackFailed_CanRenewFalse) { .WillOnce(Return(kLicenseStartTime + kStreamingLicenseDuration + 10)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); EXPECT_CALL(check_, Call(2)); + ExpectSessionKeysChange(kKeyStatusExpired, false); EXPECT_CALL(mock_event_listener_, OnSessionExpiration(_)); EXPECT_CALL(check_, Call(3)); @@ -502,6 +580,7 @@ TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccess) { kLicenseRenewalRetryInterval + 10)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); @@ -550,14 +629,17 @@ TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccess_WithFutureStartTime) { .WillOnce(Return(kLicenseStartTime + license_renewal_delay + 60)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); EXPECT_CALL(mock_event_listener_, OnSessionRenewalNeeded(_)); EXPECT_CALL(check_, Call(2)); + ExpectSessionKeysChange(kKeyStatusPending, false); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, new_license_start_time + kLowDuration)); EXPECT_CALL(check_, Call(3)); + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(check_, Call(4)); policy_engine_->SetLicense(license_); @@ -598,6 +680,7 @@ TEST_F(PolicyEngineTest, PlaybackFailed_RenewFailedVersionNotUpdated) { .WillOnce(Return(kLicenseStartTime + kStreamingLicenseDuration + 10)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); @@ -605,6 +688,7 @@ TEST_F(PolicyEngineTest, PlaybackFailed_RenewFailedVersionNotUpdated) { EXPECT_CALL(check_, Call(2)); EXPECT_CALL(mock_event_listener_, OnSessionRenewalNeeded(_)); EXPECT_CALL(check_, Call(3)); + ExpectSessionKeysChange(kKeyStatusExpired, false); EXPECT_CALL(mock_event_listener_, OnSessionExpiration(_)); EXPECT_CALL(check_, Call(4)); @@ -652,6 +736,7 @@ TEST_F(PolicyEngineTest, PlaybackFailed_RepeatedRenewFailures) { .WillOnce(Return(kLicenseStartTime + kStreamingLicenseDuration + 15)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); @@ -664,6 +749,7 @@ TEST_F(PolicyEngineTest, PlaybackFailed_RepeatedRenewFailures) { EXPECT_CALL(mock_event_listener_, OnSessionRenewalNeeded(_)); EXPECT_CALL(check_, Call(6)); EXPECT_CALL(check_, Call(7)); + ExpectSessionKeysChange(kKeyStatusExpired, false); EXPECT_CALL(mock_event_listener_, OnSessionExpiration(_)); EXPECT_CALL(check_, Call(8)); @@ -702,6 +788,7 @@ TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccessAfterExpiry) { .WillOnce(Return(kLicenseStartTime + kStreamingLicenseDuration + 40)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); @@ -714,8 +801,10 @@ TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccessAfterExpiry) { EXPECT_CALL(mock_event_listener_, OnSessionRenewalNeeded(_)); EXPECT_CALL(check_, Call(6)); EXPECT_CALL(check_, Call(7)); + ExpectSessionKeysChange(kKeyStatusExpired, false); EXPECT_CALL(mock_event_listener_, OnSessionExpiration(_)); EXPECT_CALL(check_, Call(8)); + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL( mock_event_listener_, OnExpirationUpdate(_, new_license_start_time + new_license_duration)); @@ -767,6 +856,7 @@ TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccessAfterFailures) { .WillOnce(Return(kLicenseStartTime + license_renewal_delay + 200)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); @@ -818,6 +908,7 @@ TEST_F(PolicyEngineTest, PlaybackOk_RenewedWithUsage) { .WillOnce(Return(kLicenseStartTime + 50)); InSequence s; + ExpectSessionKeysChange(kKeyStatusUsable, true); EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); EXPECT_CALL(check_, Call(1)); @@ -850,6 +941,87 @@ TEST_F(PolicyEngineTest, PlaybackOk_RenewedWithUsage) { EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId)); } +TEST_F(PolicyEngineTest, MultipleKeysInLicense) { + const char kSigningKeyId[] = "signing_key"; + + license_.clear_key(); + License::KeyContainer* content_key = license_.add_key(); + content_key->set_type(License::KeyContainer::CONTENT); + content_key->set_id(kKeyId); + License::KeyContainer* non_content_key = license_.add_key(); + non_content_key->set_type(License::KeyContainer::SIGNING); + non_content_key->set_id(kSigningKeyId); + License::KeyContainer* content_key_without_id = license_.add_key(); + content_key_without_id->set_type(License::KeyContainer::CONTENT); + License::KeyContainer* another_content_key = license_.add_key(); + another_content_key->set_type(License::KeyContainer::CONTENT); + another_content_key->set_id(kAnotherKeyId); + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)); + + ExpectSessionKeysChange(kKeyStatusUsable, kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, _)); + + policy_engine_->SetLicense(license_); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kAnotherKeyId)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kSigningKeyId)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kSomeRandomKeyId)); +} + +TEST_F(PolicyEngineTest, KeysOutputNotAllowedByMaxResEngine) { + license_.clear_key(); + License::KeyContainer* content_key = license_.add_key(); + content_key->set_type(License::KeyContainer::CONTENT); + content_key->set_id(kKeyId); + License::KeyContainer* another_content_key = license_.add_key(); + another_content_key->set_type(License::KeyContainer::CONTENT); + another_content_key->set_id(kAnotherKeyId); + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kLicenseStartTime + 5)) + .WillOnce(Return(kLicenseStartTime + 8)) + .WillOnce(Return(kLicenseStartTime + 10)); + + InjectMockMaxResEngine(); + EXPECT_CALL(*mock_max_res_engine_, CanDecrypt(kKeyId)) + .WillOnce(Return(false)) + .WillOnce(Return(true)) + .WillOnce(Return(false)) + .WillOnce(Return(true)); + EXPECT_CALL(*mock_max_res_engine_, CanDecrypt(kAnotherKeyId)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(true)) + .WillOnce(Return(true)); + + InSequence s; + ExpectSessionKeysChange(kKeyStatusOutputNotAllowed, + kKeyStatusOutputNotAllowed, false); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, _)); + ExpectSessionKeysChange(kKeyStatusUsable, kKeyStatusOutputNotAllowed, true); + ExpectSessionKeysChange(kKeyStatusOutputNotAllowed, kKeyStatusUsable, true); + ExpectSessionKeysChange(kKeyStatusUsable, kKeyStatusUsable, true); + + policy_engine_->SetLicense(license_); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kAnotherKeyId)); + + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecrypt(kAnotherKeyId)); + + policy_engine_->OnTimerEvent(); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kAnotherKeyId)); + + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId)); + EXPECT_TRUE(policy_engine_->CanDecrypt(kAnotherKeyId)); +} + class PolicyEngineQueryTest : public PolicyEngineTest { protected: virtual void SetUp() { diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index 1777f171..73b6af8f 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -23,6 +23,12 @@ #include "wv_cdm_event_listener.h" #include "wv_content_decryption_module.h" +using ::testing::_; +using ::testing::AtLeast; +using ::testing::Each; +using ::testing::Field; +using ::testing::StrictMock; + namespace { const char kPathDelimiter = '/'; @@ -444,6 +450,10 @@ class TestWvCdmEventListener : public WvCdmEventListener { MOCK_METHOD1(OnSessionRenewalNeeded, void(const CdmSessionId& session_id)); MOCK_METHOD1(OnSessionExpiration, void(const CdmSessionId& session_id)); + MOCK_METHOD3(OnSessionKeysChange, + void(const CdmSessionId& session_id, + const std::vector& cdm_keys_info, + bool has_new_usable_key)); MOCK_METHOD2(OnExpirationUpdate, void(const CdmSessionId& session_id, int64_t new_expiry_time)); }; @@ -1156,10 +1166,15 @@ TEST_F(WvCdmRequestLicenseTest, ExpiryOnReleaseOfflineKeyTest) { session_id_.clear(); key_set_id_.clear(); - ::testing::StrictMock listener; + StrictMock listener; decryptor_.OpenSession(g_key_system, NULL, &listener, &session_id_); CdmSessionId restore_session_id = session_id_; - EXPECT_CALL(listener, OnExpirationUpdate(restore_session_id, ::testing::_)); + EXPECT_CALL(listener, + OnSessionKeysChange(restore_session_id, + Each(Field(&CdmKeyInformation::key_status, + kKeyStatusUsable)), + true)); + EXPECT_CALL(listener, OnExpirationUpdate(restore_session_id, _)); EXPECT_EQ(wvcdm::KEY_ADDED, decryptor_.RestoreKey(restore_session_id, key_set_id)); @@ -1167,7 +1182,12 @@ TEST_F(WvCdmRequestLicenseTest, ExpiryOnReleaseOfflineKeyTest) { key_set_id_.clear(); // Maybe called since VerifyKeyRequestResponse could take some time. EXPECT_CALL(listener, OnSessionRenewalNeeded(restore_session_id)) - .Times(::testing::AtLeast(0)); + .Times(AtLeast(0)); + EXPECT_CALL(listener, + OnSessionKeysChange(restore_session_id, + Each(Field(&CdmKeyInformation::key_status, + kKeyStatusExpired)), + false)); EXPECT_CALL(listener, OnSessionExpiration(restore_session_id)); GenerateKeyRelease(key_set_id); key_set_id_ = key_set_id; diff --git a/libwvdrmengine/mediadrm/include/WVDrmPlugin.h b/libwvdrmengine/mediadrm/include/WVDrmPlugin.h index d5394734..1e89ffab 100644 --- a/libwvdrmengine/mediadrm/include/WVDrmPlugin.h +++ b/libwvdrmengine/mediadrm/include/WVDrmPlugin.h @@ -32,6 +32,7 @@ using android::status_t; using android::String8; using android::Vector; using std::map; +using wvcdm::CdmKeyInformation; using wvcdm::CdmSessionId; using wvcdm::CdmResponseType; using wvcdm::WvContentDecryptionModule; @@ -143,6 +144,11 @@ class WVDrmPlugin : public android::DrmPlugin, virtual void OnSessionExpiration(const CdmSessionId& cdmSessionId); + virtual void OnSessionKeysChange( + const CdmSessionId& session_id, + const std::vector& cdm_keys_info, + bool has_new_usable_key); + virtual void OnExpirationUpdate(const CdmSessionId& cdmSessionId, int64_t new_expiry_time); diff --git a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp index 32f0fb25..c79484bc 100644 --- a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp @@ -954,6 +954,13 @@ void WVDrmPlugin::OnSessionExpiration(const CdmSessionId& cdmSessionId) { sendEvent(kDrmPluginEventKeyExpired, 0, &sessionId, NULL); } +void WVDrmPlugin::OnSessionKeysChange( + const CdmSessionId& session_id, + const std::vector& cdm_keys_info, + bool has_new_usable_key) { + // TODO(kqyang): Glue with DrmPlugin API when it is ready. +} + void WVDrmPlugin::OnExpirationUpdate(const CdmSessionId& cdmSessionId, int64_t new_expiry_time) { // TODO(kqyang): Glue with DrmPlugin API when it is ready. Note that