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:
@@ -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);
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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*> {};
|
||||
|
||||
Reference in New Issue
Block a user