diff --git a/libwvdrmengine/cdm/core/include/policy_engine.h b/libwvdrmengine/cdm/core/include/policy_engine.h index fc0f6f6e..b40f42ad 100644 --- a/libwvdrmengine/cdm/core/include/policy_engine.h +++ b/libwvdrmengine/cdm/core/include/policy_engine.h @@ -68,7 +68,7 @@ class PolicyEngine { virtual void SetLicenseForRelease(const video_widevine::License& license); // Call this on first decrypt to set the start of playback. - virtual void BeginDecryption(void); + virtual bool BeginDecryption(void); virtual void DecryptionEvent(void); // UpdateLicense is used in handling a license response for a renewal request. diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 2ac37d13..be59d203 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -640,8 +640,7 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) { if (status == NO_ERROR) { if (is_initial_decryption_) { - policy_engine_->BeginDecryption(); - is_initial_decryption_ = false; + is_initial_decryption_ = !policy_engine_->BeginDecryption(); } has_decrypted_since_last_report_ = true; if (!is_usage_update_needed_) { diff --git a/libwvdrmengine/cdm/core/src/policy_engine.cpp b/libwvdrmengine/cdm/core/src/policy_engine.cpp index 9efa4a2c..c78cacb8 100644 --- a/libwvdrmengine/cdm/core/src/policy_engine.cpp +++ b/libwvdrmengine/cdm/core/src/policy_engine.cpp @@ -231,7 +231,7 @@ void PolicyEngine::UpdateLicense(const License& license) { NotifyExpirationUpdate(current_time); } -void PolicyEngine::BeginDecryption() { +bool PolicyEngine::BeginDecryption() { if (playback_start_time_ == 0) { switch (license_state_) { case kLicenseStateCanPlay: @@ -246,14 +246,17 @@ void PolicyEngine::BeginDecryption() { license_state_ = kLicenseStateNeedRenewal; } NotifyExpirationUpdate(playback_start_time_); - break; + return true; case kLicenseStateInitial: case kLicenseStatePending: case kLicenseStateExpired: default: - break; + return false; } } + else { + return true; + } } void PolicyEngine::DecryptionEvent() { last_playback_time_ = GetCurrentTime(); } diff --git a/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp b/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp index f3a848c1..f346cd86 100644 --- a/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp @@ -819,6 +819,40 @@ TEST_F(PolicyEngineTest, PlaybackOk_RentalAndLicense0_WithPlayback) { EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); } +TEST_F(PolicyEngineTest, + PlaybackOk_RentalAndLicense0_WithPlaybackBeforeLicense) { + License_Policy* policy = license_.mutable_policy(); + policy->clear_license_duration_seconds(); + policy->clear_rental_duration_seconds(); + // Only |playback_duration_seconds| set. + + policy_engine_->BeginDecryption(); + + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kPlaybackStartTime)) + .WillOnce(Return(kPlaybackStartTime + kPlaybackDuration - 10)) + .WillOnce(Return(kPlaybackStartTime + kPlaybackDuration + 10)); + + ExpectSessionKeysChange(kKeyStatusExpired, false); + ExpectSessionKeysChange(kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, 0)); + EXPECT_CALL(mock_event_listener_, + OnExpirationUpdate(_, kPlaybackStartTime + kPlaybackDuration)); + + policy_engine_->SetLicense(license_); + policy_engine_->BeginDecryption(); + policy_engine_->OnTimerEvent(); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); + + policy_engine_->OnTimerEvent(); + + EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); +} + TEST_F(PolicyEngineTest, PlaybackOk_Durations0) { License_Policy* policy = license_.mutable_policy(); policy->set_rental_duration_seconds(kDurationUnlimited); diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index a5efbffe..0eb867b2 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -520,6 +520,16 @@ std::string kPsshStreamingClip7 = wvcdm::a2bs_hex( "EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id "08011a0d7769646576696e655f74657374220f73" // pssh data "747265616d696e675f636c697037"); +std::string kPsshStreamingClip20 = wvcdm::a2bs_hex( + "000000437073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000023" // Widevine system id + "08011a0d7769646576696e655f746573" // pssh data + "74221073747265616d696e675f636c69703230"); +std::string kPsshStreamingClip21 = wvcdm::a2bs_hex( + "000000437073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000023" // Widevine system id + "08011a0d7769646576696e655f746573" // pssh data + "74221073747265616d696e675f636c69703231"); std::string kProviderSessionTokenStreamingClip3 = wvcdm::a2bs_hex( "4851305A4A4156485A554936444E4931"); @@ -527,6 +537,15 @@ std::string kProviderSessionTokenStreamingClip4 = wvcdm::a2bs_hex( "4942524F4355544E5557553145463243"); std::string kProviderSessionTokenStreamingClip7 = wvcdm::a2bs_hex( "44434C53524F4E30394C4E5535544B4C"); +std::string kProviderSessionTokenStreamingClip20 = wvcdm::a2bs_hex( + "4851305A4A4156485A554936444E4931"); +std::string kProviderSessionTokenStreamingClip21 = wvcdm::a2bs_hex( + "4851305A4A4156485A554936444E4931"); + +// playback duration is 10 seconds+uncertainty window +const std::chrono::milliseconds + kExpirationStreamingClip21PlaybackDurationTimeMs = + std::chrono::milliseconds(12*1000); UsageLicenseAndSubSampleInfo kUsageLicenseTestVector1[] = { { kPsshStreamingClip3, &usage_info_sub_samples_icp[0], @@ -1634,6 +1653,12 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { bool VerifyDecryption(const CdmSessionId& session_id, const SubSampleInfo& data) { + return VerifyDecryption(session_id, data, NO_ERROR); + } + + bool VerifyDecryption(const CdmSessionId& session_id, + const SubSampleInfo& data, + CdmResponseType expected_response) { std::vector decrypt_buffer(data.encrypt_data.size()); CdmDecryptionParameters decryption_parameters( &data.key_id, &data.encrypt_data.front(), data.encrypt_data.size(), @@ -1644,7 +1669,7 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { CdmResponseType status = decryptor_.Decrypt(session_id, data.validate_key_id, decryption_parameters); - EXPECT_EQ(NO_ERROR, status); + EXPECT_EQ(expected_response, status); if (status != NO_ERROR) return false; @@ -4981,6 +5006,70 @@ TEST_F(WvCdmRequestLicenseTest, DecryptionKeyExpiredTest) { decryptor_.CloseSession(session_id_); } +TEST_F(WvCdmRequestLicenseTest, PlaybackExpiry) { + StrictMock listener; + DecryptCallbackTester decrypt_callback( + &decryptor_, + &usage_info_sub_samples_icp[0]); + decryptor_.OpenSession(config_.key_system(), NULL, kDefaultCdmIdentifier, + &listener, &session_id_); + + EXPECT_CALL( + listener, + OnSessionKeysChange( + session_id_, + AllOf(Each(Pair(_, kKeyStatusUsable)), Not(IsEmpty())), true)) + .WillOnce(Invoke(&decrypt_callback, &DecryptCallbackTester::Decrypt)); + EXPECT_CALL(listener, OnExpirationUpdate(session_id_, _)); + + GenerateKeyRequest(kPsshStreamingClip21, kLicenseTypeStreaming, NULL); + VerifyKeyRequestResponse(config_.license_server(), config_.client_auth()); + + EXPECT_CALL( + listener, + OnSessionKeysChange( + session_id_, + AllOf(Each(Pair(_, kKeyStatusExpired)), Not(IsEmpty())), false)); + + // Elapse time so that the key should now be considered expired. + std::this_thread::sleep_for(kExpirationStreamingClip21PlaybackDurationTimeMs); +} + +TEST_F(WvCdmRequestLicenseTest, PlaybackExpiry_DecryptBeforeLicense) { + StrictMock listener; + DecryptCallbackTester decrypt_callback( + &decryptor_, + &usage_info_sub_samples_icp[0]); + decryptor_.OpenSession(config_.key_system(), NULL, kDefaultCdmIdentifier, + &listener, &session_id_); + + // Decrypt before license is received is expected to fail but should + // not start the playback timer + EXPECT_FALSE(VerifyDecryption(session_id_, + usage_info_sub_samples_icp[0], NEED_KEY)); + std::this_thread::sleep_for(kExpirationStreamingClip21PlaybackDurationTimeMs); + + EXPECT_CALL( + listener, + OnSessionKeysChange( + session_id_, + AllOf(Each(Pair(_, kKeyStatusUsable)), Not(IsEmpty())), true)) + .WillOnce(Invoke(&decrypt_callback, &DecryptCallbackTester::Decrypt)); + EXPECT_CALL(listener, OnExpirationUpdate(session_id_, _)); + + GenerateKeyRequest(kPsshStreamingClip21, kLicenseTypeStreaming, NULL); + VerifyKeyRequestResponse(config_.license_server(), config_.client_auth()); + + EXPECT_CALL( + listener, + OnSessionKeysChange( + session_id_, + AllOf(Each(Pair(_, kKeyStatusExpired)), Not(IsEmpty())), false)); + + // Elapse time so that the key should now be considered expired. + std::this_thread::sleep_for(kExpirationStreamingClip21PlaybackDurationTimeMs); +} + TEST_F(WvCdmRequestLicenseTest, SessionKeyChangeNotificationTest) { StrictMock listener; DecryptCallbackTester decrypt_callback(