From e6f668ca166b485ae727e75e3c35fcb9d9640d70 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Fri, 11 Nov 2016 18:20:27 -0800 Subject: [PATCH] Release offline release sessions [ Merge of http://go/wvgerrit/21960 ] When an offline release message is generated, a session is created internally to generate the release and handle the release response. If the response is never provided (network, server errors), or there is an error when the response is being processed, the session may not be closed. This change introduces a time to live for release sessions of 60 seconds and will be reclaimed after this period. Test: verified by unittests on angler b/32223945 Change-Id: I3bd4637733ddf6c343956ed9f97c68d84dc7d4e4 --- libwvdrmengine/cdm/core/include/cdm_engine.h | 11 +- libwvdrmengine/cdm/core/src/cdm_engine.cpp | 75 ++++++++++-- .../cdm/test/cdm_extended_duration_test.cpp | 111 +++++++++++++++++- .../cdm/test/request_license_test.cpp | 67 +++++++++++ 4 files changed, 247 insertions(+), 17 deletions(-) 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);