Use session with longest remaining duration when session sharing is used.

[ Merge of https://go/wvgerrit/16940 ]

An alternate scenario to renewing keys is to load the same keys in
a separate session and make use of them by using the session sharing
feature.

Session sharing involves iterating through a map of sessions and
returning the first session that contains the Key ID. In certain cases
(license about to expire) we might prefer an alternate session
be chosen.

Licenses may expire in two ways. Policy engine, driven by a 1 second
timer may detect expiry and send an asynchronous event. OEMCrypto may
also detect expiry based on information in the key control block
and return an error during decryption. It is possible that these
may differ by upto a second. This can lead to issues where decryption
fails but EVENT_KEY_EXPIRED is not generated till later.

It is possible to address this by using information from both timers
to notify the app about expiry. To implement this correctly will
add complexity and require synchronization between threads. To avoid
this an alternate solution is, if session sharing is used, to pick
the session that has a license with the longest remaining validity.

b/27041140

Change-Id: I398cc4c10ee3a2f192d4a0befe7c8a469dd5bf86
This commit is contained in:
Rahul Frias
2016-02-24 10:13:37 -08:00
parent 8f6f46aee9
commit aefd104981
6 changed files with 113 additions and 23 deletions

View File

@@ -91,6 +91,7 @@ class CdmSession {
virtual CdmResponseType ReleaseKey(const CdmKeyResponse& key_response);
virtual bool IsKeyLoaded(const KeyId& key_id);
virtual int64_t GetDurationRemaining();
// Used for notifying the Policy Engine of resolution changes
virtual void NotifyResolution(uint32_t width, uint32_t height);

View File

@@ -84,6 +84,7 @@ class PolicyEngine {
bool IsPlaybackStarted() { return playback_start_time_ > 0; }
bool IsLicenseOrPlaybackDurationExpired(int64_t current_time);
int64_t GetLicenseOrPlaybackDurationRemaining();
bool CanRenew() { return policy_.can_renew(); }

View File

@@ -1034,24 +1034,31 @@ CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id,
// else we must be level 1 direct and we don't need to return a buffer.
}
CdmSessionMap::iterator iter;
CdmSessionMap::iterator session_iter = sessions_.end();
if (session_id.empty()) {
// Loop through the sessions to find the session containing the key_id.
for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) {
// Loop through the sessions to find the session containing the key_id
// with the longest remaining license validity.
int64_t seconds_remaining = 0;
for (CdmSessionMap::iterator iter = sessions_.begin();
iter != sessions_.end(); ++iter) {
if (iter->second->IsKeyLoaded(*parameters.key_id)) {
break;
int64_t duration = iter->second->GetDurationRemaining();
if (duration > seconds_remaining) {
session_iter = iter;
seconds_remaining = duration;
}
}
}
} else {
iter = sessions_.find(session_id);
session_iter = sessions_.find(session_id);
}
if (iter == sessions_.end()) {
if (session_iter == sessions_.end()) {
LOGE("CdmEngine::Decrypt: session not found: id=%s, id size=%d",
session_id.c_str(), session_id.size());
return SESSION_NOT_FOUND_FOR_DECRYPT;
}
return iter->second->Decrypt(parameters);
return session_iter->second->Decrypt(parameters);
}
bool CdmEngine::IsKeyLoaded(const KeyId& key_id) {
@@ -1071,25 +1078,29 @@ bool CdmEngine::FindSessionForKey(const KeyId& key_id,
return false;
}
CdmSessionMap::iterator iter = sessions_.find(*session_id);
if (iter != sessions_.end()) {
if (iter->second->IsKeyLoaded(key_id)) {
return true;
}
}
uint32_t session_sharing_id = Properties::GetSessionSharingId(*session_id);
for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) {
CdmSessionMap::iterator session_iter = sessions_.end();
int64_t seconds_remaining = 0;
for (CdmSessionMap::iterator iter = sessions_.begin(); iter != sessions_.end(); ++iter) {
CdmSessionId local_session_id = iter->second->session_id();
if (Properties::GetSessionSharingId(local_session_id) ==
session_sharing_id) {
if (iter->second->IsKeyLoaded(key_id)) {
*session_id = local_session_id;
return true;
int64_t duration = iter->second->GetDurationRemaining();
if (duration > seconds_remaining) {
session_iter = iter;
seconds_remaining = duration;
}
}
}
}
if (session_iter != sessions_.end()) {
*session_id = session_iter->second->session_id();
return true;
}
return false;
}

View File

@@ -461,6 +461,11 @@ bool CdmSession::IsKeyLoaded(const KeyId& key_id) {
return license_parser_->IsKeyLoaded(key_id);
}
int64_t CdmSession::GetDurationRemaining() {
if (policy_engine_->IsLicenseForFuture()) return 0;
return policy_engine_->GetLicenseOrPlaybackDurationRemaining();
}
CdmSessionId CdmSession::GenerateSessionId() {
static int session_num = 1;
return SESSION_ID_PREFIX + IntToString(++session_num);

View File

@@ -272,6 +272,12 @@ bool PolicyEngine::GetSecondsSinceLastPlayed(
return (*seconds_since_last_played >= 0) ? true : false;
}
int64_t PolicyEngine::GetLicenseOrPlaybackDurationRemaining() {
int64_t current_time = clock_->GetCurrentTime();
return std::min(GetLicenseDurationRemaining(current_time),
GetPlaybackDurationRemaining(current_time));
}
void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time,
int64_t last_playback_time) {
playback_start_time_ = (playback_start_time > 0) ? playback_start_time : 0;

View File

@@ -363,8 +363,9 @@ SubSampleInfo usage_info_sub_samples_icp[] = {
"614f080d83f3b15cbc6600ddda43adff5d2941da13ebe49d80fd0cea5025412b"),
wvcdm::a2b_hex("964c2dfda920357c668308d52d33c652"), 0, 3}};
// License duration + fudge factor
const uint32_t kSingleEncryptedSubSampleIcpLicenseDurationExpiration = 5 + 2;
// License duration and uncertainty window
const uint32_t kSingleEncryptedSubSampleIcpLicenseDurationExpiration = 5;
const uint32_t kSingleEncryptedSubSampleIcpLicenseExpirationWindow = 2;
struct SessionSharingSubSampleInfo {
SubSampleInfo* sub_sample;
@@ -2663,6 +2664,74 @@ TEST_P(WvCdmSessionSharingTest, SessionSharingTest) {
decryptor_.CloseSession(gp_session_id_2);
}
INSTANTIATE_TEST_CASE_P(Cdm, WvCdmSessionSharingTest,
::testing::Range(&session_sharing_sub_samples[0],
&session_sharing_sub_samples[6]));
TEST_F(WvCdmRequestLicenseTest, SessionSharingTest) {
TestWvCdmClientPropertySet property_set;
property_set.set_session_sharing_mode(true);
// TODO(rfrias): Move content information to ConfigTestEnv
const std::string init_data1 = a2bs_hex(
"000000347073736800000000" // blob size and pssh
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
"0801121030313233343536373839616263646566"); // pssh data
decryptor_.OpenSession(g_key_system, &property_set, EMPTY_ORIGIN, NULL,
&session_id_);
CdmSessionId session_id1 = session_id_;
GenerateKeyRequest(init_data1, kLicenseTypeStreaming);
VerifyKeyRequestResponse(g_license_server, g_client_auth, false);
// TODO(rfrias): Move content information to ConfigTestEnv
std::string gp_client_auth2 =
"?source=YOUTUBE&video_id=z3S_NhwueaM&oauth=ya.gtsqawidevine";
std::string init_data2 = a2bs_hex(
"000000347073736800000000" // blob size and pssh
"edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id
"08011210bdf1cb4fffc6506b8b7945b0bd2917fb"); // pssh data
decryptor_.OpenSession(g_key_system, &property_set, EMPTY_ORIGIN, NULL,
&session_id_);
CdmSessionId session_id2 = session_id_;
GenerateKeyRequest(init_data2, kLicenseTypeStreaming);
VerifyKeyRequestResponse(g_license_server, gp_client_auth2, false);
SubSampleInfo* data = &single_encrypted_sub_sample_short_expiry;
std::vector<uint8_t> decrypt_buffer(data->encrypt_data.size());
CdmDecryptionParameters decryption_parameters(
&data->key_id, &data->encrypt_data.front(), data->encrypt_data.size(),
&data->iv, data->block_offset, &decrypt_buffer[0]);
decryption_parameters.is_encrypted = data->is_encrypted;
decryption_parameters.is_secure = data->is_secure;
EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id1, data->validate_key_id,
decryption_parameters));
sleep(kSingleEncryptedSubSampleIcpLicenseDurationExpiration -
kSingleEncryptedSubSampleIcpLicenseExpirationWindow);
decryptor_.OpenSession(g_key_system, &property_set, EMPTY_ORIGIN, NULL,
&session_id_);
CdmSessionId session_id3 = session_id_;
GenerateKeyRequest(init_data1, kLicenseTypeStreaming);
VerifyKeyRequestResponse(g_license_server, g_client_auth, false);
EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id1, data->validate_key_id,
decryption_parameters));
sleep(2*kSingleEncryptedSubSampleIcpLicenseExpirationWindow);
// session 1 will be expired, shared session 3 will be used to decrypt
EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id1, data->validate_key_id,
decryption_parameters));
decryptor_.CloseSession(session_id1);
decryptor_.CloseSession(session_id2);
decryptor_.CloseSession(session_id3);
}
TEST_F(WvCdmRequestLicenseTest, DecryptionKeyExpiredTest) {
const std::string kCpKeyId = a2bs_hex(
"000000347073736800000000" // blob size and pssh
@@ -2683,16 +2752,13 @@ TEST_F(WvCdmRequestLicenseTest, DecryptionKeyExpiredTest) {
decryption_parameters.is_secure = data->is_secure;
EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id_, data->validate_key_id,
decryption_parameters));
sleep(kSingleEncryptedSubSampleIcpLicenseDurationExpiration);
sleep(kSingleEncryptedSubSampleIcpLicenseDurationExpiration +
kSingleEncryptedSubSampleIcpLicenseExpirationWindow);
EXPECT_EQ(NEED_KEY, decryptor_.Decrypt(session_id_, data->validate_key_id,
decryption_parameters));
decryptor_.CloseSession(session_id_);
}
INSTANTIATE_TEST_CASE_P(Cdm, WvCdmSessionSharingTest,
::testing::Range(&session_sharing_sub_samples[0],
&session_sharing_sub_samples[6]));
class WvCdmDecryptionTest
: public WvCdmRequestLicenseTest,
public ::testing::WithParamInterface<SubSampleInfo*> {};