From 9dd196e0ec12aa1b89ddf4af92d5694d299c9733 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Tue, 29 Sep 2015 08:58:23 -0700 Subject: [PATCH] Add PolicyEngine::SetLicenseForRelease() * Add CE test for incomplete remove() [ Merge of http://go/wvgerrit/14658 ] This depends on I064c053dd986a432865163aed5c9c3493f14340b to get PolicyEngine to implement the EME semantics expressed in this test. This also excludes another error code from causing an error log in CdmEngine::AddKey, because this is actually an expected, handled error in the CE CDM and it causes some confusing noise during testing and development. * Drop CdmEngine test main [ Merge of http://go/wvgerrit/14693 ] The command-line arguments are no longer in use anywhere, and dropping the CdmEngine test's main allows me to add those tests to the CE test suite. * Add PolicyEngine::SetLicenseForRelease() [ Merge of http://go/wvgerrit/14651 ] In order to implement the EME-specified behaviors for load() & remove(), some small changes are required in PolicyEngine. According to EME, you should be able to remove() an active session. This means that releasing a persistent session is not a separate load operation. EME also states that the keys should be expired when this is done. Remove() is implemented using GenerateKeyRequest(type=release). This leads to CdmLicense::RestoreLicenseForRelease, which in turn calls PolicyEngine::SetLicense. When removing an active session, the policy engine will have keys already loaded. The old behavior would cause these keys to be reloaded. We need them to be expired, instead. Once a remove() has been started, the keys should never be loadable again. If a release confirmation is not received by the CDM, the session should still be loadable. EME states that once a session has had remove() called, then is loaded again later, there should be no keys. Not that they should be expired, but not present. The old behavior would cause these keys to be reloaded as usable. This new method allows EME remove() and load() behaviors to be faithfully implemented in the CE CDM. When removing an active session, the old keys become expired. When removing a partially- removed, newly-loaded session, no keys will be loaded at all. This change does not affect any existing tests in core/. New tests have been added in PolicyEngineTest to cover the behavior of the new method. Change-Id: Idd61487c277c9eadb4a044cb2a563e151442a548 --- .../cdm/core/include/policy_engine.h | 5 + libwvdrmengine/cdm/core/src/cdm_engine.cpp | 2 +- libwvdrmengine/cdm/core/src/license.cpp | 4 +- libwvdrmengine/cdm/core/src/policy_engine.cpp | 11 ++ .../cdm/core/test/cdm_engine_test.cpp | 113 +++--------------- .../cdm/core/test/policy_engine_unittest.cpp | 38 ++++++ libwvdrmengine/cdm/core/test/url_request.cpp | 4 +- 7 files changed, 75 insertions(+), 102 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/policy_engine.h b/libwvdrmengine/cdm/core/include/policy_engine.h index 584d790f..aefe3c1e 100644 --- a/libwvdrmengine/cdm/core/include/policy_engine.h +++ b/libwvdrmengine/cdm/core/include/policy_engine.h @@ -45,6 +45,11 @@ class PolicyEngine { // permits playback. virtual void SetLicense(const video_widevine_server::sdk::License& license); + // SetLicenseForRelease is used when releasing a license. The keys in this + // license will be ignored, and any old keys will be expired. + virtual void SetLicenseForRelease( + const video_widevine_server::sdk::License& license); + // Call this on first decrypt to set the start of playback. virtual void BeginDecryption(void); virtual void DecryptionEvent(void); diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index c7aa69c7..14fb673e 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -328,7 +328,7 @@ CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id, cert_provisioning_requested_security_level_ = iter->second->GetRequestedSecurityLevel(); } - if (sts != KEY_ADDED) { + if (sts != KEY_ADDED && sts != GET_RELEASED_LICENSE_ERROR) { LOGE("CdmEngine::RestoreKey: restore offline session failed = %d", sts); } return sts; // TODO ewew diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index 61611f0d..54962d6b 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -783,7 +783,9 @@ bool CdmLicense::RestoreLicenseForRelease( if (license.policy().has_renewal_server_url()) server_url_ = license.policy().renewal_server_url(); - policy_engine_->SetLicense(license); + // If the policy engine already has keys, they will now expire. + // If the policy engine does not already have keys, this will not add any. + policy_engine_->SetLicenseForRelease(license); return true; } diff --git a/libwvdrmengine/cdm/core/src/policy_engine.cpp b/libwvdrmengine/cdm/core/src/policy_engine.cpp index 64f4423a..98a803a3 100644 --- a/libwvdrmengine/cdm/core/src/policy_engine.cpp +++ b/libwvdrmengine/cdm/core/src/policy_engine.cpp @@ -123,6 +123,17 @@ void PolicyEngine::SetLicense(const License& license) { max_res_engine_->SetLicense(license); } +void PolicyEngine::SetLicenseForRelease(const License& license) { + license_id_.Clear(); + license_id_.CopyFrom(license.id()); + policy_.Clear(); + + // Expire any old keys. + NotifyKeysChange(kKeyStatusExpired); + + UpdateLicense(license); +} + void PolicyEngine::UpdateLicense(const License& license) { if (!license.has_policy()) return; diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp index 97c9ece5..1f1dd619 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp @@ -4,13 +4,10 @@ // This is because we need a valid RSA certificate, and will attempt to connect // to the provisioning server to request one if we don't. -#include -#include +#include #include -#include - #include "cdm_engine.h" #include "config_test_env.h" #include "initialization_data.h" @@ -45,6 +42,20 @@ const std::string kWebmMimeType = "video/webm"; class WvCdmEngineTest : public testing::Test { public: + static void SetUpTestCase() { + ConfigTestEnv config(kContentProtectionUatServer); + g_client_auth.assign(config.client_auth()); + g_key_system.assign(config.key_system()); + g_wrong_key_id.assign(config.wrong_key_id()); + g_license_server.assign(config.license_server()); + g_key_id_pssh.assign(a2bs_hex(config.key_id())); + + // Extract the key ID from the PSSH box. + InitializationData extractor(CENC_INIT_DATA_FORMAT, + g_key_id_pssh); + g_key_id_unwrapped = extractor.data(); + } + virtual void SetUp() { CdmResponseType status = cdm_engine_.OpenSession(g_key_system, NULL, EMPTY_ORIGIN, NULL, @@ -211,97 +222,3 @@ TEST_F(WvCdmEngineTest, LicenseRenewal) { } } // namespace wvcdm - -int main(int argc, char** argv) { - using namespace wvcdm; - - ::testing::InitGoogleTest(&argc, argv); - InitLogging(argc, argv); - - ConfigTestEnv config(kContentProtectionUatServer); - g_client_auth.assign(config.client_auth()); - g_key_system.assign(config.key_system()); - g_wrong_key_id.assign(config.wrong_key_id()); - - // The following variables are configurable through command line options. - g_license_server.assign(config.license_server()); - g_key_id_pssh.assign(config.key_id()); - std::string license_server(g_license_server); - - int show_usage = 0; - static const struct option long_options[] = { - {"keyid", required_argument, NULL, 'k'}, - {"server", required_argument, NULL, 's'}, - {NULL, 0, NULL, '\0'}}; - - int option_index = 0; - int opt = 0; - while ((opt = getopt_long(argc, argv, "k:s:v", long_options, - &option_index)) != -1) { - switch (opt) { - case 'k': { - g_key_id_pssh.clear(); - g_key_id_pssh.assign(optarg); - break; - } - case 's': { - g_license_server.clear(); - g_license_server.assign(optarg); - break; - } - case 'v': { - // This option _may_ have already been consumed by wvcdm::InitLogging() - // above, depending on the platform-specific logging implementation. - // We only tell getopt about it so that it is not an error. We ignore - // the option here when seen. - // TODO: Stop passing argv to InitLogging, and instead set the log - // level here through the logging API. We should keep all command-line - // parsing at the application level, rather than split between various - // apps and various platform-specific logging implementations. - break; - } - case '?': { - show_usage = 1; - break; - } - } - } - - if (show_usage) { - std::cout << std::endl; - std::cout << "usage: " << argv[0] << " [options]" << std::endl << std::endl; - std::cout << " enclose multiple arguments in '' when using adb shell" - << std::endl; - std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" - << std::endl - << std::endl; - - std::cout << std::setw(30) << std::left << " --server="; - std::cout - << "configure the license server url, please include http[s] in the url" - << std::endl; - std::cout << std::setw(30) << std::left << " "; - std::cout << "default: " << license_server << std::endl; - - std::cout << std::setw(30) << std::left << " --keyid="; - std::cout << "configure the key id or pssh, in hex format" << std::endl; - std::cout << std::setw(30) << std::left << " default keyid:"; - std::cout << g_key_id_pssh << std::endl; - return 0; - } - - std::cout << std::endl; - std::cout << "Server: " << g_license_server << std::endl; - std::cout << "KeyID: " << g_key_id_pssh << std::endl << std::endl; - - g_key_id_pssh = wvcdm::a2bs_hex(g_key_id_pssh); - config.set_license_server(g_license_server); - config.set_key_id(g_key_id_pssh); - - // Extract the key ID from the PSSH box. - wvcdm::InitializationData extractor(wvcdm::CENC_INIT_DATA_FORMAT, - g_key_id_pssh); - g_key_id_unwrapped = extractor.data(); - - return RUN_ALL_TESTS(); -} diff --git a/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp b/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp index 75a14c36..68b30b15 100644 --- a/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp @@ -1907,4 +1907,42 @@ TEST_F(PolicyEngineQueryTest, QuerySuccess_RenewWithFutureStartTime) { EXPECT_EQ(kRenewalServerUrl, query_info[QUERY_KEY_RENEWAL_SERVER_URL]); } +TEST_F(PolicyEngineTest, SetLicenseForRelease) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)); + + // No key change event will fire. + EXPECT_CALL(mock_event_listener_, + OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); + policy_engine_->SetLicenseForRelease(license_); + // No keys were loaded. + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId)); +} + +TEST_F(PolicyEngineTest, SetLicenseForReleaseAfterSetLicense) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kPlaybackStartTime)) + .WillOnce(Return(kLicenseStartTime + 10)); + + ExpectSessionKeysChange(kKeyStatusUsable, true); + EXPECT_CALL(mock_event_listener_, + OnExpirationUpdate(_, kLicenseStartTime + kLowDuration)); + EXPECT_CALL(mock_event_listener_, + OnExpirationUpdate(_, kPlaybackStartTime + kPlaybackDuration)); + + policy_engine_->SetLicense(license_); + policy_engine_->BeginDecryption(); + policy_engine_->OnTimerEvent(); + EXPECT_TRUE(policy_engine_->CanDecrypt(kKeyId)); + ::testing::Mock::VerifyAndClear(&mock_event_listener_); + + // Set the license again with use_keys set to false. + // This would happen when asking the session to generate a release message + // on an existing session. + ExpectSessionKeysChange(kKeyStatusExpired, false); + policy_engine_->SetLicenseForRelease(license_); + EXPECT_FALSE(policy_engine_->CanDecrypt(kKeyId)); +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/url_request.cpp b/libwvdrmengine/cdm/core/test/url_request.cpp index 2e652417..58f99844 100644 --- a/libwvdrmengine/cdm/core/test/url_request.cpp +++ b/libwvdrmengine/cdm/core/test/url_request.cpp @@ -109,7 +109,7 @@ bool UrlRequest::GetResponse(std::string* message) { } ConcatenateChunkedResponse(response, message); - LOGD("HTTP response: (%d): %s", message->size(), b2a_hex(*message).c_str()); + LOGV("HTTP response: (%d): %s", message->size(), b2a_hex(*message).c_str()); return true; } @@ -154,7 +154,7 @@ bool UrlRequest::PostRequestWithPath(const std::string& path, request.append(data); int ret = socket_.Write(request.c_str(), request.size(), kWriteTimeoutMs); - LOGD("HTTP request: (%d): %s", request.size(), b2a_hex(request).c_str()); + LOGV("HTTP request: (%d): %s", request.size(), b2a_hex(request).c_str()); return ret != -1; }