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:
@@ -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());
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
227
libwvdrmengine/cdm/core/test/license_holder.cpp
Normal file
227
libwvdrmengine/cdm/core/test/license_holder.cpp
Normal 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
|
||||
133
libwvdrmengine/cdm/core/test/license_holder.h
Normal file
133
libwvdrmengine/cdm/core/test/license_holder.h
Normal 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_
|
||||
@@ -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 \
|
||||
|
||||
Reference in New Issue
Block a user