diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index a37eb5cb..355c66f4 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -91,6 +91,7 @@ class CdmSession { virtual CdmResponseType ReleaseKey(const CdmKeyResponse& key_response); virtual bool IsKeyLoaded(const KeyId& key_id); + virtual int64_t GetDurationRemaining(); // Used for notifying the Policy Engine of resolution changes virtual void NotifyResolution(uint32_t width, uint32_t height); diff --git a/libwvdrmengine/cdm/core/include/policy_engine.h b/libwvdrmengine/cdm/core/include/policy_engine.h index aefe3c1e..60e019f3 100644 --- a/libwvdrmengine/cdm/core/include/policy_engine.h +++ b/libwvdrmengine/cdm/core/include/policy_engine.h @@ -84,6 +84,7 @@ class PolicyEngine { bool IsPlaybackStarted() { return playback_start_time_ > 0; } bool IsLicenseOrPlaybackDurationExpired(int64_t current_time); + int64_t GetLicenseOrPlaybackDurationRemaining(); bool CanRenew() { return policy_.can_renew(); } diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 65f4db9b..d130bef8 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -1034,24 +1034,31 @@ CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id, // else we must be level 1 direct and we don't need to return a buffer. } - CdmSessionMap::iterator iter; + CdmSessionMap::iterator session_iter = sessions_.end(); if (session_id.empty()) { - // Loop through the sessions to find the session containing the key_id. - for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) { + // Loop through the sessions to find the session containing the key_id + // with the longest remaining license validity. + int64_t seconds_remaining = 0; + for (CdmSessionMap::iterator iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { if (iter->second->IsKeyLoaded(*parameters.key_id)) { - break; + int64_t duration = iter->second->GetDurationRemaining(); + if (duration > seconds_remaining) { + session_iter = iter; + seconds_remaining = duration; + } } } } else { - iter = sessions_.find(session_id); + session_iter = sessions_.find(session_id); } - if (iter == sessions_.end()) { + if (session_iter == sessions_.end()) { LOGE("CdmEngine::Decrypt: session not found: id=%s, id size=%d", session_id.c_str(), session_id.size()); return SESSION_NOT_FOUND_FOR_DECRYPT; } - return iter->second->Decrypt(parameters); + return session_iter->second->Decrypt(parameters); } bool CdmEngine::IsKeyLoaded(const KeyId& key_id) { @@ -1071,25 +1078,29 @@ bool CdmEngine::FindSessionForKey(const KeyId& key_id, return false; } - CdmSessionMap::iterator iter = sessions_.find(*session_id); - if (iter != sessions_.end()) { - if (iter->second->IsKeyLoaded(key_id)) { - return true; - } - } uint32_t session_sharing_id = Properties::GetSessionSharingId(*session_id); - for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) { + CdmSessionMap::iterator session_iter = sessions_.end(); + int64_t seconds_remaining = 0; + for (CdmSessionMap::iterator iter = sessions_.begin(); iter != sessions_.end(); ++iter) { CdmSessionId local_session_id = iter->second->session_id(); if (Properties::GetSessionSharingId(local_session_id) == session_sharing_id) { if (iter->second->IsKeyLoaded(key_id)) { - *session_id = local_session_id; - return true; + int64_t duration = iter->second->GetDurationRemaining(); + if (duration > seconds_remaining) { + session_iter = iter; + seconds_remaining = duration; + } } } } + + if (session_iter != sessions_.end()) { + *session_id = session_iter->second->session_id(); + return true; + } return false; } diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 7eb4adb7..bcd2663f 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -461,6 +461,11 @@ bool CdmSession::IsKeyLoaded(const KeyId& key_id) { return license_parser_->IsKeyLoaded(key_id); } +int64_t CdmSession::GetDurationRemaining() { + if (policy_engine_->IsLicenseForFuture()) return 0; + return policy_engine_->GetLicenseOrPlaybackDurationRemaining(); +} + CdmSessionId CdmSession::GenerateSessionId() { static int session_num = 1; return SESSION_ID_PREFIX + IntToString(++session_num); diff --git a/libwvdrmengine/cdm/core/src/policy_engine.cpp b/libwvdrmengine/cdm/core/src/policy_engine.cpp index 98a803a3..ee24fede 100644 --- a/libwvdrmengine/cdm/core/src/policy_engine.cpp +++ b/libwvdrmengine/cdm/core/src/policy_engine.cpp @@ -272,6 +272,12 @@ bool PolicyEngine::GetSecondsSinceLastPlayed( return (*seconds_since_last_played >= 0) ? true : false; } +int64_t PolicyEngine::GetLicenseOrPlaybackDurationRemaining() { + int64_t current_time = clock_->GetCurrentTime(); + return std::min(GetLicenseDurationRemaining(current_time), + GetPlaybackDurationRemaining(current_time)); +} + void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time, int64_t last_playback_time) { playback_start_time_ = (playback_start_time > 0) ? playback_start_time : 0; diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index 69ca6876..e56f2340 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -363,8 +363,9 @@ SubSampleInfo usage_info_sub_samples_icp[] = { "614f080d83f3b15cbc6600ddda43adff5d2941da13ebe49d80fd0cea5025412b"), wvcdm::a2b_hex("964c2dfda920357c668308d52d33c652"), 0, 3}}; -// License duration + fudge factor -const uint32_t kSingleEncryptedSubSampleIcpLicenseDurationExpiration = 5 + 2; +// License duration and uncertainty window +const uint32_t kSingleEncryptedSubSampleIcpLicenseDurationExpiration = 5; +const uint32_t kSingleEncryptedSubSampleIcpLicenseExpirationWindow = 2; struct SessionSharingSubSampleInfo { SubSampleInfo* sub_sample; @@ -2663,6 +2664,74 @@ TEST_P(WvCdmSessionSharingTest, SessionSharingTest) { decryptor_.CloseSession(gp_session_id_2); } +INSTANTIATE_TEST_CASE_P(Cdm, WvCdmSessionSharingTest, + ::testing::Range(&session_sharing_sub_samples[0], + &session_sharing_sub_samples[6])); + +TEST_F(WvCdmRequestLicenseTest, SessionSharingTest) { + TestWvCdmClientPropertySet property_set; + property_set.set_session_sharing_mode(true); + + // TODO(rfrias): Move content information to ConfigTestEnv + const std::string init_data1 = a2bs_hex( + "000000347073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id + "0801121030313233343536373839616263646566"); // pssh data + + decryptor_.OpenSession(g_key_system, &property_set, EMPTY_ORIGIN, NULL, + &session_id_); + CdmSessionId session_id1 = session_id_; + GenerateKeyRequest(init_data1, kLicenseTypeStreaming); + VerifyKeyRequestResponse(g_license_server, g_client_auth, false); + + // TODO(rfrias): Move content information to ConfigTestEnv + std::string gp_client_auth2 = + "?source=YOUTUBE&video_id=z3S_NhwueaM&oauth=ya.gtsqawidevine"; + std::string init_data2 = a2bs_hex( + "000000347073736800000000" // blob size and pssh + "edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id + "08011210bdf1cb4fffc6506b8b7945b0bd2917fb"); // pssh data + + decryptor_.OpenSession(g_key_system, &property_set, EMPTY_ORIGIN, NULL, + &session_id_); + CdmSessionId session_id2 = session_id_; + GenerateKeyRequest(init_data2, kLicenseTypeStreaming); + VerifyKeyRequestResponse(g_license_server, gp_client_auth2, false); + + SubSampleInfo* data = &single_encrypted_sub_sample_short_expiry; + + std::vector decrypt_buffer(data->encrypt_data.size()); + CdmDecryptionParameters decryption_parameters( + &data->key_id, &data->encrypt_data.front(), data->encrypt_data.size(), + &data->iv, data->block_offset, &decrypt_buffer[0]); + decryption_parameters.is_encrypted = data->is_encrypted; + decryption_parameters.is_secure = data->is_secure; + + EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id1, data->validate_key_id, + decryption_parameters)); + + sleep(kSingleEncryptedSubSampleIcpLicenseDurationExpiration - + kSingleEncryptedSubSampleIcpLicenseExpirationWindow); + + decryptor_.OpenSession(g_key_system, &property_set, EMPTY_ORIGIN, NULL, + &session_id_); + CdmSessionId session_id3 = session_id_; + GenerateKeyRequest(init_data1, kLicenseTypeStreaming); + VerifyKeyRequestResponse(g_license_server, g_client_auth, false); + + EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id1, data->validate_key_id, + decryption_parameters)); + + sleep(2*kSingleEncryptedSubSampleIcpLicenseExpirationWindow); + // session 1 will be expired, shared session 3 will be used to decrypt + EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id1, data->validate_key_id, + decryption_parameters)); + + decryptor_.CloseSession(session_id1); + decryptor_.CloseSession(session_id2); + decryptor_.CloseSession(session_id3); +} + TEST_F(WvCdmRequestLicenseTest, DecryptionKeyExpiredTest) { const std::string kCpKeyId = a2bs_hex( "000000347073736800000000" // blob size and pssh @@ -2683,16 +2752,13 @@ TEST_F(WvCdmRequestLicenseTest, DecryptionKeyExpiredTest) { decryption_parameters.is_secure = data->is_secure; EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id_, data->validate_key_id, decryption_parameters)); - sleep(kSingleEncryptedSubSampleIcpLicenseDurationExpiration); + sleep(kSingleEncryptedSubSampleIcpLicenseDurationExpiration + + kSingleEncryptedSubSampleIcpLicenseExpirationWindow); EXPECT_EQ(NEED_KEY, decryptor_.Decrypt(session_id_, data->validate_key_id, decryption_parameters)); decryptor_.CloseSession(session_id_); } -INSTANTIATE_TEST_CASE_P(Cdm, WvCdmSessionSharingTest, - ::testing::Range(&session_sharing_sub_samples[0], - &session_sharing_sub_samples[6])); - class WvCdmDecryptionTest : public WvCdmRequestLicenseTest, public ::testing::WithParamInterface {};