Commit 6db0788f authored by Alex St-Onge's avatar Alex St-Onge Committed by Commit Bot

Add remove PUR session with no decrypt

This change adds a test that create and then remove a
persistent-usage-record session and verifies that the record of key
usage is present in the license-release message and are both 'null'

Bug: 1102976
Change-Id: I766a3104d84b1e477e54c65f30e62aa5645bd426
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2308173
Commit-Queue: Alex St-Onge <alstonge@chromium.org>
Reviewed-by: default avatarXiaohan Wang <xhwang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#790657}
parent 8de851ba
...@@ -885,6 +885,13 @@ IN_PROC_BROWSER_TEST_P(ECKEncryptedMediaTest, VerifyPersistentUsageRecord) { ...@@ -885,6 +885,13 @@ IN_PROC_BROWSER_TEST_P(ECKEncryptedMediaTest, VerifyPersistentUsageRecord) {
media::kEnded); media::kEnded);
} }
IN_PROC_BROWSER_TEST_P(ECKEncryptedMediaTest, RemovePersistentUsageRecord) {
RunEncryptedMediaTest("eme_remove_session_test.html",
"bear-320x240-v_enc-v.webm", kExternalClearKeyKeySystem,
SrcType::MSE, kPersistentUsageRecord, false,
PlayCount::ONCE, media::kEnded);
}
const char kExternalClearKeyDecryptOnlyKeySystem[] = const char kExternalClearKeyDecryptOnlyKeySystem[] =
"org.chromium.externalclearkey.decryptonly"; "org.chromium.externalclearkey.decryptonly";
......
...@@ -644,14 +644,14 @@ CdmKeysInfo AesDecryptor::GenerateKeysInfoList( ...@@ -644,14 +644,14 @@ CdmKeysInfo AesDecryptor::GenerateKeysInfoList(
return keys_info; return keys_info;
} }
void AesDecryptor::GetRecordOfKeyUsage(const std::string& session_id, bool AesDecryptor::GetRecordOfKeyUsage(const std::string& session_id,
KeyIdList& key_ids, KeyIdList& key_ids,
base::Time& first_decryption_time, base::Time& first_decryption_time,
base::Time& latest_decryption_time) { base::Time& latest_decryption_time) {
auto it = open_sessions_.find(session_id); auto it = open_sessions_.find(session_id);
if (it == open_sessions_.end() || if (it == open_sessions_.end() ||
it->second != CdmSessionType::kPersistentUsageRecord) { it->second != CdmSessionType::kPersistentUsageRecord) {
return; return false;
} }
base::AutoLock auto_lock(key_map_lock_); base::AutoLock auto_lock(key_map_lock_);
...@@ -663,6 +663,8 @@ void AesDecryptor::GetRecordOfKeyUsage(const std::string& session_id, ...@@ -663,6 +663,8 @@ void AesDecryptor::GetRecordOfKeyUsage(const std::string& session_id,
first_decryption_time = first_decryption_time_; first_decryption_time = first_decryption_time_;
latest_decryption_time = latest_decryption_time_; latest_decryption_time = latest_decryption_time_;
return true;
} }
AesDecryptor::DecryptionKey::DecryptionKey(const std::string& secret) AesDecryptor::DecryptionKey::DecryptionKey(const std::string& secret)
......
...@@ -176,9 +176,10 @@ class MEDIA_EXPORT AesDecryptor : public ContentDecryptionModule, ...@@ -176,9 +176,10 @@ class MEDIA_EXPORT AesDecryptor : public ContentDecryptionModule,
CdmKeysInfo GenerateKeysInfoList(const std::string& session_id, CdmKeysInfo GenerateKeysInfoList(const std::string& session_id,
CdmKeyInformation::KeyStatus status); CdmKeyInformation::KeyStatus status);
// Returns the record of key usage for persistent-usage-record session. Used // Gets the record of key usage for persistent-usage-record session. Used
// by ClearKeyPersistentSessionCdm. // by ClearKeyPersistentSessionCdm.
void GetRecordOfKeyUsage(const std::string& session_id, // Returns false if the session type is not Persistent-Usage-Record.
bool GetRecordOfKeyUsage(const std::string& session_id,
KeyIdList& key_ids, KeyIdList& key_ids,
base::Time& first_decryption_time, base::Time& first_decryption_time,
base::Time& latest_decryption_time); base::Time& latest_decryption_time);
......
...@@ -33,6 +33,8 @@ const char kTypeTag[] = "type"; ...@@ -33,6 +33,8 @@ const char kTypeTag[] = "type";
const char kTemporarySession[] = "temporary"; const char kTemporarySession[] = "temporary";
const char kPersistentLicenseSession[] = "persistent-license"; const char kPersistentLicenseSession[] = "persistent-license";
const char kPersistentUsageRecordSession[] = "persistent-usage-record"; const char kPersistentUsageRecordSession[] = "persistent-usage-record";
const char kPersistentUsageRecordFirstTime[] = "firstTime";
const char kPersistentUsageRecordLatestTime[] = "latestTime";
static std::string ShortenTo64Characters(const std::string& input) { static std::string ShortenTo64Characters(const std::string& input) {
// Convert |input| into a string with escaped characters replacing any // Convert |input| into a string with escaped characters replacing any
...@@ -69,6 +71,12 @@ static std::unique_ptr<base::DictionaryValue> CreateJSONDictionary( ...@@ -69,6 +71,12 @@ static std::unique_ptr<base::DictionaryValue> CreateJSONDictionary(
return jwk; return jwk;
} }
// base::DictionaryValue::Set() does not accept nullptr. A 'null' Value must
// be used instead if we want to add a key to a JSON with a 'null' value.
static std::unique_ptr<base::Value> GetNullValue() {
return std::make_unique<base::Value>(base::Value::Type::NONE);
}
std::string GenerateJWKSet(const uint8_t* key, std::string GenerateJWKSet(const uint8_t* key,
int key_length, int key_length,
const uint8_t* key_id, const uint8_t* key_id,
...@@ -380,7 +388,13 @@ void CreateKeyIdsInitData(const KeyIdList& key_ids, ...@@ -380,7 +388,13 @@ void CreateKeyIdsInitData(const KeyIdList& key_ids,
// "kids" // "kids"
// An array of key IDs. Each element of the array is the base64url encoding // An array of key IDs. Each element of the array is the base64url encoding
// of the octet sequence containing the key ID value. // of the octet sequence containing the key ID value.
// std::vector<uint8_t> CreateLicenseReleaseMessage(const KeyIdList& key_ids) {
// Create the init_data.
auto dictionary = std::make_unique<base::DictionaryValue>();
AddKeyIdsToDictionary(key_ids, dictionary.get());
return SerializeDictionaryToVector(dictionary.get());
}
// For sessions of type "persistent-usage-record" the object shall also contain // For sessions of type "persistent-usage-record" the object shall also contain
// the following members: // the following members:
// //
...@@ -399,13 +413,20 @@ std::vector<uint8_t> CreateLicenseReleaseMessage( ...@@ -399,13 +413,20 @@ std::vector<uint8_t> CreateLicenseReleaseMessage(
auto dictionary = std::make_unique<base::DictionaryValue>(); auto dictionary = std::make_unique<base::DictionaryValue>();
AddKeyIdsToDictionary(key_ids, dictionary.get()); AddKeyIdsToDictionary(key_ids, dictionary.get());
if (!first_decrypt_time.is_null() && !latest_decrypt_time.is_null()) { if (!first_decrypt_time.is_null()) {
// Persistent-Usage-Record // Persistent-Usage-Record
// Time need to be millisecond since 01 January, 1970 UTC // Time need to be millisecond since 01 January, 1970 UTC
dictionary->SetDouble("firstTime", dictionary->SetDouble(kPersistentUsageRecordFirstTime,
first_decrypt_time.ToJsTimeIgnoringNull()); first_decrypt_time.ToJsTimeIgnoringNull());
dictionary->SetDouble("latestTime", } else {
dictionary->Set(kPersistentUsageRecordFirstTime, GetNullValue());
}
if (!latest_decrypt_time.is_null()) {
dictionary->SetDouble(kPersistentUsageRecordLatestTime,
latest_decrypt_time.ToJsTimeIgnoringNull()); latest_decrypt_time.ToJsTimeIgnoringNull());
} else {
dictionary->Set(kPersistentUsageRecordLatestTime, GetNullValue());
} }
return SerializeDictionaryToVector(dictionary.get()); return SerializeDictionaryToVector(dictionary.get());
......
...@@ -95,10 +95,13 @@ MEDIA_EXPORT void CreateLicenseRequest(const KeyIdList& key_ids, ...@@ -95,10 +95,13 @@ MEDIA_EXPORT void CreateLicenseRequest(const KeyIdList& key_ids,
MEDIA_EXPORT void CreateKeyIdsInitData(const KeyIdList& key_ids, MEDIA_EXPORT void CreateKeyIdsInitData(const KeyIdList& key_ids,
std::vector<uint8_t>* key_ids_init_data); std::vector<uint8_t>* key_ids_init_data);
MEDIA_EXPORT std::vector<uint8_t> CreateLicenseReleaseMessage(
const KeyIdList& key_ids);
MEDIA_EXPORT std::vector<uint8_t> CreateLicenseReleaseMessage( MEDIA_EXPORT std::vector<uint8_t> CreateLicenseReleaseMessage(
const KeyIdList& key_ids, const KeyIdList& key_ids,
const base::Time first_decrypt_time = base::Time(), const base::Time first_decrypt_time,
const base::Time latest_decrypt_time = base::Time()); const base::Time latest_decrypt_time);
// Extract the first key from the license request message. Returns true if // Extract the first key from the license request message. Returns true if
// |license| is a valid license request and contains at least one key, // |license| is a valid license request and contains at least one key,
......
...@@ -303,8 +303,9 @@ void ClearKeyPersistentSessionCdm::RemoveSession( ...@@ -303,8 +303,9 @@ void ClearKeyPersistentSessionCdm::RemoveSession(
// Not a persistent session, so simply pass the request on. // Not a persistent session, so simply pass the request on.
// TODO(crbug.com/1102976) Add test for loading PUR session // TODO(crbug.com/1102976) Add test for loading PUR session
// Query the record of key usage before calling remove as RemoveSession will // TODO(crbug.com/1107614) Move session message for persistent-license
// delete the keys. Steps from // session to ClearKeyPersistentSessionCdm Query the record of key usage
// before calling remove as RemoveSession will delete the keys. Steps from
// https://w3c.github.io/encrypted-media/#remove. 4.4.1.2 Follow the steps // https://w3c.github.io/encrypted-media/#remove. 4.4.1.2 Follow the steps
// for the value of this object's session type // for the value of this object's session type
// "persistent-usage-record" // "persistent-usage-record"
...@@ -313,12 +314,12 @@ void ClearKeyPersistentSessionCdm::RemoveSession( ...@@ -313,12 +314,12 @@ void ClearKeyPersistentSessionCdm::RemoveSession(
KeyIdList key_ids; KeyIdList key_ids;
base::Time first_decryption_time; base::Time first_decryption_time;
base::Time latest_decryption_time; base::Time latest_decryption_time;
cdm_->GetRecordOfKeyUsage(session_id, key_ids, first_decryption_time, bool is_persistent_usage_record_session = cdm_->GetRecordOfKeyUsage(
latest_decryption_time); session_id, key_ids, first_decryption_time, latest_decryption_time);
cdm_->RemoveSession(session_id, std::move(promise)); cdm_->RemoveSession(session_id, std::move(promise));
// Both times will be null if the session type is not PUR. // Both times will be null if the session type is not PUR.
if (!first_decryption_time.is_null() && !latest_decryption_time.is_null()) { if (is_persistent_usage_record_session) {
std::vector<uint8_t> message = CreateLicenseReleaseMessage( std::vector<uint8_t> message = CreateLicenseReleaseMessage(
key_ids, first_decryption_time, latest_decryption_time); key_ids, first_decryption_time, latest_decryption_time);
// EME spec specifies that the message event should be fired before the // EME spec specifies that the message event should be fired before the
......
...@@ -349,7 +349,7 @@ PlayerUtils.removeSession = async function(player) { ...@@ -349,7 +349,7 @@ PlayerUtils.removeSession = async function(player) {
Utils.timeLog(e.messageType); Utils.timeLog(e.messageType);
if (e.messageType == 'license-release' && if (e.messageType == 'license-release' &&
player.testConfig.sessionToLoad == 'PersistentUsageRecord') { player.testConfig.sessionToLoad == 'PersistentUsageRecord') {
Utils.verifyUsageRecord(e.message); Utils.verifyUsageRecord(e.message, /* expectNullTime= */ false);
} }
// TODO: verify license-release message for persistent-license session // TODO: verify license-release message for persistent-license session
resolve(); resolve();
......
...@@ -107,20 +107,26 @@ Utils.extractFirstLicenseKeyId = function(message) { ...@@ -107,20 +107,26 @@ Utils.extractFirstLicenseKeyId = function(message) {
}; };
Utils.verifyUsageRecord = Utils.verifyUsageRecord =
function(message) { function(message, expectNullTime) {
try { try {
var json = JSON.parse(convertToString(message)); var json = JSON.parse(convertToString(message));
var first_decrypt_time = new Date(json.firstTime); if (expectNullTime && (json.firstTime != null || json.latestTime != null)) {
var last_decrypt_time = new Date(json.latestTime);
Utils.timeLog('First decrypt time: ' + first_decrypt_time.toISOString());
Utils.timeLog('Last decrypt time: ' + last_decrypt_time.toISOString());
var delta = json.latestTime - json.firstTime;
// The video used for the tests is roughly 2.5 seconds.
if (delta < 2000 || delta > 3000) {
Utils.failTest( Utils.failTest(
'The usage record reported by the CDM was not in the' + 'Expecting null time for usage record but got firstTime=' +
'expected range') json.firstTime + ', latestTime=' + json.latestTime);
} else if (!expectNullTime) {
var first_decrypt_time = new Date(json.firstTime);
var last_decrypt_time = new Date(json.latestTime);
Utils.timeLog('First decrypt time: ' + first_decrypt_time.toISOString());
Utils.timeLog('Last decrypt time: ' + last_decrypt_time.toISOString());
var delta = json.latestTime - json.firstTime;
// The video used for the tests is roughly 2.5 seconds.
if (delta < 2000 || delta > 3000) {
Utils.failTest(
'The usage record reported by the CDM was not in the ' +
'expected range')
}
} }
} catch (error) { } catch (error) {
......
...@@ -44,6 +44,11 @@ ...@@ -44,6 +44,11 @@
event, resolve, reject) { event, resolve, reject) {
var mediaKeySession = event.target; var mediaKeySession = event.target;
if (event.messageType == "license-release" &&
testConfig.sessionToLoad == 'PersistentUsageRecord') {
Utils.verifyUsageRecord(event.message, /* expectNullTime= */ true);
}
Utils.timeLog('Calling update()'); Utils.timeLog('Calling update()');
const jwkSet = Utils.createJWKData(keyId, key); const jwkSet = Utils.createJWKData(keyId, key);
mediaKeySession.update(jwkSet).then(resolve, reject); mediaKeySession.update(jwkSet).then(resolve, reject);
...@@ -63,7 +68,7 @@ ...@@ -63,7 +68,7 @@
// This test doesn't play any media, so no concern with specifying multiple // This test doesn't play any media, so no concern with specifying multiple
// codecs. This is done to provide a set of codecs that should cover all // codecs. This is done to provide a set of codecs that should cover all
// user agents. // user agents.
const config = [{ config = [{
initDataTypes: ['webm'], initDataTypes: ['webm'],
audioCapabilities: [ audioCapabilities: [
{contentType: 'audio/mp4; codecs="mp4a.40.2"'}, {contentType: 'audio/mp4; codecs="mp4a.40.2"'},
...@@ -73,6 +78,10 @@ ...@@ -73,6 +78,10 @@
sessionTypes: ['temporary'], sessionTypes: ['temporary'],
}]; }];
if (testConfig.sessionToLoad == "PersistentUsageRecord") {
config[0].sessionTypes.push('persistent-usage-record');
}
var mediaKeySession; var mediaKeySession;
navigator.requestMediaKeySystemAccess(testConfig.keySystem, config) navigator.requestMediaKeySystemAccess(testConfig.keySystem, config)
.then(function(access) { .then(function(access) {
...@@ -83,7 +92,11 @@ ...@@ -83,7 +92,11 @@
}) })
.then(function(mediaKeys) { .then(function(mediaKeys) {
Utils.timeLog('Creating session'); Utils.timeLog('Creating session');
mediaKeySession = mediaKeys.createSession(); if (testConfig.sessionToLoad == "PersistentUsageRecord") {
mediaKeySession = mediaKeys.createSession("persistent-usage-record");
} else {
mediaKeySession = mediaKeys.createSession();
}
// Register for the 'message' event before it happens. Although the // Register for the 'message' event before it happens. Although the
// event shouldn't be generated until after the generateRequest() // event shouldn't be generated until after the generateRequest()
...@@ -123,15 +136,22 @@ ...@@ -123,15 +136,22 @@
Utils.verifyKeyStatuses( Utils.verifyKeyStatuses(
mediaKeySession.keyStatuses, [{keyId: keyId, status: 'usable'}]); mediaKeySession.keyStatuses, [{keyId: keyId, status: 'usable'}]);
promises = [];
// Once remove() is called, another 'keystatuseschange' event will // Once remove() is called, another 'keystatuseschange' event will
// happen. // happen.
const waitForKeyStatusChangePromise = promises.push(Utils.waitForEvent(mediaKeySession, 'keystatuseschange'));
Utils.waitForEvent(mediaKeySession, 'keystatuseschange');
// For persistent-usage-record session, a license-release "message"
// event will happen.
if (testConfig.sessionToLoad == "PersistentUsageRecord") {
promises.push(Utils.waitForEvent(
mediaKeySession, 'message', keySystemHelper.onMessage));
}
Utils.timeLog('Calling remove()'); Utils.timeLog('Calling remove()');
const removePromise = mediaKeySession.remove(); promises.push(mediaKeySession.remove());
return Promise.all([removePromise, waitForKeyStatusChangePromise]); return Promise.all(promises);
}) })
.then(function() { .then(function() {
// After remove() all keys should be 'released'. // After remove() all keys should be 'released'.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment