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:
Rahul Frias
2015-09-29 08:58:23 -07:00
parent 0f579c519f
commit 9dd196e0ec
7 changed files with 75 additions and 102 deletions

View File

@@ -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);

View File

@@ -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

View File

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

View File

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

View File

@@ -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();
}

View File

@@ -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

View File

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