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
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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=<server_url>";
|
||||
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=<key_id>";
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user