Integration tests: add license holder

Merged from http://go/wvgerrit/146154

Many integration tests require a license from a license server. This
CL creates a helper class to fetch, load, and hold a license.

Test: ./build_and_run_all_unit_tests.sh

Bug: 194342800
Bug: 194342778
Change-Id: I0de7bcab4db1b365f074bad29fc157a5eca135d8
This commit is contained in:
Edwin
2022-03-10 09:28:23 -08:00
committed by Rahul Frias
parent 6cda6717a9
commit 3da1d24a1d
5 changed files with 399 additions and 186 deletions

View File

@@ -18,6 +18,7 @@
#include "device_files.h"
#include "file_store.h"
#include "initialization_data.h"
#include "license_holder.h"
#include "license_request.h"
#include "log.h"
#include "properties.h"
@@ -387,14 +388,11 @@ TEST_F(WvCdmEngineTest, DISABLED_NormalDecryptionWebm) {
TEST_F(WvCdmEngineTest, LoadKey) {
EnsureProvisioned();
TestLicenseHolder holder(&cdm_engine_);
holder.OpenSession(config_.key_system());
holder.GenerateKeyRequest(binary_key_id(), ISO_BMFF_VIDEO_MIME_TYPE);
holder.CreateDefaultLicense();
std::vector<uint8_t> key_data(CONTENT_KEY_SIZE, '1');
wvoec::KeyControlBlock block = {};
holder.AddKey("key_one", key_data, block);
holder.SignAndLoadLicense();
LicenseHolder holder("CDM_Streaming", &cdm_engine_, config_);
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
ASSERT_NO_FATAL_FAILURE(holder.FetchLicense());
ASSERT_NO_FATAL_FAILURE(holder.LoadLicense());
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
}
// This test generates a renewal and then requests the renewal using the server
@@ -402,6 +400,7 @@ TEST_F(WvCdmEngineTest, LoadKey) {
// skip this test when you want to set the license and renewal server on the
// command line.
TEST_F(WvCdmEngineTest, LicenseRenewalSpecifiedServer) {
EnsureProvisioned();
GenerateKeyRequest(binary_key_id(), kCencMimeType);
VerifyNewKeyResponse(config_.license_server(), config_.client_auth());
@@ -423,6 +422,7 @@ TEST_F(WvCdmEngineTest, LicenseRenewalSpecifiedServer) {
// This test generates a renewal and then requests it from the server specified
// by the current test configuration.
TEST_F(WvCdmEngineTest, LicenseRenewal) {
EnsureProvisioned();
GenerateKeyRequest(binary_key_id(), kCencMimeType);
VerifyNewKeyResponse(config_.license_server(), config_.client_auth());

View File

@@ -18,7 +18,7 @@
#include "clock.h"
#include "config_test_env.h"
#include "initialization_data.h"
#include "license_request.h"
#include "license_holder.h"
#include "log.h"
#include "metrics_collections.h"
#include "odk_structs.h"
@@ -36,8 +36,6 @@ namespace wvcdm {
namespace {
constexpr int kHttpOk = 200;
const std::string kCencMimeType = "cenc";
// How many seconds to fudge the start or stop playback times. We fudge these
// times because there might be a little round off when sleeping, and because
// the time that the license was signed or loaded might be off a little bit due
@@ -86,27 +84,6 @@ const RenewalPolicy kHeartbeatRenewal = {"CDM_Heartbeat_renewal", 10, 30};
// Key ID in all duration tests.
const KeyId kKeyId = "Duration_Key====";
class SimpleEventListener : public wvcdm::WvCdmEventListener {
public:
SimpleEventListener() { renewal_needed_ = false; }
// We will want to know when a renewal is needed.
void OnSessionRenewalNeeded(const CdmSessionId& /*session*/) override {
renewal_needed_ = true;
}
void OnSessionKeysChange(const CdmSessionId&, const CdmKeyStatusMap&,
bool) override {}
void OnExpirationUpdate(const CdmSessionId&,
int64_t expiry_time UNUSED) override {}
bool renewal_needed() { return renewal_needed_; }
void set_renewal_needed(bool renewal_needed) {
renewal_needed_ = renewal_needed;
}
private:
bool renewal_needed_;
};
} // namespace
// All duration tests are parameterized by can_persist = true or false.
@@ -114,7 +91,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
public ::testing::WithParamInterface<bool> {
public:
CdmDurationTest(const std::string& content_id)
: content_id_(content_id),
: license_holder_(content_id, &cdm_engine_, config_),
first_load_occurred_(false),
allow_lenience_(false) {
// These are reasonable initial values for most tests. This is an unlimited
@@ -150,24 +127,22 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
void SetUp() override {
WvCdmTestBase::SetUp();
EnsureProvisioned();
ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id_));
can_persist_ = GetParam();
if (can_persist_) {
license_holder_.set_can_persist(GetParam());
ASSERT_NO_FATAL_FAILURE(license_holder_.OpenSession());
if (license_holder_.can_persist()) {
// Each content/policy in a test class below should match two policies in
// UAT. One policy id matches the string exactly, and one has
// _can_persist appended.
content_id_ = content_id_ + "_can_persist";
license_type_ = kLicenseTypeOffline;
} else {
license_type_ = kLicenseTypeStreaming;
license_holder_.set_content_id(license_holder_.content_id() +
"_can_persist");
}
// All times in the license are relative to the rental clock.
start_of_rental_clock_ = wvutil::Clock().GetCurrentTime();
FetchLicense();
license_holder_.FetchLicense();
}
void TearDown() override {
cdm_engine_.CloseSession(session_id_);
license_holder_.CloseSession();
// Log the time used in this test suite. When this comment was written,
// these tests took over three hours. If we want to improve that, we need to
// track these times.
@@ -223,99 +198,20 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
return now - start_of_rental_clock_;
}
void OpenSession(CdmSessionId* session_id) {
CdmResponseType status = cdm_engine_.OpenSession(
config_.key_system(), nullptr, &event_listener_, session_id);
ASSERT_EQ(NO_ERROR, status);
ASSERT_TRUE(cdm_engine_.IsOpenSession(*session_id));
}
void CloseSession(const CdmSessionId& session_id) {
CdmResponseType status = cdm_engine_.CloseSession(session_id);
ASSERT_EQ(NO_ERROR, status);
ASSERT_FALSE(cdm_engine_.IsOpenSession(session_id));
}
void FetchLicense() {
video_widevine::WidevinePsshData pssh;
pssh.set_content_id(content_id_);
const std::string init_data_string = MakePSSH(pssh);
const InitializationData init_data(kCencMimeType, init_data_string);
init_data.DumpToLogs();
CdmKeyRequest key_request;
ASSERT_NO_FATAL_FAILURE(GenerateKeyRequest(init_data, &key_request));
ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request));
}
void GenerateKeyRequest(const InitializationData& init_data,
CdmKeyRequest* key_request) {
CdmAppParameterMap empty_app_parameters;
CdmKeySetId empty_key_set_id;
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
session_id_, empty_key_set_id, init_data, license_type_,
empty_app_parameters, key_request);
ASSERT_EQ(KEY_MESSAGE, result);
ASSERT_EQ(kKeyRequestTypeInitial, key_request->type);
}
// Append the content/policy id for the test to the URL.
std::string MakeUrl(const std::string& server_url,
const std::string& policy_id) {
// For these tests, we want to specify the policy, but the UAT server only
// allows us to set the content id as the video_id. So each policy is
// matched to a single license with the same name. The local license
// server, on the other hand, wants to see the policy id in the url. So we
// have to guess which format to use based on the name of the server.
const std::string path = server_url + config_.client_auth();
std::string video_query;
if (path.find("proxy.uat") != std::string::npos) {
// This is uat or uat-nightly. Set the video_id.
video_query = "video_id=" + policy_id;
} else {
// This is probably a local license server. Set the policy.
video_query = "policy=" + policy_id;
}
// If there is already a parameter, then we don't need to add another
// question mark.
return path + ((path.find("?") == std::string::npos) ? "?" : "&") +
video_query;
}
void GetKeyResponse(const CdmKeyRequest& key_request) {
// The content id matches the policy id used on UAT.
const std::string url = MakeUrl(config_.license_server(), content_id_);
UrlRequest url_request(url);
ASSERT_TRUE(url_request.is_connected());
std::string http_response;
url_request.PostRequest(key_request.message);
ASSERT_TRUE(url_request.GetResponse(&http_response));
int status_code = url_request.GetStatusCode(http_response);
ASSERT_EQ(kHttpOk, status_code) << "Error with url = " << url;
LicenseRequest license_request;
license_request.GetDrmMessage(http_response, key_response_);
}
// This ensures that the licenses are loaded into the sessions.
void LoadLicense() {
if (!first_load_occurred_) {
CdmLicenseType license_type;
ASSERT_EQ(KEY_ADDED, cdm_engine_.AddKey(session_id_, key_response_,
&license_type, &key_set_id_));
ASSERT_EQ(license_type_, license_type);
license_holder_.LoadLicense();
first_load_occurred_ = true;
} else if (can_persist_) {
// For the persistent license, we use restore key.
ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id_));
CdmResponseType status = cdm_engine_.RestoreKey(session_id_, key_set_id_);
ASSERT_EQ(KEY_ADDED, status);
} else if (license_holder_.can_persist()) {
ASSERT_NO_FATAL_FAILURE(license_holder_.OpenSession());
license_holder_.ReloadLicense();
}
}
void UnloadLicense() {
if (can_persist_) {
CloseSession(session_id_);
if (license_holder_.can_persist()) {
license_holder_.CloseSession();
}
}
@@ -380,12 +276,8 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
void AllowLenience() { allow_lenience_ = true; }
void Decrypt() {
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
std::vector<uint8_t> output(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
const uint64_t now = CurrentRentalTime();
EXPECT_EQ(NO_ERROR, Decrypt(session_id_, kKeyId, input, iv, &output))
EXPECT_EQ(NO_ERROR, license_holder_.Decrypt(kKeyId))
<< "Failed to decrypt when rental clock = " << now
<< ", and playback clock = "
<< ((now < start_of_playback_) ? 0 : (now - start_of_playback_));
@@ -403,17 +295,12 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
const bool low_end_device =
(wvoec::global_features.api_version < wvoec::kCoreMessagesAPI ||
!wvoec::global_features.usage_table);
if (allow_lenience_ && low_end_device && can_persist_) {
if (allow_lenience_ && low_end_device && license_holder_.can_persist()) {
allow_success = true;
}
allow_lenience_ = false; // Only allow lenience once per test.
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
std::vector<uint8_t> output(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
const uint64_t now = CurrentRentalTime();
CdmResponseType status = Decrypt(session_id_, kKeyId, input, iv, &output);
CdmResponseType status = license_holder_.Decrypt(kKeyId);
// We always allow failure. that's what we usually expect.
if (status == NEED_KEY) return;
// No other error code is allowed: either NO_ERROR or NEED_KEY.
@@ -428,26 +315,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
<< (now < start_of_playback_ ? 0 : now - start_of_playback_);
}
CdmResponseType Decrypt(const CdmSessionId& session_id, const KeyId& key_id,
const std::vector<uint8_t>& input,
const std::vector<uint8_t>& iv,
std::vector<uint8_t>* output) {
CdmDecryptionParametersV16 params(key_id);
params.is_secure = false;
CdmDecryptionSample sample(input.data(), output->data(), 0, input.size(),
iv);
CdmDecryptionSubsample subsample(0, input.size());
sample.subsamples.push_back(subsample);
params.samples.push_back(sample);
return cdm_engine_.DecryptV16(session_id, params);
}
CdmSessionId session_id_;
std::string content_id_;
CdmLicenseType license_type_;
CdmKeySetId key_set_id_;
std::string key_response_;
LicenseHolder license_holder_;
// Time license requests generated. All test times are relative to this value.
uint64_t start_of_rental_clock_;
// The start of playback. This is set to the planned start at the beginning of
@@ -457,9 +325,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
// not have to reload the streaming license, and we should use RestoreKey
// instead of AddKey for the offline license.
bool first_load_occurred_;
bool can_persist_;
ODK_TimerLimits timer_limits_;
SimpleEventListener event_listener_;
// If this is set, then the next time we expect a playback to be terminated,
// we will allow lenient failure.
bool allow_lenience_;
@@ -751,7 +617,8 @@ TEST_P(CdmUseCase_SevenHardTwoSoft, Case4) {
LoadAndAllowPlayback(start_of_playback_,
start_of_playback_ + 4 * kPlayDuration);
UnloadLicense();
if (!can_persist_) return; // streaming license cannot restart.
// streaming license cannot restart.
if (!license_holder_.can_persist()) return;
AllowLenience();
ForbidPlayback(EndOfPlaybackWindow() + kFudge);
}
@@ -947,7 +814,8 @@ TEST_P(CdmUseCase_SevenSoftTwoSoft, Case4) {
LoadAndAllowPlayback(start_of_playback_,
start_of_playback_ + 4 * kPlayDuration);
UnloadLicense();
if (!can_persist_) return; // streaming license cannot restart.
// streaming license cannot restart.
if (!license_holder_.can_persist()) return;
AllowLenience();
ForbidPlayback(EndOfPlaybackWindow() + kFudge);
}
@@ -989,7 +857,7 @@ TEST_P(CdmUseCase_SevenSoftTwoSoft, Case7) {
EndOfPlaybackWindow() + kPlayDuration);
UnloadLicense();
// But forbid restart after playback window.
if (!can_persist_) return; // streaming license cannot restart.
if (!license_holder_.can_persist()) return;
ForbidPlayback(EndOfPlaybackWindow() + 2 * kPlayDuration);
}
@@ -1028,7 +896,7 @@ class RenewalTest : public CdmDurationTest {
// Sleep until a renewal request event is generated. If one does not occur
// before cutoff, an error happens.
void SleepUntilRenewalNeeded(uint64_t cutoff) {
while (!event_listener_.renewal_needed()) {
while (!license_holder_.event_listener().renewal_needed()) {
uint64_t now = CurrentRentalTime();
ASSERT_LT(now, cutoff);
SleepUntil(now + 1);
@@ -1042,34 +910,18 @@ class RenewalTest : public CdmDurationTest {
}
void RequestRenewal(const RenewalPolicy& renewal_policy) {
event_listener_.set_renewal_needed(false);
CdmKeyRequest request;
const CdmResponseType result =
cdm_engine_.GenerateRenewalRequest(session_id_, &request);
ASSERT_EQ(KEY_MESSAGE, result);
const std::string url =
MakeUrl(config_.renewal_server(), renewal_policy.policy_id);
renewal_in_flight_.reset(new UrlRequest(url));
ASSERT_TRUE(renewal_in_flight_->is_connected());
renewal_in_flight_->PostRequest(request.message);
license_holder_.GenerateAndPostRenewalRequest(renewal_policy.policy_id);
}
void LoadRenewal(uint64_t time_of_load, const RenewalPolicy& renewal_policy) {
ASSERT_NE(renewal_in_flight_, nullptr);
std::string http_response;
// Most of the network latency will probably show up in the next few
// commands. I think the tests have enough slop to account for reasonable
// latency with the current value of kRoundTripTime. But we'll know I made a
// mistake if we see errors about "Test Clock skew..." in the SleepUntil
// call below.
ASSERT_TRUE(renewal_in_flight_->GetResponse(&http_response));
int status_code = renewal_in_flight_->GetStatusCode(http_response);
ASSERT_EQ(kHttpOk, status_code);
license_holder_.FetchRenewal();
SleepUntil(time_of_load);
LicenseRequest license_request;
license_request.GetDrmMessage(http_response, renewal_message_);
EXPECT_EQ(KEY_ADDED, cdm_engine_.RenewKey(session_id_, renewal_message_));
license_holder_.LoadRenewal();
ComputeCutoff(time_of_load, renewal_policy);
}
@@ -1260,7 +1112,7 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case4) {
RenewAndContinue(start, load_time, stop, kShortRenewal);
}
UnloadLicense();
if (can_persist_) {
if (license_holder_.can_persist()) {
// Reload license after timer expired.
const uint64_t reload_time = current_cutoff_ + 20;
ReloadAndAllowPlayback(
@@ -1464,7 +1316,7 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case4) {
RenewAndContinue(start, load_time, stop, kShortRenewal);
}
UnloadLicense();
if (can_persist_) {
if (license_holder_.can_persist()) {
// Reload license after timer expired.
const uint64_t reload_time = current_cutoff_ + 20;
ReloadAndAllowPlayback(

View File

@@ -0,0 +1,227 @@
// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "license_holder.h"
#include "license_request.h"
#include "oec_device_features.h"
#include "test_base.h"
namespace wvcdm {
namespace {
constexpr int kHttpOk = 200;
const std::string kCencMimeType = "cenc";
} // namespace
LicenseHolder::~LicenseHolder() {}
void LicenseHolder::OpenSession() {
CdmResponseType status = cdm_engine_->OpenSession(
config_.key_system(), nullptr, &event_listener_, &session_id_);
ASSERT_EQ(NO_ERROR, status);
ASSERT_TRUE(cdm_engine_->IsOpenSession(session_id_));
}
void LicenseHolder::CloseSession() {
CdmResponseType status = cdm_engine_->CloseSession(session_id_);
ASSERT_EQ(NO_ERROR, status);
ASSERT_FALSE(cdm_engine_->IsOpenSession(session_id_));
}
void LicenseHolder::FetchLicense() {
video_widevine::WidevinePsshData pssh;
pssh.set_content_id(content_id_);
const std::string init_data_string = MakePSSH(pssh);
const InitializationData init_data(kCencMimeType, init_data_string);
init_data.DumpToLogs();
CdmKeyRequest key_request;
ASSERT_NO_FATAL_FAILURE(GenerateKeyRequest(init_data, &key_request))
<< "Failed for " << content_id();
ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request))
<< "Failed for " << content_id();
}
void LicenseHolder::LoadLicense() {
CdmLicenseType license_type;
ASSERT_EQ(KEY_ADDED, cdm_engine_->AddKey(session_id_, key_response_,
&license_type, &key_set_id_))
<< "Failed to load license for " << content_id();
if (can_persist_) {
ASSERT_EQ(license_type, kLicenseTypeOffline)
<< "Failed for " << content_id();
} else {
ASSERT_EQ(license_type, kLicenseTypeStreaming)
<< "Failed for " << content_id();
}
}
void LicenseHolder::ReloadLicense() {
CdmResponseType status = cdm_engine_->RestoreKey(session_id_, key_set_id_);
ASSERT_EQ(KEY_ADDED, status)
<< "Failed to reload license for " << content_id();
}
void LicenseHolder::GenerateAndPostRenewalRequest(
const std::string& policy_id) {
event_listener_.set_renewal_needed(false);
CdmKeyRequest request;
const CdmResponseType result =
cdm_engine_->GenerateRenewalRequest(session_id_, &request);
ASSERT_EQ(KEY_MESSAGE, result) << "Failed for " << content_id();
const std::string url = MakeUrl(config_.renewal_server(), policy_id);
renewal_in_flight_.reset(new UrlRequest(url));
ASSERT_TRUE(renewal_in_flight_->is_connected())
<< "Failed for " << content_id();
renewal_in_flight_->PostRequest(request.message);
}
void LicenseHolder::FetchRenewal() {
ASSERT_NE(renewal_in_flight_, nullptr) << "Failed for " << content_id();
ASSERT_TRUE(renewal_in_flight_->GetResponse(&renewal_response_))
<< "Failed for " << content_id();
int status_code = renewal_in_flight_->GetStatusCode(renewal_response_);
ASSERT_EQ(kHttpOk, status_code) << "Failed for " << content_id();
}
void LicenseHolder::LoadRenewal() {
LicenseRequest license_request;
license_request.GetDrmMessage(renewal_response_, renewal_message_);
EXPECT_EQ(KEY_ADDED, cdm_engine_->RenewKey(session_id_, renewal_message_))
<< "Failed for " << content_id();
}
void LicenseHolder::RemoveLicense() {
EXPECT_EQ(NO_ERROR, cdm_engine_->RemoveLicense(session_id_))
<< "Failed for " << content_id();
}
CdmResponseType LicenseHolder::Decrypt(const std::string& key_id) {
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
std::vector<uint8_t> output(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
CdmDecryptionParametersV16 params(key_id);
params.is_secure = false;
CdmDecryptionSample sample(input.data(), output.data(), 0, input.size(), iv);
CdmDecryptionSubsample subsample(0, input.size());
sample.subsamples.push_back(subsample);
params.samples.push_back(sample);
return cdm_engine_->DecryptV16(session_id_, params);
}
void LicenseHolder::DecryptSecure(const KeyId& key_id) {
ASSERT_TRUE(wvoec::global_features.test_secure_buffers);
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
// To create a secure buffer, we need to know the OEMCrypto session id.
CdmQueryMap query_map;
cdm_engine_->QueryOemCryptoSessionId(session_id_, &query_map);
const std::string oec_session_id_string =
query_map[QUERY_KEY_OEMCRYPTO_SESSION_ID];
uint32_t oec_session_id = std::stoi(oec_session_id_string);
int secure_buffer_fid;
OEMCrypto_DestBufferDesc output_descriptor;
output_descriptor.type = OEMCrypto_BufferType_Secure;
output_descriptor.buffer.secure.secure_buffer_length = buffer_size;
ASSERT_EQ(
OEMCrypto_AllocateSecureBuffer(oec_session_id, buffer_size,
&output_descriptor, &secure_buffer_fid),
OEMCrypto_SUCCESS);
ASSERT_NE(output_descriptor.buffer.secure.secure_buffer, nullptr);
// It is OK if OEMCrypto changes the maximum size, but there must
// still be enough room for our data.
ASSERT_GE(output_descriptor.buffer.secure.secure_buffer_length, buffer_size);
output_descriptor.buffer.secure.offset = 0;
// Now create a sample array for the CDM layer.
CdmDecryptionParametersV16 params(key_id);
params.is_secure = true;
CdmDecryptionSample sample(input.data(),
output_descriptor.buffer.secure.secure_buffer, 0,
input.size(), iv);
CdmDecryptionSubsample subsample(0, input.size());
sample.subsamples.push_back(subsample);
params.samples.push_back(sample);
CdmResponseType status = cdm_engine_->DecryptV16(session_id_, params);
// Free the secure buffer before we check the return status.
EXPECT_EQ(OEMCrypto_FreeSecureBuffer(oec_session_id, &output_descriptor,
secure_buffer_fid),
OEMCrypto_SUCCESS);
ASSERT_EQ(status, NO_ERROR);
}
void LicenseHolder::FailDecrypt(const KeyId& key_id,
CdmResponseType expected_status) {
CdmResponseType status = Decrypt(key_id);
// If the server knows we cannot handle the key, it would not have given us
// the key. In that case, the status should indicate no key.
if (status != NEED_KEY) {
// Otherwise, we should have gotten the expected error.
ASSERT_EQ(expected_status, status);
}
}
void LicenseHolder::GenerateKeyRequest(const InitializationData& init_data,
CdmKeyRequest* key_request) {
CdmAppParameterMap empty_app_parameters;
CdmKeySetId empty_key_set_id;
CdmLicenseType license_type =
can_persist_ ? kLicenseTypeOffline : kLicenseTypeStreaming;
CdmResponseType result = cdm_engine_->GenerateKeyRequest(
session_id_, empty_key_set_id, init_data, license_type,
empty_app_parameters, key_request);
ASSERT_EQ(KEY_MESSAGE, result);
ASSERT_EQ(kKeyRequestTypeInitial, key_request->type);
}
std::string LicenseHolder::MakeUrl(const std::string& server_url,
const std::string& policy_id) {
// For tests, we want to specify the policy, but the UAT server only allows us
// to set the content id as the video_id. So each policy is matched to a
// single license with the same name. The local license server, on the other
// hand, wants to see the policy id in the url. So we have to guess which
// format to use based on the name of the server.
const std::string path = server_url + config_.client_auth();
std::string video_query;
if (!policy_id.empty()) {
if (path.find("proxy.uat") != std::string::npos) {
// This is uat or uat-nightly. Set the video_id.
video_query = "video_id=" + policy_id;
} else {
// This is probably a local license server. Set the policy.
video_query = "policy=" + policy_id;
}
// If there is already a parameter, then we don't need to add another
// question mark.
return path + ((path.find("?") == std::string::npos) ? "?" : "&") +
video_query;
} else {
return path;
}
}
void LicenseHolder::GetKeyResponse(const CdmKeyRequest& key_request) {
// The content id matches the policy id used on UAT.
const std::string url = MakeUrl(config_.license_server(), "");
UrlRequest url_request(url);
ASSERT_TRUE(url_request.is_connected());
std::string http_response;
url_request.PostRequest(key_request.message);
ASSERT_TRUE(url_request.GetResponse(&http_response));
int status_code = url_request.GetStatusCode(http_response);
ASSERT_EQ(kHttpOk, status_code) << "Error with url = " << url << "\n"
<< "content_id = " << content_id() << "\n"
<< "response = " << http_response;
LicenseRequest license_request;
license_request.GetDrmMessage(http_response, key_response_);
}
} // namespace wvcdm

View File

@@ -0,0 +1,133 @@
// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#ifndef WVCDM_CORE_TEST_LICENSE_HOLDER_H_
#define WVCDM_CORE_TEST_LICENSE_HOLDER_H_
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include "cdm_engine.h"
#include "config_test_env.h"
#include "url_request.h"
#include "wv_attributes.h"
#include "wv_cdm_event_listener.h"
#include "log.h"
namespace wvcdm {
// An event listener to tell when a renewal is needed.
class SimpleEventListener : public wvcdm::WvCdmEventListener {
public:
SimpleEventListener() { renewal_needed_ = false; }
// We will want to know when a renewal is needed.
void OnSessionRenewalNeeded(const CdmSessionId& /*session*/) override {
renewal_needed_ = true;
}
void OnSessionKeysChange(const CdmSessionId&, const CdmKeyStatusMap&,
bool) override {}
void OnExpirationUpdate(const CdmSessionId&,
int64_t expiry_time UNUSED) override {}
bool renewal_needed() { return renewal_needed_; }
void set_renewal_needed(bool renewal_needed) {
renewal_needed_ = renewal_needed;
}
private:
bool renewal_needed_ = false;
};
class LicenseHolder {
public:
// Set up a new license holder with the specified content id.
LicenseHolder(const std::string& content_id, CdmEngine* cdm_engine,
const ConfigTestEnv& config)
: content_id_(content_id), cdm_engine_(cdm_engine), config_(config) {}
~LicenseHolder();
// Open a session for the license. Must be called before FetchLicense or
// ReloadLicense.
void OpenSession();
// Close the session and release resources.
void CloseSession();
// Generate a license request, send it to the license server, and wait for the
// response. The response is *not* loaded into the cdm engine.
void FetchLicense();
// Load the license response into the CDM engine. A call to FetchLicense()
// must be made first.
void LoadLicense();
// Reload the license. Call OpenSession() before calling
// ReloadLicense(). Also, the key_set_id must have been set previously. The
// key_set_id is set by calling LoadLicense(), or by calling set_key_set_id().
void ReloadLicense();
// Generate the renewal request, and send it to the server.
void GenerateAndPostRenewalRequest(const std::string& policy_id);
// Fetch the renewal response. This can add a few seconds of latency.
void FetchRenewal();
// Load the renewal response that was fetched in FetchRenewal().
void LoadRenewal();
// Releases the license and frees up entry in usage table.
void RemoveLicense();
// Try to decrypt some random data. This does not verify that the data is
// decrypted correctly. Returns the result of the decrypt operation.
CdmResponseType Decrypt(const std::string& key_id);
// Try to decrypt some random data to a secure buffer. If the test harness
// does not allow creating a secure buffer, then this function fails
// immediately. Otherwise, a secure buffer is created and used for a
// decryption operation.
void DecryptSecure(const KeyId& key_id);
// Try to decrypt some random data, but expect failure. The failure may
// be either the expected_status, or NEED_KEY. We allow NEED_KEY in case
// the server recognized that we cannot support the given key.
void FailDecrypt(const KeyId& key_id, CdmResponseType expected_status);
const std::string& content_id() const { return content_id_; }
void set_content_id(const std::string& content_id) {
content_id_ = content_id;
}
// The session id. This is only valid after a call to OpenSession.
const std::string& session_id() { return session_id_; }
// Returns true if the license is offline.
bool can_persist() const { return can_persist_; }
// Sets whether the license is offline or not.
void set_can_persist(bool can_persist) { can_persist_ = can_persist; }
uint64_t start_of_rental_clock() const { return start_of_rental_clock_; }
const std::string& key_set_id() const { return key_set_id_; }
void set_key_set_id(const std::string& key_set_id) {
key_set_id_ = key_set_id;
}
SimpleEventListener& event_listener() { return event_listener_; }
private:
std::string content_id_;
CdmSessionId session_id_;
CdmKeySetId key_set_id_;
std::string key_response_;
bool can_persist_ = false;
uint64_t start_of_rental_clock_ = 0u;
CdmEngine* cdm_engine_ = nullptr;
const ConfigTestEnv& config_;
SimpleEventListener event_listener_;
std::unique_ptr<UrlRequest> renewal_in_flight_;
std::string renewal_message_;
std::string renewal_response_;
// Generate the license request.
void GenerateKeyRequest(const InitializationData& init_data,
CdmKeyRequest* key_request);
// Generate a URL for the specified policy. The license request should be sent
// to this url.
std::string MakeUrl(const std::string& server_url,
const std::string& policy_id);
// Fetch the key response from the server.
void GetKeyResponse(const CdmKeyRequest& key_request);
};
} // namespace wvcdm
#endif // WVCDM_CORE_TEST_LICENSE_HOLDER_H_

View File

@@ -17,6 +17,7 @@ LOCAL_SRC_FILES := \
../core/test/config_test_env.cpp \
../core/test/fake_provisioning_server.cpp \
../core/test/http_socket.cpp \
../core/test/license_holder.cpp \
../core/test/license_request.cpp \
../core/test/test_base.cpp \
../core/test/test_printers.cpp \