diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index d4582df2..f8501c11 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -6,6 +6,7 @@ #include #include "certificate_provisioning.h" +#include "clock.h" #include "crypto_session.h" #include "file_store.h" #include "initialization_data.h" @@ -23,7 +24,10 @@ class UsagePropertySet; class WvCdmEventListener; typedef std::map CdmSessionMap; -typedef std::map CdmReleaseKeySetMap; +typedef std::map< + CdmKeySetId, + std::pair > + CdmReleaseKeySetMap; class CdmEngine { public: @@ -222,12 +226,15 @@ class CdmEngine { std::string MapHdcpVersion(CryptoSession::HdcpCapability version); + void CloseExpiredReleaseSessions(); + // instance variables CdmSessionMap sessions_; CdmReleaseKeySetMap release_key_sets_; scoped_ptr cert_provisioning_; SecurityLevel cert_provisioning_requested_security_level_; FileSystem* file_system_; + Clock clock_; static bool seeded_; @@ -244,6 +251,8 @@ class CdmEngine { // occur simultaneously with OpenSession or CloseSession. Lock session_list_lock_; + Lock release_key_sets_lock_; + CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine); }; diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 0352551e..eb6859ee 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -20,6 +20,7 @@ #include "wv_cdm_event_listener.h" namespace { +const uint64_t kReleaseSessionTimeToLive = 60; // seconds const uint32_t kUpdateUsageInformationPeriod = 60; // seconds const size_t kUsageReportsPerRequest = 1; } // namespace @@ -63,8 +64,7 @@ CdmEngine::CdmEngine(FileSystem* file_system) assert(file_system); Properties::Init(); if (!seeded_) { - Clock clock; - srand(clock.GetCurrentTime()); + srand(clock_.GetCurrentTime()); seeded_ = true; } } @@ -117,6 +117,8 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, } } + CloseExpiredReleaseSessions(); + scoped_ptr new_session(new CdmSession(file_system_)); CdmResponseType sts = new_session->Init(property_set, forced_session_id, @@ -148,13 +150,24 @@ CdmResponseType CdmEngine::OpenKeySetSession( return EMPTY_KEYSET_ID_ENG_1; } + bool exists = false; + { + AutoLock lock(release_key_sets_lock_); + exists = release_key_sets_.find(key_set_id) != release_key_sets_.end(); + } + if (exists) + CloseKeySetSession(key_set_id); + CdmSessionId session_id; CdmResponseType sts = OpenSession(KEY_SYSTEM, property_set, event_listener, NULL /* forced_session_id */, &session_id); if (sts != NO_ERROR) return sts; - release_key_sets_[key_set_id] = session_id; + AutoLock lock(release_key_sets_lock_); + release_key_sets_[key_set_id] = std::make_pair(session_id, + clock_.GetCurrentTime() + kReleaseSessionTimeToLive); + return NO_ERROR; } @@ -175,19 +188,30 @@ CdmResponseType CdmEngine::CloseSession(const CdmSessionId& session_id) { CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) { LOGI("CdmEngine::CloseKeySetSession"); - CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); - if (iter == release_key_sets_.end()) { - LOGE("CdmEngine::CloseKeySetSession: key set id not found = %s", - key_set_id.c_str()); - return KEYSET_ID_NOT_FOUND_1; + CdmSessionId session_id; + { + AutoLock lock(release_key_sets_lock_); + CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); + if (iter == release_key_sets_.end()) { + LOGE("CdmEngine::CloseKeySetSession: key set id not found = %s", + key_set_id.c_str()); + return KEYSET_ID_NOT_FOUND_1; + } + session_id = iter->second.first; } - CdmResponseType sts = CloseSession(iter->second); - release_key_sets_.erase(iter); + CdmResponseType sts = CloseSession(session_id); + + AutoLock lock(release_key_sets_lock_); + CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); + if (iter != release_key_sets_.end()) { + release_key_sets_.erase(iter); + } return sts; } bool CdmEngine::IsOpenSession(const CdmSessionId& session_id) { + AutoLock lock(session_list_lock_); CdmSessionMap::iterator iter = sessions_.find(session_id); return iter != sessions_.end(); } @@ -216,6 +240,7 @@ CdmResponseType CdmEngine::GenerateKeyRequest( return INVALID_SESSION_ID; } + AutoLock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); if (iter == release_key_sets_.end()) { LOGE("CdmEngine::GenerateKeyRequest: key set ID not found = %s", @@ -223,7 +248,7 @@ CdmResponseType CdmEngine::GenerateKeyRequest( return KEYSET_ID_NOT_FOUND_2; } - id = iter->second; + id = iter->second.first; } CdmSessionMap::iterator iter = sessions_.find(id); @@ -289,13 +314,14 @@ CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id, return EMPTY_KEYSET_ID_ENG_3; } + AutoLock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(*key_set_id); if (iter == release_key_sets_.end()) { LOGE("CdmEngine::AddKey: key set id not found = %s", key_set_id->c_str()); return KEYSET_ID_NOT_FOUND_3; } - id = iter->second; + id = iter->second.first; } CdmSessionMap::iterator iter = sessions_.find(id); @@ -1286,6 +1312,8 @@ void CdmEngine::OnTimerEvent() { } } } + + CloseExpiredReleaseSessions(); } void CdmEngine::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) { @@ -1315,6 +1343,29 @@ std::string CdmEngine::MapHdcpVersion( return ""; } +void CdmEngine::CloseExpiredReleaseSessions() { + int64_t current_time = clock_.GetCurrentTime(); + + std::set close_session_set; + { + AutoLock lock(release_key_sets_lock_); + for (CdmReleaseKeySetMap::iterator iter = release_key_sets_.begin(); + iter != release_key_sets_.end();) { + if (iter->second.second < current_time) { + close_session_set.insert(iter->second.first); + release_key_sets_.erase(iter++); + } else { + ++iter; + } + } + } + + for (std::set::iterator iter = close_session_set.begin(); + iter != close_session_set.end(); ++iter) { + CloseSession(*iter); + } +} + void CdmEngine::DeleteAllUsageReportsUponFactoryReset() { std::string device_base_path_level1 = ""; std::string device_base_path_level3 = ""; diff --git a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp index 5008a7e1..6c55c06b 100644 --- a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp +++ b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp @@ -166,6 +166,14 @@ std::string kOfflineClip2PstInitData = wvcdm::a2bs_hex( "08011a0d7769646576696e655f74657374220d6f" // pssh data "66666c696e655f636c697032"); +std::string kOfflineClip4 = wvcdm::a2bs_hex( + "000000427073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000020" // Widevine system id + "08011a0d7769646576696e655f74657374220d6f" // pssh data + "66666c696e655f636c697034"); + +std::string kUatLicenseServer = "https://proxy.uat.widevine.com/proxy"; + bool StringToInt64(const std::string& input, int64_t* output) { std::istringstream ss(input); ss >> *output; @@ -286,19 +294,26 @@ class WvCdmExtendedDurationTest : public WvCdmTestBase { EXPECT_NE(0u, key_request.url.size()); } - void GenerateKeyRelease(CdmKeySetId key_set_id) { + void GenerateKeyRelease(CdmKeySetId key_set_id, + CdmResponseType expected_response) { CdmSessionId session_id; CdmInitData init_data; CdmAppParameterMap app_parameters; CdmKeyRequest key_request; - EXPECT_EQ(KEY_MESSAGE, decryptor_.GenerateKeyRequest( + EXPECT_EQ(expected_response, decryptor_.GenerateKeyRequest( session_id, key_set_id, "video/mp4", init_data, kLicenseTypeRelease, app_parameters, NULL, EMPTY_ORIGIN, &key_request)); - key_msg_ = key_request.message; - EXPECT_EQ(kKeyRequestTypeRelease, key_request.type); + if (expected_response == KEY_MESSAGE) { + key_msg_ = key_request.message; + EXPECT_EQ(kKeyRequestTypeRelease, key_request.type); + } + } + + void GenerateKeyRelease(CdmKeySetId key_set_id) { + GenerateKeyRelease(key_set_id, KEY_MESSAGE); } void LogResponseError(const std::string& message, int http_status_code) { @@ -612,6 +627,19 @@ class WvCdmExtendedDurationTest : public WvCdmTestBase { EXPECT_TRUE(StringToInt64(query_info[key], playback_duration_remaining)); } + uint32_t QueryStatus(SecurityLevel security_level, const std::string& key) { + std::string str; + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_.QueryStatus(security_level, key, &str)); + + std::istringstream ss(str); + uint32_t value; + ss >> value; + EXPECT_FALSE(ss.fail()); + EXPECT_TRUE(ss.eof()); + return value; + } + std::string GetSecurityLevel(TestWvCdmClientPropertySet* property_set) { decryptor_.OpenSession(g_key_system, property_set, EMPTY_ORIGIN, NULL, &session_id_); @@ -837,6 +865,81 @@ TEST_F(WvCdmExtendedDurationTest, UsageOverflowTest) { } } +// This test verifies that sessions allocated internally during +// key release message generation are deallocated after their +// time to live period expires. +// TODO: Disabled till b/32617908 is addressed +TEST_F(WvCdmExtendedDurationTest, DISABLED_AutomatedOfflineSessionReleaseTest) { + Unprovision(); + Provision(); + + // override default settings unless configured through the command line + std::string key_id; + std::string client_auth; + GetOfflineConfiguration(&key_id, &client_auth); + + uint32_t initial_open_sessions = + QueryStatus(kLevelDefault, wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS); + + uint32_t max_sessions = + QueryStatus(kLevelDefault, wvcdm::QUERY_KEY_MAX_NUMBER_OF_SESSIONS); + + uint32_t num_key_set_ids = max_sessions - initial_open_sessions; + if (num_key_set_ids > kMaxUsageTableSize) + num_key_set_ids = kMaxUsageTableSize; + + std::set key_set_id_map; + for (uint32_t i = 0; i < num_key_set_ids; ++i) { + decryptor_.OpenSession(g_key_system, NULL, EMPTY_ORIGIN, NULL, + &session_id_); + GenerateKeyRequest(kOfflineClip4, kLicenseTypeOffline); + VerifyKeyRequestResponse(kUatLicenseServer, client_auth, false); + + EXPECT_FALSE(key_set_id_.empty()); + decryptor_.CloseSession(session_id_); + key_set_id_map.insert(key_set_id_); + } + + std::set::iterator iter; + for (iter = key_set_id_map.begin(); iter != key_set_id_map.end(); ++iter) { + session_id_.clear(); + key_set_id_.clear(); + decryptor_.OpenSession(g_key_system, NULL, EMPTY_ORIGIN, NULL, + &session_id_); + EXPECT_EQ(wvcdm::KEY_ADDED, decryptor_.RestoreKey(session_id_, *iter)); + decryptor_.CloseSession(session_id_); + } + + for (iter = key_set_id_map.begin(); iter != key_set_id_map.end(); ++iter) { + session_id_.clear(); + GenerateKeyRelease(*iter); + } + + uint32_t open_sessions = + QueryStatus(kLevelDefault, wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS); + + EXPECT_EQ(open_sessions, key_set_id_map.size() + initial_open_sessions); + + sleep(kMinute + kClockTolerance); + + iter = key_set_id_map.begin(); + session_id_.clear(); + GenerateKeyRelease(*iter); + + open_sessions = + QueryStatus(kLevelDefault, wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS); + + EXPECT_GE(open_sessions, initial_open_sessions); + EXPECT_LE(open_sessions - initial_open_sessions, key_set_id_map.size()); + + for (iter = key_set_id_map.begin(); iter != key_set_id_map.end(); ++iter) { + session_id_.clear(); + GenerateKeyRelease(*iter); + key_set_id_ = *iter; + VerifyKeyRequestResponse(kUatLicenseServer, client_auth, false); + } +} + class WvCdmStreamingNoPstTest : public WvCdmExtendedDurationTest, public ::testing::WithParamInterface {}; diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index c528b9e5..c523421a 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -1218,6 +1218,19 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { return security_level; } + uint32_t QueryStatus(SecurityLevel security_level, const std::string& key) { + std::string str; + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_.QueryStatus(security_level, key, &str)); + + std::istringstream ss(str); + uint32_t value; + ss >> value; + EXPECT_FALSE(ss.fail()); + EXPECT_TRUE(ss.eof()); + return value; + } + wvcdm::WvContentDecryptionModule decryptor_; CdmKeyMessage key_msg_; CdmSessionId session_id_; @@ -1860,6 +1873,60 @@ TEST_F(WvCdmRequestLicenseTest, ExpiryOnReleaseOfflineKeyTest) { decryptor_.CloseSession(restore_session_id); } +// This test verifies that repeated generation of the key release message +// for the same key_set_id results in the previous session being +// deallocated (rather than leaked) and a new one allocated. +TEST_F(WvCdmRequestLicenseTest, AutomatedOfflineSessionReleaseTest) { + Unprovision(); + Provision(kLevelDefault); + + // override default settings unless configured through the command line + std::string key_id; + std::string client_auth; + GetOfflineConfiguration(&key_id, &client_auth); + + decryptor_.OpenSession(g_key_system, NULL, EMPTY_ORIGIN, NULL, &session_id_); + GenerateKeyRequest(key_id, kLicenseTypeOffline); + VerifyKeyRequestResponse(g_license_server, client_auth, false); + + CdmKeySetId key_set_id = key_set_id_; + EXPECT_FALSE(key_set_id_.empty()); + decryptor_.CloseSession(session_id_); + + session_id_.clear(); + key_set_id_.clear(); + decryptor_.OpenSession(g_key_system, NULL, EMPTY_ORIGIN, NULL, &session_id_); + EXPECT_EQ(wvcdm::KEY_ADDED, decryptor_.RestoreKey(session_id_, key_set_id)); + decryptor_.CloseSession(session_id_); + + uint32_t open_sessions = + QueryStatus(kLevelDefault, wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS); + + session_id_.clear(); + key_set_id_.clear(); + GenerateKeyRelease(key_set_id); + key_set_id_ = key_set_id; + + EXPECT_EQ( + ++open_sessions, + QueryStatus(kLevelDefault, wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS)); + + session_id_.clear(); + key_set_id_.clear(); + GenerateKeyRelease(key_set_id); + key_set_id_ = key_set_id; + + EXPECT_EQ( + open_sessions, + QueryStatus(kLevelDefault, wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS)); + + VerifyKeyRequestResponse(g_license_server, client_auth, false); + + EXPECT_EQ( + --open_sessions, + QueryStatus(kLevelDefault, wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS)); +} + TEST_F(WvCdmRequestLicenseTest, StreamingLicenseRenewal) { decryptor_.OpenSession(g_key_system, NULL, EMPTY_ORIGIN, NULL, &session_id_); GenerateKeyRequest(g_key_id, kLicenseTypeStreaming);