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; }